diff --git a/CMakeLists.txt b/CMakeLists.txt index 89b5fbaf84f..d7f9811acd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1219,6 +1219,7 @@ add_library( src/library/missing_hidden/hiddentablemodel.cpp src/library/missing_hidden/missingtablemodel.cpp src/library/mixxxlibraryfeature.cpp + src/library/overviewcache.cpp src/library/parser.cpp src/library/parsercsv.cpp src/library/parserm3u.cpp @@ -1248,6 +1249,7 @@ add_library( src/library/tabledelegates/keydelegate.cpp src/library/tabledelegates/locationdelegate.cpp src/library/tabledelegates/multilineeditdelegate.cpp + src/library/tabledelegates/overviewdelegate.cpp src/library/tabledelegates/previewbuttondelegate.cpp src/library/tabledelegates/stardelegate.cpp src/library/tabledelegates/stareditor.cpp @@ -1470,11 +1472,13 @@ add_library( src/util/workerthreadscheduler.cpp src/util/xml.cpp src/waveform/guitick.cpp + src/waveform/overviewtype.cpp src/waveform/renderers/glwaveformrenderbackground.cpp src/waveform/renderers/glvsynctestrenderer.cpp src/waveform/renderers/waveformmark.cpp src/waveform/renderers/waveformmarkrange.cpp src/waveform/renderers/waveformmarkset.cpp + src/waveform/renderers/waveformoverviewrenderer.cpp src/waveform/renderers/waveformrenderbackground.cpp src/waveform/renderers/waveformrenderbeat.cpp src/waveform/renderers/waveformrendererabstract.cpp diff --git a/res/skins/Deere/library.xml b/res/skins/Deere/library.xml index 1f08cc11345..235578a2796 100644 --- a/res/skins/Deere/library.xml +++ b/res/skins/Deere/library.xml @@ -72,6 +72,10 @@ false 0.175 + + + diff --git a/res/skins/Deere/preview_deck.xml b/res/skins/Deere/preview_deck.xml index 1806cb669f5..7437022b816 100644 --- a/res/skins/Deere/preview_deck.xml +++ b/res/skins/Deere/preview_deck.xml @@ -100,7 +100,7 @@ - #FF8000 + #00FF00 false diff --git a/res/skins/Deere/sampler_controls_row.xml b/res/skins/Deere/sampler_controls_row.xml index a56c178839a..62e5b8d6a88 100644 --- a/res/skins/Deere/sampler_controls_row.xml +++ b/res/skins/Deere/sampler_controls_row.xml @@ -42,7 +42,7 @@ - #FF8000 + #00FF00 false diff --git a/res/skins/Deere/skin.xml b/res/skins/Deere/skin.xml index 74f7ad973c8..0cbd46f1bc9 100644 --- a/res/skins/Deere/skin.xml +++ b/res/skins/Deere/skin.xml @@ -135,6 +135,9 @@ 22,-1 f,me + + #FF8000 + + + diff --git a/res/skins/Shade/deck_overview.xml b/res/skins/Shade/deck_overview.xml index 5d7ad987fc0..044a455346e 100644 --- a/res/skins/Shade/deck_overview.xml +++ b/res/skins/Shade/deck_overview.xml @@ -9,7 +9,7 @@ - #191F24 + #00FF00 #EA0000 diff --git a/res/skins/Shade/preview_deck.xml b/res/skins/Shade/preview_deck.xml index aa132122d68..c260cdc88ad 100644 --- a/res/skins/Shade/preview_deck.xml +++ b/res/skins/Shade/preview_deck.xml @@ -171,7 +171,7 @@ - #191F24 + #00FF00 false diff --git a/res/skins/Shade/sampler.xml b/res/skins/Shade/sampler.xml index ec147651d11..09a06ffccfb 100644 --- a/res/skins/Shade/sampler.xml +++ b/res/skins/Shade/sampler.xml @@ -176,7 +176,7 @@ - #191F24 + #00FF00 false diff --git a/res/skins/Shade/skin.xml b/res/skins/Shade/skin.xml index 57584106cfd..a87cae5b918 100644 --- a/res/skins/Shade/skin.xml +++ b/res/skins/Shade/skin.xml @@ -273,6 +273,17 @@ vertical + + + #191F24 + #55F764 + + #8d98a3 + + + - #55F764 - - #8d98a3 - - Overview1 diff --git a/res/skins/Tango/decks/preview_deck.xml b/res/skins/Tango/decks/preview_deck.xml index f31cd98321f..9d156a3a429 100644 --- a/res/skins/Tango/decks/preview_deck.xml +++ b/res/skins/Tango/decks/preview_deck.xml @@ -114,7 +114,7 @@ Variables: 1me,34f #151515 - #bababa + #FF4300 #00FF00 diff --git a/res/skins/Tango/library.xml b/res/skins/Tango/library.xml index 090bea24728..3add726203b 100644 --- a/res/skins/Tango/library.xml +++ b/res/skins/Tango/library.xml @@ -118,6 +118,10 @@ Description: #eece33 false 0.2 + + + diff --git a/res/skins/Tango/mic_aux_sampler/sampler.xml b/res/skins/Tango/mic_aux_sampler/sampler.xml index 305776cdbf8..83ff7c842d3 100644 --- a/res/skins/Tango/mic_aux_sampler/sampler.xml +++ b/res/skins/Tango/mic_aux_sampler/sampler.xml @@ -200,7 +200,7 @@ Variables: me,min #151515 - #bababa + #FF4300 false diff --git a/res/skins/Tango/skin.xml b/res/skins/Tango/skin.xml index 052b60090d3..d2cd9c342c0 100644 --- a/res/skins/Tango/skin.xml +++ b/res/skins/Tango/skin.xml @@ -182,6 +182,8 @@ #19260B purple green + + #bababa empty pixmap, add to ignore list"; + m_tracksWithoutOverview.insert(res.trackId); + } + m_currentlyLoading.remove(res.trackId); + + emit overviewReady(res.requester, res.trackId, !pixmap.isNull()); +} diff --git a/src/library/overviewcache.h b/src/library/overviewcache.h new file mode 100644 index 00000000000..a052f211e42 --- /dev/null +++ b/src/library/overviewcache.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include "analyzer/analyzerprogress.h" +#include "preferences/usersettings.h" +#include "track/track.h" +#include "util/db/dbconnectionpool.h" +#include "util/singleton.h" +#include "waveform/overviewtype.h" + +class WaveformSignalColors; + +class OverviewCache : public QObject, public Singleton { + Q_OBJECT + public: + void onTrackSummaryChanged(TrackId); + + QPixmap requestCachedOverview( + const mixxx::OverviewType type, + const TrackId trackId, + const QObject* pRequester, + const QSize desiredSize); + QPixmap requestUncachedOverview( + const mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + const TrackId trackId, + const QObject* pRequester, + const QSize desiredSize); + + struct FutureResult { + FutureResult() + : requester(nullptr) { + } + + TrackId trackId; + mixxx::OverviewType type; + QImage image; + QSize resizedToSize; + const QObject* requester; + }; + + public slots: + void onNormalizeOrVisualGainChanged(); + void overviewPrepared(); + void onTrackAnalysisProgress(TrackId trackId, AnalyzerProgress analyzerProgress); + + signals: + void overviewReady( + const QObject* pRequester, + const TrackId trackId, + bool pixmapValid); + + void overviewChanged(TrackId); + + protected: + OverviewCache(UserSettingsPointer pConfig, + mixxx::DbConnectionPoolPtr m_pDbConnectionPool); + virtual ~OverviewCache() override = default; + friend class Singleton; + + static FutureResult prepareOverview( + UserSettingsPointer pConfig, + mixxx::DbConnectionPoolPtr pDbConnectionPool, + const mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + const TrackId trackId, + const QObject* pRequester, + const QSize desiredSize); + + private: + UserSettingsPointer m_pConfig; + mixxx::DbConnectionPoolPtr m_pDbConnectionPool; + + QSet m_currentlyLoading; + QSet m_tracksWithoutOverview; + QMultiHash m_cacheKeysByTrackId; + bool m_clearingCache; + bool m_stopClearing; +}; diff --git a/src/library/tabledelegates/overviewdelegate.cpp b/src/library/tabledelegates/overviewdelegate.cpp new file mode 100644 index 00000000000..ec6f93fb689 --- /dev/null +++ b/src/library/tabledelegates/overviewdelegate.cpp @@ -0,0 +1,172 @@ +#include "library/tabledelegates/overviewdelegate.h" + +#include + +#include "control/controlproxy.h" +#include "library/dao/trackdao.h" +#include "library/overviewcache.h" +#include "library/trackmodel.h" +#include "moc_overviewdelegate.cpp" +#include "util/logger.h" +#include "util/make_const_iterator.h" +#include "widget/wlibrary.h" + +namespace { + +const mixxx::Logger kLogger("OverviewDelegate"); + +inline TrackModel* asTrackModel( + QTableView* pTableView) { + auto* pTrackModel = + dynamic_cast(pTableView->model()); + DEBUG_ASSERT(pTrackModel); + return pTrackModel; +} + +inline WLibrary* findLibraryWidgetParent(QWidget* pWidget) { + while (pWidget) { + WLibrary* pLibrary = qobject_cast(pWidget); + if (pLibrary) { + return pLibrary; + } + + pWidget = pWidget->parentWidget(); + } + + return nullptr; +} + +} // anonymous namespace + +OverviewDelegate::OverviewDelegate(QTableView* pTableView) + : TableItemDelegate(pTableView), + m_pTrackModel(asTrackModel(pTableView)), + m_pCache(OverviewCache::instance()), + m_type(mixxx::OverviewType::RGB), + m_inhibitLazyLoading(false) { + WLibrary* pLibrary = findLibraryWidgetParent(pTableView); + if (pLibrary) { + m_signalColors = pLibrary->getOverviewSignalColors(); + } + + connect(m_pCache, + &OverviewCache::overviewReady, + this, + &OverviewDelegate::slotOverviewReady); + + connect(m_pCache, + &OverviewCache::overviewChanged, + this, + &OverviewDelegate::slotOverviewChanged); + + m_pTypeControl = make_parented( + QStringLiteral("[Waveform]"), + QStringLiteral("WaveformOverviewType"), + this); + m_pTypeControl->connectValueChanged(this, &OverviewDelegate::slotTypeControlChanged); + slotTypeControlChanged(m_pTypeControl->get()); +} + +void OverviewDelegate::slotTypeControlChanged(double v) { + // Assert that v is in enum range to prevent UB. + DEBUG_ASSERT(v >= 0 && v < QMetaEnum::fromType().keyCount()); + mixxx::OverviewType type = static_cast(static_cast(v)); + if (type == m_type) { + return; + } + + m_type = type; + // Instantly update visible overviews so we get a live preview + // when changing the type in the preferences. + m_pTableView->update(); +} + +void OverviewDelegate::emitOverviewRowsChanged(QSet&& trackIds) { + if (trackIds.isEmpty()) { + return; + } + + QList rows; + for (auto id : trackIds) { + const QList idRows = m_pTrackModel->getTrackRows(id); + rows.append(idRows); + } + if (rows.isEmpty()) { + // For some reason this can happen during startup + return; + } + // Sort in ascending order... + std::sort(rows.begin(), rows.end()); + // ...and then deduplicate... + constErase( + &rows, + make_const_iterator( + rows, std::unique(rows.begin(), rows.end())), + rows.constEnd()); + // ...before emitting the signal. + DEBUG_ASSERT(!rows.isEmpty()); + emit overviewRowsChanged(std::move(rows)); +} + +void OverviewDelegate::slotInhibitLazyLoading(bool inhibitLazyLoading) { + m_inhibitLazyLoading = inhibitLazyLoading; + if (m_inhibitLazyLoading || m_cacheMissIds.isEmpty()) { + return; + } + // If we can request non-cache covers now, request updates + // for all rows that were cache misses since the last time. + // Reset the member variable before mutating the aggregated + // rows list (-> implicit sharing) and emitting a signal that + // in turn may trigger new signals for CoverArtDelegate! + QSet staleIds = std::move(m_cacheMissIds); + DEBUG_ASSERT(m_cacheMissIds.isEmpty()); + emitOverviewRowsChanged(std::move(staleIds)); +} + +/// Maybe request repaint via dataChanged() by BaseTrackTableModel +void OverviewDelegate::slotOverviewReady(const QObject* pRequester, + const TrackId trackId, + bool pixmapValid) { + // kLogger.info() << "slotOverviewReady()" << trackId << "pixmap valid:" << pixmapValid; + + if (pRequester == this && pixmapValid) { + emitOverviewRowsChanged(QSet{trackId}); + } +} + +/// Maybe request repaint via dataChanged() by BaseTrackTableModel +void OverviewDelegate::slotOverviewChanged(const TrackId trackId) { + // kLogger.info() << "slotOverviewChanged()" << trackId; + emitOverviewRowsChanged(QSet{trackId}); +} + +void OverviewDelegate::paintItem(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { + const TrackId trackId(m_pTrackModel->getTrackId(index)); + const double scaleFactor = m_pTableView->devicePixelRatioF(); + QPixmap pixmap = m_pCache->requestCachedOverview(m_type, + trackId, + this, + option.rect.size() * scaleFactor); + if (pixmap.isNull()) { + // Cache miss + if (m_inhibitLazyLoading) { + // We are requesting cache-only covers and got a cache + // miss. Record this row so that when we switch to requesting + // non-cache we can request an update. + m_cacheMissIds.insert(trackId); + } else { + pixmap = m_pCache->requestUncachedOverview(m_type, + m_signalColors, + trackId, + this, + option.rect.size() * scaleFactor); + } + paintItemBackground(painter, option, index); + } else { + // We have a cached pixmap, paint it + pixmap.setDevicePixelRatio(scaleFactor); + painter->drawPixmap(option.rect, pixmap); + } +} diff --git a/src/library/tabledelegates/overviewdelegate.h b/src/library/tabledelegates/overviewdelegate.h new file mode 100644 index 00000000000..219ccba1d75 --- /dev/null +++ b/src/library/tabledelegates/overviewdelegate.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +#include "library/tabledelegates/tableitemdelegate.h" +#include "track/trackid.h" +#include "util/parented_ptr.h" +#include "waveform/overviewtype.h" +#include "waveform/renderers/waveformsignalcolors.h" + +class ControlProxy; +class OverviewCache; +class TrackModel; + +class OverviewDelegate : public TableItemDelegate { + Q_OBJECT + + public: + explicit OverviewDelegate(QTableView* parent); + ~OverviewDelegate() override = default; + + void paintItem(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const final; + + signals: + void overviewRowsChanged(const QList& rows); + + public slots: + // Advise the delegate to temporarily inhibit lazy loading + // of overview images and to only display those images + // that have already been cached. + // + // It is useful to handle cases when the user scroll down + // very fast or when they hold an arrow key. In this case + // it is NOT desirable to start multiple expensive file + // system operations for loading and scaling images that + // are not even displayed after scrolling beyond them. + void slotInhibitLazyLoading(bool inhibitLazyLoading); + + private slots: + void slotTypeControlChanged(double v); + void slotOverviewReady(const QObject* pRequester, + const TrackId trackId, + bool pixmapValid); + void slotOverviewChanged(const TrackId trackId); + + protected: + TrackModel* const m_pTrackModel; + + private: + void emitOverviewRowsChanged(QSet&& trackIds); + OverviewCache* const m_pCache; + mixxx::OverviewType m_type; + bool m_inhibitLazyLoading; + parented_ptr m_pTypeControl; + WaveformSignalColors m_signalColors; + + mutable QSet m_cacheMissIds; +}; diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp index 3d0edcef530..e703a3a016b 100644 --- a/src/mixxxmainwindow.cpp +++ b/src/mixxxmainwindow.cpp @@ -38,6 +38,7 @@ #ifdef __ENGINEPRIME__ #include "library/export/libraryexporter.h" #endif +#include "library/overviewcache.h" #include "library/trackcollectionmanager.h" #include "mixer/playerinfo.h" #include "mixer/playermanager.h" @@ -274,6 +275,18 @@ void MixxxMainWindow::initialize() { WaveformWidgetFactory::createInstance(); // takes a long time WaveformWidgetFactory::instance()->setConfig(m_pCoreServices->getSettings()); WaveformWidgetFactory::instance()->startVSync(m_pGuiTick, m_pVisualsManager); + // Connect OverviewCache so we can clear and re-render overviews in the library + // when "OverviewNormalized" or "VisualGain_0" (all) have been changed in the + // preferences. + auto* pOverviewCache = OverviewCache::instance(); + connect(WaveformWidgetFactory::instance(), + &WaveformWidgetFactory::overallVisualGainChanged, + pOverviewCache, + &OverviewCache::onNormalizeOrVisualGainChanged); + connect(WaveformWidgetFactory::instance(), + &WaveformWidgetFactory::overviewNormalizeChanged, + pOverviewCache, + &OverviewCache::onNormalizeOrVisualGainChanged); connect(this, &MixxxMainWindow::skinLoaded, diff --git a/src/preferences/dialog/dlgprefwaveform.cpp b/src/preferences/dialog/dlgprefwaveform.cpp index 3e4c50ea460..67b1b65c71f 100644 --- a/src/preferences/dialog/dlgprefwaveform.cpp +++ b/src/preferences/dialog/dlgprefwaveform.cpp @@ -8,9 +8,9 @@ #include "moc_dlgprefwaveform.cpp" #include "preferences/waveformsettings.h" #include "util/db/dbconnectionpooled.h" +#include "waveform/overviewtype.h" #include "waveform/renderers/waveformwidgetrenderer.h" #include "waveform/waveformwidgetfactory.h" -#include "widget/woverview.h" namespace { const ConfigKey kOverviewTypeCfgKey(QStringLiteral("[Waveform]"), @@ -28,20 +28,20 @@ DlgPrefWaveform::DlgPrefWaveform( // Waveform overview init waveformOverviewComboBox->addItem( - tr("Filtered"), QVariant::fromValue(WOverview::Type::Filtered)); - waveformOverviewComboBox->addItem(tr("HSV"), QVariant::fromValue(WOverview::Type::HSV)); - waveformOverviewComboBox->addItem(tr("RGB"), QVariant::fromValue(WOverview::Type::RGB)); + tr("Filtered"), QVariant::fromValue(mixxx::OverviewType::Filtered)); + waveformOverviewComboBox->addItem(tr("HSV"), QVariant::fromValue(mixxx::OverviewType::HSV)); + waveformOverviewComboBox->addItem(tr("RGB"), QVariant::fromValue(mixxx::OverviewType::RGB)); m_pTypeControl = std::make_unique(kOverviewTypeCfgKey); - m_pTypeControl->setStates(QMetaEnum::fromType().keyCount()); + m_pTypeControl->setStates(QMetaEnum::fromType().keyCount()); m_pTypeControl->setReadOnly(); // Update the control with the config value - WOverview::Type overviewType = - m_pConfig->getValue(kOverviewTypeCfgKey, WOverview::Type::RGB); + mixxx::OverviewType overviewType = + m_pConfig->getValue(kOverviewTypeCfgKey, mixxx::OverviewType::RGB); int cfgTypeIndex = waveformOverviewComboBox->findData(QVariant::fromValue(overviewType)); if (cfgTypeIndex == -1) { // Invalid config value, set default type RGB and write it to config waveformOverviewComboBox->setCurrentIndex( - waveformOverviewComboBox->findData(QVariant::fromValue(WOverview::Type::RGB))); + waveformOverviewComboBox->findData(QVariant::fromValue(mixxx::OverviewType::RGB))); m_pConfig->setValue(kOverviewTypeCfgKey, cfgTypeIndex); } else { waveformOverviewComboBox->setCurrentIndex(cfgTypeIndex); @@ -304,10 +304,10 @@ void DlgPrefWaveform::slotUpdate() { WaveformWidgetFactory::toUntilMarkTextHeightLimitIndex( factory->getUntilMarkTextHeightLimit())); - WOverview::Type cfgOverviewType = - m_pConfig->getValue(kOverviewTypeCfgKey, WOverview::Type::RGB); + mixxx::OverviewType cfgOverviewType = + m_pConfig->getValue(kOverviewTypeCfgKey, mixxx::OverviewType::RGB); // Assumes the combobox index is in sync with the ControlPushButton - if (cfgOverviewType != waveformOverviewComboBox->currentData().value()) { + if (cfgOverviewType != waveformOverviewComboBox->currentData().value()) { int cfgOverviewTypeIndex = waveformOverviewComboBox->findData(QVariant::fromValue(cfgOverviewType)); waveformOverviewComboBox->setCurrentIndex(cfgOverviewTypeIndex); @@ -368,7 +368,7 @@ void DlgPrefWaveform::slotResetToDefaults() { // RGB overview. waveformOverviewComboBox->setCurrentIndex( - waveformOverviewComboBox->findData(QVariant::fromValue(WOverview::Type::RGB))); + waveformOverviewComboBox->findData(QVariant::fromValue(mixxx::OverviewType::RGB))); // Don't normalize overview. normalizeOverviewCheckBox->setChecked(false); @@ -551,8 +551,8 @@ void DlgPrefWaveform::updateEnableUntilMark() { void DlgPrefWaveform::slotSetWaveformOverviewType() { // Apply immediately QVariant comboboxData = waveformOverviewComboBox->currentData(); - DEBUG_ASSERT(comboboxData.canConvert()); - auto type = comboboxData.value(); + DEBUG_ASSERT(comboboxData.canConvert()); + auto type = comboboxData.value(); m_pConfig->setValue(kOverviewTypeCfgKey, type); m_pTypeControl->forceSet(static_cast(type)); } diff --git a/src/skin/legacy/legacyskinparser.cpp b/src/skin/legacy/legacyskinparser.cpp index a509f8c0fb9..6b52f1c0f7e 100644 --- a/src/skin/legacy/legacyskinparser.cpp +++ b/src/skin/legacy/legacyskinparser.cpp @@ -1049,6 +1049,13 @@ QWidget* LegacySkinParser::parseOverview(const QDomElement& node) { &PlayerManager::slotLoadLocationToPlayerMaybePlay); connect(overviewWidget, &WOverview::cloneDeck, m_pPlayerManager, &PlayerManager::slotCloneDeck); + // This signal stems from AnalysisFeature, and the overview can now + // render the analysis progress like when listening to Playermanager's + // analysis progress signal when loading a track. + connect(m_pLibrary, + &Library::onTrackAnalyzerProgress, + overviewWidget, + &WOverview::onTrackAnalyzerProgress); commonWidgetSetup(node, overviewWidget); overviewWidget->setup(node, *m_pContext); diff --git a/src/waveform/overviewtype.cpp b/src/waveform/overviewtype.cpp new file mode 100644 index 00000000000..5077ce71b68 --- /dev/null +++ b/src/waveform/overviewtype.cpp @@ -0,0 +1,4 @@ +// just a stub so the MOC file can be included somewhere +#include "overviewtype.h" + +#include "moc_overviewtype.cpp" diff --git a/src/waveform/overviewtype.h b/src/waveform/overviewtype.h new file mode 100644 index 00000000000..811c9a3a2a1 --- /dev/null +++ b/src/waveform/overviewtype.h @@ -0,0 +1,16 @@ +#pragma once + +// required for Qt-Macros +#include + +namespace mixxx { +Q_NAMESPACE + +enum class OverviewType { + Filtered, + HSV, + RGB, +}; +Q_ENUM_NS(OverviewType); + +} // namespace mixxx diff --git a/src/waveform/renderers/waveformoverviewrenderer.cpp b/src/waveform/renderers/waveformoverviewrenderer.cpp new file mode 100644 index 00000000000..362c1691cfa --- /dev/null +++ b/src/waveform/renderers/waveformoverviewrenderer.cpp @@ -0,0 +1,328 @@ +#include "waveformoverviewrenderer.h" + +#include + +#include "util/colorcomponents.h" +#include "util/math.h" +#include "waveform/renderers/waveformsignalcolors.h" +#include "waveform/waveformwidgetfactory.h" + +QImage WaveformOverviewRenderer::render(ConstWaveformPointer pWaveform, + mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + bool mono) { + const int dataSize = pWaveform->getDataSize(); + if (dataSize <= 0) { + return QImage(); + } + + QImage image(dataSize / 2, 2 * 255, QImage::Format_ARGB32_Premultiplied); + image.fill(QColor(0, 0, 0, 0).value()); + + QPainter painter(&image); + painter.translate(0.0, static_cast(image.height()) / 2.0); + + if (type == mixxx::OverviewType::HSV) { + drawWaveformPartHSV(&painter, + pWaveform, + nullptr, + dataSize, + signalColors, + mono); + } else if (type == mixxx::OverviewType::Filtered) { + drawWaveformPartLMH(&painter, + pWaveform, + nullptr, + dataSize, + signalColors, + mono); + } else { + drawWaveformPartRGB(&painter, + pWaveform, + nullptr, + dataSize, + signalColors, + mono); + } + + // Evaluate waveform ratio peak + float peak = 1; + for (int i = 0; i < dataSize; i += 2) { + peak = math_max3( + peak, + static_cast(pWaveform->getAll(i)), + static_cast(pWaveform->getAll(i + 1))); + } + // Normalize + WaveformWidgetFactory* widgetFactory = WaveformWidgetFactory::instance(); + float diffGain = 0; + bool normalize = widgetFactory->isOverviewNormalized(); + if (normalize && peak > 1) { + diffGain = 255 - peak - 1; + } else { + const auto visualGain = static_cast( + widgetFactory->getVisualGain(WaveformWidgetFactory::All)); + diffGain = 255.0f - (255.0f / visualGain); + } + + const int topLeft = static_cast(mono ? diffGain * 2 : diffGain); + const QRect sourceRect(0, + topLeft, + image.width(), + image.height() - + 2 * static_cast(diffGain)); + QImage croppedImage = image.copy(sourceRect); + // Copy image, otherwise QPainter crashes when we alter it. + QImage normImage = croppedImage.scaled(image.size(), + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + + return normImage; +} + +void WaveformOverviewRenderer::drawWaveformPartRGB( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono) { + int startVal = 0; + if (start) { + startVal = *start; + } + + const QColor lowColor = signalColors.getRgbLowColor(); + const QColor midColor = signalColors.getRgbMidColor(); + const QColor highColor = signalColors.getRgbHighColor(); + QColor color; + + float lowColor_r = 0, lowColor_g = 0, lowColor_b = 0, + midColor_r = 0, midColor_g = 0, midColor_b = 0, + highColor_r = 0, highColor_g = 0, highColor_b = 0, + all = 0, low = 0, mid = 0, high = 0, + red = 0, green = 0, blue = 0, max = 0; + + getRgbF(lowColor, &lowColor_r, &lowColor_g, &lowColor_b); + getRgbF(midColor, &midColor_r, &midColor_g, &midColor_b); + getRgbF(highColor, &highColor_r, &highColor_g, &highColor_b); + + if (mono) { + // Mono means we're going to paint from bottom to top with l+r. + const qreal dy = pPainter->deviceTransform().dy(); + pPainter->resetTransform(); + // shift y0 to bottom + pPainter->translate(0, 2 * dy); + // flip y-axis + pPainter->scale(1, -1); + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + // Left + all = pWaveform->getAll(i) + pWaveform->getAll(i + 1); + low = pWaveform->getLow(i) + pWaveform->getLow(i + 1); + mid = pWaveform->getMid(i) + pWaveform->getMid(i + 1); + high = pWaveform->getHigh(i) + pWaveform->getHigh(i + 1); + + red = low * lowColor_r + mid * midColor_r + high * highColor_r; + green = low * lowColor_g + mid * midColor_g + high * highColor_g; + blue = low * lowColor_b + mid * midColor_b + high * highColor_b; + // Normalize + max = math_max3(red, green, blue); + // Draw + if (max > 0.0) { + color.setRgbF(static_cast(low / max), + static_cast(mid / max), + static_cast(high / max)); + pPainter->setPen(color); + pPainter->drawLine(x, static_cast(all), x, 0); + } + } + } else { // stereo + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + // Left + all = pWaveform->getAll(i); + low = pWaveform->getLow(i); + mid = pWaveform->getMid(i); + high = pWaveform->getHigh(i); + + red = low * lowColor_r + mid * midColor_r + high * highColor_r; + green = low * lowColor_g + mid * midColor_g + high * highColor_g; + blue = low * lowColor_b + mid * midColor_b + high * highColor_b; + // Normalize + max = math_max3(red, green, blue); + // Draw + if (max > 0.0) { + color.setRgbF(static_cast(low / max), + static_cast(mid / max), + static_cast(high / max)); + pPainter->setPen(color); + pPainter->drawLine(x, static_cast(-all), x, 0); + } + + // Right + all = pWaveform->getAll(i + 1); + low = pWaveform->getLow(i + 1); + mid = pWaveform->getMid(i + 1); + high = pWaveform->getHigh(i + 1); + + red = low * lowColor_r + mid * midColor_r + high * highColor_r; + green = low * lowColor_g + mid * midColor_g + high * highColor_g; + blue = low * lowColor_b + mid * midColor_b + high * highColor_b; + + max = math_max3(red, green, blue); + + if (max > 0.0) { + color.setRgbF(static_cast(low / max), + static_cast(mid / max), + static_cast(high / max)); + pPainter->setPen(color); + pPainter->drawLine(x, 0, x, static_cast(all)); + } + } + } + + if (start) { + *start = end; + } +} + +void WaveformOverviewRenderer::drawWaveformPartLMH( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono) { + const QColor lowColor = signalColors.getLowColor(); + const QColor midColor = signalColors.getMidColor(); + const QColor highColor = signalColors.getHighColor(); + int startVal = 0; + if (start) { + startVal = *start; + } + + if (mono) { + // Mono means we're going to paint from bottom to top with l+r. + const qreal dy = pPainter->deviceTransform().dy(); + pPainter->resetTransform(); + // shift y0 to bottom + pPainter->translate(0, 2 * dy); + // flip y-axis + pPainter->scale(1, -1); + + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + x = i / 2; + pPainter->setPen(lowColor); + pPainter->drawLine(QPoint(x, 0), + QPoint(x, pWaveform->getLow(i) + pWaveform->getLow(i + 1))); + + pPainter->setPen(midColor); + pPainter->drawLine(QPoint(x, 0), + QPoint(x, pWaveform->getMid(i) + pWaveform->getMid(i + 1))); + + pPainter->setPen(highColor); + pPainter->drawLine(QPoint(x, 0), + QPoint(x, pWaveform->getHigh(i) + pWaveform->getHigh(i + 1))); + } + } else { // stereo + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + x = i / 2; + pPainter->setPen(lowColor); + pPainter->drawLine(QPoint(x, -pWaveform->getLow(i)), + QPoint(x, pWaveform->getLow(i + 1))); + + pPainter->setPen(midColor); + pPainter->drawLine(QPoint(x, -pWaveform->getMid(i)), + QPoint(x, pWaveform->getMid(i + 1))); + + pPainter->setPen(highColor); + pPainter->drawLine(QPoint(x, -pWaveform->getHigh(i)), + QPoint(x, pWaveform->getHigh(i + 1))); + } + } + + if (start) { + *start = end; + } +} + +void WaveformOverviewRenderer::drawWaveformPartHSV( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono) { + int startVal = 0; + if (start) { + startVal = *start; + } + + float h = 0, s = 0, v = 0, lo = 0, hi = 0, total = 0; + // Get HSV of low color. + const QColor lowColor = signalColors.getLowColor(); + getHsvF(lowColor, &h, &s, &v); + QColor color; + + unsigned char low[2] = {0, 0}; + unsigned char high[2] = {0, 0}; + unsigned char mid[2] = {0, 0}; + unsigned char all[2] = {0, 0}; + + if (mono) { + // Mono means we're going to paint from bottom to top with l+r. + const qreal dy = pPainter->deviceTransform().dy(); + pPainter->resetTransform(); + // shift y0 to bottom + pPainter->translate(0, 2 * dy); + // flip y-axis + pPainter->scale(1, -1); + } + + for (int i = startVal, x = startVal / 2; i < end; i += 2, ++x) { + x = i / 2; + all[0] = pWaveform->getAll(i); + all[1] = pWaveform->getAll(i + 1); + + if (!all[0] && !all[1]) { + continue; + } + + low[0] = pWaveform->getLow(i); + low[1] = pWaveform->getLow(i + 1); + mid[0] = pWaveform->getMid(i); + mid[1] = pWaveform->getMid(i + 1); + high[0] = pWaveform->getHigh(i); + high[1] = pWaveform->getHigh(i + 1); + + total = (low[0] + low[1] + mid[0] + mid[1] + + high[0] + high[1]) * + 1.2f; + + // Prevent division by zero + if (total > 0) { + // Normalize low and high + // (mid not need, because it not change the color) + lo = (low[0] + low[1]) / total; + hi = (high[0] + high[1]) / total; + } else { + lo = hi = 0.0; + } + + // Set color + color.setHsvF(h, 1.0f - hi, 1.0f - lo); + + if (mono) { + pPainter->setPen(color); + pPainter->drawLine(QPoint(i / 2, 0), + QPoint(i / 2, all[0] + all[1])); + } else { + pPainter->setPen(color); + pPainter->drawLine(QPoint(i / 2, -all[0]), + QPoint(i / 2, all[1])); + } + } + + if (start) { + *start = end; + } +} diff --git a/src/waveform/renderers/waveformoverviewrenderer.h b/src/waveform/renderers/waveformoverviewrenderer.h new file mode 100644 index 00000000000..825e4f2a341 --- /dev/null +++ b/src/waveform/renderers/waveformoverviewrenderer.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "waveform/overviewtype.h" +#include "waveform/waveform.h" + +class QPainter; +class WaveformSignalColors; + +class WaveformOverviewRenderer { + public: + static QImage render(ConstWaveformPointer pWaveform, + mixxx::OverviewType type, + const WaveformSignalColors& signalColors, + bool mono = false); + /// These paint methods allow "mono" rendering (mono-mixdown, bottom-aligned). + /// Note: Don't use with WOverview, it's not adjusted yet! It does some + /// additional scaling for normalization which will atm cut off the bottom part. + static void drawWaveformPartRGB( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono = false); + static void drawWaveformPartLMH( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono = false); + static void drawWaveformPartHSV( + QPainter* pPainter, + ConstWaveformPointer pWaveform, + int* start, + int end, + const WaveformSignalColors& signalColors, + bool mono = false); +}; diff --git a/src/widget/wlibrary.cpp b/src/widget/wlibrary.cpp index 8787b3f0487..dcc0390dde3 100644 --- a/src/widget/wlibrary.cpp +++ b/src/widget/wlibrary.cpp @@ -30,6 +30,8 @@ void WLibrary::setup(const QDomNode& node, const SkinContext& context) { kDefaultTrackTableBackgroundColorOpacity), kMinTrackTableBackgroundColorOpacity, kMaxTrackTableBackgroundColorOpacity); + + m_overviewSignalColors.setup(node, context); } bool WLibrary::registerView(const QString& name, QWidget* pView) { diff --git a/src/widget/wlibrary.h b/src/widget/wlibrary.h index a017e9d823d..ebce2f45fa6 100644 --- a/src/widget/wlibrary.h +++ b/src/widget/wlibrary.h @@ -8,6 +8,7 @@ #include "library/libraryview.h" #include "skin/legacy/skincontext.h" #include "util/compatibility/qmutex.h" +#include "waveform/renderers/waveformsignalcolors.h" #include "widget/wbasewidget.h" class LibraryView; @@ -55,6 +56,10 @@ class WLibrary : public QStackedWidget, public WBaseWidget { return m_bShowButtonText; } + WaveformSignalColors getOverviewSignalColors() const { + return m_overviewSignalColors; + } + signals: FocusWidget setLibraryFocus(FocusWidget newFocus); @@ -77,4 +82,5 @@ class WLibrary : public QStackedWidget, public WBaseWidget { QMap m_viewMap; double m_trackTableBackgroundColorOpacity; bool m_bShowButtonText; + WaveformSignalColors m_overviewSignalColors; }; diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h index ad060872f2f..fb915d2ce10 100644 --- a/src/widget/wlibrarytableview.h +++ b/src/widget/wlibrarytableview.h @@ -66,7 +66,7 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { #endif bool play = false); void trackSelected(TrackPointer pTrack); - void onlyCachedCoverArt(bool); + void onlyCachedCoversAndOverviews(bool); void scrollValueChanged(int); FocusWidget setLibraryFocus(FocusWidget newFocus); diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 21824313e0d..e7682dda88b 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -21,6 +21,7 @@ #include "util/math.h" #include "util/painterscope.h" #include "util/timer.h" +#include "waveform/renderers/waveformoverviewrenderer.h" #include "waveform/waveform.h" #include "waveform/waveformwidgetfactory.h" #include "widget/controlwidgetconnection.h" @@ -34,7 +35,7 @@ WOverview::WOverview( : WWidget(parent), m_group(group), m_pConfig(pConfig), - m_type(Type::RGB), + m_type(mixxx::OverviewType::RGB), m_actualCompletion(0), m_pixmapDone(false), m_waveformPeak(-1.0), @@ -427,8 +428,8 @@ void WOverview::onPassthroughChange(double v) { void WOverview::slotTypeControlChanged(double v) { // Assert that v is in enum range to prevent UB. - DEBUG_ASSERT(v >= 0 && v < QMetaEnum::fromType().keyCount()); - Type type = static_cast(static_cast(v)); + DEBUG_ASSERT(v >= 0 && v < QMetaEnum::fromType().keyCount()); + mixxx::OverviewType type = static_cast(static_cast(v)); if (type == m_type) { return; } @@ -701,8 +702,8 @@ void WOverview::drawAxis(QPainter* pPainter) { } void WOverview::drawWaveformPixmap(QPainter* pPainter) { - WaveformWidgetFactory* widgetFactory = WaveformWidgetFactory::instance(); if (!m_waveformSourceImage.isNull()) { + WaveformWidgetFactory* widgetFactory = WaveformWidgetFactory::instance(); PainterScope painterScope(pPainter); float diffGain; bool normalize = widgetFactory->isOverviewNormalized(); @@ -1368,83 +1369,8 @@ bool WOverview::drawNextPixmapPart() { QPainter painter(&m_waveformSourceImage); painter.translate(0.0, static_cast(m_waveformSourceImage.height()) / 2.0); - if (m_type == Type::Filtered) { - drawNextPixmapPartLMH(&painter, pWaveform, nextCompletion); - } else if (m_type == Type::HSV) { - drawNextPixmapPartHSV(&painter, pWaveform, nextCompletion); - } else { // Type::RGB: - drawNextPixmapPartRGB(&painter, pWaveform, nextCompletion); - } - - m_waveformImageScaled = QImage(); - m_diffGain = 0; - - // Test if the complete waveform is done - if (m_actualCompletion >= dataSize - 2) { - m_pixmapDone = true; - // qDebug() << "m_waveformPeakRatio" << m_waveformPeak; - } - - return true; -} - -void WOverview::drawNextPixmapPartHSV(QPainter* pPainter, - ConstWaveformPointer pWaveform, - const int nextCompletion) { - DEBUG_ASSERT(!m_waveformSourceImage.isNull()); - ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartHSV")); - - // Get HSV of low color. - float h, s, v; - getHsvF(m_signalColors.getLowColor(), &h, &s, &v); - - QColor color; - float lo, hi, total; - - unsigned char maxLow[2] = {0, 0}; - unsigned char maxHigh[2] = {0, 0}; - unsigned char maxMid[2] = {0, 0}; - unsigned char maxAll[2] = {0, 0}; - - int currentCompletion = 0; - for (int currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - maxAll[0] = pWaveform->getAll(currentCompletion); - maxAll[1] = pWaveform->getAll(currentCompletion + 1); - if (maxAll[0] || maxAll[1]) { - maxLow[0] = pWaveform->getLow(currentCompletion); - maxLow[1] = pWaveform->getLow(currentCompletion + 1); - maxMid[0] = pWaveform->getMid(currentCompletion); - maxMid[1] = pWaveform->getMid(currentCompletion + 1); - maxHigh[0] = pWaveform->getHigh(currentCompletion); - maxHigh[1] = pWaveform->getHigh(currentCompletion + 1); - - total = (maxLow[0] + maxLow[1] + maxMid[0] + maxMid[1] + - maxHigh[0] + maxHigh[1]) * - 1.2f; - - // Prevent division by zero - if (total > 0) { - // Normalize low and high - // (mid not need, because it not change the color) - lo = (maxLow[0] + maxLow[1]) / total; - hi = (maxHigh[0] + maxHigh[1]) / total; - } else { - lo = hi = 0.0; - } - - // Set color - color.setHsvF(h, 1.0f - hi, 1.0f - lo); - - pPainter->setPen(color); - pPainter->drawLine(QPoint(currentCompletion / 2, -maxAll[0]), - QPoint(currentCompletion / 2, maxAll[1])); - } - } - // Evaluate waveform ratio peak - for (currentCompletion = m_actualCompletion; + for (int currentCompletion = m_actualCompletion; currentCompletion < nextCompletion; currentCompletion += 2) { m_waveformPeak = math_max3( @@ -1453,145 +1379,41 @@ void WOverview::drawNextPixmapPartHSV(QPainter* pPainter, static_cast(pWaveform->getAll(currentCompletion + 1))); } - m_actualCompletion = nextCompletion; -} - -void WOverview::drawNextPixmapPartLMH(QPainter* pPainter, - ConstWaveformPointer pWaveform, - const int nextCompletion) { - DEBUG_ASSERT(!m_waveformSourceImage.isNull()); - ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartLMH")); - - QColor lowColor = m_signalColors.getLowColor(); - QPen lowColorPen(QBrush(lowColor), 1); - - QColor midColor = m_signalColors.getMidColor(); - QPen midColorPen(QBrush(midColor), 1); - - QColor highColor = m_signalColors.getHighColor(); - QPen highColorPen(QBrush(highColor), 1); - - int currentCompletion = 0; - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - unsigned char lowNeg = pWaveform->getLow(currentCompletion); - unsigned char lowPos = pWaveform->getLow(currentCompletion + 1); - if (lowPos || lowNeg) { - pPainter->setPen(lowColorPen); - pPainter->drawLine(QPoint(currentCompletion / 2, -lowNeg), - QPoint(currentCompletion / 2, lowPos)); - } + if (m_type == mixxx::OverviewType::Filtered) { + ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartLMH")); + WaveformOverviewRenderer::drawWaveformPartLMH( + &painter, + pWaveform, + &m_actualCompletion, + nextCompletion, + m_signalColors); + } else if (m_type == mixxx::OverviewType::HSV) { + ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartHSV")); + WaveformOverviewRenderer::drawWaveformPartHSV( + &painter, + pWaveform, + &m_actualCompletion, + nextCompletion, + m_signalColors); + } else { // mixxx::OverviewType::RGB: + ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartRGB")); + WaveformOverviewRenderer::drawWaveformPartRGB( + &painter, + pWaveform, + &m_actualCompletion, + nextCompletion, + m_signalColors); } - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - pPainter->setPen(midColorPen); - pPainter->drawLine(QPoint(currentCompletion / 2, - -pWaveform->getMid(currentCompletion)), - QPoint(currentCompletion / 2, - pWaveform->getMid(currentCompletion + 1))); - } - - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - pPainter->setPen(highColorPen); - pPainter->drawLine(QPoint(currentCompletion / 2, - -pWaveform->getHigh(currentCompletion)), - QPoint(currentCompletion / 2, - pWaveform->getHigh(currentCompletion + 1))); - } - - // Evaluate waveform ratio peak - - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - m_waveformPeak = math_max3( - m_waveformPeak, - static_cast(pWaveform->getAll(currentCompletion)), - static_cast(pWaveform->getAll(currentCompletion + 1))); - } - - m_actualCompletion = nextCompletion; -} - -void WOverview::drawNextPixmapPartRGB(QPainter* pPainter, - ConstWaveformPointer pWaveform, - const int nextCompletion) { - DEBUG_ASSERT(!m_waveformSourceImage.isNull()); - ScopedTimer t(QStringLiteral("WOverview::drawNextPixmapPartRGB")); - - QColor color; - - float lowColor_r, lowColor_g, lowColor_b; - getRgbF(m_signalColors.getRgbLowColor(), &lowColor_r, &lowColor_g, &lowColor_b); - - float midColor_r, midColor_g, midColor_b; - getRgbF(m_signalColors.getRgbMidColor(), &midColor_r, &midColor_g, &midColor_b); - - float highColor_r, highColor_g, highColor_b; - getRgbF(m_signalColors.getRgbHighColor(), &highColor_r, &highColor_g, &highColor_b); - - int currentCompletion = 0; - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - unsigned char left = pWaveform->getAll(currentCompletion); - unsigned char right = pWaveform->getAll(currentCompletion + 1); - - // Retrieve "raw" LMH values from waveform - float low = static_cast(pWaveform->getLow(currentCompletion)); - float mid = static_cast(pWaveform->getMid(currentCompletion)); - float high = static_cast(pWaveform->getHigh(currentCompletion)); - - // Do matrix multiplication - float red = low * lowColor_r + mid * midColor_r + high * highColor_r; - float green = low * lowColor_g + mid * midColor_g + high * highColor_g; - float blue = low * lowColor_b + mid * midColor_b + high * highColor_b; - - // Normalize and draw - float max = math_max3(red, green, blue); - if (max > 0.0) { - color.setRgbF(red / max, green / max, blue / max); - pPainter->setPen(color); - pPainter->drawLine(QPointF(currentCompletion / 2, -left), - QPointF(currentCompletion / 2, 0)); - } - - // Retrieve "raw" LMH values from waveform - low = static_cast(pWaveform->getLow(currentCompletion + 1)); - mid = static_cast(pWaveform->getMid(currentCompletion + 1)); - high = static_cast(pWaveform->getHigh(currentCompletion + 1)); - - // Do matrix multiplication - red = low * lowColor_r + mid * midColor_r + high * highColor_r; - green = low * lowColor_g + mid * midColor_g + high * highColor_g; - blue = low * lowColor_b + mid * midColor_b + high * highColor_b; - - // Normalize and draw - max = math_max3(red, green, blue); - if (max > 0.0) { - color.setRgbF(red / max, green / max, blue / max); - pPainter->setPen(color); - pPainter->drawLine(QPointF(currentCompletion / 2, 0), - QPointF(currentCompletion / 2, right)); - } - } + m_waveformImageScaled = QImage(); + m_diffGain = 0; - // Evaluate waveform ratio peak - for (currentCompletion = m_actualCompletion; - currentCompletion < nextCompletion; - currentCompletion += 2) { - m_waveformPeak = math_max3( - m_waveformPeak, - static_cast(pWaveform->getAll(currentCompletion)), - static_cast(pWaveform->getAll(currentCompletion + 1))); + // Test if the complete waveform is done + if (m_actualCompletion >= dataSize - 2) { + m_pixmapDone = true; } - m_actualCompletion = nextCompletion; + return true; } void WOverview::paintText(const QString& text, QPainter* pPainter) { diff --git a/src/widget/woverview.h b/src/widget/woverview.h index 242802d057f..b2587eedd9c 100644 --- a/src/widget/woverview.h +++ b/src/widget/woverview.h @@ -8,6 +8,7 @@ #include "track/track_decl.h" #include "track/trackid.h" #include "util/parented_ptr.h" +#include "waveform/overviewtype.h" #include "waveform/renderers/waveformmarkrange.h" #include "waveform/renderers/waveformmarkset.h" #include "waveform/renderers/waveformsignalcolors.h" @@ -32,13 +33,6 @@ class WOverview : public WWidget, public TrackDropTarget { void setup(const QDomNode& node, const SkinContext& context); virtual void initWithTrack(TrackPointer pTrack); - enum class Type { - Filtered, - HSV, - RGB, - }; - Q_ENUM(Type); - public slots: void onConnectedControlChanged(double dParameter, double dValue) override; void slotTrackLoaded(TrackPointer pTrack); @@ -143,7 +137,7 @@ class WOverview : public WWidget, public TrackDropTarget { const QString m_group; UserSettingsPointer m_pConfig; - Type m_type; + mixxx::OverviewType m_type; int m_actualCompletion; bool m_pixmapDone; float m_waveformPeak; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index c5241fea0e5..aad6d94c936 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -89,7 +89,7 @@ void WTrackTableView::enableCachedOnly() { if (!m_loadCachedOnly) { // don't try to load and search covers, drawing only // covers which are already in the QPixmapCache. - emit onlyCachedCoverArt(true); + emit onlyCachedCoversAndOverviews(true); m_loadCachedOnly = true; } m_lastUserAction = mixxx::Time::elapsed(); @@ -153,7 +153,7 @@ void WTrackTableView::slotGuiTick50ms(double /*unused*/) { // This allows CoverArtDelegate to request that we load covers from disk // (as opposed to only serving them from cache). - emit onlyCachedCoverArt(false); + emit onlyCachedCoversAndOverviews(false); m_loadCachedOnly = false; } }