From 0363ee6d16a87f6997eb0add12c6162dcb3da5ba Mon Sep 17 00:00:00 2001 From: Lisa Magdalena Riedler Date: Sun, 6 Oct 2024 11:26:57 +0200 Subject: [PATCH 01/16] fix various AudioFileProcessor bugs (#7533) * fix out-of-bounds crash in AudioFileProcessor by correctly setting m_from and m_to without them interfering with each other * fixed flattened wave caused by inaccurate math see PR for before/after * simply stop drawing AFP waveform when there's no more data this fixes the single point at the end of waveforms that sometimes shows up * fixed seemingly insignificant type confusion (?) execution seemed fine but my debugger started freaking out, and if gdb is telling me I got a negative-sized vector, I'd rather fix this issue than speculate "it's probably fine" * fixed data offset for AFP waveform vis the data itself isn't reversed, so we have to account for that --- .../AudioFileProcessor/AudioFileProcessorWaveView.cpp | 8 ++++++-- src/gui/SampleWaveform.cpp | 11 ++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp index 7c5f9387eec..b41af33e016 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp @@ -338,10 +338,12 @@ void AudioFileProcessorWaveView::updateGraph() m_graph.fill(Qt::transparent); QPainter p(&m_graph); p.setPen(QColor(255, 255, 255)); + + const auto dataOffset = m_reversed ? m_sample->sampleSize() - m_to : m_from; const auto rect = QRect{0, 0, m_graph.width(), m_graph.height()}; const auto waveform = SampleWaveform::Parameters{ - m_sample->data() + m_from, static_cast(range()), m_sample->amplification(), m_sample->reversed()}; + m_sample->data() + dataOffset, static_cast(range()), m_sample->amplification(), m_sample->reversed()}; SampleWaveform::visualize(waveform, p, rect); } @@ -467,9 +469,11 @@ void AudioFileProcessorWaveView::reverse() - m_sample->endFrame() - m_sample->startFrame() ); + + const int fromTmp = m_from; setFrom(m_sample->sampleSize() - m_to); - setTo(m_sample->sampleSize() - m_from); + setTo(m_sample->sampleSize() - fromTmp); m_reversed = ! m_reversed; } diff --git a/src/gui/SampleWaveform.cpp b/src/gui/SampleWaveform.cpp index ca356e72713..165ede4ee89 100644 --- a/src/gui/SampleWaveform.cpp +++ b/src/gui/SampleWaveform.cpp @@ -44,12 +44,12 @@ void SampleWaveform::visualize(Parameters parameters, QPainter& painter, const Q const float resolution = std::max(1.0f, framesPerPixel / maxFramesPerPixel); const float framesPerResolution = framesPerPixel / resolution; - const size_t numPixels = std::min(parameters.size, width); + size_t numPixels = std::min(parameters.size, static_cast(width)); auto min = std::vector(numPixels, 1); auto max = std::vector(numPixels, -1); auto squared = std::vector(numPixels, 0); - const size_t maxFrames = numPixels * static_cast(framesPerPixel); + const size_t maxFrames = static_cast(numPixels * framesPerPixel); auto pixelIndex = std::size_t{0}; @@ -67,12 +67,9 @@ void SampleWaveform::visualize(Parameters parameters, QPainter& painter, const Q squared[pixelIndex] += value * value; } - while (pixelIndex < numPixels) + if (pixelIndex < numPixels) { - max[pixelIndex] = 0.0; - min[pixelIndex] = 0.0; - - pixelIndex++; + numPixels = pixelIndex; } for (auto i = std::size_t{0}; i < numPixels; i++) From 7dbc80926d91486ba0873ff576ccd0f821fc7f07 Mon Sep 17 00:00:00 2001 From: regulus79 <117475203+regulus79@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:27:39 -0400 Subject: [PATCH 02/16] Adding proportional scrolling (#7476) Add proportional scrolling to the song editor, piano roll and automation editor. Proportional scrolling means that if for example a certain measure is on the right side of the song editor then it will take a certain number of mouse wheel moves to get it to the left side of the editor. It is the same number of wheel moves regardless of the zoom level. --- include/AutomationEditor.h | 2 ++ include/PianoRoll.h | 2 ++ include/SongEditor.h | 2 ++ src/gui/editors/AutomationEditor.cpp | 12 +++++++----- src/gui/editors/PianoRoll.cpp | 12 ++++++++---- src/gui/editors/SongEditor.cpp | 12 ++++++++---- 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index 1110e8e4c9e..15f6dd22ec5 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -241,6 +241,8 @@ protected slots: QScrollBar * m_leftRightScroll; QScrollBar * m_topBottomScroll; + void adjustLeftRightScoll(int value); + TimePos m_currentPosition; Action m_action; diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 35550a5b32f..a85e50a163b 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -374,6 +374,8 @@ protected slots: QScrollBar * m_leftRightScroll; QScrollBar * m_topBottomScroll; + void adjustLeftRightScoll(int value); + TimePos m_currentPosition; bool m_recording; bool m_doAutoQuantization{false}; diff --git a/include/SongEditor.h b/include/SongEditor.h index 98a9096fb26..1f719623a1e 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -128,6 +128,8 @@ private slots: QScrollBar * m_leftRightScroll; + void adjustLeftRightScoll(int value); + LcdSpinBox * m_tempoSpinBox; TimeLineWidget * m_timeLine; diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index df67398827e..3dc0285d282 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -1560,7 +1560,11 @@ void AutomationEditor::resizeEvent(QResizeEvent * re) update(); } - +void AutomationEditor::adjustLeftRightScoll(int value) +{ + m_leftRightScroll->setValue(m_leftRightScroll->value() - + value * 0.3f / m_zoomXLevels[m_zoomingXModel.value()]); +} // TODO: Move this method up so it's closer to the other mouse events @@ -1625,13 +1629,11 @@ void AutomationEditor::wheelEvent(QWheelEvent * we ) // FIXME: Reconsider if determining orientation is necessary in Qt6. else if(abs(we->angleDelta().x()) > abs(we->angleDelta().y())) // scrolling is horizontal { - m_leftRightScroll->setValue(m_leftRightScroll->value() - - we->angleDelta().x() * 2 / 15); + adjustLeftRightScoll(we->angleDelta().x()); } else if(we->modifiers() & Qt::ShiftModifier) { - m_leftRightScroll->setValue(m_leftRightScroll->value() - - we->angleDelta().y() * 2 / 15); + adjustLeftRightScoll(we->angleDelta().y()); } else { diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index f8472b6888c..2193456e459 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -3743,6 +3743,12 @@ void PianoRoll::resizeEvent(QResizeEvent* re) } +void PianoRoll::adjustLeftRightScoll(int value) +{ + m_leftRightScroll->setValue(m_leftRightScroll->value() - + value * 0.3f / m_zoomLevels[m_zoomingModel.value()]); +} + void PianoRoll::wheelEvent(QWheelEvent * we ) @@ -3875,13 +3881,11 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) // FIXME: Reconsider if determining orientation is necessary in Qt6. else if(abs(we->angleDelta().x()) > abs(we->angleDelta().y())) // scrolling is horizontal { - m_leftRightScroll->setValue(m_leftRightScroll->value() - - we->angleDelta().x() * 2 / 15); + adjustLeftRightScoll(we->angleDelta().x()); } else if(we->modifiers() & Qt::ShiftModifier) { - m_leftRightScroll->setValue(m_leftRightScroll->value() - - we->angleDelta().y() * 2 / 15); + adjustLeftRightScoll(we->angleDelta().y()); } else { diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index f6e9fc83bf8..72ee28bc82a 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -516,6 +516,12 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) +void SongEditor::adjustLeftRightScoll(int value) +{ + m_leftRightScroll->setValue(m_leftRightScroll->value() + - value * DEFAULT_PIXELS_PER_BAR / pixelsPerBar()); +} + void SongEditor::wheelEvent( QWheelEvent * we ) { @@ -544,13 +550,11 @@ void SongEditor::wheelEvent( QWheelEvent * we ) // FIXME: Reconsider if determining orientation is necessary in Qt6. else if (std::abs(we->angleDelta().x()) > std::abs(we->angleDelta().y())) // scrolling is horizontal { - m_leftRightScroll->setValue(m_leftRightScroll->value() - - we->angleDelta().x()); + adjustLeftRightScoll(we->angleDelta().x()); } else if (we->modifiers() & Qt::ShiftModifier) { - m_leftRightScroll->setValue(m_leftRightScroll->value() - - we->angleDelta().y()); + adjustLeftRightScoll(we->angleDelta().y()); } else { From 639e122efe61f27412f251b19b460a2bd761588f Mon Sep 17 00:00:00 2001 From: firewall1110 <57725851+firewall1110@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:15:07 +0300 Subject: [PATCH 03/16] Fix empty editor windows (#7515) Fix the problem with empty windows as described in issue #7412. The `refocus` method in `MainWindow` is made public so that it can be called from `Editor::closeEvent`. It has also been refactored for better readability. --------- Co-authored-by: Michael Gregorius Co-authored-by: Dalton Messmer --- include/Editor.h | 2 +- include/MainWindow.h | 3 ++- src/gui/MainWindow.cpp | 29 ++++++++++++----------------- src/gui/editors/Editor.cpp | 7 +++++-- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/include/Editor.h b/include/Editor.h index 681bce46ccc..a5b667166fb 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -56,7 +56,7 @@ class Editor : public QMainWindow DropToolBar * addDropToolBar(Qt::ToolBarArea whereToAdd, QString const & windowTitle); DropToolBar * addDropToolBar(QWidget * parent, Qt::ToolBarArea whereToAdd, QString const & windowTitle); - void closeEvent( QCloseEvent * _ce ) override; + void closeEvent(QCloseEvent * event) override; protected slots: virtual void play() {} virtual void record() {} diff --git a/include/MainWindow.h b/include/MainWindow.h index 4442a7ac252..6c140a1e61d 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -72,6 +72,8 @@ class MainWindow : public QMainWindow LMMS_EXPORT SubWindow* addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags = QFlag(0)); + void refocus(); + /// /// \brief Asks whether changes made to the project are to be saved. /// @@ -195,7 +197,6 @@ private slots: void finalize(); void toggleWindow( QWidget *window, bool forceShow = false ); - void refocus(); void exportProject(bool multiExport = false); void handleSaveResult(QString const & filename, bool songSavedSuccessfully); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index fa0c509569f..c45ea14aca1 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -975,26 +975,21 @@ void MainWindow::toggleFullscreen() */ void MainWindow::refocus() { - QList editors; - editors - << getGUI()->songEditor()->parentWidget() - << getGUI()->patternEditor()->parentWidget() - << getGUI()->pianoRoll()->parentWidget() - << getGUI()->automationEditor()->parentWidget(); - - bool found = false; - QList::Iterator editor; - for( editor = editors.begin(); editor != editors.end(); ++editor ) - { - if( ! (*editor)->isHidden() ) { - (*editor)->setFocus(); - found = true; - break; + const auto gui = getGUI(); + + // Attempt to set the focus on the first of these editors that is not hidden... + for (auto editorParent : { gui->songEditor()->parentWidget(), gui->patternEditor()->parentWidget(), + gui->pianoRoll()->parentWidget(), gui->automationEditor()->parentWidget() }) + { + if (!editorParent->isHidden()) + { + editorParent->setFocus(); + return; } } - if( ! found ) - this->setFocus(); + // ... otherwise set the focus on the main window. + this->setFocus(); } diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index a61c2cd6081..ab12e3fb90b 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -24,6 +24,8 @@ #include "Editor.h" +#include "GuiApplication.h" +#include "MainWindow.h" #include "Song.h" #include "embed.h" @@ -138,7 +140,7 @@ QAction *Editor::playAction() const return m_playAction; } -void Editor::closeEvent( QCloseEvent * _ce ) +void Editor::closeEvent(QCloseEvent * event) { if( parentWidget() ) { @@ -148,7 +150,8 @@ void Editor::closeEvent( QCloseEvent * _ce ) { hide(); } - _ce->accept(); + getGUI()->mainWindow()->refocus(); + event->ignore(); } DropToolBar::DropToolBar(QWidget* parent) : QToolBar(parent) From 79eac411a01b6b3773eceee17877d02b59342f39 Mon Sep 17 00:00:00 2001 From: Rossmaxx <74815851+Rossmaxx@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:51:18 +0530 Subject: [PATCH 04/16] Removed dead code using #if 0 (#7521) Removed code that's has been deactivated using `#if 0`. --- src/core/AudioEngine.cpp | 11 ----------- src/core/ProjectRenderer.cpp | 11 ----------- src/tracks/SampleTrack.cpp | 3 --- 3 files changed, 25 deletions(-) diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 77453a8bad4..0da65f426a4 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -1131,17 +1131,6 @@ void AudioEngine::fifoWriter::run() { disable_denormals(); -#if 0 -#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD) -#ifdef LMMS_HAVE_SCHED_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( 0, &mask ); - sched_setaffinity( 0, sizeof( mask ), &mask ); -#endif -#endif -#endif - const fpp_t frames = m_audioEngine->framesPerPeriod(); while( m_writing ) { diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 3d83515f22e..e5410e6f272 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -159,17 +159,6 @@ void ProjectRenderer::startProcessing() void ProjectRenderer::run() { -#if 0 -#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD) -#ifdef LMMS_HAVE_SCHED_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( 0, &mask ); - sched_setaffinity( 0, sizeof( mask ), &mask ); -#endif -#endif -#endif - PerfLogTimer perfLog("Project Render"); Engine::getSong()->startExport(); diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 609043efe12..8ad75799dd0 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -191,9 +191,6 @@ Clip * SampleTrack::createClip(const TimePos & pos) void SampleTrack::saveTrackSpecificSettings(QDomDocument& _doc, QDomElement& _this, bool presetMode) { m_audioPort.effects()->saveState( _doc, _this ); -#if 0 - _this.setAttribute( "icon", tlb->pixmapFile() ); -#endif m_volumeModel.saveSettings( _doc, _this, "vol" ); m_panningModel.saveSettings( _doc, _this, "pan" ); m_mixerChannelModel.saveSettings( _doc, _this, "mixch" ); From 066f6b5e958955db3d7c0c6e6ae179b22ca7728a Mon Sep 17 00:00:00 2001 From: Steffen Baranowsky Date: Mon, 7 Oct 2024 13:25:11 +0200 Subject: [PATCH 05/16] Align the rename line edit for tracks (#7414) Align the rename line edit for tracks. Make sure that the rename line edit of the `TrackLabelButton` has the same font size as the widget itself. Because the font size is defined via style sheets the font size of the line edit has to be set when the actual renaming starts and not in the constructor. The reason is that style sheets are set after the constructor has run. Rename the local variable `txt` to the more speaking name `trackName`. Ensure that the line edit is moved to the correct place for different icon sizes by taking the icon size into account. To make this work this also has to be done in the `rename` method. ## Other changes Streamline the default style sheet of `TrackLabelButton` by removing repeated properties that are inherited from the "main" style sheet, i.e. that are already part of `lmms--gui--TrackLabelButton`. Interestingly, the `background-color` property had to be repeated for `lmms--gui--TrackLabelButton:hover`. --------- Co-authored-by: Michael Gregorius --- data/themes/default/style.css | 11 ----------- src/gui/tracks/TrackLabelButton.cpp | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/data/themes/default/style.css b/data/themes/default/style.css index ef98c060999..e2dd369f952 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -601,17 +601,11 @@ lmms--gui--TrackLabelButton:hover { background: #3B424A; border: 1px solid #515B66; border-radius: none; - font-size: 11px; - font-weight: normal; - padding: 2px 1px; } lmms--gui--TrackLabelButton:pressed { background: #262B30; border-radius: none; - font-size: 11px; - font-weight: normal; - padding: 2px 1px; } lmms--gui--TrackLabelButton:checked { @@ -619,17 +613,12 @@ lmms--gui--TrackLabelButton:checked { background: #1C1F24; background-image: url("resources:track_shadow_p.png"); border-radius: none; - font-size: 11px; - font-weight: normal; - padding: 2px 1px; } lmms--gui--TrackLabelButton:checked:pressed { border: 1px solid #2f353b; background: #0e1012; background-image: url("resources:track_shadow_p.png"); - font-size: 11px; - padding: 2px 1px; font-weight: solid; } diff --git a/src/gui/tracks/TrackLabelButton.cpp b/src/gui/tracks/TrackLabelButton.cpp index 871d42316e5..dd19f3d19fc 100644 --- a/src/gui/tracks/TrackLabelButton.cpp +++ b/src/gui/tracks/TrackLabelButton.cpp @@ -50,6 +50,7 @@ TrackLabelButton::TrackLabelButton( TrackView * _tv, QWidget * _parent ) : setAcceptDrops( true ); setCursor( QCursor( embed::getIconPixmap( "hand" ), 3, 3 ) ); setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); + m_renameLineEdit = new TrackRenameLineEdit( this ); m_renameLineEdit->hide(); @@ -60,8 +61,6 @@ TrackLabelButton::TrackLabelButton( TrackView * _tv, QWidget * _parent ) : else { setFixedSize( 160, 29 ); - m_renameLineEdit->move( 30, ( height() / 2 ) - ( m_renameLineEdit->sizeHint().height() / 2 ) ); - m_renameLineEdit->setFixedWidth( width() - 33 ); connect( m_renameLineEdit, SIGNAL(editingFinished()), this, SLOT(renameFinished())); } @@ -89,11 +88,22 @@ void TrackLabelButton::rename() } else { - QString txt = m_trackView->getTrack()->name(); - m_renameLineEdit->show(); - m_renameLineEdit->setText( txt ); + const auto & trackName = m_trackView->getTrack()->name(); + m_renameLineEdit->setText(trackName); m_renameLineEdit->selectAll(); m_renameLineEdit->setFocus(); + + // Make sure that the rename line edit uses the same font as the widget + // which is set via style sheets + m_renameLineEdit->setFont(font()); + + // Move the line edit to the correct position by taking the size of the + // icon into account. + const auto iconWidth = iconSize().width(); + m_renameLineEdit->move(iconWidth + 1, (height() / 2 - m_renameLineEdit->sizeHint().height() / 2) + 1); + m_renameLineEdit->setFixedWidth(width() - (iconWidth + 6)); + + m_renameLineEdit->show(); } } From 378ff8bab02edb67110dc8b68694e6bce008fdfe Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Thu, 10 Oct 2024 10:36:25 +0200 Subject: [PATCH 06/16] Fix the maximization of sub windows (#7530) ## Fix rendering of maximized sub windows ### Adjustments in paintEvent In `SubWindow::paintEvent` don't paint anything if the sub window is maximized . Otherwise some gradients are visible behind the maximized child content. ### Adjustments in adjustTitleBar In `SubWindow::adjustTitleBar` hide the title label and the buttons if the sub window is maximized. Always show the title and close button if not maximized. This is needed to reset the state correctly after maximization. Remove some calls to `isMaximized` where we already know that the sub window is not maximized, i.e. where these calls would always return `false`. One adjustment would have resulted in a call to `setHidden(false)`. This was changed to `setVisible(true)` to get rid of the double negation. The other `false` would have gotten in an or statement and thus could be removed completely. ### Add method addTitleButton Add the helper method `SubWindow::addTitleButton` to reduce code repetition in the constructor. ### Other changes Remove a dependency on the `MdiArea` when checking if the sub window is the active one. Query its own window state to find out if it is active. When calling `setWindowFlags` in the constructor only adjust the existing flags with the changes that we actually want to do. It was ensured that all other flags that have been set before still apply with this change. --- include/SubWindow.h | 2 ++ src/gui/SubWindow.cpp | 73 ++++++++++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/include/SubWindow.h b/include/SubWindow.h index d1cc6a7af08..c5e3f02d4d0 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -78,6 +78,8 @@ class LMMS_EXPORT SubWindow : public QMdiSubWindow void paintEvent( QPaintEvent * pe ) override; void changeEvent( QEvent * event ) override; + QPushButton* addTitleButton(const std::string& iconName, const QString& toolTip); + signals: void focusLost(); diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index dc6e49297d1..1fa0f25a71f 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -58,28 +58,13 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : m_borderColor = Qt::black; // close, maximize and restore (after maximizing) buttons - m_closeBtn = new QPushButton( embed::getIconPixmap( "close" ), QString(), this ); - m_closeBtn->resize( m_buttonSize ); - m_closeBtn->setFocusPolicy( Qt::NoFocus ); - m_closeBtn->setCursor( Qt::ArrowCursor ); - m_closeBtn->setAttribute( Qt::WA_NoMousePropagation ); - m_closeBtn->setToolTip( tr( "Close" ) ); + m_closeBtn = addTitleButton("close", tr("Close")); connect( m_closeBtn, SIGNAL(clicked(bool)), this, SLOT(close())); - m_maximizeBtn = new QPushButton( embed::getIconPixmap( "maximize" ), QString(), this ); - m_maximizeBtn->resize( m_buttonSize ); - m_maximizeBtn->setFocusPolicy( Qt::NoFocus ); - m_maximizeBtn->setCursor( Qt::ArrowCursor ); - m_maximizeBtn->setAttribute( Qt::WA_NoMousePropagation ); - m_maximizeBtn->setToolTip( tr( "Maximize" ) ); + m_maximizeBtn = addTitleButton("maximize", tr("Maximize")); connect( m_maximizeBtn, SIGNAL(clicked(bool)), this, SLOT(showMaximized())); - m_restoreBtn = new QPushButton( embed::getIconPixmap( "restore" ), QString(), this ); - m_restoreBtn->resize( m_buttonSize ); - m_restoreBtn->setFocusPolicy( Qt::NoFocus ); - m_restoreBtn->setCursor( Qt::ArrowCursor ); - m_restoreBtn->setAttribute( Qt::WA_NoMousePropagation ); - m_restoreBtn->setToolTip( tr( "Restore" ) ); + m_restoreBtn = addTitleButton("restore", tr("Restore")); connect( m_restoreBtn, SIGNAL(clicked(bool)), this, SLOT(showNormal())); // QLabel for the window title and the shadow effect @@ -93,10 +78,9 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : m_windowTitle->setAttribute( Qt::WA_TransparentForMouseEvents, true ); m_windowTitle->setGraphicsEffect( m_shadow ); - // disable the minimize button - setWindowFlags( Qt::SubWindow | Qt::WindowMaximizeButtonHint | - Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | - Qt::CustomizeWindowHint ); + // Disable the minimize button and make sure that the custom window hint is set + setWindowFlags((this->windowFlags() & ~Qt::WindowMinimizeButtonHint) | Qt::CustomizeWindowHint); + connect( mdiArea(), SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(focusChanged(QMdiSubWindow*))); } @@ -111,12 +95,14 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : */ void SubWindow::paintEvent( QPaintEvent * ) { + // Don't paint any of the other stuff if the sub window is maximized + // so that only its child content is painted. + if (isMaximized()) { return; } + QPainter p( this ); QRect rect( 0, 0, width(), m_titleBarHeight ); - bool isActive = mdiArea() - ? mdiArea()->activeSubWindow() == this - : false; + const bool isActive = windowState() & Qt::WindowActive; p.fillRect( rect, isActive ? activeColor() : p.pen().brush() ); @@ -295,9 +281,28 @@ void SubWindow::moveEvent( QMoveEvent * event ) */ void SubWindow::adjustTitleBar() { + // Don't show the title or any button if the sub window is maximized. Otherwise they + // might show up behind the actual maximized content of the child widget. + if (isMaximized()) + { + m_closeBtn->hide(); + m_maximizeBtn->hide(); + m_restoreBtn->hide(); + m_windowTitle->hide(); + + return; + } + + // The sub window is not maximized, i.e. the title must be shown + // as well as some buttons. + + // Title adjustments + m_windowTitle->show(); + // button adjustments m_maximizeBtn->hide(); m_restoreBtn->hide(); + m_closeBtn->show(); const int rightSpace = 3; const int buttonGap = 1; @@ -322,12 +327,12 @@ void SubWindow::adjustTitleBar() buttonBarWidth = buttonBarWidth + m_buttonSize.width() + buttonGap; m_maximizeBtn->move( middleButtonPos ); m_restoreBtn->move( middleButtonPos ); - m_maximizeBtn->setHidden( isMaximized() ); + m_maximizeBtn->setVisible(true); } // we're keeping the restore button around if we open projects // from older versions that have saved minimized windows - m_restoreBtn->setVisible( isMaximized() || isMinimized() ); + m_restoreBtn->setVisible(isMinimized()); if( isMinimized() ) { m_restoreBtn->move( m_maximizeBtn->isHidden() ? middleButtonPos : leftButtonPos ); @@ -403,5 +408,17 @@ void SubWindow::resizeEvent( QResizeEvent * event ) } } +QPushButton* SubWindow::addTitleButton(const std::string& iconName, const QString& toolTip) +{ + auto button = new QPushButton(embed::getIconPixmap(iconName), QString(), this); + button->resize(m_buttonSize); + button->setFocusPolicy(Qt::NoFocus); + button->setCursor(Qt::ArrowCursor); + button->setAttribute(Qt::WA_NoMousePropagation); + button->setToolTip(toolTip); + + return button; +} + -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui From fb5516cfdc717e679918d826fb40663fac113d6a Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 12 Oct 2024 10:02:56 +0200 Subject: [PATCH 07/16] Track operations widget with layout (#7537) Put the elements of the `TrackOperationsWidget` into layouts. These are: * The grip that can be used to move tracks * The gear icon that opens the operations menu * The mute button * The solo button The grip that can be used to move tracks around is extracted into its own class called `TrackGrip`. This has several advantages: * It can be put into a layout. * It can render itself at arbitrary sizes by simply repeating its pattern pixmap. * It can be used in a much more object-oriented way because it emits signals when it is grabbed and released. * It is responsible for locally updating its cursor state. The default cursor of the grip now is an open hand which indicates to the users that it can be grabbed. While being grabbed the cursor now is a closed hand. ## Technical details The class `TrackOperationsWidget` now holds an instance of `TrackGrip` and provides a getter to retrieve it. This getter is used by `TrackView` to connect to the two new signals `grabbed` and `released`. The method `TrackOperationsWidget::paintEvent` now only paints the background as it does not need to paint the grip anymore. The `TrackView` now handles the grabbing and release of the grip in `TrackView::onTrackGripGrabbed` and `TrackView::onTrackGripReleased`. Because the events and cursor states are now handled by `TrackGrip` this code could be removed from `TrackView::mousePressEvent`. There was a comment in `TrackView` which indicated that the `TrackOperationsWidget` had to be updated when the track is moved and released because it would hide some elements during the move. The comment and the corresponding code was removed because the operations widget does not hide its elements during moves (this was already the state before the changes made by this commit). Adjust the style sheets of the classic and default themes with regards to the `QPushButton` that's used to show the gear menu in the `TrackOperationsWidget`. The `>` has been removed because the `QPushButton` is not a direct decendent of the `TrackOperationsWidget` anymore. ### Wrapping of `PixmapButton` in `QWidget` The PixmapButtons that are used in `TrackOperationsWidget` current have to be wrapped into a `QWidget`. This is necessary due to some strange effect where the PixmapButtons are resized to a size that's larger than their minimum/fixed size when the method `show` is called in `TrackContainerView::realignTracks`. Specifically, with the default theme the buttons are resized from their minimum size of (16, 14) to (26, 26). This then makes them behave not as expected in layouts. The resizing is not done for QWidgets. Therefore we wrap the PixmapButton in a QWidget which is set to a fixed size that will be able to show the active and inactive pixmap. We can then use the QWidget in layouts without any disturbances. The resizing only seems to affect the track view hierarchy and is triggered by Qt's internal mechanisms. For example the buttons in the mixer view do not seem to be affected. If you want to debug this simply override "PixmapButton::resizeEvent" and trigger a break point in there, e.g. whenever the new size is not (16, 14). ### More layout-friendly PixmapButton Make the `PixmapButton` more friendly for layouts by implementing `minimumSizeHint`. It returns a size that accommodate to show the active and the inactive pixmap. Also make `sizeHint` return the minimum size hint. The previous implementation would have made layouts jump when the pixmap is toggled with pixmaps of different sizes. --- data/themes/classic/style.css | 10 +-- data/themes/default/style.css | 8 +- include/PixmapButton.h | 1 + include/TrackGrip.h | 68 ++++++++++++++ include/TrackOperationsWidget.h | 3 + include/TrackView.h | 3 +- src/gui/CMakeLists.txt | 1 + src/gui/tracks/TrackGrip.cpp | 106 ++++++++++++++++++++++ src/gui/tracks/TrackOperationsWidget.cpp | 107 +++++++++++++++-------- src/gui/tracks/TrackView.cpp | 31 ++++--- src/gui/widgets/PixmapButton.cpp | 13 ++- 11 files changed, 280 insertions(+), 71 deletions(-) create mode 100644 include/TrackGrip.h create mode 100644 src/gui/tracks/TrackGrip.cpp diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 95737ed3afe..d098bf26660 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -375,7 +375,7 @@ lmms--gui--TrackContentWidget { /* gear button in tracks */ -lmms--gui--TrackOperationsWidget > QPushButton { +lmms--gui--TrackOperationsWidget QPushButton { max-height: 26px; max-width: 26px; min-height: 26px; @@ -384,7 +384,7 @@ lmms--gui--TrackOperationsWidget > QPushButton { border: none; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator { image: url("resources:trackop.png"); subcontrol-origin: padding; subcontrol-position: center; @@ -392,12 +392,12 @@ lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { top: 1px; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:hover { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:hover { image: url("resources:trackop_h.png"); } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:pressed, -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:checked { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:pressed, +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:checked { image: url("resources:trackop_c.png"); position: relative; top: 2px; diff --git a/data/themes/default/style.css b/data/themes/default/style.css index e2dd369f952..83933df199a 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -411,7 +411,7 @@ lmms--gui--TrackContentWidget { /* gear button in tracks */ -lmms--gui--TrackOperationsWidget > QPushButton { +lmms--gui--TrackOperationsWidget QPushButton { max-height: 26px; max-width: 26px; min-height: 26px; @@ -420,7 +420,7 @@ lmms--gui--TrackOperationsWidget > QPushButton { border: none; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator { image: url("resources:trackop.png"); subcontrol-origin: padding; subcontrol-position: center; @@ -428,8 +428,8 @@ lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { top: 1px; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:pressed, -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:checked { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:pressed, +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:checked { image: url("resources:trackop.png"); position: relative; top: 2px; diff --git a/include/PixmapButton.h b/include/PixmapButton.h index 734bd11ae5c..a0b4141deba 100644 --- a/include/PixmapButton.h +++ b/include/PixmapButton.h @@ -45,6 +45,7 @@ class LMMS_EXPORT PixmapButton : public AutomatableButton void setInactiveGraphic( const QPixmap & _pm, bool _update = true ); QSize sizeHint() const override; + QSize minimumSizeHint() const override; signals: void doubleClicked(); diff --git a/include/TrackGrip.h b/include/TrackGrip.h new file mode 100644 index 00000000000..aa19222a6ff --- /dev/null +++ b/include/TrackGrip.h @@ -0,0 +1,68 @@ +/* + * TrackGrip.h - Grip that can be used to move tracks + * + * Copyright (c) 2024- Michael Gregorius + * + * 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. + * + */ + +#ifndef LMMS_GUI_TRACK_GRIP_H +#define LMMS_GUI_TRACK_GRIP_H + +#include + + +class QPixmap; + +namespace lmms +{ + +class Track; + +namespace gui +{ + +class TrackGrip : public QWidget +{ + Q_OBJECT +public: + TrackGrip(Track* track, QWidget* parent = 0); + ~TrackGrip() override = default; + +signals: + void grabbed(); + void released(); + +protected: + void mousePressEvent(QMouseEvent*) override; + void mouseReleaseEvent(QMouseEvent*) override; + void paintEvent(QPaintEvent*) override; + +private: + Track* m_track = nullptr; + bool m_isGrabbed = false; + static QPixmap* s_grabbedPixmap; + static QPixmap* s_releasedPixmap; +}; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_TRACK_GRIP_H diff --git a/include/TrackOperationsWidget.h b/include/TrackOperationsWidget.h index 4dbb5353c5f..8417298b423 100644 --- a/include/TrackOperationsWidget.h +++ b/include/TrackOperationsWidget.h @@ -33,6 +33,7 @@ namespace lmms::gui { class PixmapButton; +class TrackGrip; class TrackView; class TrackOperationsWidget : public QWidget @@ -42,6 +43,7 @@ class TrackOperationsWidget : public QWidget TrackOperationsWidget( TrackView * parent ); ~TrackOperationsWidget() override = default; + TrackGrip* getTrackGrip() const { return m_trackGrip; } protected: void mousePressEvent( QMouseEvent * me ) override; @@ -65,6 +67,7 @@ private slots: private: TrackView * m_trackView; + TrackGrip* m_trackGrip; QPushButton * m_trackOps; PixmapButton * m_muteBtn; PixmapButton * m_soloBtn; diff --git a/include/TrackView.h b/include/TrackView.h index b2654202b9a..c13ba1e870e 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -171,7 +171,8 @@ public slots: private slots: void createClipView( lmms::Clip * clip ); void muteChanged(); - + void onTrackGripGrabbed(); + void onTrackGripReleased(); } ; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4195ec58c1a..fe4a2c462b2 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -94,6 +94,7 @@ SET(LMMS_SRCS gui/tracks/TrackLabelButton.cpp gui/tracks/TrackOperationsWidget.cpp gui/tracks/TrackRenameLineEdit.cpp + gui/tracks/TrackGrip.cpp gui/tracks/TrackView.cpp gui/widgets/AutomatableButton.cpp diff --git a/src/gui/tracks/TrackGrip.cpp b/src/gui/tracks/TrackGrip.cpp new file mode 100644 index 00000000000..6133c6e962d --- /dev/null +++ b/src/gui/tracks/TrackGrip.cpp @@ -0,0 +1,106 @@ +/* + * TrackGrip.cpp - Grip that can be used to move tracks + * + * Copyright (c) 2024- Michael Gregorius + * + * 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 "TrackGrip.h" + +#include "embed.h" +#include "Track.h" + +#include +#include +#include + + +namespace lmms::gui +{ + +QPixmap* TrackGrip::s_grabbedPixmap = nullptr; +QPixmap* TrackGrip::s_releasedPixmap = nullptr; + +constexpr int c_margin = 2; + +TrackGrip::TrackGrip(Track* track, QWidget* parent) : + QWidget(parent), + m_track(track) +{ + if (!s_grabbedPixmap) + { + s_grabbedPixmap = new QPixmap(embed::getIconPixmap("track_op_grip_c")); + } + + if (!s_releasedPixmap) + { + s_releasedPixmap = new QPixmap(embed::getIconPixmap("track_op_grip")); + } + + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + + setCursor(Qt::OpenHandCursor); + + setFixedWidth(std::max(s_grabbedPixmap->width(), s_releasedPixmap->width()) + 2 * c_margin); +} + +void TrackGrip::mousePressEvent(QMouseEvent* m) +{ + m->accept(); + + m_isGrabbed = true; + setCursor(Qt::ClosedHandCursor); + + emit grabbed(); + + update(); +} + +void TrackGrip::mouseReleaseEvent(QMouseEvent* m) +{ + m->accept(); + + m_isGrabbed = false; + setCursor(Qt::OpenHandCursor); + + emit released(); + + update(); +} + +void TrackGrip::paintEvent(QPaintEvent*) +{ + QPainter p(this); + + // Check if the color of the track should be used for the background + const auto color = m_track->color(); + const auto muted = m_track->getMutedModel()->value(); + + if (color.has_value() && !muted) + { + p.fillRect(rect(), color.value()); + } + + // Paint the pixmap + auto r = rect().marginsRemoved(QMargins(c_margin, c_margin, c_margin, c_margin)); + p.drawTiledPixmap(r, m_isGrabbed ? *s_grabbedPixmap : *s_releasedPixmap); +} + +} // namespace lmms::gui diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index 6cb74e2c5b7..6aca6628242 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -24,6 +24,7 @@ #include "TrackOperationsWidget.h" +#include #include #include #include @@ -44,6 +45,7 @@ #include "StringPairDrag.h" #include "Track.h" #include "TrackContainerView.h" +#include "TrackGrip.h" #include "TrackView.h" namespace lmms::gui @@ -68,41 +70,86 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : setObjectName( "automationEnabled" ); + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->setAlignment(Qt::AlignTop); - m_trackOps = new QPushButton( this ); - m_trackOps->move( 12, 1 ); + m_trackGrip = new TrackGrip(m_trackView->getTrack(), this); + layout->addWidget(m_trackGrip); + + // This widget holds the gear icon and the mute and solo + // buttons in a layout. + auto operationsWidget = new QWidget(this); + auto operationsLayout = new QHBoxLayout(operationsWidget); + operationsLayout->setContentsMargins(0, 0, 0, 0); + operationsLayout->setSpacing(0); + + m_trackOps = new QPushButton(operationsWidget); m_trackOps->setFocusPolicy( Qt::NoFocus ); m_trackOps->setMenu( toMenu ); m_trackOps->setToolTip(tr("Actions")); + // This helper lambda wraps a PixmapButton in a QWidget. This is necessary due to some strange effect where the + // PixmapButtons are resized to a size that's larger than their minimum/fixed size when the method "show" is called + // in "TrackContainerView::realignTracks". Specifically, with the default theme the buttons are resized from + // (16, 14) to (26, 26). This then makes them behave not as expected in layouts. + // The resizing is not done for QWidgets. Therefore we wrap the PixmapButton in a QWidget which is set to a + // fixed size that will be able to show the active and inactive pixmap. We can then use the QWidget in layouts + // without any disturbances. + // + // The resizing only seems to affect the track view hierarchy and is triggered by Qt's internal mechanisms. + // For example the buttons in the mixer view do not seem to be affected. + // If you want to debug this simply override "PixmapButton::resizeEvent" and trigger a break point in there. + auto buildPixmapButtonWrappedInWidget = [](QWidget* parent, const QString& toolTip, + std::string_view activeGraphic, std::string_view inactiveGraphic, PixmapButton*& pixmapButton) + { + const auto activePixmap = embed::getIconPixmap(activeGraphic); + const auto inactivePixmap = embed::getIconPixmap(inactiveGraphic); + + auto necessarySize = activePixmap.size().expandedTo(inactivePixmap.size()); + + auto wrapperWidget = new QWidget(parent); + wrapperWidget->setFixedSize(necessarySize); + + auto button = new PixmapButton(wrapperWidget, toolTip); + button->setCheckable(true); + button->setActiveGraphic(activePixmap); + button->setInactiveGraphic(inactivePixmap); + button->setToolTip(toolTip); + + pixmapButton = button; + + return wrapperWidget; + }; - m_muteBtn = new PixmapButton( this, tr( "Mute" ) ); - m_muteBtn->setActiveGraphic( embed::getIconPixmap( "led_off" ) ); - m_muteBtn->setInactiveGraphic( embed::getIconPixmap( "led_green" ) ); - m_muteBtn->setCheckable( true ); + auto muteWidget = buildPixmapButtonWrappedInWidget(operationsWidget, tr("Mute"), "led_off", "led_green", m_muteBtn); + auto soloWidget = buildPixmapButtonWrappedInWidget(operationsWidget, tr("Solo"), "led_red", "led_off", m_soloBtn); - m_soloBtn = new PixmapButton( this, tr( "Solo" ) ); - m_soloBtn->setActiveGraphic( embed::getIconPixmap( "led_red" ) ); - m_soloBtn->setInactiveGraphic( embed::getIconPixmap( "led_off" ) ); - m_soloBtn->setCheckable( true ); + operationsLayout->addWidget(m_trackOps, Qt::AlignCenter); + operationsLayout->addSpacing(5); if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { - m_muteBtn->move( 46, 0 ); - m_soloBtn->move( 46, 16 ); + auto vlayout = new QVBoxLayout(); + vlayout->setContentsMargins(0, 0, 0, 0); + vlayout->setSpacing(0); + vlayout->addStretch(1); + vlayout->addWidget(muteWidget); + vlayout->addWidget(soloWidget); + vlayout->addStretch(1); + operationsLayout->addLayout(vlayout); } else { - m_muteBtn->move( 46, 8 ); - m_soloBtn->move( 62, 8 ); + operationsLayout->addWidget(muteWidget, Qt::AlignCenter); + operationsLayout->addWidget(soloWidget, Qt::AlignCenter); } - m_muteBtn->show(); - m_muteBtn->setToolTip(tr("Mute")); + operationsLayout->addStretch(1); - m_soloBtn->show(); - m_soloBtn->setToolTip(tr("Solo")); + layout->addWidget(operationsWidget, 0, Qt::AlignTop); connect( this, SIGNAL(trackRemovalScheduled(lmms::gui::TrackView*)), m_trackView->trackContainerView(), @@ -154,31 +201,17 @@ void TrackOperationsWidget::mousePressEvent( QMouseEvent * me ) -/*! \brief Repaint the trackOperationsWidget +/*! + * \brief Repaint the trackOperationsWidget * - * If we're not moving, and in the Pattern Editor, then turn - * automation on or off depending on its previous state and show - * ourselves. - * - * Otherwise, hide ourselves. - * - * \todo Flesh this out a bit - is it correct? - * \param pe The paint event to respond to + * Only things that's done for now is to paint the background + * with the brush of the window from the palette. */ -void TrackOperationsWidget::paintEvent( QPaintEvent * pe ) +void TrackOperationsWidget::paintEvent(QPaintEvent*) { QPainter p( this ); p.fillRect(rect(), palette().brush(QPalette::Window)); - - if (m_trackView->getTrack()->color().has_value() && !m_trackView->getTrack()->getMutedModel()->value()) - { - QRect coloredRect( 0, 0, 10, m_trackView->getTrack()->getHeight() ); - - p.fillRect(coloredRect, m_trackView->getTrack()->color().value()); - } - - p.drawPixmap(2, 2, embed::getIconPixmap(m_trackView->isMovingTrack() ? "track_op_grip_c" : "track_op_grip")); } diff --git a/src/gui/tracks/TrackView.cpp b/src/gui/tracks/TrackView.cpp index e2323602154..ecd397975f1 100644 --- a/src/gui/tracks/TrackView.cpp +++ b/src/gui/tracks/TrackView.cpp @@ -41,6 +41,7 @@ #include "PixmapButton.h" #include "StringPairDrag.h" #include "Track.h" +#include "TrackGrip.h" #include "TrackContainerView.h" #include "ClipView.h" @@ -102,6 +103,10 @@ TrackView::TrackView( Track * track, TrackContainerView * tcv ) : connect( &m_track->m_soloModel, SIGNAL(dataChanged()), m_track, SLOT(toggleSolo()), Qt::DirectConnection ); + + auto trackGrip = m_trackOperationsWidget.getTrackGrip(); + connect(trackGrip, &TrackGrip::grabbed, this, &TrackView::onTrackGripGrabbed); + connect(trackGrip, &TrackGrip::released, this, &TrackView::onTrackGripReleased); // create views for already existing clips for (const auto& clip : m_track->m_clips) @@ -284,22 +289,6 @@ void TrackView::mousePressEvent( QMouseEvent * me ) QCursor c( Qt::SizeVerCursor); QApplication::setOverrideCursor( c ); } - else - { - if( me->x()>10 ) // 10 = The width of the grip + 2 pixels to the left and right. - { - QWidget::mousePressEvent( me ); - return; - } - - m_action = Action::Move; - - QCursor c( Qt::SizeVerCursor ); - QApplication::setOverrideCursor( c ); - // update because in move-mode, all elements in - // track-op-widgets are hidden as a visual feedback - m_trackOperationsWidget.update(); - } me->accept(); } @@ -451,6 +440,16 @@ void TrackView::muteChanged() } +void TrackView::onTrackGripGrabbed() +{ + m_action = Action::Move; +} + +void TrackView::onTrackGripReleased() +{ + m_action = Action::None; +} + void TrackView::setIndicatorMute(FadeButton* indicator, bool muted) diff --git a/src/gui/widgets/PixmapButton.cpp b/src/gui/widgets/PixmapButton.cpp index 069acad567c..5f2e01b3d24 100644 --- a/src/gui/widgets/PixmapButton.cpp +++ b/src/gui/widgets/PixmapButton.cpp @@ -124,16 +124,13 @@ void PixmapButton::setInactiveGraphic( const QPixmap & _pm, bool _update ) QSize PixmapButton::sizeHint() const { - if (isActive()) - { - return m_activePixmap.size(); - } - else - { - return m_inactivePixmap.size(); - } + return minimumSizeHint(); } +QSize PixmapButton::minimumSizeHint() const +{ + return m_activePixmap.size().expandedTo(m_inactivePixmap.size()); +} bool PixmapButton::isActive() const { From 97b61bbd9a4c118e65e808ba8793035a3c4cc359 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz <1042576+JohannesLorenz@users.noreply.github.com> Date: Sat, 12 Oct 2024 10:39:42 +0200 Subject: [PATCH 08/16] Make `PluginView::isResizable` a virtual (#7541) Remove the member `PluginView::m_isResizable` and it's associated method `setResizable`. Turn `isResizable` into a virtual method. The reasoning is that whether or not an effect can be resized depends on its implementation. Therefore does not make sense to have a method like `setResizable`. If the underlying implementation does not support resizing then it would not make sense to call `setResizable(true)`. So `isResizable` now describes the underlying ability of a plugin to resize. It's then up to the clients of that method to decide how to treat the result of `isResizable`, i.e. if they want to make use of the ability to resize or not. --- include/PluginView.h | 6 +----- plugins/SlicerT/SlicerTView.cpp | 1 - plugins/SlicerT/SlicerTView.h | 2 ++ 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/include/PluginView.h b/include/PluginView.h index a85b0b9e185..dce4d6bfb0a 100644 --- a/include/PluginView.h +++ b/include/PluginView.h @@ -41,11 +41,7 @@ class LMMS_EXPORT PluginView : public QWidget, public ModelView { } - void setResizable(bool resizable) { m_isResizable = resizable; } - bool isResizable() { return m_isResizable; } - -private: - bool m_isResizable = false; + virtual bool isResizable() const { return false; } }; } // namespace lmms::gui diff --git a/plugins/SlicerT/SlicerTView.cpp b/plugins/SlicerT/SlicerTView.cpp index 4be774f6d2a..7af2db1430e 100644 --- a/plugins/SlicerT/SlicerTView.cpp +++ b/plugins/SlicerT/SlicerTView.cpp @@ -55,7 +55,6 @@ SlicerTView::SlicerTView(SlicerT* instrument, QWidget* parent) setMaximumSize(QSize(10000, 10000)); setMinimumSize(QSize(516, 400)); - setResizable(true); m_wf = new SlicerTWaveform(248, 128, instrument, this); m_wf->move(0, s_topBarHeight); diff --git a/plugins/SlicerT/SlicerTView.h b/plugins/SlicerT/SlicerTView.h index 232c2745425..f24246621fa 100644 --- a/plugins/SlicerT/SlicerTView.h +++ b/plugins/SlicerT/SlicerTView.h @@ -78,6 +78,8 @@ public slots: void resizeEvent(QResizeEvent* event) override; private: + bool isResizable() const override { return true; } + SlicerT* m_slicerTParent; Knob* m_noteThresholdKnob; From b8b1dae4073704169bc0b10c3bc7e52de3ccf9e1 Mon Sep 17 00:00:00 2001 From: Lost Robot <34612565+LostRobotMusic@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:29:47 -0500 Subject: [PATCH 09/16] Optimize dBFS/amplitude conversion functions (#7535) * Optimize dBFS <-> amplitude functions --- include/lmms_math.h | 46 ++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/include/lmms_math.h b/include/lmms_math.h index 72800838818..bdadd7ba0c4 100644 --- a/include/lmms_math.h +++ b/include/lmms_math.h @@ -164,37 +164,22 @@ inline float linearToLogScale(float min, float max, float value) return std::isnan( result ) ? 0 : result; } - - - -//! @brief Converts linear amplitude (0-1.0) to dBFS scale. Handles zeroes as -inf. -//! @param amp Linear amplitude, where 1.0 = 0dBFS. -//! @return Amplitude in dBFS. -inf for 0 amplitude. -inline float safeAmpToDbfs(float amp) +inline float fastPow10f(float x) { - return amp == 0.0f - ? -INFINITY - : log10f( amp ) * 20.0f; + return std::exp(2.302585092994046f * x); } - -//! @brief Converts dBFS-scale to linear amplitude with 0dBFS = 1.0. Handles infinity as zero. -//! @param dbfs The dBFS value to convert: all infinites are treated as -inf and result in 0 -//! @return Linear amplitude -inline float safeDbfsToAmp(float dbfs) +inline float fastLog10f(float x) { - return std::isinf( dbfs ) - ? 0.0f - : std::pow(10.f, dbfs * 0.05f ); + return std::log(x) * 0.4342944819032518f; } - //! @brief Converts linear amplitude (>0-1.0) to dBFS scale. //! @param amp Linear amplitude, where 1.0 = 0dBFS. ** Must be larger than zero! ** //! @return Amplitude in dBFS. inline float ampToDbfs(float amp) { - return log10f(amp) * 20.0f; + return fastLog10f(amp) * 20.0f; } @@ -203,10 +188,29 @@ inline float ampToDbfs(float amp) //! @return Linear amplitude inline float dbfsToAmp(float dbfs) { - return std::pow(10.f, dbfs * 0.05f); + return fastPow10f(dbfs * 0.05f); +} + + +//! @brief Converts linear amplitude (0-1.0) to dBFS scale. Handles zeroes as -inf. +//! @param amp Linear amplitude, where 1.0 = 0dBFS. +//! @return Amplitude in dBFS. -inf for 0 amplitude. +inline float safeAmpToDbfs(float amp) +{ + return amp == 0.0f ? -INFINITY : ampToDbfs(amp); } +//! @brief Converts dBFS-scale to linear amplitude with 0dBFS = 1.0. Handles infinity as zero. +//! @param dbfs The dBFS value to convert: all infinites are treated as -inf and result in 0 +//! @return Linear amplitude +inline float safeDbfsToAmp(float dbfs) +{ + return std::isinf(dbfs) ? 0.0f : dbfsToAmp(dbfs); +} + + + //! Returns the linear interpolation of the two values template constexpr T lerp(T a, T b, F t) From e6776bcfe5d181fcbe420049b45e4487118ef722 Mon Sep 17 00:00:00 2001 From: Dalton Messmer Date: Wed, 23 Oct 2024 13:17:14 -0400 Subject: [PATCH 10/16] Remove usage of `QTextCodec` in Hydrogen Import plugin (#7562) * Remove QTextCodec QTextCodec was removed from Qt6 and is only available through the Qt5Compat module. QTextCodec was only used by the HydrogenImport plugin when importing old Hydrogen files that were saved using TinyXML before it supported UTF-8. HydrogenImport would use QTextCodec to try to get the current encoding from the locale, and then use that as a best guess for interpreting the XML data in the unspecified encoding it was saved in. None of this was ever reliable, since the encoding of the computer that saved the Hydrogen file might not be the same as the computer running LMMS and importing that file. There is no good solution here, so I decided to simply assume the old Hydrogen files are UTF-8 encoded. The worst that can happen is someone's ancient Hydrogen files containing non-ASCII text of some random encoding becomes mojibake'd after importing into LMMS, which is something that already could have happened. * Clean up a little --- plugins/HydrogenImport/CMakeLists.txt | 2 +- plugins/HydrogenImport/HydrogenImport.cpp | 3 ++- .../{local_file_mgr.cpp => LocalFileMng.cpp} | 10 +++------- plugins/HydrogenImport/LocalFileMng.h | 7 ------- 4 files changed, 6 insertions(+), 16 deletions(-) rename plugins/HydrogenImport/{local_file_mgr.cpp => LocalFileMng.cpp} (97%) diff --git a/plugins/HydrogenImport/CMakeLists.txt b/plugins/HydrogenImport/CMakeLists.txt index 27e8d6a4de9..34f4d43aea6 100644 --- a/plugins/HydrogenImport/CMakeLists.txt +++ b/plugins/HydrogenImport/CMakeLists.txt @@ -1,4 +1,4 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(hydrogenimport HydrogenImport.cpp HydrogenImport.h local_file_mgr.cpp LocalFileMng.h) +BUILD_PLUGIN(hydrogenimport HydrogenImport.cpp HydrogenImport.h LocalFileMng.cpp LocalFileMng.h) diff --git a/plugins/HydrogenImport/HydrogenImport.cpp b/plugins/HydrogenImport/HydrogenImport.cpp index 144a2f5e75e..6a81b507ddb 100644 --- a/plugins/HydrogenImport/HydrogenImport.cpp +++ b/plugins/HydrogenImport/HydrogenImport.cpp @@ -1,7 +1,8 @@ +#include "HydrogenImport.h" + #include #include "LocalFileMng.h" -#include "HydrogenImport.h" #include "Song.h" #include "Engine.h" #include "Instrument.h" diff --git a/plugins/HydrogenImport/local_file_mgr.cpp b/plugins/HydrogenImport/LocalFileMng.cpp similarity index 97% rename from plugins/HydrogenImport/local_file_mgr.cpp rename to plugins/HydrogenImport/LocalFileMng.cpp index 22744050157..c4d11c7e2e4 100644 --- a/plugins/HydrogenImport/local_file_mgr.cpp +++ b/plugins/HydrogenImport/LocalFileMng.cpp @@ -1,12 +1,11 @@ -#include +#include "LocalFileMng.h" + #include #include #include #include -#include -#include "LocalFileMng.h" namespace lmms { @@ -197,10 +196,7 @@ QDomDocument LocalFileMng::openXmlDocument( const QString& filename ) return QDomDocument(); if( TinyXMLCompat ) { - QString enc = QTextCodec::codecForLocale()->name(); - if( enc == QString("System") ) { - enc = "UTF-8"; - } + const QString enc = "UTF-8"; // unknown encoding, so assume utf-8 and call it a day QByteArray line; QByteArray buf = QString("\n") .arg( enc ) diff --git a/plugins/HydrogenImport/LocalFileMng.h b/plugins/HydrogenImport/LocalFileMng.h index 0aaf7d1c855..1260dbfdd82 100644 --- a/plugins/HydrogenImport/LocalFileMng.h +++ b/plugins/HydrogenImport/LocalFileMng.h @@ -14,12 +14,6 @@ namespace lmms class LocalFileMng { public: - LocalFileMng(); - ~LocalFileMng(); - std::vector getallPatternList(){ - return m_allPatternList; - } - static QString readXmlString( QDomNode , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); static float readXmlFloat( QDomNode , const QString& nodeName, float defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); static int readXmlInt( QDomNode , const QString& nodeName, int defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); @@ -27,7 +21,6 @@ class LocalFileMng static void convertFromTinyXMLString( QByteArray* str ); static bool checkTinyXMLCompatMode( const QString& filename ); static QDomDocument openXmlDocument( const QString& filename ); - std::vector m_allPatternList; }; From 1f37c9ba7c23e5ec1f8741b3889965def9baf33a Mon Sep 17 00:00:00 2001 From: saker Date: Sat, 26 Oct 2024 12:41:46 -0400 Subject: [PATCH 11/16] Inline `TimePos` and `TimeSig` functions to improve performance (#7549) * Inline TimeSig functions * Inline TimePos functions --- include/TimePos.h | 84 ++++++++++++++++-------- src/core/TimePos.cpp | 150 ------------------------------------------- 2 files changed, 58 insertions(+), 176 deletions(-) diff --git a/include/TimePos.h b/include/TimePos.h index b86d8eb0fa6..68f3bd01b05 100644 --- a/include/TimePos.h +++ b/include/TimePos.h @@ -26,6 +26,8 @@ #ifndef LMMS_TIME_POS_H #define LMMS_TIME_POS_H +#include +#include #include "lmms_export.h" #include "lmms_basics.h" @@ -51,8 +53,8 @@ class LMMS_EXPORT TimeSig public: TimeSig( int num, int denom ); TimeSig( const MeterModel &model ); - int numerator() const; - int denominator() const; + int numerator() const { return m_num; } + int denominator() const { return m_denom; } private: int m_num; int m_denom; @@ -69,42 +71,72 @@ class LMMS_EXPORT TimePos TimePos( const tick_t ticks = 0 ); TimePos quantize(float) const; - TimePos toAbsoluteBar() const; + TimePos toAbsoluteBar() const { return getBar() * s_ticksPerBar; } - TimePos& operator+=( const TimePos& time ); - TimePos& operator-=( const TimePos& time ); + TimePos& operator+=(const TimePos& time) + { + m_ticks += time.m_ticks; + return *this; + } + + TimePos& operator-=(const TimePos& time) + { + m_ticks -= time.m_ticks; + return *this; + } // return the bar, rounded down and 0-based - bar_t getBar() const; + bar_t getBar() const { return m_ticks / s_ticksPerBar; } + // return the bar, rounded up and 0-based - bar_t nextFullBar() const; + bar_t nextFullBar() const { return (m_ticks + (s_ticksPerBar - 1)) / s_ticksPerBar; } + + void setTicks(tick_t ticks) { m_ticks = ticks; } + tick_t getTicks() const { return m_ticks; } - void setTicks( tick_t ticks ); - tick_t getTicks() const; + operator int() const { return m_ticks; } - operator int() const; + tick_t ticksPerBeat(const TimeSig& sig) const { return ticksPerBar(sig) / sig.numerator(); } - tick_t ticksPerBeat( const TimeSig &sig ) const; // Remainder ticks after bar is removed - tick_t getTickWithinBar( const TimeSig &sig ) const; + tick_t getTickWithinBar(const TimeSig& sig) const { return m_ticks % ticksPerBar(sig); } + // Returns the beat position inside the bar, 0-based - tick_t getBeatWithinBar( const TimeSig &sig ) const; + tick_t getBeatWithinBar(const TimeSig& sig) const { return getTickWithinBar(sig) / ticksPerBeat(sig); } + // Remainder ticks after bar and beat are removed - tick_t getTickWithinBeat( const TimeSig &sig ) const; + tick_t getTickWithinBeat(const TimeSig& sig) const { return getTickWithinBar(sig) % ticksPerBeat(sig); } // calculate number of frame that are needed this time - f_cnt_t frames( const float framesPerTick ) const; - - double getTimeInMilliseconds( bpm_t beatsPerMinute ) const; - - static TimePos fromFrames( const f_cnt_t frames, const float framesPerTick ); - static tick_t ticksPerBar(); - static tick_t ticksPerBar( const TimeSig &sig ); - static int stepsPerBar(); - static void setTicksPerBar( tick_t tpt ); - static TimePos stepPosition( int step ); - static double ticksToMilliseconds( tick_t ticks, bpm_t beatsPerMinute ); - static double ticksToMilliseconds( double ticks, bpm_t beatsPerMinute ); + f_cnt_t frames(const float framesPerTick) const + { + // Before, step notes used to have negative length. This + // assert is a safeguard against negative length being + // introduced again (now using Note Types instead #5902) + assert(m_ticks >= 0); + return static_cast(m_ticks * framesPerTick); + } + + double getTimeInMilliseconds(bpm_t beatsPerMinute) const { return ticksToMilliseconds(getTicks(), beatsPerMinute); } + + static TimePos fromFrames(const f_cnt_t frames, const float framesPerTick) + { + return TimePos(static_cast(frames / framesPerTick)); + } + + static tick_t ticksPerBar() { return s_ticksPerBar; } + static tick_t ticksPerBar(const TimeSig& sig) { return DefaultTicksPerBar * sig.numerator() / sig.denominator(); } + + static int stepsPerBar() { return std::max(1, ticksPerBar() / DefaultBeatsPerBar); } + static void setTicksPerBar(tick_t ticks) { s_ticksPerBar = ticks; } + static TimePos stepPosition(int step) { return step * ticksPerBar() / stepsPerBar(); } + + static double ticksToMilliseconds(tick_t ticks, bpm_t beatsPerMinute) + { + return ticksToMilliseconds(static_cast(ticks), beatsPerMinute); + } + + static double ticksToMilliseconds(double ticks, bpm_t beatsPerMinute) { return (ticks * 1250) / beatsPerMinute; } private: tick_t m_ticks; diff --git a/src/core/TimePos.cpp b/src/core/TimePos.cpp index 09c1019bcef..6e5e034fd23 100644 --- a/src/core/TimePos.cpp +++ b/src/core/TimePos.cpp @@ -43,20 +43,6 @@ TimeSig::TimeSig( const MeterModel &model ) : { } - -int TimeSig::numerator() const -{ - return m_num; -} - -int TimeSig::denominator() const -{ - return m_denom; -} - - - - TimePos::TimePos( const bar_t bar, const tick_t ticks ) : m_ticks( bar * s_ticksPerBar + ticks ) { @@ -86,140 +72,4 @@ TimePos TimePos::quantize(float bars) const return (lowPos + snapUp) * interval; } - -TimePos TimePos::toAbsoluteBar() const -{ - return getBar() * s_ticksPerBar; -} - - -TimePos& TimePos::operator+=( const TimePos& time ) -{ - m_ticks += time.m_ticks; - return *this; -} - - -TimePos& TimePos::operator-=( const TimePos& time ) -{ - m_ticks -= time.m_ticks; - return *this; -} - - -bar_t TimePos::getBar() const -{ - return m_ticks / s_ticksPerBar; -} - - -bar_t TimePos::nextFullBar() const -{ - return ( m_ticks + ( s_ticksPerBar - 1 ) ) / s_ticksPerBar; -} - - -void TimePos::setTicks( tick_t ticks ) -{ - m_ticks = ticks; -} - - -tick_t TimePos::getTicks() const -{ - return m_ticks; -} - - -TimePos::operator int() const -{ - return m_ticks; -} - - -tick_t TimePos::ticksPerBeat( const TimeSig &sig ) const -{ - // (number of ticks per bar) divided by (number of beats per bar) - return ticksPerBar(sig) / sig.numerator(); -} - - -tick_t TimePos::getTickWithinBar( const TimeSig &sig ) const -{ - return m_ticks % ticksPerBar( sig ); -} - -tick_t TimePos::getBeatWithinBar( const TimeSig &sig ) const -{ - return getTickWithinBar( sig ) / ticksPerBeat( sig ); -} - -tick_t TimePos::getTickWithinBeat( const TimeSig &sig ) const -{ - return getTickWithinBar( sig ) % ticksPerBeat( sig ); -} - - -f_cnt_t TimePos::frames( const float framesPerTick ) const -{ - // Before, step notes used to have negative length. This - // assert is a safeguard against negative length being - // introduced again (now using Note Types instead #5902) - assert(m_ticks >= 0); - return static_cast(m_ticks * framesPerTick); -} - -double TimePos::getTimeInMilliseconds( bpm_t beatsPerMinute ) const -{ - return ticksToMilliseconds( getTicks(), beatsPerMinute ); -} - -TimePos TimePos::fromFrames( const f_cnt_t frames, const float framesPerTick ) -{ - return TimePos( static_cast( frames / framesPerTick ) ); -} - - -tick_t TimePos::ticksPerBar() -{ - return s_ticksPerBar; -} - - -tick_t TimePos::ticksPerBar( const TimeSig &sig ) -{ - return DefaultTicksPerBar * sig.numerator() / sig.denominator(); -} - - -int TimePos::stepsPerBar() -{ - int steps = ticksPerBar() / DefaultBeatsPerBar; - return std::max(1, steps); -} - - -void TimePos::setTicksPerBar( tick_t tpb ) -{ - s_ticksPerBar = tpb; -} - - -TimePos TimePos::stepPosition( int step ) -{ - return step * ticksPerBar() / stepsPerBar(); -} - -double TimePos::ticksToMilliseconds( tick_t ticks, bpm_t beatsPerMinute ) -{ - return TimePos::ticksToMilliseconds( static_cast(ticks), beatsPerMinute ); -} - -double TimePos::ticksToMilliseconds(double ticks, bpm_t beatsPerMinute) -{ - // 60 * 1000 / 48 = 1250 - return ( ticks * 1250 ) / beatsPerMinute; -} - - } // namespace lmms From b5de1d50e11cf53c9a97a3c09f07f8a96cbfa03f Mon Sep 17 00:00:00 2001 From: cyberrumor <46626673+cyberrumor@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:01:08 -0700 Subject: [PATCH 12/16] Fix PeakController attack/decay, use linear interpolation between samples (#7566) Historically, the PeakController has had issues like attack/decay knobs acting like on/off switches, and audio artifacts like buzzing or clicking. This patch aims to address those issues. The PeakController previously used lerp (linear interpolation) when looping through the sample buffer, which was in updateValueBuffer. This lerp utilized attack/decay values from control knobs. This is not the correct place to utilize attack/decay because the only temporal data available to the function is the frame and sample size. Therefore the coefficient should simply be the sample rate instead. Between each sample, processImpl would set m_lastSample to the RMS without any sort of lerp. This resulted in m_lastSample producing stair-like patterns over time, rather than a smooth line. For context, m_lastSample is used to set the value of whatever is connected to the PeakController. The basic lerp formula is: m_lastSample = m_lastSample + ((1 - attack) * (RMS - m_lastSample)) This is useful because an attack of 0 sets m_lastSample to RMS, whereas an attack of 1 would set m_lastSample to m_lastSample. This means our attack/decay knobs can be used on a range from "snap to the next value immediately" to "never stray from the last value". * Remove attack/decay from PeakController frame lerp. * Set frame lerp coefficient to 100.0 / sample_rate to fix buzzing. * Add lerp between samples for PeakController to fix stairstep bug. * The newly added lerp utilizes (1 - attack or decay) as the coefficient, which means the knobs actually do something now. --- include/PeakController.h | 3 +-- .../PeakControllerEffect/PeakControllerEffect.cpp | 12 +++++++++++- src/core/PeakController.cpp | 13 ++----------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/PeakController.h b/include/PeakController.h index de9da3b1ce1..a22257374d6 100644 --- a/include/PeakController.h +++ b/include/PeakController.h @@ -78,8 +78,7 @@ public slots: static int m_loadCount; static bool m_buggedFile; - float m_attackCoeff; - float m_decayCoeff; + float m_coeff; bool m_coeffNeedsUpdate; } ; diff --git a/plugins/PeakControllerEffect/PeakControllerEffect.cpp b/plugins/PeakControllerEffect/PeakControllerEffect.cpp index 394a80efd4b..b6d05325770 100644 --- a/plugins/PeakControllerEffect/PeakControllerEffect.cpp +++ b/plugins/PeakControllerEffect/PeakControllerEffect.cpp @@ -132,8 +132,18 @@ Effect::ProcessStatus PeakControllerEffect::processImpl(SampleFrame* buf, const float curRMS = sqrt_neg(sum / frames); const float tres = c.m_tresholdModel.value(); const float amount = c.m_amountModel.value() * c.m_amountMultModel.value(); + const float attack = 1.0f - c.m_attackModel.value(); + const float decay = 1.0f - c.m_decayModel.value(); + curRMS = qAbs( curRMS ) < tres ? 0.0f : curRMS; - m_lastSample = qBound( 0.0f, c.m_baseModel.value() + amount * curRMS, 1.0f ); + float target = c.m_baseModel.value() + amount * curRMS; + // Use decay when the volume is decreasing, attack otherwise. + // Since direction can change as often as every sampleBuffer, it's difficult + // to witness attack/decay working in isolation unless using large buffer sizes. + const float t = target < m_lastSample ? decay : attack; + // Set m_lastSample to the interpolation between itself and target. + // When t is 1.0, m_lastSample snaps to target. When t is 0.0, m_lastSample shouldn't change. + m_lastSample = std::clamp(m_lastSample + t * (target - m_lastSample), 0.0f, 1.0f); return ProcessStatus::Continue; } diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 1c38cf4cb33..1b819982a6c 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -80,9 +80,7 @@ void PeakController::updateValueBuffer() { if( m_coeffNeedsUpdate ) { - const float ratio = 44100.0f / Engine::audioEngine()->outputSampleRate(); - m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio ); - m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio ); + m_coeff = 100.0f / Engine::audioEngine()->outputSampleRate(); m_coeffNeedsUpdate = false; } @@ -97,14 +95,7 @@ void PeakController::updateValueBuffer() for( f_cnt_t f = 0; f < frames; ++f ) { const float diff = ( targetSample - m_currentSample ); - if( m_currentSample < targetSample ) // going up... - { - m_currentSample += diff * m_attackCoeff; - } - else if( m_currentSample > targetSample ) // going down - { - m_currentSample += diff * m_decayCoeff; - } + m_currentSample += diff * m_coeff; values[f] = m_currentSample; } } From 9912fd88e3f8f916cf417c08cd002f3b254f785e Mon Sep 17 00:00:00 2001 From: Rossmaxx <74815851+Rossmaxx@users.noreply.github.com> Date: Thu, 31 Oct 2024 05:25:11 +0530 Subject: [PATCH 13/16] Add member variable for base sample rate and inline sample rate getter functions (#7552) Co-authored-by: saker --- include/AudioEngine.h | 19 ++++++++++++++++--- src/core/AudioEngine.cpp | 29 +---------------------------- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/include/AudioEngine.h b/include/AudioEngine.h index f6d8692b52d..b22830221d9 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -33,6 +33,7 @@ #include #include +#include "AudioDevice.h" #include "lmms_basics.h" #include "SampleFrame.h" #include "LocklessList.h" @@ -235,9 +236,20 @@ class LMMS_EXPORT AudioEngine : public QObject } - sample_rate_t baseSampleRate() const; - sample_rate_t outputSampleRate() const; - sample_rate_t inputSampleRate() const; + sample_rate_t baseSampleRate() const { return m_baseSampleRate; } + + + sample_rate_t outputSampleRate() const + { + return m_audioDev != nullptr ? m_audioDev->sampleRate() : m_baseSampleRate; + } + + + sample_rate_t inputSampleRate() const + { + return m_audioDev != nullptr ? m_audioDev->sampleRate() : m_baseSampleRate; + } + inline float masterGain() const { @@ -361,6 +373,7 @@ class LMMS_EXPORT AudioEngine : public QObject SampleFrame* m_inputBuffer[2]; f_cnt_t m_inputBufferFrames[2]; f_cnt_t m_inputBufferSize[2]; + sample_rate_t m_baseSampleRate; int m_inputBufferRead; int m_inputBufferWrite; diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 0da65f426a4..435fa38fa56 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -74,6 +74,7 @@ static thread_local bool s_renderingThread = false; AudioEngine::AudioEngine( bool renderOnly ) : m_renderOnly( renderOnly ), m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), + m_baseSampleRate(std::max(ConfigManager::inst()->value("audioengine", "samplerate").toInt(), 44100)), m_inputBufferRead( 0 ), m_inputBufferWrite( 1 ), m_outputBufferRead(nullptr), @@ -241,34 +242,6 @@ void AudioEngine::stopProcessing() -sample_rate_t AudioEngine::baseSampleRate() const -{ - sample_rate_t sr = ConfigManager::inst()->value( "audioengine", "samplerate" ).toInt(); - if( sr < 44100 ) - { - sr = 44100; - } - return sr; -} - - - - -sample_rate_t AudioEngine::outputSampleRate() const -{ - return m_audioDev != nullptr ? m_audioDev->sampleRate() : - baseSampleRate(); -} - - - - -sample_rate_t AudioEngine::inputSampleRate() const -{ - return m_audioDev != nullptr ? m_audioDev->sampleRate() : - baseSampleRate(); -} - bool AudioEngine::criticalXRuns() const { return cpuLoad() >= 99 && Engine::getSong()->isExporting() == false; From 07baf9e27a4990b2e1cc88b9cbe1347c70366d9d Mon Sep 17 00:00:00 2001 From: saker Date: Sun, 3 Nov 2024 02:13:55 -0500 Subject: [PATCH 14/16] Tidy up `MixerChannelView` (#7527) --- include/MixerChannelView.h | 37 +++++++-------- src/gui/MixerChannelView.cpp | 87 ++++++------------------------------ 2 files changed, 31 insertions(+), 93 deletions(-) diff --git a/include/MixerChannelView.h b/include/MixerChannelView.h index 8074d4dceaa..6716aee094b 100644 --- a/include/MixerChannelView.h +++ b/include/MixerChannelView.h @@ -1,7 +1,7 @@ /* - * MixerChannelView.h - the mixer channel view + * MixerChannelView.h * - * Copyright (c) 2022 saker + * Copyright (c) 2024 saker * * This file is part of LMMS - https://lmms.io * @@ -22,8 +22,8 @@ * */ -#ifndef MIXER_CHANNEL_VIEW_H -#define MIXER_CHANNEL_VIEW_H +#ifndef LMMS_GUI_MIXER_CHANNEL_VIEW_H +#define LMMS_GUI_MIXER_CHANNEL_VIEW_H #include #include @@ -46,8 +46,6 @@ class MixerChannel; namespace lmms::gui { class PeakIndicator; -constexpr int MIXER_CHANNEL_INNER_BORDER_SIZE = 3; -constexpr int MIXER_CHANNEL_OUTER_BORDER_SIZE = 1; class MixerChannelView : public QWidget { @@ -65,25 +63,24 @@ class MixerChannelView : public QWidget void mouseDoubleClickEvent(QMouseEvent*) override; bool eventFilter(QObject* dist, QEvent* event) override; - int channelIndex() const; + void reset(); + int channelIndex() const { return m_channelIndex; } void setChannelIndex(int index); - QBrush backgroundActive() const; - void setBackgroundActive(const QBrush& c); - - QColor strokeOuterActive() const; - void setStrokeOuterActive(const QColor& c); + QBrush backgroundActive() const { return m_backgroundActive; } + void setBackgroundActive(const QBrush& c) { m_backgroundActive = c; } - QColor strokeOuterInactive() const; - void setStrokeOuterInactive(const QColor& c); + QColor strokeOuterActive() const { return m_strokeOuterActive; } + void setStrokeOuterActive(const QColor& c) { m_strokeOuterActive = c; } - QColor strokeInnerActive() const; - void setStrokeInnerActive(const QColor& c); + QColor strokeOuterInactive() const { return m_strokeOuterInactive; } + void setStrokeOuterInactive(const QColor& c) { m_strokeOuterInactive = c; } - QColor strokeInnerInactive() const; - void setStrokeInnerInactive(const QColor& c); + QColor strokeInnerActive() const { return m_strokeInnerActive; } + void setStrokeInnerActive(const QColor& c) { m_strokeInnerActive = c; } - void reset(); + QColor strokeInnerInactive() const { return m_strokeInnerInactive; } + void setStrokeInnerInactive(const QColor& c) { m_strokeInnerInactive = c; } public slots: void renameChannel(); @@ -135,4 +132,4 @@ private slots: }; } // namespace lmms::gui -#endif // MIXER_CHANNEL_VIEW_H +#endif // LMMS_GUI_MIXER_CHANNEL_VIEW_H diff --git a/src/gui/MixerChannelView.cpp b/src/gui/MixerChannelView.cpp index 2228c850882..1432a42cf53 100644 --- a/src/gui/MixerChannelView.cpp +++ b/src/gui/MixerChannelView.cpp @@ -186,21 +186,18 @@ void MixerChannelView::contextMenuEvent(QContextMenuEvent*) delete contextMenu; } -void MixerChannelView::paintEvent(QPaintEvent* event) +void MixerChannelView::paintEvent(QPaintEvent*) { + static constexpr auto innerBorderSize = 3; + static constexpr auto outerBorderSize = 1; + const auto channel = mixerChannel(); - const bool muted = channel->m_muteModel.value(); - const auto name = channel->m_name; - const auto elidedName = elideName(name); const auto isActive = m_mixerView->currentMixerChannel() == this; - - if (!m_inRename && m_renameLineEdit->text() != elidedName) { m_renameLineEdit->setText(elidedName); } - const auto width = rect().width(); const auto height = rect().height(); auto painter = QPainter{this}; - if (channel->color().has_value() && !muted) + if (channel->color().has_value() && !channel->m_muteModel.value()) { painter.fillRect(rect(), channel->color()->darker(isActive ? 120 : 150)); } @@ -208,13 +205,11 @@ void MixerChannelView::paintEvent(QPaintEvent* event) // inner border painter.setPen(isActive ? strokeInnerActive() : strokeInnerInactive()); - painter.drawRect(1, 1, width - MIXER_CHANNEL_INNER_BORDER_SIZE, height - MIXER_CHANNEL_INNER_BORDER_SIZE); + painter.drawRect(1, 1, width - innerBorderSize, height - innerBorderSize); // outer border painter.setPen(isActive ? strokeOuterActive() : strokeOuterInactive()); - painter.drawRect(0, 0, width - MIXER_CHANNEL_OUTER_BORDER_SIZE, height - MIXER_CHANNEL_OUTER_BORDER_SIZE); - - QWidget::paintEvent(event); + painter.drawRect(0, 0, width - outerBorderSize, height - outerBorderSize); } void MixerChannelView::mousePressEvent(QMouseEvent*) @@ -227,7 +222,7 @@ void MixerChannelView::mouseDoubleClickEvent(QMouseEvent*) renameChannel(); } -bool MixerChannelView::eventFilter(QObject* dist, QEvent* event) +bool MixerChannelView::eventFilter(QObject*, QEvent* event) { // If we are in a rename, capture the enter/return events and handle them if (event->type() == QEvent::KeyPress) @@ -246,11 +241,6 @@ bool MixerChannelView::eventFilter(QObject* dist, QEvent* event) return false; } -int MixerChannelView::channelIndex() const -{ - return m_channelIndex; -} - void MixerChannelView::setChannelIndex(int index) { MixerChannel* mixerChannel = Engine::mixer()->mixerChannel(index); @@ -259,64 +249,10 @@ void MixerChannelView::setChannelIndex(int index) m_soloButton->setModel(&mixerChannel->m_soloModel); m_effectRackView->setModel(&mixerChannel->m_fxChain); m_channelNumberLcd->setValue(index); + m_renameLineEdit->setText(elideName(mixerChannel->m_name)); m_channelIndex = index; } -QBrush MixerChannelView::backgroundActive() const -{ - return m_backgroundActive; -} - -void MixerChannelView::setBackgroundActive(const QBrush& c) -{ - m_backgroundActive = c; -} - -QColor MixerChannelView::strokeOuterActive() const -{ - return m_strokeOuterActive; -} - -void MixerChannelView::setStrokeOuterActive(const QColor& c) -{ - m_strokeOuterActive = c; -} - -QColor MixerChannelView::strokeOuterInactive() const -{ - return m_strokeOuterInactive; -} - -void MixerChannelView::setStrokeOuterInactive(const QColor& c) -{ - m_strokeOuterInactive = c; -} - -QColor MixerChannelView::strokeInnerActive() const -{ - return m_strokeInnerActive; -} - -void MixerChannelView::setStrokeInnerActive(const QColor& c) -{ - m_strokeInnerActive = c; -} - -QColor MixerChannelView::strokeInnerInactive() const -{ - return m_strokeInnerInactive; -} - -void MixerChannelView::setStrokeInnerInactive(const QColor& c) -{ - m_strokeInnerInactive = c; -} - -void MixerChannelView::reset() -{ - m_peakIndicator->resetPeakToMinusInf(); -} - void MixerChannelView::renameChannel() { m_inRename = true; @@ -459,4 +395,9 @@ MixerChannel* MixerChannelView::mixerChannel() const return Engine::mixer()->mixerChannel(m_channelIndex); } +void MixerChannelView::reset() +{ + m_peakIndicator->resetPeakToMinusInf(); +} + } // namespace lmms::gui From e36463ce77ca39cd61f2582d729b50e7316c7978 Mon Sep 17 00:00:00 2001 From: Dalton Messmer Date: Wed, 6 Nov 2024 17:46:12 -0500 Subject: [PATCH 15/16] Update macOS CI (#7572) * Use macOS 13 See: https://github.com/actions/runner-images/issues/10721 * Upgrade to XCode 15.2 XCode 15.2 is the default on macOS 13 * Fix unqualified call to std::move warning * Fix sprintf deprecated warnings * Upgrade macOS 14 ARM64 builds to XCode 15.4 See: https://github.com/actions/runner-images/issues/10703 * Fix unused lambda capture warnings in Fader.cpp * Fix unused variable warnings * Fix formatting warning Cannot format `const void*` as a string * Force lambda conversion to function pointer --- .github/workflows/build.yml | 6 ++--- include/RemotePluginBase.h | 4 +-- include/RemotePluginClient.h | 2 +- plugins/Vestige/Vestige.cpp | 12 ++++----- plugins/VstEffect/VstEffectControls.cpp | 10 ++++---- src/core/ComboBoxModel.cpp | 11 +++------ src/core/Keymap.cpp | 2 +- src/core/ProjectRenderer.cpp | 6 ++--- src/core/Scale.cpp | 2 +- src/core/midi/MidiApple.cpp | 6 ++--- src/gui/widgets/Fader.cpp | 33 +++++++++++-------------- 11 files changed, 44 insertions(+), 50 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c2e8dd73b4..74134aabde5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,11 +72,11 @@ jobs: arch: [ x86_64, arm64 ] include: - arch: x86_64 - os: macos-12 - xcode: "13.1" + os: macos-13 + xcode: "15.2" - arch: arm64 os: macos-14 - xcode: "14.3.1" + xcode: "15.4" name: macos-${{ matrix.arch }} runs-on: ${{ matrix.os }} env: diff --git a/include/RemotePluginBase.h b/include/RemotePluginBase.h index 5214b6f9244..bbf14207ac7 100644 --- a/include/RemotePluginBase.h +++ b/include/RemotePluginBase.h @@ -398,7 +398,7 @@ class LMMS_EXPORT RemotePluginBase message & addInt( int _i ) { char buf[32]; - sprintf( buf, "%d", _i ); + std::snprintf(buf, 32, "%d", _i); data.emplace_back( buf ); return *this; } @@ -406,7 +406,7 @@ class LMMS_EXPORT RemotePluginBase message & addFloat( float _f ) { char buf[32]; - sprintf( buf, "%f", _f ); + std::snprintf(buf, 32, "%f", _f); data.emplace_back( buf ); return *this; } diff --git a/include/RemotePluginClient.h b/include/RemotePluginClient.h index 22158f1b81b..24189650688 100644 --- a/include/RemotePluginClient.h +++ b/include/RemotePluginClient.h @@ -309,7 +309,7 @@ bool RemotePluginClient::processMessage( const message & _m ) default: { char buf[64]; - sprintf( buf, "undefined message: %d\n", (int) _m.id ); + std::snprintf(buf, 64, "undefined message: %d\n", _m.id); debugMessage( buf ); break; } diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index 5a7d4afa059..05716008ef1 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -226,7 +226,7 @@ void VestigeInstrument::loadSettings( const QDomElement & _this ) QStringList s_dumpValues; for( int i = 0; i < paramCount; i++ ) { - sprintf(paramStr.data(), "param%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); knobFModel[i] = new FloatModel( 0.0f, 0.0f, 1.0f, 0.01f, this, QString::number(i) ); @@ -290,7 +290,7 @@ void VestigeInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) for( int i = 0; i < paramCount; i++ ) { if (knobFModel[i]->isAutomated() || knobFModel[i]->controllerConnection()) { - sprintf(paramStr.data(), "param%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); knobFModel[i]->saveSettings(_doc, _this, paramStr.data()); } @@ -987,7 +987,7 @@ ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrume for( int i = 0; i < m_vi->paramCount; i++ ) { - sprintf(paramStr.data(), "param%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); vstKnobs[ i ] = new CustomTextKnob( KnobType::Bright26, this, s_dumpValues.at( 1 ) ); @@ -996,7 +996,7 @@ ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrume if( !hasKnobModel ) { - sprintf(paramStr.data(), "%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "%d", i); m_vi->knobFModel[i] = new FloatModel(LocaleHelper::toFloat(s_dumpValues.at(2)), 0.0f, 1.0f, 0.01f, castModel(), paramStr.data()); } @@ -1059,8 +1059,8 @@ void ManageVestigeInstrumentView::syncPlugin( void ) // those auto-setted values are not jurnaled, tracked for undo / redo if( !( m_vi->knobFModel[ i ]->isAutomated() || m_vi->knobFModel[ i ]->controllerConnection() ) ) { - sprintf(paramStr.data(), "param%d", i); - s_dumpValues = dump[paramStr.data()].split(":"); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); + s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); m_vi->knobFModel[ i ]->setAutomatedValue( f_value ); m_vi->knobFModel[ i ]->setInitValue( f_value ); diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index c9eb4923451..ef8bd38d077 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -87,7 +87,7 @@ void VstEffectControls::loadSettings( const QDomElement & _this ) QStringList s_dumpValues; for( int i = 0; i < paramCount; i++ ) { - sprintf(paramStr.data(), "param%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); knobFModel[i] = new FloatModel( 0.0f, 0.0f, 1.0f, 0.01f, this, QString::number(i) ); @@ -137,7 +137,7 @@ void VstEffectControls::saveSettings( QDomDocument & _doc, QDomElement & _this ) for( int i = 0; i < paramCount; i++ ) { if (knobFModel[i]->isAutomated() || knobFModel[i]->controllerConnection()) { - sprintf(paramStr.data(), "param%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); knobFModel[i]->saveSettings(_doc, _this, paramStr.data()); } } @@ -386,7 +386,7 @@ ManageVSTEffectView::ManageVSTEffectView( VstEffect * _eff, VstEffectControls * for( int i = 0; i < m_vi->paramCount; i++ ) { - sprintf(paramStr.data(), "param%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); vstKnobs[ i ] = new CustomTextKnob( KnobType::Bright26, widget, s_dumpValues.at( 1 ) ); @@ -395,7 +395,7 @@ ManageVSTEffectView::ManageVSTEffectView( VstEffect * _eff, VstEffectControls * if( !hasKnobModel ) { - sprintf(paramStr.data(), "%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "%d", i); m_vi->knobFModel[i] = new FloatModel(LocaleHelper::toFloat(s_dumpValues.at(2)), 0.0f, 1.0f, 0.01f, _eff, paramStr.data()); } @@ -460,7 +460,7 @@ void ManageVSTEffectView::syncPlugin() if( !( m_vi2->knobFModel[ i ]->isAutomated() || m_vi2->knobFModel[ i ]->controllerConnection() ) ) { - sprintf(paramStr.data(), "param%d", i); + std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); m_vi2->knobFModel[ i ]->setAutomatedValue( f_value ); diff --git a/src/core/ComboBoxModel.cpp b/src/core/ComboBoxModel.cpp index f1b1c6b2e0f..80d1027deaf 100644 --- a/src/core/ComboBoxModel.cpp +++ b/src/core/ComboBoxModel.cpp @@ -29,20 +29,17 @@ namespace lmms { -using std::unique_ptr; -using std::move; - -void ComboBoxModel::addItem( QString item, unique_ptr loader ) +void ComboBoxModel::addItem(QString item, std::unique_ptr loader) { - m_items.emplace_back( move(item), move(loader) ); + m_items.emplace_back(std::move(item), std::move(loader)); setRange( 0, m_items.size() - 1 ); } -void ComboBoxModel::replaceItem(std::size_t index, QString item, unique_ptr loader) +void ComboBoxModel::replaceItem(std::size_t index, QString item, std::unique_ptr loader) { assert(index < m_items.size()); - m_items[index] = Item(move(item), move(loader)); + m_items[index] = Item(std::move(item), std::move(loader)); emit propertiesChanged(); } diff --git a/src/core/Keymap.cpp b/src/core/Keymap.cpp index 6683919fe36..be422991a6b 100644 --- a/src/core/Keymap.cpp +++ b/src/core/Keymap.cpp @@ -147,7 +147,7 @@ void Keymap::loadSettings(const QDomElement &element) QDomNode node = element.firstChild(); m_map.clear(); - for (int i = 0; !node.isNull(); i++) + while (!node.isNull()) { m_map.push_back(node.toElement().attribute("value").toInt()); node = node.nextSibling(); diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index e5410e6f272..c56c34068b4 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -210,7 +210,7 @@ void ProjectRenderer::abortProcessing() void ProjectRenderer::updateConsoleProgress() { - const int cols = 50; + constexpr int cols = 50; static int rot = 0; auto buf = std::array{}; auto prog = std::array{}; @@ -221,9 +221,9 @@ void ProjectRenderer::updateConsoleProgress() } prog[cols] = 0; - const auto activity = (const char*)"|/-\\"; + const auto activity = "|/-\\"; std::fill(buf.begin(), buf.end(), 0); - sprintf(buf.data(), "\r|%s| %3d%% %c ", prog.data(), m_progress, + std::snprintf(buf.data(), buf.size(), "\r|%s| %3d%% %c ", prog.data(), m_progress, activity[rot] ); rot = ( rot+1 ) % 4; diff --git a/src/core/Scale.cpp b/src/core/Scale.cpp index df0effe6bca..93279f2bcba 100644 --- a/src/core/Scale.cpp +++ b/src/core/Scale.cpp @@ -116,7 +116,7 @@ void Scale::loadSettings(const QDomElement &element) QDomNode node = element.firstChild(); m_intervals.clear(); - for (int i = 0; !node.isNull(); i++) + while (!node.isNull()) { Interval temp; temp.restoreState(node.toElement()); diff --git a/src/core/midi/MidiApple.cpp b/src/core/midi/MidiApple.cpp index 444f093e533..14930ed8486 100644 --- a/src/core/midi/MidiApple.cpp +++ b/src/core/midi/MidiApple.cpp @@ -159,7 +159,7 @@ void MidiApple::removePort( MidiPort* port ) QString MidiApple::sourcePortName( const MidiEvent& event ) const { - qDebug("sourcePortName return '%s'?\n", event.sourcePort()); + qDebug("sourcePortName"); /* if( event.sourcePort() ) { @@ -501,7 +501,7 @@ void MidiApple::openDevices() void MidiApple::openMidiReference( MIDIEndpointRef reference, QString refName, bool isIn ) { char * registeredName = (char*) malloc(refName.length()+1); - sprintf(registeredName, "%s",refName.toLatin1().constData()); + std::snprintf(registeredName, refName.length() + 1, "%s",refName.toLatin1().constData()); qDebug("openMidiReference refName '%s'",refName.toLatin1().constData()); MIDIClientRef mClient = getMidiClientRef(); @@ -623,7 +623,7 @@ char * MidiApple::getFullName(MIDIEndpointRef &endpoint_ref) size_t deviceNameLen = deviceName == nullptr ? 0 : strlen(deviceName); size_t endPointNameLen = endPointName == nullptr ? 0 : strlen(endPointName); char * fullName = (char *)malloc(deviceNameLen + endPointNameLen + 2); - sprintf(fullName, "%s:%s", deviceName,endPointName); + std::snprintf(fullName, deviceNameLen + endPointNameLen + 2, "%s:%s", deviceName,endPointName); if (deviceName != nullptr) { free(deviceName); } if (endPointName != nullptr) { free(endPointName); } return fullName; diff --git a/src/gui/widgets/Fader.cpp b/src/gui/widgets/Fader.cpp index 9a0da4db483..a647df416cf 100644 --- a/src/gui/widgets/Fader.cpp +++ b/src/gui/widgets/Fader.cpp @@ -282,20 +282,17 @@ void Fader::paintEvent(QPaintEvent* ev) void Fader::paintLevels(QPaintEvent* ev, QPainter& painter, bool linear) { - std::function mapper = [this](float value) { return ampToDbfs(qMax(0.0001f, value)); }; - - if (linear) - { - mapper = [this](float value) { return value; }; - } - - const float mappedMinPeak(mapper(m_fMinPeak)); - const float mappedMaxPeak(mapper(m_fMaxPeak)); - const float mappedPeakL(mapper(m_fPeakValue_L)); - const float mappedPeakR(mapper(m_fPeakValue_R)); - const float mappedPersistentPeakL(mapper(m_persistentPeak_L)); - const float mappedPersistentPeakR(mapper(m_persistentPeak_R)); - const float mappedUnity(mapper(1.f)); + const auto mapper = linear + ? +[](float value) -> float { return value; } + : +[](float value) -> float { return ampToDbfs(qMax(0.0001f, value)); }; + + const float mappedMinPeak = mapper(m_fMinPeak); + const float mappedMaxPeak = mapper(m_fMaxPeak); + const float mappedPeakL = mapper(m_fPeakValue_L); + const float mappedPeakR = mapper(m_fPeakValue_R); + const float mappedPersistentPeakL = mapper(m_persistentPeak_L); + const float mappedPersistentPeakR = mapper(m_persistentPeak_R); + const float mappedUnity = mapper(1.f); painter.save(); @@ -375,10 +372,10 @@ void Fader::paintLevels(QPaintEvent* ev, QPainter& painter, bool linear) // Please ensure that "clip starts" is the maximum value and that "ok ends" // is the minimum value and that all other values lie inbetween. Otherwise // there will be warnings when the gradient is defined. - const float mappedClipStarts(mapper(dbfsToAmp(0.f))); - const float mappedWarnEnd(mapper(dbfsToAmp(-0.01f))); - const float mappedWarnStart(mapper(dbfsToAmp(-6.f))); - const float mappedOkEnd(mapper(dbfsToAmp(-12.f))); + const float mappedClipStarts = mapper(dbfsToAmp(0.f)); + const float mappedWarnEnd = mapper(dbfsToAmp(-0.01f)); + const float mappedWarnStart = mapper(dbfsToAmp(-6.f)); + const float mappedOkEnd = mapper(dbfsToAmp(-12.f)); // Prepare the gradient for the meters // From ada836c989c04ede49670672999060c21ff737aa Mon Sep 17 00:00:00 2001 From: Kapandaria Date: Fri, 8 Nov 2024 23:15:34 +0200 Subject: [PATCH 16/16] Fix crash on Xpressive when using `integrate` function (#7499) * Fixed a bug in the integrate function, that was caused by warning fixes session. * Xpressive - fixed code style issues. --- plugins/Xpressive/ExprSynth.cpp | 43 ++++++++++++++++++++++----------- plugins/Xpressive/Xpressive.cpp | 20 +++++++++------ 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/plugins/Xpressive/ExprSynth.cpp b/plugins/Xpressive/ExprSynth.cpp index c48b94ec8dc..e6783211d8e 100644 --- a/plugins/Xpressive/ExprSynth.cpp +++ b/plugins/Xpressive/ExprSynth.cpp @@ -83,9 +83,10 @@ struct IntegrateFunction : public exprtk::ifunction IntegrateFunction(const unsigned int* frame, unsigned int sample_rate,unsigned int max_counters) : exprtk::ifunction(1), + m_firstValue(0), m_frame(frame), - m_sample_rate(sample_rate), - m_max_counters(max_counters), + m_sampleRate(sample_rate), + m_maxCounters(max_counters), m_nCounters(0), m_nCountersCalls(0), m_cc(0) @@ -96,15 +97,26 @@ struct IntegrateFunction : public exprtk::ifunction inline T operator()(const T& x) override { - if (*m_frame == 0) + if (m_frame) { - ++m_nCountersCalls; - if (m_nCountersCalls > m_max_counters) + if (m_nCountersCalls == 0) + { + m_firstValue = *m_frame; + } + if (m_firstValue == *m_frame) { - return 0; + ++m_nCountersCalls; + if (m_nCountersCalls > m_maxCounters) + { + return 0; + } + m_cc = m_nCounters; + ++m_nCounters; + } + else // we moved to the next frame + { + m_frame = 0; // this will indicate that we are no longer in init phase. } - m_cc = m_nCounters; - ++m_nCounters; } T res = 0; @@ -114,13 +126,16 @@ struct IntegrateFunction : public exprtk::ifunction m_counters[m_cc] += x; } m_cc = (m_cc + 1) % m_nCountersCalls; - return res / m_sample_rate; - } - - const unsigned int* const m_frame; - const unsigned int m_sample_rate; - const unsigned int m_max_counters; + return res / m_sampleRate; + } + unsigned int m_firstValue; + const unsigned int* m_frame; + const unsigned int m_sampleRate; + // number of counters allocated + const unsigned int m_maxCounters; + // number of integrate instances that has counters allocated unsigned int m_nCounters; + // real number of integrate instances unsigned int m_nCountersCalls; unsigned int m_cc; double *m_counters; diff --git a/plugins/Xpressive/Xpressive.cpp b/plugins/Xpressive/Xpressive.cpp index 23a76b22820..5ee7dcf8d89 100644 --- a/plugins/Xpressive/Xpressive.cpp +++ b/plugins/Xpressive/Xpressive.cpp @@ -553,7 +553,7 @@ void XpressiveView::expressionChanged() { ExprFront expr(text.constData(),sample_rate); float t=0; const float f=10,key=5,v=0.5; - unsigned int i; + unsigned int frame_counter = 0; expr.add_variable("t", t); if (m_output_expr) @@ -572,20 +572,24 @@ void XpressiveView::expressionChanged() { expr.add_cyclic_vector("W2",e->graphW2().samples(),e->graphW2().length()); expr.add_cyclic_vector("W3",e->graphW3().samples(),e->graphW3().length()); } - expr.setIntegrate(&i,sample_rate); + expr.setIntegrate(&frame_counter,sample_rate); expr.add_constant("srate",sample_rate); const bool parse_ok=expr.compile(); if (parse_ok) { e->exprValid().setValue(0); - const auto length = static_cast(m_raw_graph->length()); + const unsigned int length = static_cast(m_raw_graph->length()); auto const samples = new float[length]; - for (auto i = std::size_t{0}; i < length; i++) { - t = i / (float) length; - samples[i] = expr.evaluate(); - if (std::isinf(samples[i]) != 0 || std::isnan(samples[i]) != 0) - samples[i] = 0; + // frame_counter's reference is used in the integrate function. + for (frame_counter = 0; frame_counter < length; ++frame_counter) + { + t = frame_counter / (float) length; + samples[frame_counter] = expr.evaluate(); + if (std::isinf(samples[frame_counter]) != 0 || std::isnan(samples[frame_counter]) != 0) + { + samples[frame_counter] = 0; + } } m_raw_graph->setSamples(samples); delete[] samples;