From 2eeb5a666b16755f6ccb93d06f0e60847e93dc57 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 10 Dec 2019 15:19:22 -0600 Subject: [PATCH 001/393] DlgPrefController: remove Scripts tab This tab did nothing useful and only confused users, for example: https://mixxx.org/forums/viewtopic.php?f=3&p=43444 https://mixxx.org/forums/viewtopic.php?p=39226#p39226 https://mixxx.org/forums/viewtopic.php?p=41743#p41743 https://mixxx.org/forums/viewtopic.php?p=32084#p32084 --- src/controllers/dlgprefcontroller.cpp | 173 ------------------------ src/controllers/dlgprefcontroller.h | 5 - src/controllers/dlgprefcontrollerdlg.ui | 53 -------- 3 files changed, 231 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 0d89508ab52b..c0d5b596064c 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -38,7 +38,6 @@ DlgPrefController::DlgPrefController(QWidget* parent, Controller* controller, initTableView(m_ui.m_pInputMappingTableView); initTableView(m_ui.m_pOutputMappingTableView); - initTableView(m_ui.m_pScriptsTableWidget); connect(m_pController, SIGNAL(presetLoaded(ControllerPresetPointer)), this, SLOT(slotPresetLoaded(ControllerPresetPointer))); @@ -91,16 +90,6 @@ DlgPrefController::DlgPrefController(QWidget* parent, Controller* controller, this, SLOT(removeOutputMappings())); connect(m_ui.btnClearAllOutputMappings, SIGNAL(clicked()), this, SLOT(clearAllOutputMappings())); - - // Scripts - connect(m_ui.m_pScriptsTableWidget, SIGNAL(cellChanged(int, int)), - this, SLOT(slotDirty())); - connect(m_ui.btnAddScript, SIGNAL(clicked()), - this, SLOT(addScript())); - connect(m_ui.btnRemoveScript, SIGNAL(clicked()), - this, SLOT(removeScript())); - connect(m_ui.btnOpenScript, SIGNAL(clicked()), - this, SLOT(openScript())); } DlgPrefController::~DlgPrefController() { @@ -307,28 +296,6 @@ void DlgPrefController::slotApply() { m_pOutputTableModel->apply(); } - // Load script info from the script table. - m_pPreset->scripts.clear(); - for (int i = 0; i < m_ui.m_pScriptsTableWidget->rowCount(); ++i) { - QString scriptFile = m_ui.m_pScriptsTableWidget->item(i, 0)->text(); - - // Skip empty rows. - if (scriptFile.isEmpty()) { - continue; - } - - QString scriptPrefix = m_ui.m_pScriptsTableWidget->item(i, 1)->text(); - - bool builtin = m_ui.m_pScriptsTableWidget->item(i, 2) - ->checkState() == Qt::Checked; - - ControllerPreset::ScriptFileInfo info; - info.name = scriptFile; - info.functionPrefix = scriptPrefix; - info.builtin = builtin; - m_pPreset->scripts.append(info); - } - // Load the resulting preset (which has been mutated by the input/output // table models). The controller clones the preset so we aren't touching // the same preset. @@ -504,42 +471,6 @@ void DlgPrefController::slotPresetLoaded(ControllerPresetPointer preset) { m_pOutputProxyModel = pOutputProxyModel; delete m_pOutputTableModel; m_pOutputTableModel = pOutputModel; - - // Populate the script tab with the scripts this preset uses. - m_ui.m_pScriptsTableWidget->setRowCount(preset->scripts.length()); - m_ui.m_pScriptsTableWidget->setColumnCount(3); - m_ui.m_pScriptsTableWidget->setHorizontalHeaderItem( - 0, new QTableWidgetItem(tr("Filename"))); - m_ui.m_pScriptsTableWidget->setHorizontalHeaderItem( - 1, new QTableWidgetItem(tr("Function Prefix"))); - m_ui.m_pScriptsTableWidget->setHorizontalHeaderItem( - 2, new QTableWidgetItem(tr("Built-in"))); - m_ui.m_pScriptsTableWidget->horizontalHeader() - ->setSectionResizeMode(QHeaderView::Stretch); - - for (int i = 0; i < preset->scripts.length(); ++i) { - const ControllerPreset::ScriptFileInfo& script = preset->scripts.at(i); - - QTableWidgetItem* pScriptName = new QTableWidgetItem(script.name); - m_ui.m_pScriptsTableWidget->setItem(i, 0, pScriptName); - pScriptName->setFlags(pScriptName->flags() & ~Qt::ItemIsEditable); - - QTableWidgetItem* pScriptPrefix = new QTableWidgetItem( - script.functionPrefix); - m_ui.m_pScriptsTableWidget->setItem(i, 1, pScriptPrefix); - - // If the script is built-in don't allow editing of the prefix. - if (script.builtin) { - pScriptPrefix->setFlags(pScriptPrefix->flags() & ~Qt::ItemIsEditable); - } - - QTableWidgetItem* pScriptBuiltin = new QTableWidgetItem(); - pScriptBuiltin->setCheckState(script.builtin ? Qt::Checked : Qt::Unchecked); - pScriptBuiltin->setFlags(pScriptBuiltin->flags() & ~(Qt::ItemIsEnabled | - Qt::ItemIsEditable | - Qt::ItemIsUserCheckable)); - m_ui.m_pScriptsTableWidget->setItem(i, 2, pScriptBuiltin); - } } void DlgPrefController::slotEnableDevice(bool enable) { @@ -640,107 +571,3 @@ void DlgPrefController::clearAllOutputMappings() { slotDirty(); } } - -void DlgPrefController::addScript() { - QString scriptFile = QFileDialog::getOpenFileName( - this, tr("Add Script"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - tr("Controller Script Files (*.js)")); - - if (scriptFile.isNull()) { - return; - } - - QString importedScriptFileName; - if (!m_pControllerManager->importScript(scriptFile, &importedScriptFileName)) { - QMessageBox::warning(this, tr("Add Script"), - tr("Could not add script file: '%s'")); - return; - } - - // Don't allow duplicate entries in the table. This could happen if the file - // is missing (and the user added it to try and fix this) or if the file is - // already in the presets directory with an identical checksum. - for (int i = 0; i < m_ui.m_pScriptsTableWidget->rowCount(); ++i) { - if (m_ui.m_pScriptsTableWidget->item(i, 0)->text() == importedScriptFileName) { - return; - } - } - - int newRow = m_ui.m_pScriptsTableWidget->rowCount(); - m_ui.m_pScriptsTableWidget->setRowCount(newRow + 1); - QTableWidgetItem* pScriptName = new QTableWidgetItem(importedScriptFileName); - m_ui.m_pScriptsTableWidget->setItem(newRow, 0, pScriptName); - pScriptName->setFlags(pScriptName->flags() & ~Qt::ItemIsEditable); - - QTableWidgetItem* pScriptPrefix = new QTableWidgetItem(""); - m_ui.m_pScriptsTableWidget->setItem(newRow, 1, pScriptPrefix); - - QTableWidgetItem* pScriptBuiltin = new QTableWidgetItem(); - pScriptBuiltin->setCheckState(Qt::Unchecked); - pScriptBuiltin->setFlags(pScriptBuiltin->flags() & ~(Qt::ItemIsEditable | - Qt::ItemIsUserCheckable)); - m_ui.m_pScriptsTableWidget->setItem(newRow, 2, pScriptBuiltin); - - slotDirty(); -} - -void DlgPrefController::removeScript() { - QModelIndexList selectedIndices = m_ui.m_pScriptsTableWidget->selectionModel() - ->selection().indexes(); - if (selectedIndices.isEmpty()) { - return; - } - - QList selectedRows; - foreach (QModelIndex index, selectedIndices) { - selectedRows.append(index.row()); - } - std::sort(selectedRows.begin(), selectedRows.end()); - - int lastRow = -1; - while (!selectedRows.empty()) { - int row = selectedRows.takeLast(); - if (row == lastRow) { - continue; - } - - // You can't remove a builtin script. - QTableWidgetItem* pItem = m_ui.m_pScriptsTableWidget->item(row, 2); - if (pItem->checkState() == Qt::Checked) { - continue; - } - - lastRow = row; - m_ui.m_pScriptsTableWidget->removeRow(row); - } - slotDirty(); -} - -void DlgPrefController::openScript() { - QModelIndexList selectedIndices = m_ui.m_pScriptsTableWidget->selectionModel() - ->selection().indexes(); - if (selectedIndices.isEmpty()) { - QMessageBox::information( - this, - Version::applicationName(), - tr("Please select a script from the list to open."), - QMessageBox::Ok, QMessageBox::Ok); - return; - } - - QSet selectedRows; - foreach (QModelIndex index, selectedIndices) { - selectedRows.insert(index.row()); - } - QList scriptPaths = ControllerManager::getPresetPaths(m_pConfig); - - foreach (int row, selectedRows) { - QString scriptName = m_ui.m_pScriptsTableWidget->item(row, 0)->text(); - - QString scriptPath = ControllerManager::getAbsolutePath(scriptName, scriptPaths); - if (!scriptPath.isEmpty()) { - QDesktopServices::openUrl(QUrl::fromLocalFile(scriptPath)); - } - } -} diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index e0cb88ed5aa6..39f20ce740c6 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -69,11 +69,6 @@ class DlgPrefController : public DlgPreferencePage { void removeOutputMappings(); void clearAllOutputMappings(); - // Scripts - void addScript(); - void removeScript(); - void openScript(); - void midiInputMappingsLearned(const MidiInputMappings& mappings); private: diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index d6f341b959d7..a616322a2d0f 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -465,59 +465,6 @@ - - - Scripts - - - - - - - - - - - - - - - Add - - - - - - - Remove - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Open Selected File - - - - - - - - From c234d886e40555b0c0bef45ac3eb80f874f65813 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 7 Mar 2020 09:53:35 +0100 Subject: [PATCH 002/393] Allow restarting of web tasks --- src/network/webtask.cpp | 24 +++++++++++++++--------- src/network/webtask.h | 3 ++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index c79b5438429b..4f97bcc10484 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -163,11 +163,9 @@ void WebTask::slotStart(int timeoutMillis) { << "No network access"; return; } - VERIFY_OR_DEBUG_ASSERT(!m_abort) { - kLogger.warning() - << "Task has already been aborted"; - return; - } + // The task might be restarted after it has been aborted + // or finished. + m_abort = false; kLogger.debug() << "Starting..."; @@ -176,13 +174,13 @@ void WebTask::slotStart(int timeoutMillis) { << "Start aborted"; return; } - // Task can only be started once - m_networkAccessManager = nullptr; if (m_abort) { onAborted(); return; } + + DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId); if (timeoutMillis > 0) { m_timeoutTimerId = startTimer(timeoutMillis); DEBUG_ASSERT(m_timeoutTimerId != kInvalidTimerId); @@ -192,8 +190,13 @@ void WebTask::slotStart(int timeoutMillis) { void WebTask::slotAbort() { DEBUG_ASSERT(thread() == QThread::currentThread()); if (m_abort) { + DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId); return; } + if (m_timeoutTimerId != kInvalidTimerId) { + killTimer(m_timeoutTimerId); + m_timeoutTimerId = kInvalidTimerId; + } m_abort = true; kLogger.debug() << "Aborting..."; @@ -208,8 +211,6 @@ void WebTask::timerEvent(QTimerEvent* event) { // ignore return; } - killTimer(timerId); - m_timeoutTimerId = kInvalidTimerId; kLogger.info() << "Timed out"; slotAbort(); @@ -224,6 +225,11 @@ QPair WebTask::receiveNetworkReply() { } networkReply->deleteLater(); + if (m_timeoutTimerId != kInvalidTimerId) { + killTimer(m_timeoutTimerId); + m_timeoutTimerId = kInvalidTimerId; + } + if (m_abort) { onAborted(); return qMakePair(nullptr, statusCode); diff --git a/src/network/webtask.h b/src/network/webtask.h index 42c448a0ff70..03abcc603fd1 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -151,7 +151,8 @@ class WebTask : public QObject { // All member variables must only be accessed from // the event loop thread!! - QPointer m_networkAccessManager; + const QPointer m_networkAccessManager; + int m_timeoutTimerId; bool m_abort; }; From 0fff63c68b5f015d2ae2ce88eee2fe0c4a0f8afd Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 9 Mar 2020 09:33:16 +0100 Subject: [PATCH 003/393] Add comment about abort while being started --- src/network/webtask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index 4f97bcc10484..c1328ab3989f 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -175,6 +175,7 @@ void WebTask::slotStart(int timeoutMillis) { return; } + // The task could be aborted immediately while being started. if (m_abort) { onAborted(); return; From b81763667116376e39a7480c82499d4b34be4b54 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 4 Mar 2020 22:10:56 +0100 Subject: [PATCH 004/393] Extract BaseTrackTableModel from BaseSqlTableModel - Reduce the hard-coded dependencies to other classes to be reusable for integrating external track collections - Send a single signal for marking rows as dirty or changed --- CMakeLists.txt | 1 + build/depends.py | 1 + src/library/basesqltablemodel.cpp | 41 +++-- src/library/basesqltablemodel.h | 14 +- src/library/basetracktablemodel.cpp | 64 ++++++++ src/library/basetracktablemodel.h | 27 ++++ src/library/coverartdelegate.cpp | 223 +++++++++++++++------------- src/library/coverartdelegate.h | 80 ++++++---- 8 files changed, 299 insertions(+), 152 deletions(-) create mode 100644 src/library/basetracktablemodel.cpp create mode 100644 src/library/basetracktablemodel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aa5fba2edf67..6b8310425aa7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -327,6 +327,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/baseplaylistfeature.cpp src/library/basesqltablemodel.cpp src/library/basetrackcache.cpp + src/library/basetracktablemodel.cpp src/library/bpmdelegate.cpp src/library/browse/browsefeature.cpp src/library/browse/browsetablemodel.cpp diff --git a/build/depends.py b/build/depends.py index 4740b2c5c087..59e842bd9a5e 100644 --- a/build/depends.py +++ b/build/depends.py @@ -1021,6 +1021,7 @@ def sources(self, build): "src/library/externaltrackcollection.cpp", "src/library/basesqltablemodel.cpp", "src/library/basetrackcache.cpp", + "src/library/basetracktablemodel.cpp", "src/library/columncache.cpp", "src/library/librarytablemodel.cpp", "src/library/searchquery.cpp", diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index a45fa51965b1..fea7a461229f 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -9,6 +9,7 @@ #include "library/bpmdelegate.h" #include "library/colordelegate.h" #include "library/coverartdelegate.h" +#include "library/dao/trackschema.h" #include "library/locationdelegate.h" #include "library/previewbuttondelegate.h" #include "library/trackcollection.h" @@ -45,11 +46,14 @@ constexpr int kTrackColorRowBackgroundOpacity = 0x20; // 12.5% opacity } // anonymous namespace -BaseSqlTableModel::BaseSqlTableModel(QObject* pParent, - TrackCollectionManager* pTrackCollectionManager, - const char* settingsNamespace) - : QAbstractTableModel(pParent), - TrackModel(pTrackCollectionManager->internalCollection()->database(), settingsNamespace), +BaseSqlTableModel::BaseSqlTableModel( + QObject* parent, + TrackCollectionManager* pTrackCollectionManager, + const char* settingsNamespace) + : BaseTrackTableModel( + pTrackCollectionManager->internalCollection()->database(), + settingsNamespace, + parent), m_pTrackCollectionManager(pTrackCollectionManager), m_database(pTrackCollectionManager->internalCollection()->database()), m_previewDeckGroup(PlayerManager::groupForPreviewDeck(0)), @@ -1172,19 +1176,30 @@ QAbstractItemDelegate* BaseSqlTableModel::delegateForColumn(const int i, QObject } else if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)) { return new ColorDelegate(pTableView); } else if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART)) { - CoverArtDelegate* pCoverDelegate = new CoverArtDelegate(pTableView); - connect(pCoverDelegate, - &CoverArtDelegate::coverReadyForCell, + auto* pCoverArtDelegate = + new CoverArtDelegate(pTableView); + connect(pTableView, + &WLibraryTableView::onlyCachedCoverArt, + pCoverArtDelegate, + &CoverArtDelegate::slotInhibitLazyLoading); + connect(pCoverArtDelegate, + &CoverArtDelegate::rowsChanged, this, - &BaseSqlTableModel::refreshCell); - return pCoverDelegate; + &BaseSqlTableModel::slotRefreshCoverRows); + return pCoverArtDelegate; } return nullptr; } -void BaseSqlTableModel::refreshCell(int row, int column) { - QModelIndex coverIndex = index(row, column); - emit dataChanged(coverIndex, coverIndex); +void BaseSqlTableModel::slotRefreshCoverRows(QList rows) { + if (rows.isEmpty()) { + return; + } + const int column = fieldIndex(LIBRARYTABLE_COVERART); + VERIFY_OR_DEBUG_ASSERT(column >= 0) { + return; + } + emitDataChangedForMultipleRowsSingleColumn(rows, column); } void BaseSqlTableModel::hideTracks(const QModelIndexList& indices) { diff --git a/src/library/basesqltablemodel.h b/src/library/basesqltablemodel.h index 55e3e0d38d71..be6d764be35a 100644 --- a/src/library/basesqltablemodel.h +++ b/src/library/basesqltablemodel.h @@ -5,7 +5,7 @@ #include "library/basetrackcache.h" #include "library/dao/trackdao.h" -#include "library/trackmodel.h" +#include "library/basetracktablemodel.h" #include "library/columncache.h" #include "util/class.h" @@ -13,12 +13,13 @@ class TrackCollectionManager; // BaseSqlTableModel is a custom-written SQL-backed table which aggressively // caches the contents of the table and supports lightweight updates. -class BaseSqlTableModel : public QAbstractTableModel, public TrackModel { +class BaseSqlTableModel : public BaseTrackTableModel { Q_OBJECT public: - BaseSqlTableModel(QObject* pParent, - TrackCollectionManager* pTrackCollectionManager, - const char* settingsNamespace); + BaseSqlTableModel( + QObject* parent, + TrackCollectionManager* pTrackCollectionManager, + const char* settingsNamespace); ~BaseSqlTableModel() override; // Returns true if the BaseSqlTableModel has been initialized. Calling data @@ -115,7 +116,8 @@ class BaseSqlTableModel : public QAbstractTableModel, public TrackModel { private slots: virtual void tracksChanged(QSet trackIds); virtual void trackLoaded(QString group, TrackPointer pTrack); - void refreshCell(int row, int column); + + void slotRefreshCoverRows(QList rows); private: // A simple helper function for initializing header title and width. Note diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp new file mode 100644 index 000000000000..0b5757a1cc13 --- /dev/null +++ b/src/library/basetracktablemodel.cpp @@ -0,0 +1,64 @@ +#include "library/basetracktablemodel.h" + +#include "util/assert.h" + +BaseTrackTableModel::BaseTrackTableModel( + QSqlDatabase db, + const char* settingsNamespace, + QObject* parent) + : QAbstractTableModel(parent), + TrackModel(db, settingsNamespace) { +} + +void BaseTrackTableModel::emitDataChangedForMultipleRowsSingleColumn( + const QList& rows, + int column, + const QVector& roles) { + DEBUG_ASSERT(column >= 0); + DEBUG_ASSERT(column < columnCount()); + int beginRow = -1; + int endRow = -1; + for (const int row : rows) { + DEBUG_ASSERT(row >= rows.first()); + DEBUG_ASSERT(row <= rows.last()); + DEBUG_ASSERT(row >= 0); + if (row >= rowCount()) { + // The number of rows might have changed since the signal + // has been emitted. This case seems to occur after switching + // to a different view with less rows. + continue; + } + if (beginRow < 0) { + // Start the first stride + DEBUG_ASSERT(beginRow == endRow); + DEBUG_ASSERT(row == rows.first()); + beginRow = row; + endRow = row + 1; + } else if (row == endRow) { + // Continue the current stride + ++endRow; + } else { + // Finish the current stride... + DEBUG_ASSERT(beginRow >= rows.first()); + DEBUG_ASSERT(beginRow < endRow); + DEBUG_ASSERT(endRow - 1 <= rows.last()); + QModelIndex topLeft = index(beginRow, column); + QModelIndex bottomRight = index(endRow - 1, column); + emit dataChanged(topLeft, bottomRight, roles); + // ...before starting the next stride + // Rows are expected to be sorted in ascending order + // without duplicates! + DEBUG_ASSERT(row >= endRow); + beginRow = row; + endRow = row + 1; + } + } + if (beginRow < endRow) { + // Finish the final stride + DEBUG_ASSERT(beginRow >= rows.first()); + DEBUG_ASSERT(endRow - 1 <= rows.last()); + QModelIndex topLeft = index(beginRow, column); + QModelIndex bottomRight = index(endRow - 1, column); + emit dataChanged(topLeft, bottomRight, roles); + } +} diff --git a/src/library/basetracktablemodel.h b/src/library/basetracktablemodel.h new file mode 100644 index 000000000000..78936b87f038 --- /dev/null +++ b/src/library/basetracktablemodel.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "library/trackmodel.h" + +class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { + Q_OBJECT + DISALLOW_COPY_AND_ASSIGN(BaseTrackTableModel); + + public: + explicit BaseTrackTableModel( + QSqlDatabase db, + const char* settingsNamespace, + QObject* parent = nullptr); + ~BaseTrackTableModel() override = default; + + protected: + // Emit the dataChanged() signal for multiple rows in + // a single column. The list of rows must be sorted in + // ascending order without duplicates! + void emitDataChangedForMultipleRowsSingleColumn( + const QList& rows, + int column, + const QVector& roles = QVector()); +}; diff --git a/src/library/coverartdelegate.cpp b/src/library/coverartdelegate.cpp index 732624df6d48..2eace687f1ad 100644 --- a/src/library/coverartdelegate.cpp +++ b/src/library/coverartdelegate.cpp @@ -1,80 +1,93 @@ +#include "library/coverartdelegate.h" + #include +#include -#include "library/coverartdelegate.h" #include "library/coverartcache.h" #include "library/dao/trackschema.h" #include "library/trackmodel.h" -#include "widget/wlibrarytableview.h" -#include "util/compatibility.h" #include "util/logger.h" -#include "util/math.h" +#include "widget/wlibrarytableview.h" namespace { const mixxx::Logger kLogger("CoverArtDelegate"); +inline TrackModel* asTrackModel( + QTableView* pTableView) { + auto* pTrackModel = + dynamic_cast(pTableView->model()); + DEBUG_ASSERT(pTrackModel); + return pTrackModel; +} + } // anonymous namespace -CoverArtDelegate::CoverArtDelegate(WLibraryTableView* parent) +CoverArtDelegate::CoverArtDelegate(QTableView* parent) : TableItemDelegate(parent), - m_pTableView(parent), - m_pTrackModel(nullptr), - m_bOnlyCachedCover(false), - m_iCoverColumn(-1), + m_pTrackModel(asTrackModel(parent)), + m_pCache(CoverArtCache::instance()), + m_inhibitLazyLoading(false), m_iCoverSourceColumn(-1), m_iCoverTypeColumn(-1), m_iCoverLocationColumn(-1), m_iCoverHashColumn(-1), - m_iTrackLocationColumn(-1), - m_iIdColumn(-1) { - // This assumes that the parent is wtracktableview - connect(parent, - &WLibraryTableView::onlyCachedCoverArt, - this, - &CoverArtDelegate::slotOnlyCachedCoverArt); - - CoverArtCache* pCache = CoverArtCache::instance(); - if (pCache) { - connect(pCache, + m_iTrackIdColumn(-1), + m_iTrackLocationColumn(-1) { + if (m_pCache) { + connect(m_pCache, &CoverArtCache::coverFound, this, &CoverArtDelegate::slotCoverFound); - } - - QTableView* pTableView = qobject_cast(parent); - if (pTableView) { - m_pTrackModel = dynamic_cast(pTableView->model()); + } else { + kLogger.warning() + << "Caching of cover art is not available"; } if (m_pTrackModel) { - m_iCoverColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_COVERART); m_iCoverSourceColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_COVERART_SOURCE); + LIBRARYTABLE_COVERART_SOURCE); m_iCoverTypeColumn = m_pTrackModel->fieldIndex( LIBRARYTABLE_COVERART_TYPE); m_iCoverHashColumn = m_pTrackModel->fieldIndex( LIBRARYTABLE_COVERART_HASH); m_iCoverLocationColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_COVERART_LOCATION); + LIBRARYTABLE_COVERART_LOCATION); + m_iTrackIdColumn = m_pTrackModel->fieldIndex( + LIBRARYTABLE_ID); m_iTrackLocationColumn = m_pTrackModel->fieldIndex( - TRACKLOCATIONSTABLE_LOCATION); - m_iIdColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_ID); + TRACKLOCATIONSTABLE_LOCATION); } } -void CoverArtDelegate::slotOnlyCachedCoverArt(bool b) { - m_bOnlyCachedCover = b; +void CoverArtDelegate::emitRowsChanged( + QList&& rows) { + if (rows.isEmpty()) { + return; + } + // Sort in ascending order... + std::sort(rows.begin(), rows.end()); + // ...and then deduplicate... + rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); + // ...before emitting the signal. + DEBUG_ASSERT(!rows.isEmpty()); + emit rowsChanged(std::move(rows)); +} - // If we can request non-cache covers now, request updates for all rows that - // were cache misses since the last time. - if (!m_bOnlyCachedCover) { - foreach (int row, m_cacheMissRows) { - emit coverReadyForCell(row, m_iCoverColumn); - } - m_cacheMissRows.clear(); +void CoverArtDelegate::slotInhibitLazyLoading( + bool inhibitLazyLoading) { + m_inhibitLazyLoading = inhibitLazyLoading; + if (m_inhibitLazyLoading || m_cacheMissRows.isEmpty()) { + return; } + // If we can request non-cache covers now, request updates + // for all rows that were cache misses since the last time. + auto staleRows = m_cacheMissRows; + // 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! + m_cacheMissRows = QList(); + emitRowsChanged(std::move(staleRows)); } void CoverArtDelegate::slotCoverFound( @@ -87,15 +100,9 @@ void CoverArtDelegate::slotCoverFound( if (pRequestor != this) { return; } - const QLinkedList rows = - m_hashToRow.take(requestedHash); - foreach(int row, rows) { - emit coverReadyForCell(row, m_iCoverColumn); - } - if (m_pTrackModel && coverInfoUpdated) { + if (coverInfoUpdated) { const auto pTrack = - m_pTrackModel->getTrackByRef( - TrackRef::fromFileInfo(coverInfo.trackLocation)); + loadTrackByLocation(coverInfo.trackLocation); if (pTrack) { kLogger.info() << "Updating cover info of track" @@ -103,64 +110,78 @@ void CoverArtDelegate::slotCoverFound( pTrack->setCoverInfo(coverInfo); } } + QList refreshRows = m_pendingCacheRows.values(requestedHash); + m_pendingCacheRows.remove(requestedHash); + emitRowsChanged(std::move(refreshRows)); } -void CoverArtDelegate::paintItem(QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const { - paintItemBackground(painter, option, index); - - if (m_iIdColumn < 0 || - m_iCoverSourceColumn == -1 || - m_iCoverTypeColumn == -1 || - m_iCoverLocationColumn == -1 || - m_iCoverHashColumn == -1) { - return; +TrackPointer CoverArtDelegate::loadTrackByLocation( + const QString& trackLocation) const { + VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) { + return TrackPointer(); } + return m_pTrackModel->getTrackByRef( + TrackRef::fromFileInfo(trackLocation)); +} - CoverInfo info; - info.type = static_cast( - index.sibling(index.row(), m_iCoverTypeColumn).data().toInt()); - - // We don't support types other than METADATA or FILE currently. - if (info.type != CoverInfo::METADATA && info.type != CoverInfo::FILE) { - return; +CoverInfo CoverArtDelegate::coverInfoForIndex( + const QModelIndex& index) const { + CoverInfo coverInfo; + VERIFY_OR_DEBUG_ASSERT(m_iTrackIdColumn >= 0 && + m_iCoverSourceColumn >= 0 && + m_iCoverTypeColumn >= 0 && + m_iCoverLocationColumn >= 0 && + m_iCoverHashColumn >= 0) { + return coverInfo; } + coverInfo.hash = + index.sibling(index.row(), m_iCoverHashColumn).data().toUInt(); + coverInfo.type = static_cast( + index.sibling(index.row(), m_iCoverTypeColumn).data().toInt()); + coverInfo.source = static_cast( + index.sibling(index.row(), m_iCoverSourceColumn).data().toInt()); + coverInfo.coverLocation = + index.sibling(index.row(), m_iCoverLocationColumn).data().toString(); + coverInfo.trackLocation = + index.sibling(index.row(), m_iTrackLocationColumn).data().toString(); + return coverInfo; +} - info.source = static_cast( - index.sibling(index.row(), m_iCoverSourceColumn).data().toInt()); - info.coverLocation = index.sibling(index.row(), m_iCoverLocationColumn).data().toString(); - info.hash = index.sibling(index.row(), m_iCoverHashColumn).data().toUInt(); - info.trackLocation = index.sibling(index.row(), m_iTrackLocationColumn).data().toString(); - - double scaleFactor = getDevicePixelRatioF(static_cast(parent())); - // We listen for updates via slotCoverFound above and signal to - // BaseSqlTableModel when a row's cover is ready. - CoverArtCache* const pCache = CoverArtCache::instance(); - VERIFY_OR_DEBUG_ASSERT(pCache) { - return; - } - QPixmap pixmap = pCache->tryLoadCover( - this, - info, - option.rect.width() * scaleFactor, - m_bOnlyCachedCover ? CoverArtCache::Loading::CachedOnly : CoverArtCache::Loading::Default); - if (!pixmap.isNull()) { - // Cache hit - pixmap.setDevicePixelRatio(scaleFactor); - painter->drawPixmap(option.rect.topLeft(), pixmap); - return; - } +void CoverArtDelegate::paintItem( + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { + paintItemBackground(painter, option, index); - if (m_bOnlyCachedCover) { - // 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_cacheMissRows.append(index.row()); - } else { - // If we asked for a non-cache image and got a null pixmap, then our - // request was queued. We cannot use the cover image hash, because this - // might be refreshed while loading the image! - m_hashToRow[info.hash].append(index.row()); + CoverInfo coverInfo = coverInfoForIndex(index); + if (CoverImageUtils::isValidHash(coverInfo.hash)) { + VERIFY_OR_DEBUG_ASSERT(m_pCache) { + return; + } + const double scaleFactor = + getDevicePixelRatioF(static_cast(parent())); + QPixmap pixmap = m_pCache->tryLoadCover( + this, + coverInfo, + option.rect.width() * scaleFactor, + m_inhibitLazyLoading ? CoverArtCache::Loading::CachedOnly : CoverArtCache::Loading::Default); + 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_cacheMissRows.append(index.row()); + } else { + // If we asked for a non-cache image and got a null pixmap, + // then our request was queued. + m_pendingCacheRows.insertMulti(coverInfo.hash, index.row()); + } + } else { + // Cache hit + pixmap.setDevicePixelRatio(scaleFactor); + painter->drawPixmap(option.rect.topLeft(), pixmap); + return; + } } } diff --git a/src/library/coverartdelegate.h b/src/library/coverartdelegate.h index 1d3e00ac8f30..3348624efdf0 100644 --- a/src/library/coverartdelegate.h +++ b/src/library/coverartdelegate.h @@ -1,42 +1,50 @@ -#ifndef COVERARTDELEGATE_H -#define COVERARTDELEGATE_H +#pragma once #include #include -#include +#include #include "library/tableitemdelegate.h" +#include "track/track.h" +#include "util/cache.h" -class CoverInfo; +class CoverArtCache; class TrackModel; -class WLibraryTableView; class CoverArtDelegate : public TableItemDelegate { Q_OBJECT + public: - explicit CoverArtDelegate(WLibraryTableView* parent); + explicit CoverArtDelegate( + QTableView* parent); ~CoverArtDelegate() override = default; - void paintItem(QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + void paintItem( + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const final; signals: - void coverReadyForCell(int row, int column); + // Sent when rows need to be refreshed + void rowsChanged( + QList rows); - private slots: - // If it is true, it must not try to load and search covers. - // - // It means that in this cases it will just draw - // covers which are already in the pixmapcache. + public slots: + // Advise the delegate to temporarily inhibit lazy loading + // of cover images and to only display those cover images + // that have already been cached. Otherwise only the solid + // (background) color is painted. // // It is useful to handle cases when the user scroll down - // very fast or when they hold an arrow key, because - // in these cases 'paint()' would be called very often - // and it might make CoverDelegate starts many searches, - // which could bring performance issues. - void slotOnlyCachedCoverArt(bool b); + // very fast or when they hold an arrow key. In thise case + // it is NOT desirable to start multiple expensive file + // system operations in worker threads for loading and + // scaling cover images that are not even displayed after + // scrolling beyond them. + void slotInhibitLazyLoading( + bool inhibitLazyLoading); + private slots: void slotCoverFound( const QObject* pRequestor, const CoverInfo& coverInfo, @@ -45,21 +53,29 @@ class CoverArtDelegate : public TableItemDelegate { bool coverInfoUpdated); private: - QTableView* m_pTableView; - TrackModel* m_pTrackModel; - bool m_bOnlyCachedCover; - int m_iCoverColumn; + void emitRowsChanged( + QList&& rows); + + TrackPointer loadTrackByLocation( + const QString& trackLocation) const; + + CoverInfo coverInfoForIndex( + const QModelIndex& index) const; + + TrackModel* const m_pTrackModel; + + CoverArtCache* const m_pCache; + bool m_inhibitLazyLoading; + + // We need to record rows in paint() (which is const) so + // these are marked mutable. + mutable QList m_cacheMissRows; + mutable QHash m_pendingCacheRows; + int m_iCoverSourceColumn; int m_iCoverTypeColumn; int m_iCoverLocationColumn; int m_iCoverHashColumn; + int m_iTrackIdColumn; int m_iTrackLocationColumn; - int m_iIdColumn; - - // We need to record rows in paint() (which is const) so these are marked - // mutable. - mutable QList m_cacheMissRows; - mutable QHash > m_hashToRow; }; - -#endif // COVERARTDELEGATE_H From b94cac41fc904486962b8118f8a1a87aedb3dfe3 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 29 Feb 2020 19:06:43 +0100 Subject: [PATCH 005/393] Extract BaseCoverArtDelegate from CoverArtDelegate --- CMakeLists.txt | 1 + build/depends.py | 1 + src/library/basecoverartdelegate.cpp | 143 ++++++++++++++++++++ src/library/basecoverartdelegate.h | 75 +++++++++++ src/library/coverartdelegate.cpp | 189 ++++----------------------- src/library/coverartdelegate.h | 68 +--------- 6 files changed, 249 insertions(+), 228 deletions(-) create mode 100644 src/library/basecoverartdelegate.cpp create mode 100644 src/library/basecoverartdelegate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b8310425aa7..6c5e73dfe2f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -321,6 +321,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/banshee/bansheedbconnection.cpp src/library/banshee/bansheefeature.cpp src/library/banshee/bansheeplaylistmodel.cpp + src/library/basecoverartdelegate.cpp src/library/baseexternallibraryfeature.cpp src/library/baseexternalplaylistmodel.cpp src/library/baseexternaltrackmodel.cpp diff --git a/build/depends.py b/build/depends.py index 59e842bd9a5e..8aecd5b52460 100644 --- a/build/depends.py +++ b/build/depends.py @@ -1116,6 +1116,7 @@ def sources(self, build): "src/library/bpmdelegate.cpp", "src/library/previewbuttondelegate.cpp", "src/library/colordelegate.cpp", + "src/library/basecoverartdelegate.cpp", "src/library/coverartdelegate.cpp", "src/library/locationdelegate.cpp", "src/library/tableitemdelegate.cpp", diff --git a/src/library/basecoverartdelegate.cpp b/src/library/basecoverartdelegate.cpp new file mode 100644 index 000000000000..afb31d03b343 --- /dev/null +++ b/src/library/basecoverartdelegate.cpp @@ -0,0 +1,143 @@ +#include "library/coverartdelegate.h" + +#include +#include + +#include "library/coverartcache.h" +#include "library/dao/trackschema.h" +#include "library/trackmodel.h" +#include "util/logger.h" +#include "widget/wlibrarytableview.h" + +namespace { + +const mixxx::Logger kLogger("BaseCoverArtDelegate"); + +inline TrackModel* asTrackModel( + QTableView* pTableView) { + auto* pTrackModel = + dynamic_cast(pTableView->model()); + DEBUG_ASSERT(pTrackModel); + return pTrackModel; +} + +} // anonymous namespace + +BaseCoverArtDelegate::BaseCoverArtDelegate(QTableView* parent) + : TableItemDelegate(parent), + m_pTrackModel(asTrackModel(parent)), + m_pCache(CoverArtCache::instance()), + m_inhibitLazyLoading(false) { + if (m_pCache) { + connect(m_pCache, + &CoverArtCache::coverFound, + this, + &BaseCoverArtDelegate::slotCoverFound); + } else { + kLogger.warning() + << "Caching of cover art is not available"; + } +} + +void BaseCoverArtDelegate::emitRowsChanged( + QList&& rows) { + if (rows.isEmpty()) { + return; + } + // Sort in ascending order... + std::sort(rows.begin(), rows.end()); + // ...and then deduplicate... + rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); + // ...before emitting the signal. + DEBUG_ASSERT(!rows.isEmpty()); + emit rowsChanged(std::move(rows)); +} + +void BaseCoverArtDelegate::slotInhibitLazyLoading( + bool inhibitLazyLoading) { + m_inhibitLazyLoading = inhibitLazyLoading; + if (m_inhibitLazyLoading || m_cacheMissRows.isEmpty()) { + return; + } + // If we can request non-cache covers now, request updates + // for all rows that were cache misses since the last time. + auto staleRows = m_cacheMissRows; + // Reset the member variable before mutating the aggregated + // rows list (-> implicit sharing) and emitting a signal that + // in turn may trigger new signals for BaseCoverArtDelegate! + m_cacheMissRows = QList(); + emitRowsChanged(std::move(staleRows)); +} + +void BaseCoverArtDelegate::slotCoverFound( + const QObject* pRequestor, + const CoverInfo& coverInfo, + const QPixmap& pixmap, + mixxx::cache_key_t requestedImageHash, + bool coverInfoUpdated) { + Q_UNUSED(pixmap); + if (pRequestor != this) { + return; + } + if (coverInfoUpdated) { + const auto pTrack = + loadTrackByLocation(coverInfo.trackLocation); + if (pTrack) { + kLogger.info() + << "Updating cover info of track" + << coverInfo.trackLocation; + pTrack->setCoverInfo(coverInfo); + } + } + QList refreshRows = m_pendingCacheRows.values(requestedImageHash); + m_pendingCacheRows.remove(requestedImageHash); + emitRowsChanged(std::move(refreshRows)); +} + +TrackPointer BaseCoverArtDelegate::loadTrackByLocation( + const QString& trackLocation) const { + VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) { + return TrackPointer(); + } + return m_pTrackModel->getTrackByRef( + TrackRef::fromFileInfo(trackLocation)); +} + +void BaseCoverArtDelegate::paintItem( + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { + paintItemBackground(painter, option, index); + + CoverInfo coverInfo = coverInfoForIndex(index); + if (CoverImageUtils::isValidHash(coverInfo.hash)) { + VERIFY_OR_DEBUG_ASSERT(m_pCache) { + return; + } + const double scaleFactor = + getDevicePixelRatioF(static_cast(parent())); + QPixmap pixmap = m_pCache->tryLoadCover( + this, + coverInfo, + option.rect.width() * scaleFactor, + m_inhibitLazyLoading ? CoverArtCache::Loading::CachedOnly : CoverArtCache::Loading::Default); + 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_cacheMissRows.append(index.row()); + } else { + // If we asked for a non-cache image and got a null pixmap, + // then our request was queued. + m_pendingCacheRows.insertMulti(coverInfo.hash, index.row()); + } + } else { + // Cache hit + pixmap.setDevicePixelRatio(scaleFactor); + painter->drawPixmap(option.rect.topLeft(), pixmap); + return; + } + } +} diff --git a/src/library/basecoverartdelegate.h b/src/library/basecoverartdelegate.h new file mode 100644 index 000000000000..cf7745705cdb --- /dev/null +++ b/src/library/basecoverartdelegate.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include + +#include "library/tableitemdelegate.h" +#include "track/track.h" +#include "util/cache.h" + +class CoverArtCache; +class TrackModel; + +class BaseCoverArtDelegate : public TableItemDelegate { + Q_OBJECT + + public: + explicit BaseCoverArtDelegate( + QTableView* parent); + ~BaseCoverArtDelegate() override = default; + + void paintItem( + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const final; + + signals: + // Sent when rows need to be refreshed + void rowsChanged( + QList rows); + + public slots: + // Advise the delegate to temporarily inhibit lazy loading + // of cover images and to only display those cover images + // that have already been cached. Otherwise only the solid + // (background) color is painted. + // + // It is useful to handle cases when the user scroll down + // very fast or when they hold an arrow key. In thise case + // it is NOT desirable to start multiple expensive file + // system operations in worker threads for loading and + // scaling cover images that are not even displayed after + // scrolling beyond them. + void slotInhibitLazyLoading( + bool inhibitLazyLoading); + + private slots: + void slotCoverFound( + const QObject* pRequestor, + const CoverInfo& coverInfo, + const QPixmap& pixmap, + mixxx::cache_key_t requestedImageHash, + bool coverInfoUpdated); + + protected: + TrackModel* const m_pTrackModel; + + private: + void emitRowsChanged( + QList&& rows); + + TrackPointer loadTrackByLocation( + const QString& trackLocation) const; + + virtual CoverInfo coverInfoForIndex( + const QModelIndex& index) const = 0; + + CoverArtCache* const m_pCache; + bool m_inhibitLazyLoading; + + // We need to record rows in paint() (which is const) so + // these are marked mutable. + mutable QList m_cacheMissRows; + mutable QHash m_pendingCacheRows; +}; diff --git a/src/library/coverartdelegate.cpp b/src/library/coverartdelegate.cpp index 2eace687f1ad..eb807f45b498 100644 --- a/src/library/coverartdelegate.cpp +++ b/src/library/coverartdelegate.cpp @@ -1,141 +1,37 @@ #include "library/coverartdelegate.h" -#include -#include - -#include "library/coverartcache.h" #include "library/dao/trackschema.h" #include "library/trackmodel.h" -#include "util/logger.h" +#include "util/assert.h" #include "widget/wlibrarytableview.h" -namespace { - -const mixxx::Logger kLogger("CoverArtDelegate"); - -inline TrackModel* asTrackModel( - QTableView* pTableView) { - auto* pTrackModel = - dynamic_cast(pTableView->model()); - DEBUG_ASSERT(pTrackModel); - return pTrackModel; -} - -} // anonymous namespace - -CoverArtDelegate::CoverArtDelegate(QTableView* parent) - : TableItemDelegate(parent), - m_pTrackModel(asTrackModel(parent)), - m_pCache(CoverArtCache::instance()), - m_inhibitLazyLoading(false), - m_iCoverSourceColumn(-1), - m_iCoverTypeColumn(-1), - m_iCoverLocationColumn(-1), - m_iCoverHashColumn(-1), - m_iTrackIdColumn(-1), - m_iTrackLocationColumn(-1) { - if (m_pCache) { - connect(m_pCache, - &CoverArtCache::coverFound, - this, - &CoverArtDelegate::slotCoverFound); - } else { - kLogger.warning() - << "Caching of cover art is not available"; - } - - if (m_pTrackModel) { - m_iCoverSourceColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_COVERART_SOURCE); - m_iCoverTypeColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_COVERART_TYPE); - m_iCoverHashColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_COVERART_HASH); - m_iCoverLocationColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_COVERART_LOCATION); - m_iTrackIdColumn = m_pTrackModel->fieldIndex( - LIBRARYTABLE_ID); - m_iTrackLocationColumn = m_pTrackModel->fieldIndex( - TRACKLOCATIONSTABLE_LOCATION); - } -} - -void CoverArtDelegate::emitRowsChanged( - QList&& rows) { - if (rows.isEmpty()) { - return; - } - // Sort in ascending order... - std::sort(rows.begin(), rows.end()); - // ...and then deduplicate... - rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); - // ...before emitting the signal. - DEBUG_ASSERT(!rows.isEmpty()); - emit rowsChanged(std::move(rows)); -} - -void CoverArtDelegate::slotInhibitLazyLoading( - bool inhibitLazyLoading) { - m_inhibitLazyLoading = inhibitLazyLoading; - if (m_inhibitLazyLoading || m_cacheMissRows.isEmpty()) { - return; - } - // If we can request non-cache covers now, request updates - // for all rows that were cache misses since the last time. - auto staleRows = m_cacheMissRows; - // 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! - m_cacheMissRows = QList(); - emitRowsChanged(std::move(staleRows)); -} - -void CoverArtDelegate::slotCoverFound( - const QObject* pRequestor, - const CoverInfo& coverInfo, - const QPixmap& pixmap, - quint16 requestedHash, - bool coverInfoUpdated) { - Q_UNUSED(pixmap); - if (pRequestor != this) { - return; - } - if (coverInfoUpdated) { - const auto pTrack = - loadTrackByLocation(coverInfo.trackLocation); - if (pTrack) { - kLogger.info() - << "Updating cover info of track" - << coverInfo.trackLocation; - pTrack->setCoverInfo(coverInfo); - } - } - QList refreshRows = m_pendingCacheRows.values(requestedHash); - m_pendingCacheRows.remove(requestedHash); - emitRowsChanged(std::move(refreshRows)); -} - -TrackPointer CoverArtDelegate::loadTrackByLocation( - const QString& trackLocation) const { - VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) { - return TrackPointer(); - } - return m_pTrackModel->getTrackByRef( - TrackRef::fromFileInfo(trackLocation)); +CoverArtDelegate::CoverArtDelegate( + WLibraryTableView* parent) + : BaseCoverArtDelegate(parent), + m_iCoverSourceColumn(m_pTrackModel->fieldIndex( + LIBRARYTABLE_COVERART_SOURCE)), + m_iCoverTypeColumn(m_pTrackModel->fieldIndex( + LIBRARYTABLE_COVERART_TYPE)), + m_iCoverLocationColumn(m_pTrackModel->fieldIndex( + LIBRARYTABLE_COVERART_LOCATION)), + m_iCoverHashColumn(m_pTrackModel->fieldIndex( + LIBRARYTABLE_COVERART_HASH)), + m_iTrackIdColumn(m_pTrackModel->fieldIndex( + LIBRARYTABLE_ID)), + m_iTrackLocationColumn(m_pTrackModel->fieldIndex( + TRACKLOCATIONSTABLE_LOCATION)) { + DEBUG_ASSERT(m_iCoverSourceColumn >= 0); + DEBUG_ASSERT(m_iCoverTypeColumn >= 0); + DEBUG_ASSERT(m_iCoverLocationColumn >= 0); + DEBUG_ASSERT(m_iCoverHashColumn >= 0); + DEBUG_ASSERT(m_iTrackIdColumn >= 0); + DEBUG_ASSERT(m_iTrackLocationColumn >= 0); } CoverInfo CoverArtDelegate::coverInfoForIndex( const QModelIndex& index) const { CoverInfo coverInfo; - VERIFY_OR_DEBUG_ASSERT(m_iTrackIdColumn >= 0 && - m_iCoverSourceColumn >= 0 && - m_iCoverTypeColumn >= 0 && - m_iCoverLocationColumn >= 0 && - m_iCoverHashColumn >= 0) { - return coverInfo; - } - coverInfo.hash = - index.sibling(index.row(), m_iCoverHashColumn).data().toUInt(); + coverInfo.hash = index.sibling(index.row(), m_iCoverHashColumn).data().toUInt(); coverInfo.type = static_cast( index.sibling(index.row(), m_iCoverTypeColumn).data().toInt()); coverInfo.source = static_cast( @@ -146,42 +42,3 @@ CoverInfo CoverArtDelegate::coverInfoForIndex( index.sibling(index.row(), m_iTrackLocationColumn).data().toString(); return coverInfo; } - -void CoverArtDelegate::paintItem( - QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const { - paintItemBackground(painter, option, index); - - CoverInfo coverInfo = coverInfoForIndex(index); - if (CoverImageUtils::isValidHash(coverInfo.hash)) { - VERIFY_OR_DEBUG_ASSERT(m_pCache) { - return; - } - const double scaleFactor = - getDevicePixelRatioF(static_cast(parent())); - QPixmap pixmap = m_pCache->tryLoadCover( - this, - coverInfo, - option.rect.width() * scaleFactor, - m_inhibitLazyLoading ? CoverArtCache::Loading::CachedOnly : CoverArtCache::Loading::Default); - 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_cacheMissRows.append(index.row()); - } else { - // If we asked for a non-cache image and got a null pixmap, - // then our request was queued. - m_pendingCacheRows.insertMulti(coverInfo.hash, index.row()); - } - } else { - // Cache hit - pixmap.setDevicePixelRatio(scaleFactor); - painter->drawPixmap(option.rect.topLeft(), pixmap); - return; - } - } -} diff --git a/src/library/coverartdelegate.h b/src/library/coverartdelegate.h index 3348624efdf0..e92e9c3f6d0b 100644 --- a/src/library/coverartdelegate.h +++ b/src/library/coverartdelegate.h @@ -1,76 +1,20 @@ #pragma once -#include -#include -#include +#include "library/basecoverartdelegate.h" -#include "library/tableitemdelegate.h" -#include "track/track.h" -#include "util/cache.h" +class WLibraryTableView; -class CoverArtCache; -class TrackModel; - -class CoverArtDelegate : public TableItemDelegate { +class CoverArtDelegate : public BaseCoverArtDelegate { Q_OBJECT public: explicit CoverArtDelegate( - QTableView* parent); - ~CoverArtDelegate() override = default; - - void paintItem( - QPainter* painter, - const QStyleOptionViewItem& option, - const QModelIndex& index) const final; - - signals: - // Sent when rows need to be refreshed - void rowsChanged( - QList rows); - - public slots: - // Advise the delegate to temporarily inhibit lazy loading - // of cover images and to only display those cover images - // that have already been cached. Otherwise only the solid - // (background) color is painted. - // - // It is useful to handle cases when the user scroll down - // very fast or when they hold an arrow key. In thise case - // it is NOT desirable to start multiple expensive file - // system operations in worker threads for loading and - // scaling cover images that are not even displayed after - // scrolling beyond them. - void slotInhibitLazyLoading( - bool inhibitLazyLoading); - - private slots: - void slotCoverFound( - const QObject* pRequestor, - const CoverInfo& coverInfo, - const QPixmap& pixmap, - quint16 requestedHash, - bool coverInfoUpdated); + WLibraryTableView* parent); + ~CoverArtDelegate() final = default; private: - void emitRowsChanged( - QList&& rows); - - TrackPointer loadTrackByLocation( - const QString& trackLocation) const; - CoverInfo coverInfoForIndex( - const QModelIndex& index) const; - - TrackModel* const m_pTrackModel; - - CoverArtCache* const m_pCache; - bool m_inhibitLazyLoading; - - // We need to record rows in paint() (which is const) so - // these are marked mutable. - mutable QList m_cacheMissRows; - mutable QHash m_pendingCacheRows; + const QModelIndex& index) const final; int m_iCoverSourceColumn; int m_iCoverTypeColumn; From f69fc5e37437e90b08ec062f3d8a7cf4875b3721 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 11 Mar 2020 10:05:24 +0100 Subject: [PATCH 006/393] Move common functions into BaseTrackTableModel --- src/library/banshee/bansheeplaylistmodel.cpp | 65 +- src/library/banshee/bansheeplaylistmodel.h | 11 +- src/library/baseexternalplaylistmodel.cpp | 35 +- src/library/baseexternalplaylistmodel.h | 3 +- src/library/baseexternaltrackmodel.cpp | 35 +- src/library/baseexternaltrackmodel.h | 4 +- src/library/basesqltablemodel.cpp | 750 ++++--------------- src/library/basesqltablemodel.h | 90 +-- src/library/basetrackcache.cpp | 6 +- src/library/basetrackcache.h | 2 +- src/library/basetracktablemodel.cpp | 730 +++++++++++++++++- src/library/basetracktablemodel.h | 191 ++++- src/library/coverartdelegate.cpp | 3 +- src/library/coverartdelegate.h | 4 +- src/library/dao/autodjcratesdao.cpp | 4 +- src/library/dao/trackdao.cpp | 2 +- src/library/dao/trackdao.h | 1 + src/library/tableitemdelegate.cpp | 45 +- src/library/tableitemdelegate.h | 16 +- src/library/trackcollection.cpp | 44 +- src/library/trackcollection.h | 8 + 21 files changed, 1233 insertions(+), 816 deletions(-) diff --git a/src/library/banshee/bansheeplaylistmodel.cpp b/src/library/banshee/bansheeplaylistmodel.cpp index 68c52afa6d07..d1b125a57e55 100644 --- a/src/library/banshee/bansheeplaylistmodel.cpp +++ b/src/library/banshee/bansheeplaylistmodel.cpp @@ -221,13 +221,6 @@ void BansheePlaylistModel::setTableModel(int playlistId) { setSort(defaultSortColumn(), defaultSortOrder()); } -bool BansheePlaylistModel::setData(const QModelIndex& index, const QVariant& value, int role) { - Q_UNUSED(index); - Q_UNUSED(value); - Q_UNUSED(role); - return false; -} - TrackModel::CapabilitiesFlags BansheePlaylistModel::getCapabilities() const { return TRACKMODELCAPS_NONE | TRACKMODELCAPS_ADDTOPLAYLIST @@ -238,65 +231,23 @@ TrackModel::CapabilitiesFlags BansheePlaylistModel::getCapabilities() const { } Qt::ItemFlags BansheePlaylistModel::flags(const QModelIndex &index) const { - return readWriteFlags(index); -} - -Qt::ItemFlags BansheePlaylistModel::readWriteFlags(const QModelIndex &index) const { - if (!index.isValid()) { - return Qt::ItemIsEnabled; - } - - Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); - - // Enable dragging songs from this data model to elsewhere (like the waveform - // widget to load a track into a Player). - defaultFlags |= Qt::ItemIsDragEnabled; - - return defaultFlags; -} - -Qt::ItemFlags BansheePlaylistModel::readOnlyFlags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); - if (!index.isValid()) - return Qt::ItemIsEnabled; - - //Enable dragging songs from this data model to elsewhere (like the waveform widget to - //load a track into a Player). - defaultFlags |= Qt::ItemIsDragEnabled; - - return defaultFlags; + return readOnlyFlags(index); } void BansheePlaylistModel::tracksChanged(QSet trackIds) { Q_UNUSED(trackIds); } -void BansheePlaylistModel::trackLoaded(QString group, TrackPointer pTrack) { - if (group == m_previewDeckGroup) { - // If there was a previously loaded track, refresh its rows so the - // preview state will update. - if (m_previewDeckTrackId.isValid()) { - const int numColumns = columnCount(); - QLinkedList rows = getTrackRows(m_previewDeckTrackId); - m_previewDeckTrackId = TrackId(); // invalidate - foreach (int row, rows) { - QModelIndex left = index(row, 0); - QModelIndex right = index(row, numColumns); - emit dataChanged(left, right); - } - } - if (pTrack) { - for (int row = 0; row < rowCount(); ++row) { - const QUrl rowUrl(getFieldString(index(row, 0), CLM_URI)); - if (TrackFile::fromUrl(rowUrl) == pTrack->getFileInfo()) { - m_previewDeckTrackId = - TrackId(getFieldVariant(index(row, 0), CLM_VIEW_ORDER)); - break; - } +TrackId BansheePlaylistModel::doGetTrackId(const TrackPointer& pTrack) const { + if (pTrack) { + for (int row = 0; row < rowCount(); ++row) { + const QUrl rowUrl(getFieldString(index(row, 0), CLM_URI)); + if (TrackFile::fromUrl(rowUrl) == pTrack->getFileInfo()) { + return TrackId(getFieldVariant(index(row, 0), CLM_VIEW_ORDER)); } } } + return TrackId(); } QVariant BansheePlaylistModel::getFieldVariant(const QModelIndex& index, diff --git a/src/library/banshee/bansheeplaylistmodel.h b/src/library/banshee/bansheeplaylistmodel.h index 6e7149a710a3..d6cad06cf5a0 100644 --- a/src/library/banshee/bansheeplaylistmodel.h +++ b/src/library/banshee/bansheeplaylistmodel.h @@ -28,19 +28,12 @@ class BansheePlaylistModel : public BaseSqlTableModel { Qt::ItemFlags flags(const QModelIndex &index) const final; CapabilitiesFlags getCapabilities() const final; - bool setData(const QModelIndex& index, const QVariant& value, int role=Qt::EditRole) final; - - protected: - // Use this if you want a model that is read-only. - Qt::ItemFlags readOnlyFlags(const QModelIndex &index) const final; - // Use this if you want a model that can be changed - Qt::ItemFlags readWriteFlags(const QModelIndex &index) const final; - private slots: void tracksChanged(QSet trackIds); - void trackLoaded(QString group, TrackPointer pTrack); private: + TrackId doGetTrackId(const TrackPointer& pTrack) const final; + QString getFieldString(const QModelIndex& index, const QString& fieldName) const; QVariant getFieldVariant(const QModelIndex& index, const QString& fieldName) const; void dropTempTable(); diff --git a/src/library/baseexternalplaylistmodel.cpp b/src/library/baseexternalplaylistmodel.cpp index fd560fbc59b3..0a2b17807cb5 100644 --- a/src/library/baseexternalplaylistmodel.cpp +++ b/src/library/baseexternalplaylistmodel.cpp @@ -145,34 +145,19 @@ void BaseExternalPlaylistModel::setPlaylist(QString playlist_path) { setSearch(""); } -void BaseExternalPlaylistModel::trackLoaded(QString group, TrackPointer pTrack) { - if (group == m_previewDeckGroup) { - // If there was a previously loaded track, refresh its rows so the - // preview state will update. - if (m_previewDeckTrackId.isValid()) { - const int numColumns = columnCount(); - QLinkedList rows = getTrackRows(m_previewDeckTrackId); - m_previewDeckTrackId = TrackId(); // invalidate - foreach (int row, rows) { - QModelIndex left = index(row, 0); - QModelIndex right = index(row, numColumns); - emit dataChanged(left, right); - } - } - if (pTrack) { - // The external table has foreign Track IDs, so we need to compare - // by location - for (int row = 0; row < rowCount(); ++row) { - QString nativeLocation = index(row, fieldIndex("location")).data().toString(); - QString location = QDir::fromNativeSeparators(nativeLocation); - if (location == pTrack->getLocation()) { - m_previewDeckTrackId = TrackId(index(row, 0).data()); - //Debug() << "foreign track id" << m_previewDeckTrackId; - break; - } +TrackId BaseExternalPlaylistModel::doGetTrackId(const TrackPointer& pTrack) const { + if (pTrack) { + // The external table has foreign Track IDs, so we need to compare + // by location + for (int row = 0; row < rowCount(); ++row) { + QString nativeLocation = index(row, fieldIndex("location")).data().toString(); + QString location = QDir::fromNativeSeparators(nativeLocation); + if (location == pTrack->getLocation()) { + return TrackId(index(row, 0).data()); } } } + return TrackId(); } TrackModel::CapabilitiesFlags BaseExternalPlaylistModel::getCapabilities() const { diff --git a/src/library/baseexternalplaylistmodel.h b/src/library/baseexternalplaylistmodel.h index 8d0321ff9c02..b1b19e9f0057 100644 --- a/src/library/baseexternalplaylistmodel.h +++ b/src/library/baseexternalplaylistmodel.h @@ -28,10 +28,11 @@ class BaseExternalPlaylistModel : public BaseSqlTableModel { TrackId getTrackId(const QModelIndex& index) const override; bool isColumnInternal(int column) override; Qt::ItemFlags flags(const QModelIndex &index) const override; - void trackLoaded(QString group, TrackPointer pTrack) override; CapabilitiesFlags getCapabilities() const override; private: + TrackId doGetTrackId(const TrackPointer& pTrack) const override; + QString m_playlistsTable; QString m_playlistTracksTable; QSharedPointer m_trackSource; diff --git a/src/library/baseexternaltrackmodel.cpp b/src/library/baseexternaltrackmodel.cpp index 6d4d552522ab..68f2aa87948e 100644 --- a/src/library/baseexternaltrackmodel.cpp +++ b/src/library/baseexternaltrackmodel.cpp @@ -88,34 +88,19 @@ TrackId BaseExternalTrackModel::getTrackId(const QModelIndex& index) const { } } -void BaseExternalTrackModel::trackLoaded(QString group, TrackPointer pTrack) { - if (group == m_previewDeckGroup) { - // If there was a previously loaded track, refresh its rows so the - // preview state will update. - if (m_previewDeckTrackId.isValid()) { - const int numColumns = columnCount(); - QLinkedList rows = getTrackRows(m_previewDeckTrackId); - m_previewDeckTrackId = TrackId(); // invalidate - foreach (int row, rows) { - QModelIndex left = index(row, 0); - QModelIndex right = index(row, numColumns); - emit dataChanged(left, right); - } - } - if (pTrack) { - // The external table has foreign Track IDs, so we need to compare - // by location - for (int row = 0; row < rowCount(); ++row) { - QString nativeLocation = index(row, fieldIndex("location")).data().toString(); - QString location = QDir::fromNativeSeparators(nativeLocation); - if (location == pTrack->getLocation()) { - m_previewDeckTrackId = TrackId(index(row, 0).data()); - //qDebug() << "foreign track id" << m_previewDeckTrackId; - break; - } +TrackId BaseExternalTrackModel::doGetTrackId(const TrackPointer& pTrack) const { + if (pTrack) { + // The external table has foreign Track IDs, so we need to compare + // by location + for (int row = 0; row < rowCount(); ++row) { + QString nativeLocation = index(row, fieldIndex("location")).data().toString(); + QString location = QDir::fromNativeSeparators(nativeLocation); + if (location == pTrack->getLocation()) { + return TrackId(index(row, 0).data()); } } } + return TrackId(); } bool BaseExternalTrackModel::isColumnInternal(int column) { diff --git a/src/library/baseexternaltrackmodel.h b/src/library/baseexternaltrackmodel.h index 16db84caabfa..23ed68092c4f 100644 --- a/src/library/baseexternaltrackmodel.h +++ b/src/library/baseexternaltrackmodel.h @@ -23,9 +23,11 @@ class BaseExternalTrackModel : public BaseSqlTableModel { CapabilitiesFlags getCapabilities() const override; TrackId getTrackId(const QModelIndex& index) const override; TrackPointer getTrack(const QModelIndex& index) const override; - void trackLoaded(QString group, TrackPointer pTrack) override; bool isColumnInternal(int column) override; Qt::ItemFlags flags(const QModelIndex &index) const override; + + private: + TrackId doGetTrackId(const TrackPointer& pTrack) const override; }; #endif /* BASEEXTERNALTRACKMODEL_H */ diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index fea7a461229f..2c9538465344 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -1,32 +1,25 @@ // Created by RJ Ryan (rryan@mit.edu) 1/29/2010 -#include -#include -#include - #include "library/basesqltablemodel.h" -#include "library/bpmdelegate.h" -#include "library/colordelegate.h" +#include +#include +#include + #include "library/coverartdelegate.h" #include "library/dao/trackschema.h" -#include "library/locationdelegate.h" -#include "library/previewbuttondelegate.h" +#include "library/queryutil.h" +#include "library/starrating.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" -#include "library/stardelegate.h" -#include "library/starrating.h" -#include "library/queryutil.h" #include "mixer/playermanager.h" -#include "mixer/playerinfo.h" #include "track/keyutils.h" #include "track/trackmetadata.h" +#include "util/assert.h" #include "util/db/dbconnection.h" #include "util/duration.h" -#include "util/assert.h" #include "util/performancetimer.h" #include "util/platform.h" -#include "widget/wlibrarytableview.h" namespace { @@ -41,8 +34,7 @@ const int kMaxSortColumns = 3; // Constant for getModelSetting(name) const QString COLUMNS_SORTING = QStringLiteral("ColumnsSorting"); -// Alpha value for row color background (range 0 - 255) -constexpr int kTrackColorRowBackgroundOpacity = 0x20; // 12.5% opacity +const QString kEmptyString = QStringLiteral(""); } // anonymous namespace @@ -51,88 +43,27 @@ BaseSqlTableModel::BaseSqlTableModel( TrackCollectionManager* pTrackCollectionManager, const char* settingsNamespace) : BaseTrackTableModel( - pTrackCollectionManager->internalCollection()->database(), settingsNamespace, + pTrackCollectionManager, parent), m_pTrackCollectionManager(pTrackCollectionManager), m_database(pTrackCollectionManager->internalCollection()->database()), - m_previewDeckGroup(PlayerManager::groupForPreviewDeck(0)), m_bInitialized(false), - m_currentSearch("") { - connect(&PlayerInfo::instance(), - &PlayerInfo::trackLoaded, - this, - &BaseSqlTableModel::trackLoaded); - connect(&pTrackCollectionManager->internalCollection()->getTrackDAO(), - &TrackDAO::forceModelUpdate, - this, - &BaseSqlTableModel::select); - // TODO(rryan): This is a virtual function call from a constructor. - trackLoaded(m_previewDeckGroup, PlayerInfo::instance().getTrackInfo(m_previewDeckGroup)); + m_currentSearch(kEmptyString) { } BaseSqlTableModel::~BaseSqlTableModel() { } -void BaseSqlTableModel::initHeaderData() { - // Set the column heading labels, rename them for translations and have - // proper capitalization - - // TODO(owilliams): Clean this up to make it readable. - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED, - tr("Played"), 50); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST, - tr("Artist"), 200); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_TITLE, - tr("Title"), 300); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_ALBUM, - tr("Album"), 200); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST, - tr("Album Artist"), 100); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_GENRE, - tr("Genre"), 100); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER, - tr("Composer"), 50); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING, - tr("Grouping"), 10); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_YEAR, - tr("Year"), 40); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE, - tr("Type"), 50); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION, - tr("Location"), 100); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_COMMENT, - tr("Comment"), 250); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_DURATION, - tr("Duration"), 70); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_RATING, - tr("Rating"), 100); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE, - tr("Bitrate"), 50); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_BPM, - tr("BPM"), 70); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER, - tr("Track #"), 10); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED, - tr("Date Added"), 90); +void BaseSqlTableModel::initHeaderProperties() { + BaseTrackTableModel::initHeaderProperties(); + // Add playlist columns setHeaderProperties(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_POSITION, - tr("#"), 30); + tr("#"), + 30); setHeaderProperties(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_DATETIMEADDED, - tr("Timestamp"), 80); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_KEY, - tr("Key"), 50); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK, - tr("BPM Lock"), 10); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW, - tr("Preview"), 50); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_COVERART, - tr("Cover Art"), 90); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_COLOR, - tr("Color"), 10); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN, - tr("ReplayGain"), 50); - setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_SAMPLERATE, - tr("Samplerate"), 50); + tr("Timestamp"), + 80); } void BaseSqlTableModel::initSortColumnMapping() { @@ -172,79 +103,6 @@ void BaseSqlTableModel::initSortColumnMapping() { } } -void BaseSqlTableModel::setHeaderProperties( - ColumnCache::Column column, QString title, int defaultWidth) { - int fi = fieldIndex(column); - setHeaderData(fi, Qt::Horizontal, m_tableColumnCache.columnName(column), - TrackModel::kHeaderNameRole); - setHeaderData(fi, Qt::Horizontal, title, Qt::DisplayRole); - setHeaderData(fi, Qt::Horizontal, defaultWidth, TrackModel::kHeaderWidthRole); -} - -bool BaseSqlTableModel::setHeaderData(int section, Qt::Orientation orientation, - const QVariant &value, int role) { - int numColumns = columnCount(); - if (section < 0 || section >= numColumns) { - return false; - } - - if (orientation != Qt::Horizontal) { - // We only care about horizontal headers. - return false; - } - - if (m_headerInfo.size() != numColumns) { - m_headerInfo.resize(numColumns); - } - - m_headerInfo[section][role] = value; - emit headerDataChanged(orientation, section, section); - return true; -} - -QVariant BaseSqlTableModel::headerData(int section, Qt::Orientation orientation, - int role) const { - if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { - QVariant headerValue = m_headerInfo.value(section).value(role); - if (!headerValue.isValid()) { - // Try EditRole if DisplayRole wasn't present - headerValue = m_headerInfo.value(section).value(Qt::EditRole); - } - if (!headerValue.isValid()) { - headerValue = QVariant(section).toString(); - } - return headerValue; - } else if (role == TrackModel::kHeaderWidthRole && orientation == Qt::Horizontal) { - QVariant widthValue = m_headerInfo.value(section).value(role); - if (!widthValue.isValid()) { - return 50; - } - return widthValue; - } else if (role == TrackModel::kHeaderNameRole && orientation == Qt::Horizontal) { - return m_headerInfo.value(section).value(role); - } else if (role == Qt::ToolTipRole && orientation == Qt::Horizontal) { - QVariant tooltip = m_headerInfo.value(section).value(role); - if (tooltip.isValid()) return tooltip; - } - return QAbstractTableModel::headerData(section, orientation, role); -} - - -bool BaseSqlTableModel::isColumnHiddenByDefault(int column) { - if ((column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_SAMPLERATE))) { - return true; - } - return false; -} - void BaseSqlTableModel::clearRows() { DEBUG_ASSERT(m_rowInfo.empty() == m_trackIdToRows.empty()); DEBUG_ASSERT(m_rowInfo.size() >= m_trackIdToRows.size()); @@ -259,8 +117,8 @@ void BaseSqlTableModel::clearRows() { } void BaseSqlTableModel::replaceRows( - QVector&& rows, - TrackId2Rows&& trackIdToRows) { + QVector&& rows, + TrackId2Rows&& trackIdToRows) { // NOTE(uklotzde): Use r-value references for parameters here, because // conceptually those parameters should replace the corresponding internal // member variables. Currently Qt4/5 doesn't support move semantics and @@ -304,7 +162,7 @@ void BaseSqlTableModel::select() { // Prepare query for id and all columns not in m_trackSource QString queryString = QString("SELECT %1 FROM %2 %3") - .arg(m_tableColumns.join(","), m_tableName, m_tableOrderBy); + .arg(m_tableColumns.join(","), m_tableName, m_tableOrderBy); if (sDebug) { qDebug() << this << "select() executing:" << queryString; @@ -358,7 +216,7 @@ void BaseSqlTableModel::select() { // current position defines the ordering rowInfo.order = rowInfos.size(); rowInfo.metadata.reserve(sqlRecord.count()); - for (int i = 0; i < m_tableColumns.size(); ++i) { + for (int i = 0; i < m_tableColumns.size(); ++i) { rowInfo.metadata.push_back(sqlRecord.value(i)); } rowInfos.push_back(rowInfo); @@ -370,16 +228,16 @@ void BaseSqlTableModel::select() { if (m_trackSource) { m_trackSource->filterAndSort(trackIds, - m_currentSearch, - m_currentSearchFilter, - m_trackSourceOrderBy, - m_sortColumns, - m_tableColumns.size() - 1, // exclude the 1st column with the id - &m_trackSortOrder); + m_currentSearch, + m_currentSearchFilter, + m_trackSourceOrderBy, + m_sortColumns, + m_tableColumns.size() - 1, // exclude the 1st column with the id + &m_trackSortOrder); // Re-sort the track IDs since filterAndSort can change their order or mark // them for removal (by setting their row to -1). - for (auto& rowInfo: rowInfos) { + for (auto& rowInfo : rowInfos) { // If the sort is not a track column then we will sort only to // separate removed tracks (order == -1) from present tracks (order == // 0). Otherwise we sort by the order that filterAndSort returned to us. @@ -428,9 +286,9 @@ void BaseSqlTableModel::select() { } void BaseSqlTableModel::setTable(const QString& tableName, - const QString& idColumn, - const QStringList& tableColumns, - QSharedPointer trackSource) { + const QString& idColumn, + const QStringList& tableColumns, + QSharedPointer trackSource) { if (sDebug) { qDebug() << this << "setTable" << tableName << tableColumns << idColumn; } @@ -460,10 +318,7 @@ void BaseSqlTableModel::setTable(const QString& tableName, Qt::QueuedConnection); } - // Build a map from the column names to their indices, used by fieldIndex() - m_tableColumnCache.setColumns(m_tableColumns); - - initHeaderData(); + initTableColumnsAndHeaderProperties(m_tableColumns); initSortColumnMapping(); m_bInitialized = true; @@ -481,7 +336,6 @@ TrackModel::SortColumnId BaseSqlTableModel::sortColumnIdFromColumnIndex(int inde return m_sortColumnIdByColumnIndex.value(index, TrackModel::SortColumnId::SORTCOLUMN_INVALID); } - const QString BaseSqlTableModel::currentSearch() const { return m_currentSearch; } @@ -538,7 +392,9 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { in >> name >> ordI; int col = fieldIndex(name); - if (col < 0) continue; + if (col < 0) { + continue; + } Qt::SortOrder ord; ord = ordI > 0 ? Qt::AscendingOrder : Qt::DescendingOrder; @@ -547,8 +403,8 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { } } if (m_sortColumns.size() > 0 && m_sortColumns.at(0).m_column == column) { - // Only the order has changed - m_sortColumns.replace(0, SortColumn(column, order)); + // Only the order has changed + m_sortColumns.replace(0, SortColumn(column, order)); } else { // Remove column if already in history // As reverse loop to not skip an entry when removing the previous @@ -571,7 +427,6 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { QString val; QTextStream out(&val); for (SortColumn& sc : m_sortColumns) { - QString name; if (sc.m_column > 0 && sc.m_column < m_tableColumns.size()) { name = m_tableColumns[sc.m_column]; @@ -591,7 +446,6 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { qDebug() << "setSort() sortColumns:" << val; } - // we have two selects for sorting, since keeping the select history // across the two selects is hard, we do this only for the trackSource // this is OK, because the columns of the table are virtual in case of @@ -618,7 +472,7 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { m_sortColumns.prepend(SortColumn(column, order)); } else if (m_trackSource) { bool first = true; - for (const SortColumn &sc : m_sortColumns) { + for (const SortColumn& sc : m_sortColumns) { QString sort_field; if (sc.m_column < m_tableColumns.size()) { if (sc.m_column == kIdColumn) { @@ -640,10 +494,9 @@ void BaseSqlTableModel::setSort(int column, Qt::SortOrder order) { continue; } - m_trackSourceOrderBy.append(first ? "ORDER BY ": ", "); + m_trackSourceOrderBy.append(first ? "ORDER BY " : ", "); m_trackSourceOrderBy.append(mixxx::DbConnection::collateLexicographically(sort_field)); - m_trackSourceOrderBy.append((sc.m_order == Qt::AscendingOrder) ? - " ASC" : " DESC"); + m_trackSourceOrderBy.append((sc.m_order == Qt::AscendingOrder) ? " ASC" : " DESC"); //qDebug() << m_trackSourceOrderBy; first = false; } @@ -665,22 +518,20 @@ int BaseSqlTableModel::rowCount(const QModelIndex& parent) const { } int BaseSqlTableModel::columnCount(const QModelIndex& parent) const { - if (parent.isValid()) { + VERIFY_OR_DEBUG_ASSERT(!parent.isValid()) { return 0; } - // Subtract one from trackSource::columnCount to ignore the id column int count = m_tableColumns.size() + - (m_trackSource ? m_trackSource->columnCount() - 1: 0); + (m_trackSource ? m_trackSource->columnCount() - 1 : 0); return count; } int BaseSqlTableModel::fieldIndex(ColumnCache::Column column) const { - int tableIndex = m_tableColumnCache.fieldIndex(column); - if (tableIndex > -1) { + int tableIndex = BaseTrackTableModel::fieldIndex(column); + if (tableIndex >= 0) { return tableIndex; } - if (m_trackSource) { // We need to account for the case where the field name is not a table // column or a source column. @@ -690,15 +541,14 @@ int BaseSqlTableModel::fieldIndex(ColumnCache::Column column) const { return m_tableColumns.size() + sourceTableIndex - 1; } } - return -1; + return tableIndex; } int BaseSqlTableModel::fieldIndex(const QString& fieldName) const { - int tableIndex = m_tableColumnCache.fieldIndex(fieldName); - if (tableIndex > -1) { + int tableIndex = BaseTrackTableModel::fieldIndex(fieldName); + if (tableIndex >= 0) { return tableIndex; } - if (m_trackSource) { // We need to account for the case where the field name is not a table // column or a source column. @@ -708,318 +558,93 @@ int BaseSqlTableModel::fieldIndex(const QString& fieldName) const { return m_tableColumns.size() + sourceTableIndex - 1; } } - return -1; + return tableIndex; } -QVariant BaseSqlTableModel::data(const QModelIndex& index, int role) const { - //qDebug() << this << "data()"; - if (!index.isValid() || ( - role != Qt::BackgroundRole && - role != Qt::DisplayRole && - role != Qt::EditRole && - role != Qt::CheckStateRole && - role != Qt::ToolTipRole)) { - return QVariant(); - } +QVariant BaseSqlTableModel::rawValue( + const QModelIndex& index) const { + DEBUG_ASSERT(index.isValid()); - int row = index.row(); - int column = index.column(); - - // This value is the value in its most raw form. It was looked up either - // from the SQL table or from the cached track layer. - QVariant value = getBaseValue(index, role); - - // Format the value based on whether we are in a tooltip, display, or edit - // role - switch (role) { - case Qt::BackgroundRole: { - QModelIndex colorIndex = index.sibling( - index.row(), - fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)); - QColor color = mixxx::RgbColor::toQColor( - mixxx::RgbColor::fromQVariant(getBaseValue(colorIndex, role))); - if (color.isValid()) { - color.setAlpha(kTrackColorRowBackgroundOpacity); - value = QBrush(color); - } else { - value = QVariant(); - } - break; - } - case Qt::ToolTipRole: - if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)) { - value = mixxx::RgbColor::toQString(mixxx::RgbColor::fromQVariant(value)); - } - M_FALLTHROUGH_INTENDED; - case Qt::DisplayRole: - if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION)) { - int duration = value.toInt(); - if (duration > 0) { - value = mixxx::Duration::formatTime(duration); - } else { - value = QString(); - } - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { - if (value.canConvert(QMetaType::Int)) - value = QVariant::fromValue(StarRating(value.toInt())); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { - if (value.canConvert(QMetaType::Int)) - value = QString("(%1)").arg(value.toInt()); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)) { - value = value.toBool(); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED)) { - QDateTime gmtDate = value.toDateTime(); - gmtDate.setTimeSpec(Qt::UTC); - value = gmtDate.toLocalTime(); - } else if (column == fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_DATETIMEADDED)) { - QDateTime gmtDate = value.toDateTime(); - gmtDate.setTimeSpec(Qt::UTC); - value = gmtDate.toLocalTime(); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { - if (role == Qt::DisplayRole) { - value = value.toDouble() == 0.0 - ? "-" : QString("%1").arg(value.toDouble(), 0, 'f', 1); - } - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) { - value = value.toBool(); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR)) { - value = mixxx::TrackMetadata::formatCalendarYear(value.toString()); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER)) { - int track_number = value.toInt(); - if (track_number <= 0) { - // clear invalid values - value = QString(); - } - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE)) { - int bitrate = value.toInt(); - if (bitrate <= 0) { - // clear invalid values - value = QString(); - } - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY)) { - // If we know the semantic key via the LIBRARYTABLE_KEY_ID - // column (as opposed to the string representation of the key - // currently stored in the DB) then lookup the key and render it - // using the user's selected notation. - int keyIdColumn = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY_ID); - if (keyIdColumn != -1) { - mixxx::track::io::key::ChromaticKey key = - KeyUtils::keyFromNumericValue( - index.sibling(row, keyIdColumn).data().toInt()); - - if (key != mixxx::track::io::key::INVALID) { - // Render this key with the user-provided notation. - value = KeyUtils::keyToString(key); - } - } - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN)) { - value = mixxx::ReplayGain::ratioToString(value.toDouble()); - } // Otherwise, just use the column value. - - break; - case Qt::EditRole: - if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { - value = value.toDouble(); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { - value = index.sibling( - row, fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)).data().toBool(); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { - if (value.canConvert(QMetaType::Int)) { - value = QVariant::fromValue(StarRating(value.toInt())); - } - } - break; - case Qt::CheckStateRole: - if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { - bool played = index.sibling( - row, fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)).data().toBool(); - value = played ? Qt::Checked : Qt::Unchecked; - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { - bool locked = index.sibling( - row, fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)).data().toBool(); - value = locked ? Qt::Checked : Qt::Unchecked; - } - break; - default: - break; + const int row = index.row(); + DEBUG_ASSERT(row >= 0); + if (row >= m_rowInfo.size()) { + return QVariant(); } - return value; -} -bool BaseSqlTableModel::setData( - const QModelIndex& index, const QVariant& value, int role) { - if (!index.isValid()) - return false; + const int column = index.column(); + DEBUG_ASSERT(column >= 0); + // TODO(rryan) check range on column - int row = index.row(); - int column = index.column(); + const RowInfo& rowInfo = m_rowInfo[row]; + const TrackId trackId = rowInfo.trackId; - if (sDebug) { - qDebug() << this << "setData() column:" << column << "value:" << value << "role:" << role; - } + // If the row info has the row-specific column, return that. + if (column < m_tableColumns.size()) { + // Special case for preview column. Return whether trackId is the + // current preview deck track. + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) { + return previewDeckTrackId() == trackId; + } - // Over-ride sets to TIMESPLAYED and re-direct them to PLAYED - if (role == Qt::CheckStateRole) { - QString val = value.toInt() > 0 ? QString("true") : QString("false"); - if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { - QModelIndex playedIndex = index.sibling(index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)); - return setData(playedIndex, val, Qt::EditRole); - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { - QModelIndex bpmLockindex = index.sibling(index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)); - return setData(bpmLockindex, val, Qt::EditRole); + const QVector& columns = rowInfo.metadata; + if (sDebug) { + qDebug() << "Returning table-column value" + << columns.at(column) + << "for column" << column; } - return false; + return columns[column]; } - if (row < 0 || row >= m_rowInfo.size()) { + // Otherwise, return the information from the track record cache for the + // given track ID + if (!m_trackSource) { + return QVariant(); + } + // Subtract table columns from index to get the track source column + // number and add 1 to skip over the id column. + int trackSourceColumn = column - m_tableColumns.size() + 1; + if (!m_trackSource->isCached(trackId)) { + // Ideally Mixxx would have notified us of this via a signal, but in + // the case that a track is not in the cache, we attempt to load it + // on the fly. This will be a steep penalty to pay if there are tons + // of these tracks in the table that are not cached. + qDebug() << __FILE__ << __LINE__ + << "Track" << trackId + << "was not present in cache and had to be manually fetched."; + m_trackSource->ensureCached(trackId); + } + return m_trackSource->data(trackId, trackSourceColumn); +} + +QVariant BaseSqlTableModel::roleValue( + const QModelIndex& index, + QVariant&& rawValue, + int role) const { + if (role == Qt::DisplayRole && + index.column() == fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_DATETIMEADDED)) { + QDateTime gmtDate = rawValue.toDateTime(); + gmtDate.setTimeSpec(Qt::UTC); + return gmtDate.toLocalTime(); + } + return BaseTrackTableModel::roleValue(index, std::move(rawValue), role); +} + +bool BaseSqlTableModel::setTrackValueForColumn( + const TrackPointer& pTrack, + int column, + const QVariant& value, + int role) { + if (role != Qt::EditRole) { return false; } - - const RowInfo& rowInfo = m_rowInfo[row]; - TrackId trackId(rowInfo.trackId); - // You can't set something in the table columns because we have no way of // persisting it. if (column < m_tableColumns.size()) { return false; } - // TODO(rryan) ugly and only works because the mixxx library tables are the - // only ones that aren't read-only. This should be moved into BTC. - TrackPointer pTrack = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); - if (!pTrack) { - return false; - } - setTrackValueForColumn(pTrack, column, value); - - // Do not save the track here. Changing the track dirties it and the caching - // system will automatically save the track once it is unloaded from - // memory. rryan 10/2010 - - return true; -} - -Qt::ItemFlags BaseSqlTableModel::flags(const QModelIndex &index) const { - return readWriteFlags(index); -} - -Qt::ItemFlags BaseSqlTableModel::readWriteFlags( - const QModelIndex &index) const { - if (!index.isValid()) - return Qt::ItemIsEnabled; - - Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); - - // Enable dragging songs from this data model to elsewhere (like the - // waveform widget to load a track into a Player). - defaultFlags |= Qt::ItemIsDragEnabled; - - int column = index.column(); - - if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)) { - return defaultFlags; - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { - return defaultFlags | Qt::ItemIsUserCheckable; - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) { - return defaultFlags | Qt::ItemIsUserCheckable; - } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { - // Allow checking of the BPM-locked indicator. - defaultFlags |= Qt::ItemIsUserCheckable; - // Disable editing of BPM field when BPM is locked - bool locked = index.sibling( - index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) - .data().toBool(); - return locked ? defaultFlags : defaultFlags | Qt::ItemIsEditable; - } else { - return defaultFlags | Qt::ItemIsEditable; - } -} - -Qt::ItemFlags BaseSqlTableModel::readOnlyFlags(const QModelIndex &index) const { - Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); - if (!index.isValid()) - return Qt::ItemIsEnabled; - - // Enable dragging songs from this data model to elsewhere (like the - // waveform widget to load a track into a Player). - defaultFlags |= Qt::ItemIsDragEnabled; - - return defaultFlags; -} - -TrackId BaseSqlTableModel::getTrackId(const QModelIndex& index) const { - if (index.isValid()) { - return TrackId(index.sibling(index.row(), fieldIndex(m_idColumn)).data()); - } else { - return TrackId(); - } -} - -TrackPointer BaseSqlTableModel::getTrack(const QModelIndex& index) const { - return m_pTrackCollectionManager->internalCollection()->getTrackById(getTrackId(index)); -} - -TrackPointer BaseSqlTableModel::getTrackByRef( - const TrackRef& trackRef) const { - return m_pTrackCollectionManager->internalCollection()->getTrackByRef(trackRef); -} - -QString BaseSqlTableModel::getTrackLocation(const QModelIndex& index) const { - if (!index.isValid()) { - return QString(); - } - QString nativeLocation = - index.sibling(index.row(), - fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION)) - .data().toString(); - return QDir::fromNativeSeparators(nativeLocation); -} - -void BaseSqlTableModel::trackLoaded(QString group, TrackPointer pTrack) { - if (group == m_previewDeckGroup) { - // If there was a previously loaded track, refresh its rows so the - // preview state will update. - if (m_previewDeckTrackId.isValid()) { - const int numColumns = columnCount(); - QLinkedList rows = getTrackRows(m_previewDeckTrackId); - m_previewDeckTrackId = TrackId(); // invalidate - foreach (int row, rows) { - QModelIndex left = index(row, 0); - QModelIndex right = index(row, numColumns); - emit dataChanged(left, right); - } - } - m_previewDeckTrackId = pTrack ? pTrack->getId() : TrackId(); - } -} - -void BaseSqlTableModel::tracksChanged(QSet trackIds) { - if (sDebug) { - qDebug() << this << "trackChanged" << trackIds.size(); - } - - const int numColumns = columnCount(); - for (const auto& trackId : trackIds) { - QLinkedList rows = getTrackRows(trackId); - foreach (int row, rows) { - //qDebug() << "Row in this result set was updated. Signalling update. track:" << trackId << "row:" << row; - QModelIndex left = index(row, 0); - QModelIndex right = index(row, numColumns); - emit dataChanged(left, right); - } - } -} - -void BaseSqlTableModel::setTrackValueForColumn(TrackPointer pTrack, int column, - QVariant value) { // TODO(XXX) Qt properties could really help here. + DEBUG_ASSERT(pTrack); if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ARTIST) == column) { pTrack->setArtist(value.toString()); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TITLE) == column) { @@ -1041,7 +666,6 @@ void BaseSqlTableModel::setTrackValueForColumn(TrackPointer pTrack, int column, } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMMENT) == column) { pTrack->setComment(value.toString()); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM) == column) { - // QVariant::toFloat needs >= QT 4.6.x pTrack->setBpm(static_cast(value.toDouble())); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) == column) { // Update both the played flag and the number of times played @@ -1062,133 +686,65 @@ void BaseSqlTableModel::setTrackValueForColumn(TrackPointer pTrack, int column, pTrack->setRating(starRating.starCount()); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY) == column) { pTrack->setKeyText(value.toString(), - mixxx::track::io::key::USER); + mixxx::track::io::key::USER); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK) == column) { pTrack->setBpmLocked(value.toBool()); } else { // We never should get up to this point! VERIFY_OR_DEBUG_ASSERT(false) { qWarning() << "Column" - << m_tableColumnCache.columnNameForFieldIndex(column) - << "is not editable!"; + << columnNameForFieldIndex(column) + << "is not editable!"; } + return false; } + return true; } -QVariant BaseSqlTableModel::getBaseValue( - const QModelIndex& index, int role) const { - if (role != Qt::BackgroundRole && - role != Qt::DisplayRole && - role != Qt::ToolTipRole && - role != Qt::EditRole) { - return QVariant(); - } - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= m_rowInfo.size()) { - return QVariant(); - } - - // TODO(rryan) check range on column - - const RowInfo& rowInfo = m_rowInfo[row]; - TrackId trackId(rowInfo.trackId); - - // If the row info has the row-specific column, return that. - if (column < m_tableColumns.size()) { - // Special case for preview column. Return whether trackId is the - // current preview deck track. - if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) { - if (role == Qt::ToolTipRole) { - return ""; - } - return m_previewDeckTrackId == trackId; - } +TrackPointer BaseSqlTableModel::getTrack(const QModelIndex& index) const { + return m_pTrackCollectionManager->internalCollection()->getTrackById(getTrackId(index)); +} - const QVector& columns = rowInfo.metadata; - if (sDebug) { - qDebug() << "Returning table-column value" << columns.at(column) - << "for column" << column << "role" << role; - } - return columns[column]; +TrackId BaseSqlTableModel::getTrackId(const QModelIndex& index) const { + if (index.isValid()) { + return TrackId(index.sibling(index.row(), fieldIndex(m_idColumn)).data()); + } else { + return TrackId(); } +} - // Otherwise, return the information from the track record cache for the - // given track ID - if (m_trackSource) { - // Subtract table columns from index to get the track source column - // number and add 1 to skip over the id column. - int trackSourceColumn = column - m_tableColumns.size() + 1; - if (!m_trackSource->isCached(trackId)) { - // Ideally Mixxx would have notified us of this via a signal, but in - // the case that a track is not in the cache, we attempt to load it - // on the fly. This will be a steep penalty to pay if there are tons - // of these tracks in the table that are not cached. - qDebug() << __FILE__ << __LINE__ - << "Track" << trackId - << "was not present in cache and had to be manually fetched."; - m_trackSource->ensureCached(trackId); - } - return m_trackSource->data(trackId, trackSourceColumn); +QString BaseSqlTableModel::getTrackLocation(const QModelIndex& index) const { + if (!index.isValid()) { + return QString(); } - return QVariant(); + QString nativeLocation = + index.sibling(index.row(), + fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION)) + .data() + .toString(); + return QDir::fromNativeSeparators(nativeLocation); } -QMimeData* BaseSqlTableModel::mimeData(const QModelIndexList &indexes) const { - QMimeData *mimeData = new QMimeData(); - QList urls; - - // The list of indexes we're given contains separates indexes for each - // column, so even if only one row is selected, we'll have columnCount() - // indices. We need to only count each row once: - QSet rows; +void BaseSqlTableModel::tracksChanged(QSet trackIds) { + if (sDebug) { + qDebug() << this << "trackChanged" << trackIds.size(); + } - foreach (QModelIndex index, indexes) { - if (!index.isValid() || rows.contains(index.row())) { - continue; - } - rows.insert(index.row()); - QUrl url = TrackFile(getTrackLocation(index)).toUrl(); - if (!url.isValid()) { - qDebug() << this << "ERROR: invalid url" << url; - continue; + const int numColumns = columnCount(); + for (const auto& trackId : trackIds) { + QLinkedList rows = getTrackRows(trackId); + foreach (int row, rows) { + //qDebug() << "Row in this result set was updated. Signalling update. track:" << trackId << "row:" << row; + QModelIndex topLeft = index(row, 0); + QModelIndex bottomRight = index(row, numColumns); + emit dataChanged(topLeft, bottomRight); } - urls.append(url); } - mimeData->setUrls(urls); - return mimeData; } -QAbstractItemDelegate* BaseSqlTableModel::delegateForColumn(const int i, QObject* pParent) { - auto* pTableView = qobject_cast(pParent); - DEBUG_ASSERT(pTableView); - - if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { - return new StarDelegate(pTableView); - } else if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { - return new BPMDelegate(pTableView); - } else if (PlayerManager::numPreviewDecks() > 0 && i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) { - return new PreviewButtonDelegate(pTableView, i); - } else if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION)) { - return new LocationDelegate(pTableView); - } else if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)) { - return new ColorDelegate(pTableView); - } else if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART)) { - auto* pCoverArtDelegate = - new CoverArtDelegate(pTableView); - connect(pTableView, - &WLibraryTableView::onlyCachedCoverArt, - pCoverArtDelegate, - &CoverArtDelegate::slotInhibitLazyLoading); - connect(pCoverArtDelegate, - &CoverArtDelegate::rowsChanged, - this, - &BaseSqlTableModel::slotRefreshCoverRows); - return pCoverArtDelegate; - } - return nullptr; +BaseCoverArtDelegate* BaseSqlTableModel::doCreateCoverArtDelegate( + QTableView* pTableView) const { + return new CoverArtDelegate(pTableView); } void BaseSqlTableModel::slotRefreshCoverRows(QList rows) { @@ -1199,7 +755,7 @@ void BaseSqlTableModel::slotRefreshCoverRows(QList rows) { VERIFY_OR_DEBUG_ASSERT(column >= 0) { return; } - emitDataChangedForMultipleRowsSingleColumn(rows, column); + emitDataChangedForMultipleRowsInColumn(rows, column); } void BaseSqlTableModel::hideTracks(const QModelIndexList& indices) { diff --git a/src/library/basesqltablemodel.h b/src/library/basesqltablemodel.h index be6d764be35a..be574d22c57c 100644 --- a/src/library/basesqltablemodel.h +++ b/src/library/basesqltablemodel.h @@ -32,74 +32,66 @@ class BaseSqlTableModel : public BaseTrackTableModel { void setSearch(const QString& searchText, const QString& extraFilter = QString()); void setSort(int column, Qt::SortOrder order); - int fieldIndex(ColumnCache::Column column) const; - - /////////////////////////////////////////////////////////////////////////// - // Inherited from TrackModel - /////////////////////////////////////////////////////////////////////////// - int fieldIndex(const QString& fieldName) const final; - /////////////////////////////////////////////////////////////////////////// // Inherited from QAbstractItemModel /////////////////////////////////////////////////////////////////////////// - void sort(int column, Qt::SortOrder order) final; - int rowCount(const QModelIndex& parent=QModelIndex()) const final; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const final; + int rowCount(const QModelIndex& parent = QModelIndex()) const final; int columnCount(const QModelIndex& parent = QModelIndex()) const final; - bool setHeaderData(int section, Qt::Orientation orientation, - const QVariant &value, int role = Qt::DisplayRole) final; - QVariant headerData(int section, Qt::Orientation orientation, - int role=Qt::DisplayRole) const final; - QMimeData* mimeData(const QModelIndexList &indexes) const final; - - /////////////////////////////////////////////////////////////////////////// - // Functions that might be reimplemented/overridden in derived classes - /////////////////////////////////////////////////////////////////////////// - // This class also has protected variables that should be used in children - // m_database, m_pTrackCollection - // calls readWriteFlags() by default, reimplement this if the child calls - // should be readOnly - Qt::ItemFlags flags(const QModelIndex &index) const override; + void sort(int column, Qt::SortOrder order) final; /////////////////////////////////////////////////////////////////////////// // Inherited from TrackModel /////////////////////////////////////////////////////////////////////////// - bool isColumnHiddenByDefault(int column) override; + int fieldIndex(const QString& fieldName) const final; + TrackPointer getTrack(const QModelIndex& index) const override; - TrackPointer getTrackByRef(const TrackRef& trackRef) const override; TrackId getTrackId(const QModelIndex& index) const override; + QString getTrackLocation(const QModelIndex& index) const override; + const QLinkedList getTrackRows(TrackId trackId) const override { return m_trackIdToRows.value(trackId); } - QString getTrackLocation(const QModelIndex& index) const override; - void hideTracks(const QModelIndexList& indices) override; + void search(const QString& searchText, const QString& extraFilter = QString()) override; const QString currentSearch() const override; - QAbstractItemDelegate* delegateForColumn(const int i, QObject* pParent) override; + TrackModel::SortColumnId sortColumnIdFromColumnIndex(int column) override; int columnIndexFromSortColumnId(TrackModel::SortColumnId sortColumn) override; - /////////////////////////////////////////////////////////////////////////// - // Inherited from QAbstractItemModel - /////////////////////////////////////////////////////////////////////////// - bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + void hideTracks(const QModelIndexList& indices) override; - public slots: void select() override; + /////////////////////////////////////////////////////////////////////////// + // Inherited from BaseTrackTableModel + /////////////////////////////////////////////////////////////////////////// + int fieldIndex( + ColumnCache::Column column) const final; + protected: + /////////////////////////////////////////////////////////////////////////// + // Inherited from BaseTrackTableModel + /////////////////////////////////////////////////////////////////////////// + QVariant rawValue( + const QModelIndex& index) const override; + QVariant roleValue( + const QModelIndex& index, + QVariant&& rawValue, + int role) const override; + + bool setTrackValueForColumn( + const TrackPointer& pTrack, + int column, + const QVariant& value, + int role) final; + void setTable(const QString& tableName, const QString& trackIdColumn, const QStringList& tableColumns, QSharedPointer trackSource); - void initHeaderData(); + void initHeaderProperties() override; virtual void initSortColumnMapping(); - // Use this if you want a model that is read-only. - virtual Qt::ItemFlags readOnlyFlags(const QModelIndex &index) const; - // Use this if you want a model that can be changed - virtual Qt::ItemFlags readWriteFlags(const QModelIndex &index) const; - TrackCollectionManager* const m_pTrackCollectionManager; protected: @@ -107,25 +99,22 @@ class BaseSqlTableModel : public BaseTrackTableModel { QSqlDatabase m_database; - QString m_previewDeckGroup; - TrackId m_previewDeckTrackId; QString m_tableOrderBy; int m_columnIndexBySortColumnId[NUM_SORTCOLUMNIDS]; QMap m_sortColumnIdByColumnIndex; private slots: - virtual void tracksChanged(QSet trackIds); - virtual void trackLoaded(QString group, TrackPointer pTrack); + void tracksChanged(QSet trackIds); void slotRefreshCoverRows(QList rows); private: - // A simple helper function for initializing header title and width. Note - // that the ideal width of a column is based on the width of its data, - // not the title string itself. - void setHeaderProperties(ColumnCache::Column column, QString title, int defaultWidth); - inline void setTrackValueForColumn(TrackPointer pTrack, int column, QVariant value); - QVariant getBaseValue(const QModelIndex& index, int role = Qt::DisplayRole) const; + BaseCoverArtDelegate* doCreateCoverArtDelegate( + QTableView* pTableView) const final; + + void setTrackValueForColumn( + TrackPointer pTrack, int column, QVariant value); + // Set the columns used for searching. Names must correspond to the column // names in the table provided to setTable. Must be called after setTable is // called. @@ -160,7 +149,6 @@ class BaseSqlTableModel : public BaseTrackTableModel { QString m_idColumn; QSharedPointer m_trackSource; QStringList m_tableColumns; - ColumnCache m_tableColumnCache; QList m_sortColumns; bool m_bInitialized; QHash m_trackSortOrder; diff --git a/src/library/basetrackcache.cpp b/src/library/basetrackcache.cpp index 29d7e7dbfd49..99a54c88105a 100644 --- a/src/library/basetrackcache.cpp +++ b/src/library/basetrackcache.cpp @@ -74,7 +74,7 @@ QString BaseTrackCache::columnSortForFieldIndex(int index) const { return m_columnCache.columnSortForFieldIndex(index); } -void BaseTrackCache::slotTracksAdded(QSet trackIds) { +void BaseTrackCache::slotTracksAddedOrChanged(QSet trackIds) { if (sDebug) { qDebug() << this << "slotTracksAdded" << trackIds.size(); } @@ -113,9 +113,7 @@ void BaseTrackCache::slotTrackChanged(TrackId trackId) { if (sDebug) { qDebug() << this << "slotTrackChanged" << trackId; } - QSet trackIds; - trackIds.insert(trackId); - emit tracksChanged(trackIds); + emit tracksChanged(QSet{trackId}); } void BaseTrackCache::slotTrackClean(TrackId trackId) { diff --git a/src/library/basetrackcache.h b/src/library/basetrackcache.h index b66b3519191d..96e5b91a8ac9 100644 --- a/src/library/basetrackcache.h +++ b/src/library/basetrackcache.h @@ -75,7 +75,7 @@ class BaseTrackCache : public QObject { void tracksChanged(QSet trackIds); public slots: - void slotTracksAdded(QSet trackId); + void slotTracksAddedOrChanged(QSet trackId); void slotTracksRemoved(QSet trackId); void slotTrackDirty(TrackId trackId); void slotTrackClean(TrackId trackId); diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 0b5757a1cc13..e09e4d1be0f2 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -1,16 +1,735 @@ #include "library/basetracktablemodel.h" +#include "library/basecoverartdelegate.h" +#include "library/bpmdelegate.h" +#include "library/colordelegate.h" +#include "library/dao/trackschema.h" +#include "library/locationdelegate.h" +#include "library/previewbuttondelegate.h" +#include "library/stardelegate.h" +#include "library/starrating.h" +#include "library/trackcollection.h" +#include "library/trackcollectionmanager.h" +#include "mixer/playerinfo.h" +#include "mixer/playermanager.h" #include "util/assert.h" +#include "util/compatibility.h" +#include "util/logger.h" +#include "widget/wlibrarytableview.h" + +namespace { + +const mixxx::Logger kLogger("BaseTrackTableModel"); + +const QString kEmptyString = QStringLiteral(""); + +// Alpha value for row color background (range 0 - 255) +constexpr int kTrackColorRowBackgroundOpacity = 0x20; // 12.5% opacity + +const QStringList kDefaultTableColumns = { + LIBRARYTABLE_ALBUM, + LIBRARYTABLE_ALBUMARTIST, + LIBRARYTABLE_ARTIST, + LIBRARYTABLE_BPM, + LIBRARYTABLE_BPM_LOCK, + LIBRARYTABLE_BITRATE, + LIBRARYTABLE_CHANNELS, + LIBRARYTABLE_COLOR, + LIBRARYTABLE_COMMENT, + LIBRARYTABLE_COMPOSER, + LIBRARYTABLE_COVERART, + LIBRARYTABLE_DATETIMEADDED, + LIBRARYTABLE_DURATION, + LIBRARYTABLE_FILETYPE, + LIBRARYTABLE_GENRE, + LIBRARYTABLE_GROUPING, + LIBRARYTABLE_KEY, + LIBRARYTABLE_LOCATION, + LIBRARYTABLE_PLAYED, + LIBRARYTABLE_PREVIEW, + LIBRARYTABLE_RATING, + LIBRARYTABLE_REPLAYGAIN, + LIBRARYTABLE_SAMPLERATE, + LIBRARYTABLE_TIMESPLAYED, + LIBRARYTABLE_TITLE, + LIBRARYTABLE_TRACKNUMBER, + LIBRARYTABLE_YEAR, +}; + +inline QSqlDatabase cloneDatabase( + const QSqlDatabase& prototype) { + const auto connectionName = + uuidToStringWithoutBraces(QUuid::createUuid()); + auto cloned = QSqlDatabase::cloneDatabase( + prototype, + connectionName); + if (prototype.isOpen() && !cloned.open()) { + kLogger.warning() + << "Failed to open cloned database connection" + << cloned + << cloned.lastError(); + } + return cloned; +} + +QSqlDatabase cloneDatabase( + TrackCollectionManager* pTrackCollectionManager) { + DEBUG_ASSERT(pTrackCollectionManager); + DEBUG_ASSERT(pTrackCollectionManager->internalCollection()); + const auto connectionName = + uuidToStringWithoutBraces(QUuid::createUuid()); + return cloneDatabase( + pTrackCollectionManager->internalCollection()->database()); +} + +} // anonymous namespace + +//static +QStringList BaseTrackTableModel::defaultTableColumns() { + return kDefaultTableColumns; +} BaseTrackTableModel::BaseTrackTableModel( - QSqlDatabase db, const char* settingsNamespace, + TrackCollectionManager* pTrackCollectionManager, QObject* parent) : QAbstractTableModel(parent), - TrackModel(db, settingsNamespace) { + TrackModel( + cloneDatabase(pTrackCollectionManager), + settingsNamespace), + m_pTrackCollectionManager(pTrackCollectionManager), + m_previewDeckGroup(PlayerManager::groupForPreviewDeck(0)) { + connect(&pTrackCollectionManager->internalCollection()->getTrackDAO(), + &TrackDAO::forceModelUpdate, + this, + &BaseTrackTableModel::slotRefreshAllRows); + connect(&PlayerInfo::instance(), + &PlayerInfo::trackLoaded, + this, + &BaseTrackTableModel::slotTrackLoaded); +} + +void BaseTrackTableModel::initTableColumnsAndHeaderProperties( + const QStringList& tableColumns) { + m_columnCache.setColumns(tableColumns); + if (m_columnHeaders.size() < tableColumns.size()) { + m_columnHeaders.resize(tableColumns.size()); + } + initHeaderProperties(); +} + +void BaseTrackTableModel::initHeaderProperties() { + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_ALBUM, + tr("Album"), + defaultColumnWidth() * 4); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST, + tr("Album Artist"), + defaultColumnWidth() * 4); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_ARTIST, + tr("Artist"), + defaultColumnWidth() * 4); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_BITRATE, + tr("Bitrate"), + defaultColumnWidth()); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_BPM, + tr("BPM"), + defaultColumnWidth() * 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_CHANNELS, + tr("Channels"), + defaultColumnWidth() / 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_COLOR, + tr("Color"), + defaultColumnWidth() / 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_COMMENT, + tr("Comment"), + defaultColumnWidth() * 6); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER, + tr("Composer"), + defaultColumnWidth() * 4); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_COVERART, + tr("Cover Art"), + defaultColumnWidth() / 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED, + tr("Date Added"), + defaultColumnWidth() * 3); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_DURATION, + tr("Duration"), + defaultColumnWidth()); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE, + tr("Type"), + defaultColumnWidth()); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_GENRE, + tr("Genre"), + defaultColumnWidth() * 4); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_GROUPING, + tr("Grouping"), + defaultColumnWidth() * 4); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_KEY, + tr("Key"), + defaultColumnWidth()); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION, + tr("Location"), + defaultColumnWidth() * 6); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW, + tr("Preview"), + defaultColumnWidth() / 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_RATING, + tr("Rating"), + defaultColumnWidth() * 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN, + tr("ReplayGain"), + defaultColumnWidth() * 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_SAMPLERATE, + tr("Samplerate"), + defaultColumnWidth()); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED, + tr("Played"), + defaultColumnWidth() * 2); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_TITLE, + tr("Title"), + defaultColumnWidth() * 4); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER, + tr("Track #"), + defaultColumnWidth()); + setHeaderProperties( + ColumnCache::COLUMN_LIBRARYTABLE_YEAR, + tr("Year"), + defaultColumnWidth()); +} + +void BaseTrackTableModel::setHeaderProperties( + ColumnCache::Column column, + QString title, + int defaultWidth) { + int section = fieldIndex(column); + if (section < 0) { + kLogger.debug() + << "Skipping header properties for unsupported column" + << column + << title; + return; + } + if (section >= m_columnHeaders.size()) { + m_columnHeaders.resize(section + 1); + } + m_columnHeaders[section].column = column; + setHeaderData( + section, + Qt::Horizontal, + m_columnCache.columnName(column), + TrackModel::kHeaderNameRole); + setHeaderData( + section, + Qt::Horizontal, + title, + Qt::DisplayRole); + setHeaderData( + section, + Qt::Horizontal, + defaultWidth, + TrackModel::kHeaderWidthRole); +} + +bool BaseTrackTableModel::setHeaderData( + int section, + Qt::Orientation orientation, + const QVariant& value, + int role) { + VERIFY_OR_DEBUG_ASSERT(section >= 0) { + return false; + } + VERIFY_OR_DEBUG_ASSERT(section < m_columnHeaders.size()) { + return false; + } + if (orientation != Qt::Horizontal) { + // We only care about horizontal headers. + return false; + } + m_columnHeaders[section].header[role] = value; + emit headerDataChanged(orientation, section, section); + return true; +} + +QVariant BaseTrackTableModel::headerData( + int section, + Qt::Orientation orientation, + int role) const { + if (orientation == Qt::Horizontal) { + switch (role) { + case Qt::DisplayRole: { + QVariant headerValue = + m_columnHeaders.value(section).header.value(role); + if (!headerValue.isValid()) { + // Try EditRole if DisplayRole wasn't present + headerValue = m_columnHeaders.value(section).header.value(Qt::EditRole); + } + if (headerValue.isValid()) { + return headerValue; + } else { + return QVariant(section).toString(); + } + } + case TrackModel::kHeaderWidthRole: { + QVariant widthValue = m_columnHeaders.value(section).header.value(role); + if (widthValue.isValid()) { + return widthValue; + } else { + return defaultColumnWidth(); + } + } + case TrackModel::kHeaderNameRole: { + return m_columnHeaders.value(section).header.value(role); + } + case Qt::ToolTipRole: { + QVariant tooltip = m_columnHeaders.value(section).header.value(role); + if (tooltip.isValid()) { + return tooltip; + } + break; + } + default: + break; + } + } + return QAbstractTableModel::headerData(section, orientation, role); +} + +int BaseTrackTableModel::countValidColumnHeaders() const { + int count = 0; + for (const auto& columnHeader : m_columnHeaders) { + if (columnHeader.column != + ColumnCache::COLUMN_LIBRARYTABLE_INVALID) { + ++count; + } + } + return count; +} + +int BaseTrackTableModel::columnCount(const QModelIndex& parent) const { + VERIFY_OR_DEBUG_ASSERT(!parent.isValid()) { + return 0; + } + return countValidColumnHeaders(); } -void BaseTrackTableModel::emitDataChangedForMultipleRowsSingleColumn( +bool BaseTrackTableModel::isColumnHiddenByDefault( + int column) { + return column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_CHANNELS) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COMPOSER) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_SAMPLERATE) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR); +} + +QAbstractItemDelegate* BaseTrackTableModel::delegateForColumn( + const int index, QObject* pParent) { + auto* pTableView = qobject_cast(pParent); + VERIFY_OR_DEBUG_ASSERT(pTableView) { + return nullptr; + } + if (index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { + return new StarDelegate(pTableView); + } else if (index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { + return new BPMDelegate(pTableView); + } else if (PlayerManager::numPreviewDecks() > 0 && + index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) { + return new PreviewButtonDelegate(pTableView, index); + } else if (index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION)) { + return new LocationDelegate(pTableView); + } else if (index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)) { + return new ColorDelegate(pTableView); + } else if (index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART)) { + auto* pCoverArtDelegate = + doCreateCoverArtDelegate(pTableView); + // WLibraryTableView -> BaseCoverArtDelegate + connect(pTableView, + &WLibraryTableView::onlyCachedCoverArt, + pCoverArtDelegate, + &BaseCoverArtDelegate::slotInhibitLazyLoading); + // BaseCoverArtDelegate -> BaseTrackTableModel + connect(pCoverArtDelegate, + &BaseCoverArtDelegate::rowsChanged, + this, + &BaseTrackTableModel::slotRefreshCoverRows); + return pCoverArtDelegate; + } + return nullptr; +} + +QVariant BaseTrackTableModel::data( + const QModelIndex& index, + int role) const { + if (!index.isValid()) { + return QVariant(); + } + + if (role == Qt::BackgroundRole) { + QModelIndex colorIndex = index.sibling( + index.row(), + fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)); + QColor color = mixxx::RgbColor::toQColor( + mixxx::RgbColor::fromQVariant(rawValue(colorIndex))); + if (color.isValid()) { + color.setAlpha(kTrackColorRowBackgroundOpacity); + } + return color; + } + + // Only retrieve a value for supported roles + if (role != Qt::DisplayRole && + role != Qt::EditRole && + role != Qt::CheckStateRole && + role != Qt::ToolTipRole) { + return QVariant(); + } + + return roleValue(index, rawValue(index), role); +} + +bool BaseTrackTableModel::setData( + const QModelIndex& index, + const QVariant& value, + int role) { + const int column = index.column(); + + // Override sets to TIMESPLAYED and redirect them to PLAYED + if (role == Qt::CheckStateRole) { + const auto val = value.toInt() > 0; + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { + QModelIndex playedIndex = index.sibling(index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)); + return setData(playedIndex, val, Qt::EditRole); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { + QModelIndex bpmLockindex = index.sibling(index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)); + return setData(bpmLockindex, val, Qt::EditRole); + } + return false; + } + + TrackPointer pTrack = getTrack(index); + if (!pTrack) { + return false; + } + + // Do not save the track here. Changing the track dirties it and the caching + // system will automatically save the track once it is unloaded from + // memory. rryan 10/2010 + return setTrackValueForColumn(pTrack, column, value, role); +} + +QVariant BaseTrackTableModel::roleValue( + const QModelIndex& index, + QVariant&& rawValue, + int role) const { + const int column = index.column(); + // Format the value based on whether we are in a tooltip, + // display, or edit role + switch (role) { + case Qt::ToolTipRole: + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)) { + return mixxx::RgbColor::toQString(mixxx::RgbColor::fromQVariant(rawValue)); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) { + return kEmptyString; + } + M_FALLTHROUGH_INTENDED; + case Qt::DisplayRole: + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION)) { + bool ok; + const auto duration = rawValue.toDouble(&ok); + if (ok && duration >= 0) { + return mixxx::Duration::formatTime( + duration, + mixxx::Duration::Precision::SECONDS); + } else { + return QVariant(); + } + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { + VERIFY_OR_DEBUG_ASSERT(rawValue.canConvert(QMetaType::Int)) { + return QVariant(); + } + return QVariant::fromValue(StarRating(rawValue.toInt())); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)) { + return rawValue.toBool(); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { + VERIFY_OR_DEBUG_ASSERT(rawValue.canConvert(QMetaType::Int)) { + return QVariant(); + } + return QString("(%1)").arg(rawValue.toInt()); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED)) { + QDateTime gmtDate = rawValue.toDateTime(); + gmtDate.setTimeSpec(Qt::UTC); + return gmtDate.toLocalTime(); + } else if (column == fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_DATETIMEADDED)) { + QDateTime gmtDate = rawValue.toDateTime(); + gmtDate.setTimeSpec(Qt::UTC); + return gmtDate.toLocalTime(); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { + bool ok; + const auto bpmValue = rawValue.toDouble(&ok); + if (ok && bpmValue > 0.0) { + return QString("%1").arg(bpmValue, 0, 'f', 1); + } else { + return QChar('-'); + } + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) { + return rawValue.toBool(); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR)) { + return mixxx::TrackMetadata::formatCalendarYear(rawValue.toString()); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER)) { + const auto trackNumber = rawValue.toInt(0); + if (trackNumber > 0) { + return rawValue; + } else { + // clear invalid values + return QVariant(); + } + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE)) { + int bitrateValue = rawValue.toInt(0); + if (bitrateValue > 0) { + return rawValue; + } else { + // clear invalid values + return QVariant(); + } + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY)) { + // If we know the semantic key via the LIBRARYTABLE_KEY_ID + // column (as opposed to the string representation of the key + // currently stored in the DB) then lookup the key and render it + // using the user's selected notation. + int keyIdColumn = fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_KEY_ID); + if (keyIdColumn != -1) { + mixxx::track::io::key::ChromaticKey key = + KeyUtils::keyFromNumericValue( + index.sibling(index.row(), keyIdColumn).data().toInt()); + if (key != mixxx::track::io::key::INVALID) { + // Render this key with the user-provided notation. + return KeyUtils::keyToString(key); + } + } + // clear invalid values + return QVariant(); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN)) { + bool ok; + const auto gainValue = rawValue.toDouble(&ok); + return ok ? mixxx::ReplayGain::ratioToString(gainValue) : QString(); + } + // Otherwise, just use the column value + break; + case Qt::EditRole: + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { + bool ok; + const auto bpmValue = rawValue.toDouble(&ok); + return ok ? bpmValue : 0.0; + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { + return index.sibling( + index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)) + .data() + .toBool(); + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { + VERIFY_OR_DEBUG_ASSERT(rawValue.canConvert(QMetaType::Int)) { + return QVariant(); + } + return QVariant::fromValue(StarRating(rawValue.toInt())); + } + // Otherwise, just use the column value + break; + case Qt::CheckStateRole: + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { + bool played = index.sibling( + index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED)) + .data() + .toBool(); + return played ? Qt::Checked : Qt::Unchecked; + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { + bool locked = index.sibling( + index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) + .data() + .toBool(); + return locked ? Qt::Checked : Qt::Unchecked; + } + // No check state supported + return QVariant(); + default: + DEBUG_ASSERT(!"unexpected role"); + break; + } + return rawValue; + +} + +bool BaseTrackTableModel::isBpmLocked( + const QModelIndex& index) const { + const auto bpmLockIndex = + index.sibling( + index.row(), + fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)); + return bpmLockIndex.data().toBool(); +} + +Qt::ItemFlags BaseTrackTableModel::defaultItemFlags( + const QModelIndex& index) const { + if (index.isValid()) { + return QAbstractItemModel::flags(index) | + // Enable dragging songs from this data model to elsewhere + // like the waveform widget to load a track into a Player + Qt::ItemIsDragEnabled; + } else { + return Qt::ItemIsEnabled; + } +} + +Qt::ItemFlags BaseTrackTableModel::readOnlyFlags( + const QModelIndex& index) const { + return defaultItemFlags(index); +} + +Qt::ItemFlags BaseTrackTableModel::readWriteFlags( + const QModelIndex& index) const { + if (!index.isValid()) { + return defaultItemFlags(index); + } + + const int column = index.column(); + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_CHANNELS) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_FILETYPE) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_NATIVELOCATION) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_SAMPLERATE)) { + return readOnlyFlags(index); + } + + Qt::ItemFlags itemFlags = defaultItemFlags(index); + if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) { + // Checkable cells + itemFlags |= Qt::ItemIsUserCheckable; + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { + // Always allow checking of the BPM-locked indicator + itemFlags |= Qt::ItemIsUserCheckable; + // Allow editing of BPM only if not locked + if (!isBpmLocked(index)) { + itemFlags |= Qt::ItemIsEditable; + } + } else { + // Cells are editable by default + itemFlags |= Qt::ItemIsEditable; + } + return itemFlags; +} + +Qt::ItemFlags BaseTrackTableModel::flags( + const QModelIndex& index) const { + return readWriteFlags(index); +} + +QList BaseTrackTableModel::collectUrls( + const QModelIndexList& indexes) const { + QList urls; + urls.reserve(indexes.size()); + // The list of indexes we're given contains separates indexes for each + // column, so even if only one row is selected, we'll have columnCount() + // indices. We need to only count each row once: + QSet visitedRows; + for (const auto& index : indexes) { + if (visitedRows.contains(index.row())) { + continue; + } + visitedRows.insert(index.row()); + QUrl url = TrackFile(getTrackLocation(index)).toUrl(); + if (url.isValid()) { + urls.append(url); + } + } + return urls; +} + +QMimeData* BaseTrackTableModel::mimeData( + const QModelIndexList& indexes) const { + const auto urls = collectUrls(indexes); + if (urls.isEmpty()) { + return nullptr; + } else { + QMimeData* mimeData = new QMimeData(); + mimeData->setUrls(urls); + return mimeData; + } +} + +void BaseTrackTableModel::slotTrackLoaded( + QString group, + TrackPointer pTrack) { + if (group == m_previewDeckGroup) { + // If there was a previously loaded track, refresh its rows so the + // preview state will update. + if (m_previewDeckTrackId.isValid()) { + const int numColumns = columnCount(); + QLinkedList rows = getTrackRows(m_previewDeckTrackId); + m_previewDeckTrackId = TrackId(); // invalidate + foreach (int row, rows) { + QModelIndex topLeft = index(row, 0); + QModelIndex bottomRight = index(row, numColumns); + emit dataChanged(topLeft, bottomRight); + } + } + m_previewDeckTrackId = doGetTrackId(pTrack); + } +} + +void BaseTrackTableModel::slotRefreshCoverRows( + QList rows) { + if (rows.isEmpty()) { + return; + } + const int column = fieldIndex(LIBRARYTABLE_COVERART); + VERIFY_OR_DEBUG_ASSERT(column >= 0) { + return; + } + emitDataChangedForMultipleRowsInColumn(rows, column); +} + +void BaseTrackTableModel::slotRefreshAllRows() { + select(); +} + +void BaseTrackTableModel::emitDataChangedForMultipleRowsInColumn( const QList& rows, int column, const QVector& roles) { @@ -62,3 +781,8 @@ void BaseTrackTableModel::emitDataChangedForMultipleRowsSingleColumn( emit dataChanged(topLeft, bottomRight, roles); } } + +TrackPointer BaseTrackTableModel::getTrackByRef( + const TrackRef& trackRef) const { + return m_pTrackCollectionManager->internalCollection()->getTrackByRef(trackRef); +} diff --git a/src/library/basetracktablemodel.h b/src/library/basetracktablemodel.h index 78936b87f038..16aeff967fb6 100644 --- a/src/library/basetracktablemodel.h +++ b/src/library/basetracktablemodel.h @@ -2,26 +2,213 @@ #include #include +#include +#include +#include "library/columncache.h" #include "library/trackmodel.h" +class BaseCoverArtDelegate; +class TrackCollectionManager; + class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { Q_OBJECT DISALLOW_COPY_AND_ASSIGN(BaseTrackTableModel); public: explicit BaseTrackTableModel( - QSqlDatabase db, const char* settingsNamespace, + TrackCollectionManager* const pTrackCollectionManager, QObject* parent = nullptr); ~BaseTrackTableModel() override = default; + /////////////////////////////////////////////////////// + // Overridable functions + /////////////////////////////////////////////////////// + + virtual int fieldIndex( + ColumnCache::Column column) const { + return m_columnCache.fieldIndex(column); + } + + /////////////////////////////////////////////////////// + // Inherited from QAbstractItemModel + /////////////////////////////////////////////////////// + + QVariant headerData( + int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const final; + bool setHeaderData( + int section, + Qt::Orientation orientation, + const QVariant& value, + int role = Qt::DisplayRole) final; + + QMimeData* mimeData( + const QModelIndexList& indexes) const final; + + QVariant data( + const QModelIndex& index, + int role = Qt::DisplayRole) const final; + bool setData( + const QModelIndex& index, + const QVariant& value, + int role = Qt::EditRole) final; + + // Calculate the number of columns from all valid + // column headers. + // Reimplement in derived classes if a more efficient + // implementation is available. + int columnCount( + const QModelIndex& parent = QModelIndex()) const override; + + // Calls readWriteFlags() by default + // Reimplement in derived classes if the table model + // should be readOnly + Qt::ItemFlags flags( + const QModelIndex& index) const override; + + /////////////////////////////////////////////////////// + // Inherited from TrackModel + /////////////////////////////////////////////////////// + + QAbstractItemDelegate* delegateForColumn( + const int column, + QObject* pParent) final; + + int fieldIndex( + const QString& fieldName) const override { + return m_columnCache.fieldIndex(fieldName); + } + + bool isColumnHiddenByDefault( + int column) override; + + TrackPointer getTrackByRef( + const TrackRef& trackRef) const override; + protected: + static constexpr int defaultColumnWidth() { + return 50; + } + static QStringList defaultTableColumns(); + + // Build a map from the column names to their indices + // used by fieldIndex(). This function has to be called + void initTableColumnsAndHeaderProperties( + const QStringList& tableColumns = defaultTableColumns()); + + QString columnNameForFieldIndex(int index) const { + return m_columnCache.columnNameForFieldIndex(index); + } + + // A simple helper function for initializing header title and width. + // Note that the ideal width of a column is based on the width of + // its data, not the title string itself. + void setHeaderProperties( + ColumnCache::Column column, + QString title, + int defaultWidth = 0); + + ColumnCache::Column mapColumn(int column) const { + if (column >= 0 && column < m_columnHeaders.size()) { + return m_columnHeaders[column].column; + } else { + return ColumnCache::COLUMN_LIBRARYTABLE_INVALID; + } + } + // Emit the dataChanged() signal for multiple rows in // a single column. The list of rows must be sorted in // ascending order without duplicates! - void emitDataChangedForMultipleRowsSingleColumn( + void emitDataChangedForMultipleRowsInColumn( const QList& rows, int column, const QVector& roles = QVector()); + + const TrackId previewDeckTrackId() const { + return m_previewDeckTrackId; + } + + bool isBpmLocked( + const QModelIndex& index) const; + + const QPointer m_pTrackCollectionManager; + + /////////////////////////////////////////////////////// + // Overridable functions + /////////////////////////////////////////////////////// + + virtual void initHeaderProperties(); + + // Use this if you want a model that is read-only. + virtual Qt::ItemFlags readOnlyFlags( + const QModelIndex& index) const; + // Use this if you want a model that can be changed + virtual Qt::ItemFlags readWriteFlags( + const QModelIndex& index) const; + + virtual QVariant rawValue( + const QModelIndex& index) const = 0; + + // Reimplement in derived classes to handle columns other + // then COLUMN_LIBRARYTABLE + virtual QVariant roleValue( + const QModelIndex& index, + QVariant&& rawValue, + int role) const; + + virtual bool setTrackValueForColumn( + const TrackPointer& pTrack, + int column, + const QVariant& value, + int role) { + Q_UNUSED(pTrack); + Q_UNUSED(column); + Q_UNUSED(value); + Q_UNUSED(role); + return false; + } + + private slots: + void slotTrackLoaded( + QString group, + TrackPointer pTrack); + + void slotRefreshCoverRows( + QList rows); + + void slotRefreshAllRows(); + + private: + // Track models may reference tracks by an external id + // TODO: TrackId should only be used for tracks from + // the internal database. + virtual TrackId doGetTrackId( + const TrackPointer& pTrack) const { + return pTrack ? pTrack->getId() : TrackId(); + } + virtual BaseCoverArtDelegate* doCreateCoverArtDelegate( + QTableView* pTableView) const = 0; + + Qt::ItemFlags defaultItemFlags( + const QModelIndex& index) const; + + QList collectUrls( + const QModelIndexList& indexes) const; + + const QString m_previewDeckGroup; + + ColumnCache m_columnCache; + + struct ColumnHeader { + ColumnCache::Column column = ColumnCache::COLUMN_LIBRARYTABLE_INVALID; + QHash header; + }; + QVector m_columnHeaders; + + int countValidColumnHeaders() const; + + TrackId m_previewDeckTrackId; }; diff --git a/src/library/coverartdelegate.cpp b/src/library/coverartdelegate.cpp index eb807f45b498..a82f490e8948 100644 --- a/src/library/coverartdelegate.cpp +++ b/src/library/coverartdelegate.cpp @@ -3,10 +3,9 @@ #include "library/dao/trackschema.h" #include "library/trackmodel.h" #include "util/assert.h" -#include "widget/wlibrarytableview.h" CoverArtDelegate::CoverArtDelegate( - WLibraryTableView* parent) + QTableView* parent) : BaseCoverArtDelegate(parent), m_iCoverSourceColumn(m_pTrackModel->fieldIndex( LIBRARYTABLE_COVERART_SOURCE)), diff --git a/src/library/coverartdelegate.h b/src/library/coverartdelegate.h index e92e9c3f6d0b..dd4e31ed0ea1 100644 --- a/src/library/coverartdelegate.h +++ b/src/library/coverartdelegate.h @@ -2,14 +2,12 @@ #include "library/basecoverartdelegate.h" -class WLibraryTableView; - class CoverArtDelegate : public BaseCoverArtDelegate { Q_OBJECT public: explicit CoverArtDelegate( - WLibraryTableView* parent); + QTableView* parent); ~CoverArtDelegate() final = default; private: diff --git a/src/library/dao/autodjcratesdao.cpp b/src/library/dao/autodjcratesdao.cpp index c56f9051c88e..62d5a86d4048 100644 --- a/src/library/dao/autodjcratesdao.cpp +++ b/src/library/dao/autodjcratesdao.cpp @@ -198,8 +198,8 @@ void AutoDJCratesDAO::createAndConnectAutoDjCratesDatabase() { // Be notified when a track is modified. // We only care when the number of times it's been played changes. - connect(&m_pTrackCollection->getTrackDAO(), - &TrackDAO::trackDirty, + connect(m_pTrackCollection, + &TrackCollection::trackDirty, this, &AutoDJCratesDAO::slotTrackDirty); diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 985e7609bd0f..a8c8b2ec5736 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -297,7 +297,7 @@ void TrackDAO::databaseTrackAdded(TrackPointer pTrack) { void TrackDAO::databaseTracksChanged(QSet changedTracks) { // results in a call of BaseTrackCache::updateTracksInIndex(trackIds); if (!changedTracks.isEmpty()) { - emit tracksAdded(changedTracks); + emit tracksChanged(changedTracks); } } diff --git a/src/library/dao/trackdao.h b/src/library/dao/trackdao.h index 3948a1730d46..a596b38a23b7 100644 --- a/src/library/dao/trackdao.h +++ b/src/library/dao/trackdao.h @@ -76,6 +76,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC void trackDirty(TrackId trackId) const; void trackClean(TrackId trackId) const; void trackChanged(TrackId trackId); + void tracksChanged(QSet trackIds); void tracksAdded(QSet trackIds); void tracksRemoved(QSet trackIds); void dbTrackAdded(TrackPointer pTrack); diff --git a/src/library/tableitemdelegate.cpp b/src/library/tableitemdelegate.cpp index 617b805d0180..0176555ebf40 100644 --- a/src/library/tableitemdelegate.cpp +++ b/src/library/tableitemdelegate.cpp @@ -9,6 +9,7 @@ TableItemDelegate::TableItemDelegate(QTableView* pTableView) : QStyledItemDelegate(pTableView), m_pTableView(pTableView) { + DEBUG_ASSERT(m_pTableView); } void TableItemDelegate::paint( @@ -16,19 +17,15 @@ void TableItemDelegate::paint( const QStyleOptionViewItem& option, const QModelIndex& index) const { PainterScope painterScope(painter); - painter->setClipRect(option.rect); // Set the palette appropriately based on whether the row is selected or // not. We also have to check if it is inactive or not and use the // appropriate ColorGroup. - QPalette::ColorGroup cg = QPalette::Normal; - if (option.state & QStyle::State_Enabled) { - if (!(option.state & QStyle::State_Active)) { - cg = QPalette::Disabled; - } - } else { - cg = QPalette::Disabled; + QPalette::ColorGroup cg = QPalette::Disabled; + if ((option.state & QStyle::State_Enabled) && + (option.state & QStyle::State_Active)) { + cg = QPalette::Normal; } if (option.state & QStyle::State_Selected) { @@ -37,15 +34,13 @@ void TableItemDelegate::paint( painter->setBrush(option.palette.color(cg, QPalette::Text)); } - if (m_pTableView) { - QStyle* style = m_pTableView->style(); - if (style) { - style->drawControl( - QStyle::CE_ItemViewItem, - &option, - painter, - m_pTableView); - } + QStyle* style = m_pTableView->style(); + if (style) { + style->drawControl( + QStyle::CE_ItemViewItem, + &option, + painter, + m_pTableView); } paintItem(painter, option, index); @@ -61,11 +56,15 @@ void TableItemDelegate::paintItemBackground( const QModelIndex& index) { // If the row is not selected, paint the desired background color before // painting the delegate item - if (!option.showDecorationSelected || !(option.state & QStyle::State_Selected)) { - QVariant bgValue = index.data(Qt::BackgroundRole); - if (bgValue.isValid()) { - DEBUG_ASSERT(bgValue.canConvert()); - painter->fillRect(option.rect, qvariant_cast(bgValue)); - } + if (option.showDecorationSelected && + (option.state & QStyle::State_Selected)) { + return; + } + QVariant bgValue = index.data(Qt::BackgroundRole); + if (!bgValue.isValid()) { + return; } + DEBUG_ASSERT(bgValue.canConvert()); + const auto bgBrush = qvariant_cast(bgValue); + painter->fillRect(option.rect, bgBrush); } diff --git a/src/library/tableitemdelegate.h b/src/library/tableitemdelegate.h index 01f6fd233569..0ac0bdc38770 100644 --- a/src/library/tableitemdelegate.h +++ b/src/library/tableitemdelegate.h @@ -3,22 +3,22 @@ #include #include - class TableItemDelegate : public QStyledItemDelegate { Q_OBJECT public: - explicit TableItemDelegate(QTableView* pTableView); + explicit TableItemDelegate( + QTableView* pTableView); ~TableItemDelegate() override = default; void paint( - QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const override; + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const override; virtual void paintItem( - QPainter *painter, - const QStyleOptionViewItem &option, - const QModelIndex &index) const = 0; + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const = 0; protected: static void paintItemBackground( diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index a6f4f07bc459..2ef5a410e940 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -23,6 +23,44 @@ TrackCollection::TrackCollection( m_analysisDao(pConfig), m_trackDao(m_cueDao, m_playlistDao, m_analysisDao, m_libraryHashDao, pConfig) { + // Forward signals from TrackDAO + connect(&m_trackDao, + &TrackDAO::trackClean, + this, + &TrackCollection::trackClean, + /*signal-to-signal*/ Qt::DirectConnection); + connect(&m_trackDao, + &TrackDAO::trackDirty, + this, + &TrackCollection::trackDirty, + /*signal-to-signal*/ Qt::DirectConnection); + connect(&m_trackDao, + &TrackDAO::trackChanged, + this, + [this](TrackId trackId) { + emit tracksChanged(QSet{trackId}); + }, + /*signal-to-signal*/ Qt::DirectConnection); + connect(&m_trackDao, + &TrackDAO::tracksAdded, + this, + &TrackCollection::tracksAdded, + /*signal-to-signal*/ Qt::DirectConnection); + connect(&m_trackDao, + &TrackDAO::tracksChanged, + this, + &TrackCollection::tracksChanged, + /*signal-to-signal*/ Qt::DirectConnection); + connect(&m_trackDao, + &TrackDAO::tracksRemoved, + this, + &TrackCollection::tracksRemoved, + /*signal-to-signal*/ Qt::DirectConnection); + connect(&m_trackDao, + &TrackDAO::forceModelUpdate, + this, + &TrackCollection::multipleTracksChanged, + /*signal-to-signal*/ Qt::DirectConnection); } TrackCollection::~TrackCollection() { @@ -87,7 +125,11 @@ void TrackCollection::connectTrackSource(QSharedPointer pTrackSo connect(&m_trackDao, &TrackDAO::tracksAdded, m_pTrackSource.data(), - &BaseTrackCache::slotTracksAdded); + &BaseTrackCache::slotTracksAddedOrChanged); + connect(&m_trackDao, + &TrackDAO::tracksChanged, + m_pTrackSource.data(), + &BaseTrackCache::slotTracksAddedOrChanged); connect(&m_trackDao, &TrackDAO::tracksRemoved, m_pTrackSource.data(), diff --git a/src/library/trackcollection.h b/src/library/trackcollection.h index 870e35edb2ec..2be31a0bbae7 100644 --- a/src/library/trackcollection.h +++ b/src/library/trackcollection.h @@ -99,6 +99,14 @@ class TrackCollection : public QObject, bool unremove); signals: + // Forwarded signals from TrackDAO + void trackClean(TrackId trackId); + void trackDirty(TrackId trackId); + void tracksAdded(QSet trackIds); + void tracksChanged(QSet trackIds); + void tracksRemoved(QSet trackIds); + void multipleTracksChanged(); + void crateInserted(CrateId id); void crateUpdated(CrateId id); void crateDeleted(CrateId id); From c314fce55c702a0da4d5b4fb0a1bace19e9581ed Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 14 Mar 2020 15:09:36 +0100 Subject: [PATCH 007/393] Add missing index validation --- src/library/basetracktablemodel.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index e09e4d1be0f2..d736c13e842d 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -400,6 +400,9 @@ QVariant BaseTrackTableModel::data( QModelIndex colorIndex = index.sibling( index.row(), fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COLOR)); + if (!colorIndex.isValid()) { + return QVariant(); + } QColor color = mixxx::RgbColor::toQColor( mixxx::RgbColor::fromQVariant(rawValue(colorIndex))); if (color.isValid()) { From 68af024004a0a146aa0da88ca4d635ac5fd5f6f6 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 14 Mar 2020 15:43:38 +0100 Subject: [PATCH 008/393] Fix Clang warning to use std::move --- src/library/basetracktablemodel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index d736c13e842d..5ee40d6bfe14 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -513,7 +513,7 @@ QVariant BaseTrackTableModel::roleValue( } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER)) { const auto trackNumber = rawValue.toInt(0); if (trackNumber > 0) { - return rawValue; + return std::move(rawValue); } else { // clear invalid values return QVariant(); @@ -521,7 +521,7 @@ QVariant BaseTrackTableModel::roleValue( } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE)) { int bitrateValue = rawValue.toInt(0); if (bitrateValue > 0) { - return rawValue; + return std::move(rawValue); } else { // clear invalid values return QVariant(); @@ -588,7 +588,7 @@ QVariant BaseTrackTableModel::roleValue( DEBUG_ASSERT(!"unexpected role"); break; } - return rawValue; + return std::move(rawValue); } From 9ada84a329dc5c772dda2c897908e0694fac2b44 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 14 Mar 2020 17:44:25 +0100 Subject: [PATCH 009/393] Return QBrush instead of QColor for Qt::BackgroundRole --- src/library/basetracktablemodel.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 5ee40d6bfe14..ee92114ae146 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -403,12 +403,16 @@ QVariant BaseTrackTableModel::data( if (!colorIndex.isValid()) { return QVariant(); } - QColor color = mixxx::RgbColor::toQColor( - mixxx::RgbColor::fromQVariant(rawValue(colorIndex))); - if (color.isValid()) { - color.setAlpha(kTrackColorRowBackgroundOpacity); + const auto trackColor = + mixxx::RgbColor::fromQVariant( + rawValue(colorIndex)); + if (!trackColor) { + return QVariant(); } - return color; + auto bgColor = mixxx::RgbColor::toQColor(trackColor); + DEBUG_ASSERT(bgColor.isValid()); + bgColor.setAlpha(kTrackColorRowBackgroundOpacity); + return QBrush(bgColor); } // Only retrieve a value for supported roles From 66585a6cdabbd7a6775e289a19b4bb142dded3a1 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 18 Mar 2020 23:17:00 +0100 Subject: [PATCH 010/393] controllers/controllermanager: Make use of the getPresetPaths() function --- src/controllers/controllermanager.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index ec74976f7f5d..51ded8c0669c 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -119,11 +119,8 @@ void ControllerManager::slotInitialize() { // Initialize preset info parsers. This object is only for use in the main // thread. Do not touch it from within ControllerManager. - QStringList presetSearchPaths; - presetSearchPaths << userPresetsPath(m_pConfig) - << resourcePresetsPath(m_pConfig); m_pMainThreadPresetEnumerator = QSharedPointer( - new PresetInfoEnumerator(presetSearchPaths)); + new PresetInfoEnumerator(getPresetPaths(m_pConfig))); // Instantiate all enumerators. Enumerators can take a long time to // construct since they interact with host MIDI APIs. From 05f7ce4998ec0db221cd7bddab8ac72760abcc78 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 18 Mar 2020 23:20:39 +0100 Subject: [PATCH 011/393] controllers/controllermanager: Rename function to sanitizeString --- src/controllers/controllermanager.cpp | 17 ++++++++--------- src/controllers/controllermanager.h | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 51ded8c0669c..b9fa43735232 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -218,7 +218,7 @@ void ControllerManager::slotSetUpDevices() { } // The filename for this device name. - QString presetBaseName = presetFilenameFromName(name); + QString presetBaseName = sanitizeString(name); // The first unique filename for this device (appends numbers at the end // if we have already seen a controller by this name on this run of @@ -347,8 +347,8 @@ void ControllerManager::openController(Controller* pController) { pController->applyPreset(getPresetPaths(m_pConfig), true); // Update configuration to reflect controller is enabled. - m_pConfig->setValue(ConfigKey( - "[Controller]", presetFilenameFromName(pController->getName())), 1); + m_pConfig->setValue( + ConfigKey("[Controller]", sanitizeString(pController->getName())), 1); } } @@ -359,8 +359,8 @@ void ControllerManager::closeController(Controller* pController) { pController->close(); maybeStartOrStopPolling(); // Update configuration to reflect controller is disabled. - m_pConfig->setValue(ConfigKey( - "[Controller]", presetFilenameFromName(pController->getName())), 0); + m_pConfig->setValue( + ConfigKey("[Controller]", sanitizeString(pController->getName())), 0); } bool ControllerManager::loadPreset(Controller* pController, @@ -372,9 +372,8 @@ bool ControllerManager::loadPreset(Controller* pController, // Save the file path/name in the config so it can be auto-loaded at // startup next time m_pConfig->set( - ConfigKey("[ControllerPreset]", - presetFilenameFromName(pController->getName())), - preset->filePath()); + ConfigKey("[ControllerPreset]", sanitizeString(pController->getName())), + preset->filePath()); return true; } @@ -391,7 +390,7 @@ void ControllerManager::slotSavePresets(bool onlyActive) { } QString name = pController->getName(); QString filename = firstAvailableFilename( - filenames, presetFilenameFromName(name)); + filenames, sanitizeString(name)); QString presetPath = userPresetsPath(m_pConfig) + filename + pController->presetExtension(); if (!pController->savePreset(presetPath)) { diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index 1fa873ba4bc0..0f2c45d1265d 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -85,7 +85,7 @@ class ControllerManager : public QObject { void stopPolling(); void maybeStartOrStopPolling(); - static QString presetFilenameFromName(QString name) { + static QString sanitizeString(QString name) { return name.replace(" ", "_").replace("/", "_").replace("\\", "_"); } From d7269cf75641da4aac415350a3591578114c8484 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 18 Mar 2020 23:24:50 +0100 Subject: [PATCH 012/393] controllers/controllermanager: Rework device setup code --- src/controllers/controllermanager.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index b9fa43735232..9a997652866f 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -218,24 +218,24 @@ void ControllerManager::slotSetUpDevices() { } // The filename for this device name. - QString presetBaseName = sanitizeString(name); + QString deviceName = sanitizeString(name); - // The first unique filename for this device (appends numbers at the end - // if we have already seen a controller by this name on this run of - // Mixxx. - presetBaseName = firstAvailableFilename(filenames, presetBaseName); - - ControllerPresetPointer pPreset = - ControllerPresetFileHandler::loadPreset( - presetBaseName + pController->presetExtension(), - getPresetPaths(m_pConfig)); + if (m_pConfig->getValueString(ConfigKey("[Controller]", deviceName)) != "1") { + continue; + } - if (!loadPreset(pController, pPreset)) { - // TODO(XXX) : auto load midi preset here. + QString presetFile = m_pConfig->getValueString( + ConfigKey("[ControllerPreset]", deviceName)); + if (presetFile.isEmpty()) { continue; } - if (m_pConfig->getValueString(ConfigKey("[Controller]", presetBaseName)) != "1") { + ControllerPresetPointer pPreset = ControllerPresetFileHandler::loadPreset( + presetFile, + getPresetPaths(m_pConfig)); + + if (!loadPreset(pController, pPreset)) { + // TODO(XXX) : auto load midi preset here. continue; } From 88132126a85c433e031420857fc643556454af47 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 09:23:34 +0100 Subject: [PATCH 013/393] controllers: Preselect configured mapping in preferences if available --- src/controllers/controllermanager.cpp | 8 +++++--- src/controllers/controllermanager.h | 1 + src/controllers/dlgprefcontroller.cpp | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 9a997652866f..8b89af3e5a3a 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -202,6 +202,10 @@ QList ControllerManager::getControllerList(bool bOutputDevices, boo return filteredDeviceList; } +QString ControllerManager::getConfiguredPresetFileForDevice(QString name) { + return m_pConfig->getValueString(ConfigKey("[ControllerPreset]", sanitizeString(name))); +} + void ControllerManager::slotSetUpDevices() { qDebug() << "ControllerManager: Setting up devices"; @@ -219,13 +223,11 @@ void ControllerManager::slotSetUpDevices() { // The filename for this device name. QString deviceName = sanitizeString(name); - if (m_pConfig->getValueString(ConfigKey("[Controller]", deviceName)) != "1") { continue; } - QString presetFile = m_pConfig->getValueString( - ConfigKey("[ControllerPreset]", deviceName)); + QString presetFile = getConfiguredPresetFileForDevice(deviceName); if (presetFile.isEmpty()) { continue; } diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index 0f2c45d1265d..03275338946d 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -36,6 +36,7 @@ class ControllerManager : public QObject { QSharedPointer getMainThreadPresetEnumerator() { return m_pMainThreadPresetEnumerator; } + QString getConfiguredPresetFileForDevice(QString name); // Prevent other parts of Mixxx from having to manually connect to our slots void setUpDevices() { emit requestSetUpDevices(); }; diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 6217a12f13a4..729e041447e9 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -261,12 +261,18 @@ void DlgPrefController::enumeratePresets() { } } - // Jump to matching device in list if it was found. - if (match.isValid()) { - int index = m_ui.comboBoxPreset->findText(match.getName()); - if (index != -1) { - m_ui.comboBoxPreset->setCurrentIndex(index); - } + QString configuredPresetFile = m_pControllerManager->getConfiguredPresetFileForDevice( + m_pController->getName()); + + // Preselect configured or matching preset + int index = -1; + if (!configuredPresetFile.isEmpty()) { + index = m_ui.comboBoxPreset->findData(configuredPresetFile); + } else if (match.isValid()) { + index = m_ui.comboBoxPreset->findText(match.getName()); + } + if (index != -1) { + m_ui.comboBoxPreset->setCurrentIndex(index); } } From 29b1df889991d63772c9a5c4d8627d66cd05bbdd Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 15:00:50 +0100 Subject: [PATCH 014/393] controllers/dlgprefcontroller: Fix reset of combobox item on apply --- src/controllers/dlgprefcontroller.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 729e041447e9..1ea7ad97ba01 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -340,9 +340,6 @@ void DlgPrefController::slotApply() { // the same preset. emit loadPreset(m_pController, m_pPreset); - //Select the "..." item again in the combobox. - m_ui.comboBoxPreset->setCurrentIndex(0); - bool wantEnabled = m_ui.chkEnabledDevice->isChecked(); bool enabled = m_pController->isOpen(); if (wantEnabled && !enabled) { From 2e314f547ded3074db6be443bed35c5ade585cfb Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 13:50:45 +0100 Subject: [PATCH 015/393] controllers/controllerpresetinfo: Remove unused header file --- src/controllers/controllerpresetinfo.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/controllerpresetinfo.cpp b/src/controllers/controllerpresetinfo.cpp index 3e8c1d581f4a..bf0051209af9 100644 --- a/src/controllers/controllerpresetinfo.cpp +++ b/src/controllers/controllerpresetinfo.cpp @@ -10,7 +10,6 @@ */ #include "controllers/controllerpresetinfo.h" -#include "controllers/controllerpresetinfoenumerator.h" #include "controllers/defs_controllers.h" #include "util/xml.h" From bc92e0ddb172f9eca3f4a1327424dddcaac8dd2e Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 13:51:12 +0100 Subject: [PATCH 016/393] controllers/dlgprefcontroller: Separate user and system presets --- src/controllers/controllermanager.cpp | 6 ++-- src/controllers/controllermanager.h | 10 ++++-- src/controllers/dlgprefcontroller.cpp | 50 +++++++++++++++++++-------- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 8b89af3e5a3a..499798a690fa 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -119,8 +119,10 @@ void ControllerManager::slotInitialize() { // Initialize preset info parsers. This object is only for use in the main // thread. Do not touch it from within ControllerManager. - m_pMainThreadPresetEnumerator = QSharedPointer( - new PresetInfoEnumerator(getPresetPaths(m_pConfig))); + m_pMainThreadUserPresetEnumerator = QSharedPointer( + new PresetInfoEnumerator(QStringList{userPresetsPath(m_pConfig)})); + m_pMainThreadSystemPresetEnumerator = QSharedPointer( + new PresetInfoEnumerator(QStringList{resourcePresetsPath(m_pConfig)})); // Instantiate all enumerators. Enumerators can take a long time to // construct since they interact with host MIDI APIs. diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index 03275338946d..e291327dd2e6 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -33,8 +33,11 @@ class ControllerManager : public QObject { QList getControllers() const; QList getControllerList(bool outputDevices=true, bool inputDevices=true); ControllerLearningEventFilter* getControllerLearningEventFilter() const; - QSharedPointer getMainThreadPresetEnumerator() { - return m_pMainThreadPresetEnumerator; + QSharedPointer getMainThreadUserPresetEnumerator() { + return m_pMainThreadUserPresetEnumerator; + } + QSharedPointer getMainThreadSystemPresetEnumerator() { + return m_pMainThreadSystemPresetEnumerator; } QString getConfiguredPresetFileForDevice(QString name); @@ -98,7 +101,8 @@ class ControllerManager : public QObject { QList m_enumerators; QList m_controllers; QThread* m_pThread; - QSharedPointer m_pMainThreadPresetEnumerator; + QSharedPointer m_pMainThreadUserPresetEnumerator; + QSharedPointer m_pMainThreadSystemPresetEnumerator; bool m_skipPoll; }; diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 1ea7ad97ba01..0e35ebd95a62 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -239,25 +239,45 @@ void DlgPrefController::enumeratePresets() { // user has their controller plugged in) m_ui.comboBoxPreset->addItem("..."); - // Ask the controller manager for a list of applicable presets - QSharedPointer pie = - m_pControllerManager->getMainThreadPresetEnumerator(); + QList presets; + PresetInfo match; - // Not ready yet. Should be rare. We will re-enumerate on the next open of - // the preferences. - if (pie.isNull()) { - return; + // Ask the controller manager for a list of applicable user presets + QSharedPointer userPresetEnumerator = + m_pControllerManager->getMainThreadUserPresetEnumerator(); + // Check if enumerator is ready. Should be rare. We will re-enumerate on + // the next open of the preferences. + if (!userPresetEnumerator.isNull()) { + // Making the list of presets in the alphabetical order + QList userPresets = userPresetEnumerator->getPresetsByExtension( + m_pController->presetExtension()); + + for (const PresetInfo& preset : userPresets) { + m_ui.comboBoxPreset->addItem(preset.getName(), preset.getPath()); + if (m_pController->matchPreset(preset)) { + match = preset; + } + } } - // Making the list of presets in the alphabetical order - QList presets = pie->getPresetsByExtension( - m_pController->presetExtension()); + // Insert a separator between user presets (+ dummy item) and system presets + m_ui.comboBoxPreset->insertSeparator(m_ui.comboBoxPreset->count()); - PresetInfo match; - for (const PresetInfo& preset : presets) { - m_ui.comboBoxPreset->addItem(preset.getName(), preset.getPath()); - if (m_pController->matchPreset(preset)) { - match = preset; + // Ask the controller manager for a list of applicable system presets + QSharedPointer systemPresetEnumerator = + m_pControllerManager->getMainThreadSystemPresetEnumerator(); + // Check if enumerator is ready. Should be rare. We will re-enumerate on + // the next open of the preferences. + if (!systemPresetEnumerator.isNull()) { + // Making the list of presets in the alphabetical order + QList systemPresets = systemPresetEnumerator->getPresetsByExtension( + m_pController->presetExtension()); + + for (const PresetInfo& preset : systemPresets) { + m_ui.comboBoxPreset->addItem(preset.getName(), preset.getPath()); + if (m_pController->matchPreset(preset)) { + match = preset; + } } } From 2730b6abc3ca1e4a20c57b55cb6d61ba445ec86f Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 15:49:57 +0100 Subject: [PATCH 017/393] controllers/controllermanager: Always write preset to config on exit --- src/controllers/controllermanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 499798a690fa..2843f5371a81 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -392,15 +392,15 @@ void ControllerManager::slotSavePresets(bool onlyActive) { if (onlyActive && !pController->isOpen()) { continue; } - QString name = pController->getName(); - QString filename = firstAvailableFilename( - filenames, sanitizeString(name)); + QString deviceName = sanitizeString(pController->getName()); + QString filename = firstAvailableFilename(filenames, deviceName); QString presetPath = userPresetsPath(m_pConfig) + filename + pController->presetExtension(); if (!pController->savePreset(presetPath)) { qWarning() << "Failed to write preset for device" - << name << "to" << presetPath; + << deviceName << "to" << presetPath; } + m_pConfig->set(ConfigKey("[ControllerPreset]", deviceName), presetPath); } } From 8b46ef0a008911fbce83951fc51fb72e927e00e2 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 16:53:35 +0100 Subject: [PATCH 018/393] controllers/midi: Make MidiControllerPreset's mappings private --- .../controllerinputmappingtablemodel.cpp | 11 ++--- .../controlleroutputmappingtablemodel.cpp | 11 ++--- src/controllers/midi/midicontroller.cpp | 26 ++++++----- src/controllers/midi/midicontrollerpreset.h | 43 ++++++++++++++++++- .../midi/midicontrollerpresetfilehandler.cpp | 18 ++++---- src/test/midicontrollertest.cpp | 2 +- 6 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/controllers/controllerinputmappingtablemodel.cpp b/src/controllers/controllerinputmappingtablemodel.cpp index 456e0556307e..237cda87745d 100644 --- a/src/controllers/controllerinputmappingtablemodel.cpp +++ b/src/controllers/controllerinputmappingtablemodel.cpp @@ -18,12 +18,13 @@ void ControllerInputMappingTableModel::apply() { if (m_pMidiPreset != NULL) { // Clear existing input mappings and insert all the input mappings in // the table into the preset. - m_pMidiPreset->inputMappings.clear(); + QHash mappings; foreach (const MidiInputMapping& mapping, m_midiInputMappings) { // Use insertMulti because we support multiple inputs mappings for // the same input MidiKey. - m_pMidiPreset->inputMappings.insertMulti(mapping.key.key, mapping); + mappings.insertMulti(mapping.key.key, mapping); } + m_pMidiPreset->setInputMappings(mappings); } } @@ -39,9 +40,9 @@ void ControllerInputMappingTableModel::onPresetLoaded() { setHeaderData(MIDI_COLUMN_ACTION, Qt::Horizontal, tr("Action")); setHeaderData(MIDI_COLUMN_COMMENT, Qt::Horizontal, tr("Comment")); - if (!m_pMidiPreset->inputMappings.isEmpty()) { - beginInsertRows(QModelIndex(), 0, m_pMidiPreset->inputMappings.size() - 1); - m_midiInputMappings = m_pMidiPreset->inputMappings.values(); + if (!m_pMidiPreset->getInputMappings().isEmpty()) { + beginInsertRows(QModelIndex(), 0, m_pMidiPreset->getInputMappings().size() - 1); + m_midiInputMappings = m_pMidiPreset->getInputMappings().values(); endInsertRows(); } } diff --git a/src/controllers/controlleroutputmappingtablemodel.cpp b/src/controllers/controlleroutputmappingtablemodel.cpp index 7dc3756e4146..253aa412d6d9 100644 --- a/src/controllers/controlleroutputmappingtablemodel.cpp +++ b/src/controllers/controlleroutputmappingtablemodel.cpp @@ -18,12 +18,13 @@ void ControllerOutputMappingTableModel::apply() { if (m_pMidiPreset != NULL) { // Clear existing output mappings and insert all the output mappings in // the table into the preset. - m_pMidiPreset->outputMappings.clear(); + QHash mappings; foreach (const MidiOutputMapping& mapping, m_midiOutputMappings) { // Use insertMulti because we support multiple outputs from the same // control. - m_pMidiPreset->outputMappings.insertMulti(mapping.controlKey, mapping); + mappings.insertMulti(mapping.controlKey, mapping); } + m_pMidiPreset->setOutputMappings(mappings); } } @@ -42,9 +43,9 @@ void ControllerOutputMappingTableModel::onPresetLoaded() { setHeaderData(MIDI_COLUMN_MAX, Qt::Horizontal, tr("On Range Max")); setHeaderData(MIDI_COLUMN_COMMENT, Qt::Horizontal, tr("Comment")); - if (!m_pMidiPreset->outputMappings.isEmpty()) { - beginInsertRows(QModelIndex(), 0, m_pMidiPreset->outputMappings.size() - 1); - m_midiOutputMappings = m_pMidiPreset->outputMappings.values(); + if (!m_pMidiPreset->getOutputMappings().isEmpty()) { + beginInsertRows(QModelIndex(), 0, m_pMidiPreset->getOutputMappings().size() - 1); + m_midiOutputMappings = m_pMidiPreset->getOutputMappings().values(); endInsertRows(); } } diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index 877897f00f19..2f613c954ba9 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -75,11 +75,11 @@ bool MidiController::applyPreset(QList scriptPaths, bool initializeScri } void MidiController::createOutputHandlers() { - if (m_preset.outputMappings.isEmpty()) { + if (m_preset.getOutputMappings().isEmpty()) { return; } - QHashIterator outIt(m_preset.outputMappings); + QHashIterator outIt(m_preset.getOutputMappings()); QStringList failures; while (outIt.hasNext()) { outIt.next(); @@ -185,15 +185,19 @@ void MidiController::clearTemporaryInputMappings() { void MidiController::commitTemporaryInputMappings() { // We want to replace duplicates that exist in m_preset but allow duplicates // in m_temporaryInputMappings. To do this, we first remove every key in - // m_temporaryInputMappings from m_preset.inputMappings. + // m_temporaryInputMappings from m_preset's input mappings. for (auto it = m_temporaryInputMappings.constBegin(); it != m_temporaryInputMappings.constEnd(); ++it) { - m_preset.inputMappings.remove(it.key()); + m_preset.removeInputMapping(it.key()); } - // Now, we can just use unite since we manually removed the duplicates in - // the original set. - m_preset.inputMappings.unite(m_temporaryInputMappings); + // Now, we can just use add all mappings from m_temporaryInputMappings + // since we removed the duplicates in the original set. + for (auto it = m_temporaryInputMappings.constBegin(); + it != m_temporaryInputMappings.constEnd(); + ++it) { + m_preset.addInputMapping(it.key(), it.value()); + } m_temporaryInputMappings.clear(); } @@ -219,8 +223,8 @@ void MidiController::receive(unsigned char status, unsigned char control, } } - auto it = m_preset.inputMappings.constFind(mappingKey.key); - for (; it != m_preset.inputMappings.constEnd() && it.key() == mappingKey.key; ++it) { + auto it = m_preset.getInputMappings().constFind(mappingKey.key); + for (; it != m_preset.getInputMappings().constEnd() && it.key() == mappingKey.key; ++it) { processInputMapping(it.value(), status, control, value, timestamp); } } @@ -470,8 +474,8 @@ void MidiController::receive(QByteArray data, mixxx::Duration timestamp) { } } - auto it = m_preset.inputMappings.constFind(mappingKey.key); - for (; it != m_preset.inputMappings.constEnd() && it.key() == mappingKey.key; ++it) { + auto it = m_preset.getInputMappings().constFind(mappingKey.key); + for (; it != m_preset.getInputMappings().constEnd() && it.key() == mappingKey.key; ++it) { processInputMapping(it.value(), data, timestamp); } } diff --git a/src/controllers/midi/midicontrollerpreset.h b/src/controllers/midi/midicontrollerpreset.h index 12ef6f0a13df..4bc131daba7d 100644 --- a/src/controllers/midi/midicontrollerpreset.h +++ b/src/controllers/midi/midicontrollerpreset.h @@ -39,9 +39,48 @@ class MidiControllerPreset : public ControllerPreset { return true; } + void addInputMapping(uint16_t key, MidiInputMapping mapping) { + m_inputMappings.insertMulti(key, mapping); + } + + void removeInputMapping(uint16_t key) { + m_inputMappings.remove(key); + } + + const QHash& getInputMappings() const { + return m_inputMappings; + } + + void setInputMappings(const QHash& mappings) { + if (m_inputMappings != mappings) { + m_inputMappings.clear(); + m_inputMappings.unite(mappings); + } + } + + void addOutputMapping(ConfigKey key, MidiOutputMapping mapping) { + m_outputMappings.insertMulti(key, mapping); + } + + void removeOutputMapping(ConfigKey key) { + m_outputMappings.remove(key); + } + + const QHash& getOutputMappings() const { + return m_outputMappings; + } + + void setOutputMappings(const QHash& mappings) { + if (m_outputMappings != mappings) { + m_outputMappings.clear(); + m_outputMappings.unite(mappings); + } + } + + private: // MIDI input and output mappings. - QHash inputMappings; - QHash outputMappings; + QHash m_inputMappings; + QHash m_outputMappings; }; #endif diff --git a/src/controllers/midi/midicontrollerpresetfilehandler.cpp b/src/controllers/midi/midicontrollerpresetfilehandler.cpp index 32e012447689..dc90812b8f65 100644 --- a/src/controllers/midi/midicontrollerpresetfilehandler.cpp +++ b/src/controllers/midi/midicontrollerpresetfilehandler.cpp @@ -101,7 +101,7 @@ ControllerPresetPointer MidiControllerPresetFileHandler::load(const QDomElement // Use insertMulti because we support multiple inputs mappings for the // same input MidiKey. - preset->inputMappings.insertMulti(mapping.key.key, mapping); + preset->addInputMapping(mapping.key.key, mapping); control = control.nextSiblingElement("control"); } @@ -181,7 +181,7 @@ ControllerPresetPointer MidiControllerPresetFileHandler::load(const QDomElement // Use insertMulti because we support multiple outputs from the same // control. - preset->outputMappings.insertMulti(mapping.controlKey, mapping); + preset->addOutputMapping(mapping.controlKey, mapping); output = output.nextSiblingElement("output"); } @@ -209,11 +209,12 @@ void MidiControllerPresetFileHandler::addControlsToDocument(const MidiController QDomElement controls = doc->createElement("controls"); // We will iterate over all of the values that have the same keys, so we need // to remove duplicate keys or else we'll duplicate those values. - auto sortedInputKeys = preset.inputMappings.uniqueKeys(); + auto sortedInputKeys = preset.getInputMappings().uniqueKeys(); std::sort(sortedInputKeys.begin(), sortedInputKeys.end()); for (const auto& key : sortedInputKeys) { - for (auto it = preset.inputMappings.constFind(key); - it != preset.inputMappings.constEnd() && it.key() == key; ++it) { + for (auto it = preset.getInputMappings().constFind(key); + it != preset.getInputMappings().constEnd() && it.key() == key; + ++it) { QDomElement controlNode = inputMappingToXML(doc, it.value()); controls.appendChild(controlNode); } @@ -223,11 +224,12 @@ void MidiControllerPresetFileHandler::addControlsToDocument(const MidiController // Repeat the process for the output mappings. QDomElement outputs = doc->createElement("outputs"); - auto sortedOutputKeys = preset.outputMappings.uniqueKeys(); + auto sortedOutputKeys = preset.getOutputMappings().uniqueKeys(); std::sort(sortedOutputKeys.begin(), sortedOutputKeys.end()); for (const auto& key : sortedOutputKeys) { - for (auto it = preset.outputMappings.constFind(key); - it != preset.outputMappings.constEnd() && it.key() == key; ++it) { + for (auto it = preset.getOutputMappings().constFind(key); + it != preset.getOutputMappings().constEnd() && it.key() == key; + ++it) { QDomElement outputNode = outputMappingToXML(doc, it.value()); outputs.appendChild(outputNode); } diff --git a/src/test/midicontrollertest.cpp b/src/test/midicontrollertest.cpp index ac520908638e..1e44a89e8503 100644 --- a/src/test/midicontrollertest.cpp +++ b/src/test/midicontrollertest.cpp @@ -33,7 +33,7 @@ class MidiControllerTest : public MixxxTest { } void addMapping(MidiInputMapping mapping) { - m_preset.inputMappings.insertMulti(mapping.key.key, mapping); + m_preset.addInputMapping(mapping.key.key, mapping); } void loadPreset(const MidiControllerPreset& preset) { From 436cd8809a58e8327885c2663478327b9ca407b0 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 17:15:57 +0100 Subject: [PATCH 019/393] controllers: Only write preset to user directory if it's dirty --- src/controllers/controllermanager.cpp | 10 ++++++++ src/controllers/controllerpreset.h | 25 ++++++++++++++++++- .../controllerpresetfilehandler.cpp | 8 +++--- src/controllers/midi/midicontrollerpreset.h | 6 +++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 2843f5371a81..2d2f594e1880 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -392,7 +392,17 @@ void ControllerManager::slotSavePresets(bool onlyActive) { if (onlyActive && !pController->isOpen()) { continue; } + ControllerPresetPointer pPreset = pController->getPreset(); + DEBUG_ASSERT(!pPreset); + QString deviceName = sanitizeString(pController->getName()); + if (!pPreset->isDirty()) { + qWarning() + << "Preset for device" << deviceName + << "is not dirty, no need to save it to the user presets."; + continue; + } + QString filename = firstAvailableFilename(filenames, deviceName); QString presetPath = userPresetsPath(m_pConfig) + filename + pController->presetExtension(); diff --git a/src/controllers/controllerpreset.h b/src/controllers/controllerpreset.h index ad4019632e19..19fcfefad9e7 100644 --- a/src/controllers/controllerpreset.h +++ b/src/controllers/controllerpreset.h @@ -21,7 +21,9 @@ class ConstControllerPresetVisitor; class ControllerPreset { public: - ControllerPreset() {} + ControllerPreset() + : m_bDirty(false) { + } virtual ~ControllerPreset() {} struct ScriptFileInfo { @@ -47,10 +49,20 @@ class ControllerPreset { info.functionPrefix = functionprefix; info.builtin = builtin; scripts.append(info); + setDirty(true); + } + + inline void setDirty(bool bDirty) { + m_bDirty = bDirty; + } + + inline bool isDirty() const { + return m_bDirty; } inline void setDeviceId(const QString id) { m_deviceId = id; + setDirty(true); } inline QString deviceId() const { @@ -59,6 +71,7 @@ class ControllerPreset { inline void setFilePath(const QString filePath) { m_filePath = filePath; + setDirty(true); } inline QString filePath() const { @@ -67,6 +80,7 @@ class ControllerPreset { inline void setName(const QString name) { m_name = name; + setDirty(true); } inline QString name() const { @@ -75,6 +89,7 @@ class ControllerPreset { inline void setAuthor(const QString author) { m_author = author; + setDirty(true); } inline QString author() const { @@ -83,6 +98,7 @@ class ControllerPreset { inline void setDescription(const QString description) { m_description = description; + setDirty(true); } inline QString description() const { @@ -91,6 +107,7 @@ class ControllerPreset { inline void setForumLink(const QString forumlink) { m_forumlink = forumlink; + setDirty(true); } inline QString forumlink() const { @@ -99,6 +116,7 @@ class ControllerPreset { inline void setWikiLink(const QString wikilink) { m_wikilink = wikilink; + setDirty(true); } inline QString wikilink() const { @@ -107,6 +125,7 @@ class ControllerPreset { inline void setSchemaVersion(const QString schemaVersion) { m_schemaVersion = schemaVersion; + setDirty(true); } inline QString schemaVersion() const { @@ -115,6 +134,7 @@ class ControllerPreset { inline void setMixxxVersion(const QString mixxxVersion) { m_mixxxVersion = mixxxVersion; + setDirty(true); } inline QString mixxxVersion() const { @@ -123,6 +143,7 @@ class ControllerPreset { inline void addProductMatch(QHash match) { m_productMatches.append(match); + setDirty(true); } virtual void accept(ControllerPresetVisitor* visitor) = 0; @@ -134,6 +155,8 @@ class ControllerPreset { QList< QHash > m_productMatches; private: + bool m_bDirty; + QString m_deviceId; QString m_filePath; QString m_name; diff --git a/src/controllers/controllerpresetfilehandler.cpp b/src/controllers/controllerpresetfilehandler.cpp index f50b8bd1b23f..22e045bef529 100644 --- a/src/controllers/controllerpresetfilehandler.cpp +++ b/src/controllers/controllerpresetfilehandler.cpp @@ -45,9 +45,11 @@ ControllerPresetPointer ControllerPresetFileHandler::loadPreset(const QString& p return ControllerPresetPointer(); } - // NOTE(rryan): We don't provide a device name. It's unused currently. - // TODO(rryan): Delete pHandler. - return pHandler->load(scriptPath, QString()); + ControllerPresetPointer pPreset = pHandler->load(scriptPath, QString()); + if (pPreset) { + pPreset->setDirty(false); + } + return pPreset; } ControllerPresetPointer ControllerPresetFileHandler::load(const QString path, diff --git a/src/controllers/midi/midicontrollerpreset.h b/src/controllers/midi/midicontrollerpreset.h index 4bc131daba7d..1c25659059b2 100644 --- a/src/controllers/midi/midicontrollerpreset.h +++ b/src/controllers/midi/midicontrollerpreset.h @@ -41,10 +41,12 @@ class MidiControllerPreset : public ControllerPreset { void addInputMapping(uint16_t key, MidiInputMapping mapping) { m_inputMappings.insertMulti(key, mapping); + setDirty(true); } void removeInputMapping(uint16_t key) { m_inputMappings.remove(key); + setDirty(true); } const QHash& getInputMappings() const { @@ -55,15 +57,18 @@ class MidiControllerPreset : public ControllerPreset { if (m_inputMappings != mappings) { m_inputMappings.clear(); m_inputMappings.unite(mappings); + setDirty(true); } } void addOutputMapping(ConfigKey key, MidiOutputMapping mapping) { m_outputMappings.insertMulti(key, mapping); + setDirty(true); } void removeOutputMapping(ConfigKey key) { m_outputMappings.remove(key); + setDirty(true); } const QHash& getOutputMappings() const { @@ -74,6 +79,7 @@ class MidiControllerPreset : public ControllerPreset { if (m_outputMappings != mappings) { m_outputMappings.clear(); m_outputMappings.unite(mappings); + setDirty(true); } } From 7f08658ad8170afbbed91abd649417fd4486c739 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 19 Mar 2020 17:31:22 +0100 Subject: [PATCH 020/393] controllers/dlgprefcontroller: Don't import scripts into user directory --- src/controllers/dlgprefcontroller.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 0e35ebd95a62..92882863964c 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -394,27 +394,6 @@ void DlgPrefController::slotLoadPreset(int chosenIndex) { return; } - // Import the preset scripts to the user scripts folder. - for (QList::iterator it = - pPreset->scripts.begin(); it != pPreset->scripts.end(); ++it) { - // No need to import builtin scripts. - if (it->builtin) { - continue; - } - - QString scriptPath = ControllerManager::getAbsolutePath( - it->name, presetDirs); - - - QString importedScriptFileName; - // If a conflict exists then importScript will provide a new filename to - // use. If importing fails then load the preset anyway without the - // import. - if (m_pControllerManager->importScript(scriptPath, &importedScriptFileName)) { - it->name = importedScriptFileName; - } - } - // TODO(rryan): We really should not load the preset here. We should load it // into the preferences GUI and then load it to the actual controller once // the user hits apply. From a653006f3cd3342490636bb36ed7dfe1397b34fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 22 Mar 2020 16:55:56 +0100 Subject: [PATCH 021/393] Improve strings in the color preferences pane --- src/preferences/dialog/dlgprefcolorsdlg.ui | 28 ++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolorsdlg.ui b/src/preferences/dialog/dlgprefcolorsdlg.ui index 08bf006daa6c..eeebc7ffbf7c 100644 --- a/src/preferences/dialog/dlgprefcolorsdlg.ui +++ b/src/preferences/dialog/dlgprefcolorsdlg.ui @@ -6,7 +6,7 @@ 0 0 - 524 + 656 333 @@ -29,27 +29,41 @@ - Hotcues: + Hotcue Palette - + + + + 0 + 0 + + + - Track: + Track Palette - + + + + 0 + 0 + + + - Auto hotcue colors + Cycle hotcue colors checkBoxAssignHotcueColors @@ -62,7 +76,7 @@ Automatically assigns a predefined color to a newly created hotcue point, based on its index. - Assign predefined colors to newly created hotcue points + Select consecutive palette colors for new hotcues From a07164d686ae049d38681c11b1e3bc1699222710 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 22 Mar 2020 16:14:00 +0100 Subject: [PATCH 022/393] controllers/controllerengine: Improve error messages for mappings --- src/controllers/controllerengine.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 1488353b5d48..cbd4e50ef476 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -494,8 +494,8 @@ void ControllerEngine::scriptErrorDialog(const QString& detailedError) { qWarning() << "ControllerEngine:" << detailedError; ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); props->setType(DLG_WARNING); - props->setTitle(tr("Controller script error")); - props->setText(tr("A control you just used is not working properly.")); + props->setTitle(tr("Controller Mapping Error")); + props->setText(QString(tr("A control you just used on your controller \"%1\" is not working properly.")).arg(m_pController->getName())); props->setInfoText(""+tr("The script code needs to be fixed.")+ "

"+tr("For now, you can: Ignore this error for this session but you may experience erratic behavior.")+ "
"+tr("Try to recover by resetting your controller.")+"

"+""); @@ -969,9 +969,10 @@ bool ControllerEngine::evaluate(const QString& scriptName, QList script // Set up error dialog ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); props->setType(DLG_WARNING); - props->setTitle("Controller script file problem"); - props->setText(QString("There was a problem opening the controller script file %1.").arg(filename)); - props->setInfoText(input.errorString()); + props->setTitle(tr("Controller Mapping File Problem")); + props->setText(tr("The mapping for controller \"%1\" cannot be opened.").arg(m_pController->getName())); + props->setInfoText(tr("The functionality provided by this controller mapping will be disabled until the issue has been resolved.")); + props->setDetails(QString(tr("File: %1\n\n")).arg(filename) + input.errorString()); // Ask above layer to display the dialog & handle user response ErrorDialogHandler::instance()->requestErrorDialog(props); @@ -1007,11 +1008,10 @@ bool ControllerEngine::evaluate(const QString& scriptName, QList script if (m_bPopups) { ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); props->setType(DLG_WARNING); - props->setTitle("Controller script file error"); - props->setText(QString("There was an error in the controller script file %1.").arg(filename)); - props->setInfoText("The functionality provided by this script file will be disabled."); - props->setDetails(error); - + props->setTitle(tr("Controller Mapping Error")); + props->setText(QString(tr("The controller mapping for \"%1\" caused an error.")).arg(m_pController->getName())); + props->setInfoText(tr("The functionality provided by this controller mapping will be disabled until the issue has been resolved.")); + props->setDetails(QString(tr("File: %1\n\n")).arg(filename) + error); ErrorDialogHandler::instance()->requestErrorDialog(props); } return false; From fe4aae286945385b5a6ec440a07d1b6c3ea1fc26 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 22 Mar 2020 16:15:34 +0100 Subject: [PATCH 023/393] controllers/controllerengine: Enable errors popups in all cases --- src/controllers/controllerengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index cbd4e50ef476..5af40afebc8b 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -34,7 +34,7 @@ ControllerEngine::ControllerEngine( : m_pEngine(nullptr), m_pController(controller), m_pConfig(pConfig), - m_bPopups(false), + m_bPopups(true), m_pBaClass(nullptr) { // Handle error dialog buttons qRegisterMetaType("QMessageBox::StandardButton"); From 0b55805ef1d04d46535b558ef33e15cc74a58d34 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 22 Mar 2020 16:49:49 +0100 Subject: [PATCH 024/393] controllers/controllerengine: Add missing nullptr checks --- src/controllers/controllerengine.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 5af40afebc8b..ea29de0b50dc 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -82,6 +82,10 @@ Output: - -------- ------------------------------------------------------ */ void ControllerEngine::callFunctionOnObjects(QList scriptFunctionPrefixes, const QString& function, QScriptValueList args) { + if (m_pEngine == nullptr) { + return; + } + const QScriptValue global = m_pEngine->globalObject(); for (const QString& prefixName : scriptFunctionPrefixes) { @@ -113,6 +117,10 @@ Output: QScriptValue of JS snippet wrapped in an anonymous function ------------------------------------------------------------------- */ QScriptValue ControllerEngine::wrapFunctionCode(const QString& codeSnippet, int numberOfArgs) { + VERIFY_OR_DEBUG_ASSERT(m_pEngine != nullptr) { + return QScriptValue(); + } + QScriptValue wrappedFunction; auto i = m_scriptWrappedFunctionCache.constFind(codeSnippet); @@ -134,6 +142,10 @@ QScriptValue ControllerEngine::wrapFunctionCode(const QString& codeSnippet, } QScriptValue ControllerEngine::getThisObjectInFunctionCall() { + VERIFY_OR_DEBUG_ASSERT(m_pEngine != nullptr) { + return QScriptValue(); + } + QScriptContext *ctxt = m_pEngine->currentContext(); // Our current context is a function call. We want to grab the 'this' // from the caller's context, so we walk up the stack. @@ -150,6 +162,10 @@ Input: - Output: - -------- ------------------------------------------------------ */ void ControllerEngine::gracefulShutdown() { + if (m_pEngine == nullptr) { + return; + } + qDebug() << "ControllerEngine shutting down..."; // Stop all timers From 3bf5b0bdc0a70487e694fe7aed2ae7cec1502502 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 22 Mar 2020 16:51:06 +0100 Subject: [PATCH 025/393] controllers/controllerengine: Move uninitialize engine code into method --- src/controllers/controllerengine.cpp | 27 ++++++++++++--------------- src/controllers/controllerengine.h | 1 + 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index ea29de0b50dc..e1126fb9e1f7 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -66,13 +66,7 @@ ControllerEngine::~ControllerEngine() { m_scratchFilters[i] = nullptr; } - // Delete the script engine, first clearing the pointer so that - // other threads will not get the dead pointer after we delete it. - if (m_pEngine != nullptr) { - QScriptEngine *engine = m_pEngine; - m_pEngine = nullptr; - engine->deleteLater(); - } + uninitializeScriptEngine(); } /* -------- ------------------------------------------------------ @@ -238,6 +232,16 @@ void ControllerEngine::initializeScriptEngine() { engineGlobalObject.setProperty("ByteArray", m_pBaClass->constructor()); } +void ControllerEngine::uninitializeScriptEngine() { + // Delete the script engine, first clearing the pointer so that + // other threads will not get the dead pointer after we delete it. + if (m_pEngine != nullptr) { + QScriptEngine* engine = m_pEngine; + m_pEngine = nullptr; + engine->deleteLater(); + } +} + /* -------- ------------------------------------------------------ Purpose: Load all script files given in the supplied list Input: List of script paths and file names to load @@ -277,14 +281,7 @@ void ControllerEngine::scriptHasChanged(const QString& scriptFilename) { this, SLOT(scriptHasChanged(QString))); gracefulShutdown(); - - // Delete the script engine, first clearing the pointer so that - // other threads will not get the dead pointer after we delete it. - if (m_pEngine != nullptr) { - QScriptEngine *engine = m_pEngine; - m_pEngine = nullptr; - engine->deleteLater(); - } + uninitializeScriptEngine(); initializeScriptEngine(); loadScriptFiles(m_lastScriptPaths, pPreset->scripts); diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 87a75abd730a..b87d221585e1 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -178,6 +178,7 @@ class ControllerEngine : public QObject { bool internalExecute(QScriptValue thisObject, QScriptValue functionObject, QScriptValueList arguments); void initializeScriptEngine(); + void uninitializeScriptEngine(); void scriptErrorDialog(const QString& detailedError); void generateScriptFunctions(const QString& code); From 2bc6e8901b6fe1a1f3210ab217a4d047f9ae85c5 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 22 Mar 2020 16:52:23 +0100 Subject: [PATCH 026/393] controllers/controllerengine: Uninitalize if script load failed --- src/controllers/controller.cpp | 3 ++- src/controllers/controllerengine.cpp | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 15a1e1dd5ec0..6ef5ad79c461 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -67,7 +67,8 @@ bool Controller::applyPreset(QList scriptPaths, bool initializeScripts) } bool success = m_pEngine->loadScriptFiles(scriptPaths, pPreset->scripts); - if (initializeScripts) { + + if (success && initializeScripts) { m_pEngine->initializeScripts(pPreset->scripts); } return success; diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index e1126fb9e1f7..8db622afcc4f 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -266,9 +266,15 @@ bool ControllerEngine::loadScriptFiles(const QList& scriptPaths, connect(&m_scriptWatcher, SIGNAL(fileChanged(QString)), this, SLOT(scriptHasChanged(QString))); - emit initialized(); + bool success = result && m_scriptErrors.isEmpty(); + if (!success) { + gracefulShutdown(); + uninitializeScriptEngine(); + } else { + emit initialized(); + } - return result && m_scriptErrors.isEmpty(); + return success; } // Slot to run when a script file has changed @@ -284,7 +290,9 @@ void ControllerEngine::scriptHasChanged(const QString& scriptFilename) { uninitializeScriptEngine(); initializeScriptEngine(); - loadScriptFiles(m_lastScriptPaths, pPreset->scripts); + if (!loadScriptFiles(m_lastScriptPaths, pPreset->scripts)) { + return; + } qDebug() << "Re-initializing scripts"; initializeScripts(pPreset->scripts); From 29158a54a305923bc382f217c664db2a126e248b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 22 Mar 2020 17:04:20 +0100 Subject: [PATCH 027/393] Allow to translate "Name" in the ColorPaletteEditor --- src/preferences/colorpaletteeditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 5e1ca4d55615..9c8b12d4c290 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -31,7 +31,7 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) m_pDiscardButton = pButtonBox->addButton(QDialogButtonBox::Discard); QHBoxLayout* pTopLayout = new QHBoxLayout(); - pTopLayout->addWidget(new QLabel("Name:")); + pTopLayout->addWidget(new QLabel(tr("Name"))); pTopLayout->addWidget(m_pPaletteNameComboBox, 1); pTopLayout->addWidget(pButtonBox); From 7e1be9eabeb1cbdb26aabca315c248918170cce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 22 Mar 2020 18:57:50 +0100 Subject: [PATCH 028/393] Move button row below the palette and optimize column widths --- src/preferences/colorpaletteeditor.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 9c8b12d4c290..24c7333f2d76 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -33,18 +33,19 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) QHBoxLayout* pTopLayout = new QHBoxLayout(); pTopLayout->addWidget(new QLabel(tr("Name"))); pTopLayout->addWidget(m_pPaletteNameComboBox, 1); - pTopLayout->addWidget(pButtonBox); QVBoxLayout* pLayout = new QVBoxLayout(); pLayout->addLayout(pTopLayout); pLayout->addWidget(m_pTableView, 1); + pLayout->addWidget(pButtonBox); setLayout(pLayout); setContentsMargins(0, 0, 0, 0); // Set up model - m_pModel->setColumnCount(2); + m_pModel->setColumnCount(3); m_pModel->setHeaderData(0, Qt::Horizontal, tr("Color"), Qt::DisplayRole); - m_pModel->setHeaderData(1, Qt::Horizontal, tr("Assign to Hotcue"), Qt::DisplayRole); + m_pModel->setHeaderData(1, Qt::Horizontal, tr("Assign to Hotcue Number"), Qt::DisplayRole); + m_pModel->setHeaderData(2, Qt::Horizontal, QString(), Qt::DisplayRole); connect(m_pModel, &ColorPaletteEditorModel::dirtyChanged, this, @@ -63,6 +64,10 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) m_pTableView->setContextMenuPolicy(Qt::CustomContextMenu); m_pTableView->setModel(m_pModel); + m_pTableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + m_pTableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + m_pTableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + connect(m_pTableView, &QTableView::doubleClicked, this, From 30aaea8a51e98dd242848c0a46f12f53c59c3c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 22 Mar 2020 20:06:49 +0100 Subject: [PATCH 029/393] Fill table with assigned cue numbers if no cue numbers are set --- src/preferences/colorpaletteeditormodel.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/preferences/colorpaletteeditormodel.cpp b/src/preferences/colorpaletteeditormodel.cpp index 62d958fbe165..9335edd13b52 100644 --- a/src/preferences/colorpaletteeditormodel.cpp +++ b/src/preferences/colorpaletteeditormodel.cpp @@ -103,9 +103,15 @@ void ColorPaletteEditorModel::setColorPalette(const ColorPalette& palette) { // Make a map of hotcue indices QMap hotcueColorIndicesMap; QList hotcueColorIndices = palette.getHotcueIndices(); - for (int i = 0; i < hotcueColorIndices.size(); i++) { - int colorIndex = hotcueColorIndices.at(i); - hotcueColorIndicesMap.insert(colorIndex, i); + if (hotcueColorIndices.size()) { + for (int i = 0; i < hotcueColorIndices.size(); i++) { + int colorIndex = hotcueColorIndices.at(i); + hotcueColorIndicesMap.insert(colorIndex, i); + } + } else { + for (int i = 0; i < palette.size(); i++) { + hotcueColorIndicesMap.insert(i, i); + } } for (int i = 0; i < palette.size(); i++) { From eca50f8563a5bc6972cb62db312cde4c0f655586 Mon Sep 17 00:00:00 2001 From: nuess0r Date: Mon, 23 Mar 2020 00:15:37 +0100 Subject: [PATCH 030/393] Initial commit for Stanton DJC.4 mapping --- res/controllers/Stanton-DJC-4-scripts.js | 495 +++++ res/controllers/Stanton-DJC-4.midi.xml | 2263 ++++++++++++++++++++++ 2 files changed, 2758 insertions(+) create mode 100644 res/controllers/Stanton-DJC-4-scripts.js create mode 100644 res/controllers/Stanton-DJC-4.midi.xml diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js new file mode 100644 index 000000000000..e37068b2e2f9 --- /dev/null +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -0,0 +1,495 @@ +/** + * Stanton DJC4 controller script v1.0 for Mixxx v2.2.3 + * + * Written by Martin Bruset Solberg + * Adopted for v2.2.3 by Christoph Zimmermann + * + * Based on MC2000 script by Esteban Serrano Roloff + * and Denon MC7000 script by OsZ + * + * + * TODO: + * Effects browsing + * Beat multiplier + * + **/ + +var djc4 = {}; + +// ---------- Global variables ---------- + +// MIDI Reception commands (from spec) +djc4.leds = { + loopminus: 2, + loopplus: 3, + loopin: 4, + loopout: 5, + loopon: 6, + loopdel: 7, + hotcue1: 8, + hotcue2: 9, + hotcue3: 10, + hotcue4: 11, + sample1: 12, + sample2: 13, + sample3: 14, + sample4: 15, + keylock: 16, + sync: 18, + pbendminus: 19, + pbendplus: 20, + scratch: 21, + tap: 22, + cue: 23, + play: 24, + highkill: 25, + midkill: 26, + lowkill: 27, + pfl: 28, + fxon: 30, + fxexf1: 31, + fxexf2: 32, + fxexf3: 33, + loadac: 34, + loadbd: 35, + videofx: 36, + xflink: 37, + keyon: 38, + filteron: 39, + tx: 46, + fx: 47 +}; + +djc4.scratchMode = [false, false, false, false]; + +// ---------- Functions ---------- + +// Called when the MIDI device is opened & set up. +djc4.init = function(id, debug) { + djc4.id = id; + djc4.debug = debug; + + // Put all LEDs to default state. + djc4.allLed2Default(); + + // ---- Connect controls ----------- + + // ---- Controls for Channel 1 to 4 + var i = 0; + for (i = 1; i <= 4; i++) { + // Cue 1-4 + var j = 0; + for (j = 1; j <= 4; j++) { + engine.makeConnection("[Channel" + i + "]", "hotcue_" + j + "_enabled", + djc4.hotcueSetLed); + } + + // Cue + engine.makeConnection("[Channel" + i + "]", "cue_indicator", + djc4.cueSetLed); + // Play + engine.makeConnection("[Channel" + i + "]", "play_indicator", + djc4.playSetLed); + + // Loop in + engine.makeConnection("[Channel" + i + "]", "loop_start_position", + djc4.loopStartSetLed); + // Loop out + engine.makeConnection("[Channel" + i + "]", "loop_end_position", + djc4.loopEndSetLed); + // Loop enabled + engine.makeConnection("[Channel" + i + "]", "loop_enabled", + djc4.loopEnabledSetLed); + // Loop double + engine.makeConnection("[Channel" + i + "]", "loop_double", + djc4.loopDoubleSetLed); + // Loop halve + engine.makeConnection("[Channel" + i + "]", "loop_halve", + djc4.loopHalveSetLed); + + // Monitor cue + engine.makeConnection("[Channel" + i + "]", "pfl", djc4.pflSetLed); + + // Kills + engine.makeConnection("[Channel" + i + "]", "filterHighKill", + djc4.highkillSetLed); + engine.makeConnection("[Channel" + i + "]", "filterMidKill", + djc4.midkillSetLed); + engine.makeConnection("[Channel" + i + "]", "filterLowKill", + djc4.lowkillSetLed); + + engine.makeConnection("[QuickEffectRack1_[Channel" + i + "]_Effect1]", + "enabled", djc4.filterSetLed); + + // Keylock + engine.makeConnection("[Channel" + i + "]", "keylock", djc4.keylockSetLed); + + // Pitch bend + engine.makeConnection("[Channel" + i + "]", "rate_temp_down", + djc4.ratetempdownSetLed); + engine.makeConnection("[Channel" + i + "]", "rate_temp_up", + djc4.ratetempupSetLed); + } + + // ---- Controls for Sampler 1 - 8 + for (i = 1; i <= 8; i++) { + engine.makeConnection("[Sampler" + i + "]", "track_loaded", + djc4.samplerSetLed); + if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 1) { + djc4.samplerSetLed(1, "[Sampler" + i + "]"); + } + } + + // ---- Controls for EffectUnit 1 to 2 + for (i = 1; i <= 2; i++) { + // Effects 1-3 + for (j = 1; j <= 3; j++) { + engine.makeConnection("[EffectRack1_EffectUnit" + i + "_Effect" + j + "]", + "enabled", djc4.fxenabledSetLed); + } + } + // Effect enabled for Channel + engine.makeConnection("[EffectRack1_EffectUnit1]", "group_[Channel1]_enable", + djc4.fxon1SetLed); + engine.makeConnection("[EffectRack1_EffectUnit1]", "group_[Channel3]_enable", + djc4.fxon3SetLed); + engine.makeConnection("[EffectRack1_EffectUnit2]", "group_[Channel2]_enable", + djc4.fxon2SetLed); + engine.makeConnection("[EffectRack1_EffectUnit2]", "group_[Channel4]_enable", + djc4.fxon4SetLed); + + // ---- VU meter (Master is shown) + engine.makeConnection("[Master]", "VuMeterL", djc4.VuMeterLSetLed); + engine.makeConnection("[Master]", "VuMeterR", djc4.VuMeterRSetLed); + + // Enable load LEDs because Channels are empty at start + djc4.setLed(1, djc4.leds["loadac"], 1); + djc4.setLed(3, djc4.leds["loadac"], 1); + djc4.setLed(2, djc4.leds["loadbd"], 1); + djc4.setLed(4, djc4.leds["loadbd"], 1); +}; + +// Called when the MIDI device is closed +djc4.shutdown = function() { + // Put all LEDs to default state. + djc4.allLed2Default(); +}; + +// === FOR MANAGING LEDS === + +djc4.allLed2Default = function() { + // All LEDs OFF for deck 1 to 4 + var i = 0; + for (i = 1; i <= 4; i++) { + for (var led in djc4.leds) { + djc4.setLed(i, djc4.leds[led], 0); + } + // Channel VU meter + midi.sendShortMsg(0xB0 + (i - 1), 2, 0); + } + // Master VU meter + midi.sendShortMsg(0xB0, 3, 0); + midi.sendShortMsg(0xB0, 4, 0); +}; + +// Set leds function +djc4.setLed = function(deck, led, status) { + var ledStatus = 0x00; // Default OFF + switch (status) { + case 0: + ledStatus = 0x00; + break; // OFF + case false: + ledStatus = 0x00; + break; // OFF + case 1: + ledStatus = 0x7F; + break; // ON + case true: + ledStatus = 0x7F; + break; // ON + default: + break; + } + midi.sendShortMsg(0x90 + (deck - 1), led, ledStatus); +}; + +// === MISC COMMON === + +djc4.group2Deck = function(group) { + var matches = group.match(/\[Channel(\d+)\]/); + if (matches === null) { + return -1; + } else { + return matches[1]; + } +}; + +djc4.group2Sampler = function(group) { + var matches = group.match(/^\[Sampler(\d+)\]$/); + if (matches === null) { + return -1; + } else { + return matches[1]; + } +}; + +// === Scratch control === + +djc4.toggleScratchMode = function(channel, control, value, status, group) { + if (!value) + return; + + var deck = djc4.group2Deck(group); + // Toggle setting + djc4.scratchMode[deck - 1] = !djc4.scratchMode[deck - 1]; + djc4.scratchSetLed(djc4.scratchMode[deck - 1], group); +}; + +// === JOG WHEEL === + +// Touch platter +djc4.wheelTouch = function(channel, control, value) { + var deck = channel + 1; + + if (control === 0x58) { // If shift is pressed, do a fast search + if (value === 0x7F) { // If touch + var alpha = 1.0 / 8; + var beta = alpha / 32; + + var rpm = 40.0; + + engine.scratchEnable(deck, 128, rpm, alpha, beta, true); + } else { // If button up + engine.scratchDisable(deck); + } + } else if (djc4.scratchMode[channel] === true) { // If scratch enabled + if (value === 0x7F) { // If touch + alpha = 1.0 / 8; + beta = alpha / 32; + + rpm = 150.0; + + engine.scratchEnable(deck, 128, rpm, alpha, beta, true); + } else { // If button up + engine.scratchDisable(deck); + } + } else if (value === 0x00) { + // In case shift is let go before the platter, + // ensure scratch is disabled + engine.scratchDisable(deck); + } +}; + +// Wheel +djc4.wheelTurn = function(channel, control, value, status, group) { + // var deck = channel + 1; + var deck = script.deckFromGroup(group); + + // B: For a control that centers on 0x40 (64): + var newValue = (value - 64); + + // See if we're scratching. If not, skip this. + if (!engine.isScratching(deck)) { + engine.setValue(group, "jog", newValue / 4); + return; + } + + // In either case, register the movement + engine.scratchTick(deck, newValue); +}; + +// === Browser === + +djc4.browseMove = function(channel, control, value, status, group) { + // Next/previous track + if (value === 0x41) { + engine.setValue(group, "MoveUp", true); + } else if (value === 0x3F) { + engine.setValue(group, "MoveDown", true); + } else + return; +}; + +djc4.browseScroll = function(channel, control, value, status, group) { + // Next/previous page + if (value === 0x41) { + engine.setValue(group, "ScrollUp", true); + } else if (value === 0x3F) { + engine.setValue(group, "ScrollDown", true); + } else + return; +}; + +// === Sampler Volume Control === + +djc4.samplerVolume = function(channel, control, value) { + // check if the Sampler Volume is at Zero and if so hide the sampler bank + if (value > 0x00) { + engine.setValue("[Samplers]", "show_samplers", true); + } else { + engine.setValue("[Samplers]", "show_samplers", false); + } + // get the Sampler Row opened with its details + engine.setValue("[SamplerRow1]", "expanded", true); + + // control up to 8 sampler volumes with the one knob on the mixer + for (var i = 1; i <= 8; i++) { + engine.setValue("[Sampler" + i + "]", "pregain", + script.absoluteNonLin(value, 0, 1.0, 4.0)); + } +}; + +// === SET LED FUNCTIONS === + +// Hot cues + +djc4.hotcueSetLed = function(value, group, control) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["hotcue" + control[7]], value); +}; + +// PFL + +djc4.pflSetLed = function( + value, + group) { djc4.setLed(djc4.group2Deck(group), djc4.leds["pfl"], value); }; + +// Play/Cue + +djc4.playSetLed = function(value, group) { + // var deck = channel + 1; + var deck = djc4.group2Deck(group); + + djc4.setLed(djc4.group2Deck(group), djc4.leds["play"], value); + + // if a deck is playing it is not possible to load a track + // -> disable corresponding load LED + if (deck === 1 || deck === 3) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["loadac"], !value); + } else { + djc4.setLed(djc4.group2Deck(group), djc4.leds["loadbd"], !value); + } +}; + +djc4.cueSetLed = function( + value, + group) { djc4.setLed(djc4.group2Deck(group), djc4.leds["cue"], value); }; + +// Keylock + +djc4.keylockSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["keylock"], value); +}; + +// Loops + +djc4.loopStartSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["loopin"], value !== -1); +}; + +djc4.loopEndSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["loopout"], value !== -1); +}; + +djc4.loopEnabledSetLed = function( + value, + group) { djc4.setLed(djc4.group2Deck(group), djc4.leds["loopon"], value); }; + +djc4.loopDoubleSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["loopplus"], value); +}; + +djc4.loopHalveSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["loopminus"], value); +}; + +// Kills + +djc4.highkillSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["highkill"], value); +}; + +djc4.midkillSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["midkill"], value); +}; + +djc4.lowkillSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["lowkill"], value); +}; + +djc4.filterSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["filteron"], !value); +}; + +// Scratch button + +djc4.scratchSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["scratch"], value); +}; + +// Pitch bend buttons +djc4.ratetempdownSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["pbendminus"], value); +}; + +djc4.ratetempupSetLed = function(value, group) { + djc4.setLed(djc4.group2Deck(group), djc4.leds["pbendplus"], value); +}; + +djc4.fxenabledSetLed = function(value, group) { + var matches = group.match(/^\[EffectRack1_EffectUnit(\d+)_Effect(\d+)\]$/); + if (matches !== null) { + var led = djc4.leds["fxexf1"] - 1 + parseInt(matches[2], 10); + + // FX1 is on deck A/C + if (parseInt(matches[1], 10) === 1) { + djc4.setLed(1, led, value); + djc4.setLed(3, led, value); + } else { + djc4.setLed(2, led, value); + djc4.setLed(4, led, value); + } + } +}; + +djc4.fxon1SetLed = function( + value) { djc4.setLed(1, djc4.leds["fxon"], value); }; + +djc4.fxon2SetLed = function( + value) { djc4.setLed(2, djc4.leds["fxon"], value); }; + +djc4.fxon3SetLed = function( + value) { djc4.setLed(3, djc4.leds["fxon"], value); }; + +djc4.fxon4SetLed = function( + value) { djc4.setLed(4, djc4.leds["fxon"], value); }; + +// Sampler + +djc4.samplerSetLed = function(value, group) { + var sampler = djc4.group2Sampler(group); + + if (sampler <= 4) { + // Sampler 1 - 4 are on deck A/C + var led = djc4.leds["sample1"] - 1 + parseInt(sampler, 10); + djc4.setLed(1, led, value); + djc4.setLed(3, led, value); + } else { + // Sampler 5 - 8 are on deck B/D + led = djc4.leds["sample1"] - 1 - 4 + parseInt(sampler, 10); + djc4.setLed(2, led, value); + djc4.setLed(4, led, value); + } +}; + +// === VU Meter === + +djc4.VuMeterLSetLed = function(value) { + var ledStatus = (value * 119); + midi.sendShortMsg(0xB0, 3, ledStatus); +}; + +djc4.VuMeterRSetLed = function(value) { + var ledStatus = (value * 119); + midi.sendShortMsg(0xB0, 4, ledStatus); +}; diff --git a/res/controllers/Stanton-DJC-4.midi.xml b/res/controllers/Stanton-DJC-4.midi.xml new file mode 100644 index 000000000000..4ee83b3d2d18 --- /dev/null +++ b/res/controllers/Stanton-DJC-4.midi.xml @@ -0,0 +1,2263 @@ + + + + Stanton DJC.4 + Martin Bruset Solberg, Christoph Zimmermann + The Stanton DJC.4 is a four-deck control surface with large, touch-sensitive jog wheels and built-in audio interface (2 inputs, 2 outputs). Configured as four-deck, two-fx and master VU meter controller + https://mixxx.org/wiki/doku.php/stanton_djc4 + + + + + + + + [Channel1] + djc4.wheelTurn + 0xB0 + 0x02 + + + + + + [Channel1] + loop_halve + 0x90 + 0x02 + + + + + + [Channel2] + djc4.wheelTurn + 0xB1 + 0x02 + + + + + + [Channel2] + loop_halve + 0x91 + 0x02 + + + + + + [Channel3] + djc4.wheelTurn + 0xB2 + 0x02 + + + + + + [Channel3] + loop_halve + 0x92 + 0x02 + + + + + + [Channel4] + djc4.wheelTurn + 0xB3 + 0x02 + + + + + + [Channel4] + loop_halve + 0x93 + 0x02 + + + + + + [Channel1] + loop_double + 0x90 + 0x03 + + + + + + [Channel1] + pregain + 0xB0 + 0x03 + + + + + + [Channel2] + loop_double + 0x91 + 0x03 + + + + + + [Channel2] + pregain + 0xB1 + 0x03 + + + + + + [Channel3] + loop_double + 0x92 + 0x03 + + + + + + [Channel3] + pregain + 0xB2 + 0x03 + + + + + + [Channel4] + loop_double + 0x93 + 0x03 + + + + + + [Channel4] + pregain + 0xB3 + 0x03 + + + + + + [Channel1] + loop_in + 0x90 + 0x04 + + + + + + [Channel2] + loop_in + 0x91 + 0x04 + + + + + + [Channel3] + loop_in + 0x92 + 0x04 + + + + + + [Channel4] + loop_in + 0x93 + 0x04 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter3 + 0xB0 + 0x04 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter3 + 0xB1 + 0x04 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + parameter3 + 0xB2 + 0x04 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + parameter3 + 0xB3 + 0x04 + + + + + + [Channel1] + loop_out + 0x90 + 0x05 + + + + + + [Channel2] + loop_out + 0x91 + 0x05 + + + + + + [Channel3] + loop_out + 0x92 + 0x05 + + + + + + [Channel4] + loop_out + 0x93 + 0x05 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter2 + 0xB0 + 0x05 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter2 + 0xB1 + 0x05 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + parameter2 + 0xB2 + 0x05 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + parameter2 + 0xB3 + 0x05 + + + + + + [Channel1] + reloop_toggle + 0x90 + 0x06 + + + + + + [Channel2] + reloop_toggle + 0x91 + 0x06 + + + + + + [Channel3] + reloop_toggle + 0x92 + 0x06 + + + + + + [Channel4] + reloop_toggle + 0x93 + 0x06 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter1 + 0xB0 + 0x06 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter1 + 0xB1 + 0x06 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + parameter1 + 0xB2 + 0x06 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + parameter1 + 0xB3 + 0x06 + + + + + + [Channel1] + volume + 0xB0 + 0x07 + + + + + + [Channel2] + volume + 0xB1 + 0x07 + + + + + + [Channel3] + volume + 0xB2 + 0x07 + + + + + + [Channel4] + volume + 0xB3 + 0x07 + + + + + + [Channel1] + hotcue_1_activate + 0x90 + 0x08 + + + + + + [Channel2] + hotcue_1_activate + 0x91 + 0x08 + + + + + + [Channel3] + hotcue_1_activate + 0x92 + 0x08 + + + + + + [Channel4] + hotcue_1_activate + 0x93 + 0x08 + + + + + + [Channel1] + hotcue_2_activate + 0x90 + 0x09 + + + + + + [Channel2] + hotcue_2_activate + 0x91 + 0x09 + + + + + + [Channel3] + hotcue_2_activate + 0x92 + 0x09 + + + + + + [Channel4] + hotcue_2_activate + 0x93 + 0x09 + + + + + + [EffectRack1_EffectUnit1_Effect1] + meta + 0xB0 + 0x09 + + + + + + [EffectRack1_EffectUnit2_Effect1] + meta + 0xB1 + 0x09 + + + + + + [EffectRack1_EffectUnit1_Effect1] + meta + 0xB2 + 0x09 + + + + + + [EffectRack1_EffectUnit2_Effect1] + meta + 0xB3 + 0x09 + + + + + + [Channel1] + hotcue_3_activate + 0x90 + 0x0A + + + + + + [Channel2] + hotcue_3_activate + 0x91 + 0x0A + + + + + + [Channel3] + hotcue_3_activate + 0x92 + 0x0A + + + + + + [Channel4] + hotcue_3_activate + 0x93 + 0x0A + + + + + + [EffectRack1_EffectUnit1_Effect2] + meta + 0xB0 + 0x0A + + + + + + [EffectRack1_EffectUnit2_Effect2] + meta + 0xB1 + 0x0A + + + + + + [EffectRack1_EffectUnit1_Effect2] + meta + 0xB2 + 0x0A + + + + + + [EffectRack1_EffectUnit2_Effect2] + meta + 0xB3 + 0x0A + + + + + + [Channel1] + hotcue_4_activate + 0x90 + 0x0B + + + + + + [Channel2] + hotcue_4_activate + 0x91 + 0x0B + + + + + + [Channel3] + hotcue_4_activate + 0x92 + 0x0B + + + + + + [Channel4] + hotcue_4_activate + 0x93 + 0x0B + + + + + + [EffectRack1_EffectUnit1_Effect3] + meta + 0xB0 + 0x0B + + + + + + [EffectRack1_EffectUnit2_Effect3] + meta + 0xB1 + 0x0B + + + + + + [EffectRack1_EffectUnit1_Effect3] + meta + 0xB2 + 0x0B + + + + + + [EffectRack1_EffectUnit2_Effect3] + meta + 0xB3 + 0x0B + + + + + + [Sampler1] + cue_gotoandplay + 0x90 + 0x0C + + + + + + [Sampler5] + cue_gotoandplay + 0x91 + 0x0C + + + + + + [Sampler1] + cue_gotoandplay + 0x92 + 0x0C + + + + + + [Sampler5] + cue_gotoandplay + 0x93 + 0x0C + + + + + + [Sampler2] + cue_gotoandplay + 0x90 + 0x0D + + + + + + [Sampler6] + cue_gotoandplay + 0x91 + 0x0D + + + + + + [Sampler2] + cue_gotoandplay + 0x92 + 0x0D + + + + + + [Sampler6] + cue_gotoandplay + 0x93 + 0x0D + + + + + + [Sampler] + djc4.samplerVolume + 0xB0 + 0x0D + + + + + + [Library] + djc4.browseMove + 0xB0 + 0x0E + + + + + + [Sampler3] + cue_gotoandplay + 0x90 + 0x0E + + + + + + [Sampler7] + cue_gotoandplay + 0x91 + 0x0E + + + + + + [Sampler3] + cue_gotoandplay + 0x92 + 0x0E + + + + + + [Sampler7] + cue_gotoandplay + 0x93 + 0x0E + + + + + + [Sampler4] + cue_gotoandplay + 0x90 + 0x0F + + + + + + [Sampler8] + cue_gotoandplay + 0x91 + 0x0F + + + + + + [Sampler4] + cue_gotoandplay + 0x92 + 0x0F + + + + + + [Sampler8] + cue_gotoandplay + 0x93 + 0x0F + + + + + + [Channel1] + keylock + 0x90 + 0x10 + + + + + + [Channel2] + keylock + 0x91 + 0x10 + + + + + + [Channel3] + keylock + 0x92 + 0x10 + + + + + + [Channel4] + keylock + 0x93 + 0x10 + + + + + + [Master] + crossfader + 0xB0 + 0x10 + + + + + + [Channel1] + beatsync + 0x90 + 0x12 + + + + + + [Channel2] + beatsync + 0x91 + 0x12 + + + + + + [Channel3] + beatsync + 0x92 + 0x12 + + + + + + [Channel4] + beatsync + 0x93 + 0x12 + + + + + + [Channel1] + rate_temp_down + 0x90 + 0x13 + + + + + + [Channel2] + rate_temp_down + 0x91 + 0x13 + + + + + + [Channel3] + rate_temp_down + 0x92 + 0x13 + + + + + + [Channel4] + rate_temp_down + 0x93 + 0x13 + + + + + + [Master] + headMix + 0xB0 + 0x13 + + + + + + [Channel1] + rate_temp_up + 0x90 + 0x14 + + + + + + [Channel2] + rate_temp_up + 0x91 + 0x14 + + + + + + [Channel3] + rate_temp_up + 0x92 + 0x14 + + + + + + [Channel4] + rate_temp_up + 0x93 + 0x14 + + + + + + [Master] + headGain + 0xB0 + 0x14 + + + + + + [Channel1] + djc4.toggleScratchMode + 0x90 + 0x15 + + + + + + [Channel2] + djc4.toggleScratchMode + 0x91 + 0x15 + + + + + + [Channel3] + djc4.toggleScratchMode + 0x92 + 0x15 + + + + + + [Channel4] + djc4.toggleScratchMode + 0x93 + 0x15 + + + + + + [Channel1] + bpm_tap + 0x90 + 0x16 + + + + + + [Channel2] + bpm_tap + 0x91 + 0x16 + + + + + + [Channel3] + bpm_tap + 0x92 + 0x16 + + + + + + [Channel4] + bpm_tap + 0x93 + 0x16 + + + + + + [Channel1] + cue_default + 0x90 + 0x17 + + + + + + [Channel2] + cue_default + 0x91 + 0x17 + + + + + + [Channel3] + cue_default + 0x92 + 0x17 + + + + + + [Channel4] + cue_default + 0x93 + 0x17 + + + + + + [Channel1] + play + 0x90 + 0x18 + + + + + + [Channel2] + play + 0x91 + 0x18 + + + + + + [Channel3] + play + 0x92 + 0x18 + + + + + + [Channel4] + play + 0x93 + 0x18 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter3 + 0x90 + 0x19 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter3 + 0x91 + 0x19 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + button_parameter3 + 0x92 + 0x19 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + button_parameter3 + 0x93 + 0x19 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter2 + 0x90 + 0x1A + + + + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter2 + 0x91 + 0x1A + + + + + + [EqualizerRack1_[Channel3]_Effect1] + button_parameter2 + 0x92 + 0x1A + + + + + + [EqualizerRack1_[Channel4]_Effect1] + button_parameter2 + 0x93 + 0x1A + + + + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter1 + 0x90 + 0x1B + + + + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter1 + 0x91 + 0x1B + + + + + + [EqualizerRack1_[Channel3]_Effect1] + button_parameter1 + 0x92 + 0x1B + + + + + + [EqualizerRack1_[Channel4]_Effect1] + button_parameter1 + 0x93 + 0x1B + + + + + + [Channel1] + pfl + 0x90 + 0x1C + + + + + + [Channel2] + pfl + 0x91 + 0x1C + + + + + + [Channel3] + pfl + 0x92 + 0x1C + + + + + + [Channel4] + pfl + 0x93 + 0x1C + + + + + + [EffectRack1_EffectUnit1] + group_[Channel1]_enable + 0x90 + 0x1E + + + + + + [EffectRack1_EffectUnit2] + group_[Channel2]_enable + 0x91 + 0x1E + + + + + + [EffectRack1_EffectUnit1] + group_[Channel3]_enable + 0x92 + 0x1E + + + + + + [EffectRack1_EffectUnit2] + group_[Channel4]_enable + 0x93 + 0x1E + + + + + + [EffectRack1_EffectUnit1_Effect1] + enabled + 0x90 + 0x1F + + + + + + [EffectRack1_EffectUnit2_Effect1] + enabled + 0x91 + 0x1F + + + + + + [EffectRack1_EffectUnit1_Effect1] + enabled + 0x92 + 0x1F + + + + + + [EffectRack1_EffectUnit2_Effect1] + enabled + 0x93 + 0x1F + + + + + + [EffectRack1_EffectUnit1_Effect2] + enabled + 0x90 + 0x20 + + + + + + [EffectRack1_EffectUnit2_Effect2] + enabled + 0x91 + 0x20 + + + + + + [EffectRack1_EffectUnit1_Effect2] + enabled + 0x92 + 0x20 + + + + + + [EffectRack1_EffectUnit2_Effect2] + enabled + 0x93 + 0x20 + + + + + + [Channel1] + djc4.wheelTurn + 0xB0 + 0x20 + + + + + + [Channel2] + djc4.wheelTurn + 0xB1 + 0x20 + + + + + + [Channel3] + djc4.wheelTurn + 0xB2 + 0x20 + + + + + + [Channel4] + djc4.wheelTurn + 0xB3 + 0x20 + + + + + + [EffectRack1_EffectUnit1_Effect3] + enabled + 0x90 + 0x21 + + + + + + [EffectRack1_EffectUnit2_Effect3] + enabled + 0x91 + 0x21 + + + + + + [EffectRack1_EffectUnit1_Effect3] + enabled + 0x92 + 0x21 + + + + + + [EffectRack1_EffectUnit2_Effect3] + enabled + 0x93 + 0x21 + + + + + + [Channel1] + LoadSelectedTrack + 0x90 + 0x22 + + + + + + [Channel3] + LoadSelectedTrack + 0x92 + 0x22 + + + + + + [Channel2] + LoadSelectedTrack + 0x91 + 0x23 + + + + + + [Channel4] + LoadSelectedTrack + 0x93 + 0x23 + + + + + + [QuickEffectRack1_[Channel1]] + super1 + 0xB0 + 0x24 + + + + + + [QuickEffectRack1_[Channel2]] + super1 + 0xB1 + 0x24 + + + + + + [QuickEffectRack1_[Channel3]] + super1 + 0xB2 + 0x24 + + + + + + [QuickEffectRack1_[Channel4]] + super1 + 0xB3 + 0x24 + + + + + + [EffectRack1_EffectUnit1] + mix + 0xB0 + 0x25 + + + + + + [EffectRack1_EffectUnit2] + mix + 0xB1 + 0x25 + + + + + + [EffectRack1_EffectUnit1] + mix + 0xB2 + 0x25 + + + + + + [EffectRack1_EffectUnit2] + mix + 0xB3 + 0x25 + + + + + + [Channel1] + djc4.wheelTouch + 0x90 + 0x26 + + + + + + [Channel2] + djc4.wheelTouch + 0x91 + 0x26 + + + + + + [Channel3] + djc4.wheelTouch + 0x92 + 0x26 + + + + + + [Channel4] + djc4.wheelTouch + 0x93 + 0x26 + + + + + + [Library] + MoveFocusForward + 0x90 + 0x27 + + + + + + [Library] + djc4.browseScroll + 0xB0 + 0x2C + + + + + + [Channel1] + loop_in_goto + 0x90 + 0x36 + + + + + + [Channel2] + loop_in_goto + 0x91 + 0x36 + + + + + + [Channel3] + loop_in_goto + 0x92 + 0x36 + + + + + + [Channel4] + loop_in_goto + 0x93 + 0x36 + + + + + + [Channel1] + loop_out_goto + 0x90 + 0x37 + + + + + + [Channel2] + loop_out_goto + 0x91 + 0x37 + + + + + + [Channel3] + loop_out_goto + 0x92 + 0x37 + + + + + + [Channel4] + loop_out_goto + 0x93 + 0x37 + + + + + + [Channel1] + reloop_andstop + 0x90 + 0x38 + + + + + + [Channel2] + reloop_andstop + 0x91 + 0x38 + + + + + + [Channel3] + reloop_andstop + 0x92 + 0x38 + + + + + + [Channel4] + reloop_andstop + 0x93 + 0x38 + + + + + + [Channel1] + hotcue_1_clear + 0x90 + 0x3A + + + + + + [Channel2] + hotcue_1_clear + 0x91 + 0x3A + + + + + + [Channel3] + hotcue_1_clear + 0x92 + 0x3A + + + + + + [Channel4] + hotcue_1_clear + 0x93 + 0x3A + + + + + + [Channel1] + hotcue_2_clear + 0x90 + 0x3B + + + + + + [Channel2] + hotcue_2_clear + 0x91 + 0x3B + + + + + + [Channel3] + hotcue_2_clear + 0x92 + 0x3B + + + + + + [Channel4] + hotcue_2_clear + 0x93 + 0x3B + + + + + + [Channel1] + hotcue_3_clear + 0x90 + 0x3C + + + + + + [Channel2] + hotcue_3_clear + 0x91 + 0x3C + + + + + + [Channel3] + hotcue_3_clear + 0x92 + 0x3C + + + + + + [Channel4] + hotcue_3_clear + 0x93 + 0x3C + + + + + + [Channel1] + hotcue_4_clear + 0x90 + 0x3D + + + + + + [Channel2] + hotcue_4_clear + 0x91 + 0x3D + + + + + + [Channel3] + hotcue_4_clear + 0x92 + 0x3D + + + + + + [Channel4] + hotcue_4_clear + 0x93 + 0x3D + + + + + + [Sampler1] + cue_default + 0x90 + 0x3E + + + + + + [Sampler5] + cue_default + 0x91 + 0x3E + + + + + + [Sampler1] + cue_default + 0x92 + 0x3E + + + + + + [Sampler5] + cue_default + 0x93 + 0x3E + + + + + + [Sampler2] + cue_default + 0x90 + 0x3F + + + + + + [Sampler6] + cue_default + 0x91 + 0x3F + + + + + + [Sampler2] + cue_default + 0x92 + 0x3F + + + + + + [Sampler6] + cue_default + 0x93 + 0x3F + + + + + + [Sampler3] + cue_default + 0x90 + 0x40 + + + + + + [Sampler7] + cue_default + 0x91 + 0x40 + + + + + + [Sampler3] + cue_default + 0x92 + 0x40 + + + + + + [Sampler7] + cue_default + 0x93 + 0x40 + + + + + + [Sampler4] + cue_default + 0x90 + 0x41 + + + + + + [Sampler8] + cue_default + 0x91 + 0x41 + + + + + + [Sampler4] + cue_default + 0x92 + 0x41 + + + + + + [Sampler8] + cue_default + 0x93 + 0x41 + + + + + + [Channel1] + quantize + 0x90 + 0x42 + + + + + + [Channel2] + quantize + 0x91 + 0x42 + + + + + + [Channel3] + quantize + 0x92 + 0x42 + + + + + + [Channel4] + quantize + 0x93 + 0x42 + + + + + + [Channel1] + reverse + 0x90 + 0x44 + + + + + + [Channel2] + reverse + 0x91 + 0x44 + + + + + + [Channel3] + reverse + 0x92 + 0x44 + + + + + + [Channel4] + reverse + 0x93 + 0x44 + + + + + + [Channel1] + cue_gotoandstop + 0x90 + 0x49 + + + + + + [Channel2] + cue_gotoandstop + 0x91 + 0x49 + + + + + + [Channel3] + cue_gotoandstop + 0x92 + 0x49 + + + + + + [Channel4] + cue_gotoandstop + 0x93 + 0x49 + + + + + + [Channel1] + cue_set + 0x90 + 0x4A + + + + + + [Channel2] + cue_set + 0x91 + 0x4A + + + + + + [Channel3] + cue_set + 0x92 + 0x4A + + + + + + [Channel4] + cue_set + 0x93 + 0x4A + + + + + + [QuickEffectRack1_[Channel1]_Effect1] + enabled + 0x90 + 0x4D + + + + + + [QuickEffectRack1_[Channel2]_Effect1] + enabled + 0x91 + 0x4D + + + + + + [QuickEffectRack1_[Channel3]_Effect1] + enabled + 0x92 + 0x4D + + + + + + [QuickEffectRack1_[Channel4]_Effect1] + enabled + 0x93 + 0x4D + + + + + + [Playlist] + djc4.browsePrevPlaylist + 0x90 + 0x54 + + + + + + [Playlist] + djc4.browseNextPlaylist + 0x91 + 0x55 + + + + + + [Library] + MoveFocusBackward + 0x90 + 0x59 + + + + + + [Channel1] + rate + 0xE0 + + + + + + [Channel2] + rate + 0xE1 + + + + + + [Channel3] + rate + 0xE2 + + + + + + [Channel4] + rate + 0xE3 + + + + + + + + From 12ed84d360f6237ef6e7210d4bcf45388e7b575c Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 20 Mar 2020 14:11:10 +0100 Subject: [PATCH 031/393] widget/woverview: Show "passthrough" on Waveform Overviews if enabled --- src/widget/woverview.cpp | 25 +++++++++++++++++++++++++ src/widget/woverview.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 4fb65c6fbfec..f5e15ba2b0b0 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -54,6 +54,7 @@ WOverview::WOverview( m_group(group), m_pConfig(pConfig), m_endOfTrack(false), + m_bPassthroughEnabled(false), m_pCueMenuPopup(std::make_unique(pConfig, this)), m_bShowCueTimes(true), m_iPosSeconds(0), @@ -80,6 +81,9 @@ WOverview::WOverview( m_trackSamplesControl = new ControlProxy(m_group, "track_samples", this); m_playpositionControl = new ControlProxy(m_group, "playposition", this); + m_pPassthroughControl = + new ControlProxy(m_group, "passthrough", this); + m_pPassthroughControl->connectValueChanged(this, &WOverview::onPassthroughChange); setAcceptDrops(true); setMouseTracking(true); @@ -236,6 +240,12 @@ void WOverview::onConnectedControlChanged(double dParameter, double dValue) { void WOverview::slotWaveformSummaryUpdated() { //qDebug() << "WOverview::slotWaveformSummaryUpdated()"; + + // Do not draw the waveform when passthrough is enabled + if (m_bPassthroughEnabled) { + return; + } + TrackPointer pTrack(m_pCurrentTrack); if (!pTrack) { return; @@ -342,6 +352,16 @@ void WOverview::onRateRatioChange(double v) { update(); } +void WOverview::onPassthroughChange(double v) { + m_bPassthroughEnabled = static_cast(v); + + if (m_bPassthroughEnabled) { + update(); + } else { + slotWaveformSummaryUpdated(); + } +} + void WOverview::updateCues(const QList &loadedCues) { m_marksToRender.clear(); for (CuePointer currentCue: loadedCues) { @@ -540,6 +560,11 @@ void WOverview::paintEvent(QPaintEvent* pEvent) { painter.drawPixmap(rect(), m_backgroundPixmap); } + if (m_bPassthroughEnabled) { + paintText(tr("Passthrough"), &painter); + return; + } + if (m_pCurrentTrack) { // Refer to util/ScopePainter.h to understand the semantics of // ScopePainter. diff --git a/src/widget/woverview.h b/src/widget/woverview.h index 25ce680bd181..a8831973eae9 100644 --- a/src/widget/woverview.h +++ b/src/widget/woverview.h @@ -98,6 +98,7 @@ class WOverview : public WWidget, public TrackDropTarget { void onMarkChanged(double v); void onMarkRangeChange(double v); void onRateRatioChange(double v); + void onPassthroughChange(double v); void receiveCuesUpdated(); void slotWaveformSummaryUpdated(); @@ -132,10 +133,12 @@ class WOverview : public WWidget, public TrackDropTarget { UserSettingsPointer m_pConfig; ControlProxy* m_endOfTrackControl; bool m_endOfTrack; + bool m_bPassthroughEnabled; ControlProxy* m_pRateRatioControl; ControlProxy* m_trackSampleRateControl; ControlProxy* m_trackSamplesControl; ControlProxy* m_playpositionControl; + ControlProxy* m_pPassthroughControl; // Current active track TrackPointer m_pCurrentTrack; From 9624e5c387ee81a2064529a1964210a034b37035 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 23 Mar 2020 11:18:58 +0100 Subject: [PATCH 032/393] widget/woverview: Fix disappearing passthru overview on skin change --- src/widget/woverview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index f5e15ba2b0b0..f7b01949ef1b 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -84,6 +84,8 @@ WOverview::WOverview( m_pPassthroughControl = new ControlProxy(m_group, "passthrough", this); m_pPassthroughControl->connectValueChanged(this, &WOverview::onPassthroughChange); + onPassthroughChange(m_pPassthroughControl->get()); + setAcceptDrops(true); setMouseTracking(true); From c28fcd654c546584a5f5934d1142d43357c27f46 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 23 Mar 2020 17:17:48 +0100 Subject: [PATCH 033/393] Prevent web network tasks from becoming parented --- src/musicbrainz/tagfetcher.cpp | 26 +++++++++---------- src/musicbrainz/tagfetcher.h | 6 ++--- src/musicbrainz/web/acoustidlookuptask.cpp | 6 ++--- src/musicbrainz/web/acoustidlookuptask.h | 3 +-- .../web/musicbrainzrecordingstask.cpp | 6 ++--- .../web/musicbrainzrecordingstask.h | 3 +-- src/network/jsonwebtask.cpp | 5 ++-- src/network/jsonwebtask.h | 3 +-- src/network/webtask.cpp | 6 ++--- src/network/webtask.h | 15 +++++++++-- 10 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp index 093c940dfdd2..18e3ef75ccfb 100644 --- a/src/musicbrainz/tagfetcher.cpp +++ b/src/musicbrainz/tagfetcher.cpp @@ -38,7 +38,7 @@ void TagFetcher::startFetch( void TagFetcher::abortAcoustIdTask() { if (m_pAcoustIdTask) { - disconnect(m_pAcoustIdTask.get()); + disconnect(m_pAcoustIdTask); m_pAcoustIdTask->deleteBeforeFinished(); m_pAcoustIdTask = nullptr; } @@ -46,7 +46,7 @@ void TagFetcher::abortAcoustIdTask() { void TagFetcher::abortMusicBrainzTask() { if (m_pMusicBrainzTask) { - disconnect(m_pMusicBrainzTask.get()); + disconnect(m_pMusicBrainzTask); m_pMusicBrainzTask->deleteBeforeFinished(); m_pMusicBrainzTask = nullptr; } @@ -78,20 +78,19 @@ void TagFetcher::slotFingerprintReady() { emit fetchProgress(tr("Identifying track through Acoustid")); DEBUG_ASSERT(!m_pAcoustIdTask); - m_pAcoustIdTask = make_parented( + m_pAcoustIdTask = new mixxx::AcoustIdLookupTask( &m_network, fingerprint, - m_pTrack->getDurationInt(), - this); - connect(m_pAcoustIdTask.get(), + m_pTrack->getDurationInt()); + connect(m_pAcoustIdTask, &mixxx::AcoustIdLookupTask::succeeded, this, &TagFetcher::slotAcoustIdTaskSucceeded); - connect(m_pAcoustIdTask.get(), + connect(m_pAcoustIdTask, &mixxx::AcoustIdLookupTask::failed, this, &TagFetcher::slotAcoustIdTaskFailed); - connect(m_pAcoustIdTask.get(), + connect(m_pAcoustIdTask, &mixxx::AcoustIdLookupTask::networkError, this, &TagFetcher::slotAcoustIdTaskNetworkError); @@ -115,19 +114,18 @@ void TagFetcher::slotAcoustIdTaskSucceeded( emit fetchProgress(tr("Retrieving metadata from MusicBrainz")); DEBUG_ASSERT(!m_pMusicBrainzTask); - m_pMusicBrainzTask = make_parented( + m_pMusicBrainzTask = new mixxx::MusicBrainzRecordingsTask( &m_network, - std::move(recordingIds), - this); - connect(m_pMusicBrainzTask.get(), + std::move(recordingIds)); + connect(m_pMusicBrainzTask, &mixxx::MusicBrainzRecordingsTask::succeeded, this, &TagFetcher::slotMusicBrainzTaskSucceeded); - connect(m_pMusicBrainzTask.get(), + connect(m_pMusicBrainzTask, &mixxx::MusicBrainzRecordingsTask::failed, this, &TagFetcher::slotMusicBrainzTaskFailed); - connect(m_pMusicBrainzTask.get(), + connect(m_pMusicBrainzTask, &mixxx::MusicBrainzRecordingsTask::networkError, this, &TagFetcher::slotMusicBrainzTaskNetworkError); diff --git a/src/musicbrainz/tagfetcher.h b/src/musicbrainz/tagfetcher.h index d0f14ad1edc3..e10e184d2377 100644 --- a/src/musicbrainz/tagfetcher.h +++ b/src/musicbrainz/tagfetcher.h @@ -2,11 +2,11 @@ #include #include +#include #include "musicbrainz/web/acoustidlookuptask.h" #include "musicbrainz/web/musicbrainzrecordingstask.h" #include "track/track.h" -#include "util/parented_ptr.h" class TagFetcher : public QObject { Q_OBJECT @@ -71,9 +71,9 @@ class TagFetcher : public QObject { QFutureWatcher m_fingerprintWatcher; - parented_ptr m_pAcoustIdTask; + QPointer m_pAcoustIdTask; - parented_ptr m_pMusicBrainzTask; + QPointer m_pMusicBrainzTask; TrackPointer m_pTrack; }; diff --git a/src/musicbrainz/web/acoustidlookuptask.cpp b/src/musicbrainz/web/acoustidlookuptask.cpp index 57a11fe3aa25..6f8a2acdfd75 100644 --- a/src/musicbrainz/web/acoustidlookuptask.cpp +++ b/src/musicbrainz/web/acoustidlookuptask.cpp @@ -67,13 +67,11 @@ network::JsonWebRequest lookupRequest() { AcoustIdLookupTask::AcoustIdLookupTask( QNetworkAccessManager* networkAccessManager, const QString& fingerprint, - int duration, - QObject* parent) + int duration) : network::JsonWebTask( networkAccessManager, kBaseUrl, - lookupRequest(), - parent), + lookupRequest()), m_urlQuery(lookupUrlQuery(fingerprint, duration)) { } diff --git a/src/musicbrainz/web/acoustidlookuptask.h b/src/musicbrainz/web/acoustidlookuptask.h index e8d0a2cce67e..34dc45749166 100644 --- a/src/musicbrainz/web/acoustidlookuptask.h +++ b/src/musicbrainz/web/acoustidlookuptask.h @@ -15,8 +15,7 @@ class AcoustIdLookupTask : public network::JsonWebTask { AcoustIdLookupTask( QNetworkAccessManager* networkAccessManager, const QString& fingerprint, - int duration, - QObject* parent = nullptr); + int duration); ~AcoustIdLookupTask() override = default; signals: diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp index 7c6d1d7df1bd..ff70cfe4bfde 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp +++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp @@ -65,11 +65,9 @@ QNetworkRequest createNetworkRequest( MusicBrainzRecordingsTask::MusicBrainzRecordingsTask( QNetworkAccessManager* networkAccessManager, - QList&& recordingIds, - QObject* parent) + QList&& recordingIds) : network::WebTask( - networkAccessManager, - parent), + networkAccessManager), m_queuedRecordingIds(std::move(recordingIds)), m_parentTimeoutMillis(0) { musicbrainz::registerMetaTypesOnce(); diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.h b/src/musicbrainz/web/musicbrainzrecordingstask.h index a81f89230500..fab025596c90 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.h +++ b/src/musicbrainz/web/musicbrainzrecordingstask.h @@ -17,8 +17,7 @@ class MusicBrainzRecordingsTask : public network::WebTask { public: MusicBrainzRecordingsTask( QNetworkAccessManager* networkAccessManager, - QList&& recordingIds, - QObject* parent = nullptr); + QList&& recordingIds); ~MusicBrainzRecordingsTask() override = default; signals: diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index 08530f664942..57a8aa195a6f 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -87,9 +87,8 @@ QNetworkRequest newRequest( JsonWebTask::JsonWebTask( QNetworkAccessManager* networkAccessManager, QUrl baseUrl, - JsonWebRequest request, - QObject* parent) - : WebTask(networkAccessManager, parent), + JsonWebRequest request) + : WebTask(networkAccessManager), m_baseUrl(std::move(baseUrl)), m_request(std::move(request)), m_pendingNetworkReply(nullptr) { diff --git a/src/network/jsonwebtask.h b/src/network/jsonwebtask.h index 1a7fb0927aa4..b7d8a791383c 100644 --- a/src/network/jsonwebtask.h +++ b/src/network/jsonwebtask.h @@ -53,8 +53,7 @@ class JsonWebTask : public WebTask { JsonWebTask( QNetworkAccessManager* networkAccessManager, QUrl baseUrl, - JsonWebRequest request, - QObject* parent = nullptr); + JsonWebRequest request); ~JsonWebTask() override; signals: diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index c1328ab3989f..0cbf129c46f6 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -59,10 +59,8 @@ bool readStatusCode( } WebTask::WebTask( - QNetworkAccessManager* networkAccessManager, - QObject* parent) - : QObject(parent), - m_networkAccessManager(networkAccessManager), + QNetworkAccessManager* networkAccessManager) + : m_networkAccessManager(networkAccessManager), m_timeoutTimerId(kInvalidTimerId), m_abort(false) { std::call_once(registerMetaTypesOnceFlag, registerMetaTypesOnce); diff --git a/src/network/webtask.h b/src/network/webtask.h index 03abcc603fd1..358f4fed57f6 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -81,13 +81,24 @@ struct CustomWebResponse : public WebResponse { QByteArray content; }; +// A transient task for performing a single HTTP network request +// asynchronously. +// +// The results are transmitted by emitting signals. Only a single +// receiver can be connected to each signal by using Qt::UniqueConnection. +// The receiver of the signal is responsible for destroying the task +// by invoking QObject::deleteLater(). If no receiver is connected to +// a signal the task will destroy itself. +// +// Instances of this class must not be parented due to their built-in +// self-destruction mechanism. All pointers to tasks should be wrapped +// into QPointer. Otherwise plain pointers might become dangling! class WebTask : public QObject { Q_OBJECT public: explicit WebTask( - QNetworkAccessManager* networkAccessManager, - QObject* parent = nullptr); + QNetworkAccessManager* networkAccessManager); ~WebTask() override; // timeoutMillis <= 0: No timeout (unlimited) From f9264c3f4921064ddd4823048bacfd6589b803e3 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 23 Mar 2020 23:21:55 +0100 Subject: [PATCH 034/393] Handle QNetworkReply::error signals --- src/musicbrainz/web/musicbrainzrecordingstask.cpp | 6 ++++++ src/network/jsonwebtask.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp index ff70cfe4bfde..cf524d918cc9 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp +++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp @@ -115,6 +115,12 @@ bool MusicBrainzRecordingsTask::doStart( &MusicBrainzRecordingsTask::slotNetworkReplyFinished, Qt::UniqueConnection); + connect(m_pendingNetworkReply, + QOverload::of(&QNetworkReply::error), + this, + &MusicBrainzRecordingsTask::slotNetworkReplyFinished, + Qt::UniqueConnection); + return true; } diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index 57a8aa195a6f..ead3d296966b 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -236,6 +236,12 @@ bool JsonWebTask::doStart( &JsonWebTask::slotNetworkReplyFinished, Qt::UniqueConnection); + connect(m_pendingNetworkReply, + QOverload::of(&QNetworkReply::error), + this, + &JsonWebTask::slotNetworkReplyFinished, + Qt::UniqueConnection); + return true; } From 785e7112ad450658905b18d22bbd5a19a48d6a0f Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 23 Mar 2020 23:28:59 +0100 Subject: [PATCH 035/393] Fix disconnect/connect when loading new tracks for lookup --- src/library/dlgtagfetcher.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/library/dlgtagfetcher.cpp b/src/library/dlgtagfetcher.cpp index 0b6b682064a6..629ecf68fd73 100644 --- a/src/library/dlgtagfetcher.cpp +++ b/src/library/dlgtagfetcher.cpp @@ -84,11 +84,11 @@ void DlgTagFetcher::init() { } void DlgTagFetcher::loadTrack(const TrackPointer& track) { - if (track == NULL) { + if (!track) { return; } results->clear(); - disconnect(track.get(), + disconnect(m_track.get(), &Track::changed, this, &DlgTagFetcher::slotTrackChanged); @@ -96,13 +96,14 @@ void DlgTagFetcher::loadTrack(const TrackPointer& track) { m_track = track; m_data = Data(); m_networkResult = NetworkResult::Ok; - m_tagFetcher.startFetch(m_track); - connect(track.get(), + connect(m_track.get(), &Track::changed, this, &DlgTagFetcher::slotTrackChanged); + m_tagFetcher.startFetch(m_track); + updateStack(); } From 6341398788beb2dc561f8ee1da5b749cb420ded3 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 23 Mar 2020 23:29:52 +0100 Subject: [PATCH 036/393] Handle abort signals of AcoustId and MusicBrainz tasks --- src/musicbrainz/tagfetcher.cpp | 16 ++++++++++++++++ src/musicbrainz/tagfetcher.h | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp index 18e3ef75ccfb..1435aa2fa503 100644 --- a/src/musicbrainz/tagfetcher.cpp +++ b/src/musicbrainz/tagfetcher.cpp @@ -90,6 +90,10 @@ void TagFetcher::slotFingerprintReady() { &mixxx::AcoustIdLookupTask::failed, this, &TagFetcher::slotAcoustIdTaskFailed); + connect(m_pAcoustIdTask, + &mixxx::AcoustIdLookupTask::aborted, + this, + &TagFetcher::slotAcoustIdTaskAborted); connect(m_pAcoustIdTask, &mixxx::AcoustIdLookupTask::networkError, this, @@ -125,6 +129,10 @@ void TagFetcher::slotAcoustIdTaskSucceeded( &mixxx::MusicBrainzRecordingsTask::failed, this, &TagFetcher::slotMusicBrainzTaskFailed); + connect(m_pMusicBrainzTask, + &mixxx::MusicBrainzRecordingsTask::aborted, + this, + &TagFetcher::slotMusicBrainzTaskAborted); connect(m_pMusicBrainzTask, &mixxx::MusicBrainzRecordingsTask::networkError, this, @@ -143,6 +151,10 @@ void TagFetcher::slotAcoustIdTaskFailed( -1); } +void TagFetcher::slotAcoustIdTaskAborted() { + abortAcoustIdTask(); +} + void TagFetcher::slotAcoustIdTaskNetworkError( QUrl requestUrl, QNetworkReply::NetworkError errorCode, @@ -158,6 +170,10 @@ void TagFetcher::slotAcoustIdTaskNetworkError( errorCode); } +void TagFetcher::slotMusicBrainzTaskAborted() { + abortMusicBrainzTask(); +} + void TagFetcher::slotMusicBrainzTaskNetworkError( QUrl requestUrl, QNetworkReply::NetworkError errorCode, diff --git a/src/musicbrainz/tagfetcher.h b/src/musicbrainz/tagfetcher.h index e10e184d2377..622cebe11cfa 100644 --- a/src/musicbrainz/tagfetcher.h +++ b/src/musicbrainz/tagfetcher.h @@ -45,6 +45,7 @@ class TagFetcher : public QObject { QList recordingIds); void slotAcoustIdTaskFailed( mixxx::network::JsonWebResponse response); + void slotAcoustIdTaskAborted(); void slotAcoustIdTaskNetworkError( QUrl requestUrl, QNetworkReply::NetworkError errorCode, @@ -57,6 +58,7 @@ class TagFetcher : public QObject { mixxx::network::WebResponse response, int errorCode, QString errorMessage); + void slotMusicBrainzTaskAborted(); void slotMusicBrainzTaskNetworkError( QUrl requestUrl, QNetworkReply::NetworkError errorCode, From 303c0bc75cd1d5cb14e2a24d0c0bcc5d0b9ba16a Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 23 Mar 2020 23:30:34 +0100 Subject: [PATCH 037/393] Signal failure to parse XML reponse --- src/musicbrainz/web/musicbrainzrecordingstask.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp index cf524d918cc9..4c4f7a7a4f83 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp +++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp @@ -179,7 +179,12 @@ void MusicBrainzRecordingsTask::slotNetworkReplyFinished() { if (!recordingsResult.second) { kLogger.warning() << "Failed to parse XML response"; - slotAbort(); + emitFailed( + network::WebResponse( + networkReply->url(), + statusCode), + -1, + "Failed to parse XML response"); return; } From db450a11e37b6f48e0f0fab64839f6b15cefda92 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 24 Mar 2020 09:53:27 +0100 Subject: [PATCH 038/393] Yet another backport for Xenial --- src/network/jsonwebtask.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index ead3d296966b..af9b084c0aab 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -11,6 +11,9 @@ #include // std::once_flag #include "util/assert.h" +#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) +#include "util/compatibility.h" +#endif #include "util/counter.h" #include "util/logger.h" @@ -237,7 +240,11 @@ bool JsonWebTask::doStart( Qt::UniqueConnection); connect(m_pendingNetworkReply, +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + qOverload(&QNetworkReply::error), +#else QOverload::of(&QNetworkReply::error), +#endif this, &JsonWebTask::slotNetworkReplyFinished, Qt::UniqueConnection); From a32808fe0a2204ca7b657aeb14d3fad259fd60af Mon Sep 17 00:00:00 2001 From: Tobias Date: Fri, 13 Mar 2020 11:19:22 +0100 Subject: [PATCH 039/393] Denon MC7000 mapping --- res/controllers/Denon-MC7000-scripts.js | 839 +++++ res/controllers/Denon-MC7000.midi.xml | 4054 +++++++++++++++++++++++ 2 files changed, 4893 insertions(+) create mode 100644 res/controllers/Denon-MC7000-scripts.js create mode 100644 res/controllers/Denon-MC7000.midi.xml diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js new file mode 100644 index 000000000000..380f41d81bb8 --- /dev/null +++ b/res/controllers/Denon-MC7000-scripts.js @@ -0,0 +1,839 @@ +/** + * Denon DJ MC7000 DJ controller script for Mixxx 2.2.3 + * + * Started in Dec. 2019 by OsZ + * + * 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Before using the mapping please make sure your MC7000 controller works for + * your operating system. For Windows you need driver software by Denon, Mac users + * should be lucky as it shall work out-of-the-box. Linux users need to compile + * their own Kernel with modified /sound/usb/clock.c see the "Denon MC7000 Mapping" + * thread at https://www.mixxx.org/forums/viewtopic.php?f=7&t=13126 +**/ + +var MC7000 = {}; + +/*/////////////////////////////////// +// USER VARIABLES BEGIN // +///////////////////////////////////*/ + +// Wanna have Needle Search active while playing a track ? +// In any case Needle Search is available holding "SHIFT" down. +// can be true or false (recommended: false) +MC7000.needleSearchPlay = false; + +// Pitch Fader ranges to cycle through with the "RANGE" buttons. +var lowest = 4 ;// lowest value in % (default: 4) +var low = 6 ;// next value in % (default: 6) +var middle = 10 ;// next value in % (default: 10) +var high = 16 ;// next value in % (default: 16) +var highest = 24 ;// highest value in % (default: 24) + +// Platter Ring LED mode +// Mode 0 = Single "off" LED chase (all others "on") +// Mode 1 = Single "on" LED chase (all others "off") +// use "SHIFT" + "DECK #" to toggle between both modes +MC7000.modeSingleLED = 1 ;// default: 1 + +// Set Vinyl Mode on ("true") or off ("false") when MIXXX starts. +// This sets the Jog Wheel touch detection / Vinyl Mode +// and the Jog LEDs ("VINYL" on = spinny, "VINYL" off = track position). +MC7000.VinylModeOn = true ;// default: true + +// Scratch algorithm parameters +MC7000.scratchParams = { + recordSpeed: 33.3 ,// default: 33.3 + alpha: (1.0/10) ,// default: (1.0/10) + beta: (1.0/10)/32 // default: (1.0/10)/32 +}; + +// Sensitivity of the jog wheel (also depends on audio latency) +// lower values make it less, higher value more sensible +MC7000.jogParams = { + jogSensitivity: 30 ,// default: 30 + maxJogValue: 3 ,// default: 3 +}; + +/*///////////////////////////////// +// USER VARIABLES END // +/////////////////////////////////*/ + + +/* OTHER VARIABLES - DONT'T TOUCH EXCEPT YOU KNOW WHAT YOU DO */ + +// Resolution of the jog wheel, set so the spinny +// Jog LED to match exactly the movement of the Jog Wheel +// The physical resolution seams to be around 1100 +MC7000.jogWheelTicksPerRevolution = 894; + +// Pitch faders up and down values (see above for user input) +MC7000.posRateRanges = [lowest/100, low/100, middle/100, high/100, highest/100]; +MC7000.negRateRanges = [highest/100, high/100, middle/100, low/100, lowest/100]; + +// must be "true" for Needle Search to be active +MC7000.needleSearchTouched = [true, true, true, true]; + +// initial value for VINYL mode per Deck (see above for user input) +MC7000.isVinylMode = [MC7000.VinylModeOn, MC7000.VinylModeOn, MC7000.VinylModeOn, MC7000.VinylModeOn]; + +// initialize the "factor" function for Spinback +MC7000.factor = []; + +// initialize the PAD Mode to Hot Cue and all others off when starting +MC7000.PADModeCue = [true, true, true, true]; +MC7000.PADModeCueLoop = [false, false, false, false]; +MC7000.PADModeFlip = [false, false, false, false]; +MC7000.PADModeRoll = [false, false, false, false]; +MC7000.PADModeSavedLoop = [false, false, false, false]; +MC7000.PADModeSlicer = [false, false, false, false]; +MC7000.PADModeSlicerLoop = [false, false, false, false]; +MC7000.PADModeSampler = [false, false, false, false]; +MC7000.PADModeVelSamp = [false, false, false, false]; +MC7000.PADModePitch = [false, false, false, false]; + +// PAD Mode Colors +MC7000.padColor = { + 'alloff': 0x01, // typically not needed for PADs + 'hotcueoff': 0x02, // lightblue Hot Cue inactive + 'hotcueon': 0x04, // darkblue Hot Cue active + 'sampleroff': 0x27, // light pink Sampler standard colour + 'samplerloaded': 0x38, // dark pink Sampler loaded colour + 'samplerplay': 0x09, // green Sampler playing + 'rollon': 0x10, // BeatloopRoll active colour + 'rolloff': 0x1B, // BeatloopRoll off colour + 'cueloopon': 0x0D, // Cueloop colour for activated cue point + 'cueloopoff': 0x1A // Cueloop colour inactive +}; + +/* DECK INITIALIZATION */ +MC7000.init = function () { + + // Decks + MC7000.leftDeck = new MC7000.Deck(1, 3); + MC7000.rightDeck = new MC7000.Deck(2, 4); + + // set default Master Volume to give a little room for mixing + engine.setValue("[Master]", "gain", 0.85); + + // VU meters + engine.connectControl("[Channel1]", "VuMeter", "MC7000.VuMeter"); + engine.connectControl("[Channel2]", "VuMeter", "MC7000.VuMeter"); + engine.connectControl("[Channel3]", "VuMeter", "MC7000.VuMeter"); + engine.connectControl("[Channel4]", "VuMeter", "MC7000.VuMeter"); + + // Platter Ring LED + midi.sendShortMsg(0x90, 0x64, MC7000.modeSingleLED); + midi.sendShortMsg(0x91, 0x64, MC7000.modeSingleLED); + midi.sendShortMsg(0x92, 0x64, MC7000.modeSingleLED); + midi.sendShortMsg(0x93, 0x64, MC7000.modeSingleLED); + engine.connectControl("[Channel1]", "playposition", "MC7000.JogLed"); + engine.connectControl("[Channel2]", "playposition", "MC7000.JogLed"); + engine.connectControl("[Channel3]", "playposition", "MC7000.JogLed"); + engine.connectControl("[Channel4]", "playposition", "MC7000.JogLed"); + + // Vinyl mode LEDs + midi.sendShortMsg(0x90, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); + midi.sendShortMsg(0x91, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); + midi.sendShortMsg(0x92, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); + midi.sendShortMsg(0x93, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); + + // PAD Mode LEDs + for (var i = 1; i <= 8; i++) { + engine.connectControl("[Channel1]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); + engine.connectControl("[Channel2]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); + engine.connectControl("[Channel3]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); + engine.connectControl("[Channel4]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); + }; + + // Sampler Volume Control + MC7000.samplerLevel = function (channel, control, value, status, group) { + // check if the Sampler Volume is at Zero and if so hide the sampler bank + if (value > 0x00) { + engine.setValue("[Samplers]", "show_samplers", true); + } else { + engine.setValue("[Samplers]", "show_samplers", false); + }; + // get the Sampler Rows opened with its details + engine.setValue("[SamplerRow1]", "expanded", true); + engine.setValue("[SamplerRow2]", "expanded", true); + + //control up to 16 sampler volumes with the one knob on the mixer + for (var i = 1; i <= 16; i++) { + engine.setValue("[Sampler"+i+"]", "pregain", script.absoluteNonLin(value, 0, 1.0, 4.0)); + }; + }; + + // The SysEx message to send to the controller to force the midi controller + // to send the status of every item on the control surface. + var ControllerStatusSysex = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7]; + + // After midi controller receive this Outbound Message request SysEx Message, + // midi controller will send the status of every item on the + // control surface. (Mixxx will be initialized with current values) + midi.sendSysexMsg(ControllerStatusSysex, ControllerStatusSysex.length); +}; + +/* CONSTRUCTOR FOR DECK OBJECT */ +MC7000.Deck = function(channel) { + + // PAD Mode Hot Cue + MC7000.padModeCue = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + // set HotCue Mode true + MC7000.PADModeCue[deckNumber] = true; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + // change PAD color when switching to Hot Cue Mode + for (var i = 1; i <= 8; i++) { + if (engine.getValue(group, "hotcue_"+i+"_enabled", true)) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.hotcueon); + } else { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.hotcueoff); + }; + }; + }; + // PAD Mode Cue Loop + MC7000.padModeCueLoop = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = true; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + }; + }; + // PAD Mode Flip + MC7000.padModeFlip = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = true; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + }; + }; + // PAD Mode Roll + MC7000.padModeRoll = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = true; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.rolloff); + }; + }; + // PAD Mode Saved Loop + MC7000.padModeSavedLoop = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = true; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + }; + }; + // PAD Mode Slicer + MC7000.padModeSlicer = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = true; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + }; + }; + // PAD Mode Slicer Loop + MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = true; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + }; + }; + // PAD Mode Sampler + MC7000.padModeSampler = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = true; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + }; + // change PAD color when switching to Sampler Mode + for (var i = 1; i <= 8; i++) { + if(engine.getValue("[Sampler"+i+"]", "play")) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); + } + else if(engine.getValue("[Sampler"+i+"]", "track_loaded") === 0 ) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.sampleroff); + } + else if(engine.getValue("[Sampler"+i+"]", "track_loaded") === 1 + && engine.getValue("[Sampler"+i+"]", "play") === 0) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); + }; + }; + }; + // PAD Mode Velocity Sampler + MC7000.padModeVelSamp = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = true; + MC7000.PADModePitch[deckNumber] = false; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + }; + }; + // PAD Mode Slicer + MC7000.padModePitch = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = true; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = true; + }; + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + }; + }; + + // PAD buttons + MC7000.PadButtons = function (channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + + // activate and clear Hot Cues + if (MC7000.PADModeCue[deckNumber] && engine.getValue(group, "track_loaded") === 1) { + for (var i = 1; i <= 8; i++) { + if (control === 0x14 + i -1 && value >= 0x01) { + engine.setValue(group, "hotcue_"+i+"_activate", true); + } else { + engine.setValue(group, "hotcue_"+i+"_activate", false); + }; + if (control === 0x1C + i -1 && value >= 0x01) { + engine.setValue(group, "hotcue_"+i+"_clear", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.hotcueoff); + }; + }; + } + // Cue Loop + else if (MC7000.PADModeFlip[deckNumber]) { + return; + } + // Flip + else if (MC7000.PADModeFlip[deckNumber]) { + return; + } + // Roll + else if (MC7000.PADModeRoll[deckNumber]) { + if (control === 0x14 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.0625_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.rollon); + } + else if (control === 0x14 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.0625_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.rolloff); + } + else if (control === 0x15 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.125_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.rollon); + } + else if (control === 0x15 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.125_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.rolloff); + } + else if (control === 0x16 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.25_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rollon); + } + else if (control === 0x16 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.25_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rolloff); + } + else if (control === 0x17 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.5_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rollon); + } + else if (control === 0x17 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.5_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rolloff); + } + else if (control === 0x18 && value >= 0x01) { + engine.setValue(group, "beatlooproll_1_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.rollon); + } + else if (control === 0x18 && value >= 0x00) { + engine.setValue(group, "beatlooproll_1_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.rolloff); + } + else if (control === 0x19 && value >= 0x01) { + engine.setValue(group, "beatlooproll_2_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.rollon); + } + else if (control === 0x19 && value >= 0x00) { + engine.setValue(group, "beatlooproll_2_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.rolloff); + } + else if (control === 0x1A && value >= 0x01) { + engine.setValue(group, "beatlooproll_4_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.rollon); + } + else if (control === 0x1A && value >= 0x00) { + engine.setValue(group, "beatlooproll_4_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.rolloff); + } + else if (control === 0x1B && value >= 0x01) { + engine.setValue(group, "beatlooproll_8_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.rollon); + } + else if (control === 0x1B && value >= 0x00) { + engine.setValue(group, "beatlooproll_8_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.rolloff); + } + } + // Saved Loop + else if (MC7000.PADModeSavedLoop[deckNumber]) { + return; + } + // Slicer + else if (MC7000.PADModeSlicer[deckNumber]) { + return; + } + // Slicer Loop + else if (MC7000.PADModeSlicerLoop[deckNumber]) { + return; + } + // Sampler 1 - 8 + else if (MC7000.PADModeSampler[deckNumber]) { + for (var i = 1; i <= 8; i++) { + if (control === 0x14 + i -1 && value >= 0x01) { + // 1st - check if track is loaded + if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { + engine.setValue("[Sampler"+i+"]", "LoadSelectedTrack", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); + } + // 2nd - if track is playing then stop it + else if(engine.getValue("[Sampler"+i+"]", "play") === 1) { + engine.setValue("[Sampler"+i+"]", "start_stop", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); + } + // 3rd - if track is loaded but not playing + else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1) { + + engine.setValue("[Sampler"+i+"]", "start_play", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); + // var samplerlength = engine.getValue("[Sampler"+i+"]", "duration"); + + } + } + else if (control === 0x1C + i -1 && value >= 0x01) { + engine.setValue("[Sampler"+i+"]", "play", 0); + engine.setValue("[Sampler"+i+"]", "eject", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i -1, MC7000.padColor.sampleroff); + engine.setValue("[Sampler"+i+"]", "eject", 0); + }; + }; + // TODO: check for the actual status of LEDs again on other decks + } + // Velocity Sampler + else if (MC7000.PADModeVelSamp[deckNumber]) { + return; + } + // Pitch + else if (MC7000.PADModePitch[deckNumber]) { + return; + } + }; + + // Toggle Vinyl Mode + MC7000.vinylModeToggle = function(channel, control, value, status, group) { + if (value === 0x00) return; // don't respond to note off messages + + if (value === 0x7F) { + var deckNumber = script.deckFromGroup(group); + MC7000.isVinylMode[deckNumber] = !MC7000.isVinylMode[deckNumber]; + midi.sendShortMsg(0x90 + channel, 0x07, MC7000.isVinylMode[deckNumber] ? 0x7F: 0x01); + }; + }; + + // The button that enables/disables scratching + MC7000.wheelTouch = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (MC7000.isVinylMode[deckNumber]) { + if (value === 0x7F) { + engine.scratchEnable(deckNumber, MC7000.jogWheelTicksPerRevolution, MC7000.scratchParams.recordSpeed, MC7000.scratchParams.alpha, MC7000.scratchParams.beta); + } else { + engine.scratchDisable(deckNumber); + } + } + }; + + // The wheel that actually controls the scratching + MC7000.wheelTurn = function(channel, control, value, status, group) { + + // A: For a control that centers on 0: + var numTicks = (value < 0x64) ? value: (value - 128); + var deckNumber = script.deckFromGroup(group); + if (engine.isScratching(deckNumber)) { + // Scratch! + engine.scratchTick(deckNumber, numTicks); + } else { + // Pitch bend + var jogDelta = numTicks/MC7000.jogWheelTicksPerRevolution*MC7000.jogParams.jogSensitivity; + var jogAbsolute = jogDelta + engine.getValue(group, "jog"); + engine.setValue(group, 'jog', Math.max(-MC7000.jogParams.maxJogValue, Math.min(MC7000.jogParams.maxJogValue, jogAbsolute))); + } + }; + + // Needle Search Touch detection + MC7000.needleSearchTouch = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (engine.getValue(group, "play")) { + MC7000.needleSearchTouched[deckNumber] = MC7000.needleSearchPlay && (value ? true : false); + } else { + MC7000.needleSearchTouched[deckNumber] = value ? true : false; + } + }; + + // Needle Search Touch while "SHIFT" button is pressed + MC7000.needleSearchTouchShift = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + MC7000.needleSearchTouched[deckNumber] = value ? true : false; + }; + + // Needle Search Position detection (LSB) + MC7000.needleSearchLSB = function(channel, control, value, status, group) { + MC7000.needleDropLSB = value; // just defining rough position + }; + + // Needle Search Position detection (LSB + MSB) + MC7000.needleSearchStripPosition = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (MC7000.needleSearchTouched[deckNumber]) { + var fullValue = (MC7000.needleDropLSB << 7) + value; // move LSB 7 binary gigits to the left and add MSB + var position = (fullValue / 0x3FFF); // devide by all possible positions to get relative between 0 - 1 + engine.setParameter(group, "playposition", position); + } + }; + + // Pitch Fader (LSB) + MC7000.pitchFaderLSB = function(channel, control, value, status, group) { + MC7000.pitchLSB = value; // just defining rough position + }; + + // Pitch Fader Position (LSB + MSB) + MC7000.pitchFaderPosition = function(channel, control, value, status, group) { + var fullValue = (MC7000.pitchLSB << 7) + value; + var position = 1 - (fullValue / 0x3FFF); // 1 - () to turn around the direction + engine.setParameter(group, "rate", position); + }; + + // Next Rate range toggle + MC7000.nextRateRange = function(midichan, control, value, status, group) { + if (value === 0) return; // don't respond to note off messages + var currRateRange = engine.getValue(group, "rateRange"); + engine.setValue(group, "rateRange", MC7000.getNextRateRange(currRateRange)); + }; + + // Previous Rate range toggle + MC7000.prevRateRange = function(midichan, control, value, status, group) { + if (value === 0) return; // don't respond to note off messages + var currRateRange = engine.getValue(group, "rateRange"); + engine.setValue(group, "rateRange", MC7000.getPrevRateRange(currRateRange)); + }; + + // Key Select + MC7000.keySelect = function(midichan, control, value, status, group) { + if (value === 0x01) { + engine.setValue(group, "pitch_up", true); + } + else if (value === 0x7F) { + engine.setValue(group, "pitch_down", true); + } + }; + + // Assign Channel to Crossfader + MC7000.crossfaderAssign = function(channel, control, value, status, group) { + // Centre position + if (value === 0x00) { + engine.setValue(group, "orientation", 1); + } + // Left position + else if (value === 0x01) { + engine.setValue(group, "orientation", 0); + } + // Right position + else if (value === 0x02) { + engine.setValue(group, "orientation", 2); + } + }; + + // Assign Spinback length to STOP TIME knob + MC7000.stopTime = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + // "factor" for engine.brake() + // this formula produces factors between 31 (min STOP TIME for ca 7 sec back in track) + // and 1 (max STOP TIME for ca 18.0 sec back in track) + MC7000.factor[deckNumber] = (1.1 - (value / 127)) * 30 - 2; + }; + + // Use the CENSOR button as Spinback with STOP TIME adjusted length + MC7000.brake_button = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + var deck = parseInt(group.substring(8,9)); // work out which deck we are using + engine.brake(deck, value > 0, MC7000.factor[deckNumber], - 15); // start at a rate of -15 and decrease by "factor" + }; +}; + +/* SET CROSSFADER CURVE */ +MC7000.crossFaderCurve = function (control, value) { + script.crossfaderCurve(value); +}; + +/* Set FX wet/dry value */ +MC7000.fxWetDry = function(midichan, control, value, status, group) { + var numTicks = (value < 0x64) ? value: (value - 128); + var newVal = engine.getValue(group, "mix") + numTicks/64*2; + engine.setValue(group, "mix", Math.max(0, Math.min(1, newVal))); +}; + +/* Next Rate range calculation */ +MC7000.getNextRateRange = function(currRateRange) { + for (var i = 0; i < MC7000.posRateRanges.length; i++) { + if (MC7000.posRateRanges[i] > currRateRange) { + return MC7000.posRateRanges[i]; + } + } + return MC7000.posRateRanges[0]; +}; + +/* Previous Rate range calculation */ +MC7000.getPrevRateRange = function(currRateRange) { + for (var i = 0; i < MC7000.negRateRanges.length; i++) { + if (MC7000.negRateRanges[i] < currRateRange) { + return MC7000.negRateRanges[i]; + } + } + return MC7000.negRateRanges[0]; +}; + +/* LEDs for VuMeter */ +// VuMeters only for Channel 1-4 / Master is on Hardware +MC7000.VuMeter = function(value, group) { + var deckNumber = script.deckFromGroup(group), + peak = 0x76, // where the red LED starts (clipping indicator) + level = value*value*value*value*0x69; + + if (engine.getValue(group, "PeakIndicator")) { + var level = peak; + } + // now send the level meter signal to controller + midi.sendShortMsg(0xB0 + deckNumber - 1, 0x1F, level); +}; + +/* LEDs around Jog wheel */ +MC7000.JogLed = function(value, group) { + var deckNumber = script.deckFromGroup(group); + // do nothing before track starts + if (value < 0) return; + // While "VINYL" is active show spinny LEDs + if (MC7000.isVinylMode[deckNumber]) { + var trackDuration = engine.getValue(group, "duration"), + position = value * trackDuration / 60 * MC7000.scratchParams.recordSpeed, + activeLED = Math.round(position * 96) % 96; + // While "VINYL" is off show track position + } else { + var activeLED = value * 96; + }; + // sending the position of active LED to the controller + midi.sendShortMsg(0x90 + deckNumber -1, 0x06, activeLED); +}; + +// initial HotCue LED when loading a track with already existing hotcues +MC7000.HotCueLED = function(value, group) { + var deckNumber = script.deckFromGroup(group); + if (MC7000.PADModeCue[deckNumber]) { + for (var i = 1; i <= 8; i++) { + if (value === 1) { + if (engine.getValue(group, "hotcue_"+i+"_enabled") === 1) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueon); + } + } else { + if (engine.getValue(group, "hotcue_"+i+"_enabled") === 0) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueoff); + } + }; + }; + }; +}; + +/* CONTROLLER SHUTDOWN */ +MC7000.shutdown = function () { + +// Need to switch off LEDs one by one, +// otherwise the controller cannot handle the signal traffic + + // Switch off Transport section LEDs + for (var i = 0; i <= 3; i++) { + midi.sendShortMsg(0x90 + i, 0x00, 0x01); + midi.sendShortMsg(0x90 + i, 0x01, 0x01); + midi.sendShortMsg(0x90 + i, 0x02, 0x01); + midi.sendShortMsg(0x90 + i, 0x03, 0x01); + midi.sendShortMsg(0x90 + i, 0x04, 0x01); + midi.sendShortMsg(0x90 + i, 0x05, 0x01); + }; + // Switch off Loop Section LEDs + for (var i = 0; i <= 3; i++) { + midi.sendShortMsg(0x94 + i, 0x32, 0x01); + midi.sendShortMsg(0x94 + i, 0x33, 0x01); + midi.sendShortMsg(0x94 + i, 0x34, 0x01); + midi.sendShortMsg(0x94 + i, 0x35, 0x01); + midi.sendShortMsg(0x94 + i, 0x38, 0x01); + midi.sendShortMsg(0x94 + i, 0x39, 0x01); + // switch PAD Mode to CUE LED + midi.sendShortMsg(0x94 + i, 0x00, 0x04); + }; + // Switch all PAD LEDs to HotCue mode + for (var i = 0x14; i <= 0x1B; i++) { + midi.sendShortMsg(0x94, i, 0x02); + midi.sendShortMsg(0x95, i, 0x02); + midi.sendShortMsg(0x96, i, 0x02); + midi.sendShortMsg(0x97, i, 0x02); + }; + // Switch off Channel Cue, VINYL, SLIP, KEY LOCK LEDs + for (var i = 0; i <= 3; i++) { + midi.sendShortMsg(0x90 + i, 0x07, 0x01); + midi.sendShortMsg(0x90 + i, 0x0F, 0x01); + midi.sendShortMsg(0x90 + i, 0x0D, 0x01); + midi.sendShortMsg(0x90 + i, 0x1B, 0x01); + }; + // Switch off FX Section LEDs + for (var i = 0; i <= 1; i++) { + midi.sendShortMsg(0x98 + i, 0x00, 0x01); + midi.sendShortMsg(0x98 + i, 0x01, 0x01); + midi.sendShortMsg(0x98 + i, 0x02, 0x01); + midi.sendShortMsg(0x98 + i, 0x04, 0x01); + midi.sendShortMsg(0x98 + i, 0x0A, 0x01); + midi.sendShortMsg(0x98 + i, 0x05, 0x01); + midi.sendShortMsg(0x98 + i, 0x06, 0x01); + midi.sendShortMsg(0x98 + i, 0x07, 0x01); + midi.sendShortMsg(0x98 + i, 0x08, 0x01); + }; + // Reset Level Meters and JogLED + for (var i = 0; i <= 3; i++) { + // Switch off Level Meters + midi.sendShortMsg(0xB0 + i, 0x1F, 0x00); + // Platter Ring: Reset JogLED to Zero position + midi.sendShortMsg(0x90 + i, 0x06, 0x00); + // Platter Ring: Switch all LEDs on + midi.sendShortMsg(0x90 + i, 0x64, 0x00); + }; +}; diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml new file mode 100644 index 000000000000..eb68ffa6a6fd --- /dev/null +++ b/res/controllers/Denon-MC7000.midi.xml @@ -0,0 +1,4054 @@ + + + + Denon MC7000 beta 0.14 + OsZ + Denon MC7000 mapping for testing. Check your Linux Kernel version to get the Audio Interface working - see WIKI page. + https://www.mixxx.org/forums/ + https://www.mixxx.org/wiki/doku.php/denon_mc7000 + + + + + + + + + + [Channel1] + MC7000.padModeCue + Pad Mode HotCues + 0x94 + 0x00 + + + + + + [Channel2] + MC7000.padModeCue + Pad Mode HotCues + 0x95 + 0x00 + + + + + + [Channel3] + MC7000.padModeCue + Pad Mode HotCues + 0x96 + 0x00 + + + + + + [Channel4] + MC7000.padModeCue + Pad Mode HotCues + 0x97 + 0x00 + + + + + + [Channel1] + MC7000.padModeCueLoop + Pad Mode HotCues + 0x94 + 0x03 + + + + + + [Channel2] + MC7000.padModeCueLoop + Pad Mode HotCues + 0x95 + 0x03 + + + + + + [Channel3] + MC7000.padModeCueLoop + Pad Mode HotCues + 0x96 + 0x03 + + + + + + [Channel4] + MC7000.padModeCueLoop + Pad Mode HotCues + 0x97 + 0x03 + + + + + + [Channel1] + MC7000.padModeFlip + Pad Mode HotCues + 0x94 + 0x02 + + + + + + [Channel2] + MC7000.padModeFlip + Pad Mode HotCues + 0x95 + 0x02 + + + + + + [Channel3] + MC7000.padModeFlip + Pad Mode HotCues + 0x96 + 0x02 + + + + + + [Channel4] + MC7000.padModeFlip + Pad Mode HotCues + 0x97 + 0x02 + + + + + + [Channel1] + MC7000.padModeRoll + Pad Mode HotCues + 0x94 + 0x07 + + + + + + [Channel2] + MC7000.padModeRoll + Pad Mode HotCues + 0x95 + 0x07 + + + + + + [Channel3] + MC7000.padModeRoll + Pad Mode HotCues + 0x96 + 0x07 + + + + + + [Channel4] + MC7000.padModeRoll + Pad Mode HotCues + 0x97 + 0x07 + + + + + + [Channel1] + MC7000.padModeSavedLoop + Pad Mode HotCues + 0x94 + 0x0D + + + + + + [Channel2] + MC7000.padModeSavedLoop + Pad Mode HotCues + 0x95 + 0x0D + + + + + + [Channel3] + MC7000.padModeSavedLoop + Pad Mode HotCues + 0x96 + 0x0D + + + + + + [Channel4] + MC7000.padModeSavedLoop + Pad Mode HotCues + 0x97 + 0x0D + + + + + + [Channel1] + MC7000.padModeSlicer + Pad Mode HotCues + 0x94 + 0x09 + + + + + + [Channel2] + MC7000.padModeSlicer + Pad Mode HotCues + 0x95 + 0x09 + + + + + + [Channel3] + MC7000.padModeSlicer + Pad Mode HotCues + 0x96 + 0x09 + + + + + + [Channel4] + MC7000.padModeSlicer + Pad Mode HotCues + 0x97 + 0x09 + + + + + + [Channel1] + MC7000.padModeSlicerLoop + Pad Mode HotCues + 0x94 + 0x0A + + + + + + [Channel2] + MC7000.padModeSlicerLoop + Pad Mode HotCues + 0x95 + 0x0A + + + + + + [Channel3] + MC7000.padModeSlicerLoop + Pad Mode HotCues + 0x96 + 0x0A + + + + + + [Channel4] + MC7000.padModeSlicerLoop + Pad Mode HotCues + 0x97 + 0x0A + + + + + + [Channel1] + MC7000.padModeSampler + Pad Mode HotCues + 0x94 + 0x0B + + + + + + [Channel2] + MC7000.padModeSampler + Pad Mode HotCues + 0x95 + 0x0B + + + + + + [Channel3] + MC7000.padModeSampler + Pad Mode HotCues + 0x96 + 0x0B + + + + + + [Channel4] + MC7000.padModeSampler + Pad Mode HotCues + 0x97 + 0x0B + + + + + + [Channel1] + MC7000.padModeVelSamp + Pad Mode HotCues + 0x94 + 0x0C + + + + + + [Channel2] + MC7000.padModeVelSamp + Pad Mode HotCues + 0x95 + 0x0C + + + + + + [Channel3] + MC7000.padModeVelSamp + Pad Mode HotCues + 0x96 + 0x0C + + + + + + [Channel4] + MC7000.padModeVelSamp + Pad Mode HotCues + 0x97 + 0x0C + + + + + + [Channel1] + MC7000.padModePitch + Pad Mode HotCues + 0x94 + 0x0F + + + + + + [Channel2] + MC7000.padModePitch + Pad Mode HotCues + 0x95 + 0x0F + + + + + + [Channel3] + MC7000.padModePitch + Pad Mode HotCues + 0x96 + 0x0F + + + + + + [Channel4] + MC7000.padModePitch + Pad Mode HotCues + 0x97 + 0x0F + + + + + + + [Channel1] + MC7000.pitchFaderPosition + MIDI Learned from 180 messages. + 0xB0 + 0x77 + + + + + + [Channel2] + MC7000.pitchFaderPosition + MIDI Learned from 144 messages. + 0xB1 + 0x77 + + + + + + [Channel3] + MC7000.pitchFaderPosition + MIDI Learned from 150 messages. + 0xB2 + 0x77 + + + + + + [Channel4] + MC7000.pitchFaderPosition + MIDI Learned from 128 messages. + 0xB3 + 0x77 + + + + + + [Channel1] + MC7000.pitchFaderLSB + MIDI Learned from 180 messages. + 0xB0 + 0x09 + + + + + + [Channel2] + MC7000.pitchFaderLSB + MIDI Learned from 144 messages. + 0xB1 + 0x09 + + + + + + [Channel3] + MC7000.pitchFaderLSB + MIDI Learned from 150 messages. + 0xB2 + 0x09 + + + + + + [Channel4] + MC7000.pitchFaderLSB + MIDI Learned from 128 messages. + 0xB3 + 0x09 + + + + + + + [Channel1] + MC7000.keySelect + Pitch +/- needs tuning + 0xB0 + 0x26 + + + + + + [Channel2] + MC7000.keySelect + Pitch +/- needs tuning + 0xB1 + 0x26 + + + + + + [Channel3] + MC7000.keySelect + Pitch +/- needs tuning + 0xB2 + 0x26 + + + + + + [Channel4] + MC7000.keySelect + Pitch +/- needs tuning + 0xB3 + 0x26 + + + + + + + [Channel1] + MC7000.crossfaderAssign + Ch1 THRU + 0x90 + 0x1E + + + + + + [Channel2] + MC7000.crossfaderAssign + Ch2 THRU + 0x91 + 0x1E + + + + + + [Channel3] + MC7000.crossfaderAssign + Ch3 THRU + 0x92 + 0x1E + + + + + + [Channel4] + MC7000.crossfaderAssign + Ch4 THRU + 0x93 + 0x1E + + + + + + + [Channel1] + MC7000.prevRateRange + Toggle next Rate Range + 0x90 + 0x2C + + + + + + [Channel2] + MC7000.prevRateRange + Toggle next Rate Range + 0x91 + 0x2C + + + + + + [Channel3] + MC7000.prevRateRange + Toggle next Rate Range + 0x92 + 0x2C + + + + + + [Channel4] + MC7000.prevRateRange + Toggle next Rate Range + 0x93 + 0x2C + + + + + + [Channel1] + MC7000.nextRateRange + Toggle next Rate Range + 0x90 + 0x2B + + + + + + [Channel2] + MC7000.nextRateRange + Toggle next Rate Range + 0x91 + 0x2B + + + + + + [Channel3] + MC7000.nextRateRange + Toggle next Rate Range + 0x92 + 0x2B + + + + + + [Channel4] + MC7000.nextRateRange + Toggle next Rate Range + 0x93 + 0x2B + + + + + + + [Channel1] + MC7000.needleSearchTouch + Touch detection for Needle Search + 0x90 + 0x50 + + + + + + [Channel1] + MC7000.needleSearchTouchShift + "Shift" + Touch detection for Needle Search + 0x90 + 0x51 + + + + + + [Channel1] + MC7000.needleSearchLSB + Jump to track position (Needle Search) + 0xB0 + 0x2B + + + + + + [Channel1] + MC7000.needleSearchStripPosition + Jump to track position (Needle Search) + 0xB0 + 0x78 + + + + + + [Channel2] + MC7000.needleSearchTouch + Touch detection for Needle Search + 0x91 + 0x50 + + + + + + [Channel2] + MC7000.needleSearchTouchShift + "Shift" + Touch detection for Needle Search + 0x91 + 0x51 + + + + + + [Channel2] + MC7000.needleSearchLSB + Jump to track position (Needle Search) + 0xB1 + 0x2B + + + + + + [Channel2] + MC7000.needleSearchStripPosition + Jump to track position (Needle Search) + 0xB1 + 0x78 + + + + + + [Channel3] + MC7000.needleSearchTouch + Touch detection for Needle Search + 0x92 + 0x50 + + + + + + [Channel3] + MC7000.needleSearchTouchShift + "Shift" + Touch detection for Needle Search + 0x92 + 0x51 + + + + + + [Channel3] + MC7000.needleSearchLSB + Jump to track position (Needle Search) + 0xB2 + 0x2B + + + + + + [Channel3] + MC7000.needleSearchStripPosition + Jump to track position (Needle Search) + 0xB2 + 0x78 + + + + + + [Channel4] + MC7000.needleSearchTouch + Touch detection for Needle Search + 0x93 + 0x50 + + + + + + [Channel4] + MC7000.needleSearchTouchShift + "Shift" + Touch detection for Needle Search + 0x93 + 0x51 + + + + + + [Channel4] + MC7000.needleSearchLSB + Jump to track position (Needle Search) + 0xB3 + 0x2B + + + + + + [Channel4] + MC7000.needleSearchStripPosition + Jump to track position (Needle Search) + 0xB3 + 0x78 + + + + + + + [EffectRack1_EffectUnit1] + MC7000.fxWetDry + 0xB8 + 0x03 + + + + + + [EffectRack1_EffectUnit2] + MC7000.fxWetDry + 0xB9 + 0x03 + + + + + + [Master] + MC7000.samplerLevel + 0xBF + 0x1A + + + + + + [Mixer Profile] + MC7000.crossFaderCurve + sets the Crossfader Curve + 0xBF + 0x09 + + + + + + + [Channel1] + MC7000.vinylModeToggle + Vinyl toggle + 0x90 + 0x07 + + + + + + [Channel2] + MC7000.vinylModeToggle + Vinyl toggle + 0x91 + 0x07 + + + + + + [Channel3] + MC7000.vinylModeToggle + Vinyl toggle + 0x92 + 0x07 + + + + + + [Channel4] + MC7000.vinylModeToggle + Vinyl toggle + 0x93 + 0x07 + + + + + + [Channel1] + MC7000.wheelTouch + MIDI Learned from 759 messages. + 0x90 + 0x06 + + + + + + [Channel2] + MC7000.wheelTouch + MIDI Learned from 759 messages. + 0x91 + 0x06 + + + + + + [Channel3] + MC7000.wheelTouch + MIDI Learned from 759 messages. + 0x92 + 0x06 + + + + + + [Channel4] + MC7000.wheelTouch + MIDI Learned from 759 messages. + 0x93 + 0x06 + + + + + + [Channel1] + MC7000.wheelTurn + MIDI Learned from 759 messages. + 0xB0 + 0x06 + + + + + + [Channel2] + MC7000.wheelTurn + MIDI Learned from 759 messages. + 0xB1 + 0x06 + + + + + + [Channel3] + MC7000.wheelTurn + MIDI Learned from 759 messages. + 0xB2 + 0x06 + + + + + + [Channel4] + MC7000.wheelTurn + MIDI Learned from 759 messages. + 0xB3 + 0x06 + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter3 + Hi Ch1 + 0xB0 + 0x17 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter3 + Hi Ch2 + 0xB1 + 0x17 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + parameter3 + Hi Ch3 + 0xB2 + 0x17 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + parameter3 + Hi Ch4 + 0xB3 + 0x17 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter2 + MID Ch1 + 0xB0 + 0x18 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter2 + MID Ch2 + 0xB1 + 0x18 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + parameter2 + MID Ch3 + 0xB2 + 0x18 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + parameter2 + MID Ch4 + 0xB3 + 0x18 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter1 + LOW Ch1 + 0xB0 + 0x19 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter1 + LOW Ch2 + 0xB1 + 0x19 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + parameter1 + LOW Ch3 + 0xB2 + 0x19 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + parameter1 + LOW Ch4 + 0xB3 + 0x19 + + + + + + [QuickEffectRack1_[Channel1]] + super1 + Filter Ch1 + 0xB0 + 0x1A + + + + + + [QuickEffectRack1_[Channel2]] + super1 + Filter Ch2 + 0xB1 + 0x1A + + + + + + [QuickEffectRack1_[Channel3]] + super1 + Filter Ch3 + 0xB2 + 0x1A + + + + + + [QuickEffectRack1_[Channel4]] + super1 + Filter Ch4 + 0xB3 + 0x1A + + + + + + + [Channel1] + pregain + Level / Gain Ch1 + 0xB0 + 0x16 + + + + + + [Channel2] + pregain + Level / Gain Ch2 + 0xB1 + 0x16 + + + + + + [Channel3] + pregain + Level / Gain Ch3 + 0xB2 + 0x16 + + + + + + [Channel4] + pregain + Level / Gain Ch4 + 0xB3 + 0x16 + + + + + + [Channel1] + volume + Line Fader Ch1 + 0xB0 + 0x1C + + + + + + [Channel2] + volume + Line Fader Ch2 + 0xB1 + 0x1C + + + + + + [Channel3] + volume + Line Fader Ch3 + 0xB2 + 0x1C + + + + + + [Channel4] + volume + Line Fader Ch4 + 0xB3 + 0x1C + + + + + + [Master] + crossfader + Crossfader + 0xBF + 0x08 + + + + + + [Channel1] + pfl + PFL Ch1 + 0x90 + 0x1B + + + + + + [Channel2] + pfl + PFL Ch2 + 0x91 + 0x1B + + + + + + [Channel3] + pfl + PFL Ch3 + 0x92 + 0x1B + + + + + + [Channel4] + pfl + PFL Ch4 + 0x93 + 0x1B + + + + + + + [Channel1] + play + Play Ch1 + 0x90 + 0x00 + + + + + + [Channel2] + play + Play Ch2 + 0x91 + 0x00 + + + + + + [Channel3] + play + Play Ch3 + 0x92 + 0x00 + + + + + + [Channel4] + play + Play Ch4 + 0x93 + 0x00 + + + + + + [Channel1] + play_stutter + Stutter play Ch1 + 0x90 + 0x04 + + + + + + [Channel2] + play_stutter + Stutter play Ch2 + 0x91 + 0x04 + + + + + + [Channel3] + play_stutter + Stutter play Ch3 + 0x92 + 0x04 + + + + + + [Channel4] + play_stutter + Stutter play Ch4 + 0x93 + 0x04 + + + + + + [PreviewDeck1] + play + Play Preview Deck + 0x9F + 0x10 + + + + + + [Channel1] + cue_default + CUE Ch1 + 0x90 + 0x01 + + + + + + [Channel2] + cue_default + CUE Ch2 + 0x91 + 0x01 + + + + + + [Channel3] + cue_default + CUE Ch3 + 0x92 + 0x01 + + + + + + [Channel4] + cue_default + CUE Ch4 + 0x93 + 0x01 + + + + + + [Channel1] + start_stop + start stop Ch1 + 0x90 + 0x05 + + + + + + [Channel2] + start_stop + start stop Ch2 + 0x91 + 0x05 + + + + + + [Channel3] + start_stop + start stop Ch3 + 0x92 + 0x05 + + + + + + [Channel4] + start_stop + start stop Ch4 + 0x93 + 0x05 + + + + + + [Channel1] + sync_enabled + SYNC Ch1 + 0x90 + 0x02 + + + + + + [Channel2] + sync_enabled + SYNC Ch2 + 0x91 + 0x02 + + + + + + [Channel3] + sync_enabled + SYNC Ch3 + 0x92 + 0x02 + + + + + + [Channel4] + sync_enabled + SYNC Ch4 + 0x93 + 0x02 + + + + + + + [Channel1] + beats_translate_curpos + BeatGrid Adjust Ch1 + 0x94 + 0x46 + + + + + + [Channel2] + beats_translate_curpos + BeatGrid Adjust Ch2 + 0x95 + 0x46 + + + + + + [Channel3] + beats_translate_curpos + BeatGrid Adjust Ch3 + 0x96 + 0x46 + + + + + + [Channel4] + beats_translate_curpos + BeatGrid Adjust Ch4 + 0x97 + 0x46 + + + + + + [Channel1] + beats_translate_match_alignment + BeatGrid Slide Ch1 + 0x94 + 0x48 + + + + + + [Channel2] + beats_translate_match_alignment + BeatGrid Slide Ch2 + 0x95 + 0x48 + + + + + + [Channel3] + beats_translate_match_alignment + BeatGrid Slide Ch3 + 0x96 + 0x48 + + + + + + [Channel4] + beats_translate_match_alignment + BeatGrid Slide Ch4 + 0x97 + 0x48 + + + + + + [Channel1] + quantize + Quantize + 0x94 + 0x47 + + + + + + [Channel2] + quantize + Quantize + 0x95 + 0x47 + + + + + + [Channel3] + quantize + Quantize + 0x96 + 0x47 + + + + + + [Channel4] + quantize + Quantize + 0x97 + 0x47 + + + + + + [Channel1] + beatloop_activate + AutoLoop Ch1 + 0x94 + 0x32 + + + + + + [Channel2] + beatloop_activate + AutoLoop Ch2 + 0x95 + 0x32 + + + + + + [Channel3] + beatloop_activate + AutoLoop Ch3 + 0x96 + 0x32 + + + + + + [Channel4] + beatloop_activate + AutoLoop Ch4 + 0x97 + 0x32 + + + + + + [Channel1] + loop_halve + X1/2 Ch1 + 0x94 + 0x34 + + + + + + [Channel2] + loop_halve + X1/2 Ch2 + 0x95 + 0x34 + + + + + + [Channel3] + loop_halve + X1/2 Ch3 + 0x96 + 0x34 + + + + + + [Channel4] + loop_halve + X1/2 Ch4 + 0x97 + 0x34 + + + + + + [Channel1] + loop_double + X2 Ch1 + 0x94 + 0x35 + + + + + + [Channel2] + loop_double + X2 Ch2 + 0x95 + 0x35 + + + + + + [Channel3] + loop_double + X2 Ch3 + 0x96 + 0x35 + + + + + + [Channel4] + loop_double + X2 Ch4 + 0x97 + 0x35 + + + + + + [Channel1] + loop_in + Loop in Ch1 + 0x94 + 0x38 + + + + + + [Channel2] + loop_in + Loop in Ch2 + 0x95 + 0x38 + + + + + + [Channel3] + loop_in + Loop in Ch3 + 0x96 + 0x38 + + + + + + [Channel4] + loop_in + Loop in Ch4 + 0x97 + 0x38 + + + + + + [Channel1] + loop_out + Loop out Ch1 + 0x94 + 0x39 + + + + + + [Channel2] + loop_out + Loop out Ch2 + 0x95 + 0x39 + + + + + + [Channel3] + loop_out + Loop out Ch3 + 0x96 + 0x39 + + + + + + [Channel4] + loop_out + Loop out Ch4 + 0x97 + 0x39 + + + + + + [Channel1] + reloop_toggle + Reloop Ch1 + 0x94 + 0x33 + + + + + + [Channel2] + reloop_toggle + Reloop Ch2 + 0x95 + 0x33 + + + + + + [Channel3] + reloop_toggle + Reloop Ch3 + 0x96 + 0x33 + + + + + + [Channel4] + reloop_toggle + Reloop Ch4 + 0x97 + 0x33 + + + + + + + [Channel1] + beatjump_8_backward + MIDI Learned from 10 messages. + 0x94 + 0x28 + + + + + + [Channel1] + beatjump_8_forward + MIDI Learned from 2 messages. + 0x94 + 0x29 + + + + + + [Channel1] + beatjump_32_backward + MIDI Learned from 8 messages. + 0x94 + 0x2A + + + + + + [Channel1] + beatjump_32_forward + MIDI Learned from 6 messages. + 0x94 + 0x2B + + + + + + [Channel2] + beatjump_8_backward + MIDI Learned from 10 messages. + 0x95 + 0x28 + + + + + + [Channel2] + beatjump_8_forward + MIDI Learned from 2 messages. + 0x95 + 0x29 + + + + + + [Channel2] + beatjump_32_backward + MIDI Learned from 8 messages. + 0x95 + 0x2A + + + + + + [Channel2] + beatjump_32_forward + MIDI Learned from 6 messages. + 0x95 + 0x2B + + + + + + [Channel3] + beatjump_8_backward + MIDI Learned from 10 messages. + 0x96 + 0x28 + + + + + + [Channel3] + beatjump_8_forward + MIDI Learned from 2 messages. + 0x96 + 0x29 + + + + + + [Channel3] + beatjump_32_backward + MIDI Learned from 8 messages. + 0x96 + 0x2A + + + + + + [Channel3] + beatjump_32_forward + MIDI Learned from 6 messages. + 0x96 + 0x2B + + + + + + [Channel4] + beatjump_8_backward + MIDI Learned from 10 messages. + 0x97 + 0x28 + + + + + + [Channel4] + beatjump_8_forward + MIDI Learned from 2 messages. + 0x97 + 0x29 + + + + + + [Channel4] + beatjump_32_backward + MIDI Learned from 8 messages. + 0x97 + 0x2A + + + + + + [Channel4] + beatjump_32_forward + MIDI Learned from 6 messages. + 0x97 + 0x2B + + + + + + + [Library] + MoveVertical + Select line + 0xBF + 0x00 + + + + + + [Library] + ScrollVertical + Select page + 0xBF + 0x01 + + + + + + [Channel1] + LoadSelectedTrack + MIDI Learned from 2 messages. + 0x9F + 0x02 + + + + + + [Channel2] + LoadSelectedTrack + MIDI Learned from 2 messages. + 0x9F + 0x03 + + + + + + [Channel3] + LoadSelectedTrack + MIDI Learned from 2 messages. + 0x9F + 0x04 + + + + + + [Channel4] + LoadSelectedTrack + MIDI Learned from 2 messages. + 0x9F + 0x05 + + + + + + [Library] + GoToItem + Select Item + 0x9F + 0x1F + + + + + + [Library] + MoveFocusForward + change active panel + 0x9F + 0x06 + + + + + + [Library] + MoveFocusBackward + change active panel + 0x9F + 0x07 + + + + + + [PreviewDeck1] + LoadSelectedTrack + Load in Preview deck + 0x9F + 0x1B + + + + + + [EffectRack1] + show + Show Effect Rack + 0x9F + 0x11 + + + + + + [Master] + maximize_library + Full screen Library + 0x9F + 0x0F + + + + + + + + [EffectRack1_EffectUnit1] + group_[Channel1]_enable + FX 1 to Ch1 + 0x98 + 0x05 + + + + + + [EffectRack1_EffectUnit1] + group_[Channel2]_enable + FX 1 to Ch2 + 0x98 + 0x06 + + + + + + [EffectRack1_EffectUnit1] + group_[Channel3]_enable + FX 1 to Ch3 + 0x98 + 0x07 + + + + + + [EffectRack1_EffectUnit1] + group_[Channel4]_enable + FX 1 to Ch4 + 0x98 + 0x08 + + + + + + [EffectRack1_EffectUnit2] + group_[Channel1]_enable + FX 2 to Ch1 + 0x99 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + group_[Channel2]_enable + FX 2 to Ch2 + 0x99 + 0x06 + + + + + + [EffectRack1_EffectUnit2] + group_[Channel3]_enable + FX 2 to Ch3 + 0x99 + 0x07 + + + + + + [EffectRack1_EffectUnit2] + group_[Channel4]_enable + FX 2 to Ch4 + 0x99 + 0x08 + + + + + + + [EffectRack1_EffectUnit1_Effect1] + enabled + FX1 1 on + 0x98 + 0x00 + + + + + + [EffectRack1_EffectUnit1_Effect2] + enabled + FX1 2 on + 0x98 + 0x01 + + + + + + [EffectRack1_EffectUnit1_Effect3] + enabled + FX1 3 on + 0x98 + 0x02 + + + + + + [EffectRack1_EffectUnit1_Effect1] + meta + FX1 Level 1 + 0xB8 + 0x00 + + + + + + [EffectRack1_EffectUnit1_Effect2] + meta + FX1 Level 2 + 0xB8 + 0x01 + + + + + + [EffectRack1_EffectUnit1_Effect3] + meta + FX1 Level 3 + 0xB8 + 0x02 + + + + + + + [EffectRack1_EffectUnit2_Effect1] + enabled + FX2 1 on + 0x99 + 0x00 + + + + + + [EffectRack1_EffectUnit2_Effect2] + enabled + FX2 2 on + 0x99 + 0x01 + + + + + + [EffectRack1_EffectUnit2_Effect3] + enabled + FX2 3 on + 0x99 + 0x02 + + + + + + [EffectRack1_EffectUnit2_Effect1] + meta + FX2 Level 1 + 0xB9 + 0x00 + + + + + + [EffectRack1_EffectUnit2_Effect2] + meta + FX2 Level 2 + 0xB9 + 0x01 + + + + + + [EffectRack1_EffectUnit2_Effect3] + meta + FX2 Level 3 + 0xB9 + 0x02 + + + + + + + [EffectRack1_EffectUnit1_Effect1] + next_effect + FX1 1 + 0x98 + 0x0B + + + + + + [EffectRack1_EffectUnit2_Effect1] + next_effect + FX2 1 + 0x99 + 0x0B + + + + + + [EffectRack1_EffectUnit1_Effect3] + next_effect + FX1 3 + 0x98 + 0x0D + + + + + + [EffectRack1_EffectUnit1_Effect2] + next_effect + FX1 2 + 0x98 + 0x0C + + + + + + [EffectRack1_EffectUnit2_Effect3] + next_effect + FX2 3 + 0x99 + 0x0D + + + + + + [EffectRack1_EffectUnit2_Effect2] + next_effect + FX2 2 + 0x99 + 0x0C + + + + + + + [EffectRack1_EffectUnit1] + group_[Master]_enable + FX1 on master + 0x98 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + group_[Master]_enable + FX2 on master + 0x99 + 0x04 + + + + + + [EffectRack1_EffectUnit1] + group_[Headphone]_enable + MIDI Learned from 2 messages. + 0x98 + 0x0A + + + + + + [EffectRack1_EffectUnit2] + group_[Headphone]_enable + MIDI Learned from 2 messages. + 0x99 + 0x0A + + + + + + + [Channel1] + slip_enabled + MIDI Learned from 12 messages. + 0x90 + 0x0F + + + + + + [Channel2] + slip_enabled + MIDI Learned from 124 messages. + 0x91 + 0x0F + + + + + + [Channel3] + slip_enabled + MIDI Learned from 70 messages. + 0x92 + 0x0F + + + + + + [Channel4] + slip_enabled + MIDI Learned from 160 messages. + 0x93 + 0x0F + + + + + + [Channel1] + MC7000.brake_button + Backspin Ch1 + 0x90 + 0x10 + + + + + + [Channel2] + MC7000.brake_button + Backspin Ch2 + 0x91 + 0x10 + + + + + + [Channel3] + MC7000.brake_button + Backspin Ch3 + 0x92 + 0x10 + + + + + + [Channel4] + MC7000.brake_button + Backspin Ch4 + 0x93 + 0x10 + + + + + + [Channel1] + MC7000.stopTime + Stop Time Ch1 + 0xB0 + 0x13 + + + + + + [Channel2] + MC7000.stopTime + Stop Time Ch2 + 0xB1 + 0x13 + + + + + + [Channel3] + MC7000.stopTime + Stop Time Ch3 + 0xB2 + 0x13 + + + + + + [Channel4] + MC7000.stopTime + Stop Time Ch4 + 0xB3 + 0x13 + + + + + + [Channel1] + reverse + Reverse Ch1 + 0x90 + 0x11 + + + + + + [Channel2] + reverse + Reverse Ch2 + 0x91 + 0x11 + + + + + + [Channel3] + reverse + Reverse Ch3 + 0x92 + 0x11 + + + + + + [Channel4] + reverse + Reverse Ch4 + 0x93 + 0x11 + + + + + + + [Channel1] + keylock + Keylock Ch1 + 0x90 + 0x0D + + + + + + [Channel2] + keylock + Keylock Ch2 + 0x91 + 0x0D + + + + + + [Channel3] + keylock + Keylock Ch3 + 0x92 + 0x0D + + + + + + [Channel4] + keylock + Keylock Ch4 + 0x93 + 0x0D + + + + + + [Channel1] + sync_key + sync_key Ch1 + 0x90 + 0x29 + + + + + + [Channel2] + sync_key + sync_key Ch2 + 0x91 + 0x29 + + + + + + [Channel3] + sync_key + sync_key Ch3 + 0x92 + 0x29 + + + + + + [Channel4] + sync_key + sync_key Ch4 + 0x93 + 0x29 + + + + + + [Channel1] + reset_key + Default Key + 0x90 + 0x2A + + + + + + [Channel2] + reset_key + Default Key + 0x91 + 0x2A + + + + + + [Channel3] + reset_key + Default Key + 0x92 + 0x2A + + + + + + [Channel4] + reset_key + Default Key + 0x93 + 0x2A + + + + + + + [Channel1] + rate_temp_down_small + Pitch Bend - + 0x90 + 0x0C + + + + + + [Channel2] + rate_temp_down_small + Pitch Bend - + 0x91 + 0x0C + + + + + + [Channel3] + rate_temp_down_small + Pitch Bend - + 0x92 + 0x0C + + + + + + [Channel4] + rate_temp_down_small + Pitch Bend - + 0x93 + 0x0C + + + + + + [Channel1] + rate_temp_up_small + Pitch Bend + + 0x90 + 0x0B + + + + + + [Channel2] + rate_temp_up_small + Pitch Bend + + 0x91 + 0x0B + + + + + + [Channel3] + rate_temp_up_small + Pitch Bend + + 0x92 + 0x0B + + + + + + [Channel4] + rate_temp_up_small + Pitch Bend + + 0x93 + 0x0B + + + + + + + [Channel1] + MC7000.PadButtons + PAD-1 + 0x94 + 0x14 + + + + + + [Channel2] + MC7000.PadButtons + PAD-1 + 0x95 + 0x14 + + + + + + [Channel3] + MC7000.PadButtons + PAD-1 + 0x96 + 0x14 + + + + + + [Channel4] + MC7000.PadButtons + PAD-1 + 0x97 + 0x14 + + + + + + [Channel1] + MC7000.PadButtons + PAD-2 + 0x94 + 0x15 + + + + + + [Channel2] + MC7000.PadButtons + PAD-2 + 0x95 + 0x15 + + + + + + [Channel3] + MC7000.PadButtons + PAD-2 + 0x96 + 0x15 + + + + + + [Channel4] + MC7000.PadButtons + PAD-2 + 0x97 + 0x15 + + + + + + [Channel1] + MC7000.PadButtons + PAD_3 + 0x94 + 0x16 + + + + + + [Channel2] + MC7000.PadButtons + PAD_3 + 0x95 + 0x16 + + + + + + [Channel3] + MC7000.PadButtons + PAD_3 + 0x96 + 0x16 + + + + + + [Channel4] + MC7000.PadButtons + PAD_3 + 0x97 + 0x16 + + + + + + [Channel1] + MC7000.PadButtons + PAD_4 + 0x94 + 0x17 + + + + + + [Channel2] + MC7000.PadButtons + PAD_4 + 0x95 + 0x17 + + + + + + [Channel3] + MC7000.PadButtons + PAD_4 + 0x96 + 0x17 + + + + + + [Channel4] + MC7000.PadButtons + PAD_4 + 0x97 + 0x17 + + + + + + [Channel1] + MC7000.PadButtons + PAD_5 + 0x94 + 0x18 + + + + + + [Channel2] + MC7000.PadButtons + PAD_5 + 0x95 + 0x18 + + + + + + [Channel3] + MC7000.PadButtons + PAD_5 + 0x96 + 0x18 + + + + + + [Channel4] + MC7000.PadButtons + PAD_5 + 0x97 + 0x18 + + + + + + [Channel1] + MC7000.PadButtons + PAD_6 + 0x94 + 0x19 + + + + + + [Channel2] + MC7000.PadButtons + PAD_6 + 0x95 + 0x19 + + + + + + [Channel3] + MC7000.PadButtons + PAD_6 + 0x96 + 0x19 + + + + + + [Channel4] + MC7000.PadButtons + PAD_6 + 0x97 + 0x19 + + + + + + [Channel1] + MC7000.PadButtons + PAD_7 + 0x94 + 0x1A + + + + + + [Channel2] + MC7000.PadButtons + PAD_7 + 0x95 + 0x1A + + + + + + [Channel3] + MC7000.PadButtons + PAD_7 + 0x96 + 0x1A + + + + + + [Channel4] + MC7000.PadButtons + PAD_7 + 0x97 + 0x1A + + + + + + [Channel1] + MC7000.PadButtons + PAD_8 + 0x94 + 0x1B + + + + + + [Channel2] + MC7000.PadButtons + PAD_8 + 0x95 + 0x1B + + + + + + [Channel3] + MC7000.PadButtons + PAD_8 + 0x96 + 0x1B + + + + + + [Channel4] + MC7000.PadButtons + PAD_8 + 0x97 + 0x1B + + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD-1 + 0x94 + 0x1C + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD-1 + 0x95 + 0x1C + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD-1 + 0x96 + 0x1C + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD-1 + 0x97 + 0x1C + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD-2 + 0x94 + 0x1D + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD-2 + 0x95 + 0x1D + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD-2 + 0x96 + 0x1D + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD-2 + 0x97 + 0x1D + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD_3 + 0x94 + 0x1E + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD_3 + 0x95 + 0x1E + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD_3 + 0x96 + 0x1E + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD_3 + 0x97 + 0x1E + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD_4 + 0x94 + 0x1F + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD_4 + 0x95 + 0x1F + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD_4 + 0x96 + 0x1F + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD_4 + 0x97 + 0x1F + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD_5 + 0x94 + 0x20 + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD_5 + 0x95 + 0x20 + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD_5 + 0x96 + 0x20 + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD_5 + 0x97 + 0x20 + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD_6 + 0x94 + 0x21 + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD_6 + 0x95 + 0x21 + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD_6 + 0x96 + 0x21 + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD_6 + 0x97 + 0x21 + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD_7 + 0x94 + 0x22 + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD_7 + 0x95 + 0x22 + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD_7 + 0x96 + 0x22 + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD_7 + 0x97 + 0x22 + + + + + + [Channel1] + MC7000.PadButtons + CLEAR_PAD_8 + 0x94 + 0x23 + + + + + + [Channel2] + MC7000.PadButtons + CLEAR_PAD_8 + 0x95 + 0x23 + + + + + + [Channel3] + MC7000.PadButtons + CLEAR_PAD_8 + 0x96 + 0x23 + + + + + + [Channel4] + MC7000.PadButtons + CLEAR_PAD_8 + 0x97 + 0x23 + + + + + + + + + [Channel1] + loop_enabled + Autoloop LED + 0x94 + 0x32 + 0x01 + 0.5 + + + [Channel1] + loop_enabled + Reloop LED + 0x94 + 0x33 + 0x02 + 0x01 + 0.5 + + + [Channel2] + loop_enabled + Autoloop LED + 0x95 + 0x32 + 0x01 + 0.5 + + + [Channel2] + loop_enabled + Reloop LED + 0x95 + 0x33 + 0x02 + 0x01 + 0.5 + + + [Channel3] + loop_enabled + Autoloop LED + 0x96 + 0x32 + 0x01 + 0.5 + + + [Channel3] + loop_enabled + Reloop LED + 0x96 + 0x33 + 0x02 + 0x01 + 0.5 + + + [Channel4] + loop_enabled + Autoloop LED + 0x97 + 0x32 + 0x01 + 0.5 + + + [Channel4] + loop_enabled + Reloop LED + 0x97 + 0x33 + 0x02 + 0x01 + 0.5 + + + [Channel1] + play_indicator + Play LED + 0x90 + 0x00 + 0x01 + 0.5 + + + [Channel2] + play_indicator + Play LED + 0x91 + 0x00 + 0x01 + 0.5 + + + [Channel3] + play_indicator + Play LED + 0x92 + 0x00 + 0x01 + 0.5 + + + [Channel4] + play_indicator + Play LED + 0x93 + 0x00 + 0x01 + 0.5 + + + [Channel1] + cue_indicator + Cue LED + 0x90 + 0x01 + 0x01 + 0.5 + + + [Channel2] + cue_indicator + Cue LED + 0x91 + 0x01 + 0x01 + 0.5 + + + [Channel3] + cue_indicator + Cue LED + 0x92 + 0x01 + 0x01 + 0.5 + + + [Channel4] + cue_indicator + Cue LED + 0x93 + 0x01 + 0x01 + 0.5 + + + [Channel1] + sync_enabled + Sync LED + 0x90 + 0x02 + 0x01 + 0.5 + + + [Channel2] + sync_enabled + Sync LED + 0x91 + 0x02 + 0x01 + 0.5 + + + [Channel3] + sync_enabled + Sync LED + 0x92 + 0x02 + 0x01 + 0.5 + + + [Channel4] + sync_enabled + Sync LED + 0x93 + 0x02 + 0x01 + 0.5 + + + [Channel1] + slip_enabled + Slip LED + 0x90 + 0x0F + 0x01 + 0.5 + + + [Channel2] + slip_enabled + Slip LED + 0x91 + 0x0F + 0x01 + 0.5 + + + [Channel3] + slip_enabled + Slip LED + 0x92 + 0x0F + 0x01 + 0.5 + + + [Channel4] + slip_enabled + Slip LED + 0x93 + 0x0F + 0x01 + 0.5 + + + [Channel1] + pfl + PFL LED + 0x90 + 0x1B + 0x01 + 0.5 + + + [Channel2] + pfl + PFL LED + 0x91 + 0x1B + 0x01 + 0.5 + + + [Channel3] + pfl + PFL LED + 0x92 + 0x1B + 0x01 + 0.5 + + + [Channel4] + pfl + PFL LED + 0x93 + 0x1B + 0x01 + 0.5 + + + [Channel1] + keylock + keylock LED + 0x90 + 0x0D + 0x01 + 0.5 + + + [Channel2] + keylock + keylock LED + 0x91 + 0x0D + 0x01 + 0.5 + + + [Channel3] + keylock + keylock LED + 0x92 + 0x0D + 0x01 + 0.5 + + + [Channel4] + keylock + keylock LED + 0x93 + 0x0D + 0x01 + 0.5 + + + [Channel1] + reverseroll + Censor LED + 0x90 + 0x10 + 0x01 + 0.5 + + + [Channel2] + reverseroll + Censor LED + 0x91 + 0x10 + 0x01 + 0.5 + + + [Channel3] + reverseroll + Censor LED + 0x92 + 0x10 + 0x01 + 0.5 + + + [Channel4] + reverseroll + Censor LED + 0x93 + 0x10 + 0x01 + 0.5 + + + + + + [EffectRack1_EffectUnit1] + group_[Channel1]_enable + FX 1 to Ch1 + 0x98 + 0x05 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit1] + group_[Channel2]_enable + FX 1 to Ch2 + 0x98 + 0x06 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit1] + group_[Channel3]_enable + FX 1 to Ch3 + 0x98 + 0x07 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit1] + group_[Channel4]_enable + FX 1 to Ch4 + 0x98 + 0x08 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2] + group_[Channel1]_enable + FX 2 to Ch1 + 0x99 + 0x05 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2] + group_[Channel2]_enable + FX 2 to Ch2 + 0x99 + 0x06 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2] + group_[Channel3]_enable + FX 2 to Ch3 + 0x99 + 0x07 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2] + group_[Channel4]_enable + FX 2 to Ch4 + 0x99 + 0x08 + 0x01 + 0.5 + + + + [EffectRack1_EffectUnit1_Effect1] + enabled + FX1 1 on + 0x98 + 0x00 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit1_Effect2] + enabled + FX1 2 on + 0x98 + 0x01 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit1_Effect3] + enabled + FX1 3 on + 0x98 + 0x02 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2_Effect1] + enabled + FX2 1 on + 0x99 + 0x00 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2_Effect2] + enabled + FX2 2 on + 0x99 + 0x01 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2_Effect3] + enabled + FX2 3 on + 0x99 + 0x02 + 0x01 + 0.5 + + + + [EffectRack1_EffectUnit1] + group_[Master]_enable + FX1 on master + 0x98 + 0x04 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2] + group_[Master]_enable + FX2 on master + 0x99 + 0x04 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit1] + group_[Headphone]_enable + 0x98 + 0x0A + 0x02 + 0x01 + 0.5 + + + [EffectRack1_EffectUnit2] + group_[Headphone]_enable + 0x99 + 0x0A + 0x02 + 0x01 + 0.5 + + + [Channel1] + sync_key + sync_key Ch1 + 0x90 + 0x29 + 0x02 + 0xFF + 0.5 + + + [Channel2] + sync_key + sync_key Ch2 + 0x91 + 0x29 + 0x02 + 0xFF + 0.5 + + + [Channel3] + sync_key + sync_key Ch3 + 0x92 + 0x29 + 0x02 + 0xFF + 0.5 + + + [Channel4] + sync_key + sync_key Ch4 + 0x93 + 0x29 + 0x02 + 0xFF + 0.5 + + + [Channel1] + reset_key + Default Key + 0x90 + 0x29 + 0x01 + 0.5 + + + [Channel2] + reset_key + Default Key + 0x91 + 0x29 + 0x01 + 0.5 + + + [Channel3] + reset_key + Default Key + 0x92 + 0x29 + 0x01 + 0.5 + + + [Channel4] + reset_key + Default Key + 0x93 + 0x29 + 0x01 + 0.5 + + + [Channel1] + quantize + Quantize + 0x94 + 0x47 + 0x02 + 0x01 + 0.5 + + + [Channel2] + quantize + Quantize + 0x95 + 0x47 + 0x02 + 0x01 + 0.5 + + + [Channel3] + quantize + Quantize + 0x96 + 0x47 + 0x02 + 0x01 + 0.5 + + + [Channel4] + quantize + Quantize + 0x97 + 0x47 + 0x02 + 0x01 + 0.5 + + + + From 9f44bbee24340e34ef1ea3593b2b06624e225165 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 14 Mar 2020 17:05:09 +0100 Subject: [PATCH 040/393] incorporated suggestions from first review --- res/controllers/Denon-MC7000-scripts.js | 686 +++++++++++------------- res/controllers/Denon-MC7000.midi.xml | 66 +-- 2 files changed, 356 insertions(+), 396 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 380f41d81bb8..5b79f5776333 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -1,27 +1,29 @@ /** * Denon DJ MC7000 DJ controller script for Mixxx 2.2.3 - * + * * Started in Dec. 2019 by OsZ * * 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; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * + * * Before using the mapping please make sure your MC7000 controller works for - * your operating system. For Windows you need driver software by Denon, Mac users - * should be lucky as it shall work out-of-the-box. Linux users need to compile - * their own Kernel with modified /sound/usb/clock.c see the "Denon MC7000 Mapping" - * thread at https://www.mixxx.org/forums/viewtopic.php?f=7&t=13126 + * your operating system. For Windows you need driver software by Denon, Mac users + * should be lucky as it shall work out-of-the-box. Linux users need to know that + * the MC7000 internal audio interface is not available out-of-the-box for + * older Linux Kernels. You should upgrade your Kernel to minimum versions + * LTS: 4.19.105 or 5.4.21, stable branch 5.5.5 or current 5.6 (2020-Feb-19). + * Newer Kernels will surely also provide native audio support for this controller. **/ var MC7000 = {}; @@ -36,35 +38,36 @@ var MC7000 = {}; MC7000.needleSearchPlay = false; // Pitch Fader ranges to cycle through with the "RANGE" buttons. -var lowest = 4 ;// lowest value in % (default: 4) -var low = 6 ;// next value in % (default: 6) -var middle = 10 ;// next value in % (default: 10) -var high = 16 ;// next value in % (default: 16) -var highest = 24 ;// highest value in % (default: 24) +var lowest = 4; // lowest value in % (default: 4) +var low = 6; // next value in % (default: 6) +var middle = 10; // next value in % (default: 10) +var high = 16; // next value in % (default: 16) +var highest = 24; // highest value in % (default: 24) // Platter Ring LED mode // Mode 0 = Single "off" LED chase (all others "on") // Mode 1 = Single "on" LED chase (all others "off") // use "SHIFT" + "DECK #" to toggle between both modes -MC7000.modeSingleLED = 1 ;// default: 1 +MC7000.modeSingleLED = 1; // default: 1 // Set Vinyl Mode on ("true") or off ("false") when MIXXX starts. // This sets the Jog Wheel touch detection / Vinyl Mode // and the Jog LEDs ("VINYL" on = spinny, "VINYL" off = track position). -MC7000.VinylModeOn = true ;// default: true +MC7000.VinylModeOn = true; // default: true // Scratch algorithm parameters MC7000.scratchParams = { - recordSpeed: 33.3 ,// default: 33.3 - alpha: (1.0/10) ,// default: (1.0/10) - beta: (1.0/10)/32 // default: (1.0/10)/32 + recordSpeed: 33.3, // default: 33.3 + alpha: (1.0/10), // default: (1.0/10) + beta: (1.0/10)/32 // default: (1.0/10)/32 }; // Sensitivity of the jog wheel (also depends on audio latency) -// lower values make it less, higher value more sensible MC7000.jogParams = { - jogSensitivity: 30 ,// default: 30 - maxJogValue: 3 ,// default: 3 + // Lower values for less, higher values for more sensitive + jogSensitivity: 30, // default: 30 + // this will limit the parameter of "jog" (keep between 0.5 and 3) + maxJogValue: 3 // default: 3 }; /*///////////////////////////////// @@ -76,14 +79,14 @@ MC7000.jogParams = { // Resolution of the jog wheel, set so the spinny // Jog LED to match exactly the movement of the Jog Wheel -// The physical resolution seams to be around 1100 +// The physical resolution seems to be around 1100 MC7000.jogWheelTicksPerRevolution = 894; // Pitch faders up and down values (see above for user input) MC7000.posRateRanges = [lowest/100, low/100, middle/100, high/100, highest/100]; MC7000.negRateRanges = [highest/100, high/100, middle/100, low/100, lowest/100]; -// must be "true" for Needle Search to be active +// must be "true" for Needle Search to be active MC7000.needleSearchTouched = [true, true, true, true]; // initial value for VINYL mode per Deck (see above for user input) @@ -106,76 +109,77 @@ MC7000.PADModePitch = [false, false, false, false]; // PAD Mode Colors MC7000.padColor = { - 'alloff': 0x01, // typically not needed for PADs - 'hotcueoff': 0x02, // lightblue Hot Cue inactive - 'hotcueon': 0x04, // darkblue Hot Cue active - 'sampleroff': 0x27, // light pink Sampler standard colour - 'samplerloaded': 0x38, // dark pink Sampler loaded colour - 'samplerplay': 0x09, // green Sampler playing - 'rollon': 0x10, // BeatloopRoll active colour - 'rolloff': 0x1B, // BeatloopRoll off colour - 'cueloopon': 0x0D, // Cueloop colour for activated cue point - 'cueloopoff': 0x1A // Cueloop colour inactive + "alloff": 0x01, // switch off completely + "hotcueoff": 0x02, // lightblue Hot Cue inactive + "hotcueon": 0x04, // darkblue Hot Cue active + "sampleroff": 0x27, // light pink Sampler standard colour + "samplerloaded": 0x38, // dark pink Sampler loaded colour + "samplerplay": 0x09, // green Sampler playing + "rollon": 0x10, // BeatloopRoll active colour + "rolloff": 0x1B, // BeatloopRoll off colour + "cueloopon": 0x0D, // Cueloop colour for activated cue point + "cueloopoff": 0x1A // Cueloop colour inactive }; /* DECK INITIALIZATION */ -MC7000.init = function () { - +MC7000.init = function() { + // Decks MC7000.leftDeck = new MC7000.Deck(1, 3); MC7000.rightDeck = new MC7000.Deck(2, 4); - - // set default Master Volume to give a little room for mixing - engine.setValue("[Master]", "gain", 0.85); - + + // set default Master Volume to 85% to give a little head room for mixing + // engine.setValue("[Master]", "gain", 0.85); + // VU meters - engine.connectControl("[Channel1]", "VuMeter", "MC7000.VuMeter"); - engine.connectControl("[Channel2]", "VuMeter", "MC7000.VuMeter"); - engine.connectControl("[Channel3]", "VuMeter", "MC7000.VuMeter"); - engine.connectControl("[Channel4]", "VuMeter", "MC7000.VuMeter"); - - // Platter Ring LED + print(typeof MC7000.VuMeter); + engine.makeConnection("[Channel1]", "VuMeter", MC7000.VuMeter); + engine.makeConnection("[Channel2]", "VuMeter", MC7000.VuMeter); + engine.makeConnection("[Channel3]", "VuMeter", MC7000.VuMeter); + engine.makeConnection("[Channel4]", "VuMeter", MC7000.VuMeter); + + // Platter Ring LED midi.sendShortMsg(0x90, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x91, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x92, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x93, 0x64, MC7000.modeSingleLED); - engine.connectControl("[Channel1]", "playposition", "MC7000.JogLed"); - engine.connectControl("[Channel2]", "playposition", "MC7000.JogLed"); - engine.connectControl("[Channel3]", "playposition", "MC7000.JogLed"); - engine.connectControl("[Channel4]", "playposition", "MC7000.JogLed"); - - // Vinyl mode LEDs + engine.makeConnection("[Channel1]", "playposition", MC7000.JogLed); + engine.makeConnection("[Channel2]", "playposition", MC7000.JogLed); + engine.makeConnection("[Channel3]", "playposition", MC7000.JogLed); + engine.makeConnection("[Channel4]", "playposition", MC7000.JogLed); + + // Vinyl mode LEDs midi.sendShortMsg(0x90, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); midi.sendShortMsg(0x91, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); midi.sendShortMsg(0x92, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); midi.sendShortMsg(0x93, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); - + // PAD Mode LEDs for (var i = 1; i <= 8; i++) { - engine.connectControl("[Channel1]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); - engine.connectControl("[Channel2]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); - engine.connectControl("[Channel3]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); - engine.connectControl("[Channel4]", "hotcue_"+i+"_enabled", "MC7000.HotCueLED"); - }; - + engine.makeConnection("[Channel1]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); + engine.makeConnection("[Channel2]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); + engine.makeConnection("[Channel3]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); + engine.makeConnection("[Channel4]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); + } + // Sampler Volume Control - MC7000.samplerLevel = function (channel, control, value, status, group) { + MC7000.samplerLevel = function(channel, control, value) { // check if the Sampler Volume is at Zero and if so hide the sampler bank if (value > 0x00) { engine.setValue("[Samplers]", "show_samplers", true); } else { engine.setValue("[Samplers]", "show_samplers", false); - }; - // get the Sampler Rows opened with its details - engine.setValue("[SamplerRow1]", "expanded", true); - engine.setValue("[SamplerRow2]", "expanded", true); - - //control up to 16 sampler volumes with the one knob on the mixer - for (var i = 1; i <= 16; i++) { + } + // get the Sampler Rows opened with its details + engine.setValue("[SamplerRow1]", "expanded", true); + engine.setValue("[SamplerRow2]", "expanded", true); + + //control up to 16 sampler volumes with the one knob on the mixer + for (var i = 1; i <= 16; i++) { engine.setValue("[Sampler"+i+"]", "pregain", script.absoluteNonLin(value, 0, 1.0, 4.0)); - }; + } }; - + // The SysEx message to send to the controller to force the midi controller // to send the status of every item on the control surface. var ControllerStatusSysex = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7]; @@ -187,16 +191,16 @@ MC7000.init = function () { }; /* CONSTRUCTOR FOR DECK OBJECT */ -MC7000.Deck = function(channel) { - +MC7000.Deck = function() { + // PAD Mode Hot Cue MC7000.padModeCue = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - // set HotCue Mode true - MC7000.PADModeCue[deckNumber] = true; - MC7000.PADModeCueLoop[deckNumber] = false; + // set HotCue Mode true + MC7000.PADModeCue[deckNumber] = true; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -205,23 +209,23 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } // change PAD color when switching to Hot Cue Mode for (var i = 1; i <= 8; i++) { if (engine.getValue(group, "hotcue_"+i+"_enabled", true)) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.hotcueon); } else { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.hotcueoff); - }; - }; + } + } }; // PAD Mode Cue Loop MC7000.padModeCueLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = true; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = true; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -230,18 +234,18 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - }; + } }; // PAD Mode Flip MC7000.padModeFlip = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = true; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -250,18 +254,18 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - }; + } }; // PAD Mode Roll MC7000.padModeRoll = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = true; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -270,18 +274,18 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.rolloff); - }; + } }; // PAD Mode Saved Loop MC7000.padModeSavedLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = true; @@ -290,18 +294,18 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - }; + } }; // PAD Mode Slicer MC7000.padModeSlicer = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -310,18 +314,18 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - }; + } }; // PAD Mode Slicer Loop MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -330,18 +334,18 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - }; + } }; // PAD Mode Sampler MC7000.padModeSampler = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -350,28 +354,26 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = true; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - }; + } // change PAD color when switching to Sampler Mode for (var i = 1; i <= 8; i++) { - if(engine.getValue("[Sampler"+i+"]", "play")) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); - } - else if(engine.getValue("[Sampler"+i+"]", "track_loaded") === 0 ) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.sampleroff); - } - else if(engine.getValue("[Sampler"+i+"]", "track_loaded") === 1 - && engine.getValue("[Sampler"+i+"]", "play") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); - }; - }; + if (engine.getValue("[Sampler"+i+"]", "play")) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); + } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.sampleroff); + } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1 + && engine.getValue("[Sampler"+i+"]", "play") === 0) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); + } + } }; // PAD Mode Velocity Sampler MC7000.padModeVelSamp = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -380,18 +382,18 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = true; MC7000.PADModePitch[deckNumber] = false; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - }; + } }; // PAD Mode Slicer MC7000.padModePitch = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; MC7000.PADModeFlip[deckNumber] = false; MC7000.PADModeRoll[deckNumber] = false; MC7000.PADModeSavedLoop[deckNumber] = false; @@ -400,309 +402,274 @@ MC7000.Deck = function(channel) { MC7000.PADModeSampler[deckNumber] = false; MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = true; - }; + } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - }; + } }; - - // PAD buttons - MC7000.PadButtons = function (channel, control, value, status, group) { + + // PAD buttons + MC7000.PadButtons = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); - - // activate and clear Hot Cues + + // activate and clear Hot Cues if (MC7000.PADModeCue[deckNumber] && engine.getValue(group, "track_loaded") === 1) { for (var i = 1; i <= 8; i++) { if (control === 0x14 + i -1 && value >= 0x01) { - engine.setValue(group, "hotcue_"+i+"_activate", true); + engine.setValue(group, "hotcue_"+i+"_activate", true); } else { - engine.setValue(group, "hotcue_"+i+"_activate", false); - }; + engine.setValue(group, "hotcue_"+i+"_activate", false); + } if (control === 0x1C + i -1 && value >= 0x01) { - engine.setValue(group, "hotcue_"+i+"_clear", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.hotcueoff); - }; - }; - } - // Cue Loop - else if (MC7000.PADModeFlip[deckNumber]) { - return; - } - // Flip - else if (MC7000.PADModeFlip[deckNumber]) { - return; - } - // Roll - else if (MC7000.PADModeRoll[deckNumber]) { + engine.setValue(group, "hotcue_"+i+"_clear", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.hotcueoff); + } + } + } else if (MC7000.PADModeFlip[deckNumber]) { + return; + } else if (MC7000.PADModeFlip[deckNumber]) { + return; + } else if (MC7000.PADModeRoll[deckNumber]) { if (control === 0x14 && value >= 0x01) { engine.setValue(group, "beatlooproll_0.0625_activate", true); midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.rollon); - } - else if (control === 0x14 && value >= 0x00) { + } else if (control === 0x14 && value >= 0x00) { engine.setValue(group, "beatlooproll_0.0625_activate", false); midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.rolloff); - } - else if (control === 0x15 && value >= 0x01) { + } else if (control === 0x15 && value >= 0x01) { engine.setValue(group, "beatlooproll_0.125_activate", true); midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.rollon); - } - else if (control === 0x15 && value >= 0x00) { + } else if (control === 0x15 && value >= 0x00) { engine.setValue(group, "beatlooproll_0.125_activate", false); midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.rolloff); - } - else if (control === 0x16 && value >= 0x01) { - engine.setValue(group, "beatlooproll_0.25_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rollon); - } - else if (control === 0x16 && value >= 0x00) { - engine.setValue(group, "beatlooproll_0.25_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rolloff); - } - else if (control === 0x17 && value >= 0x01) { - engine.setValue(group, "beatlooproll_0.5_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rollon); - } - else if (control === 0x17 && value >= 0x00) { - engine.setValue(group, "beatlooproll_0.5_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rolloff); - } - else if (control === 0x18 && value >= 0x01) { + } else if (control === 0x16 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.25_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rollon); + } else if (control === 0x16 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.25_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rolloff); + } else if (control === 0x17 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.5_activate", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rollon); + } else if (control === 0x17 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.5_activate", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rolloff); + } else if (control === 0x18 && value >= 0x01) { engine.setValue(group, "beatlooproll_1_activate", true); midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.rollon); - } - else if (control === 0x18 && value >= 0x00) { + } else if (control === 0x18 && value >= 0x00) { engine.setValue(group, "beatlooproll_1_activate", false); midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.rolloff); - } - else if (control === 0x19 && value >= 0x01) { + } else if (control === 0x19 && value >= 0x01) { engine.setValue(group, "beatlooproll_2_activate", true); midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.rollon); - } - else if (control === 0x19 && value >= 0x00) { + } else if (control === 0x19 && value >= 0x00) { engine.setValue(group, "beatlooproll_2_activate", false); midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.rolloff); - } - else if (control === 0x1A && value >= 0x01) { + } else if (control === 0x1A && value >= 0x01) { engine.setValue(group, "beatlooproll_4_activate", true); midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.rollon); - } - else if (control === 0x1A && value >= 0x00) { + } else if (control === 0x1A && value >= 0x00) { engine.setValue(group, "beatlooproll_4_activate", false); midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.rolloff); - } - else if (control === 0x1B && value >= 0x01) { + } else if (control === 0x1B && value >= 0x01) { engine.setValue(group, "beatlooproll_8_activate", true); midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.rollon); - } - else if (control === 0x1B && value >= 0x00) { + } else if (control === 0x1B && value >= 0x00) { engine.setValue(group, "beatlooproll_8_activate", false); midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.rolloff); } - } - // Saved Loop - else if (MC7000.PADModeSavedLoop[deckNumber]) { - return; - } - // Slicer - else if (MC7000.PADModeSlicer[deckNumber]) { - return; - } - // Slicer Loop - else if (MC7000.PADModeSlicerLoop[deckNumber]) { - return; - } - // Sampler 1 - 8 - else if (MC7000.PADModeSampler[deckNumber]) { - for (var i = 1; i <= 8; i++) { + } else if (MC7000.PADModeSavedLoop[deckNumber]) { + return; + } else if (MC7000.PADModeSlicer[deckNumber]) { + return; + } else if (MC7000.PADModeSlicerLoop[deckNumber]) { + return; + } else if (MC7000.PADModeSampler[deckNumber]) { + for (i = 1; i <= 8; i++) { if (control === 0x14 + i -1 && value >= 0x01) { // 1st - check if track is loaded - if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { - engine.setValue("[Sampler"+i+"]", "LoadSelectedTrack", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); - } - // 2nd - if track is playing then stop it - else if(engine.getValue("[Sampler"+i+"]", "play") === 1) { - engine.setValue("[Sampler"+i+"]", "start_stop", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); - } - // 3rd - if track is loaded but not playing - else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1) { - + if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { + engine.setValue("[Sampler"+i+"]", "LoadSelectedTrack", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); + } + // 2nd - if track is playing then stop it + else if (engine.getValue("[Sampler"+i+"]", "play") === 1) { + engine.setValue("[Sampler"+i+"]", "start_stop", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); + } + // 3rd - if track is loaded but not playing + else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1) { + engine.setValue("[Sampler"+i+"]", "start_play", 1); midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); - // var samplerlength = engine.getValue("[Sampler"+i+"]", "duration"); + // var samplerlength = engine.getValue("[Sampler"+i+"]", "duration"); } + } else if (control === 0x1C + i -1 && value >= 0x01) { + engine.setValue("[Sampler"+i+"]", "play", 0); + engine.setValue("[Sampler"+i+"]", "eject", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i -1, MC7000.padColor.sampleroff); + engine.setValue("[Sampler"+i+"]", "eject", 0); } - else if (control === 0x1C + i -1 && value >= 0x01) { - engine.setValue("[Sampler"+i+"]", "play", 0); - engine.setValue("[Sampler"+i+"]", "eject", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i -1, MC7000.padColor.sampleroff); - engine.setValue("[Sampler"+i+"]", "eject", 0); - }; - }; + } // TODO: check for the actual status of LEDs again on other decks - } - // Velocity Sampler - else if (MC7000.PADModeVelSamp[deckNumber]) { - return; - } - // Pitch - else if (MC7000.PADModePitch[deckNumber]) { - return; + } else if (MC7000.PADModeVelSamp[deckNumber]) { + return; + } else if (MC7000.PADModePitch[deckNumber]) { + return; } }; - + // Toggle Vinyl Mode MC7000.vinylModeToggle = function(channel, control, value, status, group) { if (value === 0x00) return; // don't respond to note off messages - + if (value === 0x7F) { var deckNumber = script.deckFromGroup(group); MC7000.isVinylMode[deckNumber] = !MC7000.isVinylMode[deckNumber]; midi.sendShortMsg(0x90 + channel, 0x07, MC7000.isVinylMode[deckNumber] ? 0x7F: 0x01); - }; - }; - - // The button that enables/disables scratching + } + }; + + // The button that enables/disables scratching MC7000.wheelTouch = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (MC7000.isVinylMode[deckNumber]) { - if (value === 0x7F) { - engine.scratchEnable(deckNumber, MC7000.jogWheelTicksPerRevolution, MC7000.scratchParams.recordSpeed, MC7000.scratchParams.alpha, MC7000.scratchParams.beta); - } else { - engine.scratchDisable(deckNumber); - } - } + var deckNumber = script.deckFromGroup(group); + if (MC7000.isVinylMode[deckNumber]) { + if (value === 0x7F) { + engine.scratchEnable(deckNumber, MC7000.jogWheelTicksPerRevolution, MC7000.scratchParams.recordSpeed, MC7000.scratchParams.alpha, MC7000.scratchParams.beta); + } else { + engine.scratchDisable(deckNumber); + } + } }; - + // The wheel that actually controls the scratching MC7000.wheelTurn = function(channel, control, value, status, group) { - - // A: For a control that centers on 0: - var numTicks = (value < 0x64) ? value: (value - 128); + + // A: For a control that centers on 0: + var numTicks = (value < 0x64) ? value: (value - 128); var deckNumber = script.deckFromGroup(group); if (engine.isScratching(deckNumber)) { - // Scratch! + // Scratch! engine.scratchTick(deckNumber, numTicks); } else { - // Pitch bend + // Pitch bend var jogDelta = numTicks/MC7000.jogWheelTicksPerRevolution*MC7000.jogParams.jogSensitivity; var jogAbsolute = jogDelta + engine.getValue(group, "jog"); - engine.setValue(group, 'jog', Math.max(-MC7000.jogParams.maxJogValue, Math.min(MC7000.jogParams.maxJogValue, jogAbsolute))); + engine.setValue(group, "jog", Math.max(-MC7000.jogParams.maxJogValue, Math.min(MC7000.jogParams.maxJogValue, jogAbsolute))); } }; - + // Needle Search Touch detection MC7000.needleSearchTouch = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (engine.getValue(group, "play")) { - MC7000.needleSearchTouched[deckNumber] = MC7000.needleSearchPlay && (value ? true : false); + MC7000.needleSearchTouched[deckNumber] = MC7000.needleSearchPlay && (!!value); } else { - MC7000.needleSearchTouched[deckNumber] = value ? true : false; + MC7000.needleSearchTouched[deckNumber] = !!value; } }; - + // Needle Search Touch while "SHIFT" button is pressed MC7000.needleSearchTouchShift = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); - MC7000.needleSearchTouched[deckNumber] = value ? true : false; + MC7000.needleSearchTouched[deckNumber] = !!value; }; - // Needle Search Position detection (LSB) - MC7000.needleSearchLSB = function(channel, control, value, status, group) { - MC7000.needleDropLSB = value; // just defining rough position + // Needle Search Position detection (MSB) + MC7000.needleSearchMSB = function(channel, control, value) { + MC7000.needleDropMSB = value; // just defining rough position }; - - // Needle Search Position detection (LSB + MSB) + + // Needle Search Position detection (MSB + LSB) MC7000.needleSearchStripPosition = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (MC7000.needleSearchTouched[deckNumber]) { - var fullValue = (MC7000.needleDropLSB << 7) + value; // move LSB 7 binary gigits to the left and add MSB + var fullValue = (MC7000.needleDropMSB << 7) + value; // move MSB 7 binary gigits to the left and add LSB var position = (fullValue / 0x3FFF); // devide by all possible positions to get relative between 0 - 1 engine.setParameter(group, "playposition", position); } }; - - // Pitch Fader (LSB) - MC7000.pitchFaderLSB = function(channel, control, value, status, group) { - MC7000.pitchLSB = value; // just defining rough position + + // Pitch Fader (MSB) + MC7000.pitchFaderMSB = function(channel, control, value) { + MC7000.pitchMSB = value; // just defining rough position }; - - // Pitch Fader Position (LSB + MSB) + + // Pitch Fader Position (MSB + LSB) MC7000.pitchFaderPosition = function(channel, control, value, status, group) { - var fullValue = (MC7000.pitchLSB << 7) + value; + var fullValue = (MC7000.pitchMSB << 7) + value; var position = 1 - (fullValue / 0x3FFF); // 1 - () to turn around the direction engine.setParameter(group, "rate", position); }; - + // Next Rate range toggle MC7000.nextRateRange = function(midichan, control, value, status, group) { if (value === 0) return; // don't respond to note off messages var currRateRange = engine.getValue(group, "rateRange"); engine.setValue(group, "rateRange", MC7000.getNextRateRange(currRateRange)); }; - + // Previous Rate range toggle MC7000.prevRateRange = function(midichan, control, value, status, group) { if (value === 0) return; // don't respond to note off messages var currRateRange = engine.getValue(group, "rateRange"); engine.setValue(group, "rateRange", MC7000.getPrevRateRange(currRateRange)); }; - - // Key Select + + // Key Select MC7000.keySelect = function(midichan, control, value, status, group) { - if (value === 0x01) { - engine.setValue(group, "pitch_up", true); - } - else if (value === 0x7F) { - engine.setValue(group, "pitch_down", true); - } + if (value === 0x01) { + engine.setValue(group, "pitch_up", true); + } else if (value === 0x7F) { + engine.setValue(group, "pitch_down", true); + } }; - + // Assign Channel to Crossfader MC7000.crossfaderAssign = function(channel, control, value, status, group) { - // Centre position - if (value === 0x00) { + // Centre position + if (value === 0x00) { engine.setValue(group, "orientation", 1); } // Left position - else if (value === 0x01) { + else if (value === 0x01) { engine.setValue(group, "orientation", 0); } // Right position - else if (value === 0x02) { + else if (value === 0x02) { engine.setValue(group, "orientation", 2); } }; - + // Assign Spinback length to STOP TIME knob MC7000.stopTime = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - // "factor" for engine.brake() - // this formula produces factors between 31 (min STOP TIME for ca 7 sec back in track) - // and 1 (max STOP TIME for ca 18.0 sec back in track) - MC7000.factor[deckNumber] = (1.1 - (value / 127)) * 30 - 2; - }; - + var deckNumber = script.deckFromGroup(group); + // "factor" for engine.brake() + // this formula produces factors between 31 (min STOP TIME for ca 7 sec back in track) + // and 1 (max STOP TIME for ca 18.0 sec back in track) + MC7000.factor[deckNumber] = (1.1 - (value / 127)) * 30 - 2; + }; + // Use the CENSOR button as Spinback with STOP TIME adjusted length - MC7000.brake_button = function(channel, control, value, status, group) { + MC7000.censor = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); - var deck = parseInt(group.substring(8,9)); // work out which deck we are using + var deck = parseInt(group.substring(8, 9)); // work out which deck we are using engine.brake(deck, value > 0, MC7000.factor[deckNumber], - 15); // start at a rate of -15 and decrease by "factor" }; }; /* SET CROSSFADER CURVE */ -MC7000.crossFaderCurve = function (control, value) { +MC7000.crossFaderCurve = function(control, value) { script.crossfaderCurve(value); }; /* Set FX wet/dry value */ MC7000.fxWetDry = function(midichan, control, value, status, group) { - var numTicks = (value < 0x64) ? value: (value - 128); - var newVal = engine.getValue(group, "mix") + numTicks/64*2; - engine.setValue(group, "mix", Math.max(0, Math.min(1, newVal))); + var numTicks = (value < 0x64) ? value: (value - 128); + var newVal = engine.getValue(group, "mix") + numTicks/64*2; + engine.setValue(group, "mix", Math.max(0, Math.min(1, newVal))); }; /* Next Rate range calculation */ @@ -724,36 +691,29 @@ MC7000.getPrevRateRange = function(currRateRange) { } return MC7000.negRateRanges[0]; }; - + /* LEDs for VuMeter */ // VuMeters only for Channel 1-4 / Master is on Hardware MC7000.VuMeter = function(value, group) { - var deckNumber = script.deckFromGroup(group), - peak = 0x76, // where the red LED starts (clipping indicator) - level = value*value*value*value*0x69; + var VuMeterLEDPeakValue = 0x76, + VULevelOutValue = engine.getValue(group, "PeakIndicator") ? VuMeterLEDPeakValue : value*value*value*value*0x69, + deckNumber = script.deckFromGroup(group); - if (engine.getValue(group, "PeakIndicator")) { - var level = peak; - } - // now send the level meter signal to controller - midi.sendShortMsg(0xB0 + deckNumber - 1, 0x1F, level); + midi.sendShortMsg(0xB0 + deckNumber - 1, 0x1F, VULevelOutValue); }; /* LEDs around Jog wheel */ MC7000.JogLed = function(value, group) { - var deckNumber = script.deckFromGroup(group); - // do nothing before track starts - if (value < 0) return; - // While "VINYL" is active show spinny LEDs - if (MC7000.isVinylMode[deckNumber]) { - var trackDuration = engine.getValue(group, "duration"), - position = value * trackDuration / 60 * MC7000.scratchParams.recordSpeed, - activeLED = Math.round(position * 96) % 96; - // While "VINYL" is off show track position - } else { - var activeLED = value * 96; - }; - // sending the position of active LED to the controller + var deckNumber = script.deckFromGroup(group); + // do nothing before track starts + if (value < 0) return; + + var trackDuration = engine.getValue(group, "duration"), + position = value * trackDuration / 60 * MC7000.scratchParams.recordSpeed, + // LED ring contains 48 segments with each LED activated by the next even number + LEDmidiSignal = 48 * 2, + activeLED = MC7000.isVinylMode[deckNumber] ? Math.round(position * LEDmidiSignal) % LEDmidiSignal : value * LEDmidiSignal; + midi.sendShortMsg(0x90 + deckNumber -1, 0x06, activeLED); }; @@ -765,75 +725,75 @@ MC7000.HotCueLED = function(value, group) { if (value === 1) { if (engine.getValue(group, "hotcue_"+i+"_enabled") === 1) { midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueon); - } + } } else { if (engine.getValue(group, "hotcue_"+i+"_enabled") === 0) { midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueoff); } - }; - }; - }; + } + } + } }; /* CONTROLLER SHUTDOWN */ -MC7000.shutdown = function () { - -// Need to switch off LEDs one by one, -// otherwise the controller cannot handle the signal traffic +MC7000.shutdown = function() { + + // Need to switch off LEDs one by one, + // otherwise the controller cannot handle the signal traffic // Switch off Transport section LEDs for (var i = 0; i <= 3; i++) { - midi.sendShortMsg(0x90 + i, 0x00, 0x01); - midi.sendShortMsg(0x90 + i, 0x01, 0x01); - midi.sendShortMsg(0x90 + i, 0x02, 0x01); - midi.sendShortMsg(0x90 + i, 0x03, 0x01); - midi.sendShortMsg(0x90 + i, 0x04, 0x01); - midi.sendShortMsg(0x90 + i, 0x05, 0x01); - }; + midi.sendShortMsg(0x90 + i, 0x00, 0x01); + midi.sendShortMsg(0x90 + i, 0x01, 0x01); + midi.sendShortMsg(0x90 + i, 0x02, 0x01); + midi.sendShortMsg(0x90 + i, 0x03, 0x01); + midi.sendShortMsg(0x90 + i, 0x04, 0x01); + midi.sendShortMsg(0x90 + i, 0x05, 0x01); + } // Switch off Loop Section LEDs - for (var i = 0; i <= 3; i++) { - midi.sendShortMsg(0x94 + i, 0x32, 0x01); - midi.sendShortMsg(0x94 + i, 0x33, 0x01); - midi.sendShortMsg(0x94 + i, 0x34, 0x01); - midi.sendShortMsg(0x94 + i, 0x35, 0x01); - midi.sendShortMsg(0x94 + i, 0x38, 0x01); - midi.sendShortMsg(0x94 + i, 0x39, 0x01); - // switch PAD Mode to CUE LED - midi.sendShortMsg(0x94 + i, 0x00, 0x04); - }; + for (i = 0; i <= 3; i++) { + midi.sendShortMsg(0x94 + i, 0x32, 0x01); + midi.sendShortMsg(0x94 + i, 0x33, 0x01); + midi.sendShortMsg(0x94 + i, 0x34, 0x01); + midi.sendShortMsg(0x94 + i, 0x35, 0x01); + midi.sendShortMsg(0x94 + i, 0x38, 0x01); + midi.sendShortMsg(0x94 + i, 0x39, 0x01); + // switch PAD Mode to CUE LED + midi.sendShortMsg(0x94 + i, 0x00, 0x04); + } // Switch all PAD LEDs to HotCue mode - for (var i = 0x14; i <= 0x1B; i++) { + for (i = 0x14; i <= 0x1B; i++) { midi.sendShortMsg(0x94, i, 0x02); midi.sendShortMsg(0x95, i, 0x02); midi.sendShortMsg(0x96, i, 0x02); midi.sendShortMsg(0x97, i, 0x02); - }; + } // Switch off Channel Cue, VINYL, SLIP, KEY LOCK LEDs - for (var i = 0; i <= 3; i++) { - midi.sendShortMsg(0x90 + i, 0x07, 0x01); - midi.sendShortMsg(0x90 + i, 0x0F, 0x01); - midi.sendShortMsg(0x90 + i, 0x0D, 0x01); - midi.sendShortMsg(0x90 + i, 0x1B, 0x01); - }; + for (i = 0; i <= 3; i++) { + midi.sendShortMsg(0x90 + i, 0x07, 0x01); + midi.sendShortMsg(0x90 + i, 0x0F, 0x01); + midi.sendShortMsg(0x90 + i, 0x0D, 0x01); + midi.sendShortMsg(0x90 + i, 0x1B, 0x01); + } // Switch off FX Section LEDs - for (var i = 0; i <= 1; i++) { - midi.sendShortMsg(0x98 + i, 0x00, 0x01); - midi.sendShortMsg(0x98 + i, 0x01, 0x01); - midi.sendShortMsg(0x98 + i, 0x02, 0x01); - midi.sendShortMsg(0x98 + i, 0x04, 0x01); - midi.sendShortMsg(0x98 + i, 0x0A, 0x01); - midi.sendShortMsg(0x98 + i, 0x05, 0x01); - midi.sendShortMsg(0x98 + i, 0x06, 0x01); - midi.sendShortMsg(0x98 + i, 0x07, 0x01); - midi.sendShortMsg(0x98 + i, 0x08, 0x01); - }; + for (i = 0; i <= 1; i++) { + midi.sendShortMsg(0x98 + i, 0x00, 0x01); + midi.sendShortMsg(0x98 + i, 0x01, 0x01); + midi.sendShortMsg(0x98 + i, 0x02, 0x01); + midi.sendShortMsg(0x98 + i, 0x04, 0x01); + midi.sendShortMsg(0x98 + i, 0x0A, 0x01); + midi.sendShortMsg(0x98 + i, 0x05, 0x01); + midi.sendShortMsg(0x98 + i, 0x06, 0x01); + midi.sendShortMsg(0x98 + i, 0x07, 0x01); + midi.sendShortMsg(0x98 + i, 0x08, 0x01); + } // Reset Level Meters and JogLED - for (var i = 0; i <= 3; i++) { + for (i = 0; i <= 3; i++) { // Switch off Level Meters midi.sendShortMsg(0xB0 + i, 0x1F, 0x00); // Platter Ring: Reset JogLED to Zero position midi.sendShortMsg(0x90 + i, 0x06, 0x00); // Platter Ring: Switch all LEDs on midi.sendShortMsg(0x90 + i, 0x64, 0x00); - }; + } }; diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index eb68ffa6a6fd..65f0bdef4cdb 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -253,7 +253,7 @@ - + [Channel1] MC7000.padModeSlicerLoop @@ -293,7 +293,7 @@ - + [Channel1] MC7000.padModeSampler @@ -373,7 +373,7 @@ - + [Channel1] MC7000.padModePitch @@ -413,7 +413,7 @@ - + [Channel1] @@ -457,7 +457,7 @@ [Channel1] - MC7000.pitchFaderLSB + MC7000.pitchFaderMSB MIDI Learned from 180 messages. 0xB0 0x09 @@ -467,7 +467,7 @@ [Channel2] - MC7000.pitchFaderLSB + MC7000.pitchFaderMSB MIDI Learned from 144 messages. 0xB1 0x09 @@ -477,7 +477,7 @@ [Channel3] - MC7000.pitchFaderLSB + MC7000.pitchFaderMSB MIDI Learned from 150 messages. 0xB2 0x09 @@ -487,7 +487,7 @@ [Channel4] - MC7000.pitchFaderLSB + MC7000.pitchFaderMSB MIDI Learned from 128 messages. 0xB3 0x09 @@ -681,7 +681,7 @@ [Channel1] - MC7000.needleSearchLSB + MC7000.needleSearchMSB Jump to track position (Needle Search) 0xB0 0x2B @@ -721,7 +721,7 @@ [Channel2] - MC7000.needleSearchLSB + MC7000.needleSearchMSB Jump to track position (Needle Search) 0xB1 0x2B @@ -761,7 +761,7 @@ [Channel3] - MC7000.needleSearchLSB + MC7000.needleSearchMSB Jump to track position (Needle Search) 0xB2 0x2B @@ -801,7 +801,7 @@ [Channel4] - MC7000.needleSearchLSB + MC7000.needleSearchMSB Jump to track position (Needle Search) 0xB3 0x2B @@ -1058,7 +1058,7 @@ - + [EqualizerRack1_[Channel1]_Effect1] parameter1 @@ -1078,7 +1078,7 @@ - + [EqualizerRack1_[Channel3]_Effect1] parameter1 @@ -1179,7 +1179,7 @@ - + [Channel1] volume @@ -1270,7 +1270,7 @@ - + [Channel1] play @@ -1440,7 +1440,7 @@ - + [Channel1] sync_enabled @@ -1470,7 +1470,7 @@ - + [Channel4] sync_enabled @@ -1744,7 +1744,7 @@ [Channel3] - loop_in + loop_in Loop in Ch3 0x96 0x38 @@ -1761,7 +1761,7 @@ - + [Channel1] loop_out @@ -1784,7 +1784,7 @@ [Channel3] - loop_out + loop_out Loop out Ch3 0x96 0x39 @@ -1824,7 +1824,7 @@ [Channel3] - reloop_toggle + reloop_toggle Reloop Ch3 0x96 0x33 @@ -2206,7 +2206,7 @@ - + [EffectRack1_EffectUnit1_Effect1] enabled @@ -2267,7 +2267,7 @@ - + [EffectRack1_EffectUnit2_Effect1] enabled @@ -2430,7 +2430,7 @@ - + [Channel1] slip_enabled @@ -2473,7 +2473,7 @@ [Channel1] - MC7000.brake_button + MC7000.censor Backspin Ch1 0x90 0x10 @@ -2483,7 +2483,7 @@ [Channel2] - MC7000.brake_button + MC7000.censor Backspin Ch2 0x91 0x10 @@ -2493,7 +2493,7 @@ [Channel3] - MC7000.brake_button + MC7000.censor Backspin Ch3 0x92 0x10 @@ -2503,7 +2503,7 @@ [Channel4] - MC7000.brake_button + MC7000.censor Backspin Ch4 0x93 0x10 @@ -3434,9 +3434,9 @@ - + - + [Channel1] @@ -3767,7 +3767,7 @@ 0.5 - + [EffectRack1_EffectUnit1] @@ -3841,7 +3841,7 @@ 0x01 0.5 - + [EffectRack1_EffectUnit1_Effect1] enabled From c4905a485c5e89b45eaf45c40d63fb643b4b4c40 Mon Sep 17 00:00:00 2001 From: OsZ <58949409+toszlanyi@users.noreply.github.com> Date: Sat, 14 Mar 2020 17:34:33 +0100 Subject: [PATCH 041/393] Update res/controllers/Denon-MC7000-scripts.js Co-Authored-By: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> --- res/controllers/Denon-MC7000-scripts.js | 1 - 1 file changed, 1 deletion(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 5b79f5776333..b4efa1207b6c 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -132,7 +132,6 @@ MC7000.init = function() { // engine.setValue("[Master]", "gain", 0.85); // VU meters - print(typeof MC7000.VuMeter); engine.makeConnection("[Channel1]", "VuMeter", MC7000.VuMeter); engine.makeConnection("[Channel2]", "VuMeter", MC7000.VuMeter); engine.makeConnection("[Channel3]", "VuMeter", MC7000.VuMeter); From 5767379a442e97accd5b3e7ac210c2bad4a8a251 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sun, 15 Mar 2020 12:25:31 +0100 Subject: [PATCH 042/393] 2nd mod - reworked CodeFactor issues and Sampler section --- res/controllers/Denon-MC7000-scripts.js | 78 ++++++++++++++----------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index b4efa1207b6c..5a29a3ed7a6b 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -107,6 +107,9 @@ MC7000.PADModeSampler = [false, false, false, false]; MC7000.PADModeVelSamp = [false, false, false, false]; MC7000.PADModePitch = [false, false, false, false]; +// Define the MIDI signal for red LED at VU Meters +MC7000.VuMeterLEDPeakValue = 0x76; + // PAD Mode Colors MC7000.padColor = { "alloff": 0x01, // switch off completely @@ -161,6 +164,13 @@ MC7000.init = function() { engine.makeConnection("[Channel4]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); } + // Sampler Mode LED indicator + for (i = 1; i <= 8; i++) { + engine.makeConnection("[Sampler"+i+"]", "track_loaded", MC7000.SamplerLED); + engine.makeConnection("[Sampler"+i+"]", "play", MC7000.SamplerLED); + } + + // Sampler Volume Control MC7000.samplerLevel = function(channel, control, value) { // check if the Sampler Volume is at Zero and if so hide the sampler bank @@ -360,8 +370,7 @@ MC7000.Deck = function() { midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.sampleroff); - } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1 - && engine.getValue("[Sampler"+i+"]", "play") === 0) { + } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1 && engine.getValue("[Sampler"+i+"]", "play") === 0) { midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); } } @@ -487,29 +496,19 @@ MC7000.Deck = function() { } else if (MC7000.PADModeSampler[deckNumber]) { for (i = 1; i <= 8; i++) { if (control === 0x14 + i -1 && value >= 0x01) { - // 1st - check if track is loaded if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { engine.setValue("[Sampler"+i+"]", "LoadSelectedTrack", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); - } - // 2nd - if track is playing then stop it - else if (engine.getValue("[Sampler"+i+"]", "play") === 1) { - engine.setValue("[Sampler"+i+"]", "start_stop", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); - } - // 3rd - if track is loaded but not playing - else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1) { - - engine.setValue("[Sampler"+i+"]", "start_play", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); - // var samplerlength = engine.getValue("[Sampler"+i+"]", "duration"); - + } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1) { + engine.setValue("[Sampler"+i+"]", "cue_gotoandplay", 1); } } else if (control === 0x1C + i -1 && value >= 0x01) { - engine.setValue("[Sampler"+i+"]", "play", 0); - engine.setValue("[Sampler"+i+"]", "eject", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i -1, MC7000.padColor.sampleroff); - engine.setValue("[Sampler"+i+"]", "eject", 0); + if (engine.getValue("[Sampler"+i+"]", "play") === 1) { + engine.setValue("[Sampler"+i+"]", "cue_gotoandstop", 1); + } else { + engine.setValue("[Sampler"+i+"]", "eject", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i -1, MC7000.padColor.sampleroff); + engine.setValue("[Sampler"+i+"]", "eject", 0); + } } } // TODO: check for the actual status of LEDs again on other decks @@ -628,17 +627,12 @@ MC7000.Deck = function() { // Assign Channel to Crossfader MC7000.crossfaderAssign = function(channel, control, value, status, group) { - // Centre position if (value === 0x00) { - engine.setValue(group, "orientation", 1); - } - // Left position - else if (value === 0x01) { - engine.setValue(group, "orientation", 0); - } - // Right position - else if (value === 0x02) { - engine.setValue(group, "orientation", 2); + engine.setValue(group, "orientation", 1); // Centre position + } else if (value === 0x01) { + engine.setValue(group, "orientation", 0); // Right position + } else if (value === 0x02) { + engine.setValue(group, "orientation", 2); // Left position } }; @@ -694,8 +688,7 @@ MC7000.getPrevRateRange = function(currRateRange) { /* LEDs for VuMeter */ // VuMeters only for Channel 1-4 / Master is on Hardware MC7000.VuMeter = function(value, group) { - var VuMeterLEDPeakValue = 0x76, - VULevelOutValue = engine.getValue(group, "PeakIndicator") ? VuMeterLEDPeakValue : value*value*value*value*0x69, + var VULevelOutValue = engine.getValue(group, "PeakIndicator") ? MC7000.VuMeterLEDPeakValue : value*value*value*value*0x69, deckNumber = script.deckFromGroup(group); midi.sendShortMsg(0xB0 + deckNumber - 1, 0x1F, VULevelOutValue); @@ -734,6 +727,25 @@ MC7000.HotCueLED = function(value, group) { } }; +// Sampler LED +MC7000.SamplerLED = function() { + for (var j = 1; j <= 4; j++) { + for (var i = 1; i <= 8; i++) { + if (MC7000.PADModeSampler[j]) { + if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1) { + if (engine.getValue("[Sampler"+i+"]", "play") === 0) { + midi.sendShortMsg(0x94 + j - 1, 0x14 + i - 1, MC7000.padColor.samplerloaded); + } else { + midi.sendShortMsg(0x94 + j - 1, 0x14 + i - 1, MC7000.padColor.samplerplay); + } + } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { + midi.sendShortMsg(0x94 + j - 1, 0x1C + i - 1, MC7000.padColor.sampleroff); + } + } + } + } +}; + /* CONTROLLER SHUTDOWN */ MC7000.shutdown = function() { From 9d71d3cf6be695597e356f64432c3a89d4a891f4 Mon Sep 17 00:00:00 2001 From: Tobias Date: Mon, 16 Mar 2020 18:21:20 +0100 Subject: [PATCH 043/393] implemented Change Range code by Swiftb0y --- res/controllers/Denon-MC7000-scripts.js | 60 +++++++++++-------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 5a29a3ed7a6b..07cef3af5171 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -37,12 +37,15 @@ var MC7000 = {}; // can be true or false (recommended: false) MC7000.needleSearchPlay = false; -// Pitch Fader ranges to cycle through with the "RANGE" buttons. -var lowest = 4; // lowest value in % (default: 4) -var low = 6; // next value in % (default: 6) -var middle = 10; // next value in % (default: 10) -var high = 16; // next value in % (default: 16) -var highest = 24; // highest value in % (default: 24) +// Possible pitchfader rateranges given in percent. +// can be cycled through be the RANGE buttons. +MC7000.rateRanges = [ + 4/100, // default: 4/100 + 6/100, // default: 6/100 + 10/100, // default: 10/100 + 16/100, // default: 16/100 + 24/100, // default: 24/100 +]; // Platter Ring LED mode // Mode 0 = Single "off" LED chase (all others "on") @@ -82,16 +85,16 @@ MC7000.jogParams = { // The physical resolution seems to be around 1100 MC7000.jogWheelTicksPerRevolution = 894; -// Pitch faders up and down values (see above for user input) -MC7000.posRateRanges = [lowest/100, low/100, middle/100, high/100, highest/100]; -MC7000.negRateRanges = [highest/100, high/100, middle/100, low/100, lowest/100]; - // must be "true" for Needle Search to be active MC7000.needleSearchTouched = [true, true, true, true]; // initial value for VINYL mode per Deck (see above for user input) MC7000.isVinylMode = [MC7000.VinylModeOn, MC7000.VinylModeOn, MC7000.VinylModeOn, MC7000.VinylModeOn]; +// used to keep track of which the rateRange of each slider. +// value used as an index to MC7000.rateRanges +MC7000.currentRateRangeIndex = [0, 0, 0, 0]; + // initialize the "factor" function for Spinback MC7000.factor = []; @@ -132,7 +135,7 @@ MC7000.init = function() { MC7000.rightDeck = new MC7000.Deck(2, 4); // set default Master Volume to 85% to give a little head room for mixing - // engine.setValue("[Master]", "gain", 0.85); + engine.setValue("[Master]", "gain", 0.85); // VU meters engine.makeConnection("[Channel1]", "VuMeter", MC7000.VuMeter); @@ -170,7 +173,6 @@ MC7000.init = function() { engine.makeConnection("[Sampler"+i+"]", "play", MC7000.SamplerLED); } - // Sampler Volume Control MC7000.samplerLevel = function(channel, control, value) { // check if the Sampler Volume is at Zero and if so hide the sampler bank @@ -605,15 +607,23 @@ MC7000.Deck = function() { // Next Rate range toggle MC7000.nextRateRange = function(midichan, control, value, status, group) { if (value === 0) return; // don't respond to note off messages - var currRateRange = engine.getValue(group, "rateRange"); - engine.setValue(group, "rateRange", MC7000.getNextRateRange(currRateRange)); + var deckNumber = script.deckFromGroup(group); + // increment currentRateRangeIndex and check for overflow + if (++MC7000.currentRateRangeIndex[deckNumber-1] === MC7000.rateRanges.length) { + MC7000.currentRateRangeIndex[deckNumber-1] = 0; + } + engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber-1]]); }; // Previous Rate range toggle MC7000.prevRateRange = function(midichan, control, value, status, group) { if (value === 0) return; // don't respond to note off messages - var currRateRange = engine.getValue(group, "rateRange"); - engine.setValue(group, "rateRange", MC7000.getPrevRateRange(currRateRange)); + var deckNumber = script.deckFromGroup(group); + // decrement currentRateRangeIndex and check for underflow + if (--MC7000.currentRateRangeIndex[deckNumber-1] < 0) { + MC7000.currentRateRangeIndex[deckNumber-1] = MC7000.rateRanges.length - 1; + } + engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber-1]]); }; // Key Select @@ -663,26 +673,8 @@ MC7000.fxWetDry = function(midichan, control, value, status, group) { var numTicks = (value < 0x64) ? value: (value - 128); var newVal = engine.getValue(group, "mix") + numTicks/64*2; engine.setValue(group, "mix", Math.max(0, Math.min(1, newVal))); -}; -/* Next Rate range calculation */ -MC7000.getNextRateRange = function(currRateRange) { - for (var i = 0; i < MC7000.posRateRanges.length; i++) { - if (MC7000.posRateRanges[i] > currRateRange) { - return MC7000.posRateRanges[i]; - } - } - return MC7000.posRateRanges[0]; -}; -/* Previous Rate range calculation */ -MC7000.getPrevRateRange = function(currRateRange) { - for (var i = 0; i < MC7000.negRateRanges.length; i++) { - if (MC7000.negRateRanges[i] < currRateRange) { - return MC7000.negRateRanges[i]; - } - } - return MC7000.negRateRanges[0]; }; /* LEDs for VuMeter */ From 51b7833c24a7deff0607ada9fcac588eb7f1789f Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 21 Mar 2020 07:47:18 +0100 Subject: [PATCH 044/393] SLICER Mode as beatjump and little improvements of SAMPLER LEDs --- res/controllers/Denon-MC7000-scripts.js | 83 +++++++++++++++++++++---- 1 file changed, 70 insertions(+), 13 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 07cef3af5171..f022f2a67569 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -116,15 +116,23 @@ MC7000.VuMeterLEDPeakValue = 0x76; // PAD Mode Colors MC7000.padColor = { "alloff": 0x01, // switch off completely - "hotcueoff": 0x02, // lightblue Hot Cue inactive + // Hot Cue "hotcueon": 0x04, // darkblue Hot Cue active - "sampleroff": 0x27, // light pink Sampler standard colour + "hotcueoff": 0x02, // lightblue Hot Cue inactive + // Cue Loop + "cueloopon": 0x0D, // Cueloop colour for activated cue point + "cueloopoff": 0x1A, // Cueloop colour inactive + // Roll + "rollon": 0x20, // BeatloopRoll active colour + "rolloff": 0x06, // BeatloopRoll off colour + // Slicer + "sliceron": 0x11, // activated Slicer + "slicerJumpFwd": 0x31, // Sliver forward jump + "slicerJumpBack": 0x31, // Sliver backward jump + // Sampler "samplerloaded": 0x38, // dark pink Sampler loaded colour "samplerplay": 0x09, // green Sampler playing - "rollon": 0x10, // BeatloopRoll active colour - "rolloff": 0x1B, // BeatloopRoll off colour - "cueloopon": 0x0D, // Cueloop colour for activated cue point - "cueloopoff": 0x1A // Cueloop colour inactive + "sampleroff": 0x12 // light pink Sampler standard colour }; /* DECK INITIALIZATION */ @@ -135,7 +143,7 @@ MC7000.init = function() { MC7000.rightDeck = new MC7000.Deck(2, 4); // set default Master Volume to 85% to give a little head room for mixing - engine.setValue("[Master]", "gain", 0.85); + // engine.setValue("[Master]", "gain", 0.85); // VU meters engine.makeConnection("[Channel1]", "VuMeter", MC7000.VuMeter); @@ -267,7 +275,7 @@ MC7000.Deck = function() { MC7000.PADModePitch[deckNumber] = false; } for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.alloff); } }; // PAD Mode Roll @@ -327,7 +335,7 @@ MC7000.Deck = function() { MC7000.PADModePitch[deckNumber] = false; } for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.sliceron); } }; // PAD Mode Slicer Loop @@ -397,7 +405,7 @@ MC7000.Deck = function() { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); } }; - // PAD Mode Slicer + // PAD Mode Pitch MC7000.padModePitch = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value === 0x00) return; // don't respond to note off messages @@ -415,6 +423,7 @@ MC7000.Deck = function() { } for (var i = 1; i <= 8; i++) { midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.alloff); } }; @@ -492,7 +501,55 @@ MC7000.Deck = function() { } else if (MC7000.PADModeSavedLoop[deckNumber]) { return; } else if (MC7000.PADModeSlicer[deckNumber]) { - return; + if (control === 0x14 && value >= 0x01) { + engine.setValue(group, "beatjump_1_forward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.slicerJumpFwd); + } else if (control === 0x14 && value >= 0x00) { + engine.setValue(group, "beatjump_1_forward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.sliceron); + } else if (control === 0x15 && value >= 0x01) { + engine.setValue(group, "beatjump_2_forward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.slicerJumpFwd); + } else if (control === 0x15 && value >= 0x00) { + engine.setValue(group, "beatjump_2_forward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.sliceron); + } else if (control === 0x16 && value >= 0x01) { + engine.setValue(group, "beatjump_4_forward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.slicerJumpFwd); + } else if (control === 0x16 && value >= 0x00) { + engine.setValue(group, "beatjump_4_forward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.sliceron); + } else if (control === 0x17 && value >= 0x01) { + engine.setValue(group, "beatjump_8_forward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.slicerJumpFwd); + } else if (control === 0x17 && value >= 0x00) { + engine.setValue(group, "beatjump_8_forward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.sliceron); + } else if (control === 0x18 && value >= 0x01) { + engine.setValue(group, "beatjump_1_backward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.slicerJumpBack); + } else if (control === 0x18 && value >= 0x00) { + engine.setValue(group, "beatjump_1_backward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.sliceron); + } else if (control === 0x19 && value >= 0x01) { + engine.setValue(group, "beatjump_2_backward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.slicerJumpBack); + } else if (control === 0x19 && value >= 0x00) { + engine.setValue(group, "beatjump_2_backward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.sliceron); + } else if (control === 0x1A && value >= 0x01) { + engine.setValue(group, "beatjump_4_backward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.slicerJumpBack); + } else if (control === 0x1A && value >= 0x00) { + engine.setValue(group, "beatjump_4_backward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.sliceron); + } else if (control === 0x1B && value >= 0x01) { + engine.setValue(group, "beatjump_8_backward", true); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.slicerJumpBack); + } else if (control === 0x1B && value >= 0x00) { + engine.setValue(group, "beatjump_8_backward", false); + midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.sliceron); + } } else if (MC7000.PADModeSlicerLoop[deckNumber]) { return; } else if (MC7000.PADModeSampler[deckNumber]) { @@ -506,6 +563,7 @@ MC7000.Deck = function() { } else if (control === 0x1C + i -1 && value >= 0x01) { if (engine.getValue("[Sampler"+i+"]", "play") === 1) { engine.setValue("[Sampler"+i+"]", "cue_gotoandstop", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.samplerloaded); } else { engine.setValue("[Sampler"+i+"]", "eject", 1); midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i -1, MC7000.padColor.sampleroff); @@ -513,7 +571,6 @@ MC7000.Deck = function() { } } } - // TODO: check for the actual status of LEDs again on other decks } else if (MC7000.PADModeVelSamp[deckNumber]) { return; } else if (MC7000.PADModePitch[deckNumber]) { @@ -731,7 +788,7 @@ MC7000.SamplerLED = function() { midi.sendShortMsg(0x94 + j - 1, 0x14 + i - 1, MC7000.padColor.samplerplay); } } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { - midi.sendShortMsg(0x94 + j - 1, 0x1C + i - 1, MC7000.padColor.sampleroff); + midi.sendShortMsg(0x94 + j - 1, 0x14 + i - 1, MC7000.padColor.sampleroff); } } } From df9d06004ad639ed43b895f0c0e93a925c8c50bc Mon Sep 17 00:00:00 2001 From: Tobias Date: Tue, 24 Mar 2020 11:48:54 +0100 Subject: [PATCH 045/393] Little chnages as suggested by Holzhaus --- res/controllers/Denon-MC7000-scripts.js | 1021 ++++++++++++----------- res/controllers/Denon-MC7000.midi.xml | 2 +- 2 files changed, 538 insertions(+), 485 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index f022f2a67569..32d2b9e0cb66 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -138,10 +138,6 @@ MC7000.padColor = { /* DECK INITIALIZATION */ MC7000.init = function() { - // Decks - MC7000.leftDeck = new MC7000.Deck(1, 3); - MC7000.rightDeck = new MC7000.Deck(2, 4); - // set default Master Volume to 85% to give a little head room for mixing // engine.setValue("[Master]", "gain", 0.85); @@ -209,515 +205,572 @@ MC7000.init = function() { midi.sendSysexMsg(ControllerStatusSysex, ControllerStatusSysex.length); }; -/* CONSTRUCTOR FOR DECK OBJECT */ -MC7000.Deck = function() { - - // PAD Mode Hot Cue - MC7000.padModeCue = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - // set HotCue Mode true - MC7000.PADModeCue[deckNumber] = true; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; +// PAD Mode Hot Cue +MC7000.padModeCue = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + // set HotCue Mode true + MC7000.PADModeCue[deckNumber] = true; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + // change PAD color when switching to Hot Cue Mode + for (var i = 1; i <= 8; i++) { + if (engine.getValue(group, "hotcue_" + i + "_enabled", true)) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.hotcueon); + } else { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.hotcueoff); + } + } +}; +// PAD Mode Cue Loop +MC7000.padModeCueLoop = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = true; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.alloff); + } +}; +// PAD Mode Flip +MC7000.padModeFlip = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = true; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, + MC7000.padColor.alloff); + } +}; +// PAD Mode Roll +MC7000.padModeRoll = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = true; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.rolloff); + } +}; +// PAD Mode Saved Loop +MC7000.padModeSavedLoop = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = true; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.alloff); + } +}; +// PAD Mode Slicer +MC7000.padModeSlicer = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = true; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.sliceron); + } +}; +// PAD Mode Slicer Loop +MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = true; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.alloff); + } +}; +// PAD Mode Sampler +MC7000.padModeSampler = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = true; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = false; + } + // change PAD color when switching to Sampler Mode + for (var i = 1; i <= 8; i++) { + if (engine.getValue("[Sampler" + i + "]", "play")) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.samplerplay); + } else if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 0) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.sampleroff); + } else if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 1 && + engine.getValue("[Sampler" + i + "]", "play") === 0) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.samplerloaded); } - // change PAD color when switching to Hot Cue Mode + } +}; +// PAD Mode Velocity Sampler +MC7000.padModeVelSamp = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = false; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = true; + MC7000.PADModePitch[deckNumber] = false; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.alloff); + } +}; +// PAD Mode Pitch +MC7000.padModePitch = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (value === 0x00) + return; // don't respond to note off messages + if (value === 0x7F) { + MC7000.PADModeCue[deckNumber] = false; + MC7000.PADModeCueLoop[deckNumber] = false; + MC7000.PADModeFlip[deckNumber] = false; + MC7000.PADModeRoll[deckNumber] = false; + MC7000.PADModeSavedLoop[deckNumber] = false; + MC7000.PADModeSlicer[deckNumber] = true; + MC7000.PADModeSlicerLoop[deckNumber] = false; + MC7000.PADModeSampler[deckNumber] = false; + MC7000.PADModeVelSamp[deckNumber] = false; + MC7000.PADModePitch[deckNumber] = true; + } + for (var i = 1; i <= 8; i++) { + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, + MC7000.padColor.alloff); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, + MC7000.padColor.alloff); + } +}; + +// PAD buttons +MC7000.PadButtons = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + + // activate and clear Hot Cues + if (MC7000.PADModeCue[deckNumber] && + engine.getValue(group, "track_loaded") === 1) { for (var i = 1; i <= 8; i++) { - if (engine.getValue(group, "hotcue_"+i+"_enabled", true)) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.hotcueon); + if (control === 0x14 + i - 1 && value >= 0x01) { + engine.setValue(group, "hotcue_" + i + "_activate", true); } else { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.hotcueoff); + engine.setValue(group, "hotcue_" + i + "_activate", false); } - } - }; - // PAD Mode Cue Loop - MC7000.padModeCueLoop = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = true; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - } - }; - // PAD Mode Flip - MC7000.padModeFlip = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = true; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.alloff); - } - }; - // PAD Mode Roll - MC7000.padModeRoll = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = true; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.rolloff); - } - }; - // PAD Mode Saved Loop - MC7000.padModeSavedLoop = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = true; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - } - }; - // PAD Mode Slicer - MC7000.padModeSlicer = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = true; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.sliceron); - } - }; - // PAD Mode Slicer Loop - MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = true; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - } - }; - // PAD Mode Sampler - MC7000.padModeSampler = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = true; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = false; - } - // change PAD color when switching to Sampler Mode - for (var i = 1; i <= 8; i++) { - if (engine.getValue("[Sampler"+i+"]", "play")) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerplay); - } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.sampleroff); - } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1 && engine.getValue("[Sampler"+i+"]", "play") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i -1, MC7000.padColor.samplerloaded); + if (control === 0x1C + i - 1 && value >= 0x01) { + engine.setValue(group, "hotcue_" + i + "_clear", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, + MC7000.padColor.hotcueoff); } } - }; - // PAD Mode Velocity Sampler - MC7000.padModeVelSamp = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = false; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = true; - MC7000.PADModePitch[deckNumber] = false; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - } - }; - // PAD Mode Pitch - MC7000.padModePitch = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (value === 0x00) return; // don't respond to note off messages - if (value === 0x7F) { - MC7000.PADModeCue[deckNumber] = false; - MC7000.PADModeCueLoop[deckNumber] = false; - MC7000.PADModeFlip[deckNumber] = false; - MC7000.PADModeRoll[deckNumber] = false; - MC7000.PADModeSavedLoop[deckNumber] = false; - MC7000.PADModeSlicer[deckNumber] = true; - MC7000.PADModeSlicerLoop[deckNumber] = false; - MC7000.PADModeSampler[deckNumber] = false; - MC7000.PADModeVelSamp[deckNumber] = false; - MC7000.PADModePitch[deckNumber] = true; - } - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, 0x14 + i -1, MC7000.padColor.alloff); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.alloff); - } - }; - - // PAD buttons - MC7000.PadButtons = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - - // activate and clear Hot Cues - if (MC7000.PADModeCue[deckNumber] && engine.getValue(group, "track_loaded") === 1) { - for (var i = 1; i <= 8; i++) { - if (control === 0x14 + i -1 && value >= 0x01) { - engine.setValue(group, "hotcue_"+i+"_activate", true); - } else { - engine.setValue(group, "hotcue_"+i+"_activate", false); - } - if (control === 0x1C + i -1 && value >= 0x01) { - engine.setValue(group, "hotcue_"+i+"_clear", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1C + i -1, MC7000.padColor.hotcueoff); + } else if (MC7000.PADModeFlip[deckNumber]) { + return; + } else if (MC7000.PADModeFlip[deckNumber]) { + return; + } else if (MC7000.PADModeRoll[deckNumber]) { + if (control === 0x14 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.0625_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14, MC7000.padColor.rollon); + } else if (control === 0x14 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.0625_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14, MC7000.padColor.rolloff); + } else if (control === 0x15 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.125_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x15, MC7000.padColor.rollon); + } else if (control === 0x15 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.125_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x15, MC7000.padColor.rolloff); + } else if (control === 0x16 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.25_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x16, MC7000.padColor.rollon); + } else if (control === 0x16 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.25_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x16, MC7000.padColor.rolloff); + } else if (control === 0x17 && value >= 0x01) { + engine.setValue(group, "beatlooproll_0.5_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x17, MC7000.padColor.rollon); + } else if (control === 0x17 && value >= 0x00) { + engine.setValue(group, "beatlooproll_0.5_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x17, MC7000.padColor.rolloff); + } else if (control === 0x18 && value >= 0x01) { + engine.setValue(group, "beatlooproll_1_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x18, MC7000.padColor.rollon); + } else if (control === 0x18 && value >= 0x00) { + engine.setValue(group, "beatlooproll_1_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x18, MC7000.padColor.rolloff); + } else if (control === 0x19 && value >= 0x01) { + engine.setValue(group, "beatlooproll_2_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x19, MC7000.padColor.rollon); + } else if (control === 0x19 && value >= 0x00) { + engine.setValue(group, "beatlooproll_2_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x19, MC7000.padColor.rolloff); + } else if (control === 0x1A && value >= 0x01) { + engine.setValue(group, "beatlooproll_4_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1A, MC7000.padColor.rollon); + } else if (control === 0x1A && value >= 0x00) { + engine.setValue(group, "beatlooproll_4_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1A, MC7000.padColor.rolloff); + } else if (control === 0x1B && value >= 0x01) { + engine.setValue(group, "beatlooproll_8_activate", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1B, MC7000.padColor.rollon); + } else if (control === 0x1B && value >= 0x00) { + engine.setValue(group, "beatlooproll_8_activate", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1B, MC7000.padColor.rolloff); + } + } else if (MC7000.PADModeSavedLoop[deckNumber]) { + return; + } else if (MC7000.PADModeSlicer[deckNumber]) { + if (control === 0x14 && value >= 0x01) { + engine.setValue(group, "beatjump_1_forward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14, + MC7000.padColor.slicerJumpFwd); + } else if (control === 0x14 && value >= 0x00) { + engine.setValue(group, "beatjump_1_forward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x14, MC7000.padColor.sliceron); + } else if (control === 0x15 && value >= 0x01) { + engine.setValue(group, "beatjump_2_forward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x15, + MC7000.padColor.slicerJumpFwd); + } else if (control === 0x15 && value >= 0x00) { + engine.setValue(group, "beatjump_2_forward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x15, MC7000.padColor.sliceron); + } else if (control === 0x16 && value >= 0x01) { + engine.setValue(group, "beatjump_4_forward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x16, + MC7000.padColor.slicerJumpFwd); + } else if (control === 0x16 && value >= 0x00) { + engine.setValue(group, "beatjump_4_forward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x16, MC7000.padColor.sliceron); + } else if (control === 0x17 && value >= 0x01) { + engine.setValue(group, "beatjump_8_forward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x17, + MC7000.padColor.slicerJumpFwd); + } else if (control === 0x17 && value >= 0x00) { + engine.setValue(group, "beatjump_8_forward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x17, MC7000.padColor.sliceron); + } else if (control === 0x18 && value >= 0x01) { + engine.setValue(group, "beatjump_1_backward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x18, + MC7000.padColor.slicerJumpBack); + } else if (control === 0x18 && value >= 0x00) { + engine.setValue(group, "beatjump_1_backward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x18, MC7000.padColor.sliceron); + } else if (control === 0x19 && value >= 0x01) { + engine.setValue(group, "beatjump_2_backward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x19, + MC7000.padColor.slicerJumpBack); + } else if (control === 0x19 && value >= 0x00) { + engine.setValue(group, "beatjump_2_backward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x19, MC7000.padColor.sliceron); + } else if (control === 0x1A && value >= 0x01) { + engine.setValue(group, "beatjump_4_backward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1A, + MC7000.padColor.slicerJumpBack); + } else if (control === 0x1A && value >= 0x00) { + engine.setValue(group, "beatjump_4_backward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1A, MC7000.padColor.sliceron); + } else if (control === 0x1B && value >= 0x01) { + engine.setValue(group, "beatjump_8_backward", true); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1B, + MC7000.padColor.slicerJumpBack); + } else if (control === 0x1B && value >= 0x00) { + engine.setValue(group, "beatjump_8_backward", false); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1B, MC7000.padColor.sliceron); + } + } else if (MC7000.PADModeSlicerLoop[deckNumber]) { + return; + } else if (MC7000.PADModeSampler[deckNumber]) { + for (i = 1; i <= 8; i++) { + if (control === 0x14 + i - 1 && value >= 0x01) { + if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 0) { + engine.setValue("[Sampler" + i + "]", "LoadSelectedTrack", 1); + } else if (engine.getValue("[Sampler" + i + "]", "track_loaded") === + 1) { + engine.setValue("[Sampler" + i + "]", "cue_gotoandplay", 1); } - } - } else if (MC7000.PADModeFlip[deckNumber]) { - return; - } else if (MC7000.PADModeFlip[deckNumber]) { - return; - } else if (MC7000.PADModeRoll[deckNumber]) { - if (control === 0x14 && value >= 0x01) { - engine.setValue(group, "beatlooproll_0.0625_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.rollon); - } else if (control === 0x14 && value >= 0x00) { - engine.setValue(group, "beatlooproll_0.0625_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.rolloff); - } else if (control === 0x15 && value >= 0x01) { - engine.setValue(group, "beatlooproll_0.125_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.rollon); - } else if (control === 0x15 && value >= 0x00) { - engine.setValue(group, "beatlooproll_0.125_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.rolloff); - } else if (control === 0x16 && value >= 0x01) { - engine.setValue(group, "beatlooproll_0.25_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rollon); - } else if (control === 0x16 && value >= 0x00) { - engine.setValue(group, "beatlooproll_0.25_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.rolloff); - } else if (control === 0x17 && value >= 0x01) { - engine.setValue(group, "beatlooproll_0.5_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rollon); - } else if (control === 0x17 && value >= 0x00) { - engine.setValue(group, "beatlooproll_0.5_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.rolloff); - } else if (control === 0x18 && value >= 0x01) { - engine.setValue(group, "beatlooproll_1_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.rollon); - } else if (control === 0x18 && value >= 0x00) { - engine.setValue(group, "beatlooproll_1_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.rolloff); - } else if (control === 0x19 && value >= 0x01) { - engine.setValue(group, "beatlooproll_2_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.rollon); - } else if (control === 0x19 && value >= 0x00) { - engine.setValue(group, "beatlooproll_2_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.rolloff); - } else if (control === 0x1A && value >= 0x01) { - engine.setValue(group, "beatlooproll_4_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.rollon); - } else if (control === 0x1A && value >= 0x00) { - engine.setValue(group, "beatlooproll_4_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.rolloff); - } else if (control === 0x1B && value >= 0x01) { - engine.setValue(group, "beatlooproll_8_activate", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.rollon); - } else if (control === 0x1B && value >= 0x00) { - engine.setValue(group, "beatlooproll_8_activate", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.rolloff); - } - } else if (MC7000.PADModeSavedLoop[deckNumber]) { - return; - } else if (MC7000.PADModeSlicer[deckNumber]) { - if (control === 0x14 && value >= 0x01) { - engine.setValue(group, "beatjump_1_forward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.slicerJumpFwd); - } else if (control === 0x14 && value >= 0x00) { - engine.setValue(group, "beatjump_1_forward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x14, MC7000.padColor.sliceron); - } else if (control === 0x15 && value >= 0x01) { - engine.setValue(group, "beatjump_2_forward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.slicerJumpFwd); - } else if (control === 0x15 && value >= 0x00) { - engine.setValue(group, "beatjump_2_forward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x15, MC7000.padColor.sliceron); - } else if (control === 0x16 && value >= 0x01) { - engine.setValue(group, "beatjump_4_forward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.slicerJumpFwd); - } else if (control === 0x16 && value >= 0x00) { - engine.setValue(group, "beatjump_4_forward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x16, MC7000.padColor.sliceron); - } else if (control === 0x17 && value >= 0x01) { - engine.setValue(group, "beatjump_8_forward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.slicerJumpFwd); - } else if (control === 0x17 && value >= 0x00) { - engine.setValue(group, "beatjump_8_forward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x17, MC7000.padColor.sliceron); - } else if (control === 0x18 && value >= 0x01) { - engine.setValue(group, "beatjump_1_backward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.slicerJumpBack); - } else if (control === 0x18 && value >= 0x00) { - engine.setValue(group, "beatjump_1_backward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x18, MC7000.padColor.sliceron); - } else if (control === 0x19 && value >= 0x01) { - engine.setValue(group, "beatjump_2_backward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.slicerJumpBack); - } else if (control === 0x19 && value >= 0x00) { - engine.setValue(group, "beatjump_2_backward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x19, MC7000.padColor.sliceron); - } else if (control === 0x1A && value >= 0x01) { - engine.setValue(group, "beatjump_4_backward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.slicerJumpBack); - } else if (control === 0x1A && value >= 0x00) { - engine.setValue(group, "beatjump_4_backward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1A, MC7000.padColor.sliceron); - } else if (control === 0x1B && value >= 0x01) { - engine.setValue(group, "beatjump_8_backward", true); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.slicerJumpBack); - } else if (control === 0x1B && value >= 0x00) { - engine.setValue(group, "beatjump_8_backward", false); - midi.sendShortMsg(0x94 + deckNumber -1, 0x1B, MC7000.padColor.sliceron); - } - } else if (MC7000.PADModeSlicerLoop[deckNumber]) { - return; - } else if (MC7000.PADModeSampler[deckNumber]) { - for (i = 1; i <= 8; i++) { - if (control === 0x14 + i -1 && value >= 0x01) { - if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 0) { - engine.setValue("[Sampler"+i+"]", "LoadSelectedTrack", 1); - } else if (engine.getValue("[Sampler"+i+"]", "track_loaded") === 1) { - engine.setValue("[Sampler"+i+"]", "cue_gotoandplay", 1); - } - } else if (control === 0x1C + i -1 && value >= 0x01) { - if (engine.getValue("[Sampler"+i+"]", "play") === 1) { - engine.setValue("[Sampler"+i+"]", "cue_gotoandstop", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.samplerloaded); - } else { - engine.setValue("[Sampler"+i+"]", "eject", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i -1, MC7000.padColor.sampleroff); - engine.setValue("[Sampler"+i+"]", "eject", 0); - } + } else if (control === 0x1C + i - 1 && value >= 0x01) { + if (engine.getValue("[Sampler" + i + "]", "play") === 1) { + engine.setValue("[Sampler" + i + "]", "cue_gotoandstop", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, + MC7000.padColor.samplerloaded); + } else { + engine.setValue("[Sampler" + i + "]", "eject", 1); + midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, + MC7000.padColor.sampleroff); + engine.setValue("[Sampler" + i + "]", "eject", 0); } } - } else if (MC7000.PADModeVelSamp[deckNumber]) { - return; - } else if (MC7000.PADModePitch[deckNumber]) { - return; } - }; + } else if (MC7000.PADModeVelSamp[deckNumber]) { + return; + } else if (MC7000.PADModePitch[deckNumber]) { + return; + } +}; - // Toggle Vinyl Mode - MC7000.vinylModeToggle = function(channel, control, value, status, group) { - if (value === 0x00) return; // don't respond to note off messages +// Toggle Vinyl Mode +MC7000.vinylModeToggle = function(channel, control, value, status, group) { + if (value === 0x00) + return; // don't respond to note off messages - if (value === 0x7F) { - var deckNumber = script.deckFromGroup(group); - MC7000.isVinylMode[deckNumber] = !MC7000.isVinylMode[deckNumber]; - midi.sendShortMsg(0x90 + channel, 0x07, MC7000.isVinylMode[deckNumber] ? 0x7F: 0x01); - } - }; - - // The button that enables/disables scratching - MC7000.wheelTouch = function(channel, control, value, status, group) { + if (value === 0x7F) { var deckNumber = script.deckFromGroup(group); - if (MC7000.isVinylMode[deckNumber]) { - if (value === 0x7F) { - engine.scratchEnable(deckNumber, MC7000.jogWheelTicksPerRevolution, MC7000.scratchParams.recordSpeed, MC7000.scratchParams.alpha, MC7000.scratchParams.beta); - } else { - engine.scratchDisable(deckNumber); - } - } - }; - - // The wheel that actually controls the scratching - MC7000.wheelTurn = function(channel, control, value, status, group) { + MC7000.isVinylMode[deckNumber] = !MC7000.isVinylMode[deckNumber]; + midi.sendShortMsg(0x90 + channel, 0x07, + MC7000.isVinylMode[deckNumber] ? 0x7F : 0x01); + } +}; - // A: For a control that centers on 0: - var numTicks = (value < 0x64) ? value: (value - 128); - var deckNumber = script.deckFromGroup(group); - if (engine.isScratching(deckNumber)) { - // Scratch! - engine.scratchTick(deckNumber, numTicks); +// The button that enables/disables scratching +MC7000.wheelTouch = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (MC7000.isVinylMode[deckNumber]) { + if (value === 0x7F) { + engine.scratchEnable(deckNumber, MC7000.jogWheelTicksPerRevolution, + MC7000.scratchParams.recordSpeed, + MC7000.scratchParams.alpha, + MC7000.scratchParams.beta); } else { - // Pitch bend - var jogDelta = numTicks/MC7000.jogWheelTicksPerRevolution*MC7000.jogParams.jogSensitivity; - var jogAbsolute = jogDelta + engine.getValue(group, "jog"); - engine.setValue(group, "jog", Math.max(-MC7000.jogParams.maxJogValue, Math.min(MC7000.jogParams.maxJogValue, jogAbsolute))); + engine.scratchDisable(deckNumber); } - }; + } +}; - // Needle Search Touch detection - MC7000.needleSearchTouch = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (engine.getValue(group, "play")) { - MC7000.needleSearchTouched[deckNumber] = MC7000.needleSearchPlay && (!!value); - } else { - MC7000.needleSearchTouched[deckNumber] = !!value; - } - }; +// The wheel that actually controls the scratching +MC7000.wheelTurn = function(channel, control, value, status, group) { + // A: For a control that centers on 0: + var numTicks = (value < 0x64) ? value : (value - 128); + var deckNumber = script.deckFromGroup(group); + if (engine.isScratching(deckNumber)) { + // Scratch! + engine.scratchTick(deckNumber, numTicks); + } else { + // Pitch bend + var jogDelta = numTicks / MC7000.jogWheelTicksPerRevolution * + MC7000.jogParams.jogSensitivity; + var jogAbsolute = jogDelta + engine.getValue(group, "jog"); + engine.setValue( + group, "jog", + Math.max(-MC7000.jogParams.maxJogValue, + Math.min(MC7000.jogParams.maxJogValue, jogAbsolute))); + } +}; - // Needle Search Touch while "SHIFT" button is pressed - MC7000.needleSearchTouchShift = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); +// Needle Search Touch detection +MC7000.needleSearchTouch = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + if (engine.getValue(group, "play")) { + MC7000.needleSearchTouched[deckNumber] = + MC7000.needleSearchPlay && (!!value); + } else { MC7000.needleSearchTouched[deckNumber] = !!value; - }; + } +}; - // Needle Search Position detection (MSB) - MC7000.needleSearchMSB = function(channel, control, value) { - MC7000.needleDropMSB = value; // just defining rough position - }; +// Needle Search Touch while "SHIFT" button is pressed +MC7000.needleSearchTouchShift = function(channel, control, value, status, + group) { + var deckNumber = script.deckFromGroup(group); + MC7000.needleSearchTouched[deckNumber] = !!value; +}; - // Needle Search Position detection (MSB + LSB) - MC7000.needleSearchStripPosition = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - if (MC7000.needleSearchTouched[deckNumber]) { - var fullValue = (MC7000.needleDropMSB << 7) + value; // move MSB 7 binary gigits to the left and add LSB - var position = (fullValue / 0x3FFF); // devide by all possible positions to get relative between 0 - 1 - engine.setParameter(group, "playposition", position); - } - }; +// Needle Search Position detection (MSB) +MC7000.needleSearchMSB = function(channel, control, value) { + MC7000.needleDropMSB = value; // just defining rough position +}; - // Pitch Fader (MSB) - MC7000.pitchFaderMSB = function(channel, control, value) { - MC7000.pitchMSB = value; // just defining rough position - }; +// Needle Search Position detection (MSB + LSB) +MC7000.needleSearchStripPosition = function(channel, control, value, status, + group) { + var deckNumber = script.deckFromGroup(group); + if (MC7000.needleSearchTouched[deckNumber]) { + var fullValue = (MC7000.needleDropMSB << 7) + + value; // move MSB 7 binary gigits to the left and add LSB + var position = (fullValue / 0x3FFF); // devide by all possible positions to + // get relative between 0 - 1 + engine.setParameter(group, "playposition", position); + } +}; - // Pitch Fader Position (MSB + LSB) - MC7000.pitchFaderPosition = function(channel, control, value, status, group) { - var fullValue = (MC7000.pitchMSB << 7) + value; - var position = 1 - (fullValue / 0x3FFF); // 1 - () to turn around the direction - engine.setParameter(group, "rate", position); - }; +// Pitch Fader (MSB) +MC7000.pitchFaderMSB = function(channel, control, value) { + MC7000.pitchMSB = value; // just defining rough position +}; - // Next Rate range toggle - MC7000.nextRateRange = function(midichan, control, value, status, group) { - if (value === 0) return; // don't respond to note off messages - var deckNumber = script.deckFromGroup(group); - // increment currentRateRangeIndex and check for overflow - if (++MC7000.currentRateRangeIndex[deckNumber-1] === MC7000.rateRanges.length) { - MC7000.currentRateRangeIndex[deckNumber-1] = 0; - } - engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber-1]]); - }; +// Pitch Fader Position (MSB + LSB) +MC7000.pitchFaderPosition = function(channel, control, value, status, group) { + var fullValue = (MC7000.pitchMSB << 7) + value; + var position = + 1 - (fullValue / 0x3FFF); // 1 - () to turn around the direction + engine.setParameter(group, "rate", position); +}; - // Previous Rate range toggle - MC7000.prevRateRange = function(midichan, control, value, status, group) { - if (value === 0) return; // don't respond to note off messages - var deckNumber = script.deckFromGroup(group); - // decrement currentRateRangeIndex and check for underflow - if (--MC7000.currentRateRangeIndex[deckNumber-1] < 0) { - MC7000.currentRateRangeIndex[deckNumber-1] = MC7000.rateRanges.length - 1; - } - engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber-1]]); - }; +// Next Rate range toggle +MC7000.nextRateRange = function(midichan, control, value, status, group) { + if (value === 0) + return; // don't respond to note off messages + var deckNumber = script.deckFromGroup(group); + // increment currentRateRangeIndex and check for overflow + if (++MC7000.currentRateRangeIndex[deckNumber - 1] === + MC7000.rateRanges.length) { + MC7000.currentRateRangeIndex[deckNumber - 1] = 0; + } + engine.setValue( + group, "rateRange", + MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber - 1]]); +}; - // Key Select - MC7000.keySelect = function(midichan, control, value, status, group) { - if (value === 0x01) { - engine.setValue(group, "pitch_up", true); - } else if (value === 0x7F) { - engine.setValue(group, "pitch_down", true); - } - }; +// Previous Rate range toggle +MC7000.prevRateRange = function(midichan, control, value, status, group) { + if (value === 0) + return; // don't respond to note off messages + var deckNumber = script.deckFromGroup(group); + // decrement currentRateRangeIndex and check for underflow + if (--MC7000.currentRateRangeIndex[deckNumber - 1] < 0) { + MC7000.currentRateRangeIndex[deckNumber - 1] = MC7000.rateRanges.length - 1; + } + engine.setValue( + group, "rateRange", + MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber - 1]]); +}; - // Assign Channel to Crossfader - MC7000.crossfaderAssign = function(channel, control, value, status, group) { - if (value === 0x00) { - engine.setValue(group, "orientation", 1); // Centre position - } else if (value === 0x01) { - engine.setValue(group, "orientation", 0); // Right position - } else if (value === 0x02) { - engine.setValue(group, "orientation", 2); // Left position - } - }; +// Key Select +MC7000.keySelect = function(midichan, control, value, status, group) { + if (value === 0x01) { + engine.setValue(group, "pitch_up", true); + } else if (value === 0x7F) { + engine.setValue(group, "pitch_down", true); + } +}; - // Assign Spinback length to STOP TIME knob - MC7000.stopTime = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - // "factor" for engine.brake() - // this formula produces factors between 31 (min STOP TIME for ca 7 sec back in track) - // and 1 (max STOP TIME for ca 18.0 sec back in track) - MC7000.factor[deckNumber] = (1.1 - (value / 127)) * 30 - 2; - }; +// Assign Channel to Crossfader +MC7000.crossfaderAssign = function(channel, control, value, status, group) { + if (value === 0x00) { + engine.setValue(group, "orientation", 1); // Centre position + } else if (value === 0x01) { + engine.setValue(group, "orientation", 0); // Right position + } else if (value === 0x02) { + engine.setValue(group, "orientation", 2); // Left position + } +}; - // Use the CENSOR button as Spinback with STOP TIME adjusted length - MC7000.censor = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - var deck = parseInt(group.substring(8, 9)); // work out which deck we are using - engine.brake(deck, value > 0, MC7000.factor[deckNumber], - 15); // start at a rate of -15 and decrease by "factor" - }; +// Assign Spinback length to STOP TIME knob +MC7000.stopTime = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + // "factor" for engine.brake() + // this formula produces factors between 31 (min STOP TIME for ca 7 sec back + // in track) and 1 (max STOP TIME for ca 18.0 sec back in track) + MC7000.factor[deckNumber] = (1.1 - (value / 127)) * 30 - 2; +}; + +// Use the CENSOR button as Spinback with STOP TIME adjusted length +MC7000.censor = function(channel, control, value, status, group) { + var deckNumber = script.deckFromGroup(group); + var deck = + parseInt(group.substring(8, 9)); // work out which deck we are using + engine.brake(deck, value > 0, MC7000.factor[deckNumber], + -15); // start at a rate of -15 and decrease by "factor" }; /* SET CROSSFADER CURVE */ diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index 65f0bdef4cdb..42eec7693335 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -1,7 +1,7 @@ - Denon MC7000 beta 0.14 + Denon MC7000 beta for MIXXX 2.2.x OsZ Denon MC7000 mapping for testing. Check your Linux Kernel version to get the Audio Interface working - see WIKI page. https://www.mixxx.org/forums/ From 3b66047a7fb2431a4ae0d0187c9c7fc3e332ef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 11:43:09 +0100 Subject: [PATCH 046/393] Introduce Compbobox for selecting the hotcue default color --- src/preferences/dialog/dlgprefcolors.cpp | 56 ++++++++++++++++++++-- src/preferences/dialog/dlgprefcolorsdlg.ui | 40 ++++++---------- src/util/color/predefinedcolorpalettes.cpp | 1 + 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 6888e8db98be..2bd28aa27b4a 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -1,12 +1,19 @@ #include "preferences/dialog/dlgprefcolors.h" #include +#include #include #include #include "control/controlobject.h" #include "util/color/predefinedcolorpalettes.h" +namespace { + +constexpr int kHotcueDefaultColorIndex = -1; + +} // anonymous namespace + DlgPrefColors::DlgPrefColors( QWidget* parent, UserSettingsPointer pConfig) : DlgPreferencePage(parent), @@ -47,12 +54,44 @@ void DlgPrefColors::loadSettings() { comboBoxTrackColors->addItem(paletteName); } + const ColorPalette hotcuePalette = m_colorPaletteSettings.getHotcueColorPalette(); + comboBoxHotcueColors->setCurrentText( - m_colorPaletteSettings.getHotcueColorPalette().getName()); + hotcuePalette.getName()); comboBoxTrackColors->setCurrentText( m_colorPaletteSettings.getTrackColorPalette().getName()); - slotApply(); + QPixmap pixmap(80, 80); + QPainter painter(&pixmap); + pixmap.fill(Qt::black); + + comboBoxHotcueDefaultColor->addItem(tr("By hotcue number"), -1); + + for (int i = 0; i < hotcuePalette.size() && i < 4; ++i) { + painter.setBrush(mixxx::RgbColor::toQColor(hotcuePalette.at(i))); + painter.drawRect(0, i * 20, 80, 20); + } + comboBoxHotcueDefaultColor->setItemIcon(0, QIcon(pixmap)); + + for (int i = 0; i < hotcuePalette.size(); ++i) { + comboBoxHotcueDefaultColor->addItem(tr("Palette") + + QStringLiteral(" ") + QString::number(i + 1), + i); + pixmap.fill(mixxx::RgbColor::toQColor(hotcuePalette.at(i))); + comboBoxHotcueDefaultColor->setItemIcon(i + 1, QIcon(pixmap)); + } + + bool autoHotcueColors = + m_pConfig->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false); + if (autoHotcueColors) { + comboBoxHotcueDefaultColor->setCurrentIndex(0); + } else { + int hotcueDefaultColorIndex = m_pConfig->getValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), kHotcueDefaultColorIndex); + if (hotcueDefaultColorIndex < 0 || hotcueDefaultColorIndex >= hotcuePalette.size()) { + hotcueDefaultColorIndex = hotcuePalette.size() - 1; // default to last color (orange) + } + comboBoxHotcueDefaultColor->setCurrentIndex(hotcueDefaultColorIndex + 1); + } } // Set the default values for all the widgets @@ -61,6 +100,7 @@ void DlgPrefColors::slotResetToDefaults() { mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette.getName()); comboBoxTrackColors->setCurrentText( mixxx::PredefinedColorPalettes::kDefaultTrackColorPalette.getName()); + comboBoxHotcueDefaultColor->setCurrentIndex(1); slotApply(); } @@ -94,7 +134,13 @@ void DlgPrefColors::slotApply() { m_colorPaletteSettings.getTrackColorPalette())); } - m_pConfig->setValue( - ConfigKey("[Controls]", "auto_hotcue_colors"), - checkBoxAssignHotcueColors->isChecked()); + int index = comboBoxHotcueDefaultColor->currentIndex(); + + if (index > 0) { + m_pConfig->setValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false); + m_pConfig->setValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), index - 1); + } else { + m_pConfig->setValue(ConfigKey("[Controls]", "auto_hotcue_colors"), true); + m_pConfig->setValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), -1); + } } diff --git a/src/preferences/dialog/dlgprefcolorsdlg.ui b/src/preferences/dialog/dlgprefcolorsdlg.ui index eeebc7ffbf7c..3f053f56275e 100644 --- a/src/preferences/dialog/dlgprefcolorsdlg.ui +++ b/src/preferences/dialog/dlgprefcolorsdlg.ui @@ -26,14 +26,14 @@ Colors - + - Hotcue Palette + Hotcue palette - + @@ -43,14 +43,21 @@ - + + + + Hotcue default color + + + + - Track Palette + Track palette - + @@ -60,25 +67,8 @@ - - - - Cycle hotcue colors - - - checkBoxAssignHotcueColors - - - - - - - Automatically assigns a predefined color to a newly created hotcue point, based on its index. - - - Select consecutive palette colors for new hotcues - - + + diff --git a/src/util/color/predefinedcolorpalettes.cpp b/src/util/color/predefinedcolorpalettes.cpp index db6deb0ed7b2..26d59f5eccbf 100644 --- a/src/util/color/predefinedcolorpalettes.cpp +++ b/src/util/color/predefinedcolorpalettes.cpp @@ -122,6 +122,7 @@ const ColorPalette PredefinedColorPalettes::kMixxxHotcueColorPalette = kColorMixxxPurple, kColorMixxxPink, kColorMixxxWhite, + kSchemaMigrationReplacementColor, }); const ColorPalette PredefinedColorPalettes::kSeratoDJIntroHotcueColorPalette = From d990c5ea7873eda6a36adb01cbfd712e538b6cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 12:24:26 +0100 Subject: [PATCH 047/393] Respect new hotcue default settings --- src/engine/controls/cuecontrol.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 15c3fa77af23..7869dd640a50 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -618,12 +618,16 @@ void CueControl::hotcueSet(HotcueControl* pControl, double v) { pCue->setLabel(); pCue->setType(mixxx::CueType::HotCue); - ConfigKey autoHotcueColorsKey("[Controls]", "auto_hotcue_colors"); - if (getConfig()->getValue(autoHotcueColorsKey, false)) { - auto hotcueColorPalette = m_colorPaletteSettings.getHotcueColorPalette(); + const ColorPalette hotcueColorPalette = + m_colorPaletteSettings.getHotcueColorPalette(); + if (getConfig()->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false)) { pCue->setColor(hotcueColorPalette.colorForHotcueIndex(hotcue)); } else { - pCue->setColor(mixxx::PredefinedColorPalettes::kDefaultCueColor); + int hotcueDefaultColorIndex = m_pConfig->getValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), -1); + if (hotcueDefaultColorIndex < 0 || hotcueDefaultColorIndex >= hotcueColorPalette.size()) { + hotcueDefaultColorIndex = hotcueColorPalette.size() - 1; // default to last color (orange) + } + pCue->setColor(hotcueColorPalette.at(hotcueDefaultColorIndex)); } // TODO(XXX) deal with spurious signals From 38f625de0034c3662805395c66d10544946fbf88 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 24 Mar 2020 13:07:36 +0100 Subject: [PATCH 048/393] Handle client-side timeouts --- .../web/musicbrainzrecordingstask.cpp | 23 ++++- .../web/musicbrainzrecordingstask.h | 3 +- src/network/jsonwebtask.cpp | 24 +++++- src/network/jsonwebtask.h | 3 +- src/network/webtask.cpp | 85 +++++++++++++++---- src/network/webtask.h | 28 ++++-- 6 files changed, 139 insertions(+), 27 deletions(-) diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp index 4c4f7a7a4f83..cccdaf673c52 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp +++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp @@ -124,10 +124,29 @@ bool MusicBrainzRecordingsTask::doStart( return true; } -void MusicBrainzRecordingsTask::doAbort() { +QUrl MusicBrainzRecordingsTask::doAbort() { + QUrl requestUrl; if (m_pendingNetworkReply) { - m_pendingNetworkReply->abort(); + requestUrl = abortPendingNetworkReply(m_pendingNetworkReply); + if (requestUrl.isValid()) { + // Already finished + m_pendingNetworkReply->deleteLater(); + m_pendingNetworkReply = nullptr; + } } + return requestUrl; +} + +QUrl MusicBrainzRecordingsTask::doTimeOut() { + DEBUG_ASSERT(thread() == QThread::currentThread()); + QUrl requestUrl; + if (m_pendingNetworkReply) { + requestUrl = timeOutPendingNetworkReply(m_pendingNetworkReply); + // Don't wait until finished + m_pendingNetworkReply->deleteLater(); + m_pendingNetworkReply = nullptr; + } + return requestUrl; } void MusicBrainzRecordingsTask::slotNetworkReplyFinished() { diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.h b/src/musicbrainz/web/musicbrainzrecordingstask.h index fab025596c90..614315d75840 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.h +++ b/src/musicbrainz/web/musicbrainzrecordingstask.h @@ -35,7 +35,8 @@ class MusicBrainzRecordingsTask : public network::WebTask { bool doStart( QNetworkAccessManager* networkAccessManager, int parentTimeoutMillis) override; - void doAbort() override; + QUrl doAbort() override; + QUrl doTimeOut() override; void emitSucceeded( QList&& trackReleases); diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index af9b084c0aab..8ccdcffddcb2 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -252,10 +252,30 @@ bool JsonWebTask::doStart( return true; } -void JsonWebTask::doAbort() { +QUrl JsonWebTask::doAbort() { + DEBUG_ASSERT(thread() == QThread::currentThread()); + QUrl requestUrl; + if (m_pendingNetworkReply) { + requestUrl = abortPendingNetworkReply(m_pendingNetworkReply); + if (requestUrl.isValid()) { + // Already finished + m_pendingNetworkReply->deleteLater(); + m_pendingNetworkReply = nullptr; + } + } + return requestUrl; +} + +QUrl JsonWebTask::doTimeOut() { + DEBUG_ASSERT(thread() == QThread::currentThread()); + QUrl requestUrl; if (m_pendingNetworkReply) { - m_pendingNetworkReply->abort(); + requestUrl = timeOutPendingNetworkReply(m_pendingNetworkReply); + // Don't wait until finished + m_pendingNetworkReply->deleteLater(); + m_pendingNetworkReply = nullptr; } + return requestUrl; } void JsonWebTask::slotNetworkReplyFinished() { diff --git a/src/network/jsonwebtask.h b/src/network/jsonwebtask.h index b7d8a791383c..879feb1cf6fa 100644 --- a/src/network/jsonwebtask.h +++ b/src/network/jsonwebtask.h @@ -86,7 +86,8 @@ class JsonWebTask : public WebTask { bool doStart( QNetworkAccessManager* networkAccessManager, int parentTimeoutMillis) override; - void doAbort() override; + QUrl doAbort() override; + QUrl doTimeOut() override; // All member variables must only be accessed from // the event loop thread!! diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index 0cbf129c46f6..eb2e43cf6a33 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -62,7 +62,7 @@ WebTask::WebTask( QNetworkAccessManager* networkAccessManager) : m_networkAccessManager(networkAccessManager), m_timeoutTimerId(kInvalidTimerId), - m_abort(false) { + m_status(Status::Idle) { std::call_once(registerMetaTypesOnceFlag, registerMetaTypesOnce); DEBUG_ASSERT(m_networkAccessManager); s_instanceCounter.increment(1); @@ -72,15 +72,38 @@ WebTask::~WebTask() { s_instanceCounter.increment(-1); } -void WebTask::onAborted() { - DEBUG_ASSERT(m_abort); +void WebTask::onAborted( + QUrl requestUrl) { + DEBUG_ASSERT(m_status == Status::Aborted); const auto signal = QMetaMethod::fromSignal( &WebTask::aborted); if (isSignalConnected(signal)) { emit aborted(); } else { kLogger.info() - << "Request aborted"; + << "Request aborted" + << requestUrl; + deleteAfterFinished(); + } +} + +void WebTask::onTimedOut( + QUrl requestUrl) { + DEBUG_ASSERT(m_status == Status::TimedOut); + const auto signal = QMetaMethod::fromSignal( + &WebTask::networkError); + if (isSignalConnected(signal)) { + emit networkError( + std::move(requestUrl), + QNetworkReply::TimeoutError, + QStringLiteral("Client-side timeout"), + QByteArray()); + } else { + kLogger.warning() + << "Aborting request after client-side timeout" + << requestUrl; + // Do not abort the request before deleting it, + // i.e. pretend it already has been finished. deleteAfterFinished(); } } @@ -156,14 +179,12 @@ void WebTask::deleteAfterFinished() { void WebTask::slotStart(int timeoutMillis) { DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_status == Status::Idle); VERIFY_OR_DEBUG_ASSERT(m_networkAccessManager) { kLogger.warning() << "No network access"; return; } - // The task might be restarted after it has been aborted - // or finished. - m_abort = false; kLogger.debug() << "Starting..."; @@ -174,10 +195,11 @@ void WebTask::slotStart(int timeoutMillis) { } // The task could be aborted immediately while being started. - if (m_abort) { - onAborted(); + if (m_status == Status::Aborted) { + onAborted(doAbort()); return; } + m_status = Status::Pending; DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId); if (timeoutMillis > 0) { @@ -186,9 +208,33 @@ void WebTask::slotStart(int timeoutMillis) { } } +QUrl WebTask::abortPendingNetworkReply( + QNetworkReply* pendingNetworkReply) { + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(pendingNetworkReply); + if (pendingNetworkReply->isRunning()) { + pendingNetworkReply->abort(); + // Suspend until finished + return QUrl(); + } + return pendingNetworkReply->request().url(); +} + +QUrl WebTask::timeOutPendingNetworkReply( + QNetworkReply* pendingNetworkReply) { + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(pendingNetworkReply); + if (pendingNetworkReply->isRunning()) { + //pendingNetworkReply->abort(); + // Don't suspend until finished, i.e. abort and then + // delete the pending network request instantly + } + return pendingNetworkReply->request().url(); +} + void WebTask::slotAbort() { DEBUG_ASSERT(thread() == QThread::currentThread()); - if (m_abort) { + if (m_status != Status::Pending) { DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId); return; } @@ -196,10 +242,10 @@ void WebTask::slotAbort() { killTimer(m_timeoutTimerId); m_timeoutTimerId = kInvalidTimerId; } - m_abort = true; + m_status = Status::Aborted; kLogger.debug() << "Aborting..."; - doAbort(); + onAborted(doAbort()); } void WebTask::timerEvent(QTimerEvent* event) { @@ -210,13 +256,19 @@ void WebTask::timerEvent(QTimerEvent* event) { // ignore return; } - kLogger.info() + killTimer(m_timeoutTimerId); + m_timeoutTimerId = kInvalidTimerId; + if (m_status != Status::Aborted) { + m_status = Status::TimedOut; + } + kLogger.debug() << "Timed out"; - slotAbort(); + onTimedOut(doTimeOut()); } QPair WebTask::receiveNetworkReply() { DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_status != Status::Idle); auto* const networkReply = qobject_cast(sender()); HttpStatusCode statusCode = kHttpStatusCodeInvalid; VERIFY_OR_DEBUG_ASSERT(networkReply) { @@ -229,10 +281,11 @@ QPair WebTask::receiveNetworkReply() { m_timeoutTimerId = kInvalidTimerId; } - if (m_abort) { - onAborted(); + if (m_status == Status::Aborted) { + onAborted(networkReply->request().url()); return qMakePair(nullptr, statusCode); } + m_status = Status::Finished; if (networkReply->error() != QNetworkReply::NetworkError::NoError) { onNetworkError( diff --git a/src/network/webtask.h b/src/network/webtask.h index 358f4fed57f6..e78f0c2be94a 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -136,12 +136,28 @@ class WebTask : public QObject { QByteArray errorContent); protected: - void timerEvent(QTimerEvent* event) override; + void timerEvent(QTimerEvent* event) final; - // Handle an aborted request and ensure that the task eventually + enum class Status { + Idle, + Pending, + Aborted, + TimedOut, + Finished, + }; + + // Handle status changes and ensure that the task eventually // gets deleted. The default implementation simply deletes the // task. - virtual void onAborted(); + virtual void onAborted( + QUrl requestUrl); + virtual void onTimedOut( + QUrl requestUrl); + + QUrl abortPendingNetworkReply( + QNetworkReply* pendingNetworkReply); + QUrl timeOutPendingNetworkReply( + QNetworkReply* pendingNetworkReply); // Handle the abort and ensure that the task eventually // gets deleted. The default implementation logs a warning @@ -158,14 +174,16 @@ class WebTask : public QObject { virtual bool doStart( QNetworkAccessManager* networkAccessManager, int parentTimeoutMillis) = 0; - virtual void doAbort() = 0; + // Handle status change requests and return the request URL + virtual QUrl doAbort() = 0; + virtual QUrl doTimeOut() = 0; // All member variables must only be accessed from // the event loop thread!! const QPointer m_networkAccessManager; int m_timeoutTimerId; - bool m_abort; + Status m_status; }; } // namespace network From 88b083a7665ef9830a669c7d3f6d8775424b1ead Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 24 Mar 2020 15:48:24 +0100 Subject: [PATCH 049/393] Fix restarting of WebTask --- src/network/webtask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index eb2e43cf6a33..102149301270 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -179,7 +179,7 @@ void WebTask::deleteAfterFinished() { void WebTask::slotStart(int timeoutMillis) { DEBUG_ASSERT(thread() == QThread::currentThread()); - DEBUG_ASSERT(m_status == Status::Idle); + DEBUG_ASSERT(m_status != Status::Pending); VERIFY_OR_DEBUG_ASSERT(m_networkAccessManager) { kLogger.warning() << "No network access"; @@ -188,6 +188,7 @@ void WebTask::slotStart(int timeoutMillis) { kLogger.debug() << "Starting..."; + m_status = Status::Idle; if (!doStart(m_networkAccessManager, timeoutMillis)) { kLogger.warning() << "Start aborted"; From c53f7c17a7760178341a5a38dfc1aa9b5eb8c3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 15:37:19 +0100 Subject: [PATCH 050/393] Added a preview stripe for the selected palette. Repopulate the default color selector when changing the palette. --- src/preferences/dialog/dlgprefcolors.cpp | 136 ++++++++++++++------- src/preferences/dialog/dlgprefcolors.h | 5 + src/preferences/dialog/dlgprefcolorsdlg.ui | 22 +++- 3 files changed, 118 insertions(+), 45 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 2bd28aa27b4a..9cce1909c11d 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -7,6 +7,8 @@ #include "control/controlobject.h" #include "util/color/predefinedcolorpalettes.h" +#include "util/compatibility.h" +#include "util/math.h" namespace { @@ -26,14 +28,22 @@ DlgPrefColors::DlgPrefColors( connect(colorPaletteEditor, &ColorPaletteEditor::paletteChanged, - [this] { - loadSettings(); - }); + this, + &DlgPrefColors::loadSettings); connect(colorPaletteEditor, &ColorPaletteEditor::paletteRemoved, - [this] { - loadSettings(); - }); + this, + &DlgPrefColors::loadSettings); + + connect(comboBoxTrackColors, + QOverload::of(&QComboBox::currentIndexChanged), + this, + &DlgPrefColors::slotTrackPaletteChanged); + + connect(comboBoxHotcueColors, + QOverload::of(&QComboBox::currentIndexChanged), + this, + &DlgPrefColors::slotHotcuePaletteChanged); } DlgPrefColors::~DlgPrefColors() { @@ -54,44 +64,17 @@ void DlgPrefColors::loadSettings() { comboBoxTrackColors->addItem(paletteName); } - const ColorPalette hotcuePalette = m_colorPaletteSettings.getHotcueColorPalette(); - + const ColorPalette hotcuePalette = + m_colorPaletteSettings.getHotcueColorPalette(); comboBoxHotcueColors->setCurrentText( hotcuePalette.getName()); - comboBoxTrackColors->setCurrentText( - m_colorPaletteSettings.getTrackColorPalette().getName()); + slotHotcuePaletteChanged(hotcuePalette.getName()); - QPixmap pixmap(80, 80); - QPainter painter(&pixmap); - pixmap.fill(Qt::black); - - comboBoxHotcueDefaultColor->addItem(tr("By hotcue number"), -1); - - for (int i = 0; i < hotcuePalette.size() && i < 4; ++i) { - painter.setBrush(mixxx::RgbColor::toQColor(hotcuePalette.at(i))); - painter.drawRect(0, i * 20, 80, 20); - } - comboBoxHotcueDefaultColor->setItemIcon(0, QIcon(pixmap)); - - for (int i = 0; i < hotcuePalette.size(); ++i) { - comboBoxHotcueDefaultColor->addItem(tr("Palette") + - QStringLiteral(" ") + QString::number(i + 1), - i); - pixmap.fill(mixxx::RgbColor::toQColor(hotcuePalette.at(i))); - comboBoxHotcueDefaultColor->setItemIcon(i + 1, QIcon(pixmap)); - } - - bool autoHotcueColors = - m_pConfig->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false); - if (autoHotcueColors) { - comboBoxHotcueDefaultColor->setCurrentIndex(0); - } else { - int hotcueDefaultColorIndex = m_pConfig->getValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), kHotcueDefaultColorIndex); - if (hotcueDefaultColorIndex < 0 || hotcueDefaultColorIndex >= hotcuePalette.size()) { - hotcueDefaultColorIndex = hotcuePalette.size() - 1; // default to last color (orange) - } - comboBoxHotcueDefaultColor->setCurrentIndex(hotcueDefaultColorIndex + 1); - } + const ColorPalette trackPalette = + m_colorPaletteSettings.getTrackColorPalette(); + comboBoxTrackColors->setCurrentText( + trackPalette.getName()); + slotTrackPaletteChanged(trackPalette.getName()); } // Set the default values for all the widgets @@ -144,3 +127,74 @@ void DlgPrefColors::slotApply() { m_pConfig->setValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), -1); } } + +QPixmap DlgPrefColors::drawPalettePreview(const QString& paletteName) { + foreach (const ColorPalette& palette, mixxx::PredefinedColorPalettes::kPalettes) { + if (paletteName == palette.getName()) { + int count = math_max(palette.size(), 1); + int width = math_min((200 / count), 16); + QPixmap pixmap(count * width, 16); + pixmap.fill(Qt::black); + QPainter painter(&pixmap); + for (int i = 0; i < palette.size(); ++i) { + painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); + painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); + painter.drawRect(i * width, 0, width, 16); + } + return pixmap; + } + } + return QPixmap(); +} + +void DlgPrefColors::slotTrackPaletteChanged(const QString& paletteName) { + QPixmap pixmap = drawPalettePreview(paletteName); + labelTrackPalette->setPixmap(pixmap); +} + +void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { + QPixmap preview = drawPalettePreview(paletteName); + labelHotcuePalette->setPixmap(preview); + + ColorPalette palette = mixxx::PredefinedColorPalettes::kPalettes[0]; + foreach (const ColorPalette& pal, mixxx::PredefinedColorPalettes::kPalettes) { + if (paletteName == pal.getName()) { + palette = pal; + break; + } + } + + comboBoxHotcueDefaultColor->clear(); + + QPixmap pixmap(80, 80); + QPainter painter(&pixmap); + pixmap.fill(Qt::black); + + comboBoxHotcueDefaultColor->addItem(tr("By hotcue number"), -1); + for (int i = 0; i < palette.size() && i < 4; ++i) { + painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); + painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); + painter.drawRect(0, i * 20, 80, 20); + } + comboBoxHotcueDefaultColor->setItemIcon(0, QIcon(pixmap)); + + for (int i = 0; i < palette.size(); ++i) { + comboBoxHotcueDefaultColor->addItem(tr("Palette") + + QStringLiteral(" ") + QString::number(i + 1), + i); + pixmap.fill(mixxx::RgbColor::toQColor(palette.at(i))); + comboBoxHotcueDefaultColor->setItemIcon(i + 1, QIcon(pixmap)); + } + + bool autoHotcueColors = + m_pConfig->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false); + if (autoHotcueColors) { + comboBoxHotcueDefaultColor->setCurrentIndex(0); + } else { + int hotcueDefaultColorIndex = m_pConfig->getValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), kHotcueDefaultColorIndex); + if (hotcueDefaultColorIndex < 0 || hotcueDefaultColorIndex >= palette.size()) { + hotcueDefaultColorIndex = palette.size() - 1; // default to last color (orange) + } + comboBoxHotcueDefaultColor->setCurrentIndex(hotcueDefaultColorIndex + 1); + } +} diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index 9feb41a873b9..fb77d862d3bf 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -22,9 +22,14 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { signals: void apply(const QString&); + private slots: + void slotTrackPaletteChanged(const QString& palette); + void slotHotcuePaletteChanged(const QString& palette); + private: void loadSettings(); void loadPaletteIntoEditor(const ColorPalette& palette); + QPixmap drawPalettePreview(const QString& paletteName); const UserSettingsPointer m_pConfig; ColorPaletteSettings m_colorPaletteSettings; diff --git a/src/preferences/dialog/dlgprefcolorsdlg.ui b/src/preferences/dialog/dlgprefcolorsdlg.ui index 3f053f56275e..023567dbc7dd 100644 --- a/src/preferences/dialog/dlgprefcolorsdlg.ui +++ b/src/preferences/dialog/dlgprefcolorsdlg.ui @@ -26,14 +26,21 @@ Colors - + + + + Hotcue Palette + + + + Hotcue palette - + @@ -43,7 +50,7 @@ - + Hotcue default color @@ -67,9 +74,16 @@ - + + + + + Track Palette + + + From b4d3fa54fad8a4302698d4d1819a087ce17519c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 15:47:10 +0100 Subject: [PATCH 051/393] Fix default default hotcue selection --- src/preferences/dialog/dlgprefcolors.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 9cce1909c11d..cc4948cd099a 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -83,7 +83,8 @@ void DlgPrefColors::slotResetToDefaults() { mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette.getName()); comboBoxTrackColors->setCurrentText( mixxx::PredefinedColorPalettes::kDefaultTrackColorPalette.getName()); - comboBoxHotcueDefaultColor->setCurrentIndex(1); + comboBoxHotcueDefaultColor->setCurrentIndex( + mixxx::PredefinedColorPalettes::kDefaultTrackColorPalette.size()); slotApply(); } @@ -156,7 +157,7 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { QPixmap preview = drawPalettePreview(paletteName); labelHotcuePalette->setPixmap(preview); - ColorPalette palette = mixxx::PredefinedColorPalettes::kPalettes[0]; + ColorPalette palette = mixxx::PredefinedColorPalettes::kDefaultTrackColorPalette; foreach (const ColorPalette& pal, mixxx::PredefinedColorPalettes::kPalettes) { if (paletteName == pal.getName()) { palette = pal; From e2740bac94ad34ac8da8fc4a8a6da6bded9e6668 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 24 Mar 2020 19:26:01 +0100 Subject: [PATCH 052/393] Prevent re-import of metadata from file tags after MusicBrainz lookup --- src/library/dlgtagfetcher.cpp | 7 ++++++- src/track/track.cpp | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/library/dlgtagfetcher.cpp b/src/library/dlgtagfetcher.cpp index 629ecf68fd73..c7394dea4c84 100644 --- a/src/library/dlgtagfetcher.cpp +++ b/src/library/dlgtagfetcher.cpp @@ -177,7 +177,12 @@ void DlgTagFetcher::apply() { trackRelease.releaseGroupId); } #endif // __EXTRA_METADATA__ - m_track->importMetadata(std::move(trackMetadata)); + m_track->importMetadata( + std::move(trackMetadata), + // Prevent re-import of outdated metadata from file tags + // by explicitly setting the synchronization time stamp + // to the current time. + QDateTime::currentDateTimeUtc()); } void DlgTagFetcher::quit() { diff --git a/src/track/track.cpp b/src/track/track.cpp index 27e04c8282ff..3252dc3faf6a 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -135,9 +135,20 @@ void Track::importMetadata( // enter locking scope QMutexLocker lock(&m_qMutex); - bool modified = compareAndSet( - &m_record.refMetadataSynchronized(), - !metadataSynchronized.isNull()); + bool modified = false; + // Only set the metadata synchronized flag (column `header_parsed` + // in the database) from false to true, but never reset it back to + // false. Otherwise file tags would be re-imported and overwrite + // the metadata stored in the database, e.g. after retrieving metadata + // from MusicBrainz! + // TODO: In the future this flag should become a time stamp + // to detect updates of files and then decide based on time + // stamps if file tags need to be re-imported. + if (!metadataSynchronized.isNull()) { + modified |= compareAndSet( + &m_record.refMetadataSynchronized(), + true); + } bool modifiedReplayGain = false; if (m_record.getMetadata() != importedMetadata) { modifiedReplayGain = From 04ed4681ccfe11de082a57964641d58eb2fdf85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 22:03:33 +0100 Subject: [PATCH 053/393] Revert "Fill table with assigned cue numbers if no cue numbers are set" This reverts commit 30aaea8a51e98dd242848c0a46f12f53c59c3c65. --- src/preferences/colorpaletteeditormodel.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/preferences/colorpaletteeditormodel.cpp b/src/preferences/colorpaletteeditormodel.cpp index 9335edd13b52..62d958fbe165 100644 --- a/src/preferences/colorpaletteeditormodel.cpp +++ b/src/preferences/colorpaletteeditormodel.cpp @@ -103,15 +103,9 @@ void ColorPaletteEditorModel::setColorPalette(const ColorPalette& palette) { // Make a map of hotcue indices QMap hotcueColorIndicesMap; QList hotcueColorIndices = palette.getHotcueIndices(); - if (hotcueColorIndices.size()) { - for (int i = 0; i < hotcueColorIndices.size(); i++) { - int colorIndex = hotcueColorIndices.at(i); - hotcueColorIndicesMap.insert(colorIndex, i); - } - } else { - for (int i = 0; i < palette.size(); i++) { - hotcueColorIndicesMap.insert(i, i); - } + for (int i = 0; i < hotcueColorIndices.size(); i++) { + int colorIndex = hotcueColorIndices.at(i); + hotcueColorIndicesMap.insert(colorIndex, i); } for (int i = 0; i < palette.size(); i++) { From a171d135db334c493155987f9d87911186b28ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 22:10:43 +0100 Subject: [PATCH 054/393] Improve the labels of the colors in the combobox --- src/preferences/dialog/dlgprefcolors.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index cc4948cd099a..586d6b9ff9d0 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -180,10 +180,15 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { comboBoxHotcueDefaultColor->setItemIcon(0, QIcon(pixmap)); for (int i = 0; i < palette.size(); ++i) { - comboBoxHotcueDefaultColor->addItem(tr("Palette") + - QStringLiteral(" ") + QString::number(i + 1), + QColor color = mixxx::RgbColor::toQColor(palette.at(i)); + comboBoxHotcueDefaultColor->addItem( + tr("Color") + + QStringLiteral(" ") + + QString::number(i + 1) + + QStringLiteral(": ") + + color.name(), i); - pixmap.fill(mixxx::RgbColor::toQColor(palette.at(i))); + pixmap.fill(color); comboBoxHotcueDefaultColor->setItemIcon(i + 1, QIcon(pixmap)); } From 68742d99617b9eda1edd43bffb38c93c579fa4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 23:04:43 +0100 Subject: [PATCH 055/393] introduce getHotcueColorPalette(const QString&) and make use of it. --- src/preferences/colorpalettesettings.cpp | 18 +++++++++-- src/preferences/colorpalettesettings.h | 6 +++- src/preferences/dialog/dlgprefcolors.cpp | 40 ++++++++++-------------- src/util/color/colorpalette.h | 5 ++- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/preferences/colorpalettesettings.cpp b/src/preferences/colorpalettesettings.cpp index fed31b327431..1b01ca9973b3 100644 --- a/src/preferences/colorpalettesettings.cpp +++ b/src/preferences/colorpalettesettings.cpp @@ -110,7 +110,14 @@ void ColorPaletteSettings::removePalette(const QString& name) { ColorPalette ColorPaletteSettings::getHotcueColorPalette() const { QString name = m_pConfig->getValueString(kHotcueColorPaletteConfigKey); - return getColorPalette(name, mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette); + return getHotcueColorPalette(name); +} + +ColorPalette ColorPaletteSettings::getHotcueColorPalette( + const QString& name) const { + return getColorPalette( + name, + mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette); } void ColorPaletteSettings::setHotcueColorPalette(const ColorPalette& colorPalette) { @@ -123,9 +130,16 @@ void ColorPaletteSettings::setHotcueColorPalette(const ColorPalette& colorPalett setColorPalette(name, colorPalette); } +ColorPalette ColorPaletteSettings::getTrackColorPalette( + const QString& name) const { + return getColorPalette( + name, + mixxx::PredefinedColorPalettes::kDefaultTrackColorPalette); +} + ColorPalette ColorPaletteSettings::getTrackColorPalette() const { QString name = m_pConfig->getValueString(kTrackColorPaletteConfigKey); - return getColorPalette(name, mixxx::PredefinedColorPalettes::kDefaultTrackColorPalette); + return getTrackColorPalette(name); } void ColorPaletteSettings::setTrackColorPalette(const ColorPalette& colorPalette) { diff --git a/src/preferences/colorpalettesettings.h b/src/preferences/colorpalettesettings.h index 39ce03e49e18..3816073f7446 100644 --- a/src/preferences/colorpalettesettings.h +++ b/src/preferences/colorpalettesettings.h @@ -9,13 +9,17 @@ class ColorPaletteSettings { : m_pConfig(pConfig) { } + ColorPalette getHotcueColorPalette(const QString& name) const; ColorPalette getHotcueColorPalette() const; void setHotcueColorPalette(const ColorPalette& colorPalette); + ColorPalette getTrackColorPalette(const QString& name) const; ColorPalette getTrackColorPalette() const; void setTrackColorPalette(const ColorPalette& colorPalette); - ColorPalette getColorPalette(const QString& name, const ColorPalette& defaultPalette) const; + ColorPalette getColorPalette( + const QString& name, + const ColorPalette& defaultPalette) const; void setColorPalette(const QString& name, const ColorPalette& colorPalette); void removePalette(const QString& name); QSet getColorPaletteNames() const; diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 586d6b9ff9d0..de37ec7e605e 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -53,13 +53,13 @@ DlgPrefColors::~DlgPrefColors() { void DlgPrefColors::loadSettings() { comboBoxHotcueColors->clear(); comboBoxTrackColors->clear(); - foreach (const ColorPalette& palette, mixxx::PredefinedColorPalettes::kPalettes) { + for (const auto& palette : qAsConst(mixxx::PredefinedColorPalettes::kPalettes)) { QString paletteName = palette.getName(); comboBoxHotcueColors->addItem(paletteName); comboBoxTrackColors->addItem(paletteName); } - foreach (const QString& paletteName, m_colorPaletteSettings.getColorPaletteNames()) { + for (const auto& paletteName : m_colorPaletteSettings.getColorPaletteNames()) { comboBoxHotcueColors->addItem(paletteName); comboBoxTrackColors->addItem(paletteName); } @@ -95,7 +95,7 @@ void DlgPrefColors::slotApply() { bool bHotcueColorPaletteFound = false; bool bTrackColorPaletteFound = false; - foreach (const ColorPalette& palette, mixxx::PredefinedColorPalettes::kPalettes) { + for (const auto& palette : qAsConst(mixxx::PredefinedColorPalettes::kPalettes)) { if (!bHotcueColorPaletteFound && hotcueColorPaletteName == palette.getName()) { m_colorPaletteSettings.setHotcueColorPalette(palette); bHotcueColorPaletteFound = true; @@ -130,20 +130,19 @@ void DlgPrefColors::slotApply() { } QPixmap DlgPrefColors::drawPalettePreview(const QString& paletteName) { - foreach (const ColorPalette& palette, mixxx::PredefinedColorPalettes::kPalettes) { - if (paletteName == palette.getName()) { - int count = math_max(palette.size(), 1); - int width = math_min((200 / count), 16); - QPixmap pixmap(count * width, 16); - pixmap.fill(Qt::black); - QPainter painter(&pixmap); - for (int i = 0; i < palette.size(); ++i) { - painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); - painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); - painter.drawRect(i * width, 0, width, 16); - } - return pixmap; + ColorPalette palette = m_colorPaletteSettings.getHotcueColorPalette(paletteName); + if (paletteName == palette.getName()) { + int count = math_max(palette.size(), 1); + int width = math_min((200 / count), 16); + QPixmap pixmap(count * width, 16); + pixmap.fill(Qt::black); + QPainter painter(&pixmap); + for (int i = 0; i < palette.size(); ++i) { + painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); + painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); + painter.drawRect(i * width, 0, width, 16); } + return pixmap; } return QPixmap(); } @@ -157,13 +156,8 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { QPixmap preview = drawPalettePreview(paletteName); labelHotcuePalette->setPixmap(preview); - ColorPalette palette = mixxx::PredefinedColorPalettes::kDefaultTrackColorPalette; - foreach (const ColorPalette& pal, mixxx::PredefinedColorPalettes::kPalettes) { - if (paletteName == pal.getName()) { - palette = pal; - break; - } - } + ColorPalette palette = + m_colorPaletteSettings.getHotcueColorPalette(paletteName); comboBoxHotcueDefaultColor->clear(); diff --git a/src/util/color/colorpalette.h b/src/util/color/colorpalette.h index 1354df10af75..410f9542af3a 100644 --- a/src/util/color/colorpalette.h +++ b/src/util/color/colorpalette.h @@ -6,7 +6,10 @@ class ColorPalette final { public: - explicit ColorPalette(QString name, QList colorList, QList hotcueColorIndices = {}) + ColorPalette( + QString name, + QList colorList, + QList hotcueColorIndices = {}) : m_name(name), m_colorList(colorList), m_hotcueColorIndices(hotcueColorIndices) { From f9714bbe6100fa046d9cbd2c52fba40ab8febd3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 24 Mar 2020 23:48:45 +0100 Subject: [PATCH 056/393] Added Icons to the palette select boxes --- src/preferences/dialog/dlgprefcolors.cpp | 45 ++++++++++++++++++------ src/preferences/dialog/dlgprefcolors.h | 1 + 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index de37ec7e605e..12bfb81cd1a1 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -55,13 +55,28 @@ void DlgPrefColors::loadSettings() { comboBoxTrackColors->clear(); for (const auto& palette : qAsConst(mixxx::PredefinedColorPalettes::kPalettes)) { QString paletteName = palette.getName(); + QIcon paletteIcon = drawPaletteIcon(paletteName); comboBoxHotcueColors->addItem(paletteName); + comboBoxHotcueColors->setItemIcon( + comboBoxHotcueColors->count() - 1, + paletteIcon); + comboBoxTrackColors->addItem(paletteName); + comboBoxTrackColors->setItemIcon( + comboBoxTrackColors->count() - 1, + paletteIcon); } for (const auto& paletteName : m_colorPaletteSettings.getColorPaletteNames()) { + QIcon paletteIcon = drawPaletteIcon(paletteName); comboBoxHotcueColors->addItem(paletteName); + comboBoxHotcueColors->setItemIcon( + comboBoxHotcueColors->count() - 1, + paletteIcon); comboBoxTrackColors->addItem(paletteName); + comboBoxTrackColors->setItemIcon( + comboBoxHotcueColors->count() - 1, + paletteIcon); } const ColorPalette hotcuePalette = @@ -147,6 +162,23 @@ QPixmap DlgPrefColors::drawPalettePreview(const QString& paletteName) { return QPixmap(); } +QIcon DlgPrefColors::drawPaletteIcon(const QString& paletteName) { + QPixmap pixmap(16, 16); + QPainter painter(&pixmap); + pixmap.fill(Qt::black); + + ColorPalette palette = m_colorPaletteSettings.getHotcueColorPalette(paletteName); + if (paletteName == palette.getName()) { + for (int i = 0; i < palette.size() && i < 4; ++i) { + painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); + painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); + painter.drawRect(0, i * 4, 16, 4); + } + return QIcon(pixmap); + } + return QIcon(); +} + void DlgPrefColors::slotTrackPaletteChanged(const QString& paletteName) { QPixmap pixmap = drawPalettePreview(paletteName); labelTrackPalette->setPixmap(pixmap); @@ -161,18 +193,11 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { comboBoxHotcueDefaultColor->clear(); - QPixmap pixmap(80, 80); - QPainter painter(&pixmap); - pixmap.fill(Qt::black); - comboBoxHotcueDefaultColor->addItem(tr("By hotcue number"), -1); - for (int i = 0; i < palette.size() && i < 4; ++i) { - painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); - painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); - painter.drawRect(0, i * 20, 80, 20); - } - comboBoxHotcueDefaultColor->setItemIcon(0, QIcon(pixmap)); + QIcon icon = drawPaletteIcon(paletteName); + comboBoxHotcueDefaultColor->setItemIcon(0, icon); + QPixmap pixmap(16, 16); for (int i = 0; i < palette.size(); ++i) { QColor color = mixxx::RgbColor::toQColor(palette.at(i)); comboBoxHotcueDefaultColor->addItem( diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index fb77d862d3bf..5ced4c710cfc 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -30,6 +30,7 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { void loadSettings(); void loadPaletteIntoEditor(const ColorPalette& palette); QPixmap drawPalettePreview(const QString& paletteName); + QIcon drawPaletteIcon(const QString& paletteName); const UserSettingsPointer m_pConfig; ColorPaletteSettings m_colorPaletteSettings; From 539d4daadf499cd5d750e75bd01b0163ec215458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 25 Mar 2020 00:20:20 +0100 Subject: [PATCH 057/393] Limit auto color asignment to 8 --- src/util/color/colorpalette.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/color/colorpalette.cpp b/src/util/color/colorpalette.cpp index f5ff71be2490..d6d57b15e6f8 100644 --- a/src/util/color/colorpalette.cpp +++ b/src/util/color/colorpalette.cpp @@ -20,7 +20,8 @@ mixxx::RgbColor ColorPalette::colorForHotcueIndex(unsigned int hotcueIndex) cons int colorIndex; if (m_hotcueColorIndices.isEmpty()) { // For hotcue n, get nth color from palette - colorIndex = hotcueIndex; + // But use only 8 to avoid odd apearances on Hercules P32 or similar + colorIndex = hotcueIndex % 8; } else { // For hotcue n, get nth color from palette colorIndex = m_hotcueColorIndices.at(hotcueIndex % m_hotcueColorIndices.size()); From 7b151703f49a7f54c000b4ee073f73acf78d7de5 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 24 Mar 2020 21:42:41 -0500 Subject: [PATCH 058/393] DlgPrefColors: use full palette preview for icons in comboboxes --- src/preferences/dialog/dlgprefcolors.cpp | 19 +++++----- src/preferences/dialog/dlgprefcolorsdlg.ui | 42 ++++++++-------------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 12bfb81cd1a1..cca242c7f437 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -13,6 +13,7 @@ namespace { constexpr int kHotcueDefaultColorIndex = -1; +constexpr QSize kPalettePreviewSize = QSize(200, 16); } // anonymous namespace @@ -23,6 +24,8 @@ DlgPrefColors::DlgPrefColors( m_colorPaletteSettings(ColorPaletteSettings(pConfig)) { setupUi(this); colorPaletteEditor->initialize(pConfig); + comboBoxHotcueColors->setIconSize(kPalettePreviewSize); + comboBoxTrackColors->setIconSize(kPalettePreviewSize); loadSettings(); @@ -55,7 +58,7 @@ void DlgPrefColors::loadSettings() { comboBoxTrackColors->clear(); for (const auto& palette : qAsConst(mixxx::PredefinedColorPalettes::kPalettes)) { QString paletteName = palette.getName(); - QIcon paletteIcon = drawPaletteIcon(paletteName); + QIcon paletteIcon = drawPalettePreview(paletteName); comboBoxHotcueColors->addItem(paletteName); comboBoxHotcueColors->setItemIcon( comboBoxHotcueColors->count() - 1, @@ -68,7 +71,7 @@ void DlgPrefColors::loadSettings() { } for (const auto& paletteName : m_colorPaletteSettings.getColorPaletteNames()) { - QIcon paletteIcon = drawPaletteIcon(paletteName); + QIcon paletteIcon = drawPalettePreview(paletteName); comboBoxHotcueColors->addItem(paletteName); comboBoxHotcueColors->setItemIcon( comboBoxHotcueColors->count() - 1, @@ -147,15 +150,15 @@ void DlgPrefColors::slotApply() { QPixmap DlgPrefColors::drawPalettePreview(const QString& paletteName) { ColorPalette palette = m_colorPaletteSettings.getHotcueColorPalette(paletteName); if (paletteName == palette.getName()) { + QPixmap pixmap(kPalettePreviewSize); int count = math_max(palette.size(), 1); - int width = math_min((200 / count), 16); - QPixmap pixmap(count * width, 16); + int widthPerColor = pixmap.width() / count; pixmap.fill(Qt::black); QPainter painter(&pixmap); for (int i = 0; i < palette.size(); ++i) { painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); - painter.drawRect(i * width, 0, width, 16); + painter.drawRect(i * widthPerColor, 0, widthPerColor, pixmap.height()); } return pixmap; } @@ -180,14 +183,10 @@ QIcon DlgPrefColors::drawPaletteIcon(const QString& paletteName) { } void DlgPrefColors::slotTrackPaletteChanged(const QString& paletteName) { - QPixmap pixmap = drawPalettePreview(paletteName); - labelTrackPalette->setPixmap(pixmap); + Q_UNUSED(paletteName); } void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { - QPixmap preview = drawPalettePreview(paletteName); - labelHotcuePalette->setPixmap(preview); - ColorPalette palette = m_colorPaletteSettings.getHotcueColorPalette(paletteName); diff --git a/src/preferences/dialog/dlgprefcolorsdlg.ui b/src/preferences/dialog/dlgprefcolorsdlg.ui index 023567dbc7dd..5e621e0780a9 100644 --- a/src/preferences/dialog/dlgprefcolorsdlg.ui +++ b/src/preferences/dialog/dlgprefcolorsdlg.ui @@ -26,14 +26,7 @@ Colors - - - - Hotcue Palette - - - - + Hotcue palette @@ -41,21 +34,7 @@ - - - - 0 - 0 - - - - - - - - Hotcue default color - - + @@ -74,13 +53,20 @@ - - + + + + Hotcue default color + + - - - Track Palette + + + + 0 + 0 + From 5d4321f037d3db020a8641813d6b1db321020503 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 24 Mar 2020 22:21:11 -0500 Subject: [PATCH 059/393] limit default hotcue palette to 8 colors --- src/util/color/colorpalette.cpp | 5 +---- src/util/color/predefinedcolorpalettes.cpp | 6 +++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/util/color/colorpalette.cpp b/src/util/color/colorpalette.cpp index d6d57b15e6f8..0e6dd4286b42 100644 --- a/src/util/color/colorpalette.cpp +++ b/src/util/color/colorpalette.cpp @@ -19,11 +19,8 @@ mixxx::RgbColor ColorPalette::previousColor(mixxx::RgbColor color) const { mixxx::RgbColor ColorPalette::colorForHotcueIndex(unsigned int hotcueIndex) const { int colorIndex; if (m_hotcueColorIndices.isEmpty()) { - // For hotcue n, get nth color from palette - // But use only 8 to avoid odd apearances on Hercules P32 or similar - colorIndex = hotcueIndex % 8; + colorIndex = hotcueIndex; } else { - // For hotcue n, get nth color from palette colorIndex = m_hotcueColorIndices.at(hotcueIndex % m_hotcueColorIndices.size()); } return at(colorIndex % size()); diff --git a/src/util/color/predefinedcolorpalettes.cpp b/src/util/color/predefinedcolorpalettes.cpp index 26d59f5eccbf..7dabeb4273d7 100644 --- a/src/util/color/predefinedcolorpalettes.cpp +++ b/src/util/color/predefinedcolorpalettes.cpp @@ -123,7 +123,11 @@ const ColorPalette PredefinedColorPalettes::kMixxxHotcueColorPalette = kColorMixxxPink, kColorMixxxWhite, kSchemaMigrationReplacementColor, - }); + }, + // Exclude kSchemaMigrationReplacementColor from the colors assigned to hotcues. + // If there were 9 colors assigned to hotcues, that would look weird on + // controllers with >8 hotcue buttons, for example a Novation Launchpad. + QList{0, 1, 2, 3, 4, 5, 6, 7}); const ColorPalette PredefinedColorPalettes::kSeratoDJIntroHotcueColorPalette = ColorPalette( From f1d3976921fc96f9b9cd64630d5f58a3dedbc4ce Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 25 Mar 2020 09:55:13 +0100 Subject: [PATCH 060/393] Add documentation for pure virtual functions --- src/network/webtask.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/network/webtask.h b/src/network/webtask.h index e78f0c2be94a..81f62bc46cca 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -171,10 +171,17 @@ class WebTask : public QObject { QPair receiveNetworkReply(); private: + // Try to compose and send the actual network request. If + // true is returned than the network request is running + // and a reply is pending. virtual bool doStart( QNetworkAccessManager* networkAccessManager, int parentTimeoutMillis) = 0; - // Handle status change requests and return the request URL + + // Handle status change requests by aborting a running request + // and return the request URL. If no request is running or if + // the request has already been finished tha QUrl() must be + // returned. virtual QUrl doAbort() = 0; virtual QUrl doTimeOut() = 0; From 168381f4475dc7bda970387d7e6791d886742f1e Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 25 Mar 2020 09:55:52 +0100 Subject: [PATCH 061/393] Simplify start procedure after rethinking the interactions --- src/network/webtask.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index 102149301270..b385e5461c18 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -190,16 +190,18 @@ void WebTask::slotStart(int timeoutMillis) { << "Starting..."; m_status = Status::Idle; if (!doStart(m_networkAccessManager, timeoutMillis)) { + // Still idle, because we are in the same thread. + // The callee is not supposed to abort a request + // before it has beeen started successfully. + DEBUG_ASSERT(m_status == Status::Idle); kLogger.warning() << "Start aborted"; return; } - - // The task could be aborted immediately while being started. - if (m_status == Status::Aborted) { - onAborted(doAbort()); - return; - } + // Still idle after the request has been started + // successfully, i.e. nothing happend yet in this + // thread. + DEBUG_ASSERT(m_status == Status::Idle); m_status = Status::Pending; DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId); From 9a155b8309d4ece3c6fc18a81799b4a2a504f022 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Wed, 25 Mar 2020 10:37:40 +0100 Subject: [PATCH 062/393] widget/woverview: Always repaint on passthru change (even without a track) --- src/widget/woverview.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 749757ea055a..1d86575c2903 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -357,11 +357,12 @@ void WOverview::onRateRatioChange(double v) { void WOverview::onPassthroughChange(double v) { m_bPassthroughEnabled = static_cast(v); - if (m_bPassthroughEnabled) { - update(); - } else { + if (!m_bPassthroughEnabled) { slotWaveformSummaryUpdated(); } + + // Always call this to trigger a repaint even if not track is loaded + update(); } void WOverview::updateCues(const QList &loadedCues) { From e07af9ebe45ee3d528ebfb69e0881f029f9cd0f5 Mon Sep 17 00:00:00 2001 From: Philip Gottschling Date: Wed, 25 Mar 2020 14:30:16 +0100 Subject: [PATCH 063/393] Fix LP1855321 - Prevent undesired jumping to main cue --- src/analyzer/analyzerthread.cpp | 1 + src/engine/controls/cuecontrol.cpp | 34 ++++++++++++++++----- src/engine/controls/cuecontrol.h | 1 + src/test/cuecontrol_test.cpp | 48 ++++++++++++++++++++++++++++-- src/track/track.cpp | 4 +++ src/track/track.h | 3 ++ 6 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/analyzer/analyzerthread.cpp b/src/analyzer/analyzerthread.cpp index a6f7f0802ea3..9affe5f545ad 100644 --- a/src/analyzer/analyzerthread.cpp +++ b/src/analyzer/analyzerthread.cpp @@ -348,6 +348,7 @@ void AnalyzerThread::emitDoneProgress(AnalyzerProgress doneProgress) { // thread that might trigger database actions! The TrackAnalysisScheduler // must store a TrackPointer until receiving the Done signal. TrackId trackId = m_currentTrack->getId(); + m_currentTrack->analysisFinished(); m_currentTrack.reset(); emitProgress(AnalyzerThreadState::Done, trackId, doneProgress); } diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 59bfd428f48f..35e53669736a 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -363,6 +363,10 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { } m_pLoadedTrack = pNewTrack; + connect(m_pLoadedTrack.get(), &Track::analyzed, + this, &CueControl::trackAnalyzed, + Qt::DirectConnection); + connect(m_pLoadedTrack.get(), &Track::cuesUpdated, this, &CueControl::trackCuesUpdated, Qt::DirectConnection); @@ -551,13 +555,14 @@ void CueControl::reloadCuesFromTrack() { if (!m_pLoadedTrack) return; - // Determine current playing position of the track. - TrackAt trackAt = getTrackAt(); - bool wasTrackAtZeroPos = isTrackAtZeroPos(); - bool wasTrackAtIntroCue = isTrackAtIntroCue(); - // Update COs with cues from track. loadCuesFromTrack(); +} + +void CueControl::trackAnalyzed() { + if (!m_pLoadedTrack) { + return; + } // Retrieve current position of cues from COs. double cue = m_pCuePoint->get(); @@ -567,11 +572,11 @@ void CueControl::reloadCuesFromTrack() { SeekOnLoadMode seekOnLoadMode = getSeekOnLoadPreference(); if (seekOnLoadMode == SeekOnLoadMode::MainCue) { - if ((trackAt == TrackAt::Cue || wasTrackAtZeroPos) && cue != Cue::kNoPosition) { + if (cue != Cue::kNoPosition) { seekExact(cue); } } else if (seekOnLoadMode == SeekOnLoadMode::IntroStart) { - if ((wasTrackAtIntroCue || wasTrackAtZeroPos) && intro != Cue::kNoPosition) { + if (intro != Cue::kNoPosition) { seekExact(intro); } } @@ -588,7 +593,22 @@ void CueControl::trackBeatsUpdated() { void CueControl::quantizeChanged(double v) { Q_UNUSED(v); + // check if we were at the cue point before + bool wasTrackAtCue = getTrackAt() == TrackAt::Cue; + bool wasTrackAtIntro = isTrackAtIntroCue(); + reloadCuesFromTrack(); + + // Retrieve new cue pos and follow + double cue = m_pCuePoint->get(); + if (wasTrackAtCue && cue != Cue::kNoPosition) { + seekExact(cue); + } + // Retrieve new intro start pos and follow + double intro = m_pIntroStartPosition->get(); + if(wasTrackAtIntro && intro != Cue::kNoPosition) { + seekExact(intro); + } } void CueControl::hotcueSet(HotcueControl* pControl, double v) { diff --git a/src/engine/controls/cuecontrol.h b/src/engine/controls/cuecontrol.h index 2d406a7d439e..b1fdad21cac5 100644 --- a/src/engine/controls/cuecontrol.h +++ b/src/engine/controls/cuecontrol.h @@ -138,6 +138,7 @@ class CueControl : public EngineControl { void quantizeChanged(double v); void cueUpdated(); + void trackAnalyzed(); void trackCuesUpdated(); void trackBeatsUpdated(); void hotcueSet(HotcueControl* pControl, double v); diff --git a/src/test/cuecontrol_test.cpp b/src/test/cuecontrol_test.cpp index de18a0eb64f8..f2932ffc8b65 100644 --- a/src/test/cuecontrol_test.cpp +++ b/src/test/cuecontrol_test.cpp @@ -273,8 +273,9 @@ TEST_F(CueControlTest, SeekOnLoadMainCue) { EXPECT_DOUBLE_EQ(100.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(100.0, getCurrentSample()); - // Move cue and check if track is following it. + // Move cue like silence analysis does and check if track is following it pTrack->setCuePoint(CuePosition(200.0)); + pTrack->analysisFinished(); ProcessBuffer(); EXPECT_DOUBLE_EQ(200.0, m_pCuePoint->get()); @@ -292,14 +293,57 @@ TEST_F(CueControlTest, SeekOnLoadDefault_CueInPreroll) { EXPECT_DOUBLE_EQ(-100.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(-100.0, getCurrentSample()); - // Move cue and check if track is following it. + // Move cue like silence analysis does and check if track is following it pTrack->setCuePoint(CuePosition(-200.0)); + pTrack->analysisFinished(); ProcessBuffer(); EXPECT_DOUBLE_EQ(-200.0, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(-200.0, getCurrentSample()); } +TEST_F(CueControlTest, FollowCueOnQuantize) { + config()->set(ConfigKey("[Controls]", "CueRecall"), + ConfigValue(static_cast(SeekOnLoadMode::MainCue))); + TrackPointer pTrack = createTestTrack(); + pTrack->setSampleRate(44100); + pTrack->setBpm(120.0); + + const int frameSize = 2; + const int sampleRate = pTrack->getSampleRate(); + const double bpm = pTrack->getBpm(); + const double beatLength = (60.0 * sampleRate / bpm) * frameSize; + double cuePos = 1.8*beatLength; + double quantizedCuePos = 2.0*beatLength; + pTrack->setCuePoint(cuePos); + + loadTrack(pTrack); + + EXPECT_DOUBLE_EQ(cuePos, m_pCuePoint->get()); + EXPECT_DOUBLE_EQ(cuePos, getCurrentSample()); + + // enable quantization and expect current position to follow + m_pQuantizeEnabled->set(1); + ProcessBuffer(); + EXPECT_DOUBLE_EQ(quantizedCuePos, m_pCuePoint->get()); + EXPECT_DOUBLE_EQ(quantizedCuePos, getCurrentSample()); + + // move current position to track start + m_pQuantizeEnabled->set(0); + ProcessBuffer(); + setCurrentSample(0.0); + ProcessBuffer(); + EXPECT_DOUBLE_EQ(0.0, getCurrentSample()); + + // enable quantization again and expect play position to stay at track start + m_pQuantizeEnabled->set(1); + ProcessBuffer(); + EXPECT_DOUBLE_EQ(quantizedCuePos, m_pCuePoint->get()); + EXPECT_DOUBLE_EQ(0.0, getCurrentSample()); + + +} + TEST_F(CueControlTest, IntroCue_SetStartEnd_ClearStartEnd) { TrackPointer pTrack = createAndLoadFakeTrack(); diff --git a/src/track/track.cpp b/src/track/track.cpp index 27e04c8282ff..9aa425c01244 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -702,6 +702,10 @@ void Track::setCuePoint(CuePosition cue) { emit cuesUpdated(); } +void Track::analysisFinished() { + emit analyzed(); +} + CuePosition Track::getCuePoint() const { QMutexLocker lock(&m_qMutex); return m_record.getCuePoint(); diff --git a/src/track/track.h b/src/track/track.h index 78965cf65dfa..87de8eba241d 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -248,6 +248,8 @@ class Track : public QObject { CuePosition getCuePoint() const; // Set the track's main cue point void setCuePoint(CuePosition cue); + // Call when analysis is done. + void analysisFinished(); // Calls for managing the track's cue points CuePointer createAndAddCue(); @@ -325,6 +327,7 @@ class Track : public QObject { void keysUpdated(); void ReplayGainUpdated(mixxx::ReplayGain replayGain); void cuesUpdated(); + void analyzed(); void changed(TrackId trackId); void dirty(TrackId trackId); From 97fc29860900e51bdd29234677d76ac3ae66ec4f Mon Sep 17 00:00:00 2001 From: Philip Gottschling Date: Wed, 25 Mar 2020 15:06:12 +0100 Subject: [PATCH 064/393] do not follow cue when playing --- src/engine/controls/cuecontrol.cpp | 16 ++++++++++++---- src/test/cuecontrol_test.cpp | 6 ++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 35e53669736a..bb5f322a0e73 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -363,9 +363,7 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { } m_pLoadedTrack = pNewTrack; - connect(m_pLoadedTrack.get(), &Track::analyzed, - this, &CueControl::trackAnalyzed, - Qt::DirectConnection); + connect(m_pLoadedTrack.get(), &Track::analyzed, this, &CueControl::trackAnalyzed, Qt::DirectConnection); connect(m_pLoadedTrack.get(), &Track::cuesUpdated, this, &CueControl::trackCuesUpdated, @@ -564,6 +562,11 @@ void CueControl::trackAnalyzed() { return; } + // if we are playing (no matter what reason for) do not seek + if (m_pPlay->toBool()) { + return; + } + // Retrieve current position of cues from COs. double cue = m_pCuePoint->get(); double intro = m_pIntroStartPosition->get(); @@ -599,6 +602,11 @@ void CueControl::quantizeChanged(double v) { reloadCuesFromTrack(); + // if we are playing (no matter what reason for) do not seek + if (m_pPlay->toBool()) { + return; + } + // Retrieve new cue pos and follow double cue = m_pCuePoint->get(); if (wasTrackAtCue && cue != Cue::kNoPosition) { @@ -606,7 +614,7 @@ void CueControl::quantizeChanged(double v) { } // Retrieve new intro start pos and follow double intro = m_pIntroStartPosition->get(); - if(wasTrackAtIntro && intro != Cue::kNoPosition) { + if (wasTrackAtIntro && intro != Cue::kNoPosition) { seekExact(intro); } } diff --git a/src/test/cuecontrol_test.cpp b/src/test/cuecontrol_test.cpp index f2932ffc8b65..ed21f7473da9 100644 --- a/src/test/cuecontrol_test.cpp +++ b/src/test/cuecontrol_test.cpp @@ -313,8 +313,8 @@ TEST_F(CueControlTest, FollowCueOnQuantize) { const int sampleRate = pTrack->getSampleRate(); const double bpm = pTrack->getBpm(); const double beatLength = (60.0 * sampleRate / bpm) * frameSize; - double cuePos = 1.8*beatLength; - double quantizedCuePos = 2.0*beatLength; + double cuePos = 1.8 * beatLength; + double quantizedCuePos = 2.0 * beatLength; pTrack->setCuePoint(cuePos); loadTrack(pTrack); @@ -340,8 +340,6 @@ TEST_F(CueControlTest, FollowCueOnQuantize) { ProcessBuffer(); EXPECT_DOUBLE_EQ(quantizedCuePos, m_pCuePoint->get()); EXPECT_DOUBLE_EQ(0.0, getCurrentSample()); - - } TEST_F(CueControlTest, IntroCue_SetStartEnd_ClearStartEnd) { From edc4af63551a672c94d8c72c7edddb1e83e44d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 26 Mar 2020 21:34:43 +0100 Subject: [PATCH 065/393] Rename Intro Palette to Track Metadata und remove it from the user available list. --- src/util/color/predefinedcolorpalettes.cpp | 83 +++++++++++----------- src/util/color/predefinedcolorpalettes.h | 2 +- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/util/color/predefinedcolorpalettes.cpp b/src/util/color/predefinedcolorpalettes.cpp index 26d59f5eccbf..4a5a9ac70486 100644 --- a/src/util/color/predefinedcolorpalettes.cpp +++ b/src/util/color/predefinedcolorpalettes.cpp @@ -31,25 +31,28 @@ constexpr mixxx::RgbColor kTraktorProTrackColorBlue(0x0187FF); constexpr mixxx::RgbColor kTraktorProTrackColorViolet(0xA669FF); constexpr mixxx::RgbColor kTraktorProTrackColorMagenta(0xFE55EA); -// Serato DJ Intro Hotcue Color Palette -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorRed(0xCC0000); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorOrange(0xCC4400); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorBrown(0xCC8800); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorYellow(0xCCCC00); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorEmerald(0x88CC00); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorKelly(0x44CC00); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorGreen(0x00CC00); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorSea(0x00CC44); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorJade(0x00CC88); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorTurquoise(0x00CCCC); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorTeal(0x0088CC); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorBlue(0x0044CC); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorDarkBlue(0x0000CC); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorViolet(0x4400CC); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorPurple(0x8800CC); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorFuchsia(0xCC00CC); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorMagenta(0xCC0088); -constexpr mixxx::RgbColor kSeratoDJIntroHotcueColorCarmine(0xCC0044); +// Serato Track Metadata Hotcue Color Palette +// The Serato DJ Pro hotcue colors, shown in the GUI, are stored as these +// colors into the Serato's file metadata. +// Original these colors where shown in the obsolete Serato DJ Intro. +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorRed(0xCC0000); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorOrange(0xCC4400); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorBrown(0xCC8800); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorYellow(0xCCCC00); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorEmerald(0x88CC00); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorKelly(0x44CC00); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorGreen(0x00CC00); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorSea(0x00CC44); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorJade(0x00CC88); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorTurquoise(0x00CCCC); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorTeal(0x0088CC); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorBlue(0x0044CC); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorDarkBlue(0x0000CC); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorViolet(0x4400CC); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorPurple(0x8800CC); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorFuchsia(0xCC00CC); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorMagenta(0xCC0088); +constexpr mixxx::RgbColor kSeratoTrackMetadataHotcueColorCarmine(0xCC0044); // Serato DJ Pro Hotcue Color Palette constexpr mixxx::RgbColor kSeratoDJProHotcueColorRed1(0xC02626); @@ -125,28 +128,28 @@ const ColorPalette PredefinedColorPalettes::kMixxxHotcueColorPalette = kSchemaMigrationReplacementColor, }); -const ColorPalette PredefinedColorPalettes::kSeratoDJIntroHotcueColorPalette = +const ColorPalette PredefinedColorPalettes::kSeratoTrackMetadataHotcueColorPalette = ColorPalette( - QStringLiteral("Serato DJ Intro Hotcue Colors"), + QStringLiteral("Serato DJ Track Metadata Hotcue Colors"), QList{ - kSeratoDJIntroHotcueColorRed, - kSeratoDJIntroHotcueColorOrange, - kSeratoDJIntroHotcueColorBrown, - kSeratoDJIntroHotcueColorYellow, - kSeratoDJIntroHotcueColorEmerald, - kSeratoDJIntroHotcueColorKelly, - kSeratoDJIntroHotcueColorGreen, - kSeratoDJIntroHotcueColorSea, - kSeratoDJIntroHotcueColorJade, - kSeratoDJIntroHotcueColorTurquoise, - kSeratoDJIntroHotcueColorTeal, - kSeratoDJIntroHotcueColorBlue, - kSeratoDJIntroHotcueColorDarkBlue, - kSeratoDJIntroHotcueColorViolet, - kSeratoDJIntroHotcueColorPurple, - kSeratoDJIntroHotcueColorFuchsia, - kSeratoDJIntroHotcueColorMagenta, - kSeratoDJIntroHotcueColorCarmine, + kSeratoTrackMetadataHotcueColorRed, + kSeratoTrackMetadataHotcueColorOrange, + kSeratoTrackMetadataHotcueColorBrown, + kSeratoTrackMetadataHotcueColorYellow, + kSeratoTrackMetadataHotcueColorEmerald, + kSeratoTrackMetadataHotcueColorKelly, + kSeratoTrackMetadataHotcueColorGreen, + kSeratoTrackMetadataHotcueColorSea, + kSeratoTrackMetadataHotcueColorJade, + kSeratoTrackMetadataHotcueColorTurquoise, + kSeratoTrackMetadataHotcueColorTeal, + kSeratoTrackMetadataHotcueColorBlue, + kSeratoTrackMetadataHotcueColorDarkBlue, + kSeratoTrackMetadataHotcueColorViolet, + kSeratoTrackMetadataHotcueColorPurple, + kSeratoTrackMetadataHotcueColorFuchsia, + kSeratoTrackMetadataHotcueColorMagenta, + kSeratoTrackMetadataHotcueColorCarmine, }, QList{0, 2, 12, 3, 6, 15, 9, 14}); @@ -251,8 +254,6 @@ const QList PredefinedColorPalettes::kPalettes{ // Hotcue Color Palettes mixxx::PredefinedColorPalettes::kMixxxHotcueColorPalette, mixxx::PredefinedColorPalettes::kSeratoDJProHotcueColorPalette, - mixxx::PredefinedColorPalettes::kSeratoDJIntroHotcueColorPalette, - // Track Color Palettes mixxx::PredefinedColorPalettes::kRekordboxTrackColorPalette, mixxx::PredefinedColorPalettes::kSeratoDJProTrackColorPalette, diff --git a/src/util/color/predefinedcolorpalettes.h b/src/util/color/predefinedcolorpalettes.h index a1f910fc3bec..87deb01f2f7c 100644 --- a/src/util/color/predefinedcolorpalettes.h +++ b/src/util/color/predefinedcolorpalettes.h @@ -6,7 +6,7 @@ namespace mixxx { class PredefinedColorPalettes { public: static const ColorPalette kMixxxHotcueColorPalette; - static const ColorPalette kSeratoDJIntroHotcueColorPalette; + static const ColorPalette kSeratoTrackMetadataHotcueColorPalette; static const ColorPalette kSeratoDJProHotcueColorPalette; static const ColorPalette kRekordboxTrackColorPalette; From 3ab4f6372d38f7cd69d17f0adf246336c827dc94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 26 Mar 2020 23:03:20 +0100 Subject: [PATCH 066/393] Inital hide the palette editor --- src/preferences/colorpaletteeditor.cpp | 13 ++++++----- src/preferences/colorpaletteeditor.h | 6 ++++-- src/preferences/dialog/dlgprefcolors.cpp | 25 ++++++++++++++++++++++ src/preferences/dialog/dlgprefcolors.h | 2 ++ src/preferences/dialog/dlgprefcolorsdlg.ui | 24 +++++++++++++++++++++ 5 files changed, 63 insertions(+), 7 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 24c7333f2d76..6e89c14f00d2 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -28,7 +28,7 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) QDialogButtonBox* pButtonBox = new QDialogButtonBox(); m_pSaveButton = pButtonBox->addButton(QDialogButtonBox::Save); m_pResetButton = pButtonBox->addButton(QDialogButtonBox::Reset); - m_pDiscardButton = pButtonBox->addButton(QDialogButtonBox::Discard); + m_pCloseButton = pButtonBox->addButton(QDialogButtonBox::Close); QHBoxLayout* pTopLayout = new QHBoxLayout(); pTopLayout->addWidget(new QLabel(tr("Name"))); @@ -84,10 +84,10 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) &QPushButton::clicked, this, &ColorPaletteEditor::slotResetButtonClicked); - connect(m_pDiscardButton, + connect(m_pCloseButton, &QPushButton::clicked, this, - &ColorPaletteEditor::slotDiscardButtonClicked); + &ColorPaletteEditor::slotCloseButtonClicked); connect(m_pSaveButton, &QPushButton::clicked, this, @@ -117,7 +117,6 @@ void ColorPaletteEditor::slotUpdateButtons() { bool bEmpty = m_pModel->isEmpty(); m_pResetButton->setEnabled(bDirty); m_pSaveButton->setEnabled(!m_bPaletteExists || (!m_bPaletteIsReadOnly && bDirty && !bEmpty)); - m_pDiscardButton->setEnabled(m_bPaletteExists && !m_bPaletteIsReadOnly); } void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { @@ -187,7 +186,11 @@ void ColorPaletteEditor::slotPaletteNameChanged(const QString& text) { slotUpdateButtons(); } -void ColorPaletteEditor::slotDiscardButtonClicked() { +void ColorPaletteEditor::slotCloseButtonClicked() { + emit closeButtonClicked(); +} + +void ColorPaletteEditor::slotRemoveButtonClicked() { QString paletteName = m_pPaletteNameComboBox->currentText(); ColorPaletteSettings colorPaletteSettings(m_pConfig); colorPaletteSettings.removePalette(paletteName); diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index 9786c10b592c..4da688b96ac8 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -21,15 +21,17 @@ class ColorPaletteEditor : public QWidget { signals: void paletteChanged(QString name); void paletteRemoved(QString name); + void closeButtonClicked(); private slots: void slotUpdateButtons(); void slotTableViewDoubleClicked(const QModelIndex& index); void slotTableViewContextMenuRequested(const QPoint& pos); void slotPaletteNameChanged(const QString& text); - void slotDiscardButtonClicked(); + void slotCloseButtonClicked(); void slotSaveButtonClicked(); void slotResetButtonClicked(); + void slotRemoveButtonClicked(); private: bool m_bPaletteExists; @@ -40,6 +42,6 @@ class ColorPaletteEditor : public QWidget { parented_ptr m_pTableView; parented_ptr m_pModel; QPushButton* m_pSaveButton; - QPushButton* m_pDiscardButton; + QPushButton* m_pCloseButton; QPushButton* m_pResetButton; }; diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 12bfb81cd1a1..d2573ef8feec 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -24,6 +24,8 @@ DlgPrefColors::DlgPrefColors( setupUi(this); colorPaletteEditor->initialize(pConfig); + groupBoxPaletteEditor->hide(); + loadSettings(); connect(colorPaletteEditor, @@ -34,6 +36,10 @@ DlgPrefColors::DlgPrefColors( &ColorPaletteEditor::paletteRemoved, this, &DlgPrefColors::loadSettings); + connect(colorPaletteEditor, + &ColorPaletteEditor::closeButtonClicked, + this, + &DlgPrefColors::slotCloseClicked); connect(comboBoxTrackColors, QOverload::of(&QComboBox::currentIndexChanged), @@ -44,6 +50,11 @@ DlgPrefColors::DlgPrefColors( QOverload::of(&QComboBox::currentIndexChanged), this, &DlgPrefColors::slotHotcuePaletteChanged); + + connect(pushButtonEdit, + &QPushButton::clicked, + this, + &DlgPrefColors::slotEditClicked); } DlgPrefColors::~DlgPrefColors() { @@ -223,3 +234,17 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { comboBoxHotcueDefaultColor->setCurrentIndex(hotcueDefaultColorIndex + 1); } } + +void DlgPrefColors::slotEditClicked() { + pushButtonEdit->hide(); + labelCustomPalette->hide(); + widgetSpacer->hide(); + groupBoxPaletteEditor->show(); +} + +void DlgPrefColors::slotCloseClicked() { + groupBoxPaletteEditor->hide(); + widgetSpacer->show(); + pushButtonEdit->show(); + labelCustomPalette->show(); +} diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index 5ced4c710cfc..09a42b26fc25 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -18,6 +18,8 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { // Apply changes to widget void slotApply(); void slotResetToDefaults(); + void slotEditClicked(); + void slotCloseClicked(); signals: void apply(const QString&); diff --git a/src/preferences/dialog/dlgprefcolorsdlg.ui b/src/preferences/dialog/dlgprefcolorsdlg.ui index 023567dbc7dd..7f3a6dd6884a 100644 --- a/src/preferences/dialog/dlgprefcolorsdlg.ui +++ b/src/preferences/dialog/dlgprefcolorsdlg.ui @@ -84,6 +84,30 @@ + + + + Custom palettes + + + + + + + Edit + + + + + + + + 0 + 20 + + + + From 5cb7e2f375a0e7649a9f06b571be928f20d11c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 27 Mar 2020 00:29:09 +0100 Subject: [PATCH 067/393] Split Save as and Template Combobox --- src/preferences/colorpaletteeditor.cpp | 79 ++++++++++++++++---------- src/preferences/colorpaletteeditor.h | 6 +- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 6e89c14f00d2..2cc0b709e39e 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -20,23 +20,34 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) : QWidget(parent), m_bPaletteExists(false), m_bPaletteIsReadOnly(false), - m_pPaletteNameComboBox(make_parented()), + m_pPaletteTemplateComboBox(make_parented()), + m_pSaveAsComboBox(make_parented()), m_pTableView(make_parented()), m_pModel(make_parented(m_pTableView)) { - m_pPaletteNameComboBox->setEditable(true); + m_pSaveAsComboBox->setEditable(true); + + m_pResetButton = make_parented(tr("Reset"), this); QDialogButtonBox* pButtonBox = new QDialogButtonBox(); + m_pRemoveButton = pButtonBox->addButton( + tr("Remove Palette"), + QDialogButtonBox::DestructiveRole); m_pSaveButton = pButtonBox->addButton(QDialogButtonBox::Save); - m_pResetButton = pButtonBox->addButton(QDialogButtonBox::Reset); m_pCloseButton = pButtonBox->addButton(QDialogButtonBox::Close); QHBoxLayout* pTopLayout = new QHBoxLayout(); pTopLayout->addWidget(new QLabel(tr("Name"))); - pTopLayout->addWidget(m_pPaletteNameComboBox, 1); + pTopLayout->addWidget(m_pSaveAsComboBox, 1); + + QHBoxLayout* pBottomLayout = new QHBoxLayout(); + pBottomLayout->addWidget(new QLabel(tr("Reset to"))); + pBottomLayout->addWidget(m_pPaletteTemplateComboBox, 1); + pBottomLayout->addWidget(m_pResetButton.get()); QVBoxLayout* pLayout = new QVBoxLayout(); pLayout->addLayout(pTopLayout); pLayout->addWidget(m_pTableView, 1); + pLayout->addLayout(pBottomLayout); pLayout->addWidget(pButtonBox); setLayout(pLayout); setContentsMargins(0, 0, 0, 0); @@ -76,7 +87,7 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) &QTableView::customContextMenuRequested, this, &ColorPaletteEditor::slotTableViewContextMenuRequested); - connect(m_pPaletteNameComboBox, + connect(m_pSaveAsComboBox, &QComboBox::editTextChanged, this, &ColorPaletteEditor::slotPaletteNameChanged); @@ -92,6 +103,10 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) &QPushButton::clicked, this, &ColorPaletteEditor::slotSaveButtonClicked); + connect(m_pRemoveButton, + &QPushButton::clicked, + this, + &ColorPaletteEditor::slotRemoveButtonClicked); } void ColorPaletteEditor::initialize(UserSettingsPointer pConfig) { @@ -101,22 +116,34 @@ void ColorPaletteEditor::initialize(UserSettingsPointer pConfig) { } void ColorPaletteEditor::reset() { - m_pPaletteNameComboBox->clear(); + m_pPaletteTemplateComboBox->clear(); + m_pSaveAsComboBox->clear(); + for (const ColorPalette& palette : mixxx::PredefinedColorPalettes::kPalettes) { - m_pPaletteNameComboBox->addItem(palette.getName()); + m_pPaletteTemplateComboBox->addItem(palette.getName()); } - m_pPaletteNameComboBox->insertSeparator(mixxx::PredefinedColorPalettes::kPalettes.size()); + ColorPaletteSettings colorPaletteSettings(m_pConfig); - for (const QString& paletteName : colorPaletteSettings.getColorPaletteNames()) { - m_pPaletteNameComboBox->addItem(paletteName); + if (colorPaletteSettings.getColorPaletteNames().count()) { + for (const QString& paletteName : colorPaletteSettings.getColorPaletteNames()) { + m_pSaveAsComboBox->addItem(paletteName); + m_pPaletteTemplateComboBox->addItem(paletteName); + } + } else { + m_pSaveAsComboBox->addItem(tr("Custom Color Palette")); + slotResetButtonClicked(); } } void ColorPaletteEditor::slotUpdateButtons() { bool bDirty = m_pModel->isDirty(); bool bEmpty = m_pModel->isEmpty(); - m_pResetButton->setEnabled(bDirty); - m_pSaveButton->setEnabled(!m_bPaletteExists || (!m_bPaletteIsReadOnly && bDirty && !bEmpty)); + m_pSaveButton->setEnabled( + !m_pSaveAsComboBox->currentText().isEmpty() && + (!m_bPaletteExists || (!m_bPaletteIsReadOnly && bDirty && !bEmpty))); + m_pRemoveButton->setEnabled( + m_bPaletteExists && + !m_bPaletteIsReadOnly); } void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { @@ -191,7 +218,7 @@ void ColorPaletteEditor::slotCloseButtonClicked() { } void ColorPaletteEditor::slotRemoveButtonClicked() { - QString paletteName = m_pPaletteNameComboBox->currentText(); + QString paletteName = m_pSaveAsComboBox->currentText(); ColorPaletteSettings colorPaletteSettings(m_pConfig); colorPaletteSettings.removePalette(paletteName); reset(); @@ -199,30 +226,22 @@ void ColorPaletteEditor::slotRemoveButtonClicked() { } void ColorPaletteEditor::slotSaveButtonClicked() { - QString paletteName = m_pPaletteNameComboBox->currentText(); + QString paletteName = m_pSaveAsComboBox->currentText(); ColorPaletteSettings colorPaletteSettings(m_pConfig); colorPaletteSettings.setColorPalette(paletteName, m_pModel->getColorPalette(paletteName)); m_pModel->setDirty(false); reset(); - m_pPaletteNameComboBox->setCurrentText(paletteName); + m_pSaveAsComboBox->setCurrentText(paletteName); emit paletteChanged(paletteName); } void ColorPaletteEditor::slotResetButtonClicked() { - QString paletteName = m_pPaletteNameComboBox->currentText(); + QString paletteName = m_pPaletteTemplateComboBox->currentText(); ColorPaletteSettings colorPaletteSettings(m_pConfig); - bool bPaletteExists = colorPaletteSettings.getColorPaletteNames().contains(paletteName); - if (!bPaletteExists) { - for (const ColorPalette& palette : mixxx::PredefinedColorPalettes::kPalettes) { - if (paletteName == palette.getName()) { - bPaletteExists = true; - break; - } - } - } - m_pModel->setDirty(false); - reset(); - if (bPaletteExists) { - m_pPaletteNameComboBox->setCurrentText(paletteName); - } + ColorPalette palette = colorPaletteSettings.getColorPalette( + paletteName, + mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette); + m_pModel->setColorPalette(palette); + m_pModel->setDirty(true); + slotUpdateButtons(); } diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index 4da688b96ac8..308154a1990d 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -38,10 +38,12 @@ class ColorPaletteEditor : public QWidget { bool m_bPaletteIsReadOnly; UserSettingsPointer m_pConfig; - parented_ptr m_pPaletteNameComboBox; + parented_ptr m_pPaletteTemplateComboBox; + parented_ptr m_pSaveAsComboBox; parented_ptr m_pTableView; parented_ptr m_pModel; QPushButton* m_pSaveButton; QPushButton* m_pCloseButton; - QPushButton* m_pResetButton; + QPushButton* m_pRemoveButton; + parented_ptr(m_pResetButton); }; From b79ddd0208d73d56f86c800c2df8162dfcc33d1d Mon Sep 17 00:00:00 2001 From: ronso0 Date: Fri, 27 Mar 2020 01:39:22 +0100 Subject: [PATCH 068/393] optimize DlgPrefControllerDlg.ui for manual editing, use meaningful widget labels --- src/controllers/dlgprefcontrollerdlg.ui | 278 ++++++++++++------------ 1 file changed, 139 insertions(+), 139 deletions(-) diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index a616322a2d0f..b89a809c5eba 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -30,7 +30,103 @@ Controller Setup - + + + + true + + + + 0 + 0 + + + + + 14 + 75 + true + + + + Controller Name + + + + + + + true + + + + 0 + 0 + + + + + + + (device category goes here) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Enabled + + + + + + + Click to start the Controller Learning wizard. + + + + + + Learning Wizard (MIDI Only) + + + false + + + false + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Load Preset: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + comboBoxPreset + + + + @@ -52,8 +148,8 @@ Preset Info - - + + 0 @@ -61,41 +157,13 @@ - Support: + Name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - 0 - 0 - - - - Qt::ClickFocus - - - - - - Qt::ImhUrlCharactersOnly - - - (forum link for preset goes here) - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - - - @@ -121,8 +189,8 @@ - - + + 0 @@ -130,7 +198,7 @@ - Description: + Author: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -150,6 +218,22 @@ + + + + + 0 + 0 + + + + Description: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -181,8 +265,8 @@ - - + + 0 @@ -190,84 +274,44 @@ - Author: + Support: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + 0 0 + + Qt::ClickFocus + + + + + + Qt::ImhUrlCharactersOnly + - Name: + (forum link for preset goes here) - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - - - - true - - - - 0 - 0 - - - - - 14 - 75 - true - - - - Controller Name - - - - - - - true - - - - 0 - 0 - - - - - - - (device category goes here) - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Enabled - - - @@ -281,50 +325,6 @@ - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Load Preset: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - comboBoxPreset - - - - - - - Click to start the Controller Learning wizard. - - - - - - Learning Wizard (MIDI Only) - - - false - - - false - - - From 1d58a7cae134e48a582fa878b7973ccfc9acc527 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Fri, 27 Mar 2020 01:40:22 +0100 Subject: [PATCH 069/393] add scriptFiles layout --- src/controllers/dlgprefcontrollerdlg.ui | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index b89a809c5eba..fed1df3aa653 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -309,6 +309,25 @@ + + + + + 0 + 0 + + + + Script Files: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + From b267a448a8958ba0fe8b331c2cd4072e2287adcd Mon Sep 17 00:00:00 2001 From: Be Date: Fri, 27 Mar 2020 10:19:12 -0500 Subject: [PATCH 070/393] DlgPrefColors: remove unused slotTrackPaletteChanged --- src/preferences/dialog/dlgprefcolors.cpp | 10 ---------- src/preferences/dialog/dlgprefcolors.h | 1 - 2 files changed, 11 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 4c1d1e169457..57fb727f813f 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -44,11 +44,6 @@ DlgPrefColors::DlgPrefColors( this, &DlgPrefColors::slotCloseClicked); - connect(comboBoxTrackColors, - QOverload::of(&QComboBox::currentIndexChanged), - this, - &DlgPrefColors::slotTrackPaletteChanged); - connect(comboBoxHotcueColors, QOverload::of(&QComboBox::currentIndexChanged), this, @@ -103,7 +98,6 @@ void DlgPrefColors::loadSettings() { m_colorPaletteSettings.getTrackColorPalette(); comboBoxTrackColors->setCurrentText( trackPalette.getName()); - slotTrackPaletteChanged(trackPalette.getName()); } // Set the default values for all the widgets @@ -193,10 +187,6 @@ QIcon DlgPrefColors::drawPaletteIcon(const QString& paletteName) { return QIcon(); } -void DlgPrefColors::slotTrackPaletteChanged(const QString& paletteName) { - Q_UNUSED(paletteName); -} - void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { ColorPalette palette = m_colorPaletteSettings.getHotcueColorPalette(paletteName); diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index 09a42b26fc25..e03b1bd7d8a3 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -25,7 +25,6 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { void apply(const QString&); private slots: - void slotTrackPaletteChanged(const QString& palette); void slotHotcuePaletteChanged(const QString& palette); private: From 417601360ff47431e21e434ac62ba913379042cc Mon Sep 17 00:00:00 2001 From: Be Date: Fri, 27 Mar 2020 10:53:23 -0500 Subject: [PATCH 071/393] DlgPrefColors: fix black edge of palette preview pixmaps --- src/preferences/dialog/dlgprefcolors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 57fb727f813f..8bfe5ac11536 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -157,8 +157,8 @@ QPixmap DlgPrefColors::drawPalettePreview(const QString& paletteName) { if (paletteName == palette.getName()) { QPixmap pixmap(kPalettePreviewSize); int count = math_max(palette.size(), 1); - int widthPerColor = pixmap.width() / count; - pixmap.fill(Qt::black); + // Rounding up is required so the entire width of the pixmap is filled up to the edge. + int widthPerColor = ceil(pixmap.width() / static_cast(count)); QPainter painter(&pixmap); for (int i = 0; i < palette.size(); ++i) { painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); From fa3cac9aa0fce7e030536b002a70654f282b8268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 27 Mar 2020 22:19:19 +0100 Subject: [PATCH 072/393] Use small edit button with elipsis --- src/preferences/dialog/dlgprefcolorsdlg.ui | 73 ++++++++++++++-------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolorsdlg.ui b/src/preferences/dialog/dlgprefcolorsdlg.ui index 51d49776a423..2a27b65008ea 100644 --- a/src/preferences/dialog/dlgprefcolorsdlg.ui +++ b/src/preferences/dialog/dlgprefcolorsdlg.ui @@ -33,9 +33,6 @@ - - - @@ -43,16 +40,6 @@ - - - - - 0 - 0 - - - - @@ -60,27 +47,17 @@ - - - - - 0 - 0 - - - - - + Custom palettes - + - Edit + Edit… @@ -89,9 +66,45 @@ 0 + 15 + + + + + + + + Qt::Horizontal + + + + 40 20 + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + @@ -100,7 +113,13 @@ - Palette Editor + Custom Palettes Editor + + + false + + + false From 089fc08aad13ace01f40e918f7fe7d38f04fb853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 27 Mar 2020 22:21:00 +0100 Subject: [PATCH 073/393] Remove superfluid parenthes --- src/preferences/colorpaletteeditor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index 308154a1990d..d70c36f747ab 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -45,5 +45,5 @@ class ColorPaletteEditor : public QWidget { QPushButton* m_pSaveButton; QPushButton* m_pCloseButton; QPushButton* m_pRemoveButton; - parented_ptr(m_pResetButton); + parented_ptr m_pResetButton; }; From 61ecb244bcef99c8390137b1785856a1d1911c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 27 Mar 2020 22:59:19 +0100 Subject: [PATCH 074/393] Added message box when closing Palette Editor wit unsaved changes --- src/preferences/colorpaletteeditor.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 2cc0b709e39e..f8eff4f3538a 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -214,7 +215,21 @@ void ColorPaletteEditor::slotPaletteNameChanged(const QString& text) { } void ColorPaletteEditor::slotCloseButtonClicked() { - emit closeButtonClicked(); + if (m_pSaveButton->isEnabled()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Custom Palettes Editor")); + msgBox.setText(tr( + "The custom palette is not saved.\n" + "Close anyway?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + int ret = msgBox.exec(); + if (ret == QMessageBox::Ok) { + emit closeButtonClicked(); + } + } else { + emit closeButtonClicked(); + } } void ColorPaletteEditor::slotRemoveButtonClicked() { From 37a57b7d81621aef1a33e429cdbf3b89c82e2340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 27 Mar 2020 23:18:38 +0100 Subject: [PATCH 075/393] Keep temoraray selections when updateding custom paletts --- src/preferences/dialog/dlgprefcolors.cpp | 16 ++++++++++++++-- src/preferences/dialog/dlgprefcolors.h | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 8bfe5ac11536..5eac89521ce4 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -34,11 +34,11 @@ DlgPrefColors::DlgPrefColors( connect(colorPaletteEditor, &ColorPaletteEditor::paletteChanged, this, - &DlgPrefColors::loadSettings); + &DlgPrefColors::palettesUpdated); connect(colorPaletteEditor, &ColorPaletteEditor::paletteRemoved, this, - &DlgPrefColors::loadSettings); + &DlgPrefColors::palettesUpdated); connect(colorPaletteEditor, &ColorPaletteEditor::closeButtonClicked, this, @@ -237,3 +237,15 @@ void DlgPrefColors::slotCloseClicked() { pushButtonEdit->show(); labelCustomPalette->show(); } + +void DlgPrefColors::palettesUpdated() { + QString hotcueColors = comboBoxHotcueColors->currentText(); + QString trackColors = comboBoxTrackColors->currentText(); + int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); + + loadSettings(); + + comboBoxHotcueColors->setCurrentText(hotcueColors); + comboBoxTrackColors->setCurrentText(trackColors); + comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); +} diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index e03b1bd7d8a3..01fe4d660dbe 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -26,9 +26,10 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { private slots: void slotHotcuePaletteChanged(const QString& palette); + void loadSettings(); + void palettesUpdated(); private: - void loadSettings(); void loadPaletteIntoEditor(const ColorPalette& palette); QPixmap drawPalettePreview(const QString& paletteName); QIcon drawPaletteIcon(const QString& paletteName); From 3d683152bbb3eda4b7da9a0acfab43c2b6e99ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 27 Mar 2020 23:28:18 +0100 Subject: [PATCH 076/393] Keep temorary selected default color after chanigng palette --- src/preferences/dialog/dlgprefcolors.cpp | 33 +++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 5eac89521ce4..d76a525570dd 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -88,16 +88,28 @@ void DlgPrefColors::loadSettings() { paletteIcon); } + const ColorPalette trackPalette = + m_colorPaletteSettings.getTrackColorPalette(); + comboBoxTrackColors->setCurrentText( + trackPalette.getName()); + const ColorPalette hotcuePalette = m_colorPaletteSettings.getHotcueColorPalette(); comboBoxHotcueColors->setCurrentText( hotcuePalette.getName()); slotHotcuePaletteChanged(hotcuePalette.getName()); - const ColorPalette trackPalette = - m_colorPaletteSettings.getTrackColorPalette(); - comboBoxTrackColors->setCurrentText( - trackPalette.getName()); + bool autoHotcueColors = + m_pConfig->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false); + if (autoHotcueColors) { + comboBoxHotcueDefaultColor->setCurrentIndex(0); + } else { + int hotcueDefaultColorIndex = m_pConfig->getValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), kHotcueDefaultColorIndex); + if (hotcueDefaultColorIndex < 0 || hotcueDefaultColorIndex >= hotcuePalette.size()) { + hotcueDefaultColorIndex = hotcuePalette.size() - 1; // default to last color (orange) + } + comboBoxHotcueDefaultColor->setCurrentIndex(hotcueDefaultColorIndex + 1); + } } // Set the default values for all the widgets @@ -191,6 +203,7 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { ColorPalette palette = m_colorPaletteSettings.getHotcueColorPalette(paletteName); + int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); comboBoxHotcueDefaultColor->clear(); comboBoxHotcueDefaultColor->addItem(tr("By hotcue number"), -1); @@ -211,17 +224,7 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { comboBoxHotcueDefaultColor->setItemIcon(i + 1, QIcon(pixmap)); } - bool autoHotcueColors = - m_pConfig->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false); - if (autoHotcueColors) { - comboBoxHotcueDefaultColor->setCurrentIndex(0); - } else { - int hotcueDefaultColorIndex = m_pConfig->getValue(ConfigKey("[Controls]", "HotcueDefaultColorIndex"), kHotcueDefaultColorIndex); - if (hotcueDefaultColorIndex < 0 || hotcueDefaultColorIndex >= palette.size()) { - hotcueDefaultColorIndex = palette.size() - 1; // default to last color (orange) - } - comboBoxHotcueDefaultColor->setCurrentIndex(hotcueDefaultColorIndex + 1); - } + comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); } void DlgPrefColors::slotEditClicked() { From 73ee2640803e58c395bc5325dc0c68981cfd3202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 28 Mar 2020 00:16:46 +0100 Subject: [PATCH 077/393] Improve gray out state of save and reset button --- src/preferences/colorpaletteeditor.cpp | 21 +++++++++++++++++++-- src/preferences/colorpaletteeditor.h | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index f8eff4f3538a..717e158693b6 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -89,9 +89,13 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) this, &ColorPaletteEditor::slotTableViewContextMenuRequested); connect(m_pSaveAsComboBox, - &QComboBox::editTextChanged, + &QComboBox::currentTextChanged, this, &ColorPaletteEditor::slotPaletteNameChanged); + connect(m_pPaletteTemplateComboBox, + &QComboBox::currentTextChanged, + this, + &ColorPaletteEditor::slotUpdateButtons); connect(m_pResetButton, &QPushButton::clicked, this, @@ -130,6 +134,9 @@ void ColorPaletteEditor::reset() { m_pSaveAsComboBox->addItem(paletteName); m_pPaletteTemplateComboBox->addItem(paletteName); } + QString current = m_pSaveAsComboBox->currentText(); + m_pPaletteTemplateComboBox->setCurrentText(current); + m_resetedPalette = current; } else { m_pSaveAsComboBox->addItem(tr("Custom Color Palette")); slotResetButtonClicked(); @@ -145,6 +152,8 @@ void ColorPaletteEditor::slotUpdateButtons() { m_pRemoveButton->setEnabled( m_bPaletteExists && !m_bPaletteIsReadOnly); + m_pResetButton->setEnabled(bDirty || + m_resetedPalette != m_pPaletteTemplateComboBox->currentText()); } void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { @@ -211,6 +220,14 @@ void ColorPaletteEditor::slotPaletteNameChanged(const QString& text) { m_bPaletteExists = bPaletteExists; m_bPaletteIsReadOnly = bPaletteIsReadOnly; + + if (bPaletteExists && !bPaletteIsReadOnly) { + m_pPaletteTemplateComboBox->setCurrentText(text); + if (!m_pModel->isDirty()) { + m_resetedPalette = text; + } + } + slotUpdateButtons(); } @@ -257,6 +274,6 @@ void ColorPaletteEditor::slotResetButtonClicked() { paletteName, mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette); m_pModel->setColorPalette(palette); - m_pModel->setDirty(true); + m_resetedPalette = paletteName; slotUpdateButtons(); } diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index d70c36f747ab..4c52d4a26f21 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -46,4 +46,5 @@ class ColorPaletteEditor : public QWidget { QPushButton* m_pCloseButton; QPushButton* m_pRemoveButton; parented_ptr m_pResetButton; + QString m_resetedPalette; }; From e1736cf800c297dcb551c255f4a8bb2e49d1bfb2 Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sat, 28 Mar 2020 02:31:01 +0100 Subject: [PATCH 078/393] Rewrite using the outputs in XML and the components library --- res/controllers/Stanton-DJC-4-scripts.js | 545 +++----- res/controllers/Stanton-DJC-4.midi.xml | 1554 +++++++++++++++++++--- 2 files changed, 1572 insertions(+), 527 deletions(-) diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js index e37068b2e2f9..1b5ba44c3dee 100644 --- a/res/controllers/Stanton-DJC-4-scripts.js +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -4,18 +4,29 @@ * Written by Martin Bruset Solberg * Adopted for v2.2.3 by Christoph Zimmermann * - * Based on MC2000 script by Esteban Serrano Roloff - * and Denon MC7000 script by OsZ - * + * Based on: + * Denon MC2000 script by Esteban Serrano Roloff, + * Denon MC7000 script by OsZ + * Roland DJ-505 script by Jan Holthuis * * TODO: - * Effects browsing - * Beat multiplier + * Pitch range * **/ var djc4 = {}; +///////////////// +// Tweakables. // +///////////////// + +djc4.tempoRange = [0.08, 0.16, 0.5]; +djc4.autoShowFourDecks = false; + +/////////// +// Code. // +/////////// + // ---------- Global variables ---------- // MIDI Reception commands (from spec) @@ -60,113 +71,84 @@ djc4.leds = { fx: 47 }; -djc4.scratchMode = [false, false, false, false]; - // ---------- Functions ---------- // Called when the MIDI device is opened & set up. -djc4.init = function(id, debug) { - djc4.id = id; - djc4.debug = debug; +djc4.init = function() { + var i; // Put all LEDs to default state. djc4.allLed2Default(); - // ---- Connect controls ----------- + engine.makeConnection("[Channel3]", "track_loaded", djc4.autoShowDecks); + engine.makeConnection("[Channel4]", "track_loaded", djc4.autoShowDecks); - // ---- Controls for Channel 1 to 4 - var i = 0; - for (i = 1; i <= 4; i++) { - // Cue 1-4 - var j = 0; - for (j = 1; j <= 4; j++) { - engine.makeConnection("[Channel" + i + "]", "hotcue_" + j + "_enabled", - djc4.hotcueSetLed); - } - - // Cue - engine.makeConnection("[Channel" + i + "]", "cue_indicator", - djc4.cueSetLed); - // Play - engine.makeConnection("[Channel" + i + "]", "play_indicator", - djc4.playSetLed); - - // Loop in - engine.makeConnection("[Channel" + i + "]", "loop_start_position", - djc4.loopStartSetLed); - // Loop out - engine.makeConnection("[Channel" + i + "]", "loop_end_position", - djc4.loopEndSetLed); - // Loop enabled - engine.makeConnection("[Channel" + i + "]", "loop_enabled", - djc4.loopEnabledSetLed); - // Loop double - engine.makeConnection("[Channel" + i + "]", "loop_double", - djc4.loopDoubleSetLed); - // Loop halve - engine.makeConnection("[Channel" + i + "]", "loop_halve", - djc4.loopHalveSetLed); - - // Monitor cue - engine.makeConnection("[Channel" + i + "]", "pfl", djc4.pflSetLed); - - // Kills - engine.makeConnection("[Channel" + i + "]", "filterHighKill", - djc4.highkillSetLed); - engine.makeConnection("[Channel" + i + "]", "filterMidKill", - djc4.midkillSetLed); - engine.makeConnection("[Channel" + i + "]", "filterLowKill", - djc4.lowkillSetLed); - - engine.makeConnection("[QuickEffectRack1_[Channel" + i + "]_Effect1]", - "enabled", djc4.filterSetLed); - - // Keylock - engine.makeConnection("[Channel" + i + "]", "keylock", djc4.keylockSetLed); - - // Pitch bend - engine.makeConnection("[Channel" + i + "]", "rate_temp_down", - djc4.ratetempdownSetLed); - engine.makeConnection("[Channel" + i + "]", "rate_temp_up", - djc4.ratetempupSetLed); + if (engine.getValue("[Master]", "num_samplers") < 8) { + engine.setValue("[Master]", "num_samplers", 8); } - // ---- Controls for Sampler 1 - 8 - for (i = 1; i <= 8; i++) { - engine.makeConnection("[Sampler" + i + "]", "track_loaded", - djc4.samplerSetLed); - if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 1) { - djc4.samplerSetLed(1, "[Sampler" + i + "]"); - } + djc4.deck = []; + for (i = 0; i < 4; i++) { + djc4.deck[i] = new djc4.Deck(i + 1); + djc4.deck[i].setCurrentDeck("[Channel" + (i + 1) + "]"); } - // ---- Controls for EffectUnit 1 to 2 - for (i = 1; i <= 2; i++) { - // Effects 1-3 - for (j = 1; j <= 3; j++) { - engine.makeConnection("[EffectRack1_EffectUnit" + i + "_Effect" + j + "]", - "enabled", djc4.fxenabledSetLed); - } + djc4.effectUnit = []; + for (i = 0; i <= 3; i++) { + djc4.effectUnit[i] = new components.EffectUnit([i + 1]); + djc4.effectUnit[i].shiftOffset = 0x32; + djc4.effectUnit[i].shiftControl = true; + djc4.effectUnit[i].enableButtons[1].midi = [0x90 + i, 0x1F]; + djc4.effectUnit[i].enableButtons[2].midi = [0x90 + i, 0x20]; + djc4.effectUnit[i].enableButtons[3].midi = [0x90 + i, 0x21]; + djc4.effectUnit[i].effectFocusButton.midi = [0x90 + i, 0x1D]; + djc4.effectUnit[i].knobs[1].midi = [0xB0 + i, 0x09]; + djc4.effectUnit[i].knobs[2].midi = [0xB0 + i, 0x0A]; + djc4.effectUnit[i].knobs[3].midi = [0xB0 + i, 0x0B]; + djc4.effectUnit[i].dryWetKnob.midi = [0xB0 + i, 0x08]; + djc4.effectUnit[i].dryWetKnob.input = function(channel, control, value) { + if (value === 0x41) { + // 0.05 is an example. Adjust that value to whatever works well for your controller. + this.inSetParameter(this.inGetParameter() + 0.05); + } else if (value === 0x3F) { + this.inSetParameter(this.inGetParameter() - 0.05); + } + }; + djc4.effectUnit[i].init(); } - // Effect enabled for Channel - engine.makeConnection("[EffectRack1_EffectUnit1]", "group_[Channel1]_enable", - djc4.fxon1SetLed); - engine.makeConnection("[EffectRack1_EffectUnit1]", "group_[Channel3]_enable", - djc4.fxon3SetLed); - engine.makeConnection("[EffectRack1_EffectUnit2]", "group_[Channel2]_enable", - djc4.fxon2SetLed); - engine.makeConnection("[EffectRack1_EffectUnit2]", "group_[Channel4]_enable", - djc4.fxon4SetLed); - - // ---- VU meter (Master is shown) - engine.makeConnection("[Master]", "VuMeterL", djc4.VuMeterLSetLed); - engine.makeConnection("[Master]", "VuMeterR", djc4.VuMeterRSetLed); - - // Enable load LEDs because Channels are empty at start - djc4.setLed(1, djc4.leds["loadac"], 1); - djc4.setLed(3, djc4.leds["loadac"], 1); - djc4.setLed(2, djc4.leds["loadbd"], 1); - djc4.setLed(4, djc4.leds["loadbd"], 1); + + // === VU Meter === + djc4.vuMeter = new components.Component({ + midi: [0xB0, 0x03], + group: "[Master]", + outKey: "VuMeterL", + output: function(value, group) { + // The red LEDs light up with MIDI values greater than 0x60. + // The Red LEDs should only be illuminated if the track is clipping. + if (engine.getValue(group, "PeakIndicator") === 1) { + value = 0x60; + } else { + value = Math.round(value * 0x54); + } + this.send(value); + }, + }); + + djc4.vuMeter = new components.Component({ + midi: [0xB0, 0x04], + group: "[Master]", + outKey: "VuMeterR", + output: function(value, group) { + // The red LEDs light up with MIDI values greater than 0x60. + // The Red LEDs should only be illuminated if the track is clipping. + if (engine.getValue(group, "PeakIndicator") === 1) { + value = 0x60; + } else { + value = Math.round(value * 0x54); + } + this.send(value); + }, + }); }; // Called when the MIDI device is closed @@ -175,6 +157,101 @@ djc4.shutdown = function() { djc4.allLed2Default(); }; +djc4.Deck = function(deckNumbers) { + components.Deck.call(this, deckNumbers); + + // === Instantiate controls === + this.beatLoopEncoder = new components.Encoder({ + midi: [0xB0+deckNumbers-1, 0x01], + group: "[Channel" + deckNumbers + "]", + inKey: "beatloop_size", + input: function(channel, control, value) { + if (value === 0x3F) { + if (this.inGetParameter() <= 1) { + this.inSetParameter(this.inGetParameter() / 2); + } else { + this.inSetParameter(this.inGetParameter() - 1); + } + } else if (value === 0x41) { + if (this.inGetParameter() <= 1) { + this.inSetParameter(this.inGetParameter() * 2); + } else { + this.inSetParameter(this.inGetParameter() + 1); + } + } + }, + }); + + this.samplerButtons = []; + for (var i = 0; i <= 3; i++) { + this.samplerButtons[i] = new components.SamplerButton({ + number: (deckNumbers === 1 || deckNumbers === 3) ? (i + 1) : (i + 5), + midi: [0x90+deckNumbers-1, 0x0C+i], + }); + } + + // === Scratch control === + this.scratchMode = false; + + this.toggleScratchMode = function(value) { + if (value === 0x7F) { + // Toggle setting + this.scratchMode = !this.scratchMode; + djc4.setLed(script.deckFromGroup(this.currentDeck), djc4.leds["scratch"], this.scratchMode); + } + }; + + // ============================= JOG WHEELS ================================= + this.wheelTouch = function(channel, control, value) { + if (control === 0x58) { // If shift is pressed, do a fast search + if (value === 0x7F) { + var alpha = 1.0 / 8; + var beta = alpha / 32; + var rpm = 40.0; + + engine.scratchEnable(script.deckFromGroup(this.currentDeck), 128, rpm, alpha, beta, true); + } else { // If button up + engine.scratchDisable(script.deckFromGroup(this.currentDeck)); + } + } else if (this.scratchMode === true) { // If scratch enabled + if (value === 0x7F) { + alpha = 1.0/8; + beta = alpha/32; + rpm = 150.0; + + engine.scratchEnable(script.deckFromGroup(this.currentDeck), 128, rpm, alpha, beta); + } else { // If button up + engine.scratchDisable(script.deckFromGroup(this.currentDeck)); + } + } else if (value === 0x00) { + // In case shift is let go before the platter, + // ensure scratch is disabled + engine.scratchDisable(script.deckFromGroup(this.currentDeck)); + } + }; + + this.wheelTurn = function(control, value) { + // When the jog wheel is turned in clockwise direction, value is + // greater than 64 (= 0x40). If it's turned in counter-clockwise + // direction, the value is smaller than 64. + var newValue = value - 64; + var deck = script.deckFromGroup(this.currentDeck); + if (engine.isScratching(deck)) { + engine.scratchTick(deck, newValue); // Scratch! + } else if (control === 0x20) { // If shift is pressed + var oldPos = engine.getValue(this.currentDeck, "playposition"); + // Since ‘playposition’ is normalized to unity, we need to scale by + // song duration in order for the jog wheel to cover the same amount + // of time given a constant turning angle. + var duration = engine.getValue(this.currentDeck, "duration"); + var newPos = Math.max(0, oldPos + (newValue * djc4.stripSearchScaling / duration)); + engine.setValue(this.currentDeck, "playposition", newPos); // Strip search + } else { + engine.setValue(this.currentDeck, "jog", newValue); // Pitch bend + } + }; +}; + // === FOR MANAGING LEDS === djc4.allLed2Default = function() { @@ -216,113 +293,33 @@ djc4.setLed = function(deck, led, status) { // === MISC COMMON === -djc4.group2Deck = function(group) { - var matches = group.match(/\[Channel(\d+)\]/); - if (matches === null) { - return -1; - } else { - return matches[1]; - } -}; - -djc4.group2Sampler = function(group) { - var matches = group.match(/^\[Sampler(\d+)\]$/); - if (matches === null) { - return -1; - } else { - return matches[1]; - } -}; - -// === Scratch control === - -djc4.toggleScratchMode = function(channel, control, value, status, group) { - if (!value) +djc4.autoShowDecks = function() { + var anyLoaded = engine.getValue("[Channel3]", "track_loaded") || engine.getValue("[Channel4]", "track_loaded"); + if (!djc4.autoShowFourDecks) { return; - - var deck = djc4.group2Deck(group); - // Toggle setting - djc4.scratchMode[deck - 1] = !djc4.scratchMode[deck - 1]; - djc4.scratchSetLed(djc4.scratchMode[deck - 1], group); -}; - -// === JOG WHEEL === - -// Touch platter -djc4.wheelTouch = function(channel, control, value) { - var deck = channel + 1; - - if (control === 0x58) { // If shift is pressed, do a fast search - if (value === 0x7F) { // If touch - var alpha = 1.0 / 8; - var beta = alpha / 32; - - var rpm = 40.0; - - engine.scratchEnable(deck, 128, rpm, alpha, beta, true); - } else { // If button up - engine.scratchDisable(deck); - } - } else if (djc4.scratchMode[channel] === true) { // If scratch enabled - if (value === 0x7F) { // If touch - alpha = 1.0 / 8; - beta = alpha / 32; - - rpm = 150.0; - - engine.scratchEnable(deck, 128, rpm, alpha, beta, true); - } else { // If button up - engine.scratchDisable(deck); - } - } else if (value === 0x00) { - // In case shift is let go before the platter, - // ensure scratch is disabled - engine.scratchDisable(deck); } + engine.setValue("[Master]", "show_4decks", anyLoaded); }; -// Wheel -djc4.wheelTurn = function(channel, control, value, status, group) { - // var deck = channel + 1; - var deck = script.deckFromGroup(group); - - // B: For a control that centers on 0x40 (64): - var newValue = (value - 64); - - // See if we're scratching. If not, skip this. - if (!engine.isScratching(deck)) { - engine.setValue(group, "jog", newValue / 4); - return; - } - - // In either case, register the movement - engine.scratchTick(deck, newValue); +djc4.shiftButton = function(value) { + djc4.deck.concat(djc4.effectUnit).forEach( + value ? function(module) { module.shift(); } : function(module) { module.unshift(); } + ); }; // === Browser === - -djc4.browseMove = function(channel, control, value, status, group) { - // Next/previous track - if (value === 0x41) { - engine.setValue(group, "MoveUp", true); - } else if (value === 0x3F) { - engine.setValue(group, "MoveDown", true); - } else - return; -}; - -djc4.browseScroll = function(channel, control, value, status, group) { - // Next/previous page - if (value === 0x41) { - engine.setValue(group, "ScrollUp", true); - } else if (value === 0x3F) { - engine.setValue(group, "ScrollDown", true); - } else - return; -}; +djc4.browseEncoder = new components.Encoder({ + input: function(channel, control, value) { + var isShifted = (control) === 0x2C; + if (value === 0x41) { + engine.setValue("[Library]", isShifted ? "ScrollDown" : "MoveDown", true); + } else if (value === 0x3F) { + engine.setValue("[Library]", isShifted ? "ScrollUp" : "MoveUp", true); + } + } +}); // === Sampler Volume Control === - djc4.samplerVolume = function(channel, control, value) { // check if the Sampler Volume is at Zero and if so hide the sampler bank if (value > 0x00) { @@ -340,156 +337,6 @@ djc4.samplerVolume = function(channel, control, value) { } }; -// === SET LED FUNCTIONS === - -// Hot cues - -djc4.hotcueSetLed = function(value, group, control) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["hotcue" + control[7]], value); -}; - -// PFL -djc4.pflSetLed = function( - value, - group) { djc4.setLed(djc4.group2Deck(group), djc4.leds["pfl"], value); }; - -// Play/Cue - -djc4.playSetLed = function(value, group) { - // var deck = channel + 1; - var deck = djc4.group2Deck(group); - - djc4.setLed(djc4.group2Deck(group), djc4.leds["play"], value); - - // if a deck is playing it is not possible to load a track - // -> disable corresponding load LED - if (deck === 1 || deck === 3) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["loadac"], !value); - } else { - djc4.setLed(djc4.group2Deck(group), djc4.leds["loadbd"], !value); - } -}; - -djc4.cueSetLed = function( - value, - group) { djc4.setLed(djc4.group2Deck(group), djc4.leds["cue"], value); }; - -// Keylock - -djc4.keylockSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["keylock"], value); -}; - -// Loops - -djc4.loopStartSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["loopin"], value !== -1); -}; - -djc4.loopEndSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["loopout"], value !== -1); -}; - -djc4.loopEnabledSetLed = function( - value, - group) { djc4.setLed(djc4.group2Deck(group), djc4.leds["loopon"], value); }; - -djc4.loopDoubleSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["loopplus"], value); -}; - -djc4.loopHalveSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["loopminus"], value); -}; - -// Kills - -djc4.highkillSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["highkill"], value); -}; - -djc4.midkillSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["midkill"], value); -}; - -djc4.lowkillSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["lowkill"], value); -}; - -djc4.filterSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["filteron"], !value); -}; - -// Scratch button - -djc4.scratchSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["scratch"], value); -}; - -// Pitch bend buttons -djc4.ratetempdownSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["pbendminus"], value); -}; - -djc4.ratetempupSetLed = function(value, group) { - djc4.setLed(djc4.group2Deck(group), djc4.leds["pbendplus"], value); -}; - -djc4.fxenabledSetLed = function(value, group) { - var matches = group.match(/^\[EffectRack1_EffectUnit(\d+)_Effect(\d+)\]$/); - if (matches !== null) { - var led = djc4.leds["fxexf1"] - 1 + parseInt(matches[2], 10); - - // FX1 is on deck A/C - if (parseInt(matches[1], 10) === 1) { - djc4.setLed(1, led, value); - djc4.setLed(3, led, value); - } else { - djc4.setLed(2, led, value); - djc4.setLed(4, led, value); - } - } -}; - -djc4.fxon1SetLed = function( - value) { djc4.setLed(1, djc4.leds["fxon"], value); }; - -djc4.fxon2SetLed = function( - value) { djc4.setLed(2, djc4.leds["fxon"], value); }; - -djc4.fxon3SetLed = function( - value) { djc4.setLed(3, djc4.leds["fxon"], value); }; - -djc4.fxon4SetLed = function( - value) { djc4.setLed(4, djc4.leds["fxon"], value); }; - -// Sampler - -djc4.samplerSetLed = function(value, group) { - var sampler = djc4.group2Sampler(group); - - if (sampler <= 4) { - // Sampler 1 - 4 are on deck A/C - var led = djc4.leds["sample1"] - 1 + parseInt(sampler, 10); - djc4.setLed(1, led, value); - djc4.setLed(3, led, value); - } else { - // Sampler 5 - 8 are on deck B/D - led = djc4.leds["sample1"] - 1 - 4 + parseInt(sampler, 10); - djc4.setLed(2, led, value); - djc4.setLed(4, led, value); - } -}; - -// === VU Meter === - -djc4.VuMeterLSetLed = function(value) { - var ledStatus = (value * 119); - midi.sendShortMsg(0xB0, 3, ledStatus); -}; - -djc4.VuMeterRSetLed = function(value) { - var ledStatus = (value * 119); - midi.sendShortMsg(0xB0, 4, ledStatus); -}; +// give your custom Deck all the methods of the generic Deck in the Components library +djc4.Deck.prototype = Object.create(components.Deck.prototype); diff --git a/res/controllers/Stanton-DJC-4.midi.xml b/res/controllers/Stanton-DJC-4.midi.xml index 4ee83b3d2d18..e97d10919278 100644 --- a/res/controllers/Stanton-DJC-4.midi.xml +++ b/res/controllers/Stanton-DJC-4.midi.xml @@ -3,17 +3,91 @@ Stanton DJC.4 Martin Bruset Solberg, Christoph Zimmermann - The Stanton DJC.4 is a four-deck control surface with large, touch-sensitive jog wheels and built-in audio interface (2 inputs, 2 outputs). Configured as four-deck, two-fx and master VU meter controller - https://mixxx.org/wiki/doku.php/stanton_djc4 + The Stanton DJC.4 is a four-deck control surface with large, touch-sensitive jog wheels and built-in audio interface (2 inputs, 2 outputs). Configured as four-deck, four-fx and master VU meter controller + https://mixxx.org/wiki/doku.php/stanton_djc.4 + + [Channel1] - djc4.wheelTurn + djc4.deck[0].beatLoopEncoder.input + 0xB0 + 0x01 + + + + + + [Channel1] + beatloop_activate + 0x90 + 0x01 + + + + + + [Channel2] + djc4.deck[1].beatLoopEncoder.input + 0xB1 + 0x01 + + + + + + [Channel2] + beatloop_activate + 0x91 + 0x01 + + + + + + [Channel3] + djc4.deck[2].beatLoopEncoder.input + 0xB2 + 0x01 + + + + + + [Channel3] + beatloop_activate + 0x92 + 0x01 + + + + + + [Channel4] + djc4.deck[3].beatLoopEncoder.input + 0xB3 + 0x01 + + + + + + [Channel4] + beatloop_activate + 0x93 + 0x01 + + + + + + [Channel1] + djc4.deck[0].wheelTurn 0xB0 0x02 @@ -31,7 +105,7 @@ [Channel2] - djc4.wheelTurn + djc4.deck[1].wheelTurn 0xB1 0x02 @@ -49,7 +123,7 @@ [Channel3] - djc4.wheelTurn + djc4.deck[2].wheelTurn 0xB2 0x02 @@ -67,7 +141,7 @@ [Channel4] - djc4.wheelTurn + djc4.deck[3].wheelTurn 0xB3 0x02 @@ -407,6 +481,42 @@ + + [EffectRack1_EffectUnit1] + djc4.effectUnit[0].dryWetKnob.input + 0xB0 + 0x08 + + + + + + [EffectRack1_EffectUnit3] + djc4.effectUnit[1].dryWetKnob.input + 0xB1 + 0x08 + + + + + + [EffectRack1_EffectUnit3] + djc4.effectUnit[2].dryWetKnob.input + 0xB2 + 0x08 + + + + + + [EffectRack1_EffectUnit3] + djc4.effectUnit[3].dryWetKnob.input + 0xB3 + 0x08 + + + + [Channel1] hotcue_1_activate @@ -481,38 +591,38 @@ [EffectRack1_EffectUnit1_Effect1] - meta + djc4.effectUnit[0].knobs[1].input 0xB0 0x09 - + [EffectRack1_EffectUnit2_Effect1] - meta + djc4.effectUnit[1].knobs[1].input 0xB1 0x09 - + [EffectRack1_EffectUnit1_Effect1] - meta + djc4.effectUnit[2].knobs[1].input 0xB2 0x09 - + [EffectRack1_EffectUnit2_Effect1] - meta + djc4.effectUnit[3].knobs[1].input 0xB3 0x09 - + @@ -553,38 +663,38 @@ [EffectRack1_EffectUnit1_Effect2] - meta + djc4.effectUnit[0].knobs[2].input 0xB0 0x0A - + [EffectRack1_EffectUnit2_Effect2] - meta + djc4.effectUnit[1].knobs[2].input 0xB1 0x0A - + [EffectRack1_EffectUnit1_Effect2] - meta + djc4.effectUnit[2].knobs[2].input 0xB2 0x0A - + [EffectRack1_EffectUnit2_Effect2] - meta + djc4.effectUnit[3].knobs[2].input 0xB3 0x0A - + @@ -625,110 +735,110 @@ [EffectRack1_EffectUnit1_Effect3] - meta + djc4.effectUnit[0].knobs[3].input 0xB0 0x0B - + [EffectRack1_EffectUnit2_Effect3] - meta + djc4.effectUnit[1].knobs[3].input 0xB1 0x0B - + [EffectRack1_EffectUnit1_Effect3] - meta + djc4.effectUnit[2].knobs[3].input 0xB2 0x0B - + [EffectRack1_EffectUnit2_Effect3] - meta + djc4.effectUnit[3].knobs[3].input 0xB3 0x0B - + [Sampler1] - cue_gotoandplay + djc4.deck[0].samplerButtons[0].input 0x90 0x0C - + [Sampler5] - cue_gotoandplay + djc4.deck[1].samplerButtons[0].input 0x91 0x0C - + [Sampler1] - cue_gotoandplay + djc4.deck[2].samplerButtons[0].input 0x92 0x0C - + [Sampler5] - cue_gotoandplay + djc4.deck[3].samplerButtons[0].input 0x93 0x0C - + [Sampler2] - cue_gotoandplay + djc4.deck[0].samplerButtons[1].input 0x90 0x0D - + [Sampler6] - cue_gotoandplay + djc4.deck[1].samplerButtons[1].input 0x91 0x0D - + [Sampler2] - cue_gotoandplay + djc4.deck[2].samplerButtons[1].input 0x92 0x0D - + [Sampler6] - cue_gotoandplay + djc4.deck[3].samplerButtons[1].input 0x93 0x0D - + @@ -742,7 +852,7 @@ [Library] - djc4.browseMove + djc4.browseEncoder.input 0xB0 0x0E @@ -751,74 +861,74 @@ [Sampler3] - cue_gotoandplay + djc4.deck[0].samplerButtons[2].input 0x90 0x0E - + [Sampler7] - cue_gotoandplay + djc4.deck[1].samplerButtons[2].input 0x91 0x0E - + [Sampler3] - cue_gotoandplay + djc4.deck[2].samplerButtons[2].input 0x92 0x0E - + [Sampler7] - cue_gotoandplay + djc4.deck[3].samplerButtons[2].input 0x93 0x0E - + [Sampler4] - cue_gotoandplay + djc4.deck[0].samplerButtons[3].input 0x90 0x0F - + [Sampler8] - cue_gotoandplay + djc4.deck[1].samplerButtons[3].input 0x91 0x0F - + [Sampler4] - cue_gotoandplay + djc4.deck[2].samplerButtons[3].input 0x92 0x0F - + [Sampler8] - cue_gotoandplay + djc4.deck[3].samplerButtons[3].input 0x93 0x0F - + @@ -994,7 +1104,7 @@ [Channel1] - djc4.toggleScratchMode + djc4.deck[0].toggleScratchMode 0x90 0x15 @@ -1003,7 +1113,7 @@ [Channel2] - djc4.toggleScratchMode + djc4.deck[1].toggleScratchMode 0x91 0x15 @@ -1012,7 +1122,7 @@ [Channel3] - djc4.toggleScratchMode + djc4.deck[2].toggleScratchMode 0x92 0x15 @@ -1021,7 +1131,7 @@ [Channel4] - djc4.toggleScratchMode + djc4.deck[3].toggleScratchMode 0x93 0x15 @@ -1280,6 +1390,42 @@ + + [EffectRack1_EffectUnit1] + djc4.effectUnit[0].effectFocusButton.input + 0x90 + 0x1D + + + + + + [EffectRack1_EffectUnit1] + djc4.effectUnit[1].effectFocusButton.input + 0x91 + 0x1D + + + + + + [EffectRack1_EffectUnit1] + djc4.effectUnit[2].effectFocusButton.input + 0x92 + 0x1D + + + + + + [EffectRack1_EffectUnit1] + djc4.effectUnit[3].effectFocusButton.input + 0x93 + 0x1D + + + + [EffectRack1_EffectUnit1] group_[Channel1]_enable @@ -1299,7 +1445,7 @@ - [EffectRack1_EffectUnit1] + [EffectRack1_EffectUnit3] group_[Channel3]_enable 0x92 0x1E @@ -1308,7 +1454,7 @@ - [EffectRack1_EffectUnit2] + [EffectRack1_EffectUnit4] group_[Channel4]_enable 0x93 0x1E @@ -1318,79 +1464,79 @@ [EffectRack1_EffectUnit1_Effect1] - enabled + djc4.effectUnit[0].enableButtons[1].input 0x90 0x1F - + [EffectRack1_EffectUnit2_Effect1] - enabled + djc4.effectUnit[1].enableButtons[1].input 0x91 0x1F - + - [EffectRack1_EffectUnit1_Effect1] - enabled + [EffectRack1_EffectUnit3_Effect1] + djc4.effectUnit[2].enableButtons[1].input 0x92 0x1F - + - [EffectRack1_EffectUnit2_Effect1] - enabled + [EffectRack1_EffectUnit4_Effect1] + djc4.effectUnit[3].enableButtons[1].input 0x93 0x1F - + [EffectRack1_EffectUnit1_Effect2] - enabled + djc4.effectUnit[0].enableButtons[2].input 0x90 0x20 - + [EffectRack1_EffectUnit2_Effect2] - enabled + djc4.effectUnit[1].enableButtons[2].input 0x91 0x20 - + - [EffectRack1_EffectUnit1_Effect2] - enabled + [EffectRack1_EffectUnit3_Effect2] + djc4.effectUnit[2].enableButtons[2].input 0x92 0x20 - + - [EffectRack1_EffectUnit2_Effect2] - enabled + [EffectRack1_EffectUnit4_Effect2] + djc4.effectUnit[3].enableButtons[2].input 0x93 0x20 - + [Channel1] - djc4.wheelTurn + djc4.deck[0].wheelTurn 0xB0 0x20 @@ -1399,7 +1545,7 @@ [Channel2] - djc4.wheelTurn + djc4.deck[1].wheelTurn 0xB1 0x20 @@ -1408,7 +1554,7 @@ [Channel3] - djc4.wheelTurn + djc4.deck[2].wheelTurn 0xB2 0x20 @@ -1417,7 +1563,7 @@ [Channel4] - djc4.wheelTurn + djc4.deck[3].wheelTurn 0xB3 0x20 @@ -1426,38 +1572,38 @@ [EffectRack1_EffectUnit1_Effect3] - enabled + djc4.effectUnit[0].enableButtons[3].input 0x90 0x21 - + [EffectRack1_EffectUnit2_Effect3] - enabled + djc4.effectUnit[1].enableButtons[3].input 0x91 0x21 - + [EffectRack1_EffectUnit1_Effect3] - enabled + djc4.effectUnit[2].enableButtons[3].input 0x92 0x21 - + [EffectRack1_EffectUnit2_Effect3] - enabled + djc4.effectUnit[3].enableButtons[3].input 0x93 0x21 - + @@ -1475,7 +1621,7 @@ 0x92 0x22 - + @@ -1534,43 +1680,43 @@ [EffectRack1_EffectUnit1] - mix + djc4.effectUnit[0].dryWetKnob.input 0xB0 - 0x25 + 0x26 - + [EffectRack1_EffectUnit2] - mix + djc4.effectUnit[1].dryWetKnob.input 0xB1 - 0x25 + 0x26 - + - [EffectRack1_EffectUnit1] - mix + [EffectRack1_EffectUnit2] + djc4.effectUnit[2].dryWetKnob.input 0xB2 - 0x25 + 0x26 - + - [EffectRack1_EffectUnit2] - mix + [EffectRack1_EffectUnit3] + djc4.effectUnit[3].dryWetKnob.input 0xB3 - 0x25 + 0x26 - + [Channel1] - djc4.wheelTouch + djc4.deck[0].wheelTouch 0x90 0x26 @@ -1579,7 +1725,7 @@ [Channel2] - djc4.wheelTouch + djc4.deck[1].wheelTouch 0x91 0x26 @@ -1588,7 +1734,7 @@ [Channel3] - djc4.wheelTouch + djc4.deck[2].wheelTouch 0x92 0x26 @@ -1597,7 +1743,7 @@ [Channel4] - djc4.wheelTouch + djc4.deck[3].wheelTouch 0x93 0x26 @@ -1606,7 +1752,7 @@ [Library] - MoveFocusForward + MoveFocus 0x90 0x27 @@ -1615,79 +1761,124 @@ [Library] - djc4.browseScroll + djc4.browseEncoder.input 0xB0 0x2C + + [Master] + djc4.shiftButton + 0x90 + 0x2D + + + + [Channel1] - loop_in_goto + beatlooproll_activate 0x90 - 0x36 + 0x33 [Channel2] - loop_in_goto + beatlooproll_activate 0x91 - 0x36 + 0x33 [Channel3] - loop_in_goto + beatlooproll_activate 0x92 - 0x36 + 0x33 [Channel4] - loop_in_goto + beatlooproll_activate 0x93 - 0x36 + 0x33 [Channel1] - loop_out_goto + loop_in_goto 0x90 - 0x37 + 0x36 [Channel2] - loop_out_goto + loop_in_goto 0x91 - 0x37 + 0x36 [Channel3] - loop_out_goto + loop_in_goto 0x92 - 0x37 + 0x36 [Channel4] - loop_out_goto + loop_in_goto + 0x93 + 0x36 + + + + + + [Channel1] + loop_out_goto + 0x90 + 0x37 + + + + + + [Channel2] + loop_out_goto + 0x91 + 0x37 + + + + + + [Channel3] + loop_out_goto + 0x92 + 0x37 + + + + + + [Channel4] + loop_out_goto 0x93 0x37 @@ -1876,146 +2067,146 @@ [Sampler1] - cue_default + djc4.deck[0].samplerButtons[0].input 0x90 0x3E - + [Sampler5] - cue_default + djc4.deck[1].samplerButtons[0].input 0x91 0x3E - + [Sampler1] - cue_default + djc4.deck[3].samplerButtons[0].input 0x92 0x3E - + [Sampler5] - cue_default + djc4.deck[3].samplerButtons[0].input 0x93 0x3E - + [Sampler2] - cue_default + djc4.deck[0].samplerButtons[1].input 0x90 0x3F - + [Sampler6] - cue_default + djc4.deck[1].samplerButtons[1].input 0x91 0x3F - + [Sampler2] - cue_default + djc4.deck[2].samplerButtons[1].input 0x92 0x3F - + [Sampler6] - cue_default + djc4.deck[3].samplerButtons[1].input 0x93 0x3F - + [Sampler3] - cue_default + djc4.deck[0].samplerButtons[2].input 0x90 0x40 - + [Sampler7] - cue_default + djc4.deck[1].samplerButtons[2].input 0x91 0x40 - + [Sampler3] - cue_default + djc4.deck[2].samplerButtons[2].input 0x92 0x40 - + [Sampler7] - cue_default + djc4.deck[3].samplerButtons[2].input 0x93 0x40 - + [Sampler4] - cue_default + djc4.deck[0].samplerButtons[3].input 0x90 0x41 - + [Sampler8] - cue_default + djc4.deck[1].samplerButtons[3].input 0x91 0x41 - + [Sampler4] - cue_default + djc4.deck[2].samplerButtons[3].input 0x92 0x41 - + [Sampler8] - cue_default + djc4.deck[3].samplerButtons[3].input 0x93 0x41 - + @@ -2199,26 +2390,188 @@ - [Playlist] - djc4.browsePrevPlaylist + [EffectRack1_EffectUnit1] + djc4.effectUnit[0].effectFocusButton.input 0x90 - 0x54 + 0x4F - [Playlist] - djc4.browseNextPlaylist + [EffectRack1_EffectUnit1] + djc4.effectUnit[1].effectFocusButton.input 0x91 - 0x55 + 0x4F - [Library] - MoveFocusBackward + [EffectRack1_EffectUnit1] + djc4.effectUnit[2].effectFocusButton.input + 0x92 + 0x4F + + + + + + [EffectRack1_EffectUnit1] + djc4.effectUnit[3].effectFocusButton.input + 0x93 + 0x4F + + + + + + [EffectRack1_EffectUnit1_Effect1] + djc4.effectUnit[0].enableButtons[1].input + 0x90 + 0x51 + + + + + + [EffectRack1_EffectUnit2_Effect1] + djc4.effectUnit[1].enableButtons[1].input + 0x91 + 0x51 + + + + + + [EffectRack1_EffectUnit1_Effect1] + djc4.effectUnit[2].enableButtons[1].input + 0x92 + 0x51 + + + + + + [EffectRack1_EffectUnit2_Effect1] + djc4.effectUnit[3].enableButtons[1].input + 0x93 + 0x51 + + + + + + [EffectRack1_EffectUnit1_Effect2] + djc4.effectUnit[0].enableButtons[2].input + 0x90 + 0x52 + + + + + + [EffectRack1_EffectUnit2_Effect2] + djc4.effectUnit[1].enableButtons[2].input + 0x91 + 0x52 + + + + + + [EffectRack1_EffectUnit1_Effect2] + djc4.effectUnit[2].enableButtons[2].input + 0x92 + 0x52 + + + + + + [EffectRack1_EffectUnit2_Effect2] + djc4.effectUnit[3].enableButtons[2].input + 0x93 + 0x52 + + + + + + [EffectRack1_EffectUnit1_Effect3] + djc4.effectUnit[0].enableButtons[3].input + 0x90 + 0x53 + + + + + + [EffectRack1_EffectUnit2_Effect3] + djc4.effectUnit[1].enableButtons[3].input + 0x91 + 0x53 + + + + + + [EffectRack1_EffectUnit1_Effect3] + djc4.effectUnit[2].enableButtons[3].input + 0x92 + 0x53 + + + + + + [EffectRack1_EffectUnit2_Effect3] + djc4.effectUnit[3].enableButtons[3].input + 0x93 + 0x53 + + + + + + [Channel1] + djc4.deck[0].wheelTouch + 0x90 + 0x58 + + + + + + [Channel2] + djc4.deck[1].wheelTouch + 0x91 + 0x58 + + + + + + [Channel3] + djc4.deck[2].wheelTouch + 0x92 + 0x58 + + + + + + [Channel4] + djc4.deck[3].wheelTouch + 0x93 + 0x58 + + + + + + [Master] + maximize_library 0x90 0x59 @@ -2258,6 +2611,851 @@ - + + + [Channel1] + loop_halve + 0x90 + 0x02 + 0x7F + 0x00 + 0.5 + + + [Channel2] + loop_halve + 0x91 + 0x02 + 0x7F + 0x00 + 0.5 + + + [Channel3] + loop_halve + 0x92 + 0x02 + 0x7F + 0x00 + 0.5 + + + [Channel4] + loop_halve + 0x93 + 0x02 + 0x7F + 0x00 + 0.5 + + + [Channel1] + loop_double + 0x90 + 0x03 + 0x7F + 0x00 + 0.5 + + + [Channel2] + loop_double + 0x91 + 0x03 + 0x7F + 0x00 + 0.5 + + + [Channel3] + loop_double + 0x92 + 0x03 + 0x7F + 0x00 + 0.5 + + + [Channel4] + loop_double + 0x93 + 0x03 + 0x7F + 0x00 + 0.5 + + + [Channel1] + loop_start_position + 0x90 + 0x04 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel2] + loop_start_position + 0x91 + 0x04 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel3] + loop_start_position + 0x92 + 0x04 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel4] + loop_start_position + 0x93 + 0x04 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel1] + loop_end_position + 0x90 + 0x05 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel2] + loop_end_position + 0x91 + 0x05 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel3] + loop_end_position + 0x92 + 0x05 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel4] + loop_end_position + 0x93 + 0x05 + 0x7F + 0x00 + 0 + 2147483647 + + + [Channel1] + loop_enabled + 0x90 + 0x06 + 0x7F + 0x00 + 0.5 + + + [Channel2] + loop_enabled + 0x91 + 0x06 + 0x7F + 0x00 + 0.5 + + + [Channel3] + loop_enabled + 0x92 + 0x06 + 0x7F + 0x00 + 0.5 + + + [Channel4] + loop_enabled + 0x93 + 0x06 + 0x7F + 0x00 + 0.5 + + + [Channel1] + hotcue_1_enabled + 0x90 + 0x08 + 0x7F + 0x00 + 0.5 + + + [Channel2] + hotcue_1_enabled + 0x91 + 0x08 + 0x7F + 0x00 + 0.5 + + + [Channel3] + hotcue_1_enabled + 0x92 + 0x08 + 0x7F + 0x00 + 0.5 + + + [Channel4] + hotcue_1_enabled + 0x93 + 0x08 + 0x7F + 0x00 + 0.5 + + + [Channel1] + hotcue_2_enabled + 0x90 + 0x09 + 0x7F + 0x00 + 0.5 + + + [Channel2] + hotcue_2_enabled + 0x91 + 0x09 + 0x7F + 0x00 + 0.5 + + + [Channel3] + hotcue_2_enabled + 0x92 + 0x09 + 0x7F + 0x00 + 0.5 + + + [Channel4] + hotcue_2_enabled + 0x93 + 0x09 + 0x7F + 0x00 + 0.5 + + + [Channel1] + hotcue_3_enabled + 0x90 + 0x0A + 0x7F + 0x00 + 0.5 + + + [Channel2] + hotcue_3_enabled + 0x91 + 0x0A + 0x7F + 0x00 + 0.5 + + + [Channel3] + hotcue_3_enabled + 0x92 + 0x0A + 0x7F + 0x00 + 0.5 + + + [Channel4] + hotcue_3_enabled + 0x93 + 0x0A + 0x7F + 0x00 + 0.5 + + + [Channel1] + hotcue_4_enabled + 0x90 + 0x0B + 0x7F + 0x00 + 0.5 + + + [Channel2] + hotcue_4_enabled + 0x91 + 0x0B + 0x7F + 0x00 + 0.5 + + + [Channel3] + hotcue_4_enabled + 0x92 + 0x0B + 0x7F + 0x00 + 0.5 + + + [Channel4] + hotcue_4_enabled + 0x93 + 0x0B + 0x7F + 0x00 + 0.5 + + + [Channel1] + keylock + 0x90 + 0x10 + 0x7F + 0x00 + 0.5 + + + [Channel2] + keylock + 0x91 + 0x10 + 0x7F + 0x00 + 0.5 + + + [Channel3] + keylock + 0x92 + 0x10 + 0x7F + 0x00 + 0.5 + + + [Channel4] + keylock + 0x93 + 0x10 + 0x7F + 0x00 + 0.5 + + + [Channel1] + beatsync + 0x90 + 0x12 + 0x7F + 0x00 + 0.5 + + + [Channel2] + beatsync + 0x91 + 0x12 + 0x7F + 0x00 + 0.5 + + + [Channel3] + beatsync + 0x92 + 0x12 + 0x7F + 0x00 + 0.5 + + + [Channel4] + beatsync + 0x93 + 0x12 + 0x7F + 0x00 + 0.5 + + + [Channel1] + rate_temp_down + 0x90 + 0x13 + 0x7F + 0x00 + 0.5 + + + [Channel2] + rate_temp_down + 0x91 + 0x13 + 0x7F + 0x00 + 0.5 + + + [Channel3] + rate_temp_down + 0x92 + 0x13 + 0x7F + 0x00 + 0.5 + + + [Channel4] + rate_temp_down + 0x93 + 0x13 + 0x7F + 0x00 + 0.5 + + + [Channel1] + rate_temp_up + 0x90 + 0x14 + 0x7F + 0x00 + 0.5 + + + [Channel2] + rate_temp_up + 0x91 + 0x14 + 0x7F + 0x00 + 0.5 + + + [Channel3] + rate_temp_up + 0x92 + 0x14 + 0x7F + 0x00 + 0.5 + + + [Channel4] + rate_temp_up + 0x93 + 0x14 + 0x7F + 0x00 + 0.5 + + + [Channel1] + play_indicator + 0x90 + 0x16 + 0x7F + 0x00 + 0.5 + + + [Channel2] + play_indicator + 0x91 + 0x16 + 0x7F + 0x00 + 0.5 + + + [Channel3] + play_indicator + 0x92 + 0x16 + 0x7F + 0x00 + 0.5 + + + [Channel4] + play_indicator + 0x93 + 0x16 + 0x7F + 0x00 + 0.5 + + + [Channel1] + cue_indicator + 0x90 + 0x17 + 0x7F + 0x00 + 0.5 + + + [Channel2] + cue_indicator + 0x91 + 0x17 + 0x7F + 0x00 + 0.5 + + + [Channel3] + cue_indicator + 0x92 + 0x17 + 0x7F + 0x00 + 0.5 + + + [Channel4] + cue_indicator + 0x93 + 0x17 + 0x7F + 0x00 + 0.5 + + + [Channel1] + play_indicator + 0x90 + 0x18 + 0x7F + 0x00 + 0.5 + + + [Channel2] + play_indicator + 0x91 + 0x18 + 0x7F + 0x00 + 0.5 + + + [Channel3] + play_indicator + 0x92 + 0x18 + 0x7F + 0x00 + 0.5 + + + [Channel4] + play_indicator + 0x93 + 0x18 + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter3 + 0x90 + 0x19 + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter3 + 0x91 + 0x19 + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel3]_Effect1] + button_parameter3 + 0x92 + 0x19 + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel4]_Effect1] + button_parameter3 + 0x93 + 0x19 + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter2 + 0x90 + 0x1A + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter2 + 0x91 + 0x1A + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel3]_Effect1] + button_parameter2 + 0x92 + 0x1A + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel4]_Effect1] + button_parameter2 + 0x93 + 0x1A + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter1 + 0x90 + 0x1B + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter1 + 0x91 + 0x1B + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel3]_Effect1] + button_parameter1 + 0x92 + 0x1B + 0x7F + 0x00 + 0.5 + + + [EqualizerRack1_[Channel4]_Effect1] + button_parameter1 + 0x93 + 0x1B + 0x7F + 0x00 + 0.5 + + + [Channel1] + pfl + 0x90 + 0x1C + 0x7F + 0x00 + 0.5 + + + [Channel2] + pfl + 0x91 + 0x1C + 0x7F + 0x00 + 0.5 + + + [Channel3] + pfl + 0x92 + 0x1C + 0x7F + 0x00 + 0.5 + + + [Channel4] + pfl + 0x93 + 0x1C + 0x7F + 0x00 + 0.5 + + + [EffectRack1_EffectUnit1] + group_[Channel1]_enable + 0x90 + 0x1E + 0x7F + 0x00 + 0.5 + + + [EffectRack1_EffectUnit2] + group_[Channel2]_enable + 0x91 + 0x1E + 0x7F + 0x00 + 0.5 + + + [EffectRack1_EffectUnit3] + group_[Channel3]_enable + 0x92 + 0x1E + 0x7F + 0x00 + 0.5 + + + [EffectRack1_EffectUnit4] + group_[Channel4]_enable + 0x93 + 0x1E + 0x7F + 0x00 + 0.5 + + + [Channel1] + play_indicator + 0x90 + 0x22 + 0x7F + 0x00 + 0.5 + 0.0 + + + [Channel3] + play_indicator + 0x92 + 0x22 + 0x7F + 0x00 + 0.5 + 0.0 + + + [Channel2] + play_indicator + 0x91 + 0x23 + 0x7F + 0x00 + 0.5 + 0.0 + + + [Channel4] + play_indicator + 0x93 + 0x23 + 0x7F + 0x00 + 0.5 + 0.0 + + + [QuickEffectRack1_[Channel1]_Effect1] + enabled + 0x90 + 0x27 + 0x7F + 0x00 + 0.5 + 0.0 + + + [QuickEffectRack1_[Channel2]_Effect1] + enabled + 0x91 + 0x27 + 0x7F + 0x00 + 0.5 + 0.0 + + + [QuickEffectRack1_[Channel3]_Effect1] + enabled + 0x92 + 0x27 + 0x7F + 0x00 + 0.5 + 0.0 + + + [QuickEffectRack1_[Channel4]_Effect1] + enabled + 0x93 + 0x27 + 0x7F + 0x00 + 0.5 + 0.0 + + From 4da251ba2b5c2349a66399ad9f2a85f8539587a0 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 28 Mar 2020 11:47:47 +0100 Subject: [PATCH 079/393] Provide an abort() function for composite web tasks --- src/network/webtask.cpp | 14 ++++++++++---- src/network/webtask.h | 6 +++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index b385e5461c18..4348660e2522 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -78,7 +78,7 @@ void WebTask::onAborted( const auto signal = QMetaMethod::fromSignal( &WebTask::aborted); if (isSignalConnected(signal)) { - emit aborted(); + emit aborted(requestUrl); } else { kLogger.info() << "Request aborted" @@ -235,11 +235,11 @@ QUrl WebTask::timeOutPendingNetworkReply( return pendingNetworkReply->request().url(); } -void WebTask::slotAbort() { +QUrl WebTask::abort() { DEBUG_ASSERT(thread() == QThread::currentThread()); if (m_status != Status::Pending) { DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId); - return; + return QUrl(); } if (m_timeoutTimerId != kInvalidTimerId) { killTimer(m_timeoutTimerId); @@ -248,7 +248,13 @@ void WebTask::slotAbort() { m_status = Status::Aborted; kLogger.debug() << "Aborting..."; - onAborted(doAbort()); + QUrl url = doAbort(); + onAborted(url); + return url; +} + +void WebTask::slotAbort() { + abort(); } void WebTask::timerEvent(QTimerEvent* event) { diff --git a/src/network/webtask.h b/src/network/webtask.h index 81f62bc46cca..f9b13c8f7637 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -109,6 +109,9 @@ class WebTask : public QObject { // Cancel a pending request. void invokeAbort(); + // Cancel a pending request from the event loop thread. + QUrl abort(); + // Abort the pending request while suppressing any signals // and mark the task for deletion. void deleteBeforeFinished(); @@ -128,7 +131,8 @@ class WebTask : public QObject { // in memory as a dysfunctional zombie until its parent object // is finally deleted. If no receiver is connected the task // will be deleted implicitly. - void aborted(); + void aborted( + QUrl requestUrl); void networkError( QUrl requestUrl, QNetworkReply::NetworkError errorCode, From 13388888328432cb89a74785a5a6905cd7c2efc9 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 28 Mar 2020 12:14:49 +0100 Subject: [PATCH 080/393] Open "Summary" instead of "BPM" tab in DlgTrackInfo --- src/library/dlgtrackinfo.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/dlgtrackinfo.ui b/src/library/dlgtrackinfo.ui index caf42ac2c25a..a647f5f1890b 100644 --- a/src/library/dlgtrackinfo.ui +++ b/src/library/dlgtrackinfo.ui @@ -41,7 +41,7 @@ - 2 + 0 From 807ab7ec736bccc2b5d5929155d643d53d207402 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 28 Mar 2020 12:15:16 +0100 Subject: [PATCH 081/393] Restore consistent naming of slots --- src/library/dlgtrackinfo.cpp | 14 +++++++------- src/library/dlgtrackinfo.h | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index 35848013acd8..4f0398b28824 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -44,9 +44,9 @@ void DlgTrackInfo::init() { connect(btnNext, &QPushButton::clicked, this, &DlgTrackInfo::slotNext); connect(btnPrev, &QPushButton::clicked, this, &DlgTrackInfo::slotPrev); - connect(btnApply, &QPushButton::clicked, this, &DlgTrackInfo::apply); - connect(btnOK, &QPushButton::clicked, this, &DlgTrackInfo::OK); - connect(btnCancel, &QPushButton::clicked, this, &DlgTrackInfo::cancel); + connect(btnApply, &QPushButton::clicked, this, &DlgTrackInfo::slotApply); + connect(btnOK, &QPushButton::clicked, this, &DlgTrackInfo::slotOk); + connect(btnCancel, &QPushButton::clicked, this, &DlgTrackInfo::slotCancel); connect(bpmDouble, &QPushButton::clicked, @@ -115,7 +115,7 @@ void DlgTrackInfo::init() { &DlgTrackInfo::slotOpenInFileBrowser); CoverArtCache* pCache = CoverArtCache::instance(); - if (pCache != nullptr) { + if (pCache) { connect(pCache, &CoverArtCache::coverFound, this, @@ -131,16 +131,16 @@ void DlgTrackInfo::init() { &DlgTrackInfo::slotReloadCoverArt); } -void DlgTrackInfo::OK() { +void DlgTrackInfo::slotOk() { unloadTrack(true); accept(); } -void DlgTrackInfo::apply() { +void DlgTrackInfo::slotApply() { saveTrack(); } -void DlgTrackInfo::cancel() { +void DlgTrackInfo::slotCancel() { unloadTrack(false); reject(); } diff --git a/src/library/dlgtrackinfo.h b/src/library/dlgtrackinfo.h index 98dc17c5279d..64276564a1b3 100644 --- a/src/library/dlgtrackinfo.h +++ b/src/library/dlgtrackinfo.h @@ -34,9 +34,10 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { private slots: void slotNext(); void slotPrev(); - void OK(); - void apply(); - void cancel(); + void slotOk(); + void slotApply(); + void slotCancel(); + void trackUpdated(); void slotBpmDouble(); From 8e95102333bd1987e34b8f15c969a54cea6e5101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 28 Mar 2020 13:38:41 +0100 Subject: [PATCH 082/393] Improve naming and use int as index like Qt --- src/preferences/colorpaletteeditormodel.cpp | 10 ++++++---- src/preferences/colorpalettesettings.cpp | 6 +++--- src/util/color/colorpalette.cpp | 4 ++-- src/util/color/colorpalette.h | 10 +++++----- src/util/color/predefinedcolorpalettes.cpp | 6 +++--- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/preferences/colorpaletteeditormodel.cpp b/src/preferences/colorpaletteeditormodel.cpp index 62d958fbe165..291e9aef668b 100644 --- a/src/preferences/colorpaletteeditormodel.cpp +++ b/src/preferences/colorpaletteeditormodel.cpp @@ -102,9 +102,9 @@ void ColorPaletteEditorModel::setColorPalette(const ColorPalette& palette) { // Make a map of hotcue indices QMap hotcueColorIndicesMap; - QList hotcueColorIndices = palette.getHotcueIndices(); - for (int i = 0; i < hotcueColorIndices.size(); i++) { - int colorIndex = hotcueColorIndices.at(i); + QList colorIndicesByHotcue = palette.getIndicesByHotcue(); + for (int i = 0; i < colorIndicesByHotcue.size(); i++) { + int colorIndex = colorIndicesByHotcue.at(i); hotcueColorIndicesMap.insert(colorIndex, i); } @@ -119,7 +119,7 @@ void ColorPaletteEditorModel::setColorPalette(const ColorPalette& palette) { ColorPalette ColorPaletteEditorModel::getColorPalette(const QString& name) const { QList colors; - QMap hotcueColorIndices; + QMap hotcueColorIndices; for (int i = 0; i < rowCount(); i++) { QStandardItem* pColorItem = item(i, 0); QStandardItem* pHotcueIndexItem = item(i, 1); @@ -134,5 +134,7 @@ ColorPalette ColorPaletteEditorModel::getColorPalette(const QString& name) const } } } + // If we have a non consequitive list of hotcue indexes, indexes are shifted down + // due to the sorting nature of QMap. This is intended, this way we have a color for every hotcue. return ColorPalette(name, colors, hotcueColorIndices.values()); } diff --git a/src/preferences/colorpalettesettings.cpp b/src/preferences/colorpalettesettings.cpp index 1b01ca9973b3..238bff1dd88e 100644 --- a/src/preferences/colorpalettesettings.cpp +++ b/src/preferences/colorpalettesettings.cpp @@ -44,7 +44,7 @@ ColorPalette ColorPaletteSettings::getColorPalette( // Read colors from configuration const QString group = kColorPaletteGroupStart + name + kColorPaletteGroupEnd; QList colorList; - QList hotcueIndices; + QList hotcueIndices; for (const ConfigKey& key : m_pConfig->getKeysWithGroup(group)) { if (key.item == kColorPaletteHotcueIndicesConfigItem) { for (const QString& stringIndex : @@ -52,7 +52,7 @@ ColorPalette ColorPaletteSettings::getColorPalette( bool ok; int index = stringIndex.toInt(&ok); if (ok && index >= 0) { - hotcueIndices << static_cast(index); + hotcueIndices << index; } } } else { @@ -91,7 +91,7 @@ void ColorPaletteSettings::setColorPalette(const QString& name, const ColorPalet } QStringList stringIndices; - for (const unsigned int index : colorPalette.getHotcueIndices()) { + for (const unsigned int index : colorPalette.getIndicesByHotcue()) { stringIndices << QString::number(index); } if (!stringIndices.isEmpty()) { diff --git a/src/util/color/colorpalette.cpp b/src/util/color/colorpalette.cpp index 0e6dd4286b42..9d4d7186a96f 100644 --- a/src/util/color/colorpalette.cpp +++ b/src/util/color/colorpalette.cpp @@ -18,10 +18,10 @@ mixxx::RgbColor ColorPalette::previousColor(mixxx::RgbColor color) const { mixxx::RgbColor ColorPalette::colorForHotcueIndex(unsigned int hotcueIndex) const { int colorIndex; - if (m_hotcueColorIndices.isEmpty()) { + if (m_colorIndicesByHotcue.isEmpty()) { colorIndex = hotcueIndex; } else { - colorIndex = m_hotcueColorIndices.at(hotcueIndex % m_hotcueColorIndices.size()); + colorIndex = m_colorIndicesByHotcue.at(hotcueIndex % m_colorIndicesByHotcue.size()); } return at(colorIndex % size()); } diff --git a/src/util/color/colorpalette.h b/src/util/color/colorpalette.h index 410f9542af3a..d52852a9439e 100644 --- a/src/util/color/colorpalette.h +++ b/src/util/color/colorpalette.h @@ -9,10 +9,10 @@ class ColorPalette final { ColorPalette( QString name, QList colorList, - QList hotcueColorIndices = {}) + QList colorIndicesByHotcue = {}) : m_name(name), m_colorList(colorList), - m_hotcueColorIndices(hotcueColorIndices) { + m_colorIndicesByHotcue(colorIndicesByHotcue) { DEBUG_ASSERT(m_colorList.size() != 0); } @@ -52,14 +52,14 @@ class ColorPalette final { return m_colorList; } - QList getHotcueIndices() const { - return m_hotcueColorIndices; + QList getIndicesByHotcue() const { + return m_colorIndicesByHotcue; } private: QString m_name; QList m_colorList; - QList m_hotcueColorIndices; + QList m_colorIndicesByHotcue; }; inline bool operator==( diff --git a/src/util/color/predefinedcolorpalettes.cpp b/src/util/color/predefinedcolorpalettes.cpp index 5fd3125a0b17..4ff3d79e57fd 100644 --- a/src/util/color/predefinedcolorpalettes.cpp +++ b/src/util/color/predefinedcolorpalettes.cpp @@ -130,7 +130,7 @@ const ColorPalette PredefinedColorPalettes::kMixxxHotcueColorPalette = // Exclude kSchemaMigrationReplacementColor from the colors assigned to hotcues. // If there were 9 colors assigned to hotcues, that would look weird on // controllers with >8 hotcue buttons, for example a Novation Launchpad. - QList{0, 1, 2, 3, 4, 5, 6, 7}); + QList{0, 1, 2, 3, 4, 5, 6, 7}); const ColorPalette PredefinedColorPalettes::kSeratoTrackMetadataHotcueColorPalette = ColorPalette( @@ -155,7 +155,7 @@ const ColorPalette PredefinedColorPalettes::kSeratoTrackMetadataHotcueColorPalet kSeratoTrackMetadataHotcueColorMagenta, kSeratoTrackMetadataHotcueColorCarmine, }, - QList{0, 2, 12, 3, 6, 15, 9, 14}); + QList{0, 2, 12, 3, 6, 15, 9, 14}); const ColorPalette PredefinedColorPalettes::kSeratoDJProHotcueColorPalette = ColorPalette( @@ -180,7 +180,7 @@ const ColorPalette PredefinedColorPalettes::kSeratoDJProHotcueColorPalette = kSeratoDJProHotcueColorPurple, kSeratoDJProHotcueColorRed2, }, - QList{0, 2, 12, 3, 6, 15, 9, 14}); + QList{0, 2, 12, 3, 6, 15, 9, 14}); const ColorPalette PredefinedColorPalettes::kRekordboxTrackColorPalette = ColorPalette( From f900f33bdd60aad9acdb1aa51508fb7226517b6a Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 28 Mar 2020 13:44:39 +0100 Subject: [PATCH 083/393] SLICER mode PAD buttons reworked to reduce complexity --- res/controllers/Denon-MC7000-scripts.js | 73 +++++-------------------- 1 file changed, 15 insertions(+), 58 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 32d2b9e0cb66..1a90df137f94 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -163,7 +163,7 @@ MC7000.init = function() { midi.sendShortMsg(0x92, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); midi.sendShortMsg(0x93, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); - // PAD Mode LEDs + // HotCue Mode LEDs for (var i = 1; i <= 8; i++) { engine.makeConnection("[Channel1]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); engine.makeConnection("[Channel2]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); @@ -171,7 +171,7 @@ MC7000.init = function() { engine.makeConnection("[Channel4]", "hotcue_"+i+"_enabled", MC7000.HotCueLED); } - // Sampler Mode LED indicator + // Sampler Mode LEDs for (i = 1; i <= 8; i++) { engine.makeConnection("[Sampler"+i+"]", "track_loaded", MC7000.SamplerLED); engine.makeConnection("[Sampler"+i+"]", "play", MC7000.SamplerLED); @@ -521,62 +521,19 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } else if (MC7000.PADModeSavedLoop[deckNumber]) { return; } else if (MC7000.PADModeSlicer[deckNumber]) { - if (control === 0x14 && value >= 0x01) { - engine.setValue(group, "beatjump_1_forward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14, - MC7000.padColor.slicerJumpFwd); - } else if (control === 0x14 && value >= 0x00) { - engine.setValue(group, "beatjump_1_forward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14, MC7000.padColor.sliceron); - } else if (control === 0x15 && value >= 0x01) { - engine.setValue(group, "beatjump_2_forward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x15, - MC7000.padColor.slicerJumpFwd); - } else if (control === 0x15 && value >= 0x00) { - engine.setValue(group, "beatjump_2_forward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x15, MC7000.padColor.sliceron); - } else if (control === 0x16 && value >= 0x01) { - engine.setValue(group, "beatjump_4_forward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x16, - MC7000.padColor.slicerJumpFwd); - } else if (control === 0x16 && value >= 0x00) { - engine.setValue(group, "beatjump_4_forward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x16, MC7000.padColor.sliceron); - } else if (control === 0x17 && value >= 0x01) { - engine.setValue(group, "beatjump_8_forward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x17, - MC7000.padColor.slicerJumpFwd); - } else if (control === 0x17 && value >= 0x00) { - engine.setValue(group, "beatjump_8_forward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x17, MC7000.padColor.sliceron); - } else if (control === 0x18 && value >= 0x01) { - engine.setValue(group, "beatjump_1_backward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x18, - MC7000.padColor.slicerJumpBack); - } else if (control === 0x18 && value >= 0x00) { - engine.setValue(group, "beatjump_1_backward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x18, MC7000.padColor.sliceron); - } else if (control === 0x19 && value >= 0x01) { - engine.setValue(group, "beatjump_2_backward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x19, - MC7000.padColor.slicerJumpBack); - } else if (control === 0x19 && value >= 0x00) { - engine.setValue(group, "beatjump_2_backward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x19, MC7000.padColor.sliceron); - } else if (control === 0x1A && value >= 0x01) { - engine.setValue(group, "beatjump_4_backward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1A, - MC7000.padColor.slicerJumpBack); - } else if (control === 0x1A && value >= 0x00) { - engine.setValue(group, "beatjump_4_backward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1A, MC7000.padColor.sliceron); - } else if (control === 0x1B && value >= 0x01) { - engine.setValue(group, "beatjump_8_backward", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1B, - MC7000.padColor.slicerJumpBack); - } else if (control === 0x1B && value >= 0x00) { - engine.setValue(group, "beatjump_8_backward", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1B, MC7000.padColor.sliceron); + if (value > 0) { + var beats = 1 << (control % 4); + if (control > 0x17) { + engine.setValue(group, "beatjump_" + beats + "_backward", value); + midi.sendShortMsg(0x94 + deckNumber -1, control, MC7000.padColor.slicerJumpBack); + } else { + engine.setValue(group, "beatjump_" + beats + "_forward", value); + midi.sendShortMsg(0x94 + deckNumber -1, control, MC7000.padColor.slicerJumpFwd); + } + } else { + for (i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckNumber -1, control, MC7000.padColor.sliceron); + } } } else if (MC7000.PADModeSlicerLoop[deckNumber]) { return; From e41bfc3a7e43ee8bc8b54253537cfaf5c8155d54 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Fri, 27 Mar 2020 03:12:59 +0100 Subject: [PATCH 084/393] align top: controller preset description & script file label --- src/controllers/dlgprefcontrollerdlg.ui | 31 ++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index fed1df3aa653..52342ce99032 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -230,7 +230,7 @@ Description: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing @@ -321,12 +321,37 @@ Script Files: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - + + + + 0 + 0 + + + + Qt::ClickFocus + + + + + + Qt::ImhUrlCharactersOnly + + + (links to loaded preset script files go here) + + + false + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + From e30c46bb0ce34b29be8ae09f132bdc1481e72286 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 28 Mar 2020 16:24:15 +0100 Subject: [PATCH 085/393] increase space in between support links --- src/controllers/dlgprefcontroller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index c0d5b596064c..c7bbcb331a14 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -409,7 +409,7 @@ void DlgPrefController::slotPresetLoaded(ControllerPresetPointer preset) { .arg(tr("Troubleshooting")); supportLinks << troubleShooting; - QString support = supportLinks.join(" "); + QString support = supportLinks.join("  "); m_ui.labelLoadedPresetSupportLinks->setText(support); // We mutate this preset so keep a reference to it while we are using it. From 6f2f40a2f7d61cb6d9f624c8a3edc40f80d3c7ac Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 28 Mar 2020 16:27:13 +0100 Subject: [PATCH 086/393] display links to loaded script files, open in editor --- src/controllers/dlgprefcontroller.cpp | 30 +++++++++++++++++++++++++++ src/controllers/dlgprefcontroller.h | 1 + 2 files changed, 31 insertions(+) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index c7bbcb331a14..34bbd94225a8 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -73,6 +73,12 @@ DlgPrefController::DlgPrefController(QWidget* parent, Controller* controller, connect(this, SIGNAL(loadPreset(Controller*, ControllerPresetPointer)), m_pControllerManager, SLOT(loadPreset(Controller*, ControllerPresetPointer))); + // Open script file links + connect(m_ui.labelLoadedPresetScriptFileLinks, + &QLabel::linkActivated, + [](const QString & path) { + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); + // Input mappings connect(m_ui.btnAddInputMapping, SIGNAL(clicked()), this, SLOT(addInputMapping())); @@ -214,6 +220,27 @@ QString DlgPrefController::presetWikiLink(const ControllerPresetPointer pPreset) return url; } +QString DlgPrefController::presetScriptFileLinks(const ControllerPresetPointer pPreset) const { + QString scriptFileLinks; + + if (pPreset) { + QList presetDirs; + presetDirs.append(userPresetsPath(m_pConfig)); + presetDirs.append(resourcePresetsPath(m_pConfig)); + QStringList linkList; + for (QList::iterator it = + pPreset->scripts.begin(); it != pPreset->scripts.end(); ++it) { + QString name = it->name; + QString path = ControllerManager::getAbsolutePath( + name, presetDirs); + QString scriptFileLink = "" + name + ""; + linkList << scriptFileLink; + } + scriptFileLinks = linkList.join("
"); + } + return scriptFileLinks; +} + void DlgPrefController::slotDirty() { m_bDirty = true; } @@ -412,6 +439,9 @@ void DlgPrefController::slotPresetLoaded(ControllerPresetPointer preset) { QString support = supportLinks.join("  "); m_ui.labelLoadedPresetSupportLinks->setText(support); + QString scriptFiles = presetScriptFileLinks(preset); + m_ui.labelLoadedPresetScriptFileLinks->setText(scriptFiles); + // We mutate this preset so keep a reference to it while we are using it. // TODO(rryan): Clone it? Technically a waste since nothing else uses this // copy but if someone did they might not expect it to change. diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index 39f20ce740c6..58a0c81849d5 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -78,6 +78,7 @@ class DlgPrefController : public DlgPreferencePage { QString presetDescription(const ControllerPresetPointer pPreset) const; QString presetForumLink(const ControllerPresetPointer pPreset) const; QString presetWikiLink(const ControllerPresetPointer pPreset) const; + QString presetScriptFileLinks(const ControllerPresetPointer pPreset) const; void savePreset(QString path); void initTableView(QTableView* pTable); From d09484f700a11c231d5b38449b0ff0841fec2c83 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sun, 29 Mar 2020 02:13:45 +0530 Subject: [PATCH 087/393] widget/wtrackproperty: create context menu with placeholder action --- src/widget/wtrackproperty.cpp | 25 +++++++++++++++++++++++++ src/widget/wtrackproperty.h | 10 +++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 419ac95025c5..99ab9e5a00aa 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "control/controlobject.h" #include "widget/wtrackproperty.h" @@ -13,6 +14,10 @@ WTrackProperty::WTrackProperty(const char* group, m_pGroup(group), m_pConfig(pConfig) { setAcceptDrops(true); + + // Setup context menu + m_pMenu = new QMenu(this); + createContextMenuActions(); } void WTrackProperty::setup(const QDomNode& node, const SkinContext& context) { @@ -71,3 +76,23 @@ void WTrackProperty::dragEnterEvent(QDragEnterEvent *event) { void WTrackProperty::dropEvent(QDropEvent *event) { DragAndDropHelper::handleTrackDropEvent(event, *this, m_pGroup, m_pConfig); } + +void WTrackProperty::slotOpenInFileBrowser() { + +} + +void WTrackProperty::createContextMenuActions() { + m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); + connect(m_pFileBrowserAct, SIGNAL(triggered()), + this, SLOT(slotOpenInFileBrowser())); +} + +void WTrackProperty::contextMenuEvent(QContextMenuEvent *event) { + m_pMenu->addSeparator(); + m_pMenu->addAction(m_pFileBrowserAct); + m_pMenu->addSeparator(); + + // Create the right-click menu + m_pMenu->popup(event->globalPos()); +} + diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index 8b35fc52a8c5..829e6aa2146f 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -17,8 +17,9 @@ class WTrackProperty : public WLabel, public TrackDropTarget { WTrackProperty(const char* group, UserSettingsPointer pConfig, QWidget* pParent); void setup(const QDomNode& node, const SkinContext& context) override; + void contextMenuEvent(QContextMenuEvent * event) override; - signals: +signals: void trackDropped(QString filename, QString group) override; void cloneDeck(QString source_group, QString target_group) override; @@ -28,6 +29,7 @@ class WTrackProperty : public WLabel, public TrackDropTarget { private slots: void slotTrackChanged(TrackId); + void slotOpenInFileBrowser(); private: void dragEnterEvent(QDragEnterEvent *event) override; @@ -35,11 +37,17 @@ class WTrackProperty : public WLabel, public TrackDropTarget { void mouseMoveEvent(QMouseEvent *event) override; void updateLabel(); + void createContextMenuActions(); const char* m_pGroup; UserSettingsPointer m_pConfig; TrackPointer m_pCurrentTrack; QString m_property; + + // Context menu machinery + QMenu *m_pMenu; + + QAction *m_pFileBrowserAct; }; From f4b77bc83c533b9e2c73fcee61691a66d4cf1a6a Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sat, 28 Mar 2020 23:59:57 +0100 Subject: [PATCH 088/393] fixed jog wheel bug and got shift button working --- res/controllers/Stanton-DJC-4-scripts.js | 69 +++++++++++++++--------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js index 1b5ba44c3dee..7c3234ce14a5 100644 --- a/res/controllers/Stanton-DJC-4-scripts.js +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -87,6 +87,24 @@ djc4.init = function() { engine.setValue("[Master]", "num_samplers", 8); } + djc4.browseEncoder = new components.Encoder({ + group: "[Library]", + inKey: "Move", + input: function(channel, control, value) { + if (value === 0x41) { + engine.setParameter(this.group, this.inKey + "Down", 1); + } else if (value === 0x3F) { + engine.setParameter(this.group, this.inKey + "Up", 1); + } + }, + unshift: function() { + this.inKey = "Move"; + }, + shift: function() { + this.inKey = "Scroll"; + }, + }); + djc4.deck = []; for (i = 0; i < 4; i++) { djc4.deck[i] = new djc4.Deck(i + 1); @@ -162,7 +180,7 @@ djc4.Deck = function(deckNumbers) { // === Instantiate controls === this.beatLoopEncoder = new components.Encoder({ - midi: [0xB0+deckNumbers-1, 0x01], + midi: [0xB0 + deckNumbers - 1, 0x01], group: "[Channel" + deckNumbers + "]", inKey: "beatloop_size", input: function(channel, control, value) { @@ -193,7 +211,7 @@ djc4.Deck = function(deckNumbers) { // === Scratch control === this.scratchMode = false; - this.toggleScratchMode = function(value) { + this.toggleScratchMode = function(channel, control, value) { if (value === 0x7F) { // Toggle setting this.scratchMode = !this.scratchMode; @@ -201,9 +219,10 @@ djc4.Deck = function(deckNumbers) { } }; - // ============================= JOG WHEELS ================================= - this.wheelTouch = function(channel, control, value) { - if (control === 0x58) { // If shift is pressed, do a fast search + // ============================= JOG WHEELS ============================== + this.wheelTouch = function(channel, control, value, status, group) { + if (engine.getValue(group, "play") === 0) { + // If not playing, do a fast search if (value === 0x7F) { var alpha = 1.0 / 8; var beta = alpha / 32; @@ -213,7 +232,8 @@ djc4.Deck = function(deckNumbers) { } else { // If button up engine.scratchDisable(script.deckFromGroup(this.currentDeck)); } - } else if (this.scratchMode === true) { // If scratch enabled + } else if (this.scratchMode === true) { + // If scratch enabled if (value === 0x7F) { alpha = 1.0/8; beta = alpha/32; @@ -223,14 +243,12 @@ djc4.Deck = function(deckNumbers) { } else { // If button up engine.scratchDisable(script.deckFromGroup(this.currentDeck)); } - } else if (value === 0x00) { - // In case shift is let go before the platter, - // ensure scratch is disabled + } else { // If button up engine.scratchDisable(script.deckFromGroup(this.currentDeck)); } }; - this.wheelTurn = function(control, value) { + this.wheelTurn = function(channel, control, value) { // When the jog wheel is turned in clockwise direction, value is // greater than 64 (= 0x40). If it's turned in counter-clockwise // direction, the value is smaller than 64. @@ -238,7 +256,7 @@ djc4.Deck = function(deckNumbers) { var deck = script.deckFromGroup(this.currentDeck); if (engine.isScratching(deck)) { engine.scratchTick(deck, newValue); // Scratch! - } else if (control === 0x20) { // If shift is pressed + } else if (this.shifted === true) { // If shift is pressed var oldPos = engine.getValue(this.currentDeck, "playposition"); // Since ‘playposition’ is normalized to unity, we need to scale by // song duration in order for the jog wheel to cover the same amount @@ -301,23 +319,22 @@ djc4.autoShowDecks = function() { engine.setValue("[Master]", "show_4decks", anyLoaded); }; -djc4.shiftButton = function(value) { - djc4.deck.concat(djc4.effectUnit).forEach( - value ? function(module) { module.shift(); } : function(module) { module.unshift(); } - ); -}; - -// === Browser === -djc4.browseEncoder = new components.Encoder({ - input: function(channel, control, value) { - var isShifted = (control) === 0x2C; - if (value === 0x41) { - engine.setValue("[Library]", isShifted ? "ScrollDown" : "MoveDown", true); - } else if (value === 0x3F) { - engine.setValue("[Library]", isShifted ? "ScrollUp" : "MoveUp", true); +djc4.shiftButton = function(channel, control, value) { + var i; + if (value === 0x7F) { + djc4.browseEncoder.shift(); + for (i = 0; i < 4; i++) { + djc4.deck[i].shift(); + djc4.effectUnit[i].shift(); + } + } else { + djc4.browseEncoder.unshift(); + for (i = 0; i < 4; i++) { + djc4.deck[i].unshift(); + djc4.effectUnit[i].unshift(); } } -}); +}; // === Sampler Volume Control === djc4.samplerVolume = function(channel, control, value) { From f2369499b729a1c5ec249572cdbb2d5f4f3326aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 03:23:50 +0200 Subject: [PATCH 089/393] Added line-length.py to call clang-format for lines > 100 only --- .clang-format | 6 ++--- .pre-commit-config.yaml | 11 +++++++++ scripts/line-length.py | 55 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100755 scripts/line-length.py diff --git a/.clang-format b/.clang-format index a055030a6487..3511d50931d2 100644 --- a/.clang-format +++ b/.clang-format @@ -3,9 +3,9 @@ BasedOnStyle: Google IndentWidth: 4 TabWidth: 8 UseTab: Never -# NOTE(2019-02-23, uklotzde) The column limit has been set to 0 -# to avoid eagerly reformatting of the existing code base. This -# may later be changed to 80-100 characters per line if desired. +# A ColumnLimit > 0 causes clang-format to unbreaks all short lines, +# which is undesired here. +# If the line length exeedes 100, "ColumnLimit: 80" is used in scripts/line-length.py ColumnLimit: 0 --- # Customize only those options that differ from the base style! diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf9b7c4108d1..8bf25a236893 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -111,3 +111,14 @@ repos: - commit - push - manual +- repo: local + hooks: + - id: line-length + name: line-length + description: Check for lines longer 100 and brakes them before 80. + entry: ./scripts/line-length.py + stages: + - commit + - push + language: python + files: \.(c|cpp|h)$ diff --git a/scripts/line-length.py b/scripts/line-length.py new file mode 100755 index 000000000000..020b8cd6c329 --- /dev/null +++ b/scripts/line-length.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import argparse +from typing import Optional +from typing import Sequence +from subprocess import call + + +def main(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("filenames", nargs="*", help="Filenames to check") + args = parser.parse_args(argv) + + for filename in args.filenames: + with open(filename) as fd: + for lineno, line in enumerate(fd): + if len(line) > 110: + humanlineno = lineno + 1 + print(f"{filename}:{humanlineno} Line is too long.") + lc = [ + "clang-format", + "-i", + f"-lines={humanlineno}:{humanlineno}", + "-style={" + "BasedOnStyle: Google, " + "IndentWidth: 4, " + "TabWidth: 8, " + "UseTab: Never, " + "ColumnLimit: 80, " + # clang-format normally unbreaks all short lines, + # which is undesired. + # This does not happen here because line is too long + "AccessModifierOffset: -2, " + "AlignAfterOpenBracket: DontAlign, " + "AlignOperands: false, " + "AllowShortFunctionsOnASingleLine: None, " + "AllowShortIfStatementsOnASingleLine: false, " + "AllowShortLoopsOnASingleLine: false, " + "BinPackArguments: false, " + "BinPackParameters: false, " + "ConstructorInitializerIndentWidth: 8, " + "ContinuationIndentWidth: 8, " + "IndentCaseLabels: false, " + "DerivePointerAlignment: false, " + "ReflowComments: false, " + "SpaceAfterTemplateKeyword: false, " + "SpacesBeforeTrailingComments: 1}", + f"{filename}", + ] + call(lc) + return 0 + + +if __name__ == "__main__": + exit(main()) From 246d453e405798c65aec6c5cf1666e72e150de16 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sun, 29 Mar 2020 16:17:07 +0530 Subject: [PATCH 090/393] widget/wtrackproperty: add destructor --- src/widget/wtrackproperty.cpp | 13 +++++++------ src/widget/wtrackproperty.h | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 99ab9e5a00aa..629a54db937b 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -1,11 +1,8 @@ - -#include -#include -#include +#include "widget/wtrackproperty.h" #include "control/controlobject.h" -#include "widget/wtrackproperty.h" #include "util/dnd.h" +#include "widget/wcoverartmenu.h" WTrackProperty::WTrackProperty(const char* group, UserSettingsPointer pConfig, @@ -20,6 +17,11 @@ WTrackProperty::WTrackProperty(const char* group, createContextMenuActions(); } +WTrackProperty::~WTrackProperty() { + delete m_pMenu; + delete m_pFileBrowserAct; +} + void WTrackProperty::setup(const QDomNode& node, const SkinContext& context) { WLabel::setup(node, context); @@ -95,4 +97,3 @@ void WTrackProperty::contextMenuEvent(QContextMenuEvent *event) { // Create the right-click menu m_pMenu->popup(event->globalPos()); } - diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index 829e6aa2146f..04f48ed8953d 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -15,6 +15,7 @@ class WTrackProperty : public WLabel, public TrackDropTarget { Q_OBJECT public: WTrackProperty(const char* group, UserSettingsPointer pConfig, QWidget* pParent); + ~WTrackProperty() override; void setup(const QDomNode& node, const SkinContext& context) override; void contextMenuEvent(QContextMenuEvent * event) override; From a5db490534cd4aa35cc97a352de27df963c4774c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 13:30:55 +0200 Subject: [PATCH 091/393] fix line length to the desired 100 and reduce indentation --- scripts/line-length.py | 69 +++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/scripts/line-length.py b/scripts/line-length.py index 020b8cd6c329..c41453075f37 100755 --- a/scripts/line-length.py +++ b/scripts/line-length.py @@ -14,40 +14,41 @@ def main(argv: Optional[Sequence[str]] = None) -> int: for filename in args.filenames: with open(filename) as fd: for lineno, line in enumerate(fd): - if len(line) > 110: - humanlineno = lineno + 1 - print(f"{filename}:{humanlineno} Line is too long.") - lc = [ - "clang-format", - "-i", - f"-lines={humanlineno}:{humanlineno}", - "-style={" - "BasedOnStyle: Google, " - "IndentWidth: 4, " - "TabWidth: 8, " - "UseTab: Never, " - "ColumnLimit: 80, " - # clang-format normally unbreaks all short lines, - # which is undesired. - # This does not happen here because line is too long - "AccessModifierOffset: -2, " - "AlignAfterOpenBracket: DontAlign, " - "AlignOperands: false, " - "AllowShortFunctionsOnASingleLine: None, " - "AllowShortIfStatementsOnASingleLine: false, " - "AllowShortLoopsOnASingleLine: false, " - "BinPackArguments: false, " - "BinPackParameters: false, " - "ConstructorInitializerIndentWidth: 8, " - "ContinuationIndentWidth: 8, " - "IndentCaseLabels: false, " - "DerivePointerAlignment: false, " - "ReflowComments: false, " - "SpaceAfterTemplateKeyword: false, " - "SpacesBeforeTrailingComments: 1}", - f"{filename}", - ] - call(lc) + if len(line) > 100: + continue + humanlineno = lineno + 1 + print(f"{filename}:{humanlineno} Line is too long.") + lc = [ + "clang-format", + "-i", + f"-lines={humanlineno}:{humanlineno}", + "-style={" + "BasedOnStyle: Google, " + "IndentWidth: 4, " + "TabWidth: 8, " + "UseTab: Never, " + "ColumnLimit: 80, " + # clang-format normally unbreaks all short lines, + # which is undesired. + # This does not happen here because line is too long + "AccessModifierOffset: -2, " + "AlignAfterOpenBracket: DontAlign, " + "AlignOperands: false, " + "AllowShortFunctionsOnASingleLine: None, " + "AllowShortIfStatementsOnASingleLine: false, " + "AllowShortLoopsOnASingleLine: false, " + "BinPackArguments: false, " + "BinPackParameters: false, " + "ConstructorInitializerIndentWidth: 8, " + "ContinuationIndentWidth: 8, " + "IndentCaseLabels: false, " + "DerivePointerAlignment: false, " + "ReflowComments: false, " + "SpaceAfterTemplateKeyword: false, " + "SpacesBeforeTrailingComments: 1}", + f"{filename}", + ] + call(lc) return 0 From e72f7b7f585d523ef53d7c1cb6aaeedfb785ad43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 13:32:37 +0200 Subject: [PATCH 092/393] fix typo --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 3511d50931d2..764c2547d9d1 100644 --- a/.clang-format +++ b/.clang-format @@ -5,7 +5,7 @@ TabWidth: 8 UseTab: Never # A ColumnLimit > 0 causes clang-format to unbreaks all short lines, # which is undesired here. -# If the line length exeedes 100, "ColumnLimit: 80" is used in scripts/line-length.py +# If the line length exceeds 100, "ColumnLimit: 80" is used in scripts/line-length.py ColumnLimit: 0 --- # Customize only those options that differ from the base style! From fc65ff7dc2bb22fe27807eedbca86e8d36a79374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 13:35:15 +0200 Subject: [PATCH 093/393] use underscore instead of dash --- .pre-commit-config.yaml | 6 +++--- scripts/{line-length.py => line_length.py} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename scripts/{line-length.py => line_length.py} (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8bf25a236893..ea0f13baca78 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -113,10 +113,10 @@ repos: - manual - repo: local hooks: - - id: line-length - name: line-length + - id: line_length + name: line_length description: Check for lines longer 100 and brakes them before 80. - entry: ./scripts/line-length.py + entry: ./scripts/line_length.py stages: - commit - push diff --git a/scripts/line-length.py b/scripts/line_length.py similarity index 100% rename from scripts/line-length.py rename to scripts/line_length.py From 61b52e64a7946e17948819dc6d3c79b7fb3b8971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 13:43:06 +0200 Subject: [PATCH 094/393] Avoid intermediate variable --- scripts/line_length.py | 63 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index c41453075f37..32e581c3bab7 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -18,37 +18,38 @@ def main(argv: Optional[Sequence[str]] = None) -> int: continue humanlineno = lineno + 1 print(f"{filename}:{humanlineno} Line is too long.") - lc = [ - "clang-format", - "-i", - f"-lines={humanlineno}:{humanlineno}", - "-style={" - "BasedOnStyle: Google, " - "IndentWidth: 4, " - "TabWidth: 8, " - "UseTab: Never, " - "ColumnLimit: 80, " - # clang-format normally unbreaks all short lines, - # which is undesired. - # This does not happen here because line is too long - "AccessModifierOffset: -2, " - "AlignAfterOpenBracket: DontAlign, " - "AlignOperands: false, " - "AllowShortFunctionsOnASingleLine: None, " - "AllowShortIfStatementsOnASingleLine: false, " - "AllowShortLoopsOnASingleLine: false, " - "BinPackArguments: false, " - "BinPackParameters: false, " - "ConstructorInitializerIndentWidth: 8, " - "ContinuationIndentWidth: 8, " - "IndentCaseLabels: false, " - "DerivePointerAlignment: false, " - "ReflowComments: false, " - "SpaceAfterTemplateKeyword: false, " - "SpacesBeforeTrailingComments: 1}", - f"{filename}", - ] - call(lc) + call( + [ + "clang-format", + "-i", + f"-lines={humanlineno}:{humanlineno}", + "-style={" + "BasedOnStyle: Google, " + "IndentWidth: 4, " + "TabWidth: 8, " + "UseTab: Never, " + "ColumnLimit: 80, " + # clang-format normally unbreaks all short lines, + # which is undesired. + # This does not happen here because line is too long + "AccessModifierOffset: -2, " + "AlignAfterOpenBracket: DontAlign, " + "AlignOperands: false, " + "AllowShortFunctionsOnASingleLine: None, " + "AllowShortIfStatementsOnASingleLine: false, " + "AllowShortLoopsOnASingleLine: false, " + "BinPackArguments: false, " + "BinPackParameters: false, " + "ConstructorInitializerIndentWidth: 8, " + "ContinuationIndentWidth: 8, " + "IndentCaseLabels: false, " + "DerivePointerAlignment: false, " + "ReflowComments: false, " + "SpaceAfterTemplateKeyword: false, " + "SpacesBeforeTrailingComments: 1}", + f"{filename}", + ] + ) return 0 From e96c90e1ee665e928c8a8452d354870b01c6903e Mon Sep 17 00:00:00 2001 From: Philip Gottschling Date: Sun, 29 Mar 2020 14:15:35 +0200 Subject: [PATCH 095/393] removed unused and unnecessary methods --- src/engine/controls/cuecontrol.cpp | 18 +++--------------- src/engine/controls/cuecontrol.h | 2 -- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index bb5f322a0e73..b0b9a06372db 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -549,14 +549,6 @@ void CueControl::loadCuesFromTrack() { } } -void CueControl::reloadCuesFromTrack() { - if (!m_pLoadedTrack) - return; - - // Update COs with cues from track. - loadCuesFromTrack(); -} - void CueControl::trackAnalyzed() { if (!m_pLoadedTrack) { return; @@ -586,11 +578,11 @@ void CueControl::trackAnalyzed() { } void CueControl::trackCuesUpdated() { - reloadCuesFromTrack(); + loadCuesFromTrack(); } void CueControl::trackBeatsUpdated() { - reloadCuesFromTrack(); + loadCuesFromTrack(); } void CueControl::quantizeChanged(double v) { @@ -600,7 +592,7 @@ void CueControl::quantizeChanged(double v) { bool wasTrackAtCue = getTrackAt() == TrackAt::Cue; bool wasTrackAtIntro = isTrackAtIntroCue(); - reloadCuesFromTrack(); + loadCuesFromTrack(); // if we are playing (no matter what reason for) do not seek if (m_pPlay->toBool()) { @@ -1702,10 +1694,6 @@ double CueControl::quantizeCuePoint(double cuePos) { return cuePos; } -bool CueControl::isTrackAtZeroPos() { - return (fabs(getSampleOfTrack().current) < 1.0f); -} - bool CueControl::isTrackAtIntroCue() { return (fabs(getSampleOfTrack().current - m_pIntroStartPosition->get()) < 1.0f); } diff --git a/src/engine/controls/cuecontrol.h b/src/engine/controls/cuecontrol.h index b1fdad21cac5..517a511126c5 100644 --- a/src/engine/controls/cuecontrol.h +++ b/src/engine/controls/cuecontrol.h @@ -126,7 +126,6 @@ class CueControl : public EngineControl { void hintReader(HintVector* pHintList) override; bool updateIndicatorsAndModifyPlay(bool newPlay, bool playPossible); void updateIndicators(); - bool isTrackAtZeroPos(); bool isTrackAtIntroCue(); void resetIndicators(); bool isPlayingByPlayButton(); @@ -191,7 +190,6 @@ class CueControl : public EngineControl { void attachCue(CuePointer pCue, HotcueControl* pControl); void detachCue(HotcueControl* pControl); void loadCuesFromTrack(); - void reloadCuesFromTrack(); double quantizeCuePoint(double position); double getQuantizedCurrentPosition(); TrackAt getTrackAt() const; From 62e914b57498d36c18b70c96f5097ea2c95477c8 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sun, 29 Mar 2020 19:42:52 +0530 Subject: [PATCH 096/393] widget/wtrackproperty: conditional context menu --- src/widget/wtrackproperty.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 629a54db937b..ff1f979f90c3 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -1,15 +1,21 @@ #include "widget/wtrackproperty.h" +// Qt includes +#include + +// std includes +#include + +// Project includes #include "control/controlobject.h" #include "util/dnd.h" -#include "widget/wcoverartmenu.h" WTrackProperty::WTrackProperty(const char* group, UserSettingsPointer pConfig, QWidget* pParent) : WLabel(pParent), m_pGroup(group), - m_pConfig(pConfig) { + m_pConfig(std::move(pConfig)) { setAcceptDrops(true); // Setup context menu @@ -90,10 +96,10 @@ void WTrackProperty::createContextMenuActions() { } void WTrackProperty::contextMenuEvent(QContextMenuEvent *event) { - m_pMenu->addSeparator(); - m_pMenu->addAction(m_pFileBrowserAct); - m_pMenu->addSeparator(); + if (m_pCurrentTrack) { + m_pMenu->addAction(m_pFileBrowserAct); - // Create the right-click menu - m_pMenu->popup(event->globalPos()); + // Create the right-click menu + m_pMenu->popup(event->globalPos()); + } } From b5284e239c030cb4f2cbf899dd57298aefe156ce Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sun, 29 Mar 2020 16:15:08 +0200 Subject: [PATCH 097/393] Made the dryWetKnob increment/decrement value tweakable --- res/controllers/Stanton-DJC-4-scripts.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js index 7c3234ce14a5..86c2a828726e 100644 --- a/res/controllers/Stanton-DJC-4-scripts.js +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -20,9 +20,12 @@ var djc4 = {}; // Tweakables. // ///////////////// -djc4.tempoRange = [0.08, 0.16, 0.5]; +djc4.tempoRange = [0.08, 0.16, 0.5]; // not used yet! djc4.autoShowFourDecks = false; +// amount the dryWetKnob changes the value for each increment +djc4.dryWetAdjustValue = 0.05; + /////////// // Code. // /////////// @@ -126,10 +129,9 @@ djc4.init = function() { djc4.effectUnit[i].dryWetKnob.midi = [0xB0 + i, 0x08]; djc4.effectUnit[i].dryWetKnob.input = function(channel, control, value) { if (value === 0x41) { - // 0.05 is an example. Adjust that value to whatever works well for your controller. - this.inSetParameter(this.inGetParameter() + 0.05); + this.inSetParameter(this.inGetParameter() + djc4.dryWetAdjustValue); } else if (value === 0x3F) { - this.inSetParameter(this.inGetParameter() - 0.05); + this.inSetParameter(this.inGetParameter() - djc4.dryWetAdjustValue); } }; djc4.effectUnit[i].init(); @@ -175,13 +177,13 @@ djc4.shutdown = function() { djc4.allLed2Default(); }; -djc4.Deck = function(deckNumbers) { - components.Deck.call(this, deckNumbers); +djc4.Deck = function(deckNumber) { + components.Deck.call(this, deckNumber); // === Instantiate controls === this.beatLoopEncoder = new components.Encoder({ - midi: [0xB0 + deckNumbers - 1, 0x01], - group: "[Channel" + deckNumbers + "]", + midi: [0xB0 + deckNumber - 1, 0x01], + group: "[Channel" + deckNumber + "]", inKey: "beatloop_size", input: function(channel, control, value) { if (value === 0x3F) { @@ -203,8 +205,8 @@ djc4.Deck = function(deckNumbers) { this.samplerButtons = []; for (var i = 0; i <= 3; i++) { this.samplerButtons[i] = new components.SamplerButton({ - number: (deckNumbers === 1 || deckNumbers === 3) ? (i + 1) : (i + 5), - midi: [0x90+deckNumbers-1, 0x0C+i], + number: (deckNumber === 1 || deckNumber === 3) ? (i + 1) : (i + 5), + midi: [0x90+deckNumber-1, 0x0C+i], }); } From eb435da6aa65faa06aacb85be253e1b71cd91e8a Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Sun, 29 Mar 2020 16:36:24 +0200 Subject: [PATCH 098/393] added udev rule for AlphaTheta devices --- res/linux/mixxx-usb-uaccess.rules | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/linux/mixxx-usb-uaccess.rules b/res/linux/mixxx-usb-uaccess.rules index 0f8a67809b16..d7f4875214a1 100644 --- a/res/linux/mixxx-usb-uaccess.rules +++ b/res/linux/mixxx-usb-uaccess.rules @@ -34,6 +34,8 @@ SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="17cc", TAG+="uac SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="057e", TAG+="uaccess" # Pioneer Corp. SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="08e4", TAG+="uaccess" +# AlphaTheta Corp. +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2b73", TAG+="uaccess" # Rane SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="13e5", TAG+="uaccess" # Reloop From a204f7270204c3c9a1f4ed33aa8c0c1d28a611ab Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sun, 29 Mar 2020 16:45:51 +0200 Subject: [PATCH 099/393] Made it tweakable which VU meter to show Master or Channel. Default is Master --- res/controllers/Stanton-DJC-4-scripts.js | 84 +++++++++++++++--------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js index 86c2a828726e..fa2b06f18da6 100644 --- a/res/controllers/Stanton-DJC-4-scripts.js +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -22,6 +22,7 @@ var djc4 = {}; djc4.tempoRange = [0.08, 0.16, 0.5]; // not used yet! djc4.autoShowFourDecks = false; +djc4.showMasterVu = true; // if set to false, show channel VU meter // amount the dryWetKnob changes the value for each increment djc4.dryWetAdjustValue = 0.05; @@ -137,38 +138,40 @@ djc4.init = function() { djc4.effectUnit[i].init(); } - // === VU Meter === - djc4.vuMeter = new components.Component({ - midi: [0xB0, 0x03], - group: "[Master]", - outKey: "VuMeterL", - output: function(value, group) { - // The red LEDs light up with MIDI values greater than 0x60. - // The Red LEDs should only be illuminated if the track is clipping. - if (engine.getValue(group, "PeakIndicator") === 1) { - value = 0x60; - } else { - value = Math.round(value * 0x54); - } - this.send(value); - }, - }); + // === Master VU Meter === + if (djc4.showMasterVu === true) { + djc4.vuMeter = new components.Component({ + midi: [0xB0, 0x03], + group: "[Master]", + outKey: "VuMeterL", + output: function(value, group) { + // The red LEDs light up with MIDI values greater than 0x60. + // The Red LEDs should only be illuminated if the track is clipping. + if (engine.getValue(group, "PeakIndicator") === 1) { + value = 0x60; + } else { + value = Math.round(value * 0x54); + } + this.send(value); + }, + }); - djc4.vuMeter = new components.Component({ - midi: [0xB0, 0x04], - group: "[Master]", - outKey: "VuMeterR", - output: function(value, group) { - // The red LEDs light up with MIDI values greater than 0x60. - // The Red LEDs should only be illuminated if the track is clipping. - if (engine.getValue(group, "PeakIndicator") === 1) { - value = 0x60; - } else { - value = Math.round(value * 0x54); - } - this.send(value); - }, - }); + djc4.vuMeter = new components.Component({ + midi: [0xB0, 0x04], + group: "[Master]", + outKey: "VuMeterR", + output: function(value, group) { + // The red LEDs light up with MIDI values greater than 0x60. + // The Red LEDs should only be illuminated if the track is clipping. + if (engine.getValue(group, "PeakIndicator") === 1) { + value = 0x60; + } else { + value = Math.round(value * 0x54); + } + this.send(value); + }, + }); + } }; // Called when the MIDI device is closed @@ -210,6 +213,25 @@ djc4.Deck = function(deckNumber) { }); } + // === Channel VU Meter === + if (djc4.showMasterVu === false) { + djc4.vuMeter = new components.Component({ + midi: [0xB0+deckNumber-1, 0x02], + group: "[Channel" + deckNumber + "]", + outKey: "VuMeter", + output: function(value, group) { + // The red LEDs light up with MIDI values greater than 0x60. + // The Red LEDs should only be illuminated if the track is clipping. + if (engine.getValue(group, "PeakIndicator") === 1) { + value = 0x60; + } else { + value = Math.round(value * 0x54); + } + this.send(value); + }, + }); + } + // === Scratch control === this.scratchMode = false; From 22f72d3c062ff037d3c81aa6680a9a0496912e3e Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sun, 29 Mar 2020 17:59:05 +0200 Subject: [PATCH 100/393] Changed beatLoop encoder to move loop instead of adjust its size To adjust the size there are the half/double buttons and this behavior is closer to the mapping of other controllers like the VCI-400 --- res/controllers/Stanton-DJC-4-scripts.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js index fa2b06f18da6..cf91b69acc90 100644 --- a/res/controllers/Stanton-DJC-4-scripts.js +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -187,20 +187,12 @@ djc4.Deck = function(deckNumber) { this.beatLoopEncoder = new components.Encoder({ midi: [0xB0 + deckNumber - 1, 0x01], group: "[Channel" + deckNumber + "]", - inKey: "beatloop_size", + inKey: "loop_move_1", input: function(channel, control, value) { - if (value === 0x3F) { - if (this.inGetParameter() <= 1) { - this.inSetParameter(this.inGetParameter() / 2); - } else { - this.inSetParameter(this.inGetParameter() - 1); - } - } else if (value === 0x41) { - if (this.inGetParameter() <= 1) { - this.inSetParameter(this.inGetParameter() * 2); - } else { - this.inSetParameter(this.inGetParameter() + 1); - } + if (value === 0x41) { + engine.setParameter(this.group, this.inKey + "_forward", 1); + } else if (value === 0x3F) { + engine.setParameter(this.group, this.inKey + "_backward", 1); } }, }); From 456878d151da2abb407f12797221f08a62db065a Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sun, 29 Mar 2020 23:08:31 +0530 Subject: [PATCH 101/393] widget/wtrackproperty: implement open in system file browser --- src/widget/wtrackproperty.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index ff1f979f90c3..9de2b945ac91 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -5,6 +5,7 @@ // std includes #include +#include // Project includes #include "control/controlobject.h" @@ -86,7 +87,8 @@ void WTrackProperty::dropEvent(QDropEvent *event) { } void WTrackProperty::slotOpenInFileBrowser() { - + QString trackLocation = m_pCurrentTrack->getLocation(); + mixxx::DesktopHelper::openInFileBrowser(QStringList(trackLocation)); } void WTrackProperty::createContextMenuActions() { From e5981f0d79080b7b6c92f20780beeca10bbf7163 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sun, 29 Mar 2020 19:57:38 +0200 Subject: [PATCH 102/393] restore WPushButton debug tooltip --- src/widget/wpushbutton.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/wpushbutton.cpp b/src/widget/wpushbutton.cpp index 6a590836f50a..aa492aac96f5 100644 --- a/src/widget/wpushbutton.cpp +++ b/src/widget/wpushbutton.cpp @@ -444,6 +444,8 @@ bool WPushButton::event(QEvent* e) { m_bPressed = false; restyleAndRepaint(); } + } else if (e->type() == QEvent::ToolTip) { + updateTooltip(); } return QWidget::event(e); } From 582c76f83007f6b2883c40a885521402e643c1d2 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sun, 29 Mar 2020 20:00:31 +0200 Subject: [PATCH 103/393] consolidate WPushButton events --- src/widget/wpushbutton.cpp | 18 ++++++------------ src/widget/wpushbutton.h | 2 -- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/widget/wpushbutton.cpp b/src/widget/wpushbutton.cpp index aa492aac96f5..d2544cd1bb5f 100644 --- a/src/widget/wpushbutton.cpp +++ b/src/widget/wpushbutton.cpp @@ -369,18 +369,6 @@ void WPushButton::paintEvent(QPaintEvent* e) { } } -void WPushButton::enterEvent(QEvent *event) { - m_bHovered = true; - restyleAndRepaint(); - return QWidget::enterEvent(event); -} - -void WPushButton::leaveEvent(QEvent *event) { - m_bHovered = false; - restyleAndRepaint(); - return QWidget::leaveEvent(event); -} - void WPushButton::mousePressEvent(QMouseEvent * e) { const bool leftClick = e->button() == Qt::LeftButton; const bool rightClick = e->button() == Qt::RightButton; @@ -446,6 +434,12 @@ bool WPushButton::event(QEvent* e) { } } else if (e->type() == QEvent::ToolTip) { updateTooltip(); + } else if (e->type() == QEvent::Enter) { + m_bHovered = true; + restyleAndRepaint(); + } else if (e->type() == QEvent::Leave) { + m_bHovered = false; + restyleAndRepaint(); } return QWidget::event(e); } diff --git a/src/widget/wpushbutton.h b/src/widget/wpushbutton.h index 80a51339be65..ac5f6ef525e1 100644 --- a/src/widget/wpushbutton.h +++ b/src/widget/wpushbutton.h @@ -88,8 +88,6 @@ class WPushButton : public WWidget { void paintEvent(QPaintEvent* e) override; void mousePressEvent(QMouseEvent* e) override; void mouseReleaseEvent(QMouseEvent* e) override; - void enterEvent(QEvent *event) override; - void leaveEvent(QEvent *event) override; void focusOutEvent(QFocusEvent* e) override; void fillDebugTooltip(QStringList* debug) override; From 864f7f85d7b81e32cac8b1468e1bf5ad10ca23f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 20:58:30 +0200 Subject: [PATCH 104/393] Extract threshold parameters and fix to format multible long lines at once. --- scripts/line_length.py | 75 +++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index 32e581c3bab7..be1d9b4f71df 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -5,6 +5,12 @@ from typing import Sequence from subprocess import call +# We recommend a maximum line length of 80, but do allow up to 100 characters +# if deemed necessary by the developer. Lines that exceed that limit will +# be wrapped after 80 characters automatically. +lineLengthTheshold = 100 +breakBefore = 80 + def main(argv: Optional[Sequence[str]] = None) -> int: parser = argparse.ArgumentParser() @@ -12,44 +18,47 @@ def main(argv: Optional[Sequence[str]] = None) -> int: args = parser.parse_args(argv) for filename in args.filenames: + lineArguments = [] with open(filename) as fd: for lineno, line in enumerate(fd): - if len(line) > 100: + if len(line) <= lineLengthTheshold: continue humanlineno = lineno + 1 print(f"{filename}:{humanlineno} Line is too long.") - call( - [ - "clang-format", - "-i", - f"-lines={humanlineno}:{humanlineno}", - "-style={" - "BasedOnStyle: Google, " - "IndentWidth: 4, " - "TabWidth: 8, " - "UseTab: Never, " - "ColumnLimit: 80, " - # clang-format normally unbreaks all short lines, - # which is undesired. - # This does not happen here because line is too long - "AccessModifierOffset: -2, " - "AlignAfterOpenBracket: DontAlign, " - "AlignOperands: false, " - "AllowShortFunctionsOnASingleLine: None, " - "AllowShortIfStatementsOnASingleLine: false, " - "AllowShortLoopsOnASingleLine: false, " - "BinPackArguments: false, " - "BinPackParameters: false, " - "ConstructorInitializerIndentWidth: 8, " - "ContinuationIndentWidth: 8, " - "IndentCaseLabels: false, " - "DerivePointerAlignment: false, " - "ReflowComments: false, " - "SpaceAfterTemplateKeyword: false, " - "SpacesBeforeTrailingComments: 1}", - f"{filename}", - ] - ) + lineArguments += [f"-lines={humanlineno}:{humanlineno}"] + if len(lineArguments) > 0: + call( + ["clang-format"] + + lineArguments + + [ + "-i", + "-style={" + "BasedOnStyle: Google, " + "IndentWidth: 4, " + "TabWidth: 8, " + "UseTab: Never," + " " + f"ColumnLimit: {breakBefore}, " + # clang-format normally unbreaks all short lines, + # which is undesired. + # This does not happen here because line is too long + + "AccessModifierOffset: -2, " + "AlignAfterOpenBracket: DontAlign, " + "AlignOperands: false, " + "AllowShortFunctionsOnASingleLine: None, " + "AllowShortIfStatementsOnASingleLine: false, " + "AllowShortLoopsOnASingleLine: false, " + "BinPackArguments: false, " + "BinPackParameters: false, " + "ConstructorInitializerIndentWidth: 8, " + "ContinuationIndentWidth: 8, " + "IndentCaseLabels: false, " + "DerivePointerAlignment: false, " + "ReflowComments: false, " + "SpaceAfterTemplateKeyword: false, " + "SpacesBeforeTrailingComments: 1}", + f"{filename}", + ] + ) return 0 From 347eb89efbe86fa306d5b82f31dc535b8618547e Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sun, 29 Mar 2020 21:24:46 +0200 Subject: [PATCH 105/393] Added mapping for the CROSSFADER CURVE knob --- res/controllers/Stanton-DJC-4-scripts.js | 4 ++++ res/controllers/Stanton-DJC-4.midi.xml | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js index cf91b69acc90..a70ae4a78974 100644 --- a/res/controllers/Stanton-DJC-4-scripts.js +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -352,6 +352,10 @@ djc4.shiftButton = function(channel, control, value) { } }; +djc4.crossfaderCurve = function(channel, control, value) { + script.crossfaderCurve(value, 0, 127); +}; + // === Sampler Volume Control === djc4.samplerVolume = function(channel, control, value) { // check if the Sampler Volume is at Zero and if so hide the sampler bank diff --git a/res/controllers/Stanton-DJC-4.midi.xml b/res/controllers/Stanton-DJC-4.midi.xml index e97d10919278..0caf19fc5034 100644 --- a/res/controllers/Stanton-DJC-4.midi.xml +++ b/res/controllers/Stanton-DJC-4.midi.xml @@ -976,6 +976,15 @@ + + [Master] + djc4.crossfaderCurve + 0xB0 + 0x12 + + + + [Channel1] beatsync From a13a86f4f5400dbdfe6e279a87886726474b2cdf Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Mon, 30 Mar 2020 02:43:59 +0530 Subject: [PATCH 106/393] widget/wtrackproperty: add (non-functional) add to playlist menu --- src/skin/legacyskinparser.cpp | 2 +- src/widget/wtrackproperty.cpp | 67 +++++++++++++++++++++++++++++++++-- src/widget/wtrackproperty.h | 15 +++++++- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/skin/legacyskinparser.cpp b/src/skin/legacyskinparser.cpp index 30cd1c3ae7d7..dc88ef47393e 100644 --- a/src/skin/legacyskinparser.cpp +++ b/src/skin/legacyskinparser.cpp @@ -1039,7 +1039,7 @@ QWidget* LegacySkinParser::parseTrackProperty(const QDomElement& node) { if (!pPlayer) return NULL; - WTrackProperty* p = new WTrackProperty(pSafeChannelStr, m_pConfig, m_pParent); + WTrackProperty* p = new WTrackProperty(pSafeChannelStr, m_pConfig, m_pParent, m_pLibrary->trackCollections()); setupLabelWidget(node, p); connect(pPlayer, SIGNAL(newTrackLoaded(TrackPointer)), diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 9de2b945ac91..a57a7bc73202 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -2,31 +2,49 @@ // Qt includes #include +#include +#include // std includes #include -#include // Project includes #include "control/controlobject.h" +#include "library/dao/playlistdao.h" +#include "library/trackcollection.h" +#include "library/trackcollectionmanager.h" +#include "library/trackmodel.h" +#include "track/track.h" #include "util/dnd.h" +#include "util/desktophelper.h" WTrackProperty::WTrackProperty(const char* group, UserSettingsPointer pConfig, - QWidget* pParent) + QWidget* pParent, + TrackCollectionManager* pTrackCollectionManager) : WLabel(pParent), m_pGroup(group), - m_pConfig(std::move(pConfig)) { + m_pConfig(std::move(pConfig)), + m_pTrackCollectionManager(pTrackCollectionManager), + m_bPlaylistMenuLoaded(false) { setAcceptDrops(true); // Setup context menu m_pMenu = new QMenu(this); + m_pPlaylistMenu = new QMenu(this); + m_pPlaylistMenu->setTitle(tr("Add to Playlist")); + connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulatePlaylistMenu())); + + // Create all the context m_pMenu->actions (stuff that shows up when you + // right-click) createContextMenuActions(); } WTrackProperty::~WTrackProperty() { delete m_pMenu; delete m_pFileBrowserAct; + delete m_pPlaylistMenu; } void WTrackProperty::setup(const QDomNode& node, const SkinContext& context) { @@ -91,6 +109,48 @@ void WTrackProperty::slotOpenInFileBrowser() { mixxx::DesktopHelper::openInFileBrowser(QStringList(trackLocation)); } +void WTrackProperty::slotPopulatePlaylistMenu() { + // The user may open the Playlist submenu, move their cursor away, then + // return to the Playlist submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bPlaylistMenuLoaded) { + return; + } + m_pPlaylistMenu->clear(); + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + QMap playlists; + int numPlaylists = playlistDao.playlistCount(); + for (int i = 0; i < numPlaylists; ++i) { + int iPlaylistId = playlistDao.getPlaylistId(i); + playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); + } + QMapIterator it(playlists); + while (it.hasNext()) { + it.next(); + if (!playlistDao.isHidden(it.value())) { + // No leak because making the menu the parent means they will be + // auto-deleted + auto pAction = new QAction(it.key(), m_pPlaylistMenu); + bool locked = playlistDao.isPlaylistLocked(it.value()); + pAction->setEnabled(!locked); + m_pPlaylistMenu->addAction(pAction); + int iPlaylistId = it.value(); + connect(pAction, &QAction::triggered, + this, [this, iPlaylistId] { slotAddSelectionToPlaylist(iPlaylistId); }); + } + } + m_pPlaylistMenu->addSeparator(); + auto* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); + m_pPlaylistMenu->addAction(newPlaylistAction); + connect(newPlaylistAction, &QAction::triggered, + this, [this] { slotAddSelectionToPlaylist(-1); }); + m_bPlaylistMenuLoaded = true; +} + +void WTrackProperty::slotAddSelectionToPlaylist(int iPlaylistId) { + +} + void WTrackProperty::createContextMenuActions() { m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); connect(m_pFileBrowserAct, SIGNAL(triggered()), @@ -100,6 +160,7 @@ void WTrackProperty::createContextMenuActions() { void WTrackProperty::contextMenuEvent(QContextMenuEvent *event) { if (m_pCurrentTrack) { m_pMenu->addAction(m_pFileBrowserAct); + m_pMenu->addMenu(m_pPlaylistMenu); // Create the right-click menu m_pMenu->popup(event->globalPos()); diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index 04f48ed8953d..c966b36ce890 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -11,14 +11,20 @@ #include "widget/trackdroptarget.h" #include "widget/wlabel.h" +class TrackCollectionManager; + class WTrackProperty : public WLabel, public TrackDropTarget { Q_OBJECT public: - WTrackProperty(const char* group, UserSettingsPointer pConfig, QWidget* pParent); + WTrackProperty(const char* group, + UserSettingsPointer pConfig, + QWidget* pParent, + TrackCollectionManager* pTrackCollectionManager); ~WTrackProperty() override; void setup(const QDomNode& node, const SkinContext& context) override; void contextMenuEvent(QContextMenuEvent * event) override; + QList getSelectedTrackIds() const; signals: void trackDropped(QString filename, QString group) override; @@ -31,6 +37,8 @@ class WTrackProperty : public WLabel, public TrackDropTarget { private slots: void slotTrackChanged(TrackId); void slotOpenInFileBrowser(); + void slotPopulatePlaylistMenu(); + void slotAddSelectionToPlaylist(int iPlaylistId); private: void dragEnterEvent(QDragEnterEvent *event) override; @@ -47,8 +55,13 @@ class WTrackProperty : public WLabel, public TrackDropTarget { // Context menu machinery QMenu *m_pMenu; + QMenu *m_pPlaylistMenu; QAction *m_pFileBrowserAct; + + TrackCollectionManager* const m_pTrackCollectionManager; + + bool m_bPlaylistMenuLoaded; }; From fbcc980765d5b2a2cae17782b3383d113cb79bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 23:27:23 +0200 Subject: [PATCH 107/393] Use capital style with underscores for the Python constants --- scripts/line_length.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index be1d9b4f71df..a3129a62c341 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -8,8 +8,8 @@ # We recommend a maximum line length of 80, but do allow up to 100 characters # if deemed necessary by the developer. Lines that exceed that limit will # be wrapped after 80 characters automatically. -lineLengthTheshold = 100 -breakBefore = 80 +LINE_LENGTH_THRESHOLD = 100 +BREAK_BEFORE = 80 def main(argv: Optional[Sequence[str]] = None) -> int: @@ -21,7 +21,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: lineArguments = [] with open(filename) as fd: for lineno, line in enumerate(fd): - if len(line) <= lineLengthTheshold: + if len(line) <= LINE_LENGTH_THRESHOLD: continue humanlineno = lineno + 1 print(f"{filename}:{humanlineno} Line is too long.") @@ -37,7 +37,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: "IndentWidth: 4, " "TabWidth: 8, " "UseTab: Never," - " " + f"ColumnLimit: {breakBefore}, " + " " + f"ColumnLimit: {BREAK_BEFORE}, " # clang-format normally unbreaks all short lines, # which is undesired. # This does not happen here because line is too long From 5f601ce0f8749142a6921d4ffef46f2de4185cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 29 Mar 2020 23:39:43 +0200 Subject: [PATCH 108/393] Made the Palette editor a context depending pop up box for the palette combo boxes --- src/preferences/colorpaletteeditor.cpp | 117 +++++++-------------- src/preferences/colorpaletteeditor.h | 21 ++-- src/preferences/dialog/dlgprefcolors.cpp | 92 +++++++++++----- src/preferences/dialog/dlgprefcolors.h | 12 ++- src/preferences/dialog/dlgprefcolorsdlg.ui | 91 ++++------------ 5 files changed, 139 insertions(+), 194 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 717e158693b6..ad40f2527dfb 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -18,37 +18,27 @@ const QColor kDefaultPaletteColor(0, 0, 0); } ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) - : QWidget(parent), + : QDialog(parent), m_bPaletteExists(false), m_bPaletteIsReadOnly(false), - m_pPaletteTemplateComboBox(make_parented()), - m_pSaveAsComboBox(make_parented()), - m_pTableView(make_parented()), + m_pSaveAsEdit(make_parented(this)), + m_pTableView(make_parented(this)), m_pModel(make_parented(m_pTableView)) { - m_pSaveAsComboBox->setEditable(true); - - m_pResetButton = make_parented(tr("Reset"), this); - QDialogButtonBox* pButtonBox = new QDialogButtonBox(); m_pRemoveButton = pButtonBox->addButton( tr("Remove Palette"), QDialogButtonBox::DestructiveRole); + m_pCloseButton = pButtonBox->addButton(QDialogButtonBox::Discard); + m_pResetButton = pButtonBox->addButton(QDialogButtonBox::Reset); m_pSaveButton = pButtonBox->addButton(QDialogButtonBox::Save); - m_pCloseButton = pButtonBox->addButton(QDialogButtonBox::Close); - - QHBoxLayout* pTopLayout = new QHBoxLayout(); - pTopLayout->addWidget(new QLabel(tr("Name"))); - pTopLayout->addWidget(m_pSaveAsComboBox, 1); - QHBoxLayout* pBottomLayout = new QHBoxLayout(); - pBottomLayout->addWidget(new QLabel(tr("Reset to"))); - pBottomLayout->addWidget(m_pPaletteTemplateComboBox, 1); - pBottomLayout->addWidget(m_pResetButton.get()); + QHBoxLayout* pNameLayout = new QHBoxLayout(); + pNameLayout->addWidget(new QLabel(tr("Name"))); + pNameLayout->addWidget(m_pSaveAsEdit, 1); QVBoxLayout* pLayout = new QVBoxLayout(); - pLayout->addLayout(pTopLayout); pLayout->addWidget(m_pTableView, 1); - pLayout->addLayout(pBottomLayout); + pLayout->addLayout(pNameLayout); pLayout->addWidget(pButtonBox); setLayout(pLayout); setContentsMargins(0, 0, 0, 0); @@ -88,14 +78,10 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) &QTableView::customContextMenuRequested, this, &ColorPaletteEditor::slotTableViewContextMenuRequested); - connect(m_pSaveAsComboBox, - &QComboBox::currentTextChanged, + connect(m_pSaveAsEdit, + &QLineEdit::textChanged, this, &ColorPaletteEditor::slotPaletteNameChanged); - connect(m_pPaletteTemplateComboBox, - &QComboBox::currentTextChanged, - this, - &ColorPaletteEditor::slotUpdateButtons); connect(m_pResetButton, &QPushButton::clicked, this, @@ -114,46 +100,40 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) &ColorPaletteEditor::slotRemoveButtonClicked); } -void ColorPaletteEditor::initialize(UserSettingsPointer pConfig) { +void ColorPaletteEditor::initialize( + UserSettingsPointer pConfig, + const QString& paletteName) { DEBUG_ASSERT(!m_pConfig); m_pConfig = pConfig; - reset(); -} - -void ColorPaletteEditor::reset() { - m_pPaletteTemplateComboBox->clear(); - m_pSaveAsComboBox->clear(); + m_resetPalette = paletteName; + QString saveName = paletteName; for (const ColorPalette& palette : mixxx::PredefinedColorPalettes::kPalettes) { - m_pPaletteTemplateComboBox->addItem(palette.getName()); - } - - ColorPaletteSettings colorPaletteSettings(m_pConfig); - if (colorPaletteSettings.getColorPaletteNames().count()) { - for (const QString& paletteName : colorPaletteSettings.getColorPaletteNames()) { - m_pSaveAsComboBox->addItem(paletteName); - m_pPaletteTemplateComboBox->addItem(paletteName); + if (paletteName == palette.getName()) { + saveName = paletteName + QChar(' ') + tr("(Edited)"); + ColorPaletteSettings colorPaletteSettings(m_pConfig); + if (colorPaletteSettings.getColorPaletteNames().contains(saveName)) { + m_resetPalette = saveName; + } + break; } - QString current = m_pSaveAsComboBox->currentText(); - m_pPaletteTemplateComboBox->setCurrentText(current); - m_resetedPalette = current; - } else { - m_pSaveAsComboBox->addItem(tr("Custom Color Palette")); - slotResetButtonClicked(); } + + m_pSaveAsEdit->setText(saveName); + + slotResetButtonClicked(); } void ColorPaletteEditor::slotUpdateButtons() { bool bDirty = m_pModel->isDirty(); bool bEmpty = m_pModel->isEmpty(); m_pSaveButton->setEnabled( - !m_pSaveAsComboBox->currentText().isEmpty() && + !m_pSaveAsEdit->text().trimmed().isEmpty() && (!m_bPaletteExists || (!m_bPaletteIsReadOnly && bDirty && !bEmpty))); m_pRemoveButton->setEnabled( m_bPaletteExists && !m_bPaletteIsReadOnly); - m_pResetButton->setEnabled(bDirty || - m_resetedPalette != m_pPaletteTemplateComboBox->currentText()); + m_pResetButton->setEnabled(bDirty); } void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { @@ -213,7 +193,8 @@ void ColorPaletteEditor::slotPaletteNameChanged(const QString& text) { } } if (!bPaletteFound) { - m_pModel->setColorPalette(colorPaletteSettings.getColorPalette(text, mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette)); + m_pModel->setColorPalette(colorPaletteSettings.getColorPalette( + text, mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette)); } } } @@ -221,59 +202,35 @@ void ColorPaletteEditor::slotPaletteNameChanged(const QString& text) { m_bPaletteExists = bPaletteExists; m_bPaletteIsReadOnly = bPaletteIsReadOnly; - if (bPaletteExists && !bPaletteIsReadOnly) { - m_pPaletteTemplateComboBox->setCurrentText(text); - if (!m_pModel->isDirty()) { - m_resetedPalette = text; - } - } - slotUpdateButtons(); } void ColorPaletteEditor::slotCloseButtonClicked() { - if (m_pSaveButton->isEnabled()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Custom Palettes Editor")); - msgBox.setText(tr( - "The custom palette is not saved.\n" - "Close anyway?")); - msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - msgBox.setDefaultButton(QMessageBox::Cancel); - int ret = msgBox.exec(); - if (ret == QMessageBox::Ok) { - emit closeButtonClicked(); - } - } else { - emit closeButtonClicked(); - } + reject(); } void ColorPaletteEditor::slotRemoveButtonClicked() { - QString paletteName = m_pSaveAsComboBox->currentText(); + QString paletteName = m_pSaveAsEdit->text().trimmed(); ColorPaletteSettings colorPaletteSettings(m_pConfig); colorPaletteSettings.removePalette(paletteName); - reset(); emit paletteRemoved(paletteName); + accept(); } void ColorPaletteEditor::slotSaveButtonClicked() { - QString paletteName = m_pSaveAsComboBox->currentText(); + QString paletteName = m_pSaveAsEdit->text().trimmed(); ColorPaletteSettings colorPaletteSettings(m_pConfig); colorPaletteSettings.setColorPalette(paletteName, m_pModel->getColorPalette(paletteName)); m_pModel->setDirty(false); - reset(); - m_pSaveAsComboBox->setCurrentText(paletteName); emit paletteChanged(paletteName); + accept(); } void ColorPaletteEditor::slotResetButtonClicked() { - QString paletteName = m_pPaletteTemplateComboBox->currentText(); ColorPaletteSettings colorPaletteSettings(m_pConfig); ColorPalette palette = colorPaletteSettings.getColorPalette( - paletteName, + m_resetPalette, mixxx::PredefinedColorPalettes::kDefaultHotcueColorPalette); m_pModel->setColorPalette(palette); - m_resetedPalette = paletteName; slotUpdateButtons(); } diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index 4c52d4a26f21..4871f0c69075 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -1,26 +1,24 @@ #pragma once #include +#include +#include #include #include -#include #include "preferences/colorpaletteeditormodel.h" #include "preferences/usersettings.h" #include "util/parented_ptr.h" -// Widget for viewing, adding, editing and removing color palettes that can be -// used for track/hotcue colors. -class ColorPaletteEditor : public QWidget { +class ColorPaletteEditor : public QDialog { Q_OBJECT public: ColorPaletteEditor(QWidget* parent = nullptr); - void initialize(UserSettingsPointer pConfig); - void reset(); + void initialize(UserSettingsPointer pConfig, const QString& paletteName); signals: - void paletteChanged(QString name); - void paletteRemoved(QString name); + void paletteChanged(const QString& name); + void paletteRemoved(const QString& name); void closeButtonClicked(); private slots: @@ -38,13 +36,12 @@ class ColorPaletteEditor : public QWidget { bool m_bPaletteIsReadOnly; UserSettingsPointer m_pConfig; - parented_ptr m_pPaletteTemplateComboBox; - parented_ptr m_pSaveAsComboBox; + parented_ptr m_pSaveAsEdit; parented_ptr m_pTableView; parented_ptr m_pModel; QPushButton* m_pSaveButton; QPushButton* m_pCloseButton; QPushButton* m_pRemoveButton; - parented_ptr m_pResetButton; - QString m_resetedPalette; + QPushButton* m_pResetButton; + QString m_resetPalette; }; diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index d76a525570dd..240a4a9d897a 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -9,11 +9,12 @@ #include "util/color/predefinedcolorpalettes.h" #include "util/compatibility.h" #include "util/math.h" +#include "util/memory.h" namespace { constexpr int kHotcueDefaultColorIndex = -1; -constexpr QSize kPalettePreviewSize = QSize(200, 16); +constexpr QSize kPalettePreviewSize = QSize(108, 16); } // anonymous namespace @@ -23,36 +24,25 @@ DlgPrefColors::DlgPrefColors( m_pConfig(pConfig), m_colorPaletteSettings(ColorPaletteSettings(pConfig)) { setupUi(this); - colorPaletteEditor->initialize(pConfig); comboBoxHotcueColors->setIconSize(kPalettePreviewSize); comboBoxTrackColors->setIconSize(kPalettePreviewSize); - groupBoxPaletteEditor->hide(); - loadSettings(); - connect(colorPaletteEditor, - &ColorPaletteEditor::paletteChanged, - this, - &DlgPrefColors::palettesUpdated); - connect(colorPaletteEditor, - &ColorPaletteEditor::paletteRemoved, - this, - &DlgPrefColors::palettesUpdated); - connect(colorPaletteEditor, - &ColorPaletteEditor::closeButtonClicked, - this, - &DlgPrefColors::slotCloseClicked); - connect(comboBoxHotcueColors, QOverload::of(&QComboBox::currentIndexChanged), this, &DlgPrefColors::slotHotcuePaletteChanged); - connect(pushButtonEdit, + connect(pushButtonEditHotcuePalette, + &QPushButton::clicked, + this, + &DlgPrefColors::slotEditHotcuePaletteClicked); + + connect(pushButtonEditTrackPalette, &QPushButton::clicked, this, - &DlgPrefColors::slotEditClicked); + &DlgPrefColors::slotEditTrackPaletteClicked); } DlgPrefColors::~DlgPrefColors() { @@ -227,18 +217,62 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); } -void DlgPrefColors::slotEditClicked() { - pushButtonEdit->hide(); - labelCustomPalette->hide(); - widgetSpacer->hide(); - groupBoxPaletteEditor->show(); +void DlgPrefColors::slotEditTrackPaletteClicked() { + QString trackColorPaletteName = comboBoxTrackColors->currentText(); + openColorPaletteEditor(trackColorPaletteName, false); } -void DlgPrefColors::slotCloseClicked() { - groupBoxPaletteEditor->hide(); - widgetSpacer->show(); - pushButtonEdit->show(); - labelCustomPalette->show(); +void DlgPrefColors::slotEditHotcuePaletteClicked() { + QString hotcueColorPaletteName = comboBoxHotcueColors->currentText(); + openColorPaletteEditor(hotcueColorPaletteName, true); +} + +void DlgPrefColors::openColorPaletteEditor( + const QString& paletteName, + bool editHotcuePalette) { + std::unique_ptr pColorPaletteEditor = + std::make_unique(this); + + if (editHotcuePalette) { + connect(pColorPaletteEditor.get(), + &ColorPaletteEditor::paletteChanged, + this, + &DlgPrefColors::hotcuePaletteUpdated); + } else { + connect(pColorPaletteEditor.get(), + &ColorPaletteEditor::paletteChanged, + this, + &DlgPrefColors::trackPaletteUpdated); + } + connect(pColorPaletteEditor.get(), + &ColorPaletteEditor::paletteRemoved, + this, + &DlgPrefColors::palettesUpdated); + + pColorPaletteEditor->initialize(m_pConfig, paletteName); + pColorPaletteEditor->exec(); +} + +void DlgPrefColors::trackPaletteUpdated(const QString& trackColors) { + QString hotcueColors = comboBoxHotcueColors->currentText(); + int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); + + loadSettings(); + + comboBoxHotcueColors->setCurrentText(hotcueColors); + comboBoxTrackColors->setCurrentText(trackColors); + comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); +} + +void DlgPrefColors::hotcuePaletteUpdated(const QString& hotcueColors) { + QString trackColors = comboBoxTrackColors->currentText(); + int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); + + loadSettings(); + + comboBoxHotcueColors->setCurrentText(hotcueColors); + comboBoxTrackColors->setCurrentText(trackColors); + comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); } void DlgPrefColors::palettesUpdated() { diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index 01fe4d660dbe..739aafd95e60 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -3,10 +3,12 @@ #include #include "control/controlproxy.h" +#include "preferences/colorpaletteeditor.h" #include "preferences/colorpalettesettings.h" #include "preferences/dialog/ui_dlgprefcolorsdlg.h" #include "preferences/dlgpreferencepage.h" #include "preferences/usersettings.h" +#include "util/parented_ptr.h" class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { Q_OBJECT @@ -18,8 +20,6 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { // Apply changes to widget void slotApply(); void slotResetToDefaults(); - void slotEditClicked(); - void slotCloseClicked(); signals: void apply(const QString&); @@ -27,10 +27,16 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { private slots: void slotHotcuePaletteChanged(const QString& palette); void loadSettings(); + void trackPaletteUpdated(const QString& palette); + void hotcuePaletteUpdated(const QString& palette); void palettesUpdated(); + void slotEditTrackPaletteClicked(); + void slotEditHotcuePaletteClicked(); private: - void loadPaletteIntoEditor(const ColorPalette& palette); + void openColorPaletteEditor( + const QString& paletteName, + bool editHotcuePalette); QPixmap drawPalettePreview(const QString& paletteName); QIcon drawPaletteIcon(const QString& paletteName); diff --git a/src/preferences/dialog/dlgprefcolorsdlg.ui b/src/preferences/dialog/dlgprefcolorsdlg.ui index 2a27b65008ea..c9d3f25a3751 100644 --- a/src/preferences/dialog/dlgprefcolorsdlg.ui +++ b/src/preferences/dialog/dlgprefcolorsdlg.ui @@ -26,6 +26,16 @@ Colors + + + + + 0 + 0 + + + + @@ -47,48 +57,15 @@ - - - - Custom palettes - - - - - + + Edit… - - - - - 0 - 15 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + + 0 @@ -97,37 +74,19 @@ - - - - - 0 - 0 - + + + + + + + Edit… - - - - Custom Palettes Editor - - - false - - - false - - - - - - - - @@ -144,14 +103,6 @@ - - - ColorPaletteEditor - QWidget -
preferences/colorpaletteeditor.h
- 1 -
-
From 9f4d8188a1827dd5ff53b5ad7050d2b05e80a42a Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Mon, 30 Mar 2020 03:20:07 +0530 Subject: [PATCH 109/393] widget/wtrackproperty: implement add to playlist function --- src/widget/wtrackproperty.cpp | 47 ++++++++++++++++++++++++++++++++--- src/widget/wtrackproperty.h | 2 +- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index a57a7bc73202..94262c9a8541 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -136,19 +136,60 @@ void WTrackProperty::slotPopulatePlaylistMenu() { m_pPlaylistMenu->addAction(pAction); int iPlaylistId = it.value(); connect(pAction, &QAction::triggered, - this, [this, iPlaylistId] { slotAddSelectionToPlaylist(iPlaylistId); }); + this, [this, iPlaylistId] { slotAddToPlaylist(iPlaylistId); }); } } m_pPlaylistMenu->addSeparator(); auto* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); m_pPlaylistMenu->addAction(newPlaylistAction); connect(newPlaylistAction, &QAction::triggered, - this, [this] { slotAddSelectionToPlaylist(-1); }); + this, [this] { slotAddToPlaylist(-1); }); m_bPlaylistMenuLoaded = true; } -void WTrackProperty::slotAddSelectionToPlaylist(int iPlaylistId) { +void WTrackProperty::slotAddToPlaylist(int iPlaylistId) { + const TrackId trackId = m_pCurrentTrack->getId(); + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + + if (iPlaylistId == -1) { // i.e. a new playlist is suppose to be created + QString name; + bool validNameGiven = false; + + do { + bool ok = false; + name = QInputDialog::getText(nullptr, + tr("Create New Playlist"), + tr("Enter name for new playlist:"), + QLineEdit::Normal, + tr("New Playlist"), + &ok).trimmed(); + if (!ok) { + return; + } + if (playlistDao.getPlaylistIdFromName(name) != -1) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("A playlist by that name already exists.")); + } else if (name.isEmpty()) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("A playlist cannot have a blank name.")); + } else { + validNameGiven = true; + } + } while (!validNameGiven); + iPlaylistId = playlistDao.createPlaylist(name);//-1 is changed to the new playlist ID return from the DAO + if (iPlaylistId == -1) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("An unknown error occurred while creating playlist: ") + +name); + return; + } + } + + playlistDao.appendTrackToPlaylist(trackId, iPlaylistId); } void WTrackProperty::createContextMenuActions() { diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index c966b36ce890..e3d15f230120 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -38,7 +38,7 @@ class WTrackProperty : public WLabel, public TrackDropTarget { void slotTrackChanged(TrackId); void slotOpenInFileBrowser(); void slotPopulatePlaylistMenu(); - void slotAddSelectionToPlaylist(int iPlaylistId); + void slotAddToPlaylist(int iPlaylistId); private: void dragEnterEvent(QDragEnterEvent *event) override; From e9c2d34fe8dcadcf80c2aa22d0c9574ce70f54a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 00:55:34 +0200 Subject: [PATCH 110/393] Seti inital color for QColorDialog --- src/preferences/colorpaletteeditor.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index ad40f2527dfb..2cd90ecd1d08 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -138,9 +138,11 @@ void ColorPaletteEditor::slotUpdateButtons() { void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { if (index.isValid() && index.column() == 0) { - QColor color = QColorDialog::getColor(); - if (color.isValid()) { - m_pModel->setColor(index.row(), color); + QStandardItem* pColorItem = m_pModel->item(index.row(), 0); + QColor oldColor = QColor(pColorItem->text()); + QColor newColor = QColorDialog::getColor(oldColor); + if (newColor.isValid() && oldColor != newColor) { + m_pModel->setColor(index.row(), newColor); } } } From 313db52b970b42dab5eb0c74e669697799d5dc57 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Mon, 30 Mar 2020 04:37:13 +0530 Subject: [PATCH 111/393] widget/wtrackproperty: implement add to crate menu --- src/widget/wtrackproperty.cpp | 109 +++++++++++++++++++++++++++++++++- src/widget/wtrackproperty.h | 5 ++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 94262c9a8541..76859893ad03 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -4,12 +4,15 @@ #include #include #include +#include +#include // std includes #include // Project includes #include "control/controlobject.h" +#include #include "library/dao/playlistdao.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" @@ -26,7 +29,8 @@ WTrackProperty::WTrackProperty(const char* group, m_pGroup(group), m_pConfig(std::move(pConfig)), m_pTrackCollectionManager(pTrackCollectionManager), - m_bPlaylistMenuLoaded(false) { + m_bPlaylistMenuLoaded(false), + m_bCrateMenuLoaded(false) { setAcceptDrops(true); // Setup context menu @@ -35,6 +39,10 @@ WTrackProperty::WTrackProperty(const char* group, m_pPlaylistMenu->setTitle(tr("Add to Playlist")); connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), this, SLOT(slotPopulatePlaylistMenu())); + m_pCrateMenu = new QMenu(this); + m_pCrateMenu->setTitle(tr("Crates")); + connect(m_pCrateMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulateCrateMenu())); // Create all the context m_pMenu->actions (stuff that shows up when you // right-click) @@ -45,6 +53,7 @@ WTrackProperty::~WTrackProperty() { delete m_pMenu; delete m_pFileBrowserAct; delete m_pPlaylistMenu; + delete m_pCrateMenu; } void WTrackProperty::setup(const QDomNode& node, const SkinContext& context) { @@ -192,6 +201,100 @@ void WTrackProperty::slotAddToPlaylist(int iPlaylistId) { playlistDao.appendTrackToPlaylist(trackId, iPlaylistId); } +void WTrackProperty::slotPopulateCrateMenu() { + // The user may open the Crate submenu, move their cursor away, then + // return to the Crate submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bCrateMenuLoaded) { + return; + } + m_pCrateMenu->clear(); + const auto trackId = m_pCurrentTrack->getId(); + QList trackIds; + trackIds.push_back(trackId); + + CrateSummarySelectResult allCrates(m_pTrackCollectionManager->internalCollection()->crates().selectCratesWithTrackCount(trackIds)); + + CrateSummary crate; + while (allCrates.populateNext(&crate)) { + auto pAction = make_parented(m_pCrateMenu); + auto pCheckBox = make_parented(m_pCrateMenu); + + pCheckBox->setText(crate.getName()); + pCheckBox->setProperty("crateId", + QVariant::fromValue(crate.getId())); + pCheckBox->setEnabled(!crate.isLocked()); + + pAction->setEnabled(!crate.isLocked()); + pAction->setDefaultWidget(pCheckBox.get()); + + pCheckBox->setChecked(crate.getTrackCount() != 0); + + m_pCrateMenu->addAction(pAction.get()); + connect(pAction.get(), &QAction::triggered, + this, [this, pCheckBox{pCheckBox.get()}] { slotUpdateSelectionCrates(pCheckBox); }); + connect(pCheckBox.get(), &QCheckBox::stateChanged, + this, [this, pCheckBox{pCheckBox.get()}] { slotUpdateSelectionCrates(pCheckBox); }); + } + m_pCrateMenu->addSeparator(); + auto* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); + m_pCrateMenu->addAction(newCrateAction); + connect(newCrateAction, SIGNAL(triggered()), this, SLOT(slotAddSelectionToNewCrate())); + m_bCrateMenuLoaded = true; +} + +void WTrackProperty::slotUpdateSelectionCrates(QWidget *pWidget) { + auto pCheckBox = qobject_cast(pWidget); + VERIFY_OR_DEBUG_ASSERT(pCheckBox) { + qWarning() << "crateId is not of CrateId type"; + return; + } + auto crateId = pCheckBox->property("crateId").value(); + + const auto trackId = m_pCurrentTrack->getId(); + QList trackIds; + trackIds.push_back(trackId); + + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for crate"; + return; + } + + if(!pCheckBox->isChecked()) { + if (crateId.isValid()) { + m_pTrackCollectionManager->internalCollection()->removeCrateTracks(crateId, trackIds); + } + } else { + if (!crateId.isValid()) { // i.e. a new crate is suppose to be created + crateId = CrateFeatureHelper( + m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); + } + if (crateId.isValid()) { + m_pTrackCollectionManager->unhideTracks(trackIds); + m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); + } + } +} + +void WTrackProperty::slotAddSelectionToNewCrate() { + auto trackId = m_pCurrentTrack->getId(); + QList trackIds; + trackIds.push_back(trackId); + + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for crate"; + return; + } + + CrateId crateId = CrateFeatureHelper( + m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); + + if (crateId.isValid()) { + m_pTrackCollectionManager->unhideTracks(trackIds); + m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); + } +} + void WTrackProperty::createContextMenuActions() { m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); connect(m_pFileBrowserAct, SIGNAL(triggered()), @@ -200,8 +303,10 @@ void WTrackProperty::createContextMenuActions() { void WTrackProperty::contextMenuEvent(QContextMenuEvent *event) { if (m_pCurrentTrack) { - m_pMenu->addAction(m_pFileBrowserAct); m_pMenu->addMenu(m_pPlaylistMenu); + m_pMenu->addMenu(m_pCrateMenu); + m_pMenu->addSeparator(); + m_pMenu->addAction(m_pFileBrowserAct); // Create the right-click menu m_pMenu->popup(event->globalPos()); diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index e3d15f230120..01ed163735cb 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -39,6 +39,9 @@ class WTrackProperty : public WLabel, public TrackDropTarget { void slotOpenInFileBrowser(); void slotPopulatePlaylistMenu(); void slotAddToPlaylist(int iPlaylistId); + void slotPopulateCrateMenu(); + void slotUpdateSelectionCrates(QWidget* pWidget); + void slotAddSelectionToNewCrate(); private: void dragEnterEvent(QDragEnterEvent *event) override; @@ -56,12 +59,14 @@ class WTrackProperty : public WLabel, public TrackDropTarget { // Context menu machinery QMenu *m_pMenu; QMenu *m_pPlaylistMenu; + QMenu *m_pCrateMenu; QAction *m_pFileBrowserAct; TrackCollectionManager* const m_pTrackCollectionManager; bool m_bPlaylistMenuLoaded; + bool m_bCrateMenuLoaded; }; From 131de5f4a3ab43b3a737ed93fde8754a27f673fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nino=20Mi=C5=A1ki=C4=87-Pletenac?= Date: Sun, 29 Mar 2020 03:15:20 +0200 Subject: [PATCH 112/393] Prevent jump when track analysis finishes while quantization is enabled --- src/engine/controls/cuecontrol.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index b0b9a06372db..0b638185af4d 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -433,13 +433,19 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { seekExact(0.0); } break; - case SeekOnLoadMode::MainCue: - if (mainCuePoint.getPosition() != Cue::kNoPosition) { - seekExact(mainCuePoint.getPosition()); + case SeekOnLoadMode::MainCue: { + // Take main cue position from CO instead of cue point list because + // value in CO will be quantized if quantization is enabled + // while value in cue point list will never be quantized. + // This prevents jumps when track analysis finishes while quantization is enabled. + double cuePoint = m_pCuePoint->get(); + if (cuePoint != Cue::kNoPosition) { + seekExact(cuePoint); } else { seekExact(0.0); } break; + } case SeekOnLoadMode::IntroStart: { double introStart = m_pIntroStartPosition->get(); if (introStart != Cue::kNoPosition) { From efe411f0bcb95f6cb710fbeb3a60cf7bccbee2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nino=20Mi=C5=A1ki=C4=87-Pletenac?= Date: Sun, 29 Mar 2020 18:24:00 +0200 Subject: [PATCH 113/393] Fetch audible start position only when it is needed --- src/engine/controls/cuecontrol.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 0b638185af4d..a870b8444ad8 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -412,12 +412,6 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { // Seek track according to SeekOnLoadMode. SeekOnLoadMode seekOnLoadMode = getSeekOnLoadPreference(); - CuePointer pAudibleSound = pNewTrack->findCueByType(mixxx::CueType::AudibleSound); - double firstSound = Cue::kNoPosition; - if (pAudibleSound) { - firstSound = pAudibleSound->getPosition(); - } - switch (seekOnLoadMode) { case SeekOnLoadMode::Beginning: // This allows users to load tracks and have the needle-drop be maintained. @@ -426,13 +420,15 @@ void CueControl::trackLoaded(TrackPointer pNewTrack) { seekExact(0.0); } break; - case SeekOnLoadMode::FirstSound: - if (firstSound != Cue::kNoPosition) { - seekExact(firstSound); + case SeekOnLoadMode::FirstSound: { + CuePointer pAudibleSound = pNewTrack->findCueByType(mixxx::CueType::AudibleSound); + if (pAudibleSound && pAudibleSound->getPosition() != Cue::kNoPosition) { + seekExact(pAudibleSound->getPosition()); } else { seekExact(0.0); } break; + } case SeekOnLoadMode::MainCue: { // Take main cue position from CO instead of cue point list because // value in CO will be quantized if quantization is enabled From 74b47c3e151fbd5729e8e91714a6b4b92075b04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 01:03:24 +0200 Subject: [PATCH 114/393] Don't translate parentheses --- src/preferences/colorpaletteeditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 2cd90ecd1d08..75a326cbd996 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -110,7 +110,7 @@ void ColorPaletteEditor::initialize( for (const ColorPalette& palette : mixxx::PredefinedColorPalettes::kPalettes) { if (paletteName == palette.getName()) { - saveName = paletteName + QChar(' ') + tr("(Edited)"); + saveName = paletteName + QStringLiteral(" (") + tr("Edited") + QChar(')'); ColorPaletteSettings colorPaletteSettings(m_pConfig); if (colorPaletteSettings.getColorPaletteNames().contains(saveName)) { m_resetPalette = saveName; From 441b0693bc68cb69047e186ccbcc0a978c2246bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 01:32:47 +0200 Subject: [PATCH 115/393] Adjust default index when it is out set the new palette selected --- src/preferences/dialog/dlgprefcolors.cpp | 30 ++++++++++++++++-------- src/preferences/dialog/dlgprefcolors.h | 4 ++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 240a4a9d897a..d373a15878f5 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -214,7 +214,12 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { comboBoxHotcueDefaultColor->setItemIcon(i + 1, QIcon(pixmap)); } - comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); + if (comboBoxHotcueDefaultColor->count() > defaultColor) { + comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); + } else { + comboBoxHotcueDefaultColor->setCurrentIndex( + comboBoxHotcueDefaultColor->count() - 1); + } } void DlgPrefColors::slotEditTrackPaletteClicked() { @@ -258,10 +263,7 @@ void DlgPrefColors::trackPaletteUpdated(const QString& trackColors) { int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); loadSettings(); - - comboBoxHotcueColors->setCurrentText(hotcueColors); - comboBoxTrackColors->setCurrentText(trackColors); - comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); + restoreComboBoxed(hotcueColors, trackColors, defaultColor); } void DlgPrefColors::hotcuePaletteUpdated(const QString& hotcueColors) { @@ -269,10 +271,7 @@ void DlgPrefColors::hotcuePaletteUpdated(const QString& hotcueColors) { int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); loadSettings(); - - comboBoxHotcueColors->setCurrentText(hotcueColors); - comboBoxTrackColors->setCurrentText(trackColors); - comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); + restoreComboBoxed(hotcueColors, trackColors, defaultColor); } void DlgPrefColors::palettesUpdated() { @@ -281,8 +280,19 @@ void DlgPrefColors::palettesUpdated() { int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); loadSettings(); + restoreComboBoxed(hotcueColors, trackColors, defaultColor); +} +void DlgPrefColors::restoreComboBoxed( + const QString& hotcueColors, + const QString& trackColors, + int defaultColor) { comboBoxHotcueColors->setCurrentText(hotcueColors); comboBoxTrackColors->setCurrentText(trackColors); - comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); + if (comboBoxHotcueDefaultColor->count() > defaultColor) { + comboBoxHotcueDefaultColor->setCurrentIndex(defaultColor); + } else { + comboBoxHotcueDefaultColor->setCurrentIndex( + comboBoxHotcueDefaultColor->count() - 1); + } } diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index 739aafd95e60..eddc07cc6de8 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -39,6 +39,10 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { bool editHotcuePalette); QPixmap drawPalettePreview(const QString& paletteName); QIcon drawPaletteIcon(const QString& paletteName); + void restoreComboBoxed( + const QString& hotcueColors, + const QString& trackColors, + int defaultColor); const UserSettingsPointer m_pConfig; ColorPaletteSettings m_colorPaletteSettings; From f1a7c73b6056850955d15fa14d956f90bbad16ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 01:36:58 +0200 Subject: [PATCH 116/393] fix typo --- src/preferences/dialog/dlgprefcolors.cpp | 8 ++++---- src/preferences/dialog/dlgprefcolors.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index d373a15878f5..7fb144d0ced9 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -263,7 +263,7 @@ void DlgPrefColors::trackPaletteUpdated(const QString& trackColors) { int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); loadSettings(); - restoreComboBoxed(hotcueColors, trackColors, defaultColor); + restoreComboBoxes(hotcueColors, trackColors, defaultColor); } void DlgPrefColors::hotcuePaletteUpdated(const QString& hotcueColors) { @@ -271,7 +271,7 @@ void DlgPrefColors::hotcuePaletteUpdated(const QString& hotcueColors) { int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); loadSettings(); - restoreComboBoxed(hotcueColors, trackColors, defaultColor); + restoreComboBoxes(hotcueColors, trackColors, defaultColor); } void DlgPrefColors::palettesUpdated() { @@ -280,10 +280,10 @@ void DlgPrefColors::palettesUpdated() { int defaultColor = comboBoxHotcueDefaultColor->currentIndex(); loadSettings(); - restoreComboBoxed(hotcueColors, trackColors, defaultColor); + restoreComboBoxes(hotcueColors, trackColors, defaultColor); } -void DlgPrefColors::restoreComboBoxed( +void DlgPrefColors::restoreComboBoxes( const QString& hotcueColors, const QString& trackColors, int defaultColor) { diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index eddc07cc6de8..caec1a0d7ab2 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -39,7 +39,7 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { bool editHotcuePalette); QPixmap drawPalettePreview(const QString& paletteName); QIcon drawPaletteIcon(const QString& paletteName); - void restoreComboBoxed( + void restoreComboBoxes( const QString& hotcueColors, const QString& trackColors, int defaultColor); From 92d6ce908d68f963202d559e3e8b904abda929cc Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Mon, 30 Mar 2020 06:13:25 +0530 Subject: [PATCH 117/393] widget/wtrackproperty: add styles for QMenu --- res/skins/Tango/style.qss | 43 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/res/skins/Tango/style.qss b/res/skins/Tango/style.qss index fd41769564f3..84404a2e3876 100644 --- a/res/skins/Tango/style.qss +++ b/res/skins/Tango/style.qss @@ -35,6 +35,7 @@ WCueMenuPopup, WCueMenuPopup QMenu, WCueMenuPopup QLabel, WCoverArtMenu, +WTrackProperty QMenu, #LibraryContainer QMenu QCheckBox, QToolTip, #LibraryContainer QHeaderView { @@ -2080,11 +2081,14 @@ WCueMenuPopup, WCueMenuPopup QMenu, WCueMenuPopup QLabel, WCoverArtMenu, +WTrackProperty QMenu, #LibraryContainer QMenu::item, WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item, +WTrackProperty QMenu::item, #LibraryContainer QMenu QCheckBox, +WTrackProperty QMenu QCheckBox, #SkinSettings, WEffectSelector, WEffectSelector QAbstractScrollArea, #fadeModeCombobox, #fadeModeCombobox QAbstractScrollArea, @@ -2098,6 +2102,7 @@ WBeatSpinBox QMenu, WCueMenuPopup, WCueMenuPopup QMenu, WCoverArtMenu, +WTrackProperty QMenu, #SkinSettings, WEffectSelector QAbstractScrollArea, #fadeModeCombobox QAbstractScrollArea { @@ -2115,6 +2120,7 @@ WEffectSelector QAbstractScrollArea, WBeatSpinBox QMenu::separator, WCueMenuPopup QMenu::separator, WCoverArtMenu::separator, + WTrackProperty QMenu::separator, WSpinny QMenu::separator { height: 0px; border-top: 1px solid #000; @@ -2125,6 +2131,8 @@ WEffectSelector QAbstractScrollArea, WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, WCoverArtMenu::item, + WTrackProperty QMenu::item, + WTrackProperty QMenu QCheckBox, #LibraryContainer QMenu QCheckBox, #SkinSettingsLabel, #SkinSettingsButton, @@ -2137,13 +2145,15 @@ WEffectSelector QAbstractScrollArea, #LibraryContainer QMenu::item, WBeatSpinBox QMenu::item, WCueMenuPopup QMenu::item, - WCoverArtMenu::item { + WCoverArtMenu::item, + WTrackProperty QMenu::item { /* right padding creates a margin to the menu expand arrow, left padding should be bigger than menu icon width + menu icon margin */ padding: 5px 12px 5px 25px; } - #LibraryContainer QMenu QCheckBox { + #LibraryContainer QMenu QCheckBox, + WTrackProperty QMenu QCheckBox { padding: 2px 10px 2px 3px; } /* icons in editline menu (searchbox, editable track properties) @@ -2153,16 +2163,21 @@ WEffectSelector QAbstractScrollArea, WCueMenuPopup QMenu::icon, /* checkbox in Crate name context menu: "[ ] Auto DJ Track Source" */ - #LibraryContainer QMenu::indicator { + #LibraryContainer QMenu::indicator, + WTrackProperty QMenu::indicator { margin: 0px 4px 0px 2px; } #LibraryContainer QMenu::item:selected, WBeatSpinBox QMenu::item:selected, WCueMenuPopup QMenu::item:selected, WCoverArtMenu::item:selected, + WTrackProperty QMenu::item:selected, #LibraryContainer QMenu QCheckBox:selected, WBeatSpinBox QMenu QCheckBox:focus, /* selected by keyboard */ #LibraryContainer QMenu QCheckBox:hover, /* mouse hover */ + WTrackProperty QMenu QCheckBox:selected, + WTrackProperty QMenu QCheckBox:focus, + WTrackProperty QMenu QCheckBox:hover, #SkinSettingsButton:hover, #SkinSettingsCategoryButton:hover, WEffectSelector::item:selected, @@ -2173,7 +2188,9 @@ WEffectSelector QAbstractScrollArea, outline: none; } #LibraryContainer QMenu QCheckBox::indicator, -#LibraryContainer QMenu::indicator { +WTrackProperty QMenu QCheckBox::indicator, +#LibraryContainer QMenu::indicator, +WTrackProperty QMenu::indicator{ width: 13px; height: 13px; background-color: #0f0f0f; @@ -2183,16 +2200,20 @@ WEffectSelector QAbstractScrollArea, outline: none; } #LibraryContainer QMenu QCheckBox::indicator:checked, - #LibraryContainer QMenu::indicator:checked { + WTrackProperty QMenu QCheckBox::indicator:checked, + #LibraryContainer QMenu::indicator:checked, + WTrackProperty QMenu::indicator:checked { image: url(skin:/buttons/btn_lib_checkmark.svg); border: 1px solid #1e1e1e; } /* disabled menu checkbox */ - #LibraryContainer QMenu QCheckBox::indicator:!enabled { + #LibraryContainer QMenu QCheckBox::indicator:!enabled, + WTrackProperty QMenu QCheckBox::indicator:!enabled { background-color: #333; border: 1px solid #222; } - #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked { + #LibraryContainer QMenu QCheckBox::indicator:!enabled:checked, + WTrackProperty QMenu QCheckBox::indicator:!enabled:checked { image: url(skin:/buttons/btn_lib_checkmark_grey.svg); } @@ -2200,6 +2221,8 @@ WEffectSelector QAbstractScrollArea, WBeatSpinBox QMenu::item:!enabled, WCueMenuPopup QMenu::item:!enabled, WCoverArtMenu::item:!enabled, +WTrackProperty QMenu::item:!enabled, +WTrackProperty QMenu QCheckBox:!enabled, #LibraryContainer QMenu QCheckBox:!enabled { color: #555; } @@ -2211,13 +2234,15 @@ WCoverArtMenu::item:!enabled, #LibraryContainer QCheckBox::indicator:indeterminate:!enabled { image: url(skin:/buttons/btn_lib_checkmark_grey.svg); } -#LibraryContainer QMenu::right-arrow { +#LibraryContainer QMenu::right-arrow, +WTrackProperty QMenu::right-arrow { width: 8px; height: 16px; margin-right: 4px; image: url(skin:/buttons/btn_arrow_right.svg); } -#LibraryContainer QMenu::right-arrow { +#LibraryContainer QMenu::right-arrow, +WTrackProperty QMenu::right-arrow { image: url(skin:/buttons/btn_arrow_right_hover.svg); } From 989d33556bd04da77daf9c4d425f906048ce66db Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 29 Mar 2020 19:53:34 -0500 Subject: [PATCH 118/393] ColorPaletteEditor: move Add/Remove Color to buttons instead of a right click menu where they were not easily discoverable --- src/preferences/colorpaletteeditor.cpp | 63 ++++++++++++++------------ src/preferences/colorpaletteeditor.h | 5 +- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 75a326cbd996..e6514976326b 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -24,22 +24,39 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) m_pSaveAsEdit(make_parented(this)), m_pTableView(make_parented(this)), m_pModel(make_parented(m_pTableView)) { - QDialogButtonBox* pButtonBox = new QDialogButtonBox(); - m_pRemoveButton = pButtonBox->addButton( - tr("Remove Palette"), - QDialogButtonBox::DestructiveRole); - m_pCloseButton = pButtonBox->addButton(QDialogButtonBox::Discard); - m_pResetButton = pButtonBox->addButton(QDialogButtonBox::Reset); - m_pSaveButton = pButtonBox->addButton(QDialogButtonBox::Save); + // Create widgets + QHBoxLayout* pColorButtonLayout = new QHBoxLayout(); + m_pAddColorButton = new QPushButton(tr("Add Color"), this); + pColorButtonLayout->addWidget(m_pAddColorButton); + connect(m_pAddColorButton, + &QPushButton::clicked, + this, + &ColorPaletteEditor::slotAddColor); + m_pRemoveColorButton = new QPushButton(tr("Remove Color"), this); + pColorButtonLayout->addWidget(m_pRemoveColorButton); + connect(m_pRemoveColorButton, + &QPushButton::clicked, + this, + &ColorPaletteEditor::slotRemoveColor); QHBoxLayout* pNameLayout = new QHBoxLayout(); pNameLayout->addWidget(new QLabel(tr("Name"))); pNameLayout->addWidget(m_pSaveAsEdit, 1); + QDialogButtonBox* pPaletteButtonBox = new QDialogButtonBox(); + m_pRemoveButton = pPaletteButtonBox->addButton( + tr("Remove Palette"), + QDialogButtonBox::DestructiveRole); + m_pCloseButton = pPaletteButtonBox->addButton(QDialogButtonBox::Discard); + m_pResetButton = pPaletteButtonBox->addButton(QDialogButtonBox::Reset); + m_pSaveButton = pPaletteButtonBox->addButton(QDialogButtonBox::Save); + + // Add widgets to dialog QVBoxLayout* pLayout = new QVBoxLayout(); + pLayout->addLayout(pColorButtonLayout); pLayout->addWidget(m_pTableView, 1); pLayout->addLayout(pNameLayout); - pLayout->addWidget(pButtonBox); + pLayout->addWidget(pPaletteButtonBox); setLayout(pLayout); setContentsMargins(0, 0, 0, 0); @@ -74,10 +91,6 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) &QTableView::doubleClicked, this, &ColorPaletteEditor::slotTableViewDoubleClicked); - connect(m_pTableView, - &QTableView::customContextMenuRequested, - this, - &ColorPaletteEditor::slotTableViewContextMenuRequested); connect(m_pSaveAsEdit, &QLineEdit::textChanged, this, @@ -147,24 +160,18 @@ void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { } } -void ColorPaletteEditor::slotTableViewContextMenuRequested(const QPoint& pos) { - QMenu menu(this); - - QAction* pAddAction = menu.addAction("Add"); - QAction* pRemoveAction = menu.addAction("Remove"); - QAction* pAction = menu.exec(m_pTableView->viewport()->mapToGlobal(pos)); - if (pAction == pAddAction) { - m_pModel->appendRow(kDefaultPaletteColor); - } else if (pAction == pRemoveAction) { - QModelIndexList selection = m_pTableView->selectionModel()->selectedRows(); +void ColorPaletteEditor::slotAddColor() { + m_pModel->appendRow(kDefaultPaletteColor); +} - if (selection.count() > 0) { - QModelIndex index = selection.at(0); +void ColorPaletteEditor::slotRemoveColor() { + QModelIndexList selection = m_pTableView->selectionModel()->selectedRows(); - //row selected - int row = index.row(); - m_pModel->removeRow(row); - } + if (selection.count() > 0) { + QModelIndex index = selection.at(0); + //row selected + int row = index.row(); + m_pModel->removeRow(row); } } diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index 4871f0c69075..abc812e29c63 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -24,7 +24,8 @@ class ColorPaletteEditor : public QDialog { private slots: void slotUpdateButtons(); void slotTableViewDoubleClicked(const QModelIndex& index); - void slotTableViewContextMenuRequested(const QPoint& pos); + void slotAddColor(); + void slotRemoveColor(); void slotPaletteNameChanged(const QString& text); void slotCloseButtonClicked(); void slotSaveButtonClicked(); @@ -39,6 +40,8 @@ class ColorPaletteEditor : public QDialog { parented_ptr m_pSaveAsEdit; parented_ptr m_pTableView; parented_ptr m_pModel; + QPushButton* m_pAddColorButton; + QPushButton* m_pRemoveColorButton; QPushButton* m_pSaveButton; QPushButton* m_pCloseButton; QPushButton* m_pRemoveButton; From 2a8f635685ff4dd0b27678223c82958a2dfd3801 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 29 Mar 2020 20:11:39 -0500 Subject: [PATCH 119/393] ColorPaletteEditor: hide hotcue number column for track palette It has no meaning in this context. --- src/preferences/colorpaletteeditor.cpp | 6 +++++- src/preferences/colorpaletteeditor.h | 2 +- src/preferences/dialog/dlgprefcolors.cpp | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 75a326cbd996..f9a491f41f95 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -17,7 +17,7 @@ namespace { const QColor kDefaultPaletteColor(0, 0, 0); } -ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) +ColorPaletteEditor::ColorPaletteEditor(QWidget* parent, bool showHotcueNumbers) : QDialog(parent), m_bPaletteExists(false), m_bPaletteIsReadOnly(false), @@ -70,6 +70,10 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) m_pTableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); m_pTableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + if (!showHotcueNumbers) { + m_pTableView->hideColumn(1); + } + connect(m_pTableView, &QTableView::doubleClicked, this, diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index 4871f0c69075..9996a2f1a9a4 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -13,7 +13,7 @@ class ColorPaletteEditor : public QDialog { Q_OBJECT public: - ColorPaletteEditor(QWidget* parent = nullptr); + ColorPaletteEditor(QWidget* parent = nullptr, bool showHotcueNumbers = true); void initialize(UserSettingsPointer pConfig, const QString& paletteName); signals: diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 7fb144d0ced9..7aa7e350da83 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -236,7 +236,7 @@ void DlgPrefColors::openColorPaletteEditor( const QString& paletteName, bool editHotcuePalette) { std::unique_ptr pColorPaletteEditor = - std::make_unique(this); + std::make_unique(this, editHotcuePalette); if (editHotcuePalette) { connect(pColorPaletteEditor.get(), From e7873a96eaf039250454f2602e28d9308fec3045 Mon Sep 17 00:00:00 2001 From: OsZ <58949409+toszlanyi@users.noreply.github.com> Date: Mon, 30 Mar 2020 07:01:37 +0200 Subject: [PATCH 120/393] cleaned unnecessary loop --- res/controllers/Denon-MC7000-scripts.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 1a90df137f94..f26d246d9419 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -531,9 +531,7 @@ MC7000.PadButtons = function(channel, control, value, status, group) { midi.sendShortMsg(0x94 + deckNumber -1, control, MC7000.padColor.slicerJumpFwd); } } else { - for (i = 0; i < 8; i++) { - midi.sendShortMsg(0x94 + deckNumber -1, control, MC7000.padColor.sliceron); - } + midi.sendShortMsg(0x94 + deckNumber -1, control, MC7000.padColor.sliceron); } } else if (MC7000.PADModeSlicerLoop[deckNumber]) { return; From 6e66413614e1f8e5bdd0661bfa1350cea10f35d5 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 21 Mar 2020 01:11:20 +0100 Subject: [PATCH 121/393] Upgrade Travis CI Ubuntu images from Xenial to Bionic --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b51f5d3657d4..705a359078c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ jobs: - name: pre-commit if: type != pull_request os: linux - dist: xenial + dist: bionic language: python python: 3.7 # There are too many files in the repo that have formatting issues. We'll @@ -39,7 +39,7 @@ jobs: - name: pre-commit-pr if: type == pull_request os: linux - dist: xenial + dist: bionic language: python python: 3.7 cache: @@ -53,9 +53,9 @@ jobs: - name: Ubuntu/gcc/SCons build os: linux - dist: xenial + dist: bionic compiler: gcc - # Ubuntu Xenial build prerequisites + # Ubuntu Bionic build prerequisites before_install: - sudo apt-get install -y scons install: @@ -68,10 +68,10 @@ jobs: - name: Ubuntu/gcc/CMake build os: linux - dist: xenial + dist: bionic compiler: gcc cache: ccache - # Ubuntu Xenial build prerequisites + # Ubuntu Bionic build prerequisites env: CMAKEFLAGS_EXTRA="-DLOCALECOMPARE=ON" before_install: - export CMAKE_BUILD_PARALLEL_LEVEL="$(nproc)" From 829ec47831e22bb0afae953faaafd59e24aca608 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 21 Mar 2020 11:28:54 +0100 Subject: [PATCH 122/393] Add types for properties of PCM audio signals and streams --- CMakeLists.txt | 3 + build/depends.py | 4 + src/audio/signalinfo.cpp | 27 ++++++ src/audio/signalinfo.h | 78 ++++++++++++++++ src/audio/streaminfo.cpp | 27 ++++++ src/audio/streaminfo.h | 64 +++++++++++++ src/audio/types.cpp | 45 ++++++++++ src/audio/types.h | 188 +++++++++++++++++++++++++++++++++++++++ src/mixxxapplication.cpp | 12 ++- src/util/duration.h | 39 ++++---- src/util/macros.h | 4 +- src/util/optional.h | 11 +++ 12 files changed, 478 insertions(+), 24 deletions(-) create mode 100644 src/audio/signalinfo.cpp create mode 100644 src/audio/signalinfo.h create mode 100644 src/audio/streaminfo.cpp create mode 100644 src/audio/streaminfo.h create mode 100644 src/audio/types.cpp create mode 100644 src/audio/types.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5053489dca0d..ed9f31a04b33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,9 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/analyzer/plugins/analyzersoundtouchbeats.cpp src/analyzer/plugins/buffering_utils.cpp src/analyzer/trackanalysisscheduler.cpp + src/audio/types.cpp + src/audio/signalinfo.cpp + src/audio/streaminfo.cpp src/control/control.cpp src/control/controlaudiotaperpot.cpp src/control/controlbehavior.cpp diff --git a/build/depends.py b/build/depends.py index cc6572f84521..0f114321971e 100644 --- a/build/depends.py +++ b/build/depends.py @@ -902,6 +902,10 @@ def sources(self, build): "src/analyzer/plugins/analyzerqueenmarykey.cpp", "src/analyzer/plugins/buffering_utils.cpp", + "src/audio/types.cpp", + "src/audio/signalinfo.cpp", + "src/audio/streaminfo.cpp", + "src/controllers/controller.cpp", "src/controllers/controllerdebug.cpp", "src/controllers/controllerengine.cpp", diff --git a/src/audio/signalinfo.cpp b/src/audio/signalinfo.cpp new file mode 100644 index 000000000000..a86f44903347 --- /dev/null +++ b/src/audio/signalinfo.cpp @@ -0,0 +1,27 @@ +#include "audio/signalinfo.h" + +namespace mixxx { + +namespace audio { + +bool operator==( + const SignalInfo& lhs, + const SignalInfo& rhs) { + return lhs.getChannelCount() == rhs.getChannelCount() && + lhs.getSampleLayout() == rhs.getSampleLayout() && + lhs.getSampleRate() == rhs.getSampleRate(); +} + +QDebug +operator<<(QDebug dbg, const SignalInfo& arg) { + dbg << "SignalInfo{"; + arg.dbgChannelCount(dbg); + arg.dbgSampleLayout(dbg); + arg.dbgSampleRate(dbg); + dbg << '}'; + return dbg; +} + +} // namespace audio + +} // namespace mixxx diff --git a/src/audio/signalinfo.h b/src/audio/signalinfo.h new file mode 100644 index 000000000000..d2e8ffeddf93 --- /dev/null +++ b/src/audio/signalinfo.h @@ -0,0 +1,78 @@ +#pragma once + +#include "audio/types.h" +#include "util/assert.h" +#include "util/macros.h" +#include "util/optional.h" + +namespace mixxx { + +namespace audio { + +// Properties that characterize an uncompressed PCM audio signal. +class SignalInfo final { + // Properties + PROPERTY_SET_BYVAL_GET_BYREF(ChannelCount, channelCount, ChannelCount) + PROPERTY_SET_BYVAL_GET_BYREF(SampleRate, sampleRate, SampleRate) + PROPERTY_SET_BYVAL_GET_BYREF(OptionalSampleLayout, sampleLayout, SampleLayout) + + public: + constexpr SignalInfo() = default; + constexpr explicit SignalInfo( + OptionalSampleLayout sampleLayout) + : m_sampleLayout(sampleLayout) { + } + SignalInfo( + ChannelCount channelCount, + SampleRate sampleRate, + OptionalSampleLayout sampleLayout = std::nullopt) + : m_channelCount(channelCount), + m_sampleRate(sampleRate), + m_sampleLayout(sampleLayout) { + } + SignalInfo(SignalInfo&&) = default; + SignalInfo(const SignalInfo&) = default; + /*non-virtual*/ ~SignalInfo() = default; + + constexpr bool isValid() const { + return getChannelCount().isValid() && + getSampleLayout() && + getSampleRate().isValid(); + } + + SignalInfo& operator=(SignalInfo&&) = default; + SignalInfo& operator=(const SignalInfo&) = default; + + // Conversion: #samples / sample offset -> #frames / frame offset + template + inline T samples2frames(T samples) const { + DEBUG_ASSERT(getChannelCount().isValid()); + DEBUG_ASSERT(0 == (samples % getChannelCount())); + return samples / getChannelCount(); + } + + // Conversion: #frames / frame offset -> #samples / sample offset + template + inline T frames2samples(T frames) const { + DEBUG_ASSERT(getChannelCount().isValid()); + return frames * getChannelCount(); + } +}; + +bool operator==( + const SignalInfo& lhs, + const SignalInfo& rhs); + +inline bool operator!=( + const SignalInfo& lhs, + const SignalInfo& rhs) { + return !(lhs == rhs); +} + +QDebug operator<<(QDebug dbg, const SignalInfo& arg); + +} // namespace audio + +} // namespace mixxx + +Q_DECLARE_METATYPE(mixxx::audio::SignalInfo) diff --git a/src/audio/streaminfo.cpp b/src/audio/streaminfo.cpp new file mode 100644 index 000000000000..d97c2c113cef --- /dev/null +++ b/src/audio/streaminfo.cpp @@ -0,0 +1,27 @@ +#include "audio/streaminfo.h" + +namespace mixxx { + +namespace audio { + +bool operator==( + const StreamInfo& lhs, + const StreamInfo& rhs) { + return lhs.getSignalInfo() == rhs.getSignalInfo() && + lhs.getBitrate() == rhs.getBitrate() && + lhs.getDuration() == rhs.getDuration(); +} + +QDebug +operator<<(QDebug dbg, const StreamInfo& arg) { + dbg << "StreamInfo{"; + arg.dbgSignalInfo(dbg); + arg.dbgBitrate(dbg); + arg.dbgDuration(dbg); + dbg << '}'; + return dbg; +} + +} // namespace audio + +} // namespace mixxx diff --git a/src/audio/streaminfo.h b/src/audio/streaminfo.h new file mode 100644 index 000000000000..8a70a6bd391b --- /dev/null +++ b/src/audio/streaminfo.h @@ -0,0 +1,64 @@ +#pragma once + +#include "audio/signalinfo.h" +#include "util/duration.h" + +namespace mixxx { + +namespace audio { + +// Properties that characterize a (compressed) PCM audio stream. +// +// Currently we assume that every stream has a finite duration +// that is known upfront! +class StreamInfo final { + // Properties + PROPERTY_SET_BYVAL_GET_BYREF(SignalInfo, signalInfo, SignalInfo) + PROPERTY_SET_BYVAL_GET_BYREF(Bitrate, bitrate, Bitrate) + PROPERTY_SET_BYVAL_GET_BYREF(Duration, duration, Duration) + + public: + constexpr StreamInfo() = default; + constexpr explicit StreamInfo( + const SignalInfo& signalInfo) + : m_signalInfo(signalInfo) { + } + constexpr StreamInfo( + const SignalInfo& signalInfo, + Bitrate bitrate, + Duration duration) + : m_signalInfo(signalInfo), + m_bitrate(bitrate), + m_duration(duration) { + } + StreamInfo(StreamInfo&&) = default; + StreamInfo(const StreamInfo&) = default; + /*non-virtual*/ ~StreamInfo() = default; + + constexpr bool isValid() const { + return getSignalInfo().isValid() && + getBitrate().isValid() && + (getDuration() > Duration::empty()); + } + + StreamInfo& operator=(StreamInfo&&) = default; + StreamInfo& operator=(const StreamInfo&) = default; +}; + +bool operator==( + const StreamInfo& lhs, + const StreamInfo& rhs); + +inline bool operator!=( + const StreamInfo& lhs, + const StreamInfo& rhs) { + return !(lhs == rhs); +} + +QDebug operator<<(QDebug dbg, const StreamInfo& arg); + +} // namespace audio + +} // namespace mixxx + +Q_DECLARE_METATYPE(mixxx::audio::StreamInfo) diff --git a/src/audio/types.cpp b/src/audio/types.cpp new file mode 100644 index 000000000000..f7addcc954d3 --- /dev/null +++ b/src/audio/types.cpp @@ -0,0 +1,45 @@ +#include "audio/types.h" + +namespace mixxx { + +namespace audio { + +QDebug operator<<(QDebug dbg, ChannelLayout arg) { + switch (arg) { + case ChannelLayout::Mono: + return dbg << "Mono"; + case ChannelLayout::DualMono: + return dbg << "DualMono"; + case ChannelLayout::Stereo: + return dbg << "Stereo"; + } + DEBUG_ASSERT(!"unreachable code"); + return dbg; +} + +QDebug operator<<(QDebug dbg, SampleLayout arg) { + switch (arg) { + case SampleLayout::Planar: + return dbg << "Planar"; + case SampleLayout::Interleaved: + return dbg << "Interleaved"; + } + DEBUG_ASSERT(!"unreachable code"); + return dbg; +} + +QDebug operator<<(QDebug dbg, SampleRate arg) { + return dbg + << QString::number(arg).toLocal8Bit().constData() + << SampleRate::unit(); +} + +QDebug operator<<(QDebug dbg, Bitrate arg) { + return dbg + << QString::number(arg).toLocal8Bit().constData() + << Bitrate::unit(); +} + +} // namespace audio + +} // namespace mixxx diff --git a/src/audio/types.h b/src/audio/types.h new file mode 100644 index 000000000000..497fa2102e69 --- /dev/null +++ b/src/audio/types.h @@ -0,0 +1,188 @@ +#pragma once + +#include + +#include "util/assert.h" +#include "util/optional.h" +#include "util/types.h" + +// Various properties of digital PCM audio signals and streams. +// +// An audio signal or stream contains samples for multiple +// channels sampled at discrete times. +// +// The channel layout (optional) assigns meaning to the +// different channels of a signal. +// +// The sample layout defines how subsequent samples from +// different channels are represented and stored in memory. + +namespace mixxx { + +namespace audio { + +enum class ChannelLayout { + Mono, // 1 channel + DualMono, // 2 channels with identical signals + Stereo, // 2 independent channels left/right + // ...to be continued... +}; + +typedef std::optional OptionalChannelLayout; + +QDebug operator<<(QDebug dbg, ChannelLayout arg); + +class ChannelCount { + private: + static constexpr SINT kValueDefault = 0; + + public: + static constexpr SINT kValueMin = 1; // lower bound (inclusive) + static constexpr SINT kValueMax = 255; // upper bound (inclusive, 8-bit unsigned integer) + + static constexpr ChannelCount min() { + return ChannelCount(kValueMin); + } + static constexpr ChannelCount max() { + return ChannelCount(kValueMax); + } + + static ChannelCount fromLayout(ChannelLayout layout) { + switch (layout) { + case ChannelLayout::Mono: + return ChannelCount(1); + case ChannelLayout::DualMono: + return ChannelCount(1); + case ChannelLayout::Stereo: + return ChannelCount(2); + } + DEBUG_ASSERT(!"unreachable code"); + } + + explicit constexpr ChannelCount(SINT value = kValueDefault) + : m_value(value) { + } + explicit ChannelCount(ChannelLayout layout) + : m_value(fromLayout(layout).m_value) { + } + + constexpr bool isValid() const { + return (kValueMin <= m_value) && + (m_value <= kValueMax); + } + + /*implicit*/ constexpr operator SINT() const { + return m_value; + } + + private: + SINT m_value; +}; + +// Defines the ordering of how samples from multiple channels are +// stored in contiguous buffers: +// - Planar: Channel by channel +// - Interleaved: Frame by frame +// The samples from all channels that are coincident in time are +// called a "frame" (or more specific "sample frame"). +// +// Example: 10 stereo samples from left (L) and right (R) channel +// Planar layout: LLLLLLLLLLRRRRRRRRRR +// Interleaved layout: LRLRLRLRLRLRLRLRLRLR +enum class SampleLayout { + Planar, + Interleaved +}; + +typedef std::optional OptionalSampleLayout; + +QDebug operator<<(QDebug dbg, SampleLayout arg); + +class SampleRate { + private: + static constexpr SINT kValueDefault = 0; + + public: + static constexpr SINT kValueMin = 8000; // lower bound (inclusive, = minimum MP3 sample rate) + static constexpr SINT kValueMax = 192000; // upper bound (inclusive) + + static constexpr SampleRate min() { + return SampleRate(kValueMin); + } + static constexpr SampleRate max() { + return SampleRate(kValueMax); + } + + static constexpr const char* unit() { + return "Hz"; + } + + explicit constexpr SampleRate(SINT value = kValueDefault) + : m_value(value) { + } + + constexpr bool isValid() const { + return (kValueMin <= m_value) && + (m_value <= kValueMax); + } + + /*implicit*/ constexpr operator SINT() const { + return m_value; + } + + private: + SINT m_value; +}; + +QDebug operator<<(QDebug dbg, SampleRate arg); + +// The bitrate is measured in kbit/s (kbps) and provides information +// about the level of compression for lossily encoded audio streams. +// It depends on the metadata and decoder if a value for the bitrate +// is available, i.e. it might be invalid if it cannot be determined. +class Bitrate { + private: + static constexpr SINT kValueDefault = 0; + + public: + static constexpr const char* unit() { + return "kbps"; + } + + explicit constexpr Bitrate(SINT value = kValueDefault) + : m_value(value) { + } + + constexpr bool isValid() const { + return m_value > kValueDefault; + } + + /*implicit*/ operator SINT() const { + DEBUG_ASSERT(m_value >= kValueDefault); // unsigned value + return m_value; + } + + private: + SINT m_value; +}; + +QDebug operator<<(QDebug dbg, Bitrate arg); + +} // namespace audio + +} // namespace mixxx + +Q_DECLARE_TYPEINFO(mixxx::audio::ChannelCount, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::ChannelCount) + +Q_DECLARE_TYPEINFO(mixxx::audio::OptionalChannelLayout, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::OptionalChannelLayout) + +Q_DECLARE_TYPEINFO(mixxx::audio::OptionalSampleLayout, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::OptionalSampleLayout) + +Q_DECLARE_TYPEINFO(mixxx::audio::SampleRate, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::SampleRate) + +Q_DECLARE_TYPEINFO(mixxx::audio::Bitrate, Q_PRIMITIVE_TYPE); +Q_DECLARE_METATYPE(mixxx::audio::Bitrate) diff --git a/src/mixxxapplication.cpp b/src/mixxxapplication.cpp index 412c342cc150..51349d4ca5e6 100644 --- a/src/mixxxapplication.cpp +++ b/src/mixxxapplication.cpp @@ -4,6 +4,7 @@ #include "mixxxapplication.h" +#include "audio/types.h" #include "control/controlproxy.h" #include "library/crate/crateid.h" #include "soundio/soundmanagerutil.h" @@ -55,7 +56,16 @@ MixxxApplication::~MixxxApplication() { } void MixxxApplication::registerMetaTypes() { - // Register custom data types for signal processing + // Register custom data types + + // PCM audio types + qRegisterMetaType("mixxx::audio::ChannelCount"); + qRegisterMetaType("mixxx::audio::OptionalChannelLayout"); + qRegisterMetaType("mixxx::audio::OptionalSampleLayout"); + qRegisterMetaType("mixxx::audio::SampleRate"); + qRegisterMetaType("mixxx::audio::Bitrate"); + + // TrackId qRegisterMetaType(); qRegisterMetaType>(); qRegisterMetaType>(); diff --git a/src/util/duration.h b/src/util/duration.h index 0d933f9032b2..fae49b50dcd1 100644 --- a/src/util/duration.h +++ b/src/util/duration.h @@ -1,5 +1,4 @@ -#ifndef MIXXX_UTIL_DURATION_H -#define MIXXX_UTIL_DURATION_H +#pragma once #include #include @@ -23,43 +22,43 @@ class DurationBase { }; // Returns the duration as an integer number of seconds (rounded-down). - qint64 toIntegerSeconds() const { + constexpr qint64 toIntegerSeconds() const { return m_durationNanos / kNanosPerSecond; } // Returns the duration as a floating point number of seconds. - double toDoubleSeconds() const { + constexpr double toDoubleSeconds() const { return static_cast(m_durationNanos) / kNanosPerSecond; } // Returns the duration as an integer number of milliseconds (rounded-down). - qint64 toIntegerMillis() const { + constexpr qint64 toIntegerMillis() const { return m_durationNanos / kNanosPerMilli; } // Returns the duration as a floating point number of milliseconds. - double toDoubleMillis() const { + constexpr double toDoubleMillis() const { return static_cast(m_durationNanos) / kNanosPerMilli; } // Returns the duration as an integer number of microseconds (rounded-down). - qint64 toIntegerMicros() const { + constexpr qint64 toIntegerMicros() const { return m_durationNanos / kNanosPerMicro; } // Returns the duration as a floating point number of microseconds. - double toDoubleMicros() const { + constexpr double toDoubleMicros() const { return static_cast(m_durationNanos) / kNanosPerMicro; } // Returns the duration as an integer number of nanoseconds. The duration is // represented internally as nanoseconds so no rounding occurs. - qint64 toIntegerNanos() const { + constexpr qint64 toIntegerNanos() const { return m_durationNanos; } // Returns the duration as an integer number of nanoseconds. - double toDoubleNanos() const { + constexpr double toDoubleNanos() const { return static_cast(m_durationNanos); } @@ -96,7 +95,7 @@ class DurationBase { static QChar kDecimalSeparator; protected: - explicit DurationBase(qint64 durationNanos) + explicit constexpr DurationBase(qint64 durationNanos) : m_durationNanos(durationNanos) { } @@ -105,7 +104,7 @@ class DurationBase { class DurationDebug : public DurationBase { public: - DurationDebug(const DurationBase& duration, Units unit) + constexpr DurationDebug(const DurationBase& duration, Units unit) : DurationBase(duration), m_unit(unit) { } @@ -136,30 +135,30 @@ class Duration : public DurationBase { public: // Returns a Duration object representing a duration of 'seconds'. template - static Duration fromSeconds(T seconds) { + static constexpr Duration fromSeconds(T seconds) { return Duration(seconds * kNanosPerSecond); } // Returns a Duration object representing a duration of 'millis'. - static Duration fromMillis(qint64 millis) { + static constexpr Duration fromMillis(qint64 millis) { return Duration(millis * kNanosPerMilli); } // Returns a Duration object representing a duration of 'micros'. - static Duration fromMicros(qint64 micros) { + static constexpr Duration fromMicros(qint64 micros) { return Duration(micros * kNanosPerMicro); } // Returns a Duration object representing a duration of 'nanos'. - static Duration fromNanos(qint64 nanos) { + static constexpr Duration fromNanos(qint64 nanos) { return Duration(nanos); } - static Duration empty() { + static constexpr Duration empty() { return Duration(); } - Duration() + constexpr Duration() : DurationBase(0) { } @@ -265,7 +264,7 @@ class Duration : public DurationBase { } private: - explicit Duration(qint64 durationNanos) + explicit constexpr Duration(qint64 durationNanos) : DurationBase(durationNanos) { } }; @@ -274,5 +273,3 @@ class Duration : public DurationBase { Q_DECLARE_TYPEINFO(mixxx::Duration, Q_MOVABLE_TYPE); Q_DECLARE_METATYPE(mixxx::Duration) - -#endif /* MIXXX_UTIL_DURATION_H */ diff --git a/src/util/macros.h b/src/util/macros.h index accda62304a2..080c1391844e 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -14,7 +14,7 @@ // classes. #define PROPERTY_SET_BYVAL_GET_BYREF(TYPE, NAME, CAP_NAME) \ public: void set##CAP_NAME(TYPE NAME) { m_##NAME = std::move(NAME); } \ -public: TYPE const& get##CAP_NAME() const { return m_##NAME; } \ -public: TYPE& ref##CAP_NAME() { return m_##NAME; } \ +public: constexpr TYPE const& get##CAP_NAME() const { return m_##NAME; } \ +public: constexpr TYPE& ref##CAP_NAME() { return m_##NAME; } \ public: QDebug dbg##CAP_NAME(QDebug dbg) const { return dbg << #NAME ":" << m_##NAME; } \ private: TYPE m_##NAME; diff --git a/src/util/optional.h b/src/util/optional.h index 5767794f3668..6fe9139b0c3a 100644 --- a/src/util/optional.h +++ b/src/util/optional.h @@ -22,3 +22,14 @@ using std::experimental::optional; } // namespace std #endif + +#include + +template +QDebug operator<<(QDebug dbg, std::optional arg) { + if (arg) { + return dbg << *arg; + } else { + return dbg << "nullopt"; + } +} From b384921bfa4400685d729d10db46c2a59ce3752a Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 21 Mar 2020 12:02:52 +0100 Subject: [PATCH 123/393] Use new PCM audio property types --- src/analyzer/constants.h | 2 +- src/effects/effectchain.cpp | 2 +- .../bufferscalers/enginebufferscale.cpp | 27 ++- src/engine/bufferscalers/enginebufferscale.h | 13 +- .../bufferscalers/enginebufferscalelinear.cpp | 10 +- .../bufferscalers/enginebufferscalelinear.h | 2 + .../enginebufferscalerubberband.cpp | 35 ++-- .../enginebufferscalerubberband.h | 4 +- .../bufferscalers/enginebufferscalest.cpp | 46 ++--- .../bufferscalers/enginebufferscalest.h | 13 +- .../cachingreader/cachingreaderchunk.cpp | 2 +- src/engine/cachingreader/cachingreaderchunk.h | 2 +- src/engine/effects/engineeffect.cpp | 4 +- src/engine/engine.h | 27 +-- src/engine/enginebuffer.cpp | 14 +- src/engine/enginedelay.cpp | 2 +- src/library/autodj/autodjprocessor.cpp | 2 +- src/library/dao/trackdao.cpp | 83 +++++---- src/library/dlgtrackinfo.cpp | 2 +- src/mixer/basetrackplayer.cpp | 9 +- src/sources/audiosource.cpp | 12 +- src/sources/audiosource.h | 61 +++---- src/sources/soundsourceffmpeg.cpp | 18 +- src/sources/soundsourceffmpeg.h | 4 +- src/sources/soundsourcemediafoundation.cpp | 2 +- src/sources/soundsourcemodplug.cpp | 7 +- src/sources/soundsourcemp3.cpp | 42 ++--- src/sources/soundsourceopus.cpp | 21 ++- src/sources/soundsourceproxy.cpp | 14 +- src/test/analyserwaveformtest.cpp | 6 +- src/test/analyzersilence_test.cpp | 8 +- src/test/autodjprocessor_test.cpp | 2 +- src/test/beatgridtest.cpp | 32 ++-- src/test/beatmaptest.cpp | 13 +- src/test/bpmcontrol_test.cpp | 10 +- src/test/cue_test.cpp | 10 +- src/test/cuecontrol_test.cpp | 11 +- src/test/enginebufferscalelineartest.cpp | 2 +- src/test/mockedenginebackendtest.h | 2 + src/test/nativeeffects_test.cpp | 3 +- src/test/searchqueryparsertest.cpp | 45 +++-- src/test/trackmetadata_test.cpp | 14 +- src/track/albuminfo.cpp | 2 +- src/track/albuminfo.h | 2 +- src/track/cue.cpp | 12 +- src/track/cue.h | 4 +- src/track/track.cpp | 100 +++++++++-- src/track/track.h | 25 ++- src/track/trackinfo.cpp | 2 +- src/track/trackinfo.h | 2 +- src/track/trackmetadata.cpp | 88 +++++++++- src/track/trackmetadata.h | 30 ++-- src/track/trackmetadatataglib.cpp | 11 +- src/util/audiosignal.cpp | 45 ++--- src/util/audiosignal.h | 164 +++--------------- 55 files changed, 582 insertions(+), 545 deletions(-) diff --git a/src/analyzer/constants.h b/src/analyzer/constants.h index 468ed741ed2e..c936de1515c2 100644 --- a/src/analyzer/constants.h +++ b/src/analyzer/constants.h @@ -9,7 +9,7 @@ namespace mixxx { // depending on the track length. A block size of 4096 frames per block // seems to do fine. Signal processing during analysis uses the same, // fixed number of channels like the engine does, usually 2 = stereo. -constexpr mixxx::AudioSignal::ChannelCount kAnalysisChannels = mixxx::kEngineChannelCount; +constexpr audio::ChannelCount kAnalysisChannels = mixxx::kEngineChannelCount; constexpr SINT kAnalysisFramesPerChunk = 4096; constexpr SINT kAnalysisSamplesPerChunk = kAnalysisFramesPerChunk * kAnalysisChannels; diff --git a/src/effects/effectchain.cpp b/src/effects/effectchain.cpp index 3dd1ac7310e1..c24442a90f5e 100644 --- a/src/effects/effectchain.cpp +++ b/src/effects/effectchain.cpp @@ -183,7 +183,7 @@ void EffectChain::enableForInputChannel(const ChannelHandleAndGroup& handle_grou //TODO: get actual configuration of engine const mixxx::EngineParameters bufferParameters( - mixxx::AudioSignal::SampleRate(96000), + mixxx::audio::SampleRate(96000), MAX_BUFFER_LEN / mixxx::kEngineChannelCount); for (int i = 0; i < m_effects.size(); ++i) { diff --git a/src/engine/bufferscalers/enginebufferscale.cpp b/src/engine/bufferscalers/enginebufferscale.cpp index 8e3f977ad0c8..b250c48f37eb 100644 --- a/src/engine/bufferscalers/enginebufferscale.cpp +++ b/src/engine/bufferscalers/enginebufferscale.cpp @@ -2,27 +2,26 @@ #include "engine/engine.h" #include "util/defs.h" -#include "util/sample.h" EngineBufferScale::EngineBufferScale() : m_audioSignal( - mixxx::AudioSignal::SampleLayout::Interleaved, - mixxx::AudioSignal::ChannelCount(mixxx::kEngineChannelCount), - mixxx::AudioSignal::SampleRate(44100)), + mixxx::audio::SignalInfo( + mixxx::kEngineChannelCount, + mixxx::audio::SampleRate(), + mixxx::kEngineSampleLayout)), m_dBaseRate(1.0), m_bSpeedAffectsPitch(false), m_dTempoRatio(1.0), m_dPitchRatio(1.0) { - DEBUG_ASSERT(m_audioSignal.verifyReadable()); + DEBUG_ASSERT(!m_audioSignal.isValid()); } -EngineBufferScale::~EngineBufferScale() { -} - -void EngineBufferScale::setSampleRate(SINT iSampleRate) { - m_audioSignal = mixxx::AudioSignal( - m_audioSignal.sampleLayout(), - m_audioSignal.channelCount(), - mixxx::AudioSignal::SampleRate(iSampleRate)); - DEBUG_ASSERT(m_audioSignal.verifyReadable()); +void EngineBufferScale::setSampleRate( + mixxx::audio::SampleRate sampleRate) { + DEBUG_ASSERT(sampleRate.isValid()); + if (sampleRate != m_audioSignal.getSampleRate()) { + m_audioSignal.setSampleRate(sampleRate); + onSampleRateChanged(); + } + DEBUG_ASSERT(m_audioSignal.isValid()); } diff --git a/src/engine/bufferscalers/enginebufferscale.h b/src/engine/bufferscalers/enginebufferscale.h index 609cf54fb583..f8ba5d827ed6 100644 --- a/src/engine/bufferscalers/enginebufferscale.h +++ b/src/engine/bufferscalers/enginebufferscale.h @@ -3,7 +3,7 @@ #include -#include "util/audiosignal.h" +#include "audio/signalinfo.h" // MAX_SEEK_SPEED needs to be good and high to allow room for the very high // instantaneous velocities of advanced scratching (Uzi) and spin-backs. @@ -24,7 +24,7 @@ class EngineBufferScale : public QObject { Q_OBJECT public: EngineBufferScale(); - virtual ~EngineBufferScale(); + ~EngineBufferScale() override = default; // Sets the scaling parameters. // * The base rate (ratio of track sample rate to output sample rate). @@ -48,9 +48,10 @@ class EngineBufferScale : public QObject { } // Set the desired output sample rate. - virtual void setSampleRate(SINT iSampleRate); + void setSampleRate( + mixxx::audio::SampleRate sampleRate); - const mixxx::AudioSignal& getAudioSignal() const { + const mixxx::audio::SignalInfo& getAudioSignal() const { return m_audioSignal; } @@ -68,7 +69,9 @@ class EngineBufferScale : public QObject { SINT iOutputBufferSize) = 0; private: - mixxx::AudioSignal m_audioSignal; + mixxx::audio::SignalInfo m_audioSignal; + + virtual void onSampleRateChanged() = 0; protected: double m_dBaseRate; diff --git a/src/engine/bufferscalers/enginebufferscalelinear.cpp b/src/engine/bufferscalers/enginebufferscalelinear.cpp index c632158b202c..e268cfee044f 100644 --- a/src/engine/bufferscalers/enginebufferscalelinear.cpp +++ b/src/engine/bufferscalers/enginebufferscalelinear.cpp @@ -92,12 +92,12 @@ double EngineBufferScaleLinear::scaleBuffer( // if the buffer has extra samples, do a read so RAMAN ends up back where // it should be SINT iCurSample = getAudioSignal().frames2samples(static_cast(ceil(m_dCurrentFrame))); - SINT extra_samples = m_bufferIntSize - iCurSample - getAudioSignal().channelCount(); + SINT extra_samples = m_bufferIntSize - iCurSample - getAudioSignal().getChannelCount(); if (extra_samples > 0) { - if (extra_samples % getAudioSignal().channelCount() != 0) { + if (extra_samples % getAudioSignal().getChannelCount() != 0) { // extra samples should include the whole frame - extra_samples -= extra_samples % getAudioSignal().channelCount(); - extra_samples += getAudioSignal().channelCount(); + extra_samples -= extra_samples % getAudioSignal().getChannelCount(); + extra_samples += getAudioSignal().getChannelCount(); } //qDebug() << "extra samples" << extra_samples; @@ -339,7 +339,7 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { // samples. This prevents the change from being discontinuous and helps // improve sound quality. rate_add += rate_delta_abs; - i += getAudioSignal().channelCount(); + i += getAudioSignal().getChannelCount(); } SampleUtil::clear(&buf[i], buf_size - i); diff --git a/src/engine/bufferscalers/enginebufferscalelinear.h b/src/engine/bufferscalers/enginebufferscalelinear.h index f30a951db8fe..d46f342ba697 100644 --- a/src/engine/bufferscalers/enginebufferscalelinear.h +++ b/src/engine/bufferscalers/enginebufferscalelinear.h @@ -24,6 +24,8 @@ class EngineBufferScaleLinear : public EngineBufferScale { double* pPitchRatio) override; private: + void onSampleRateChanged() override {} + SINT do_scale(CSAMPLE* buf, SINT buf_size); SINT do_copy(CSAMPLE* buf, SINT buf_size); diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.cpp b/src/engine/bufferscalers/enginebufferscalerubberband.cpp index c17973804d27..c403d16bf6d8 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.cpp +++ b/src/engine/bufferscalers/enginebufferscalerubberband.cpp @@ -28,7 +28,6 @@ EngineBufferScaleRubberBand::EngineBufferScaleRubberBand( m_bBackwards(false) { m_retrieve_buffer[0] = SampleUtil::alloc(MAX_BUFFER_LEN); m_retrieve_buffer[1] = SampleUtil::alloc(MAX_BUFFER_LEN); - initRubberBand(); } EngineBufferScaleRubberBand::~EngineBufferScaleRubberBand() { @@ -37,19 +36,6 @@ EngineBufferScaleRubberBand::~EngineBufferScaleRubberBand() { SampleUtil::free(m_retrieve_buffer[1]); } -void EngineBufferScaleRubberBand::initRubberBand() { - m_pRubberBand = std::make_unique( - getAudioSignal().sampleRate(), - getAudioSignal().channelCount(), - RubberBandStretcher::OptionProcessRealTime); - m_pRubberBand->setMaxProcessSize(kRubberBandBlockSize); - // Setting the time ratio to a very high value will cause RubberBand - // to preallocate buffers large enough to (almost certainly) - // avoid memory reallocations during playback. - m_pRubberBand->setTimeRatio(2.0); - m_pRubberBand->setTimeRatio(1.0); -} - void EngineBufferScaleRubberBand::setScaleParameters(double base_rate, double* pTempoRatio, double* pPitchRatio) { @@ -111,12 +97,27 @@ void EngineBufferScaleRubberBand::setScaleParameters(double base_rate, m_dPitchRatio = *pPitchRatio; } -void EngineBufferScaleRubberBand::setSampleRate(SINT iSampleRate) { - EngineBufferScale::setSampleRate(iSampleRate); - initRubberBand(); +void EngineBufferScaleRubberBand::onSampleRateChanged() { + if (!getAudioSignal().isValid()) { + m_pRubberBand.reset(); + return; + } + m_pRubberBand = std::make_unique( + getAudioSignal().getSampleRate(), + getAudioSignal().getChannelCount(), + RubberBandStretcher::OptionProcessRealTime); + m_pRubberBand->setMaxProcessSize(kRubberBandBlockSize); + // Setting the time ratio to a very high value will cause RubberBand + // to preallocate buffers large enough to (almost certainly) + // avoid memory reallocations during playback. + m_pRubberBand->setTimeRatio(2.0); + m_pRubberBand->setTimeRatio(1.0); } void EngineBufferScaleRubberBand::clear() { + VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) { + return; + } m_pRubberBand->reset(); } diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.h b/src/engine/bufferscalers/enginebufferscalerubberband.h index 53d07370a7e7..7ee14e084c2c 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.h +++ b/src/engine/bufferscalers/enginebufferscalerubberband.h @@ -22,8 +22,6 @@ class EngineBufferScaleRubberBand : public EngineBufferScale { double* pTempoRatio, double* pPitchRatio) override; - void setSampleRate(SINT iSampleRate) override; - double scaleBuffer( CSAMPLE* pOutputBuffer, SINT iOutputBufferSize) override; @@ -33,7 +31,7 @@ class EngineBufferScaleRubberBand : public EngineBufferScale { private: // Reset RubberBand library with new audio signal - void initRubberBand(); + void onSampleRateChanged() override; void deinterleaveAndProcess(const CSAMPLE* pBuffer, SINT frames, bool flush); SINT retrieveAndDeinterleave(CSAMPLE* pBuffer, SINT frames); diff --git a/src/engine/bufferscalers/enginebufferscalest.cpp b/src/engine/bufferscalers/enginebufferscalest.cpp index 59a19a8af8a4..4e62432e599b 100644 --- a/src/engine/bufferscalers/enginebufferscalest.cpp +++ b/src/engine/bufferscalers/enginebufferscalest.cpp @@ -29,27 +29,14 @@ const SINT kSeekOffsetFrames = 519; EngineBufferScaleST::EngineBufferScaleST(ReadAheadManager *pReadAheadManager) : m_pReadAheadManager(pReadAheadManager), m_pSoundTouch(std::make_unique()), - buffer_back_size(getAudioSignal().frames2samples(kSeekOffsetFrames)), - buffer_back(SampleUtil::alloc(buffer_back_size)), m_bBackwards(false) { - DEBUG_ASSERT(getAudioSignal().verifyReadable()); - m_pSoundTouch->setChannels(getAudioSignal().channelCount()); - m_pSoundTouch->setSampleRate(getAudioSignal().sampleRate()); + m_pSoundTouch->setChannels(getAudioSignal().getChannelCount()); m_pSoundTouch->setRate(m_dBaseRate); m_pSoundTouch->setPitch(1.0); m_pSoundTouch->setSetting(SETTING_USE_QUICKSEEK, 1); - - // Setting the tempo to a very low value will force SoundTouch - // to preallocate buffers large enough to (almost certainly) - // avoid memory reallocations during playback. - m_pSoundTouch->setTempo(0.1); - m_pSoundTouch->putSamples(buffer_back, kSeekOffsetFrames); - m_pSoundTouch->clear(); - m_pSoundTouch->setTempo(m_dTempoRatio); } EngineBufferScaleST::~EngineBufferScaleST() { - SampleUtil::free(buffer_back); } void EngineBufferScaleST::setScaleParameters(double base_rate, @@ -98,17 +85,32 @@ void EngineBufferScaleST::setScaleParameters(double base_rate, // changed direction. I removed it because this is handled by EngineBuffer. } -void EngineBufferScaleST::setSampleRate(SINT iSampleRate) { - EngineBufferScale::setSampleRate(iSampleRate); - m_pSoundTouch->setSampleRate(iSampleRate); +void EngineBufferScaleST::onSampleRateChanged() { + buffer_back.clear(); + if (!getAudioSignal().isValid()) { + return; + } + m_pSoundTouch->setSampleRate(getAudioSignal().getSampleRate()); + const auto bufferSize = getAudioSignal().frames2samples(kSeekOffsetFrames); + if (bufferSize > buffer_back.size()) { + // grow buffer + buffer_back = mixxx::SampleBuffer(bufferSize); + } + // Setting the tempo to a very low value will force SoundTouch + // to preallocate buffers large enough to (almost certainly) + // avoid memory reallocations during playback. + m_pSoundTouch->setTempo(0.1); + m_pSoundTouch->putSamples(buffer_back.data(), kSeekOffsetFrames); + m_pSoundTouch->clear(); + m_pSoundTouch->setTempo(m_dTempoRatio); } void EngineBufferScaleST::clear() { m_pSoundTouch->clear(); // compensate seek offset for a rate of 1.0 - SampleUtil::clear(buffer_back, getAudioSignal().frames2samples(kSeekOffsetFrames)); - m_pSoundTouch->putSamples(buffer_back, kSeekOffsetFrames); + SampleUtil::clear(buffer_back.data(), buffer_back.size()); + m_pSoundTouch->putSamples(buffer_back.data(), kSeekOffsetFrames); } double EngineBufferScaleST::scaleBuffer( @@ -140,14 +142,14 @@ double EngineBufferScaleST::scaleBuffer( // The value doesn't matter here. All that matters is we // are going forward or backward. (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio, - buffer_back, - buffer_back_size); + buffer_back.data(), + buffer_back.size()); SINT iAvailFrames = getAudioSignal().samples2frames(iAvailSamples); if (iAvailFrames > 0) { last_read_failed = false; total_read_frames += iAvailFrames; - m_pSoundTouch->putSamples(buffer_back, iAvailFrames); + m_pSoundTouch->putSamples(buffer_back.data(), iAvailFrames); } else { if (last_read_failed) { m_pSoundTouch->flush(); diff --git a/src/engine/bufferscalers/enginebufferscalest.h b/src/engine/bufferscalers/enginebufferscalest.h index 7ac8d6110033..8212ecb75d18 100644 --- a/src/engine/bufferscalers/enginebufferscalest.h +++ b/src/engine/bufferscalers/enginebufferscalest.h @@ -1,8 +1,8 @@ -#ifndef ENGINEBUFFERSCALEST_H -#define ENGINEBUFFERSCALEST_H +#pragma once #include "engine/bufferscalers/enginebufferscale.h" #include "util/memory.h" +#include "util/samplebuffer.h" class ReadAheadManager; @@ -22,8 +22,6 @@ class EngineBufferScaleST : public EngineBufferScale { double* pTempoRatio, double* pPitchRatio) override; - void setSampleRate(SINT iSampleRate) override; - // Scale buffer. double scaleBuffer( CSAMPLE* pOutputBuffer, @@ -33,6 +31,8 @@ class EngineBufferScaleST : public EngineBufferScale { void clear() override; private: + void onSampleRateChanged() override; + // The read-ahead manager that we use to fetch samples ReadAheadManager* m_pReadAheadManager; @@ -40,11 +40,8 @@ class EngineBufferScaleST : public EngineBufferScale { std::unique_ptr m_pSoundTouch; // Temporary buffer for reading from the RAMAN. - SINT buffer_back_size; - CSAMPLE* buffer_back; + mixxx::SampleBuffer buffer_back; // Holds the playback direction. bool m_bBackwards; }; - -#endif diff --git a/src/engine/cachingreader/cachingreaderchunk.cpp b/src/engine/cachingreader/cachingreaderchunk.cpp index 732cebc00daa..9409ad97a187 100644 --- a/src/engine/cachingreader/cachingreaderchunk.cpp +++ b/src/engine/cachingreader/cachingreaderchunk.cpp @@ -25,7 +25,7 @@ const SINT kInvalidChunkIndex = -1; // easier memory alignment. // TODO(XXX): The optimum value of the "constant" kFrames depends // on the properties of the AudioSource as the remarks above suggest! -const mixxx::AudioSignal::ChannelCount CachingReaderChunk::kChannels = mixxx::kEngineChannelCount; +const mixxx::audio::ChannelCount CachingReaderChunk::kChannels = mixxx::kEngineChannelCount; const SINT CachingReaderChunk::kFrames = 8192; // ~ 170 ms at 48 kHz const SINT CachingReaderChunk::kSamples = CachingReaderChunk::frames2samples(CachingReaderChunk::kFrames); diff --git a/src/engine/cachingreader/cachingreaderchunk.h b/src/engine/cachingreader/cachingreaderchunk.h index 9f44ec3d7b93..7e9409a9b237 100644 --- a/src/engine/cachingreader/cachingreaderchunk.h +++ b/src/engine/cachingreader/cachingreaderchunk.h @@ -16,7 +16,7 @@ // and the worker. class CachingReaderChunk { public: - static const mixxx::AudioSignal::ChannelCount kChannels; + static const mixxx::audio::ChannelCount kChannels; static const SINT kFrames; static const SINT kSamples; diff --git a/src/engine/effects/engineeffect.cpp b/src/engine/effects/engineeffect.cpp index 24fef328179d..8ef459302829 100644 --- a/src/engine/effects/engineeffect.cpp +++ b/src/engine/effects/engineeffect.cpp @@ -34,7 +34,7 @@ EngineEffect::EngineEffect(EffectManifestPointer pManifest, m_pProcessor = pInstantiator->instantiate(this, pManifest); //TODO: get actual configuration of engine const mixxx::EngineParameters bufferParameters( - mixxx::AudioSignal::SampleRate(96000), + mixxx::audio::SampleRate(96000), MAX_BUFFER_LEN / mixxx::kEngineChannelCount); m_pProcessor->initialize(activeInputChannels, pEffectsManager, bufferParameters); m_effectRampsFromDry = pManifest->effectRampsFromDry(); @@ -191,7 +191,7 @@ bool EngineEffect::process(const ChannelHandle& inputHandle, if (effectiveEffectEnableState != EffectEnableState::Disabled) { //TODO: refactor rest of audio engine to use mixxx::AudioParameters const mixxx::EngineParameters bufferParameters( - mixxx::AudioSignal::SampleRate(sampleRate), + mixxx::audio::SampleRate(sampleRate), numSamples / mixxx::kEngineChannelCount); m_pProcessor->process(inputHandle, outputHandle, pInput, pOutput, diff --git a/src/engine/engine.h b/src/engine/engine.h index 014b369b3782..b7f3d780e687 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -1,11 +1,14 @@ #pragma once -#include "util/audiosignal.h" +#include "audio/signalinfo.h" namespace mixxx { // TODO(XXX): When we move from stereo to multi-channel this needs updating. - static constexpr mixxx::AudioSignal::ChannelCount kEngineChannelCount(2); + static constexpr audio::ChannelCount kEngineChannelCount = + audio::ChannelCount(2); + static constexpr audio::SampleLayout kEngineSampleLayout = + audio::SampleLayout::Interleaved; // Contains the information needed to process a buffer of audio class EngineParameters { @@ -17,25 +20,27 @@ namespace mixxx { return m_audioSignal.frames2samples(framesPerBuffer()); } - mixxx::AudioSignal::ChannelCount channelCount() const { - return m_audioSignal.channelCount(); + audio::ChannelCount channelCount() const { + return m_audioSignal.getChannelCount(); } - mixxx::AudioSignal::SampleRate sampleRate() const { - return m_audioSignal.sampleRate(); + audio::SampleRate sampleRate() const { + return m_audioSignal.getSampleRate(); } explicit EngineParameters( - AudioSignal::SampleRate sampleRate, + audio::SampleRate sampleRate, SINT framesPerBuffer) - : m_audioSignal(mixxx::AudioSignal::SampleLayout::Interleaved, - kEngineChannelCount, sampleRate), - m_framesPerBuffer(framesPerBuffer) { + : m_audioSignal( + kEngineChannelCount, + sampleRate, + kEngineSampleLayout), + m_framesPerBuffer(framesPerBuffer) { DEBUG_ASSERT(framesPerBuffer > 0); } private: - const mixxx::AudioSignal m_audioSignal; + const audio::SignalInfo m_audioSignal; const SINT m_framesPerBuffer; }; } diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index ca73661aa6fe..2f535ae4f9e0 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1025,21 +1025,19 @@ void EngineBuffer::process(CSAMPLE* pOutput, const int iBufferSize) { // - Set last sample value (m_fLastSampleValue) so that rampOut works? Other // miscellaneous upkeep issues. - int sample_rate = static_cast(m_pSampleRate->get()); + m_iSampleRate = static_cast(m_pSampleRate->get()); // If the sample rate has changed, force Rubberband to reset so that // it doesn't reallocate when the user engages keylock during playback. // We do this even if rubberband is not active. - if (sample_rate != m_iSampleRate) { - m_pScaleLinear->setSampleRate(sample_rate); - m_pScaleST->setSampleRate(sample_rate); - m_pScaleRB->setSampleRate(sample_rate); - m_iSampleRate = sample_rate; - } + const auto sampleRate = mixxx::audio::SampleRate(m_iSampleRate); + m_pScaleLinear->setSampleRate(sampleRate); + m_pScaleST->setSampleRate(sampleRate); + m_pScaleRB->setSampleRate(sampleRate); bool bTrackLoading = atomicLoadRelaxed(m_iTrackLoading) != 0; if (!bTrackLoading && m_pause.tryLock()) { - processTrackLocked(pOutput, iBufferSize, sample_rate); + processTrackLocked(pOutput, iBufferSize, m_iSampleRate); // release the pauselock m_pause.unlock(); } else { diff --git a/src/engine/enginedelay.cpp b/src/engine/enginedelay.cpp index 0f691a53eb40..72c13b84e0a4 100644 --- a/src/engine/enginedelay.cpp +++ b/src/engine/enginedelay.cpp @@ -25,7 +25,7 @@ namespace { constexpr double kdMaxDelayPot = 500; const int kiMaxDelay = (kdMaxDelayPot + 8) / 1000 * - mixxx::AudioSignal::SampleRate::kValueMax * mixxx::kEngineChannelCount; + mixxx::audio::SampleRate::kValueMax * mixxx::kEngineChannelCount; } // anonymous namespace EngineDelay::EngineDelay(const char* group, ConfigKey delayControl, bool bPersist) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 9f96a1b1d12c..73aa03acee24 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -15,7 +15,7 @@ const char* kTransitionModePreferenceName = "TransitionMode"; const double kTransitionPreferenceDefault = 10.0; const double kKeepPosition = -1.0; -const mixxx::AudioSignal::ChannelCount kChannelCount = mixxx::kEngineChannelCount; +const mixxx::audio::ChannelCount kChannelCount = mixxx::kEngineChannelCount; static const bool sDebug = false; } // anonymous namespace diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 32d0beba39ea..9ff89c781f3d 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -355,18 +355,20 @@ void TrackDAO::addTracksPrepare() { m_pQueryLibraryInsert->prepare("INSERT INTO library " "(" "artist,title,album,album_artist,year,genre,tracknumber,tracktotal,composer," - "grouping,filetype,location,color,comment,url,duration,rating,key,key_id," - "bitrate,samplerate,cuepoint,bpm,replaygain,replaygain_peak,wavesummaryhex," - "timesplayed,played,channels,mixxx_deleted,header_parsed," + "grouping,filetype,location,color,comment,url,rating,key,key_id," + "cuepoint,bpm,replaygain,replaygain_peak,wavesummaryhex," + "timesplayed,played,mixxx_deleted,header_parsed," + "channels,samplerate,bitrate,duration," "beats_version,beats_sub_version,beats,bpm_lock," "keys_version,keys_sub_version,keys," "coverart_source,coverart_type,coverart_location,coverart_hash," "datetime_added" ") VALUES (" ":artist,:title,:album,:album_artist,:year,:genre,:tracknumber,:tracktotal,:composer," - ":grouping,:filetype,:location,:color,:comment,:url,:duration,:rating,:key,:key_id," - ":bitrate,:samplerate,:cuepoint,:bpm,:replaygain,:replaygain_peak,:wavesummaryhex," - ":timesplayed,:played,:channels,:mixxx_deleted,:header_parsed," + ":grouping,:filetype,:location,:color,:comment,:url,:rating,:key,:key_id," + ":cuepoint,:bpm,:replaygain,:replaygain_peak,:wavesummaryhex," + ":timesplayed,:played,:mixxx_deleted,:header_parsed," + ":channels,:samplerate,:bitrate,:duration," ":beats_version,:beats_sub_version,:beats,:bpm_lock," ":keys_version,:keys_sub_version,:keys," ":coverart_source,:coverart_type,:coverart_location,:coverart_hash," @@ -434,15 +436,15 @@ namespace { pTrackLibraryQuery->bindValue(":color", mixxx::RgbColor::toQVariant(track.getColor())); pTrackLibraryQuery->bindValue(":comment", track.getComment()); pTrackLibraryQuery->bindValue(":url", track.getURL()); - pTrackLibraryQuery->bindValue(":duration", track.getDuration()); pTrackLibraryQuery->bindValue(":rating", track.getRating()); - pTrackLibraryQuery->bindValue(":bitrate", track.getBitrate()); - pTrackLibraryQuery->bindValue(":samplerate", track.getSampleRate()); pTrackLibraryQuery->bindValue(":cuepoint", track.getCuePoint().getPosition()); pTrackLibraryQuery->bindValue(":bpm_lock", track.isBpmLocked()? 1 : 0); pTrackLibraryQuery->bindValue(":replaygain", track.getReplayGain().getRatio()); - pTrackLibraryQuery->bindValue(":replaygain_peak", track.getReplayGain().getPeak()); + pTrackLibraryQuery->bindValue(":channels", track.getChannels()); + pTrackLibraryQuery->bindValue(":samplerate", track.getSampleRate()); + pTrackLibraryQuery->bindValue(":bitrate", track.getBitrate()); + pTrackLibraryQuery->bindValue(":duration", track.getDuration()); pTrackLibraryQuery->bindValue(":header_parsed", track.isMetadataSynchronized() ? 1 : 0); @@ -973,30 +975,12 @@ bool setTrackUrl(const QSqlRecord& record, const int column, return false; } -bool setTrackDuration(const QSqlRecord& record, const int column, - TrackPointer pTrack) { - pTrack->setDuration(record.value(column).toDouble()); - return false; -} - -bool setTrackBitrate(const QSqlRecord& record, const int column, - TrackPointer pTrack) { - pTrack->setBitrate(record.value(column).toInt()); - return false; -} - bool setTrackRating(const QSqlRecord& record, const int column, TrackPointer pTrack) { pTrack->setRating(record.value(column).toInt()); return false; } -bool setTrackSampleRate(const QSqlRecord& record, const int column, - TrackPointer pTrack) { - pTrack->setSampleRate(record.value(column).toInt()); - return false; -} - bool setTrackCuePoint(const QSqlRecord& record, const int column, TrackPointer pTrack) { pTrack->setCuePoint(CuePosition(record.value(column).toDouble())); @@ -1035,12 +1019,6 @@ bool setTrackPlayed(const QSqlRecord& record, const int column, return false; } -bool setTrackChannels(const QSqlRecord& record, const int column, - TrackPointer pTrack) { - pTrack->setChannels(record.value(column).toInt()); - return false; -} - bool setTrackDateAdded(const QSqlRecord& record, const int column, TrackPointer pTrack) { pTrack->setDateAdded(record.value(column).toDateTime()); @@ -1059,6 +1037,22 @@ bool setTrackMetadataSynchronized(const QSqlRecord& record, const int column, return false; } +bool setTrackAudioProperties( + const QSqlRecord& record, + const int firstColumn, + TrackPointer pTrack) { + const auto channels = record.value(firstColumn).toInt(); + const auto samplerate = record.value(firstColumn + 1).toInt(); + const auto bitrate = record.value(firstColumn + 2).toInt(); + const auto duration = record.value(firstColumn + 3).toDouble(); + pTrack->setAudioProperties( + mixxx::audio::ChannelCount(channels), + mixxx::audio::SampleRate(samplerate), + mixxx::audio::Bitrate(bitrate), + mixxx::Duration::fromSeconds(duration)); + return false; +} + bool setTrackBeats(const QSqlRecord& record, const int column, TrackPointer pTrack) { double bpm = record.value(column).toDouble(); @@ -1161,18 +1155,21 @@ TrackPointer TrackDAO::getTrackById(TrackId trackId) const { { "color", setTrackColor }, { "comment", setTrackComment }, { "url", setTrackUrl }, - { "duration", setTrackDuration }, - { "bitrate", setTrackBitrate }, - { "samplerate", setTrackSampleRate }, { "cuepoint", setTrackCuePoint }, { "replaygain", setTrackReplayGainRatio }, { "replaygain_peak", setTrackReplayGainPeak }, - { "channels", setTrackChannels }, { "timesplayed", setTrackTimesPlayed }, { "played", setTrackPlayed }, { "datetime_added", setTrackDateAdded }, { "header_parsed", setTrackMetadataSynchronized }, + // Audio properties are set together at once. Do not change the + // ordering of these columns or put other columns in between them! + { "channels", setTrackAudioProperties }, + { "samplerate", nullptr }, + { "bitrate", nullptr }, + { "duration", nullptr }, + // Beat detection columns are handled by setTrackBeats. Do not change // the ordering of these columns or put other columns in between them! { "bpm", setTrackBeats }, @@ -1397,21 +1394,21 @@ bool TrackDAO::updateTrack(Track* pTrack) { "color=:color," "comment=:comment," "url=:url," - "duration=:duration," "rating=:rating," "key=:key," "key_id=:key_id," - "bitrate=:bitrate," - "samplerate=:samplerate," "cuepoint=:cuepoint," "bpm=:bpm," "replaygain=:replaygain," "replaygain_peak=:replaygain_peak," "timesplayed=:timesplayed," "played=:played," - "channels=:channels," "header_parsed=:header_parsed," - "beats_version=:beats_version," + "channels=:channels," + "bitrate=:bitrate," + "samplerate=:samplerate," + "bitrate=:bitrate," + "duration=:duration," "beats_sub_version=:beats_sub_version," "beats=:beats," "bpm_lock=:bpm_lock," diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index 4f0398b28824..4de6b7905e93 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -176,7 +176,7 @@ void DlgTrackInfo::populateFields(const Track& track) { txtDuration->setText(track.getDurationText(mixxx::Duration::Precision::SECONDS)); txtLocation->setText(QDir::toNativeSeparators(track.getLocation())); txtType->setText(track.getType()); - txtBitrate->setText(QString(track.getBitrateText()) + (" ") + tr(mixxx::AudioSource::Bitrate::unit())); + txtBitrate->setText(QString(track.getBitrateText()) + (" ") + tr(mixxx::audio::Bitrate::unit())); txtBpm->setText(track.getBpmText()); m_keysClone = track.getKeys(); txtKey->setText(KeyUtils::getGlobalKeyText(m_keysClone)); diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index 74ccc9a7c6c2..2759b7aae5a2 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -11,6 +11,7 @@ #include "engine/enginebuffer.h" #include "engine/controls/enginecontrol.h" #include "engine/channels/enginedeck.h" +#include "engine/engine.h" #include "engine/enginemaster.h" #include "track/beatgrid.h" #include "waveform/renderers/waveformwidgetrenderer.h" @@ -124,9 +125,11 @@ BaseTrackPlayerImpl::~BaseTrackPlayerImpl() { TrackPointer BaseTrackPlayerImpl::loadFakeTrack(bool bPlay, double filebpm) { TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); - // 10 seconds - pTrack->setDuration(10); + pTrack->setAudioProperties( + mixxx::kEngineChannelCount, + mixxx::audio::SampleRate(44100), + mixxx::audio::Bitrate(), + mixxx::Duration::fromSeconds(10)); if (filebpm > 0) { pTrack->setBpm(filebpm); } diff --git a/src/sources/audiosource.cpp b/src/sources/audiosource.cpp index 6678c3ae3df6..26142824eae7 100644 --- a/src/sources/audiosource.cpp +++ b/src/sources/audiosource.cpp @@ -56,14 +56,14 @@ bool AudioSource::initFrameIndexRangeOnce( return true; } -bool AudioSource::initBitrateOnce(Bitrate bitrate) { - if (bitrate < Bitrate()) { +bool AudioSource::initBitrateOnce(audio::Bitrate bitrate) { + if (bitrate < audio::Bitrate()) { kLogger.warning() << "Invalid bitrate" << bitrate; return false; // abort } - VERIFY_OR_DEBUG_ASSERT(!m_bitrate.valid() || (m_bitrate == bitrate)) { + VERIFY_OR_DEBUG_ASSERT(!m_bitrate.isValid() || (m_bitrate == bitrate)) { kLogger.warning() << "Bitrate has already been initialized to" << m_bitrate @@ -83,10 +83,10 @@ bool AudioSource::verifyReadable() const { // Don't set the result to false, even if reading from an empty source // is pointless! } - if (m_bitrate != Bitrate()) { - VERIFY_OR_DEBUG_ASSERT(m_bitrate.valid()) { + if (m_bitrate != audio::Bitrate()) { + VERIFY_OR_DEBUG_ASSERT(m_bitrate.isValid()) { kLogger.warning() - << "Invalid bitrate [kbps]:" + << "Invalid bitrate" << m_bitrate; // Don't set the result to false, because bitrate is only // an informational property that does not effect the ability diff --git a/src/sources/audiosource.h b/src/sources/audiosource.h index d4ca66a61776..461b9ea2b0c5 100644 --- a/src/sources/audiosource.h +++ b/src/sources/audiosource.h @@ -1,7 +1,8 @@ #pragma once +#include "audio/streaminfo.h" +#include "engine/engine.h" #include "sources/urlresource.h" - #include "util/audiosignal.h" #include "util/indexrange.h" #include "util/memory.h" @@ -149,7 +150,7 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp // A frame for a mono signal contains a single sample. A frame // for a stereo signal contains a pair of samples, one for the // left and right channel respectively. - static constexpr SampleLayout kSampleLayout = SampleLayout::Interleaved; + static constexpr audio::SampleLayout kSampleLayout = mixxx::kEngineSampleLayout; enum class OpenMode { // In Strict mode the opening operation should be aborted @@ -186,14 +187,27 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp OpenParams() : AudioSignal(kSampleLayout) { } - OpenParams(ChannelCount channelCount, SampleRate sampleRate) - : AudioSignal(kSampleLayout, channelCount, sampleRate) { + OpenParams( + audio::ChannelCount channelCount, + audio::SampleRate sampleRate) + : AudioSignal( + audio::SignalInfo( + channelCount, + sampleRate, + kSampleLayout)) { } using AudioSignal::setChannelCount; using AudioSignal::setSampleRate; }; + audio::StreamInfo getStreamInfo() const { + return audio::StreamInfo( + getSignalInfo(), + m_bitrate, + Duration::fromSeconds(getDuration())); + } + // Opens the AudioSource for reading audio data. // // Since reopening is not supported close() will be called @@ -245,43 +259,14 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp // The actual duration in seconds. // Well defined only for valid files! inline bool hasDuration() const { - return sampleRate().valid(); + return sampleRate().isValid(); } inline double getDuration() const { DEBUG_ASSERT(hasDuration()); // prevents division by zero return double(frameLength()) / double(sampleRate()); } - // The bitrate is optional and measured in kbit/s (kbps). - // It depends on the metadata and decoder if a value for the - // bitrate is available. - class Bitrate { - private: - static constexpr SINT kValueDefault = 0; - - public: - static constexpr const char* unit() { - return "kbps"; - } - - explicit constexpr Bitrate(SINT value = kValueDefault) - : m_value(value) { - } - - bool valid() const { - return m_value > kValueDefault; - } - - /*implicit*/ operator SINT() const { - DEBUG_ASSERT(m_value >= kValueDefault); // unsigned value - return m_value; - } - - private: - SINT m_value; - }; - - Bitrate bitrate() const { + audio::Bitrate bitrate() const { return m_bitrate; } @@ -309,9 +294,9 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp that.adjustFrameIndexRange(frameIndexRange); } - bool initBitrateOnce(Bitrate bitrate); + bool initBitrateOnce(audio::Bitrate bitrate); bool initBitrateOnce(SINT bitrate) { - return initBitrateOnce(Bitrate(bitrate)); + return initBitrateOnce(audio::Bitrate(bitrate)); } // Tries to open the AudioSource for reading audio data according @@ -353,7 +338,7 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp IndexRange m_frameIndexRange; - Bitrate m_bitrate; + audio::Bitrate m_bitrate; }; typedef std::shared_ptr AudioSourcePointer; diff --git a/src/sources/soundsourceffmpeg.cpp b/src/sources/soundsourceffmpeg.cpp index e82f6940bd88..f13c171c4332 100644 --- a/src/sources/soundsourceffmpeg.cpp +++ b/src/sources/soundsourceffmpeg.cpp @@ -514,7 +514,7 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( // Request output format pavCodecContext->request_sample_fmt = kavSampleFormat; - if (params.channelCount().valid()) { + if (params.channelCount().isValid()) { // A dedicated number of channels for the output signal // has been requested. Forward this to FFmpeg to avoid // manual resampling or post-processing after decoding. @@ -556,8 +556,8 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( << '}'; } - ChannelCount channelCount; - SampleRate sampleRate; + audio::ChannelCount channelCount; + audio::SampleRate sampleRate; if (!initResampling(&channelCount, &sampleRate)) { return OpenResult::Failed; } @@ -575,8 +575,8 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( } const auto streamBitrate = - Bitrate(m_pavStream->codecpar->bit_rate / 1000); // kbps - if (streamBitrate.valid() && !initBitrateOnce(streamBitrate)) { + audio::Bitrate(m_pavStream->codecpar->bit_rate / 1000); // kbps + if (streamBitrate.isValid() && !initBitrateOnce(streamBitrate)) { kLogger.warning() << "Failed to initialize bitrate" << streamBitrate; @@ -641,12 +641,12 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( } bool SoundSourceFFmpeg::initResampling( - ChannelCount* pResampledChannelCount, - SampleRate* pResampledSampleRate) { + audio::ChannelCount* pResampledChannelCount, + audio::SampleRate* pResampledSampleRate) { const auto avStreamChannelLayout = getStreamChannelLayout(*m_pavStream); const auto streamChannelCount = - ChannelCount(m_pavStream->codecpar->channels); + audio::ChannelCount(m_pavStream->codecpar->channels); // NOTE(uklotzde, 2017-09-26): Resampling to a different number of // channels like upsampling a mono to stereo signal breaks various // tests in the EngineBufferE2ETest suite!! SoundSource decoding tests @@ -668,7 +668,7 @@ bool SoundSourceFFmpeg::initResampling( // the channels and to transform the decoded audio data into the sample // format that is used by Mixxx. const auto streamSampleRate = - SampleRate(m_pavStream->codecpar->sample_rate); + audio::SampleRate(m_pavStream->codecpar->sample_rate); const auto resampledSampleRate = streamSampleRate; if ((resampledChannelCount != streamChannelCount) || (avResampledChannelLayout != avStreamChannelLayout) || diff --git a/src/sources/soundsourceffmpeg.h b/src/sources/soundsourceffmpeg.h index f50b09c82d7a..7c1be9584620 100644 --- a/src/sources/soundsourceffmpeg.h +++ b/src/sources/soundsourceffmpeg.h @@ -31,8 +31,8 @@ class SoundSourceFFmpeg : public SoundSource { const OpenParams& params) override; bool initResampling( - ChannelCount* pResampledChannelCount, - SampleRate* pResampledSampleRate); + audio::ChannelCount* pResampledChannelCount, + audio::SampleRate* pResampledSampleRate); const CSAMPLE* resampleDecodedFrame(); // Consume as many buffered sample frames as possible and return diff --git a/src/sources/soundsourcemediafoundation.cpp b/src/sources/soundsourcemediafoundation.cpp index c1aaaf8b0a4a..60becff86252 100644 --- a/src/sources/soundsourcemediafoundation.cpp +++ b/src/sources/soundsourcemediafoundation.cpp @@ -365,7 +365,7 @@ ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( VERIFY_OR_DEBUG_ASSERT(m_currentFrameIndex == readerFrameIndex) { kLogger.debug() << "streamPos [100 ns] =" << streamPos - << ", sampleRate [Hz] =" << sampleRate(); + << ", sampleRate =" << sampleRate(); kLogger.warning() << "Stream position (in sample frames) while reading is inaccurate:" << "expected =" << m_currentFrameIndex diff --git a/src/sources/soundsourcemodplug.cpp b/src/sources/soundsourcemodplug.cpp index 05518288448a..d33bf680118f 100644 --- a/src/sources/soundsourcemodplug.cpp +++ b/src/sources/soundsourcemodplug.cpp @@ -1,5 +1,6 @@ #include "sources/soundsourcemodplug.h" +#include "audio/streaminfo.h" #include "track/trackmetadata.h" #include "util/logger.h" #include "util/sample.h" @@ -82,10 +83,10 @@ SoundSourceModPlug::importTrackMetadataAndCoverImage( pTrackMetadata->refTrackInfo().setComment(QString(ModPlug::ModPlug_GetMessage(pModFile))); pTrackMetadata->refTrackInfo().setTitle(QString(ModPlug::ModPlug_GetName(pModFile))); - pTrackMetadata->setChannels(ChannelCount(kChannelCount)); - pTrackMetadata->setSampleRate(SampleRate(kSampleRate)); + pTrackMetadata->setChannelCount(audio::ChannelCount(kChannelCount)); + pTrackMetadata->setSampleRate(audio::SampleRate(kSampleRate)); + pTrackMetadata->setBitrate(audio::Bitrate(8)); pTrackMetadata->setDuration(Duration::fromMillis(ModPlug::ModPlug_GetLength(pModFile))); - pTrackMetadata->setBitrate(Bitrate(8)); // not really, but fill in something... ModPlug::ModPlug_Unload(pModFile); return std::make_pair(ImportResult::Succeeded, QFileInfo(modFile).lastModified()); diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 661c362cc3dd..2332495c817d 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -20,7 +20,7 @@ const SINT kMaxBytesPerMp3Frame = 1441; // mp3 supports 9 different sample rates const int kSampleRateCount = 9; -int getIndexBySampleRate(AudioSignal::SampleRate sampleRate) { +int getIndexBySampleRate(audio::SampleRate sampleRate) { switch (sampleRate) { case 8000: return 0; @@ -46,29 +46,29 @@ int getIndexBySampleRate(AudioSignal::SampleRate sampleRate) { } } -AudioSignal::SampleRate getSampleRateByIndex(int sampleRateIndex) { +audio::SampleRate getSampleRateByIndex(int sampleRateIndex) { switch (sampleRateIndex) { case 0: - return AudioSignal::SampleRate(8000); + return audio::SampleRate(8000); case 1: - return AudioSignal::SampleRate(11025); + return audio::SampleRate(11025); case 2: - return AudioSignal::SampleRate(12000); + return audio::SampleRate(12000); case 3: - return AudioSignal::SampleRate(16000); + return audio::SampleRate(16000); case 4: - return AudioSignal::SampleRate(22050); + return audio::SampleRate(22050); case 5: - return AudioSignal::SampleRate(24000); + return audio::SampleRate(24000); case 6: - return AudioSignal::SampleRate(32000); + return audio::SampleRate(32000); case 7: - return AudioSignal::SampleRate(44100); + return audio::SampleRate(44100); case 8: - return AudioSignal::SampleRate(48000); + return audio::SampleRate(48000); default: // index out of range - return AudioSignal::SampleRate(); + return audio::SampleRate(); } } @@ -192,8 +192,7 @@ void SoundSourceMp3::finishDecoding() { SoundSource::OpenResult SoundSourceMp3::tryOpen( OpenMode /*mode*/, const OpenParams& /*config*/) { - DEBUG_ASSERT(!channelCount().valid()); - DEBUG_ASSERT(!sampleRate().valid()); + DEBUG_ASSERT(!getSignalInfo().isValid()); DEBUG_ASSERT(!m_file.isOpen()); if (!m_file.open(QIODevice::ReadOnly)) { @@ -238,7 +237,7 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( mad_header madHeader; mad_header_init(&madHeader); - ChannelCount maxChannelCount = channelCount(); + auto maxChannelCount = audio::ChannelCount(); do { if (!decodeFrameHeader(&madHeader, &m_madStream, true)) { if (isStreamValid(m_madStream)) { @@ -267,9 +266,9 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( continue; } - const ChannelCount madChannelCount(MAD_NCHANNELS(&madHeader)); - if (madChannelCount.valid()) { - if (maxChannelCount.valid() && (madChannelCount != maxChannelCount)) { + const audio::ChannelCount madChannelCount(MAD_NCHANNELS(&madHeader)); + if (madChannelCount.isValid()) { + if (maxChannelCount.isValid() && (madChannelCount != maxChannelCount)) { kLogger.warning() << "Differing number of channels" << madChannelCount << "<>" << maxChannelCount @@ -283,7 +282,8 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( << m_file.fileName(); } - const int sampleRateIndex = getIndexBySampleRate(SampleRate(madSampleRate)); + const int sampleRateIndex = getIndexBySampleRate( + audio::SampleRate(madSampleRate)); if (sampleRateIndex >= kSampleRateCount) { kLogger.warning() << "Invalid sample rate:" << m_file.fileName() << madSampleRate; @@ -297,7 +297,7 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( addSeekFrame(m_curFrameIndex, m_madStream.this_frame); // Accumulate data from the header - if (Bitrate(madHeader.bitrate).valid()) { + if (audio::Bitrate(madHeader.bitrate).isValid()) { // Accumulate the bitrate per decoded sample frame to calculate // a weighted average for the whole file (see below) sumBitrateFrames += static_cast(madHeader.bitrate) * static_cast(madFrameLength); @@ -368,7 +368,7 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( return OpenResult::Failed; } setSampleRate(getSampleRateByIndex(mostCommonSampleRateIndex)); - if (!maxChannelCount.valid() || (maxChannelCount > kChannelCountMax)) { + if (!maxChannelCount.isValid() || (maxChannelCount > kChannelCountMax)) { kLogger.warning() << "Invalid number of channels" << maxChannelCount diff --git a/src/sources/soundsourceopus.cpp b/src/sources/soundsourceopus.cpp index 8a60cb03dbd4..1d0f103a8a70 100644 --- a/src/sources/soundsourceopus.cpp +++ b/src/sources/soundsourceopus.cpp @@ -1,5 +1,6 @@ #include "sources/soundsourceopus.h" +#include "audio/streaminfo.h" #include "util/logger.h" namespace mixxx { @@ -9,7 +10,7 @@ namespace { const Logger kLogger("SoundSourceOpus"); // Decoded output of opusfile has a fixed sample rate of 48 kHz (fullband) -constexpr AudioSignal::SampleRate kSampleRate = AudioSignal::SampleRate(48000); +constexpr audio::SampleRate kSampleRate = audio::SampleRate(48000); // http://opus-codec.org // - Sample rate 48 kHz (fullband) @@ -121,13 +122,17 @@ SoundSourceOpus::importTrackMetadataAndCoverImage( return imported; } - pTrackMetadata->setChannels(ChannelCount(op_channel_count(pOggOpusFile, -1))); - pTrackMetadata->setSampleRate(kSampleRate); - pTrackMetadata->setBitrate(Bitrate(op_bitrate(pOggOpusFile, -1) / 1000)); + pTrackMetadata->setChannelCount( + audio::ChannelCount(op_channel_count(pOggOpusFile, -1))); + pTrackMetadata->setSampleRate( + kSampleRate); + pTrackMetadata->setBitrate( + audio::Bitrate(op_bitrate(pOggOpusFile, -1) / 1000)); // Cast to double is required for duration with sub-second precision const double dTotalFrames = op_pcm_total(pOggOpusFile, -1); - pTrackMetadata->setDuration(Duration::fromMicros( - 1000000 * dTotalFrames / pTrackMetadata->getSampleRate())); + const auto duration = Duration::fromMicros( + 1000000 * dTotalFrames / pTrackMetadata->getSampleRate()); + pTrackMetadata->setDuration(duration); #ifndef TAGLIB_HAS_OPUSFILE const OpusTags* l_ptrOpusTags = op_tags(pOggOpusFile, -1); @@ -224,7 +229,7 @@ SoundSource::OpenResult SoundSourceOpus::tryOpen( if (0 < streamChannelCount) { // opusfile supports to enforce stereo decoding bool enforceStereoDecoding = - params.channelCount().valid() && + params.channelCount().isValid() && (params.channelCount() <= 2) && // preserve mono signals if stereo signal is not requested explicitly ((params.channelCount() == 2) || (streamChannelCount > 2)); @@ -241,7 +246,7 @@ SoundSource::OpenResult SoundSourceOpus::tryOpen( } // Reserve enough capacity for buffering a stereo signal! - const auto prefetchChannelCount = std::min(channelCount(), ChannelCount(2)); + const auto prefetchChannelCount = std::min(channelCount(), audio::ChannelCount(2)); SampleBuffer(prefetchChannelCount * kNumberOfPrefetchFrames).swap(m_prefetchSampleBuffer); const ogg_int64_t pcmTotal = op_pcm_total(m_pOggOpusFile, kEntireStreamLink); diff --git a/src/sources/soundsourceproxy.cpp b/src/sources/soundsourceproxy.cpp index dcbf2075336d..b4ed1a5ade23 100644 --- a/src/sources/soundsourceproxy.cpp +++ b/src/sources/soundsourceproxy.cpp @@ -512,18 +512,8 @@ mixxx::AudioSourcePointer SoundSourceProxy::openAudioSource(const mixxx::AudioSo } // Overwrite metadata with actual audio properties if (m_pTrack) { - DEBUG_ASSERT(m_pAudioSource->channelCount().valid()); - m_pTrack->setChannels(m_pAudioSource->channelCount()); - DEBUG_ASSERT(m_pAudioSource->sampleRate().valid()); - m_pTrack->setSampleRate(m_pAudioSource->sampleRate()); - if (m_pAudioSource->hasDuration()) { - // optional property - m_pTrack->setDuration(m_pAudioSource->getDuration()); - } - if (m_pAudioSource->bitrate() != mixxx::AudioSource::Bitrate()) { - // optional property - m_pTrack->setBitrate(m_pAudioSource->bitrate()); - } + m_pTrack->updateAudioPropertiesFromStream( + m_pAudioSource->getStreamInfo()); } } else { kLogger.warning() << "Failed to open file" diff --git a/src/test/analyserwaveformtest.cpp b/src/test/analyserwaveformtest.cpp index f0980c3cf9d7..47f2429fbdea 100644 --- a/src/test/analyserwaveformtest.cpp +++ b/src/test/analyserwaveformtest.cpp @@ -25,7 +25,11 @@ class AnalyzerWaveformTest : public MixxxTest { void SetUp() override { tio = Track::newTemporary(); - tio->setSampleRate(44100); + tio->setAudioProperties( + mixxx::audio::ChannelCount(2), + mixxx::audio::SampleRate(44100), + mixxx::audio::Bitrate(), + mixxx::Duration::fromMillis(1000)); bigbuf = new CSAMPLE[BIGBUF_SIZE]; for (int i = 0; i < BIGBUF_SIZE; i++) diff --git a/src/test/analyzersilence_test.cpp b/src/test/analyzersilence_test.cpp index 26224002ffc0..e8b87ae1d310 100644 --- a/src/test/analyzersilence_test.cpp +++ b/src/test/analyzersilence_test.cpp @@ -7,7 +7,7 @@ namespace { -constexpr mixxx::AudioSignal::ChannelCount kChannelCount = mixxx::kEngineChannelCount; +constexpr mixxx::audio::ChannelCount kChannelCount = mixxx::kEngineChannelCount; constexpr int kTrackLengthFrames = 100000; constexpr double kTonePitchHz = 1000.0; // 1kHz @@ -19,7 +19,11 @@ class AnalyzerSilenceTest : public MixxxTest { void SetUp() override { pTrack = Track::newTemporary(); - pTrack->setSampleRate(44100); + pTrack->setAudioProperties( + mixxx::audio::ChannelCount(kChannelCount), + mixxx::audio::SampleRate(44100), + mixxx::audio::Bitrate(), + mixxx::Duration::fromSeconds(kTrackLengthFrames / 44100.0)); nTrackSampleDataLength = kChannelCount * kTrackLengthFrames; pTrackSampleData = new CSAMPLE[nTrackSampleDataLength]; diff --git a/src/test/autodjprocessor_test.cpp b/src/test/autodjprocessor_test.cpp index d1253761c77e..aff28911ac66 100644 --- a/src/test/autodjprocessor_test.cpp +++ b/src/test/autodjprocessor_test.cpp @@ -19,7 +19,7 @@ using ::testing::_; using ::testing::Return; static int kDefaultTransitionTime = 10; -const mixxx::AudioSignal::ChannelCount kChannelCount = mixxx::kEngineChannelCount; +const mixxx::audio::ChannelCount kChannelCount = mixxx::kEngineChannelCount; const QString kTrackLocationTest(QDir::currentPath() % "/src/test/id3-test-data/cover-test-png.mp3"); diff --git a/src/test/beatgridtest.cpp b/src/test/beatgridtest.cpp index 36915f5195d6..907cbce6db01 100644 --- a/src/test/beatgridtest.cpp +++ b/src/test/beatgridtest.cpp @@ -8,13 +8,22 @@ namespace { const double kMaxBeatError = 1e-9; -TEST(BeatGridTest, Scale) { +TrackPointer newTrack(int sampleRate) { TrackPointer pTrack(Track::newTemporary()); + pTrack->setAudioProperties( + mixxx::audio::ChannelCount(2), + mixxx::audio::SampleRate(sampleRate), + mixxx::audio::Bitrate(), + mixxx::Duration::fromSeconds(180)); + return pTrack; +} +TEST(BeatGridTest, Scale) { int sampleRate = 44100; + TrackPointer pTrack = newTrack(sampleRate); + double bpm = 60.0; pTrack->setBpm(bpm); - pTrack->setSampleRate(sampleRate); auto pGrid = std::make_unique(*pTrack, 0); pGrid->setBpm(bpm); @@ -40,13 +49,12 @@ TEST(BeatGridTest, Scale) { } TEST(BeatGridTest, TestNthBeatWhenOnBeat) { - TrackPointer pTrack(Track::newTemporary()); - int sampleRate = 44100; + TrackPointer pTrack = newTrack(sampleRate); + double bpm = 60.1; const int kFrameSize = 2; pTrack->setBpm(bpm); - pTrack->setSampleRate(sampleRate); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; auto pGrid = std::make_unique(*pTrack, 0); @@ -76,13 +84,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat) { } TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { - TrackPointer pTrack(Track::newTemporary()); - int sampleRate = 44100; + TrackPointer pTrack = newTrack(sampleRate); + double bpm = 60.1; const int kFrameSize = 2; pTrack->setBpm(bpm); - pTrack->setSampleRate(sampleRate); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; auto pGrid = std::make_unique(*pTrack, 0); @@ -114,13 +121,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { } TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) { - TrackPointer pTrack(Track::newTemporary()); - int sampleRate = 44100; + TrackPointer pTrack = newTrack(sampleRate); + double bpm = 60.1; const int kFrameSize = 2; pTrack->setBpm(bpm); - pTrack->setSampleRate(sampleRate); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; auto pGrid = std::make_unique(*pTrack, 0); @@ -152,12 +158,12 @@ TEST(BeatGridTest, TestNthBeatWhenOnBeat_AfterEpsilon) { } TEST(BeatGridTest, TestNthBeatWhenNotOnBeat) { - TrackPointer pTrack(Track::newTemporary()); int sampleRate = 44100; + TrackPointer pTrack = newTrack(sampleRate); + double bpm = 60.1; const int kFrameSize = 2; pTrack->setBpm(bpm); - pTrack->setSampleRate(sampleRate); double beatLength = (60.0 * sampleRate / bpm) * kFrameSize; auto pGrid = std::make_unique(*pTrack, 0); diff --git a/src/test/beatmaptest.cpp b/src/test/beatmaptest.cpp index 9fc62a86fd8d..907b3265e4e9 100644 --- a/src/test/beatmaptest.cpp +++ b/src/test/beatmaptest.cpp @@ -13,7 +13,11 @@ class BeatMapTest : public testing::Test { : m_pTrack(Track::newTemporary()), m_iSampleRate(100), m_iFrameSize(2) { - + m_pTrack->setAudioProperties( + mixxx::audio::ChannelCount(2), + mixxx::audio::SampleRate(m_iSampleRate), + mixxx::audio::Bitrate(), + mixxx::Duration::fromSeconds(180)); } double getBeatLengthFrames(double bpm) { @@ -42,7 +46,6 @@ class BeatMapTest : public testing::Test { TEST_F(BeatMapTest, Scale) { const double bpm = 60.0; m_pTrack->setBpm(bpm); - m_pTrack->setSampleRate(m_iSampleRate); double beatLengthFrames = getBeatLengthFrames(bpm); double startOffsetFrames = 7; const int numBeats = 100; @@ -73,7 +76,6 @@ TEST_F(BeatMapTest, Scale) { TEST_F(BeatMapTest, TestNthBeat) { const double bpm = 60.0; m_pTrack->setBpm(bpm); - m_pTrack->setSampleRate(m_iSampleRate); double beatLengthFrames = getBeatLengthFrames(bpm); double startOffsetFrames = 7; double beatLengthSamples = getBeatLengthSamples(bpm); @@ -106,7 +108,6 @@ TEST_F(BeatMapTest, TestNthBeat) { TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) { const double bpm = 60.0; m_pTrack->setBpm(bpm); - m_pTrack->setSampleRate(m_iSampleRate); double beatLengthFrames = getBeatLengthFrames(bpm); double startOffsetFrames = 7; double beatLengthSamples = getBeatLengthSamples(bpm); @@ -144,7 +145,6 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat) { TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { const double bpm = 60.0; m_pTrack->setBpm(bpm); - m_pTrack->setSampleRate(m_iSampleRate); double beatLengthFrames = getBeatLengthFrames(bpm); double startOffsetFrames = 7; double beatLengthSamples = getBeatLengthSamples(bpm); @@ -184,7 +184,6 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_BeforeEpsilon) { TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) { const double bpm = 60.0; m_pTrack->setBpm(bpm); - m_pTrack->setSampleRate(m_iSampleRate); double beatLengthFrames = getBeatLengthFrames(bpm); double startOffsetFrames = 7; double beatLengthSamples = getBeatLengthSamples(bpm); @@ -225,7 +224,6 @@ TEST_F(BeatMapTest, TestNthBeatWhenOnBeat_AfterEpsilon) { TEST_F(BeatMapTest, TestNthBeatWhenNotOnBeat) { const double bpm = 60.0; m_pTrack->setBpm(bpm); - m_pTrack->setSampleRate(m_iSampleRate); double beatLengthFrames = getBeatLengthFrames(bpm); double startOffsetFrames = 7; double beatLengthSamples = getBeatLengthSamples(bpm); @@ -263,7 +261,6 @@ TEST_F(BeatMapTest, TestBpmAround) { const double filebpm = 60.0; double approx_beat_length = getBeatLengthSamples(filebpm); m_pTrack->setBpm(filebpm); - m_pTrack->setSampleRate(m_iSampleRate); const int numBeats = 64; QVector beats; diff --git a/src/test/bpmcontrol_test.cpp b/src/test/bpmcontrol_test.cpp index 764db3e6b7c4..815393684549 100644 --- a/src/test/bpmcontrol_test.cpp +++ b/src/test/bpmcontrol_test.cpp @@ -26,11 +26,17 @@ TEST_F(BpmControlTest, ShortestPercentageChange) { TEST_F(BpmControlTest, BeatContext_BeatGrid) { const int sampleRate = 44100; + + TrackPointer pTrack = Track::newTemporary(); + pTrack->setAudioProperties( + mixxx::audio::ChannelCount(2), + mixxx::audio::SampleRate(sampleRate), + mixxx::audio::Bitrate(), + mixxx::Duration::fromSeconds(180)); + const double bpm = 60.0; const int kFrameSize = 2; const double expectedBeatLength = (60.0 * sampleRate / bpm) * kFrameSize; - TrackPointer pTrack = Track::newTemporary(); - pTrack->setSampleRate(sampleRate); BeatsPointer pBeats = BeatFactory::makeBeatGrid(*pTrack, bpm, 0); diff --git a/src/test/cue_test.cpp b/src/test/cue_test.cpp index 8154d6104d9d..5fba867a6775 100644 --- a/src/test/cue_test.cpp +++ b/src/test/cue_test.cpp @@ -11,7 +11,7 @@ namespace mixxx { TEST(CueTest, DefaultCueToCueInfoTest) { const Cue cueObject; auto cueInfo = cueObject.getCueInfo( - AudioSignal::SampleRate(44100)); + audio::SampleRate(44100)); cueInfo.setColor(std::nullopt); EXPECT_EQ(CueInfo(), cueInfo); } @@ -20,9 +20,9 @@ TEST(CueTest, DefaultCueInfoToCueRoundtrip) { const CueInfo cueInfo1; const Cue cueObject( cueInfo1, - AudioSignal::SampleRate(44100)); + audio::SampleRate(44100)); auto cueInfo2 = cueObject.getCueInfo( - AudioSignal::SampleRate(44100)); + audio::SampleRate(44100)); cueInfo2.setColor(std::nullopt); EXPECT_EQ(cueInfo1, cueInfo2); } @@ -40,9 +40,9 @@ TEST(CueTest, ConvertCueInfoToCueRoundtrip) { RgbColor::optional(0xABCDEF)); const Cue cueObject( cueInfo1, - AudioSignal::SampleRate(44100)); + audio::SampleRate(44100)); const auto cueInfo2 = cueObject.getCueInfo( - AudioSignal::SampleRate(44100)); + audio::SampleRate(44100)); EXPECT_EQ(cueInfo1, cueInfo2); } diff --git a/src/test/cuecontrol_test.cpp b/src/test/cuecontrol_test.cpp index de18a0eb64f8..cb3a547a1376 100644 --- a/src/test/cuecontrol_test.cpp +++ b/src/test/cuecontrol_test.cpp @@ -28,7 +28,13 @@ class CueControlTest : public BaseSignalPathTest { TrackPointer createTestTrack() const { const QString kTrackLocationTest = QDir::currentPath() + "/src/test/sine-30.wav"; - return Track::newTemporary(kTrackLocationTest, SecurityTokenPointer()); + const auto pTrack = Track::newTemporary(kTrackLocationTest, SecurityTokenPointer()); + pTrack->setAudioProperties( + mixxx::audio::ChannelCount(2), + mixxx::audio::SampleRate(44100), + mixxx::audio::Bitrate(), + mixxx::Duration::fromSeconds(180)); + return pTrack; } void loadTrack(TrackPointer pTrack) { @@ -163,7 +169,6 @@ TEST_F(CueControlTest, LoadAutodetectedCues_QuantizeEnabled) { m_pQuantizeEnabled->set(1); TrackPointer pTrack = createTestTrack(); - pTrack->setSampleRate(44100); pTrack->setBpm(120.0); const int frameSize = 2; @@ -196,7 +201,6 @@ TEST_F(CueControlTest, LoadAutodetectedCues_QuantizeEnabledNoBeats) { m_pQuantizeEnabled->set(1); TrackPointer pTrack = createTestTrack(); - pTrack->setSampleRate(44100); pTrack->setBpm(0.0); pTrack->setCuePoint(CuePosition(100.0)); @@ -224,7 +228,6 @@ TEST_F(CueControlTest, LoadAutodetectedCues_QuantizeDisabled) { m_pQuantizeEnabled->set(0); TrackPointer pTrack = createTestTrack(); - pTrack->setSampleRate(44100); pTrack->setBpm(120.0); pTrack->setCuePoint(CuePosition(240.0)); diff --git a/src/test/enginebufferscalelineartest.cpp b/src/test/enginebufferscalelineartest.cpp index 7ba0d6ba7c5b..1cb4399392ab 100644 --- a/src/test/enginebufferscalelineartest.cpp +++ b/src/test/enginebufferscalelineartest.cpp @@ -74,7 +74,7 @@ class EngineBufferScaleLinearTest : public MixxxTest { void SetRate(double rate) { double tempoRatio = rate; double pitchRatio = rate; - m_pScaler->setSampleRate(44100); + m_pScaler->setSampleRate(mixxx::audio::SampleRate(44100)); m_pScaler->setScaleParameters( 1.0, &tempoRatio, &pitchRatio); } diff --git a/src/test/mockedenginebackendtest.h b/src/test/mockedenginebackendtest.h index 4a34c98524c1..8d33143673ec 100644 --- a/src/test/mockedenginebackendtest.h +++ b/src/test/mockedenginebackendtest.h @@ -57,6 +57,8 @@ class MockScaler : public EngineBufferScale { } private: + void onSampleRateChanged() override {} + double m_processedTempo; double m_processedPitch; }; diff --git a/src/test/nativeeffects_test.cpp b/src/test/nativeeffects_test.cpp index 9b899bc451aa..4a28fa19ad9b 100644 --- a/src/test/nativeeffects_test.cpp +++ b/src/test/nativeeffects_test.cpp @@ -73,7 +73,7 @@ TEST_F(EffectsBenchmarkTest, BM_BuiltInEffects_DefaultParameters_##EffectName) { ConfigKey("[Mixer Profile]", "HiEQFrequency"), 0., 22040); \ hiEqFrequency.setDefaultValue(2500.0); \ mixxx::EngineParameters bufferParameters( \ - mixxx::AudioSignal::SampleRate(44100), \ + mixxx::audio::SampleRate(44100), \ state.range_x()); \ benchmarkBuiltInEffectDefaultParameters( \ bufferParameters, &state, m_pEffectsManager); \ @@ -94,4 +94,3 @@ DECLARE_EFFECT_BENCHMARK(ReverbEffect) } // namespace #endif - diff --git a/src/test/searchqueryparsertest.cpp b/src/test/searchqueryparsertest.cpp index 72f78805e059..75fc8584382e 100644 --- a/src/test/searchqueryparsertest.cpp +++ b/src/test/searchqueryparsertest.cpp @@ -7,6 +7,16 @@ #include "library/searchqueryparser.h" #include "util/assert.h" +TrackPointer newTestTrack(int sampleRate) { + TrackPointer pTrack(Track::newTemporary()); + pTrack->setAudioProperties( + mixxx::audio::ChannelCount(2), + mixxx::audio::SampleRate(sampleRate), + mixxx::audio::Bitrate(), + mixxx::Duration::fromSeconds(180)); + return pTrack; +} + class SearchQueryParserTest : public LibraryTest { protected: SearchQueryParserTest() @@ -360,8 +370,7 @@ TEST_F(SearchQueryParserTest, NumericFilter) { auto pQuery( m_parser.parseQuery("bpm:127.12", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setBpm(127); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setBpm(127.12); @@ -380,8 +389,7 @@ TEST_F(SearchQueryParserTest, NumericFilterEmpty) { auto pQuery( m_parser.parseQuery("bpm:", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setBpm(127); EXPECT_TRUE(pQuery->match(pTrack)); @@ -398,8 +406,7 @@ TEST_F(SearchQueryParserTest, NumericFilterNegation) { auto pQuery( m_parser.parseQuery("-bpm:127.12", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setBpm(127); EXPECT_TRUE(pQuery->match(pTrack)); pTrack->setBpm(127.12); @@ -418,8 +425,7 @@ TEST_F(SearchQueryParserTest, NumericFilterAllowsSpace) { auto pQuery( m_parser.parseQuery("bpm: 127.12", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setBpm(127); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setBpm(127.12); @@ -438,8 +444,7 @@ TEST_F(SearchQueryParserTest, NumericFilterOperators) { auto pQuery( m_parser.parseQuery("bpm:>127.12", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setBpm(127.12); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setBpm(127.13); @@ -485,8 +490,7 @@ TEST_F(SearchQueryParserTest, NumericRangeFilter) { auto pQuery( m_parser.parseQuery("bpm:127.12-129", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setBpm(125); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setBpm(127.12); @@ -508,8 +512,7 @@ TEST_F(SearchQueryParserTest, MultipleFilters) { m_parser.parseQuery("bpm:127.12-129 artist:\"com truise\" Colorvision", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setBpm(128); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setArtist("Com Truise"); @@ -531,7 +534,7 @@ TEST_F(SearchQueryParserTest, ExtraFilterAppended) { auto pQuery( m_parser.parseQuery("asdf", searchColumns, "1 > 2")); - TrackPointer pTrack(Track::newTemporary()); + TrackPointer pTrack = newTestTrack(44100); pTrack->setArtist("zxcv"); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setArtist("asdf"); @@ -550,8 +553,7 @@ TEST_F(SearchQueryParserTest, HumanReadableDurationSearch) { auto pQuery( m_parser.parseQuery("duration:1:30", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setDuration(91); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setDuration(90); @@ -590,8 +592,7 @@ TEST_F(SearchQueryParserTest, HumanReadableDurationSearchWithOperators) { auto pQuery( m_parser.parseQuery("duration:>1:30", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setDuration(89); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setDuration(91); @@ -690,8 +691,7 @@ TEST_F(SearchQueryParserTest, HumanReadableDurationSearchwithRangeFilter) { auto pQuery( m_parser.parseQuery("duration:2:30-3:20", searchColumns, "")); - TrackPointer pTrack(Track::newTemporary()); - pTrack->setSampleRate(44100); + TrackPointer pTrack = newTestTrack(44100); pTrack->setDuration(80); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setDuration(150); @@ -704,7 +704,6 @@ TEST_F(SearchQueryParserTest, HumanReadableDurationSearchwithRangeFilter) { qPrintable(pQuery->toSql())); pQuery = m_parser.parseQuery("duration:2:30-200", searchColumns, ""); - pTrack->setSampleRate(44100); pTrack->setDuration(80); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setDuration(150); @@ -717,7 +716,6 @@ TEST_F(SearchQueryParserTest, HumanReadableDurationSearchwithRangeFilter) { qPrintable(pQuery->toSql())); pQuery = m_parser.parseQuery("duration:150-200", searchColumns, ""); - pTrack->setSampleRate(44100); pTrack->setDuration(80); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setDuration(150); @@ -730,7 +728,6 @@ TEST_F(SearchQueryParserTest, HumanReadableDurationSearchwithRangeFilter) { qPrintable(pQuery->toSql())); pQuery = m_parser.parseQuery("duration:2m30s-3m20s", searchColumns, ""); - pTrack->setSampleRate(44100); pTrack->setDuration(80); EXPECT_FALSE(pQuery->match(pTrack)); pTrack->setDuration(150); diff --git a/src/test/trackmetadata_test.cpp b/src/test/trackmetadata_test.cpp index 62115a259f42..f0a8bee176d8 100644 --- a/src/test/trackmetadata_test.cpp +++ b/src/test/trackmetadata_test.cpp @@ -66,10 +66,10 @@ TEST_F(TrackMetadataTest, mergeImportedMetadata) { // Existing track metadata (stored in the database) without extra properties mixxx::TrackRecord oldTrackRecord; mixxx::TrackMetadata& oldTrackMetadata = oldTrackRecord.refMetadata(); - oldTrackMetadata.setBitrate(mixxx::AudioSource::Bitrate(100)); - oldTrackMetadata.setChannels(mixxx::AudioSignal::ChannelCount(1)); + oldTrackMetadata.setBitrate(mixxx::audio::Bitrate(100)); + oldTrackMetadata.setChannelCount(mixxx::audio::ChannelCount(1)); oldTrackMetadata.setDuration(mixxx::Duration::fromSeconds(60)); - oldTrackMetadata.setSampleRate(mixxx::AudioSignal::SampleRate(10000)); + oldTrackMetadata.setSampleRate(mixxx::audio::SampleRate(10000)); mixxx::TrackInfo& oldTrackInfo = oldTrackMetadata.refTrackInfo(); oldTrackInfo.setArtist("old artist"); oldTrackInfo.setBpm(mixxx::Bpm(100)); @@ -89,10 +89,10 @@ TEST_F(TrackMetadataTest, mergeImportedMetadata) { // Imported track metadata (from file tags) with extra properties mixxx::TrackMetadata newTrackMetadata; - newTrackMetadata.setBitrate(mixxx::AudioSource::Bitrate(200)); - newTrackMetadata.setChannels(mixxx::AudioSignal::ChannelCount(2)); + newTrackMetadata.setBitrate(mixxx::audio::Bitrate(200)); + newTrackMetadata.setChannelCount(mixxx::audio::ChannelCount(2)); newTrackMetadata.setDuration(mixxx::Duration::fromSeconds(120)); - newTrackMetadata.setSampleRate(mixxx::AudioSignal::SampleRate(20000)); + newTrackMetadata.setSampleRate(mixxx::audio::SampleRate(20000)); mixxx::TrackInfo& newTrackInfo = newTrackMetadata.refTrackInfo(); newTrackInfo.setArtist("new artist"); newTrackInfo.setBpm(mixxx::Bpm(200)); @@ -150,7 +150,7 @@ TEST_F(TrackMetadataTest, mergeImportedMetadata) { mixxx::TrackMetadata& mergedTrackMetadata = mergedTrackRecord.refMetadata(); EXPECT_EQ(oldTrackMetadata.getBitrate(), mergedTrackMetadata.getBitrate()); - EXPECT_EQ(oldTrackMetadata.getChannels(), mergedTrackMetadata.getChannels()); + EXPECT_EQ(oldTrackMetadata.getChannelCount(), mergedTrackMetadata.getChannelCount()); EXPECT_EQ(oldTrackMetadata.getDuration(), mergedTrackMetadata.getDuration()); EXPECT_EQ(oldTrackMetadata.getSampleRate(), mergedTrackMetadata.getSampleRate()); mixxx::TrackInfo& mergedTrackInfo = mergedTrackMetadata.refTrackInfo(); diff --git a/src/track/albuminfo.cpp b/src/track/albuminfo.cpp index 232de95ea4eb..fdd72f1fc2fb 100644 --- a/src/track/albuminfo.cpp +++ b/src/track/albuminfo.cpp @@ -18,7 +18,7 @@ bool operator==(const AlbumInfo& lhs, const AlbumInfo& rhs) { } QDebug operator<<(QDebug dbg, const AlbumInfo& arg) { - dbg << '{'; + dbg << "AlbumInfo{"; arg.dbgArtist(dbg); #if defined(__EXTRA_METADATA__) arg.dbgCopyright(dbg); diff --git a/src/track/albuminfo.h b/src/track/albuminfo.h index f4b45125aa07..4c9da32339f6 100644 --- a/src/track/albuminfo.h +++ b/src/track/albuminfo.h @@ -11,7 +11,7 @@ namespace mixxx { class AlbumInfo final { - // Album and release properties (in alphabetical order) + // Properties in alphabetical order PROPERTY_SET_BYVAL_GET_BYREF(QString, artist, Artist) #if defined(__EXTRA_METADATA__) PROPERTY_SET_BYVAL_GET_BYREF(QString, copyright, Copyright) diff --git a/src/track/cue.cpp b/src/track/cue.cpp index 7ba0ad99251a..5cdb1fee6c39 100644 --- a/src/track/cue.cpp +++ b/src/track/cue.cpp @@ -15,8 +15,8 @@ namespace { inline std::optional positionSamplesToMillis( double positionSamples, - mixxx::AudioSignal::SampleRate sampleRate) { - VERIFY_OR_DEBUG_ASSERT(sampleRate.valid()) { + mixxx::audio::SampleRate sampleRate) { + VERIFY_OR_DEBUG_ASSERT(sampleRate.isValid()) { return Cue::kNoPosition; } if (positionSamples == Cue::kNoPosition) { @@ -28,8 +28,8 @@ inline std::optional positionSamplesToMillis( inline double positionMillisToSamples( std::optional positionMillis, - mixxx::AudioSignal::SampleRate sampleRate) { - VERIFY_OR_DEBUG_ASSERT(sampleRate.valid()) { + mixxx::audio::SampleRate sampleRate) { + VERIFY_OR_DEBUG_ASSERT(sampleRate.isValid()) { return Cue::kNoPosition; } if (!positionMillis) { @@ -87,7 +87,7 @@ Cue::Cue( Cue::Cue( const mixxx::CueInfo& cueInfo, - mixxx::AudioSignal::SampleRate sampleRate) + mixxx::audio::SampleRate sampleRate) : m_bDirty(false), m_iId(-1), m_type(cueInfo.getType()), @@ -105,7 +105,7 @@ Cue::Cue( } mixxx::CueInfo Cue::getCueInfo( - mixxx::AudioSignal::SampleRate sampleRate) const { + mixxx::audio::SampleRate sampleRate) const { QMutexLocker lock(&m_mutex); return mixxx::CueInfo( m_type, diff --git a/src/track/cue.h b/src/track/cue.h index 14f605c28f73..fdda9fc06341 100644 --- a/src/track/cue.h +++ b/src/track/cue.h @@ -24,7 +24,7 @@ class Cue : public QObject { Cue(); Cue( const mixxx::CueInfo& cueInfo, - mixxx::AudioSignal::SampleRate sampleRate); + mixxx::audio::SampleRate sampleRate); ~Cue() override = default; bool isDirty() const; @@ -56,7 +56,7 @@ class Cue : public QObject { double getEndPosition() const; mixxx::CueInfo getCueInfo( - mixxx::AudioSignal::SampleRate sampleRate) const; + mixxx::audio::SampleRate sampleRate) const; signals: void updated(); diff --git a/src/track/track.cpp b/src/track/track.cpp index 27e04c8282ff..00c907f0bb6a 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -357,7 +357,19 @@ void Track::setDateAdded(const QDateTime& dateAdded) { void Track::setDuration(mixxx::Duration duration) { QMutexLocker lock(&m_qMutex); - if (compareAndSet(&m_record.refMetadata().refDuration(), duration)) { + VERIFY_OR_DEBUG_ASSERT(!m_streamInfo || + m_streamInfo->getDuration() <= mixxx::Duration::empty() || + m_streamInfo->getDuration() == duration) { + kLogger.warning() + << "Cannot override stream duration:" + << m_streamInfo->getDuration() + << "->" + << duration; + return; + } + if (compareAndSet( + &m_record.refMetadata().refDuration(), + duration)) { markDirtyAndUnlock(&lock); } } @@ -576,28 +588,14 @@ void Track::setType(const QString& sType) { } } -void Track::setSampleRate(int iSampleRate) { - QMutexLocker lock(&m_qMutex); - if (compareAndSet(&m_record.refMetadata().refSampleRate(), mixxx::AudioSignal::SampleRate(iSampleRate))) { - markDirtyAndUnlock(&lock); - } -} - int Track::getSampleRate() const { QMutexLocker lock(&m_qMutex); return m_record.getMetadata().getSampleRate(); } -void Track::setChannels(int iChannels) { - QMutexLocker lock(&m_qMutex); - if (compareAndSet(&m_record.refMetadata().refChannels(), mixxx::AudioSignal::ChannelCount(iChannels))) { - markDirtyAndUnlock(&lock); - } -} - int Track::getChannels() const { QMutexLocker lock(&m_qMutex); - return m_record.getMetadata().getChannels(); + return m_record.getMetadata().getChannelCount(); } int Track::getBitrate() const { @@ -611,7 +609,20 @@ QString Track::getBitrateText() const { void Track::setBitrate(int iBitrate) { QMutexLocker lock(&m_qMutex); - if (compareAndSet(&m_record.refMetadata().refBitrate(), mixxx::AudioSource::Bitrate(iBitrate))) { + const mixxx::audio::Bitrate bitrate(iBitrate); + VERIFY_OR_DEBUG_ASSERT(!m_streamInfo || + !m_streamInfo->getBitrate().isValid() || + m_streamInfo->getBitrate() == bitrate) { + kLogger.warning() + << "Cannot override stream bitrate:" + << m_streamInfo->getBitrate() + << "->" + << bitrate; + return; + } + if (compareAndSet( + &m_record.refMetadata().refBitrate(), + bitrate)) { markDirtyAndUnlock(&lock); } } @@ -804,7 +815,7 @@ void Track::setCuePoints(const QList& cuePoints) { void Track::importCuePoints(const QList& cueInfos) { TrackId trackId; - mixxx::AudioSignal::SampleRate sampleRate; + mixxx::audio::SampleRate sampleRate; { QMutexLocker lock(&m_qMutex); trackId = m_record.getId(); @@ -1122,3 +1133,56 @@ ExportTrackMetadataResult Track::exportMetadata( return ExportTrackMetadataResult::Failed; } } + +void Track::setAudioProperties( + mixxx::audio::ChannelCount channelCount, + mixxx::audio::SampleRate sampleRate, + mixxx::audio::Bitrate bitrate, + mixxx::Duration duration) { + QMutexLocker lock(&m_qMutex); + DEBUG_ASSERT(!m_streamInfo); + bool dirty = false; + if (compareAndSet( + &m_record.refMetadata().refChannelCount(), + channelCount)) { + dirty = true; + } + if (compareAndSet( + &m_record.refMetadata().refSampleRate(), + sampleRate)) { + dirty = true; + } + if (compareAndSet( + &m_record.refMetadata().refBitrate(), + bitrate)) { + dirty = true; + } + if (compareAndSet( + &m_record.refMetadata().refDuration(), + duration)) { + dirty = true; + } + if (dirty) { + markDirtyAndUnlock(&lock); + } +} + +void Track::updateAudioPropertiesFromStream( + mixxx::audio::StreamInfo&& streamInfo) { + QMutexLocker lock(&m_qMutex); + VERIFY_OR_DEBUG_ASSERT(!m_streamInfo || + *m_streamInfo == streamInfo) { + kLogger.warning() + << "Varying stream properties:" + << *m_streamInfo + << "->" + << streamInfo; + } + bool updated = m_record.refMetadata().updateAudioPropertiesFromStream( + streamInfo); + m_streamInfo = std::make_optional(std::move(streamInfo)); + // TODO: Continue deferred import of pending CueInfo objects + if (updated) { + markDirtyAndUnlock(&lock); + } +} diff --git a/src/track/track.h b/src/track/track.h index 78965cf65dfa..530a1d3e2a4f 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -5,6 +5,7 @@ #include #include +#include "audio/streaminfo.h" #include "track/beats.h" #include "track/cue.h" #include "track/trackfile.h" @@ -68,7 +69,7 @@ class Track : public QObject { Q_PROPERTY(double bpm READ getBpm WRITE setBpm) Q_PROPERTY(QString bpmFormatted READ getBpmText STORED false) Q_PROPERTY(QString key READ getKeyText WRITE setKeyText) - Q_PROPERTY(double duration READ getDuration WRITE setDuration) + Q_PROPERTY(double duration READ getDuration) Q_PROPERTY(QString durationFormatted READ getDurationTextSeconds STORED false) Q_PROPERTY(QString durationFormattedCentiseconds READ getDurationTextCentiseconds STORED false) Q_PROPERTY(QString durationFormattedMilliseconds READ getDurationTextMilliseconds STORED false) @@ -99,13 +100,9 @@ class Track : public QObject { void setType(const QString&); QString getType() const; - // Set number of channels - void setChannels(int iChannels); // Get number of channels int getChannels() const; - // Set sample rate - void setSampleRate(int iSampleRate); // Get sample rate int getSampleRate() const; @@ -315,6 +312,12 @@ class Track : public QObject { void markForMetadataExport(); bool isMarkedForMetadataExport() const; + void setAudioProperties( + mixxx::audio::ChannelCount channelCount, + mixxx::audio::SampleRate sampleRate, + mixxx::audio::Bitrate bitrate, + mixxx::Duration duration); + signals: void waveformUpdated(); void waveformSummaryUpdated(); @@ -366,6 +369,13 @@ class Track : public QObject { ExportTrackMetadataResult exportMetadata( mixxx::MetadataSourcePointer pMetadataSource); + // Information about the actual properties of the + // audio stream is only available after opening it. + // On this occasion the audio properties of the track + // need to be updated to reflect these values. + void updateAudioPropertiesFromStream( + mixxx::audio::StreamInfo&& streamInfo); + // Mutex protecting access to object mutable QMutex m_qMutex; @@ -384,6 +394,11 @@ class Track : public QObject { // the metadata. bool m_bMarkedForMetadataExport; + // Reliable information about the PCM audio stream + // that only becomes available when opening the + // corresponding file. + std::optional m_streamInfo; + // The list of cue points for the track QList m_cuePoints; diff --git a/src/track/trackinfo.cpp b/src/track/trackinfo.cpp index 4adcdee65201..203bb1ce2819 100644 --- a/src/track/trackinfo.cpp +++ b/src/track/trackinfo.cpp @@ -88,7 +88,7 @@ bool TrackInfo::compareEq( } QDebug operator<<(QDebug dbg, const TrackInfo& arg) { - dbg << '{'; + dbg << "TrackInfo{"; arg.dbgArtist(dbg); arg.dbgBpm(dbg); arg.dbgComment(dbg); diff --git a/src/track/trackinfo.h b/src/track/trackinfo.h index 367a4eb53d3f..9f784a181bd0 100644 --- a/src/track/trackinfo.h +++ b/src/track/trackinfo.h @@ -13,7 +13,7 @@ namespace mixxx { class TrackInfo final { - // Track properties (in alphabetical order) + // Properties in alphabetical order PROPERTY_SET_BYVAL_GET_BYREF(QString, artist, Artist) PROPERTY_SET_BYVAL_GET_BYREF(Bpm, bpm, Bpm) PROPERTY_SET_BYVAL_GET_BYREF(QString, comment, Comment) diff --git a/src/track/trackmetadata.cpp b/src/track/trackmetadata.cpp index b372430a186d..8085e260b557 100644 --- a/src/track/trackmetadata.cpp +++ b/src/track/trackmetadata.cpp @@ -1,9 +1,81 @@ #include "track/trackmetadata.h" +#include "audio/streaminfo.h" +#include "util/logger.h" + namespace mixxx { +namespace { + +const Logger kLogger("TrackMetadata"); + +} // anonymous namespace + /*static*/ constexpr int TrackMetadata::kCalendarYearInvalid; +bool TrackMetadata::updateAudioPropertiesFromStream( + const audio::StreamInfo& streamInfo) { + bool changed = false; + const auto streamChannelCount = + streamInfo.getSignalInfo().getChannelCount(); + if (streamChannelCount.isValid() && + streamChannelCount != getChannelCount()) { + if (getChannelCount().isValid()) { + kLogger.debug() + << "Modifying channel count:" + << getChannelCount() + << "->" + << streamChannelCount; + } + setChannelCount(streamChannelCount); + changed = true; + } + const auto streamSampleRate = + streamInfo.getSignalInfo().getSampleRate(); + if (streamSampleRate.isValid() && + streamSampleRate != getSampleRate()) { + if (getSampleRate().isValid()) { + kLogger.debug() + << "Modifying sample rate:" + << getSampleRate() + << "->" + << streamSampleRate; + } + setSampleRate(streamSampleRate); + changed = true; + } + const auto streamBitrate = + streamInfo.getBitrate(); + if (streamBitrate.isValid() && + streamBitrate != getBitrate()) { + if (getBitrate().isValid()) { + kLogger.debug() + << "Modifying bitrate:" + << getBitrate() + << "->" + << streamBitrate; + } + setBitrate(streamBitrate); + changed = true; + } + const auto streamDuration = + streamInfo.getDuration(); + if (streamDuration > Duration::empty() && + streamDuration != getDuration()) { + if (getDuration() > Duration::empty()) { + kLogger.debug() + << "Modifying duration:" + << getDuration() + << "->" + << streamDuration; + } + setDuration(streamDuration); + changed = true; + } + return changed; +} + + int TrackMetadata::parseCalendarYear(QString year, bool* pValid) { const QDateTime dateTime(parseDateTime(year)); if (0 < dateTime.date().year()) { @@ -83,20 +155,20 @@ bool TrackMetadata::anyFileTagsModified( } bool operator==(const TrackMetadata& lhs, const TrackMetadata& rhs) { - return (lhs.getAlbumInfo() == rhs.getAlbumInfo()) && - (lhs.getTrackInfo() == rhs.getTrackInfo()) && - (lhs.getBitrate() == rhs.getBitrate()) && - (lhs.getChannels() == rhs.getChannels()) && - (lhs.getDuration() == rhs.getDuration()) && - (lhs.getSampleRate() == rhs.getSampleRate()); + return lhs.getAlbumInfo() == rhs.getAlbumInfo() && + lhs.getTrackInfo() == rhs.getTrackInfo() && + lhs.getChannelCount() == rhs.getChannelCount() && + lhs.getSampleRate() == rhs.getSampleRate() && + lhs.getBitrate() == rhs.getBitrate() && + lhs.getDuration() == rhs.getDuration(); } QDebug operator<<(QDebug dbg, const TrackMetadata& arg) { - dbg << '{'; + dbg << "TrackMetadata{"; arg.dbgTrackInfo(dbg); arg.dbgAlbumInfo(dbg); arg.dbgBitrate(dbg); - arg.dbgChannels(dbg); + arg.dbgChannelCount(dbg); arg.dbgDuration(dbg); arg.dbgSampleRate(dbg); dbg << '}'; diff --git a/src/track/trackmetadata.h b/src/track/trackmetadata.h index 9ca165c4bed2..07bdc30b22ae 100644 --- a/src/track/trackmetadata.h +++ b/src/track/trackmetadata.h @@ -2,21 +2,27 @@ #include +#include "audio/types.h" #include "track/albuminfo.h" #include "track/trackinfo.h" - namespace mixxx { +namespace audio { + +class StreamInfo; + +} // namespace audio + class TrackMetadata final { // Audio properties - // - read-only - // - stored file tags - // - adjusted by audio decoder AFTER import from file tags - PROPERTY_SET_BYVAL_GET_BYREF(AudioSource::Bitrate, bitrate, Bitrate) - PROPERTY_SET_BYVAL_GET_BYREF(AudioSignal::ChannelCount, channels, Channels) - PROPERTY_SET_BYVAL_GET_BYREF(Duration, duration, Duration) - PROPERTY_SET_BYVAL_GET_BYREF(AudioSignal::SampleRate, sampleRate, SampleRate) + // - read-only + // - stored in file tags + // - adjusted when opening the audio stream (if available) + PROPERTY_SET_BYVAL_GET_BYREF(audio::ChannelCount, channels, ChannelCount) + PROPERTY_SET_BYVAL_GET_BYREF(audio::SampleRate, sampleRate, SampleRate) + PROPERTY_SET_BYVAL_GET_BYREF(audio::Bitrate, bitrate, Bitrate) + PROPERTY_SET_BYVAL_GET_BYREF(Duration, duration, Duration) // Track properties // - read-write @@ -24,7 +30,7 @@ class TrackMetadata final { PROPERTY_SET_BYVAL_GET_BYREF(AlbumInfo, albumInfo, AlbumInfo) PROPERTY_SET_BYVAL_GET_BYREF(TrackInfo, trackInfo, TrackInfo) -public: + public: TrackMetadata() = default; TrackMetadata(TrackMetadata&&) = default; TrackMetadata(const TrackMetadata&) = default; @@ -33,6 +39,9 @@ class TrackMetadata final { TrackMetadata& operator=(TrackMetadata&&) = default; TrackMetadata& operator=(const TrackMetadata&) = default; + bool updateAudioPropertiesFromStream( + const audio::StreamInfo& streamInfo); + // Adjusts floating-point values to match their string representation // in file tags to account for rounding errors. void normalizeBeforeExport(); @@ -72,8 +81,7 @@ class TrackMetadata final { bool operator==(const TrackMetadata& lhs, const TrackMetadata& rhs); -inline -bool operator!=(const TrackMetadata& lhs, const TrackMetadata& rhs) { +inline bool operator!=(const TrackMetadata& lhs, const TrackMetadata& rhs) { return !(lhs == rhs); } diff --git a/src/track/trackmetadatataglib.cpp b/src/track/trackmetadatataglib.cpp index d70673fee28b..d0c217cf4745 100644 --- a/src/track/trackmetadatataglib.cpp +++ b/src/track/trackmetadatataglib.cpp @@ -2,8 +2,8 @@ #include +#include "audio/streaminfo.h" #include "track/tracknumbers.h" - #include "util/assert.h" #include "util/compatibility.h" #include "util/duration.h" @@ -484,9 +484,12 @@ void readAudioProperties( // the audio data for this track. Often those properties // stored in tags don't match with the corresponding // audio data in the file. - pTrackMetadata->setChannels(AudioSignal::ChannelCount(audioProperties.channels())); - pTrackMetadata->setSampleRate(AudioSignal::SampleRate(audioProperties.sampleRate())); - pTrackMetadata->setBitrate(AudioSource::Bitrate(audioProperties.bitrate())); + pTrackMetadata->setChannelCount( + audio::ChannelCount(audioProperties.channels())); + pTrackMetadata->setSampleRate( + audio::SampleRate(audioProperties.sampleRate())); + pTrackMetadata->setBitrate( + audio::Bitrate(audioProperties.bitrate())); #if (TAGLIB_HAS_LENGTH_IN_MILLISECONDS) const auto duration = Duration::fromMillis(audioProperties.lengthInMilliseconds()); #else diff --git a/src/util/audiosignal.cpp b/src/util/audiosignal.cpp index 50ac1204544e..7df77ca474ea 100644 --- a/src/util/audiosignal.cpp +++ b/src/util/audiosignal.cpp @@ -2,7 +2,6 @@ #include "util/logger.h" - namespace mixxx { namespace { @@ -11,73 +10,61 @@ const Logger kLogger("AudioSignal"); } // anonymous namespace -bool AudioSignal::setChannelCount(ChannelCount channelCount) { - if (channelCount < ChannelCount()) { +bool AudioSignal::setChannelCount(audio::ChannelCount channelCount) { + if (channelCount < audio::ChannelCount()) { kLogger.warning() << "Invalid channel count" << channelCount; return false; // abort } else { - m_channelCount = channelCount; + m_signalInfo.setChannelCount(channelCount); return true; } } -bool AudioSignal::setSampleRate(SampleRate sampleRate) { - if (sampleRate < SampleRate()) { +bool AudioSignal::setSampleRate(audio::SampleRate sampleRate) { + if (sampleRate < audio::SampleRate()) { kLogger.warning() << "Invalid sample rate" << sampleRate; return false; // abort } else { - m_sampleRate = sampleRate; + m_signalInfo.setSampleRate(sampleRate); return true; } } bool AudioSignal::verifyReadable() const { bool result = true; - if (!channelCount().valid()) { + if (!channelCount().isValid()) { kLogger.warning() << "Invalid number of channels:" << channelCount() << "is out of range [" - << ChannelCount::min() + << audio::ChannelCount::min() << "," - << ChannelCount::max() + << audio::ChannelCount::max() << "]"; result = false; } - if (!sampleRate().valid()) { + if (!sampleRate().isValid()) { kLogger.warning() - << "Invalid sample rate [Hz]:" + << "Invalid sample rate:" << sampleRate() << "is out of range [" - << SampleRate::min() + << audio::SampleRate::min() << "," - << SampleRate::max() + << audio::SampleRate::max() << "]"; result = false; } return result; } -QDebug operator<<(QDebug dbg, AudioSignal::SampleLayout arg) { - switch (arg) { - case AudioSignal::SampleLayout::Planar: - return dbg << "Planar"; - case AudioSignal::SampleLayout::Interleaved: - return dbg << "Interleaved"; - } - DEBUG_ASSERT(!"unreachable code"); - return dbg; -} - QDebug operator<<(QDebug dbg, const AudioSignal& arg) { - return dbg << "AudioSignal{" - << "sampleLayout:" << arg.sampleLayout() - << "channelCount:" << arg.channelCount() - << "sampleRate:" << arg.sampleRate() + return dbg + << "AudioSignal{" + << arg.getSignalInfo() << "}"; } diff --git a/src/util/audiosignal.h b/src/util/audiosignal.h index 754157d2d586..21a17637b87f 100644 --- a/src/util/audiosignal.h +++ b/src/util/audiosignal.h @@ -1,8 +1,7 @@ #pragma once +#include "audio/signalinfo.h" #include "util/assert.h" -#include "util/types.h" - namespace mixxx { @@ -15,139 +14,31 @@ namespace mixxx { // over time. Therefore all functions for modifying individual properties // are declared as "protected" and are only available from derived classes. class AudioSignal { -public: - enum class ChannelLayout { - Unknown, - Mono, // 1 channel - DualMono, // 2 channels with identical signals - Stereo, // 2 independent channels left/right - // ... - }; - - class ChannelCount { - private: - static constexpr SINT kValueDefault = 0; - - public: - static constexpr SINT kValueMin = 1; // lower bound (inclusive) - static constexpr SINT kValueMax = 255; // upper bound (inclusive, 8-bit unsigned integer) - - static constexpr ChannelCount min() { return ChannelCount(kValueMin); } - static constexpr ChannelCount max() { return ChannelCount(kValueMax); } - - static ChannelCount from(ChannelLayout channelLayout) { - switch (channelLayout) { - case ChannelLayout::Unknown: - return ChannelCount(); - case ChannelLayout::Mono: - return ChannelCount(1); - case ChannelLayout::DualMono: - return ChannelCount(1); - case ChannelLayout::Stereo: - return ChannelCount(2); - } - DEBUG_ASSERT(!"unreachable code"); - return ChannelCount(); - } - - explicit constexpr ChannelCount(SINT value = kValueDefault) - : m_value(value) { - } - explicit ChannelCount(ChannelLayout channelLayout) - : m_value(from(channelLayout).m_value) { - } - - bool valid() const { - return (kValueMin <= m_value) && (m_value <= kValueMax); - } - - /*implicit*/ constexpr operator SINT() const { - return m_value; - } - - private: - SINT m_value; - }; - - // Defines the ordering of how samples from multiple channels are - // stored in contiguous buffers: - // - Planar: Channel by channel - // - Interleaved: Frame by frame - // The samples from all channels that are coincident in time are - // called a "frame" (or more specific "sample frame"). - // - // Example: 10 stereo samples from left (L) and right (R) channel - // Planar layout: LLLLLLLLLLRRRRRRRRRR - // Interleaved layout: LRLRLRLRLRLRLRLRLRLR - enum class SampleLayout { - Planar, - Interleaved - }; - - class SampleRate { - private: - static constexpr SINT kValueDefault = 0; - - public: - static constexpr SINT kValueMin = 8000; // lower bound (inclusive, = minimum MP3 sample rate) - static constexpr SINT kValueMax = 192000; // upper bound (inclusive) - - static constexpr SampleRate min() { return SampleRate(kValueMin); } - static constexpr SampleRate max() { return SampleRate(kValueMax); } - - static constexpr const char* unit() { return "Hz"; } - - explicit constexpr SampleRate(SINT value = kValueDefault) - : m_value(value) { - } - - bool valid() const { - return (kValueMin <= m_value) && (m_value <= kValueMax); - } - - /*implicit*/ constexpr operator SINT() const { - return m_value; - } - - private: - SINT m_value; - }; - + public: explicit AudioSignal( - SampleLayout sampleLayout) - : m_sampleLayout(sampleLayout) { + audio::SampleLayout sampleLayout) + : m_signalInfo(std::make_optional(sampleLayout)) { } explicit AudioSignal( - SampleLayout sampleLayout, - ChannelCount channelCount, - SampleRate sampleRate) - : m_sampleLayout(sampleLayout) { - setChannelCount(channelCount); - setSampleRate(sampleRate); + audio::SignalInfo signalInfo) + : m_signalInfo(signalInfo) { + DEBUG_ASSERT(signalInfo.isValid()); } virtual ~AudioSignal() = default; - // Returns the ordering of samples in contiguous buffers. - SampleLayout sampleLayout() const { - return m_sampleLayout; + const audio::SignalInfo& getSignalInfo() const { + DEBUG_ASSERT(m_signalInfo.isValid()); + return m_signalInfo; } - // Returns the number of channels. - ChannelCount channelCount() const { - return m_channelCount; + audio::ChannelCount channelCount() const { + return getSignalInfo().getChannelCount(); } - // Returns the sample rate in Hz. The sample rate is defined as the - // number of samples per second for each channel. Please note that this - // does not equal the total number of samples per second in the stream! - // - // NOTE(uklotzde): I consciously avoided the term "sample rate", because - // that sounds like "number of samples per second" which is wrong for - // signals with more than a single channel and might be misleading! - SampleRate sampleRate() const { - return m_sampleRate; + audio::SampleRate sampleRate() const { + return getSignalInfo().getSampleRate(); } // Verifies various properties to ensure that the audio data is @@ -171,36 +62,29 @@ class AudioSignal { // Conversion: #samples / sample offset -> #frames / frame offset template inline T samples2frames(T samples) const { - DEBUG_ASSERT(channelCount().valid()); - DEBUG_ASSERT(0 == (samples % channelCount())); - return samples / channelCount(); + return getSignalInfo().samples2frames(samples); } // Conversion: #frames / frame offset -> #samples / sample offset template inline T frames2samples(T frames) const { - DEBUG_ASSERT(channelCount().valid()); - return frames * channelCount(); + return getSignalInfo().frames2samples(frames); } -protected: - bool setChannelCount(ChannelCount channelCount); + protected: + bool setChannelCount(audio::ChannelCount channelCount); bool setChannelCount(SINT channelCount) { - return setChannelCount(ChannelCount(channelCount)); + return setChannelCount(audio::ChannelCount(channelCount)); } - bool setSampleRate(SampleRate sampleRate); + bool setSampleRate(audio::SampleRate sampleRate); bool setSampleRate(SINT sampleRate) { - return setSampleRate(SampleRate(sampleRate)); + return setSampleRate(audio::SampleRate(sampleRate)); } -private: - ChannelCount m_channelCount; - SampleLayout m_sampleLayout; - SampleRate m_sampleRate; + private: + audio::SignalInfo m_signalInfo; }; -QDebug operator<<(QDebug dbg, AudioSignal::SampleLayout arg); - QDebug operator<<(QDebug dbg, const AudioSignal& arg); -} +} // namespace mixxx From dadc2cada290d8ac2d63fc1723217c8f45e585fa Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sun, 22 Mar 2020 11:13:25 +0100 Subject: [PATCH 124/393] Get rid of the Janus-headed AudioSignal base class --- CMakeLists.txt | 1 - build/depends.py | 1 - src/analyzer/analyzerthread.cpp | 6 +- src/analyzer/constants.h | 1 - .../cachingreader/cachingreaderchunk.cpp | 4 +- .../cachingreader/cachingreaderworker.cpp | 8 +- src/musicbrainz/chromaprinter.cpp | 19 ++- src/sources/audiosource.cpp | 145 +++++++++++++++--- src/sources/audiosource.h | 103 +++++++++---- src/sources/audiosourceproxy.h | 51 ++++++ src/sources/audiosourcestereoproxy.cpp | 57 ++++--- src/sources/audiosourcestereoproxy.h | 24 +-- src/sources/audiosourcetrackproxy.h | 66 +++----- src/sources/soundsourcecoreaudio.cpp | 19 ++- src/sources/soundsourceffmpeg.cpp | 50 +++--- src/sources/soundsourceflac.cpp | 34 ++-- src/sources/soundsourcem4a.cpp | 30 ++-- src/sources/soundsourcemediafoundation.cpp | 32 ++-- src/sources/soundsourcemediafoundation.h | 4 +- src/sources/soundsourcemodplug.cpp | 10 +- src/sources/soundsourcemp3.cpp | 38 ++--- src/sources/soundsourceoggvorbis.cpp | 12 +- src/sources/soundsourceopus.cpp | 22 +-- src/sources/soundsourcesndfile.cpp | 6 +- src/sources/soundsourcewv.cpp | 12 +- src/sources/v1/legacyaudiosourceadapter.cpp | 6 +- src/test/soundproxy_test.cpp | 47 +++--- src/track/cue.h | 2 +- src/util/audiosignal.cpp | 71 --------- src/util/audiosignal.h | 90 ----------- 30 files changed, 503 insertions(+), 468 deletions(-) create mode 100644 src/sources/audiosourceproxy.h delete mode 100644 src/util/audiosignal.cpp delete mode 100644 src/util/audiosignal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed9f31a04b33..61519430318b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -562,7 +562,6 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/track/tracknumbers.cpp src/track/trackrecord.cpp src/track/trackref.cpp - src/util/audiosignal.cpp src/util/autohidpi.cpp src/util/battery/battery.cpp src/util/cache.cpp diff --git a/build/depends.py b/build/depends.py index 0f114321971e..21c74600e893 100644 --- a/build/depends.py +++ b/build/depends.py @@ -1313,7 +1313,6 @@ def sources(self, build): "src/util/logger.cpp", "src/util/logging.cpp", "src/util/cmdlineargs.cpp", - "src/util/audiosignal.cpp", "src/util/widgethider.cpp", "src/util/autohidpi.cpp", "src/util/screensaver.cpp", diff --git a/src/analyzer/analyzerthread.cpp b/src/analyzer/analyzerthread.cpp index a6f7f0802ea3..88b714ddc2b1 100644 --- a/src/analyzer/analyzerthread.cpp +++ b/src/analyzer/analyzerthread.cpp @@ -145,7 +145,7 @@ void AnalyzerThread::doRun() { // Make sure not to short-circuit initialize(...) if (analyzer.initialize( m_currentTrack, - audioSource->sampleRate(), + audioSource->getSignalInfo().getSampleRate(), audioSource->frameLength() * mixxx::kAnalysisChannels)) { processTrack = true; } @@ -226,7 +226,9 @@ AnalyzerThread::AnalysisResult AnalyzerThread::analyzeAudioSource( mixxx::AudioSourceStereoProxy audioSourceProxy( audioSource, mixxx::kAnalysisFramesPerChunk); - DEBUG_ASSERT(audioSourceProxy.channelCount() == mixxx::kAnalysisChannels); + DEBUG_ASSERT( + audioSourceProxy.getSignalInfo().getChannelCount() == + mixxx::kAnalysisChannels); // Analysis starts now emitBusyProgress(kAnalyzerProgressNone); diff --git a/src/analyzer/constants.h b/src/analyzer/constants.h index c936de1515c2..d014aae440fa 100644 --- a/src/analyzer/constants.h +++ b/src/analyzer/constants.h @@ -1,7 +1,6 @@ #pragma once #include "engine/engine.h" -#include "util/audiosignal.h" namespace mixxx { diff --git a/src/engine/cachingreader/cachingreaderchunk.cpp b/src/engine/cachingreader/cachingreaderchunk.cpp index 9409ad97a187..a24c9605d965 100644 --- a/src/engine/cachingreader/cachingreaderchunk.cpp +++ b/src/engine/cachingreader/cachingreaderchunk.cpp @@ -66,7 +66,9 @@ mixxx::IndexRange CachingReaderChunk::bufferSampleFrames( mixxx::AudioSourceStereoProxy audioSourceProxy( pAudioSource, tempOutputBuffer); - DEBUG_ASSERT(audioSourceProxy.channelCount() == kChannels); + DEBUG_ASSERT( + audioSourceProxy.getSignalInfo().getChannelCount() == + kChannels); m_bufferedSampleFrames = audioSourceProxy.readSampleFrames( mixxx::WritableSampleFrames( diff --git a/src/engine/cachingreader/cachingreaderworker.cpp b/src/engine/cachingreader/cachingreaderworker.cpp index 5f2182acc478..236453638810 100644 --- a/src/engine/cachingreader/cachingreaderworker.cpp +++ b/src/engine/cachingreader/cachingreaderworker.cpp @@ -180,7 +180,8 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) { // Adjust the internal buffer const SINT tempReadBufferSize = - m_pAudioSource->frames2samples(CachingReaderChunk::kFrames); + m_pAudioSource->getSignalInfo().frames2samples( + CachingReaderChunk::kFrames); if (m_tempReadBuffer.size() != tempReadBufferSize) { mixxx::SampleBuffer(tempReadBufferSize).swap(m_tempReadBuffer); } @@ -194,7 +195,10 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) { const SINT sampleCount = CachingReaderChunk::frames2samples( m_pAudioSource->frameLength()); - emit trackLoaded(pTrack, m_pAudioSource->sampleRate(), sampleCount); + emit trackLoaded( + pTrack, + m_pAudioSource->getSignalInfo().getSampleRate(), + sampleCount); } void CachingReaderWorker::quitWait() { diff --git a/src/musicbrainz/chromaprinter.cpp b/src/musicbrainz/chromaprinter.cpp index c25acad437e2..7775926b4b28 100644 --- a/src/musicbrainz/chromaprinter.cpp +++ b/src/musicbrainz/chromaprinter.cpp @@ -37,7 +37,7 @@ QString calcFingerprint( mixxx::SampleBuffer sampleBuffer(math_max( fingerprintRange.length(), - audioSourceProxy.frames2samples(fingerprintRange.length()))); + audioSourceProxy.getSignalInfo().frames2samples(fingerprintRange.length()))); const auto readableSampleFrames = audioSourceProxy.readSampleFrames( mixxx::WritableSampleFrames( @@ -49,7 +49,7 @@ QString calcFingerprint( } std::vector fingerprintSamples( - audioSourceProxy.frames2samples( + audioSourceProxy.getSignalInfo().frames2samples( readableSampleFrames.frameLength())); // Convert floating-point to integer SampleUtil::convertFloat32ToS16( @@ -60,11 +60,17 @@ QString calcFingerprint( qDebug() << "reading file took" << timerReadingFile.elapsed().debugMillisWithUnit(); ChromaprintContext* ctx = chromaprint_new(CHROMAPRINT_ALGORITHM_DEFAULT); - chromaprint_start(ctx, audioSourceProxy.sampleRate(), audioSourceProxy.channelCount()); + chromaprint_start( + ctx, + audioSourceProxy.getSignalInfo().getSampleRate(), + audioSourceProxy.getSignalInfo().getChannelCount()); PerformanceTimer timerGeneratingFingerprint; timerGeneratingFingerprint.start(); - int success = chromaprint_feed(ctx, &fingerprintSamples[0], static_cast(fingerprintSamples.size())); + const int success = chromaprint_feed( + ctx, + &fingerprintSamples[0], + static_cast(fingerprintSamples.size())); chromaprint_finish(ctx); if (!success) { qWarning() << "Failed to generate fingerprint from sample data"; @@ -105,7 +111,8 @@ ChromaPrinter::ChromaPrinter(QObject* parent) QString ChromaPrinter::getFingerprint(TrackPointer pTrack) { mixxx::AudioSource::OpenParams config; - config.setChannelCount(2); // always stereo / 2 channels (see below) + // always stereo / 2 channels (see below) + config.setChannelCount(mixxx::audio::ChannelCount(2)); auto pAudioSource = SoundSourceProxy(pTrack).openAudioSource(config); if (!pAudioSource) { qDebug() @@ -118,7 +125,7 @@ QString ChromaPrinter::getFingerprint(TrackPointer pTrack) { pAudioSource->frameIndexRange(), mixxx::IndexRange::forward( pAudioSource->frameIndexMin(), - kFingerprintDuration * pAudioSource->sampleRate())); + kFingerprintDuration * pAudioSource->getSignalInfo().getSampleRate())); mixxx::AudioSourceStereoProxy audioSourceProxy( pAudioSource, fingerprintRange.length()); diff --git a/src/sources/audiosource.cpp b/src/sources/audiosource.cpp index 26142824eae7..2dca3e0aa871 100644 --- a/src/sources/audiosource.cpp +++ b/src/sources/audiosource.cpp @@ -12,7 +12,16 @@ const Logger kLogger("AudioSource"); AudioSource::AudioSource(QUrl url) : UrlResource(url), - AudioSignal(kSampleLayout) { + m_signalInfo(kSampleLayout) { +} + +AudioSource::AudioSource( + const AudioSource& inner, + const audio::SignalInfo& signalInfo) + : UrlResource(inner), + m_signalInfo(signalInfo), + m_bitrate(inner.m_bitrate), + m_frameIndexRange(inner.m_frameIndexRange) { } AudioSource::OpenResult AudioSource::open( @@ -56,14 +65,61 @@ bool AudioSource::initFrameIndexRangeOnce( return true; } +bool AudioSource::initChannelCountOnce( + audio::ChannelCount channelCount) { + if (!channelCount.isValid()) { + kLogger.warning() + << "Invalid channel count" + << channelCount; + return false; // abort + } + VERIFY_OR_DEBUG_ASSERT( + !m_signalInfo.getChannelCount().isValid() || + m_signalInfo.getChannelCount() == channelCount) { + kLogger.warning() + << "Channel count has already been initialized to" + << m_signalInfo.getChannelCount() + << "which differs from" + << channelCount; + return false; // abort + } + m_signalInfo.setChannelCount(channelCount); + return true; +} + +bool AudioSource::initSampleRateOnce( + audio::SampleRate sampleRate) { + if (!sampleRate.isValid()) { + kLogger.warning() + << "Invalid sample rate" + << sampleRate; + return false; // abort + } + VERIFY_OR_DEBUG_ASSERT( + !m_signalInfo.getSampleRate().isValid() || + m_signalInfo.getSampleRate() == sampleRate) { + kLogger.warning() + << "Sample rate has already been initialized to" + << m_signalInfo.getSampleRate() + << "which differs from" + << sampleRate; + return false; // abort + } + m_signalInfo.setSampleRate(sampleRate); + return true; +} + bool AudioSource::initBitrateOnce(audio::Bitrate bitrate) { + // Bitrate is optional and might be invalid (= audio::Bitrate()) if (bitrate < audio::Bitrate()) { kLogger.warning() << "Invalid bitrate" << bitrate; return false; // abort } - VERIFY_OR_DEBUG_ASSERT(!m_bitrate.isValid() || (m_bitrate == bitrate)) { + VERIFY_OR_DEBUG_ASSERT( + !m_bitrate.isValid() || + m_bitrate == bitrate) { kLogger.warning() << "Bitrate has already been initialized to" << m_bitrate @@ -76,14 +132,34 @@ bool AudioSource::initBitrateOnce(audio::Bitrate bitrate) { } bool AudioSource::verifyReadable() const { - bool result = AudioSignal::verifyReadable(); - if (frameIndexRange().empty()) { + bool result = true; + DEBUG_ASSERT(m_signalInfo.getSampleLayout()); + if (!m_signalInfo.getChannelCount().isValid()) { kLogger.warning() - << "No audio data available"; - // Don't set the result to false, even if reading from an empty source - // is pointless! + << "Invalid number of channels:" + << getSignalInfo().getChannelCount() + << "is out of range [" + << audio::ChannelCount::min() + << "," + << audio::ChannelCount::max() + << "]"; + result = false; + } + if (!m_signalInfo.getSampleRate().isValid()) { + kLogger.warning() + << "Invalid sample rate:" + << getSignalInfo().getSampleRate() + << "is out of range [" + << audio::SampleRate::min() + << "," + << audio::SampleRate::max() + << "]"; + result = false; } + DEBUG_ASSERT(result == m_signalInfo.isValid()); + // Bitrate is optional and might be invalid (= audio::Bitrate()) if (m_bitrate != audio::Bitrate()) { + // Non-default bitrate must be valid VERIFY_OR_DEBUG_ASSERT(m_bitrate.isValid()) { kLogger.warning() << "Invalid bitrate" @@ -93,6 +169,12 @@ bool AudioSource::verifyReadable() const { // to decode audio data! } } + if (frameIndexRange().empty()) { + kLogger.warning() + << "No audio data available"; + // Don't set the result to false, even if reading from an + // empty source is pointless! + } return result; } @@ -101,12 +183,19 @@ WritableSampleFrames AudioSource::clampWritableSampleFrames( const auto readableFrameIndexRange = clampFrameIndexRange(sampleFrames.frameIndexRange()); // adjust offset and length of the sample buffer - DEBUG_ASSERT(sampleFrames.frameIndexRange().start() <= readableFrameIndexRange.end()); + DEBUG_ASSERT( + sampleFrames.frameIndexRange().start() <= + readableFrameIndexRange.end()); auto writableFrameIndexRange = - IndexRange::between(sampleFrames.frameIndexRange().start(), readableFrameIndexRange.end()); + IndexRange::between( + sampleFrames.frameIndexRange().start(), + readableFrameIndexRange.end()); const SINT minSampleBufferCapacity = - frames2samples(writableFrameIndexRange.length()); - VERIFY_OR_DEBUG_ASSERT(sampleFrames.writableLength() >= minSampleBufferCapacity) { + m_signalInfo.frames2samples( + writableFrameIndexRange.length()); + VERIFY_OR_DEBUG_ASSERT( + sampleFrames.writableLength() >= + minSampleBufferCapacity) { kLogger.critical() << "Capacity of output buffer is too small" << sampleFrames.writableLength() @@ -118,20 +207,27 @@ WritableSampleFrames AudioSource::clampWritableSampleFrames( << writableFrameIndexRange; writableFrameIndexRange = writableFrameIndexRange.splitAndShrinkFront( - samples2frames(sampleFrames.writableLength())); + m_signalInfo.samples2frames( + sampleFrames.writableLength())); kLogger.warning() << "Reduced writable sample frames" << writableFrameIndexRange; } - DEBUG_ASSERT(readableFrameIndexRange.start() >= writableFrameIndexRange.start()); + DEBUG_ASSERT( + readableFrameIndexRange.start() >= + writableFrameIndexRange.start()); const SINT writableFrameOffset = - readableFrameIndexRange.start() - writableFrameIndexRange.start(); - writableFrameIndexRange.shrinkFront(writableFrameOffset); + readableFrameIndexRange.start() - + writableFrameIndexRange.start(); + writableFrameIndexRange.shrinkFront( + writableFrameOffset); return WritableSampleFrames( writableFrameIndexRange, SampleBuffer::WritableSlice( - sampleFrames.writableData(frames2samples(writableFrameOffset)), - frames2samples(writableFrameIndexRange.length()))); + sampleFrames.writableData( + m_signalInfo.frames2samples(writableFrameOffset)), + m_signalInfo.frames2samples( + writableFrameIndexRange.length()))); } ReadableSampleFrames AudioSource::readSampleFrames( @@ -156,17 +252,22 @@ ReadableSampleFrames AudioSource::readSampleFrames( // Adjust upper bound: Consider all audio data following // the read position until the end as unreadable shrinkedFrameIndexRange.shrinkBack( - shrinkedFrameIndexRange.end() - writable.frameIndexRange().start()); + shrinkedFrameIndexRange.end() - + writable.frameIndexRange().start()); } else { // Adjust lower bound of readable audio data - if (writable.frameIndexRange().start() < readable.frameIndexRange().start()) { + if (writable.frameIndexRange().start() < + readable.frameIndexRange().start()) { shrinkedFrameIndexRange.shrinkFront( - readable.frameIndexRange().start() - shrinkedFrameIndexRange.start()); + readable.frameIndexRange().start() - + shrinkedFrameIndexRange.start()); } // Adjust upper bound of readable audio data - if (writable.frameIndexRange().end() > readable.frameIndexRange().end()) { + if (writable.frameIndexRange().end() > + readable.frameIndexRange().end()) { shrinkedFrameIndexRange.shrinkBack( - shrinkedFrameIndexRange.end() - readable.frameIndexRange().end()); + shrinkedFrameIndexRange.end() - + readable.frameIndexRange().end()); } } DEBUG_ASSERT(shrinkedFrameIndexRange < m_frameIndexRange); diff --git a/src/sources/audiosource.h b/src/sources/audiosource.h index 461b9ea2b0c5..a5cb7379cd2f 100644 --- a/src/sources/audiosource.h +++ b/src/sources/audiosource.h @@ -3,7 +3,6 @@ #include "audio/streaminfo.h" #include "engine/engine.h" #include "sources/urlresource.h" -#include "util/audiosignal.h" #include "util/indexrange.h" #include "util/memory.h" #include "util/samplebuffer.h" @@ -139,7 +138,7 @@ class IAudioSourceReader { // // Audio sources are implicitly opened upon creation and // closed upon destruction. -class AudioSource : public UrlResource, public AudioSignal, public virtual /*implements*/ IAudioSourceReader { +class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSourceReader { public: virtual ~AudioSource() = default; @@ -182,31 +181,37 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp }; // Parameters for opening audio sources - class OpenParams : public AudioSignal { + class OpenParams { public: OpenParams() - : AudioSignal(kSampleLayout) { + : m_signalInfo(kSampleLayout) { } OpenParams( audio::ChannelCount channelCount, audio::SampleRate sampleRate) - : AudioSignal( - audio::SignalInfo( - channelCount, - sampleRate, - kSampleLayout)) { + : m_signalInfo( + channelCount, + sampleRate, + kSampleLayout) { } - using AudioSignal::setChannelCount; - using AudioSignal::setSampleRate; - }; + const audio::SignalInfo& getSignalInfo() const { + return m_signalInfo; + } - audio::StreamInfo getStreamInfo() const { - return audio::StreamInfo( - getSignalInfo(), - m_bitrate, - Duration::fromSeconds(getDuration())); - } + void setChannelCount( + audio::ChannelCount channelCount) { + m_signalInfo.setChannelCount(channelCount); + } + + void setSampleRate( + audio::SampleRate sampleRate) { + m_signalInfo.setSampleRate(sampleRate); + } + + private: + audio::SignalInfo m_signalInfo; + }; // Opens the AudioSource for reading audio data. // @@ -227,6 +232,22 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp // opened, has already been closed, or if opening has failed. virtual void close() = 0; + const audio::SignalInfo& getSignalInfo() const { + DEBUG_ASSERT(m_signalInfo.isValid()); + return m_signalInfo; + } + + const audio::Bitrate getBitrate() const { + return m_bitrate; + } + + audio::StreamInfo getStreamInfo() const { + return audio::StreamInfo( + getSignalInfo(), + getBitrate(), + Duration::fromSeconds(getDuration())); + } + // The total length of audio data is bounded and measured in frames. IndexRange frameIndexRange() const { return m_frameIndexRange; @@ -259,25 +280,38 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp // The actual duration in seconds. // Well defined only for valid files! inline bool hasDuration() const { - return sampleRate().isValid(); + return getSignalInfo().getSampleRate().isValid(); } inline double getDuration() const { DEBUG_ASSERT(hasDuration()); // prevents division by zero - return double(frameLength()) / double(sampleRate()); - } - - audio::Bitrate bitrate() const { - return m_bitrate; + return double(frameLength()) / double(getSignalInfo().getSampleRate()); } - bool verifyReadable() const override; + // Verifies various properties to ensure that the audio data is + // actually readable. Warning messages are logged for properties + // with invalid values for diagnostic purposes. + bool verifyReadable() const; ReadableSampleFrames readSampleFrames( WritableSampleFrames sampleFrames); protected: explicit AudioSource(QUrl url); - AudioSource(const AudioSource&) = default; + + bool initChannelCountOnce(audio::ChannelCount channelCount); + bool initChannelCountOnce(SINT channelCount) { + return initChannelCountOnce(audio::ChannelCount(channelCount)); + } + + bool initSampleRateOnce(audio::SampleRate sampleRate); + bool initSampleRateOnce(SINT sampleRate) { + return initSampleRateOnce(audio::SampleRate(sampleRate)); + } + + bool initBitrateOnce(audio::Bitrate bitrate); + bool initBitrateOnce(SINT bitrate) { + return initBitrateOnce(audio::Bitrate(bitrate)); + } bool initFrameIndexRangeOnce( IndexRange frameIndexRange); @@ -294,11 +328,6 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp that.adjustFrameIndexRange(frameIndexRange); } - bool initBitrateOnce(audio::Bitrate bitrate); - bool initBitrateOnce(SINT bitrate) { - return initBitrateOnce(audio::Bitrate(bitrate)); - } - // Tries to open the AudioSource for reading audio data according // to the "Template Method" design pattern. // @@ -325,10 +354,18 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp } private: + AudioSource(const AudioSource&) = delete; AudioSource(AudioSource&&) = delete; AudioSource& operator=(const AudioSource&) = delete; AudioSource& operator=(AudioSource&&) = delete; + // Ugly workaround for AudioSourceProxy to wrap + // an existing AudioSource. + friend class AudioSourceProxy; + AudioSource( + const AudioSource& inner, + const audio::SignalInfo& signalInfo); + WritableSampleFrames clampWritableSampleFrames( WritableSampleFrames sampleFrames) const; IndexRange clampFrameIndexRange( @@ -336,9 +373,11 @@ class AudioSource : public UrlResource, public AudioSignal, public virtual /*imp return intersect(frameIndexRange, this->frameIndexRange()); } - IndexRange m_frameIndexRange; + audio::SignalInfo m_signalInfo; audio::Bitrate m_bitrate; + + IndexRange m_frameIndexRange; }; typedef std::shared_ptr AudioSourcePointer; diff --git a/src/sources/audiosourceproxy.h b/src/sources/audiosourceproxy.h new file mode 100644 index 000000000000..33fa6978e760 --- /dev/null +++ b/src/sources/audiosourceproxy.h @@ -0,0 +1,51 @@ +#pragma once + +#include "sources/audiosource.h" + +namespace mixxx { + +class AudioSourceProxy : public AudioSource { + public: + explicit AudioSourceProxy( + AudioSourcePointer&& pAudioSource) + : AudioSourceProxy( + std::move(pAudioSource), + pAudioSource->getSignalInfo()) { + } + AudioSourceProxy( + AudioSourcePointer&& pAudioSource, + const audio::SignalInfo& signalInfo) + : AudioSource(*pAudioSource, signalInfo), + m_pAudioSource(std::move(pAudioSource)) { + } + + void close() override { + m_pAudioSource->close(); + } + + protected: + OpenResult tryOpen( + OpenMode mode, + const OpenParams& params) override { + return tryOpenOn(*m_pAudioSource, mode, params); + } + + ReadableSampleFrames readSampleFramesClamped( + WritableSampleFrames sampleFrames) override { + DEBUG_ASSERT(getSignalInfo() == m_pAudioSource->getSignalInfo()); + DEBUG_ASSERT(getBitrate() == m_pAudioSource->getBitrate()); + DEBUG_ASSERT(frameIndexRange() == m_pAudioSource->frameIndexRange()); + return readSampleFramesClampedOn(*m_pAudioSource, sampleFrames); + } + + void adjustFrameIndexRange( + IndexRange frameIndexRange) final { + // Ugly hack to keep both sources (inherited base + inner delegate) in sync! + AudioSource::adjustFrameIndexRange(frameIndexRange); + adjustFrameIndexRangeOn(*m_pAudioSource, frameIndexRange); + } + + const AudioSourcePointer m_pAudioSource; +}; + +} // namespace mixxx diff --git a/src/sources/audiosourcestereoproxy.cpp b/src/sources/audiosourcestereoproxy.cpp index 8571e203645b..95bdd0d1cbdd 100644 --- a/src/sources/audiosourcestereoproxy.cpp +++ b/src/sources/audiosourcestereoproxy.cpp @@ -9,26 +9,39 @@ namespace { const Logger kLogger("AudioSourceStereoProxy"); +constexpr audio::ChannelCount kChannelCount = audio::ChannelCount(2); + +audio::SignalInfo proxySignalInfo( + const audio::SignalInfo& signalInfo) { + DEBUG_ASSERT(signalInfo.isValid()); + return audio::SignalInfo( + kChannelCount, + signalInfo.getSampleRate(), + signalInfo.getSampleLayout()); +} + } // anonymous namespace AudioSourceStereoProxy::AudioSourceStereoProxy( AudioSourcePointer pAudioSource, SINT maxReadableFrames) - : AudioSource(*pAudioSource), - m_pAudioSource(std::move(pAudioSource)), + : AudioSourceProxy( + std::move(pAudioSource), + proxySignalInfo(pAudioSource->getSignalInfo())), m_tempSampleBuffer( - (m_pAudioSource->channelCount() != 2) ? m_pAudioSource->frames2samples(maxReadableFrames) : 0), + (m_pAudioSource->getSignalInfo().getChannelCount() != kChannelCount) ? + m_pAudioSource->getSignalInfo().frames2samples(maxReadableFrames) : + 0), m_tempWritableSlice(m_tempSampleBuffer) { - setChannelCount(2); } AudioSourceStereoProxy::AudioSourceStereoProxy( AudioSourcePointer pAudioSource, SampleBuffer::WritableSlice tempWritableSlice) - : AudioSource(*pAudioSource), - m_pAudioSource(std::move(pAudioSource)), + : AudioSourceProxy( + std::move(pAudioSource), + proxySignalInfo(pAudioSource->getSignalInfo())), m_tempWritableSlice(std::move(tempWritableSlice)) { - setChannelCount(2); } namespace { @@ -53,7 +66,7 @@ inline bool isDisjunct( ReadableSampleFrames AudioSourceStereoProxy::readSampleFramesClamped( WritableSampleFrames sampleFrames) { - if (m_pAudioSource->channelCount() == 2) { + if (m_pAudioSource->getSignalInfo().getChannelCount() == kChannelCount) { return readSampleFramesClampedOn(*m_pAudioSource, sampleFrames); } @@ -67,7 +80,7 @@ ReadableSampleFrames AudioSourceStereoProxy::readSampleFramesClamped( } { const SINT numberOfSamplesToRead = - m_pAudioSource->frames2samples( + m_pAudioSource->getSignalInfo().frames2samples( sampleFrames.frameLength()); VERIFY_OR_DEBUG_ASSERT(m_tempWritableSlice.length() >= numberOfSamplesToRead) { kLogger.warning() @@ -90,14 +103,19 @@ ReadableSampleFrames AudioSourceStereoProxy::readSampleFramesClamped( if (readableSampleFrames.frameIndexRange().empty()) { return readableSampleFrames; } - DEBUG_ASSERT(readableSampleFrames.frameIndexRange() <= sampleFrames.frameIndexRange()); - DEBUG_ASSERT(readableSampleFrames.frameIndexRange().start() >= sampleFrames.frameIndexRange().start()); + DEBUG_ASSERT( + readableSampleFrames.frameIndexRange() <= + sampleFrames.frameIndexRange()); + DEBUG_ASSERT( + readableSampleFrames.frameIndexRange().start() >= + sampleFrames.frameIndexRange().start()); const SINT frameOffset = - readableSampleFrames.frameIndexRange().start() - sampleFrames.frameIndexRange().start(); + readableSampleFrames.frameIndexRange().start() - + sampleFrames.frameIndexRange().start(); SampleBuffer::WritableSlice writableSlice( - sampleFrames.writableData(frames2samples(frameOffset)), - frames2samples(readableSampleFrames.frameLength())); - if (m_pAudioSource->channelCount() == 1) { + sampleFrames.writableData(getSignalInfo().frames2samples(frameOffset)), + getSignalInfo().frames2samples(readableSampleFrames.frameLength())); + if (m_pAudioSource->getSignalInfo().getChannelCount() == 1) { SampleUtil::copyMonoToDualMono( writableSlice.data(), readableSampleFrames.readableData(), @@ -107,7 +125,7 @@ ReadableSampleFrames AudioSourceStereoProxy::readSampleFramesClamped( writableSlice.data(), readableSampleFrames.readableData(), readableSampleFrames.frameLength(), - m_pAudioSource->channelCount()); + m_pAudioSource->getSignalInfo().getChannelCount()); } return ReadableSampleFrames( readableSampleFrames.frameIndexRange(), @@ -116,11 +134,4 @@ ReadableSampleFrames AudioSourceStereoProxy::readSampleFramesClamped( writableSlice.length())); } -void AudioSourceStereoProxy::adjustFrameIndexRange( - IndexRange frameIndexRange) { - // Ugly hack to keep both sources (inherited base + delegate) in sync! - AudioSource::adjustFrameIndexRange(frameIndexRange); - adjustFrameIndexRangeOn(*m_pAudioSource, frameIndexRange); -} - } // namespace mixxx diff --git a/src/sources/audiosourcestereoproxy.h b/src/sources/audiosourcestereoproxy.h index f863af056543..48bd4a4b025c 100644 --- a/src/sources/audiosourcestereoproxy.h +++ b/src/sources/audiosourcestereoproxy.h @@ -1,11 +1,10 @@ -#ifndef MIXXX_AUDIOSOURCESTEREOPROXY_H -#define MIXXX_AUDIOSOURCESTEREOPROXY_H +#pragma once -#include "sources/audiosource.h" +#include "sources/audiosourceproxy.h" namespace mixxx { -class AudioSourceStereoProxy : public AudioSource { +class AudioSourceStereoProxy : public AudioSourceProxy { public: static AudioSourcePointer create( AudioSourcePointer pAudioSource, @@ -24,30 +23,15 @@ class AudioSourceStereoProxy : public AudioSource { AudioSourceStereoProxy( AudioSourcePointer pAudioSource, SampleBuffer::WritableSlice tempWritableSlice); - - void close() override { - m_pAudioSource->close(); - } + ~AudioSourceStereoProxy() override = default; protected: - OpenResult tryOpen( - OpenMode mode, - const OpenParams& params) override { - return tryOpenOn(*m_pAudioSource, mode, params); - } - ReadableSampleFrames readSampleFramesClamped( WritableSampleFrames writableSampleFrames) override; - void adjustFrameIndexRange( - IndexRange frameIndexRange) override; - private: - AudioSourcePointer m_pAudioSource; SampleBuffer m_tempSampleBuffer; SampleBuffer::WritableSlice m_tempWritableSlice; }; } // namespace mixxx - -#endif // MIXXX_AUDIOSOURCESTEREOPROXY_H diff --git a/src/sources/audiosourcetrackproxy.h b/src/sources/audiosourcetrackproxy.h index 8995fe07434b..766cdcf9f136 100644 --- a/src/sources/audiosourcetrackproxy.h +++ b/src/sources/audiosourcetrackproxy.h @@ -1,18 +1,32 @@ -#ifndef MIXXX_AUDIOSOURCETRACKPROXY_H -#define MIXXX_AUDIOSOURCETRACKPROXY_H - -#include "sources/audiosource.h" +#pragma once +#include "sources/audiosourceproxy.h" #include "track/track.h" namespace mixxx { -// Keeps the TIO alive while accessing the audio data -// of the track. The TIO must not be deleted while +class TrackPointerHolder { + public: + explicit TrackPointerHolder( + TrackPointer&& pTrack) + : m_pTrack(std::move(pTrack)) { + } + + private: + TrackPointer m_pTrack; +}; + +// Keeps the Track object alive while accessing the audio data +// of the track. The Track object must not be deleted while // accessing the corresponding file to avoid file // corruption when writing metadata while the file // is still in use. -class AudioSourceTrackProxy : public AudioSource { +class AudioSourceTrackProxy : private TrackPointerHolder, + // The audio source must be closed BEFORE releasing the track + // pointer to close any open file handles. Otherwise exporting + // track metadata into the same file may not work, because the + // file is still locked by the OS! + public AudioSourceProxy { public: static AudioSourcePointer create( TrackPointer pTrack, @@ -25,43 +39,9 @@ class AudioSourceTrackProxy : public AudioSource { AudioSourceTrackProxy( TrackPointer pTrack, AudioSourcePointer pAudioSource) - : AudioSource(*pAudioSource), - m_pTrack(std::move(pTrack)), - m_pAudioSource(std::move(pAudioSource)) { - } - - void close() override { - m_pAudioSource->close(); + : TrackPointerHolder(std::move(pTrack)), + AudioSourceProxy(std::move(pAudioSource)) { } - - protected: - OpenResult tryOpen( - OpenMode mode, - const OpenParams& params) override { - return tryOpenOn(*m_pAudioSource, mode, params); - } - - ReadableSampleFrames readSampleFramesClamped( - WritableSampleFrames sampleFrames) override { - return readSampleFramesClampedOn(*m_pAudioSource, sampleFrames); - } - - void adjustFrameIndexRange( - IndexRange frameIndexRange) override { - // Ugly hack to keep both sources (inherited base + delegate) in sync! - AudioSource::adjustFrameIndexRange(frameIndexRange); - adjustFrameIndexRangeOn(*m_pAudioSource, frameIndexRange); - } - - private: - TrackPointer m_pTrack; - // The audio source must be closed before releasing the track - // pointer to close any open file handles. Otherwise exporting - // track metadata into the same file may not work, because the - // file is still locked by the OS! - AudioSourcePointer m_pAudioSource; }; } // namespace mixxx - -#endif // MIXXX_AUDIOSOURCETRACKPROXY_H diff --git a/src/sources/soundsourcecoreaudio.cpp b/src/sources/soundsourcecoreaudio.cpp index b3f39d9f0571..8df108ad0ef2 100644 --- a/src/sources/soundsourcecoreaudio.cpp +++ b/src/sources/soundsourcecoreaudio.cpp @@ -1,6 +1,7 @@ #include "sources/soundsourcecoreaudio.h" #include "sources/mp3decoding.h" +#include "engine/engine.h" #include "util/logger.h" #include "util/math.h" @@ -88,7 +89,9 @@ SoundSource::OpenResult SoundSourceCoreAudio::tryOpen( // create the output format const UInt32 numChannels = - params.channelCount().valid() ? params.channelCount() : 2; + params.getSignalInfo().getChannelCount().isValid() ? + params.getSignalInfo().getChannelCount() : + mixxx::kEngineChannelCount; m_outputFormat = CAStreamBasicDescription(m_inputFormat.mSampleRate, numChannels, CAStreamBasicDescription::kPCMFormatFloat32, @@ -160,8 +163,8 @@ SoundSource::OpenResult SoundSourceCoreAudio::tryOpen( return OpenResult::Failed; } - setChannelCount(m_outputFormat.NumberChannels()); - setSampleRate(m_inputFormat.mSampleRate); + initChannelCountOnce(m_outputFormat.NumberChannels()); + initSampleRateOnce(m_inputFormat.mSampleRate); // TODO(XXX): Reduce totalFrameCount by m_leadingFrames??? initFrameIndexRangeOnce(IndexRange::forward(m_leadingFrames, totalFrameCount)); @@ -172,7 +175,7 @@ SoundSource::OpenResult SoundSourceCoreAudio::tryOpen( } else { m_seekPrefetchFrames = m_leadingFrames; } - m_seekPrefetchBuffer.resize(frames2samples(m_seekPrefetchFrames)); + m_seekPrefetchBuffer.resize(getSignalInfo().frames2samples(m_seekPrefetchFrames)); // Seek to the first position, skipping over all header frames seekSampleFrame(frameIndexMin()); @@ -199,7 +202,7 @@ SINT SoundSourceCoreAudio::seekSampleFrame(SINT frameIndex) { } // Decode and discard prefetched frames if (prefetchFrames > 0) { - DEBUG_ASSERT(frames2samples(prefetchFrames) <= SINT(m_seekPrefetchBuffer.size())); + DEBUG_ASSERT(getSignalInfo().frames2samples(prefetchFrames) <= SINT(m_seekPrefetchBuffer.size())); const auto prefetchedFrames = readSampleFrames(prefetchFrames, m_seekPrefetchBuffer.data()); DEBUG_ASSERT(prefetchedFrames <= prefetchFrames); if (prefetchedFrames < prefetchFrames) { @@ -246,9 +249,9 @@ SINT SoundSourceCoreAudio::readSampleFrames( AudioBufferList fillBufList; fillBufList.mNumberBuffers = 1; - fillBufList.mBuffers[0].mNumberChannels = channelCount(); - fillBufList.mBuffers[0].mDataByteSize = frames2samples(numFramesToRead) * sizeof(sampleBuffer[0]); - fillBufList.mBuffers[0].mData = sampleBuffer + frames2samples(numFramesRead); + fillBufList.mBuffers[0].mNumberChannels = getSignalInfo().getChannelCount(); + fillBufList.mBuffers[0].mDataByteSize = getSignalInfo().frames2samples(numFramesToRead) * sizeof(sampleBuffer[0]); + fillBufList.mBuffers[0].mData = sampleBuffer + getSignalInfo().frames2samples(numFramesRead); UInt32 numFramesToReadInOut = numFramesToRead; // input/output parameter OSStatus err = ExtAudioFileRead(m_audioFile, &numFramesToReadInOut, &fillBufList); diff --git a/src/sources/soundsourceffmpeg.cpp b/src/sources/soundsourceffmpeg.cpp index f13c171c4332..d9648c97dae4 100644 --- a/src/sources/soundsourceffmpeg.cpp +++ b/src/sources/soundsourceffmpeg.cpp @@ -514,12 +514,12 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( // Request output format pavCodecContext->request_sample_fmt = kavSampleFormat; - if (params.channelCount().isValid()) { + if (params.getSignalInfo().getChannelCount().isValid()) { // A dedicated number of channels for the output signal // has been requested. Forward this to FFmpeg to avoid // manual resampling or post-processing after decoding. pavCodecContext->request_channel_layout = - av_get_default_channel_layout(params.channelCount()); + av_get_default_channel_layout(params.getSignalInfo().getChannelCount()); } // Open decoding context @@ -561,13 +561,13 @@ SoundSource::OpenResult SoundSourceFFmpeg::tryOpen( if (!initResampling(&channelCount, &sampleRate)) { return OpenResult::Failed; } - if (!setChannelCount(channelCount)) { + if (!initChannelCountOnce(channelCount)) { kLogger.warning() << "Failed to initialize number of channels" << channelCount; return OpenResult::Aborted; } - if (!setSampleRate(sampleRate)) { + if (!initSampleRateOnce(sampleRate)) { kLogger.warning() << "Failed to initialize sample rate" << sampleRate; @@ -655,7 +655,7 @@ bool SoundSourceFFmpeg::initResampling( // a workaround we decode the stream's channels as is and let Mixxx decide // how to handle this later. const auto resampledChannelCount = - /*config.channelCount().valid() ? config.channelCount() :*/ streamChannelCount; + /*config.getSignalInfo().getChannelCount().isValid() ? config.getSignalInfo().getChannelCount() :*/ streamChannelCount; const auto avResampledChannelLayout = av_get_default_channel_layout(resampledChannelCount); const auto avStreamSampleFormat = @@ -785,7 +785,7 @@ WritableSampleFrames SoundSourceFFmpeg::consumeSampleBuffer( const auto bufferedRange = IndexRange::forward( m_curFrameIndex, - samples2frames(m_sampleBuffer.readableLength())); + getSignalInfo().samples2frames(m_sampleBuffer.readableLength())); DEBUG_ASSERT(m_curFrameIndex == bufferedRange.clampIndex(m_curFrameIndex)); DEBUG_ASSERT(bufferedRange <= frameIndexRange()); auto writableRange = writableSampleFrames.frameIndexRange(); @@ -806,7 +806,7 @@ WritableSampleFrames SoundSourceFFmpeg::consumeSampleBuffer( m_curFrameIndex, consumableRange.start()); m_sampleBuffer.shrinkForReading( - frames2samples(skippableRange.length())); + getSignalInfo().frames2samples(skippableRange.length())); m_curFrameIndex += skippableRange.length(); // Consume buffered samples @@ -816,8 +816,8 @@ WritableSampleFrames SoundSourceFFmpeg::consumeSampleBuffer( DEBUG_ASSERT(m_curFrameIndex == consumableRange.start()); const SampleBuffer::ReadableSlice consumableSlice = m_sampleBuffer.shrinkForReading( - frames2samples(consumableRange.length())); - DEBUG_ASSERT(consumableSlice.length() == frames2samples(consumableRange.length())); + getSignalInfo().frames2samples(consumableRange.length())); + DEBUG_ASSERT(consumableSlice.length() == getSignalInfo().frames2samples(consumableRange.length())); CSAMPLE* pOutputSampleBuffer = writableSampleFrames.writableData(); if (pOutputSampleBuffer) { SampleUtil::copy( @@ -957,7 +957,7 @@ const CSAMPLE* SoundSourceFFmpeg::resampleDecodedFrame() { if (m_pSwrContext) { // Decoded frame must be resampled before reading m_pavResampledFrame->channel_layout = m_avResampledChannelLayout; - m_pavResampledFrame->sample_rate = sampleRate(); + m_pavResampledFrame->sample_rate = getSignalInfo().getSampleRate(); m_pavResampledFrame->format = kavSampleFormat; if (m_pavDecodedFrame->channel_layout == kavChannelLayoutUndefined) { // Sometimes the channel layout is undefined. @@ -1012,7 +1012,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( readableRange, SampleBuffer::ReadableSlice( readableData, - frames2samples(readableRange.length()))); + getSignalInfo().frames2samples(readableRange.length()))); } // Adjust the current position @@ -1091,7 +1091,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( << frameIndexRange().end() << "-> padding with silence"; const auto clearSampleCount = - frames2samples(writableRange.length()); + getSignalInfo().frames2samples(writableRange.length()); if (pOutputSampleBuffer) { SampleUtil::clear( pOutputSampleBuffer, @@ -1159,12 +1159,12 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( // Rewind internally buffered samples first... const auto rewindSampleLength = m_sampleBuffer.shrinkAfterWriting( - frames2samples(rewindRange.length())); + getSignalInfo().frames2samples(rewindRange.length())); rewindRange.shrinkBack( - samples2frames(rewindSampleLength)); + getSignalInfo().samples2frames(rewindSampleLength)); // ...then rewind remaining samples from the output buffer if (pOutputSampleBuffer) { - pOutputSampleBuffer -= frames2samples(rewindRange.length()); + pOutputSampleBuffer -= getSignalInfo().frames2samples(rewindRange.length()); } writableRange = IndexRange::between(rewindRange.start(), writableRange.end()); } @@ -1219,7 +1219,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( const auto clearRange = intersect(missingRange, writableRange); if (clearRange.length() > 0) { const auto clearSampleCount = - frames2samples(clearRange.length()); + getSignalInfo().frames2samples(clearRange.length()); if (pOutputSampleBuffer) { SampleUtil::clear( pOutputSampleBuffer, @@ -1239,7 +1239,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( readFrameIndex += preskipMissingFrameCount; const auto preskipDecodedFrameCount = math_min(decodedFrameRange.length(), writableRange.start() - readFrameIndex); - pDecodedSampleData += frames2samples(preskipDecodedFrameCount); + pDecodedSampleData += getSignalInfo().frames2samples(preskipDecodedFrameCount); decodedFrameRange.shrinkFront(preskipDecodedFrameCount); readFrameIndex += preskipDecodedFrameCount; m_curFrameIndex = readFrameIndex; @@ -1262,7 +1262,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( if (writeMissingFrameCount > 0) { // Fill the gap until the first decoded frame with silence const auto clearSampleCount = - frames2samples(writeMissingFrameCount); + getSignalInfo().frames2samples(writeMissingFrameCount); if (pOutputSampleBuffer) { SampleUtil::clear( pOutputSampleBuffer, @@ -1279,7 +1279,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( if (writeDecodedFrameCount > 0) { // Copy the decoded samples into the output buffer const auto copySampleCount = - frames2samples(writeDecodedFrameCount); + getSignalInfo().frames2samples(writeDecodedFrameCount); if (pOutputSampleBuffer) { SampleUtil::copy( pOutputSampleBuffer, @@ -1310,7 +1310,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( // Buffer remaining unread sample data from // missing and decoded ranges const auto sampleBufferWriteLength = - frames2samples(missingFrameCount + decodedFrameRange.length()); + getSignalInfo().frames2samples(missingFrameCount + decodedFrameRange.length()); if (m_sampleBuffer.writableLength() < sampleBufferWriteLength) { // Increase the pre-allocated capacity of the sample buffer const auto sampleBufferCapacity = @@ -1327,7 +1327,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( if (missingFrameCount > 0) { // Fill the gap until the first decoded frame with silence const auto clearSampleCount = - frames2samples(missingFrameCount); + getSignalInfo().frames2samples(missingFrameCount); const SampleBuffer::WritableSlice writableSlice( m_sampleBuffer.growForWriting(clearSampleCount)); DEBUG_ASSERT(writableSlice.length() == clearSampleCount); @@ -1339,7 +1339,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( if (!decodedFrameRange.empty()) { // Copy the decoded samples into the internal buffer const auto copySampleCount = - frames2samples(decodedFrameRange.length()); + getSignalInfo().frames2samples(decodedFrameRange.length()); const SampleBuffer::WritableSlice writableSlice( m_sampleBuffer.growForWriting(copySampleCount)); DEBUG_ASSERT(writableSlice.length() == copySampleCount); @@ -1354,7 +1354,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( const auto bufferedRange = IndexRange::forward( m_curFrameIndex, - samples2frames(m_sampleBuffer.readableLength())); + getSignalInfo().samples2frames(m_sampleBuffer.readableLength())); if (frameIndexRange().end() < bufferedRange.end()) { // NOTE(2019-09-08, uklotzde): For some files (MP3 VBR) FFmpeg may // decode a few more samples than expected! Simply discard those @@ -1366,7 +1366,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( << overflowFrameCount << "sample frames at the end of the audio stream"; m_sampleBuffer.shrinkAfterWriting( - frames2samples(overflowFrameCount)); + getSignalInfo().frames2samples(overflowFrameCount)); } // Housekeeping before next decoding iteration @@ -1384,7 +1384,7 @@ ReadableSampleFrames SoundSourceFFmpeg::readSampleFramesClamped( readableRange, SampleBuffer::ReadableSlice( readableData, - frames2samples(readableRange.length()))); + getSignalInfo().frames2samples(readableRange.length()))); } QString SoundSourceProviderFFmpeg::getName() const { diff --git a/src/sources/soundsourceflac.cpp b/src/sources/soundsourceflac.cpp index 784095091767..3f6b52c1c502 100644 --- a/src/sources/soundsourceflac.cpp +++ b/src/sources/soundsourceflac.cpp @@ -232,7 +232,9 @@ ReadableSampleFrames SoundSourceFLAC::readSampleFramesClamped( } DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex); - const SINT numberOfSamplesTotal = frames2samples(writableSampleFrames.frameLength()); + const SINT numberOfSamplesTotal = + getSignalInfo().frames2samples( + writableSampleFrames.frameLength()); SINT numberOfSamplesRemaining = numberOfSamplesTotal; SINT outputSampleOffset = 0; @@ -299,7 +301,7 @@ ReadableSampleFrames SoundSourceFLAC::readSampleFramesClamped( readableSlice.length()); outputSampleOffset += numberOfSamplesRead; } - m_curFrameIndex += samples2frames(numberOfSamplesRead); + m_curFrameIndex += getSignalInfo().samples2frames(numberOfSamplesRead); numberOfSamplesRemaining -= numberOfSamplesRead; } @@ -307,7 +309,7 @@ ReadableSampleFrames SoundSourceFLAC::readSampleFramesClamped( DEBUG_ASSERT(numberOfSamplesTotal >= numberOfSamplesRemaining); const SINT numberOfSamples = numberOfSamplesTotal - numberOfSamplesRemaining; return ReadableSampleFrames( - IndexRange::forward(firstFrameIndex, samples2frames(numberOfSamples)), + IndexRange::forward(firstFrameIndex, getSignalInfo().samples2frames(numberOfSamples)), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), std::min(writableSampleFrames.writableLength(), numberOfSamples))); @@ -398,18 +400,18 @@ inline CSAMPLE convertDecodedSample(FLAC__int32 decodedSample, int bitsPerSample FLAC__StreamDecoderWriteStatus SoundSourceFLAC::flacWrite( const FLAC__Frame* frame, const FLAC__int32* const buffer[]) { const SINT numChannels = frame->header.channels; - if (channelCount() > numChannels) { + if (getSignalInfo().getChannelCount() > numChannels) { kLogger.warning() << "Corrupt or unsupported FLAC file:" << "Invalid number of channels in FLAC frame header" - << frame->header.channels << "<>" << channelCount(); + << frame->header.channels << "<>" << getSignalInfo().getChannelCount(); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } - if (sampleRate() != SINT(frame->header.sample_rate)) { + if (getSignalInfo().getSampleRate() != SINT(frame->header.sample_rate)) { kLogger.warning() << "Corrupt or unsupported FLAC file:" << "Invalid sample rate in FLAC frame header" - << frame->header.sample_rate << "<>" << sampleRate(); + << frame->header.sample_rate << "<>" << getSignalInfo().getSampleRate(); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } const SINT numReadableFrames = frame->header.blocksize; @@ -429,9 +431,11 @@ FLAC__StreamDecoderWriteStatus SoundSourceFLAC::flacWrite( // Decode buffer should be empty before decoding the next frame DEBUG_ASSERT(m_sampleBuffer.empty()); const SampleBuffer::WritableSlice writableSlice( - m_sampleBuffer.growForWriting(frames2samples(numReadableFrames))); + m_sampleBuffer.growForWriting( + getSignalInfo().frames2samples(numReadableFrames))); - const SINT numWritableFrames = samples2frames(writableSlice.length()); + const SINT numWritableFrames = + getSignalInfo().samples2frames(writableSlice.length()); DEBUG_ASSERT(numWritableFrames <= numReadableFrames); if (numWritableFrames < numReadableFrames) { kLogger.warning() @@ -440,8 +444,8 @@ FLAC__StreamDecoderWriteStatus SoundSourceFLAC::flacWrite( } CSAMPLE* pSampleBuffer = writableSlice.data(); - DEBUG_ASSERT(channelCount() <= numChannels); - switch (channelCount()) { + DEBUG_ASSERT(getSignalInfo().getChannelCount() <= numChannels); + switch (getSignalInfo().getChannelCount()) { case 1: { // optimized code for 1 channel (mono) for (SINT i = 0; i < numWritableFrames; ++i) { @@ -460,7 +464,7 @@ FLAC__StreamDecoderWriteStatus SoundSourceFLAC::flacWrite( default: { // generic code for multiple channels for (SINT i = 0; i < numWritableFrames; ++i) { - for (SINT j = 0; j < channelCount(); ++j) { + for (SINT j = 0; j < getSignalInfo().getChannelCount(); ++j) { *pSampleBuffer++ = convertDecodedSample(buffer[j][i], m_bitsPerSample); } } @@ -477,8 +481,8 @@ void SoundSourceFLAC::flacMetadata(const FLAC__StreamMetadata* metadata) { // "...always before the first audio frame (i.e. write callback)." switch (metadata->type) { case FLAC__METADATA_TYPE_STREAMINFO: { - setChannelCount(metadata->data.stream_info.channels); - setSampleRate(metadata->data.stream_info.sample_rate); + initChannelCountOnce(metadata->data.stream_info.channels); + initSampleRateOnce(metadata->data.stream_info.sample_rate); initFrameIndexRangeOnce( IndexRange::forward( 0, @@ -509,7 +513,7 @@ void SoundSourceFLAC::flacMetadata(const FLAC__StreamMetadata* metadata) { << "Invalid max. blocksize" << m_maxBlocksize; } const SINT sampleBufferCapacity = - m_maxBlocksize * channelCount(); + m_maxBlocksize * getSignalInfo().getChannelCount(); if (m_sampleBuffer.capacity() < sampleBufferCapacity) { m_sampleBuffer.adjustCapacity(sampleBufferCapacity); } diff --git a/src/sources/soundsourcem4a.cpp b/src/sources/soundsourcem4a.cpp index 284aeb0d8c46..81033351618f 100644 --- a/src/sources/soundsourcem4a.cpp +++ b/src/sources/soundsourcem4a.cpp @@ -265,8 +265,8 @@ bool SoundSourceM4A::openDecoder() { LibFaadLoader::Configuration* pDecoderConfig = m_pFaad->GetCurrentConfiguration( m_hDecoder); pDecoderConfig->outputFormat = FAAD_FMT_FLOAT; - if ((m_openParams.channelCount() == 1) || - (m_openParams.channelCount() == 2)) { + if ((m_openParams.getSignalInfo().getChannelCount() == 1) || + (m_openParams.getSignalInfo().getChannelCount() == 2)) { pDecoderConfig->downMatrix = 1; } else { pDecoderConfig->downMatrix = 0; @@ -304,15 +304,15 @@ bool SoundSourceM4A::openDecoder() { (kNumberOfPrefetchFrames + (m_framesPerSampleBlock - 1)) / m_framesPerSampleBlock; - setChannelCount(channelCount); - setSampleRate(sampleRate); + initChannelCountOnce(channelCount); + initSampleRateOnce(sampleRate); initFrameIndexRangeOnce( mixxx::IndexRange::forward( 0, ((m_maxSampleBlockId - kSampleBlockIdMin) + 1) * m_framesPerSampleBlock)); const SINT sampleBufferCapacity = - frames2samples(m_framesPerSampleBlock); + getSignalInfo().frames2samples(m_framesPerSampleBlock); if (m_sampleBuffer.capacity() < sampleBufferCapacity) { m_sampleBuffer.adjustCapacity(sampleBufferCapacity); } @@ -419,7 +419,7 @@ ReadableSampleFrames SoundSourceM4A::readSampleFramesClamped( } DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex); - const SINT numberOfSamplesTotal = frames2samples(writableSampleFrames.frameLength()); + const SINT numberOfSamplesTotal = getSignalInfo().frames2samples(writableSampleFrames.frameLength()); SINT numberOfSamplesRemaining = numberOfSamplesTotal; SINT outputSampleOffset = 0; @@ -435,7 +435,7 @@ ReadableSampleFrames SoundSourceM4A::readSampleFramesClamped( readableSlice.length()); outputSampleOffset += readableSlice.length(); } - m_curFrameIndex += samples2frames(readableSlice.length()); + m_curFrameIndex += getSignalInfo().samples2frames(readableSlice.length()); DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(numberOfSamplesRemaining >= readableSlice.length()); numberOfSamplesRemaining -= readableSlice.length(); @@ -478,7 +478,7 @@ ReadableSampleFrames SoundSourceM4A::readSampleFramesClamped( // we need to use a temporary buffer. CSAMPLE* pDecodeBuffer; // in/out parameter SINT decodeBufferCapacity; - const SINT decodeBufferCapacityMin = frames2samples(m_framesPerSampleBlock); + const SINT decodeBufferCapacityMin = getSignalInfo().frames2samples(m_framesPerSampleBlock); if (writableSampleFrames.writableData() && (decodeBufferCapacityMin <= numberOfSamplesRemaining)) { // Decode samples directly into the output buffer @@ -510,18 +510,18 @@ ReadableSampleFrames SoundSourceM4A::readSampleFramesClamped( DEBUG_ASSERT(pDecodeResult == pDecodeBuffer); // verify the in/out parameter // Verify the decoded sample data for consistency - VERIFY_OR_DEBUG_ASSERT(channelCount() == decFrameInfo.channels) { + VERIFY_OR_DEBUG_ASSERT(getSignalInfo().getChannelCount() == decFrameInfo.channels) { kLogger.critical() << "Corrupt or unsupported AAC file:" << "Unexpected number of channels" << decFrameInfo.channels - << "<>" << channelCount(); + << "<>" << getSignalInfo().getChannelCount(); break; // abort } - VERIFY_OR_DEBUG_ASSERT(sampleRate() == SINT(decFrameInfo.samplerate)) { + VERIFY_OR_DEBUG_ASSERT(getSignalInfo().getSampleRate() == SINT(decFrameInfo.samplerate)) { kLogger.critical() << "Corrupt or unsupported AAC file:" << "Unexpected sample rate" << decFrameInfo.samplerate - << "<>" << sampleRate(); + << "<>" << getSignalInfo().getSampleRate(); break; // abort } @@ -565,7 +565,7 @@ ReadableSampleFrames SoundSourceM4A::readSampleFramesClamped( // at the end of the file! When the end of the file has been // reached decoding can be restarted by seeking to a new // position. - m_curFrameIndex += samples2frames(numberOfSamplesRead); + m_curFrameIndex += getSignalInfo().samples2frames(numberOfSamplesRead); numberOfSamplesRemaining -= numberOfSamplesRead; } @@ -573,7 +573,9 @@ ReadableSampleFrames SoundSourceM4A::readSampleFramesClamped( DEBUG_ASSERT(numberOfSamplesTotal >= numberOfSamplesRemaining); const SINT numberOfSamples = numberOfSamplesTotal - numberOfSamplesRemaining; return ReadableSampleFrames( - IndexRange::forward(firstFrameIndex, samples2frames(numberOfSamples)), + IndexRange::forward( + firstFrameIndex, + getSignalInfo().samples2frames(numberOfSamples)), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), std::min(writableSampleFrames.writableLength(), numberOfSamples))); diff --git a/src/sources/soundsourcemediafoundation.cpp b/src/sources/soundsourcemediafoundation.cpp index 60becff86252..ee798604afce 100644 --- a/src/sources/soundsourcemediafoundation.cpp +++ b/src/sources/soundsourcemediafoundation.cpp @@ -150,7 +150,7 @@ void SoundSourceMediaFoundation::seekSampleFrame(SINT frameIndex) { // need to decode more than 2 * kNumberOfPrefetchFrames frames // while skipping SINT skipFramesCountMax = - samples2frames(m_sampleBuffer.readableLength()) + + getSignalInfo().samples2frames(m_sampleBuffer.readableLength()) + 2 * kNumberOfPrefetchFrames; if (skipFrames.length() <= skipFramesCountMax) { if (skipFrames != readSampleFramesClamped(WritableSampleFrames(skipFrames)).frameIndexRange()) { @@ -279,8 +279,8 @@ ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( while (numberOfFramesRemaining > 0) { SampleBuffer::ReadableSlice readableSlice( m_sampleBuffer.shrinkForReading( - frames2samples(numberOfFramesRemaining))); - DEBUG_ASSERT(readableSlice.length() <= frames2samples(numberOfFramesRemaining)); + getSignalInfo().frames2samples(numberOfFramesRemaining))); + DEBUG_ASSERT(readableSlice.length() <= getSignalInfo().frames2samples(numberOfFramesRemaining)); if (readableSlice.length() > 0) { DEBUG_ASSERT(isValidFrameIndex(m_currentFrameIndex)); DEBUG_ASSERT(m_currentFrameIndex < frameIndexMax()); @@ -291,8 +291,8 @@ ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( readableSlice.length()); pSampleBuffer += readableSlice.length(); } - m_currentFrameIndex += samples2frames(readableSlice.length()); - numberOfFramesRemaining -= samples2frames(readableSlice.length()); + m_currentFrameIndex += getSignalInfo().samples2frames(readableSlice.length()); + numberOfFramesRemaining -= getSignalInfo().samples2frames(readableSlice.length()); } if (numberOfFramesRemaining == 0) { break; // finished reading @@ -365,7 +365,7 @@ ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( VERIFY_OR_DEBUG_ASSERT(m_currentFrameIndex == readerFrameIndex) { kLogger.debug() << "streamPos [100 ns] =" << streamPos - << ", sampleRate =" << sampleRate(); + << ", sampleRate =" << getSignalInfo().getSampleRate(); kLogger.warning() << "Stream position (in sample frames) while reading is inaccurate:" << "expected =" << m_currentFrameIndex @@ -445,7 +445,7 @@ ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( SINT lockedSampleBufferCount = lockedSampleBufferLengthInBytes / sizeof(pLockedSampleBuffer[0]); SINT copySamplesCount = std::min( - frames2samples(numberOfFramesRemaining), + getSignalInfo().frames2samples(numberOfFramesRemaining), lockedSampleBufferCount); if (copySamplesCount > 0) { // Copy samples directly into output buffer if possible @@ -458,8 +458,8 @@ ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( } pLockedSampleBuffer += copySamplesCount; lockedSampleBufferCount -= copySamplesCount; - m_currentFrameIndex += samples2frames(copySamplesCount); - numberOfFramesRemaining -= samples2frames(copySamplesCount); + m_currentFrameIndex += getSignalInfo().samples2frames(copySamplesCount); + numberOfFramesRemaining -= getSignalInfo().samples2frames(copySamplesCount); } // Buffer the remaining samples SampleBuffer::WritableSlice writableSlice( @@ -498,7 +498,7 @@ ReadableSampleFrames SoundSourceMediaFoundation::readSampleFramesClamped( IndexRange::forward(firstFrameIndex, numberOfFrames), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), - std::min(writableSampleFrames.writableLength(), frames2samples(numberOfFrames)))); + std::min(writableSampleFrames.writableLength(), getSignalInfo().frames2samples(numberOfFrames)))); } namespace { @@ -595,8 +595,8 @@ bool configureMediaType( return false; } kLogger.debug() << "Number of channels in input stream" << numChannels; - if (params.channelCount().valid()) { - numChannels = params.channelCount(); + if (params.getSignalInfo().getChannelCount().isValid()) { + numChannels = params.getSignalInfo().getChannelCount(); hr = pAudioType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, numChannels); if (FAILED(hr)) { @@ -619,8 +619,8 @@ bool configureMediaType( return false; } kLogger.debug() << "Samples per second in input stream" << samplesPerSecond; - if (params.sampleRate().valid()) { - samplesPerSecond = params.sampleRate(); + if (params.getSignalInfo().getSampleRate().isValid()) { + samplesPerSecond = params.getSignalInfo().getSampleRate(); hr = pAudioType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, samplesPerSecond); if (FAILED(hr)) { @@ -724,7 +724,7 @@ bool SoundSourceMediaFoundation::configureAudioStream(const OpenParams& openPara safeRelease(&pAudioType); return false; } - setChannelCount(numChannels); + initChannelCountOnce(numChannels); UINT32 samplesPerSecond; hr = pAudioType->GetUINT32( @@ -735,7 +735,7 @@ bool SoundSourceMediaFoundation::configureAudioStream(const OpenParams& openPara safeRelease(&pAudioType); return false; } - setSampleRate(samplesPerSecond); + initSampleRateOnce(samplesPerSecond); UINT32 leftoverBufferSizeInBytes = 0; hr = pAudioType->GetUINT32(MF_MT_SAMPLE_SIZE, &leftoverBufferSizeInBytes); diff --git a/src/sources/soundsourcemediafoundation.h b/src/sources/soundsourcemediafoundation.h index cac4d57a8b84..4def111afe6a 100644 --- a/src/sources/soundsourcemediafoundation.h +++ b/src/sources/soundsourcemediafoundation.h @@ -20,8 +20,8 @@ class StreamUnitConverter final { } explicit StreamUnitConverter(const AudioSource* pAudioSource) : m_pAudioSource(pAudioSource), - m_fromSampleFramesToStreamUnits(double(kStreamUnitsPerSecond) / double(pAudioSource->sampleRate())), - m_fromStreamUnitsToSampleFrames(double(pAudioSource->sampleRate()) / double(kStreamUnitsPerSecond)) { + m_fromSampleFramesToStreamUnits(double(kStreamUnitsPerSecond) / double(pAudioSource->getSignalInfo().getSampleRate())), + m_fromStreamUnitsToSampleFrames(double(pAudioSource->getSignalInfo().getSampleRate()) / double(kStreamUnitsPerSecond)) { // The stream units should actually be much shorter than // sample frames to minimize jitter and rounding. Even a // frame at 192 kHz has a length of about 5000 ns >> 100 ns. diff --git a/src/sources/soundsourcemodplug.cpp b/src/sources/soundsourcemodplug.cpp index d33bf680118f..a31dc5bb91b6 100644 --- a/src/sources/soundsourcemodplug.cpp +++ b/src/sources/soundsourcemodplug.cpp @@ -164,12 +164,12 @@ SoundSource::OpenResult SoundSourceModPlug::tryOpen( << m_sampleBuf.capacity() - m_sampleBuf.size() << " samples unused capacity."; - setChannelCount(kChannelCount); - setSampleRate(kSampleRate); + initChannelCountOnce(kChannelCount); + initSampleRateOnce(kSampleRate); initFrameIndexRangeOnce( IndexRange::forward( 0, - samples2frames(m_sampleBuf.size()))); + getSignalInfo().samples2frames(m_sampleBuf.size()))); return OpenResult::Succeeded; } @@ -183,8 +183,8 @@ void SoundSourceModPlug::close() { ReadableSampleFrames SoundSourceModPlug::readSampleFramesClamped( WritableSampleFrames writableSampleFrames) { - const SINT readOffset = frames2samples(writableSampleFrames.frameIndexRange().start()); - const SINT readSamples = frames2samples(writableSampleFrames.frameLength()); + const SINT readOffset = getSignalInfo().frames2samples(writableSampleFrames.frameIndexRange().start()); + const SINT readSamples = getSignalInfo().frames2samples(writableSampleFrames.frameLength()); SampleUtil::convertS16ToFloat32( writableSampleFrames.writableData(), &m_sampleBuf[readOffset], diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 2332495c817d..892b86a79c1b 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -360,27 +360,27 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( } // Initialize the AudioSource - if (mostCommonSampleRateIndex > kSampleRateCount) { + if (!maxChannelCount.isValid() || (maxChannelCount > kChannelCountMax)) { kLogger.warning() - << "Unknown sample rate in MP3 file:" + << "Invalid number of channels" + << maxChannelCount + << "in MP3 file:" << m_file.fileName(); // Abort return OpenResult::Failed; } - setSampleRate(getSampleRateByIndex(mostCommonSampleRateIndex)); - if (!maxChannelCount.isValid() || (maxChannelCount > kChannelCountMax)) { + initChannelCountOnce(maxChannelCount); + if (mostCommonSampleRateIndex > kSampleRateCount) { kLogger.warning() - << "Invalid number of channels" - << maxChannelCount - << "in MP3 file:" + << "Unknown sample rate in MP3 file:" << m_file.fileName(); // Abort return OpenResult::Failed; } - setChannelCount(maxChannelCount); + initSampleRateOnce(getSampleRateByIndex(mostCommonSampleRateIndex)); initFrameIndexRangeOnce(IndexRange::forward(0, m_curFrameIndex)); - // Calculate average values + // Calculate average bitrate values DEBUG_ASSERT(m_seekFrameList.size() > 0); // see above m_avgSeekFrameCount = frameLength() / m_seekFrameList.size(); if (cntBitrateFrames > 0) { @@ -678,9 +678,9 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( #ifndef QT_NO_DEBUG_OUTPUT const SINT madFrameChannelCount = MAD_NCHANNELS(&m_madFrame.header); - if (madFrameChannelCount != channelCount()) { + if (madFrameChannelCount != getSignalInfo().getChannelCount()) { kLogger.warning() << "MP3 frame header with mismatching number of channels" - << madFrameChannelCount << "<>" << channelCount() + << madFrameChannelCount << "<>" << getSignalInfo().getChannelCount() << " - aborting"; abortReading = true; } @@ -690,9 +690,9 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( mad_synth_frame(&m_madSynth, &m_madFrame); #ifndef QT_NO_DEBUG_OUTPUT const SINT madSynthSampleRate = m_madSynth.pcm.samplerate; - if (madSynthSampleRate != sampleRate()) { + if (madSynthSampleRate != getSignalInfo().getSampleRate()) { kLogger.warning() << "Reading MP3 data with different sample rate" - << madSynthSampleRate << "<>" << sampleRate() + << madSynthSampleRate << "<>" << getSignalInfo().getSampleRate() << " - aborting"; abortReading = true; } @@ -716,14 +716,14 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( DEBUG_ASSERT(madSynthOffset < m_madSynth.pcm.length); const SINT madSynthChannelCount = m_madSynth.pcm.channels; DEBUG_ASSERT(0 < madSynthChannelCount); - DEBUG_ASSERT(madSynthChannelCount <= channelCount()); - if (madSynthChannelCount != channelCount()) { + DEBUG_ASSERT(madSynthChannelCount <= getSignalInfo().getChannelCount()); + if (madSynthChannelCount != getSignalInfo().getChannelCount()) { kLogger.warning() << "Reading MP3 data with different number of channels" - << madSynthChannelCount << "<>" << channelCount(); + << madSynthChannelCount << "<>" << getSignalInfo().getChannelCount(); } if (madSynthChannelCount == 1) { // MP3 frame contains a mono signal - if (channelCount() == 2) { + if (getSignalInfo().getChannelCount() == 2) { // The reader explicitly requested a stereo signal // or the AudioSource itself provides a stereo signal. // Mono -> Stereo: Copy 1st channel twice @@ -747,7 +747,7 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( // If the MP3 frame contains a stereo signal then the whole // AudioSource must also provide 2 channels, because the // maximum channel count of all MP3 frames is used. - DEBUG_ASSERT(channelCount() == 2); + DEBUG_ASSERT(getSignalInfo().getChannelCount() == 2); // Stereo -> Stereo: Copy 1st + 2nd channel for (SINT i = 0; i < synthReadCount; ++i) { *pSampleBuffer++ = madScaleSampleValue( @@ -770,7 +770,7 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( IndexRange::forward(firstFrameIndex, numberOfFrames), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), - std::min(writableSampleFrames.writableLength(), frames2samples(numberOfFrames)))); + std::min(writableSampleFrames.writableLength(), getSignalInfo().frames2samples(numberOfFrames)))); } QString SoundSourceProviderMp3::getName() const { diff --git a/src/sources/soundsourceoggvorbis.cpp b/src/sources/soundsourceoggvorbis.cpp index ce8fc27c4148..1481102b59b7 100644 --- a/src/sources/soundsourceoggvorbis.cpp +++ b/src/sources/soundsourceoggvorbis.cpp @@ -82,8 +82,8 @@ SoundSource::OpenResult SoundSourceOggVorbis::tryOpen( << getUrlString(); return OpenResult::Failed; } - setChannelCount(vi->channels); - setSampleRate(vi->rate); + initChannelCountOnce(vi->channels); + initSampleRateOnce(vi->rate); if (0 < vi->bitrate_nominal) { initBitrateOnce(vi->bitrate_nominal / 1000); } else { @@ -154,7 +154,7 @@ ReadableSampleFrames SoundSourceOggVorbis::readSampleFramesClamped( if (0 < readResult) { m_curFrameIndex += readResult; if (pSampleBuffer) { - switch (channelCount()) { + switch (getSignalInfo().getChannelCount()) { case 1: for (long i = 0; i < readResult; ++i) { *pSampleBuffer++ = pcmChannels[0][i]; @@ -168,7 +168,7 @@ ReadableSampleFrames SoundSourceOggVorbis::readSampleFramesClamped( break; default: for (long i = 0; i < readResult; ++i) { - for (SINT j = 0; j < channelCount(); ++j) { + for (SINT j = 0; j < getSignalInfo().getChannelCount(); ++j) { *pSampleBuffer++ = pcmChannels[j][i]; } } @@ -188,7 +188,9 @@ ReadableSampleFrames SoundSourceOggVorbis::readSampleFramesClamped( IndexRange::forward(firstFrameIndex, numberOfFrames), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), - std::min(writableSampleFrames.writableLength(), frames2samples(numberOfFrames)))); + std::min( + writableSampleFrames.writableLength(), + getSignalInfo().frames2samples(numberOfFrames)))); } //static diff --git a/src/sources/soundsourceopus.cpp b/src/sources/soundsourceopus.cpp index 1d0f103a8a70..ce7f55426505 100644 --- a/src/sources/soundsourceopus.cpp +++ b/src/sources/soundsourceopus.cpp @@ -229,14 +229,14 @@ SoundSource::OpenResult SoundSourceOpus::tryOpen( if (0 < streamChannelCount) { // opusfile supports to enforce stereo decoding bool enforceStereoDecoding = - params.channelCount().isValid() && - (params.channelCount() <= 2) && + params.getSignalInfo().getChannelCount().isValid() && + (params.getSignalInfo().getChannelCount() <= 2) && // preserve mono signals if stereo signal is not requested explicitly - ((params.channelCount() == 2) || (streamChannelCount > 2)); + ((params.getSignalInfo().getChannelCount() == 2) || (streamChannelCount > 2)); if (enforceStereoDecoding) { - setChannelCount(2); + initChannelCountOnce(2); } else { - setChannelCount(streamChannelCount); + initChannelCountOnce(streamChannelCount); } } else { kLogger.warning() @@ -246,7 +246,7 @@ SoundSource::OpenResult SoundSourceOpus::tryOpen( } // Reserve enough capacity for buffering a stereo signal! - const auto prefetchChannelCount = std::min(channelCount(), audio::ChannelCount(2)); + const auto prefetchChannelCount = std::min(getSignalInfo().getChannelCount(), audio::ChannelCount(2)); SampleBuffer(prefetchChannelCount * kNumberOfPrefetchFrames).swap(m_prefetchSampleBuffer); const ogg_int64_t pcmTotal = op_pcm_total(m_pOggOpusFile, kEntireStreamLink); @@ -269,7 +269,7 @@ SoundSource::OpenResult SoundSourceOpus::tryOpen( return OpenResult::Failed; } - setSampleRate(kSampleRate); + initSampleRateOnce(kSampleRate); m_curFrameIndex = frameIndexMin(); @@ -333,7 +333,7 @@ ReadableSampleFrames SoundSourceOpus::readSampleFramesClamped( SINT numberOfFramesRemaining = numberOfFramesTotal; while (0 < numberOfFramesRemaining) { SINT numberOfSamplesToRead = - frames2samples(numberOfFramesRemaining); + getSignalInfo().frames2samples(numberOfFramesRemaining); if (!writableSampleFrames.writableData()) { // NOTE(uklotzde): The opusfile API does not provide any // functions for skipping samples in the audio stream. Calling @@ -347,7 +347,7 @@ ReadableSampleFrames SoundSourceOpus::readSampleFramesClamped( } } int readResult; - if (channelCount() == 2) { + if (getSignalInfo().getChannelCount() == 2) { readResult = op_read_float_stereo( m_pOggOpusFile, pSampleBuffer, @@ -361,7 +361,7 @@ ReadableSampleFrames SoundSourceOpus::readSampleFramesClamped( } if (0 < readResult) { m_curFrameIndex += readResult; - pSampleBuffer += frames2samples(readResult); + pSampleBuffer += getSignalInfo().frames2samples(readResult); numberOfFramesRemaining -= readResult; } else { kLogger.warning() << "Failed to read sample data from OggOpus file:" @@ -377,7 +377,7 @@ ReadableSampleFrames SoundSourceOpus::readSampleFramesClamped( IndexRange::forward(firstFrameIndex, numberOfFrames), SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), - std::min(writableSampleFrames.writableLength(), frames2samples(numberOfFrames)))); + std::min(writableSampleFrames.writableLength(), getSignalInfo().frames2samples(numberOfFrames)))); } QString SoundSourceProviderOpus::getName() const { diff --git a/src/sources/soundsourcesndfile.cpp b/src/sources/soundsourcesndfile.cpp index dde6d7760efa..cb15224844a5 100644 --- a/src/sources/soundsourcesndfile.cpp +++ b/src/sources/soundsourcesndfile.cpp @@ -63,8 +63,8 @@ SoundSource::OpenResult SoundSourceSndFile::tryOpen( } } - setChannelCount(sfInfo.channels); - setSampleRate(sfInfo.samplerate); + initChannelCountOnce(sfInfo.channels); + initSampleRateOnce(sfInfo.samplerate); initFrameIndexRangeOnce(IndexRange::forward(0, sfInfo.frames)); m_curFrameIndex = frameIndexMin(); @@ -115,7 +115,7 @@ ReadableSampleFrames SoundSourceSndFile::readSampleFramesClamped( resultRange, SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), - frames2samples(readCount))); + getSignalInfo().frames2samples(readCount))); } else { kLogger.warning() << "Failed to read from libsnd file:" << readCount diff --git a/src/sources/soundsourcewv.cpp b/src/sources/soundsourcewv.cpp index e1abfc083564..e0c26d1d3ae6 100644 --- a/src/sources/soundsourcewv.cpp +++ b/src/sources/soundsourcewv.cpp @@ -42,8 +42,8 @@ SoundSource::OpenResult SoundSourceWV::tryOpen( DEBUG_ASSERT(!m_wpc); char msg[80]; // hold possible error message int openFlags = OPEN_WVC | OPEN_NORMALIZE; - if ((params.channelCount() == 1) || - (params.channelCount() == 2)) { + if ((params.getSignalInfo().getChannelCount() == 1) || + (params.getSignalInfo().getChannelCount() == 2)) { openFlags |= OPEN_2CH_MAX; } @@ -64,8 +64,8 @@ SoundSource::OpenResult SoundSourceWV::tryOpen( return OpenResult::Failed; } - setChannelCount(WavpackGetReducedChannels(static_cast(m_wpc))); - setSampleRate(WavpackGetSampleRate(static_cast(m_wpc))); + initChannelCountOnce(WavpackGetReducedChannels(static_cast(m_wpc))); + initSampleRateOnce(WavpackGetSampleRate(static_cast(m_wpc))); initFrameIndexRangeOnce( mixxx::IndexRange::forward( 0, @@ -140,7 +140,7 @@ ReadableSampleFrames SoundSourceWV::readSampleFramesClamped( DEBUG_ASSERT(unpackCount <= numberOfFramesTotal); if (!(WavpackGetMode(static_cast(m_wpc)) & MODE_FLOAT)) { // signed integer -> float - const SINT sampleCount = frames2samples(unpackCount); + const SINT sampleCount = getSignalInfo().frames2samples(unpackCount); for (SINT i = 0; i < sampleCount; ++i) { const int32_t sampleValue = *reinterpret_cast(pOutputBuffer); @@ -153,7 +153,7 @@ ReadableSampleFrames SoundSourceWV::readSampleFramesClamped( resultRange, SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), - frames2samples(unpackCount))); + getSignalInfo().frames2samples(unpackCount))); } QString SoundSourceProviderWV::getName() const { diff --git a/src/sources/v1/legacyaudiosourceadapter.cpp b/src/sources/v1/legacyaudiosourceadapter.cpp index 1d7deabb64be..c97eddfeed26 100644 --- a/src/sources/v1/legacyaudiosourceadapter.cpp +++ b/src/sources/v1/legacyaudiosourceadapter.cpp @@ -52,8 +52,8 @@ ReadableSampleFrames LegacyAudioSourceAdapter::readSampleFramesClamped( writableSampleFrames = WritableSampleFrames( remainingFrameIndexRange, SampleBuffer::WritableSlice( - writableSampleFrames.writableData(m_pOwner->frames2samples(unreadableFrameOffset)), - m_pOwner->frames2samples(remainingFrameIndexRange.length()))); + writableSampleFrames.writableData(m_pOwner->getSignalInfo().frames2samples(unreadableFrameOffset)), + m_pOwner->getSignalInfo().frames2samples(remainingFrameIndexRange.length()))); } else { writableSampleFrames = WritableSampleFrames(remainingFrameIndexRange); } @@ -72,7 +72,7 @@ ReadableSampleFrames LegacyAudioSourceAdapter::readSampleFramesClamped( resultFrameIndexRange, SampleBuffer::ReadableSlice( writableSampleFrames.writableData(), - m_pOwner->frames2samples(resultFrameIndexRange.length()))); + m_pOwner->getSignalInfo().frames2samples(resultFrameIndexRange.length()))); } } // namespace mixxx diff --git a/src/test/soundproxy_test.cpp b/src/test/soundproxy_test.cpp index 88e551d0e1c6..a39242cb1580 100644 --- a/src/test/soundproxy_test.cpp +++ b/src/test/soundproxy_test.cpp @@ -89,16 +89,17 @@ class SoundSourceProxyTest: public MixxxTest { // All test files are mono, but we are requesting a stereo signal // to test the upscaling of channels mixxx::AudioSource::OpenParams openParams; - openParams.setChannelCount(2); + const auto channelCount = mixxx::audio::ChannelCount(2); + openParams.setChannelCount(mixxx::audio::ChannelCount(2)); auto pAudioSource = proxy.openAudioSource(openParams); EXPECT_FALSE(!pAudioSource); - if (pAudioSource->channelCount() != 2) { + if (pAudioSource->getSignalInfo().getChannelCount() != channelCount) { // Wrap into proxy object pAudioSource = mixxx::AudioSourceStereoProxy::create( pAudioSource, kMaxReadFrameCount); } - EXPECT_EQ(pAudioSource->channelCount(), 2); + EXPECT_EQ(pAudioSource->getSignalInfo().getChannelCount(), channelCount); return pAudioSource; } @@ -124,7 +125,7 @@ class SoundSourceProxyTest: public MixxxTest { skippedRange.empty() ? skipRange.start() : skippedRange.end(), math_min( skipRange.length() - skippedRange.length(), - pAudioSource->samples2frames(m_skipSampleBuffer.size()))); + pAudioSource->getSignalInfo().samples2frames(m_skipSampleBuffer.size()))); EXPECT_FALSE(nextRange.empty()); EXPECT_TRUE(intersect(nextRange, skipRange) == nextRange); const auto readRange = pAudioSource->readSampleFrames( @@ -168,8 +169,8 @@ TEST_F(SoundSourceProxyTest, open) { // skip test file continue; } - EXPECT_LT(0, pAudioSource->channelCount()); - EXPECT_LT(0, pAudioSource->sampleRate()); + EXPECT_LT(0, pAudioSource->getSignalInfo().getChannelCount()); + EXPECT_LT(0, pAudioSource->getSignalInfo().getSampleRate()); EXPECT_FALSE(pAudioSource->frameIndexRange().empty()); } } @@ -230,9 +231,9 @@ TEST_F(SoundSourceProxyTest, seekForwardBackward) { continue; } mixxx::SampleBuffer contReadData( - pContReadSource->frames2samples(kReadFrameCount)); + pContReadSource->getSignalInfo().frames2samples(kReadFrameCount)); mixxx::SampleBuffer seekReadData( - pContReadSource->frames2samples(kReadFrameCount)); + pContReadSource->getSignalInfo().frames2samples(kReadFrameCount)); SINT contFrameIndex = pContReadSource->frameIndexMin(); while (pContReadSource->frameIndexRange().containsIndex(contFrameIndex)) { @@ -252,11 +253,13 @@ TEST_F(SoundSourceProxyTest, seekForwardBackward) { contFrameIndex += contSampleFrames.frameLength(); const SINT sampleCount = - pContReadSource->frames2samples(contSampleFrames.frameLength()); + pContReadSource->getSignalInfo().frames2samples(contSampleFrames.frameLength()); mixxx::AudioSourcePointer pSeekReadSource(openAudioSource(filePath)); ASSERT_FALSE(!pSeekReadSource); - ASSERT_EQ(pContReadSource->channelCount(), pSeekReadSource->channelCount()); + ASSERT_EQ( + pContReadSource->getSignalInfo().getChannelCount(), + pSeekReadSource->getSignalInfo().getChannelCount()); ASSERT_EQ(pContReadSource->frameIndexRange(), pSeekReadSource->frameIndexRange()); // Seek source to next chunk and read it @@ -311,14 +314,16 @@ TEST_F(SoundSourceProxyTest, skipAndRead) { mixxx::AudioSourcePointer pSkipReadSource(openAudioSource(filePath)); ASSERT_FALSE(!pSkipReadSource); - ASSERT_EQ(pContReadSource->channelCount(), pSkipReadSource->channelCount()); + ASSERT_EQ( + pContReadSource->getSignalInfo().getChannelCount(), + pSkipReadSource->getSignalInfo().getChannelCount()); ASSERT_EQ(pContReadSource->frameIndexRange(), pSkipReadSource->frameIndexRange()); SINT skipFrameIndex = pSkipReadSource->frameIndexMin(); mixxx::SampleBuffer contReadData( - pContReadSource->frames2samples(kReadFrameCount)); + pContReadSource->getSignalInfo().frames2samples(kReadFrameCount)); mixxx::SampleBuffer skipReadData( - pSkipReadSource->frames2samples(kReadFrameCount)); + pSkipReadSource->getSignalInfo().frames2samples(kReadFrameCount)); SINT minFrameIndex = pContReadSource->frameIndexMin(); SINT skipCount = 1; @@ -359,7 +364,7 @@ TEST_F(SoundSourceProxyTest, skipAndRead) { contFrameIndex += contSampleFrames.frameLength(); const SINT sampleCount = - pContReadSource->frames2samples(contSampleFrames.frameLength()); + pContReadSource->getSignalInfo().frames2samples(contSampleFrames.frameLength()); // Skip until reaching the frame index and read next chunk ASSERT_LE(skipFrameIndex, minFrameIndex); @@ -411,7 +416,7 @@ TEST_F(SoundSourceProxyTest, seekBoundaries) { continue; } mixxx::SampleBuffer seekReadData( - pSeekReadSource->frames2samples(kReadFrameCount)); + pSeekReadSource->getSignalInfo().frames2samples(kReadFrameCount)); std::vector seekFrameIndices; // Seek to boundaries (alternating)... @@ -464,7 +469,9 @@ TEST_F(SoundSourceProxyTest, seekBoundaries) { mixxx::AudioSourcePointer pContReadSource(openAudioSource(filePath)); ASSERT_FALSE(!pContReadSource); - ASSERT_EQ(pSeekReadSource->channelCount(), pContReadSource->channelCount()); + ASSERT_EQ( + pSeekReadSource->getSignalInfo().getChannelCount(), + pContReadSource->getSignalInfo().getChannelCount()); ASSERT_EQ(pSeekReadSource->frameIndexRange(), pContReadSource->frameIndexRange()); const auto skipFrameIndexRange = skipSampleFrames(pContReadSource, @@ -474,7 +481,7 @@ TEST_F(SoundSourceProxyTest, seekBoundaries) { ASSERT_TRUE(skipFrameIndexRange.empty() || (skipFrameIndexRange.end() == seekFrameIndex)); mixxx::SampleBuffer contReadData( - pContReadSource->frames2samples(kReadFrameCount)); + pContReadSource->getSignalInfo().frames2samples(kReadFrameCount)); const auto contSampleFrames = pContReadSource->readSampleFrames( mixxx::WritableSampleFrames( @@ -494,7 +501,7 @@ TEST_F(SoundSourceProxyTest, seekBoundaries) { } const SINT sampleCount = - pSeekReadSource->frames2samples(seekSampleFrames.frameLength()); + pSeekReadSource->getSignalInfo().frames2samples(seekSampleFrames.frameLength()); expectDecodedSamplesEqual( sampleCount, &contReadData[0], @@ -529,7 +536,7 @@ TEST_F(SoundSourceProxyTest, readBeyondEnd) { // Read beyond the end mixxx::SampleBuffer readBuffer( - pAudioSource->frames2samples(kReadFrameCount)); + pAudioSource->getSignalInfo().frames2samples(kReadFrameCount)); EXPECT_EQ( mixxx::IndexRange::forward(seekIndex, remainingFrames), pAudioSource->readSampleFrames( @@ -558,7 +565,7 @@ TEST_F(SoundSourceProxyTest, regressionTestCachingReaderChunkJumpForward) { continue; } mixxx::SampleBuffer readBuffer( - pAudioSource->frames2samples(kReadFrameCount)); + pAudioSource->getSignalInfo().frames2samples(kReadFrameCount)); // Read chunk from beginning auto firstChunkRange = mixxx::IndexRange::forward( diff --git a/src/track/cue.h b/src/track/cue.h index fdda9fc06341..7eacf9d231a9 100644 --- a/src/track/cue.h +++ b/src/track/cue.h @@ -4,9 +4,9 @@ #include #include +#include "audio/types.h" #include "track/cueinfo.h" #include "track/trackid.h" -#include "util/audiosignal.h" #include "util/color/rgbcolor.h" #include "util/memory.h" diff --git a/src/util/audiosignal.cpp b/src/util/audiosignal.cpp deleted file mode 100644 index 7df77ca474ea..000000000000 --- a/src/util/audiosignal.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "util/audiosignal.h" - -#include "util/logger.h" - -namespace mixxx { - -namespace { - -const Logger kLogger("AudioSignal"); - -} // anonymous namespace - -bool AudioSignal::setChannelCount(audio::ChannelCount channelCount) { - if (channelCount < audio::ChannelCount()) { - kLogger.warning() - << "Invalid channel count" - << channelCount; - return false; // abort - } else { - m_signalInfo.setChannelCount(channelCount); - return true; - } -} - -bool AudioSignal::setSampleRate(audio::SampleRate sampleRate) { - if (sampleRate < audio::SampleRate()) { - kLogger.warning() - << "Invalid sample rate" - << sampleRate; - return false; // abort - } else { - m_signalInfo.setSampleRate(sampleRate); - return true; - } -} - -bool AudioSignal::verifyReadable() const { - bool result = true; - if (!channelCount().isValid()) { - kLogger.warning() - << "Invalid number of channels:" - << channelCount() - << "is out of range [" - << audio::ChannelCount::min() - << "," - << audio::ChannelCount::max() - << "]"; - result = false; - } - if (!sampleRate().isValid()) { - kLogger.warning() - << "Invalid sample rate:" - << sampleRate() - << "is out of range [" - << audio::SampleRate::min() - << "," - << audio::SampleRate::max() - << "]"; - result = false; - } - return result; -} - -QDebug operator<<(QDebug dbg, const AudioSignal& arg) { - return dbg - << "AudioSignal{" - << arg.getSignalInfo() - << "}"; -} - -} // namespace mixxx diff --git a/src/util/audiosignal.h b/src/util/audiosignal.h deleted file mode 100644 index 21a17637b87f..000000000000 --- a/src/util/audiosignal.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include "audio/signalinfo.h" -#include "util/assert.h" - -namespace mixxx { - -// Common properties of audio signals in Mixxx. -// -// An audio signal describes a stream of samples for multiple channels. -// Internally each sample is represented by a floating-point value. -// -// The properties of an audio signal are immutable and must be constant -// over time. Therefore all functions for modifying individual properties -// are declared as "protected" and are only available from derived classes. -class AudioSignal { - public: - explicit AudioSignal( - audio::SampleLayout sampleLayout) - : m_signalInfo(std::make_optional(sampleLayout)) { - } - - explicit AudioSignal( - audio::SignalInfo signalInfo) - : m_signalInfo(signalInfo) { - DEBUG_ASSERT(signalInfo.isValid()); - } - - virtual ~AudioSignal() = default; - - const audio::SignalInfo& getSignalInfo() const { - DEBUG_ASSERT(m_signalInfo.isValid()); - return m_signalInfo; - } - - audio::ChannelCount channelCount() const { - return getSignalInfo().getChannelCount(); - } - - audio::SampleRate sampleRate() const { - return getSignalInfo().getSampleRate(); - } - - // Verifies various properties to ensure that the audio data is - // actually readable. Warning messages are logged for properties - // with invalid values for diagnostic purposes. - // - // Subclasses may override this function for checking additional - // properties in derived classes. Derived functions should always - // call the implementation of the super class first: - // - // bool DerivedClass::verifyReadable() const { - // bool result = BaseClass::validate(); - // if (my property is invalid) { - // qWarning() << ...warning message... - // result = false; - // } - // return result; - // } - virtual bool verifyReadable() const; - - // Conversion: #samples / sample offset -> #frames / frame offset - template - inline T samples2frames(T samples) const { - return getSignalInfo().samples2frames(samples); - } - - // Conversion: #frames / frame offset -> #samples / sample offset - template - inline T frames2samples(T frames) const { - return getSignalInfo().frames2samples(frames); - } - - protected: - bool setChannelCount(audio::ChannelCount channelCount); - bool setChannelCount(SINT channelCount) { - return setChannelCount(audio::ChannelCount(channelCount)); - } - bool setSampleRate(audio::SampleRate sampleRate); - bool setSampleRate(SINT sampleRate) { - return setSampleRate(audio::SampleRate(sampleRate)); - } - - private: - audio::SignalInfo m_signalInfo; -}; - -QDebug operator<<(QDebug dbg, const AudioSignal& arg); - -} // namespace mixxx From 64c884c7e9fbf93949fea484e44bac92c24fae63 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 25 Mar 2020 23:21:32 +0100 Subject: [PATCH 125/393] Defer import of CueInfo until stream properties are known --- src/track/track.cpp | 84 +++++++++++++++++++++++++++++++++++++-------- src/track/track.h | 12 ++++++- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index 00c907f0bb6a..85b9ea1584ca 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -794,6 +794,39 @@ QList Track::getCuePoints() const { void Track::setCuePoints(const QList& cuePoints) { //qDebug() << "setCuePoints" << cuePoints.length(); QMutexLocker lock(&m_qMutex); + setCuePointsMarkDirtyAndUnlock( + &lock, + cuePoints); +} + +void Track::importCues( + const QList& cueInfos) { + QMutexLocker lock(&m_qMutex); + if (m_streamInfo) { + // Replace existing cue points with imported cue + // points immediately + importCuesMarkDirtyAndUnlock( + &lock, + cueInfos); + } else { + kLogger.debug() + << "Deferring import of" + << cueInfos.size() + << "cue(s) until actual sample rate becomes available"; + m_importCuesPending.append(cueInfos); + // Clear all existing cue points, that are supposed + // to be replaced with the imported cue points soon. + setCuePointsMarkDirtyAndUnlock( + &lock, + QList{}); + } +} + +void Track::setCuePointsMarkDirtyAndUnlock( + QMutexLocker* pLock, + const QList& cuePoints) { + DEBUG_ASSERT(pLock); + DEBUG_ASSERT(m_importCuesPending.isEmpty()); // disconnect existing cue points for (const auto& pCue: m_cuePoints) { disconnect(pCue.get(), 0, this, 0); @@ -809,26 +842,34 @@ void Track::setCuePoints(const QList& cuePoints) { m_record.setCuePoint(CuePosition(pCue->getPosition())); } } - markDirtyAndUnlock(&lock); + markDirtyAndUnlock(pLock); emit cuesUpdated(); } -void Track::importCuePoints(const QList& cueInfos) { - TrackId trackId; - mixxx::audio::SampleRate sampleRate; - { - QMutexLocker lock(&m_qMutex); - trackId = m_record.getId(); - sampleRate = m_record.getMetadata().getSampleRate(); - } // implicitly unlocked when leaving scope - +void Track::importCuesMarkDirtyAndUnlock( + QMutexLocker* pLock, + const QList& cueInfos) { + DEBUG_ASSERT(pLock); + DEBUG_ASSERT(m_importCuesPending.isEmpty()); + // The sample rate can only be trusted after the audio + // stream has been openend. + DEBUG_ASSERT(m_streamInfo); + const auto sampleRate = + m_streamInfo->getSignalInfo().getSampleRate(); + // The sample rate is supposed to be consistent + DEBUG_ASSERT(sampleRate == + m_record.getMetadata().getSampleRate()); + const auto trackId = m_record.getId(); QList cuePoints; - for (const mixxx::CueInfo& cueInfo : cueInfos) { + cuePoints.reserve(cueInfos.size()); + for (const auto& cueInfo : cueInfos) { CuePointer pCue(new Cue(cueInfo, sampleRate)); pCue->setTrackId(trackId); cuePoints.append(pCue); } - setCuePoints(cuePoints); + setCuePointsMarkDirtyAndUnlock( + pLock, + cuePoints); } void Track::markDirty() { @@ -1181,8 +1222,21 @@ void Track::updateAudioPropertiesFromStream( bool updated = m_record.refMetadata().updateAudioPropertiesFromStream( streamInfo); m_streamInfo = std::make_optional(std::move(streamInfo)); - // TODO: Continue deferred import of pending CueInfo objects - if (updated) { - markDirtyAndUnlock(&lock); + if (m_importCuesPending.isEmpty()) { + // Nothing more to do + if (updated) { + markDirtyAndUnlock(&lock); + } + return; } + DEBUG_ASSERT(m_cuePoints.isEmpty()); + const auto cueInfos = std::move(m_importCuesPending); + DEBUG_ASSERT(m_importCuesPending.isEmpty()); + kLogger.debug() + << "Finishing deferred import of" + << cueInfos.size() + << "cue(s)"; + importCuesMarkDirtyAndUnlock( + &lock, + cueInfos); } diff --git a/src/track/track.h b/src/track/track.h index 530a1d3e2a4f..6bc212a2bda3 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -252,8 +252,9 @@ class Track : public QObject { void removeCue(const CuePointer& pCue); void removeCuesOfType(mixxx::CueType); QList getCuePoints() const; + void setCuePoints(const QList& cuePoints); - void importCuePoints(const QList& cueInfos); + void importCues(const QList& cueInfos); bool isDirty(); @@ -360,6 +361,13 @@ class Track : public QObject { void afterKeysUpdated(QMutexLocker* pLock); + void setCuePointsMarkDirtyAndUnlock( + QMutexLocker* pLock, + const QList& cuePoints); + void importCuesMarkDirtyAndUnlock( + QMutexLocker* pLock, + const QList& cueInfos); + enum class DurationRounding { SECONDS, // rounded to full seconds NONE // unmodified @@ -409,6 +417,8 @@ class Track : public QObject { ConstWaveformPointer m_waveform; ConstWaveformPointer m_waveformSummary; + QList m_importCuesPending; + friend class TrackDAO; friend class GlobalTrackCache; friend class GlobalTrackCacheResolver; From c598edd274a09302562f5638d7bb3e91ea96cdff Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 30 Mar 2020 20:59:10 +0200 Subject: [PATCH 126/393] Fix wrong channel count for dual mono layout --- src/audio/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/types.h b/src/audio/types.h index 497fa2102e69..1da4c5a5b740 100644 --- a/src/audio/types.h +++ b/src/audio/types.h @@ -52,7 +52,7 @@ class ChannelCount { case ChannelLayout::Mono: return ChannelCount(1); case ChannelLayout::DualMono: - return ChannelCount(1); + return ChannelCount(2); case ChannelLayout::Stereo: return ChannelCount(2); } From 32ef6c87cf460577f331eff7a8fea9c61fa10225 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 30 Mar 2020 20:59:43 +0200 Subject: [PATCH 127/393] Add samples/frames/seconds/milliseconds conversion functions --- src/audio/signalinfo.h | 56 +++++++++++++++++++++++++++++++++++++-- src/sources/audiosource.h | 2 +- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/audio/signalinfo.h b/src/audio/signalinfo.h index d2e8ffeddf93..df08b1eb6cef 100644 --- a/src/audio/signalinfo.h +++ b/src/audio/signalinfo.h @@ -44,8 +44,9 @@ class SignalInfo final { SignalInfo& operator=(const SignalInfo&) = default; // Conversion: #samples / sample offset -> #frames / frame offset + // Only works for sample offsets on frame boundaries! template - inline T samples2frames(T samples) const { + T samples2frames(T samples) const { DEBUG_ASSERT(getChannelCount().isValid()); DEBUG_ASSERT(0 == (samples % getChannelCount())); return samples / getChannelCount(); @@ -53,10 +54,61 @@ class SignalInfo final { // Conversion: #frames / frame offset -> #samples / sample offset template - inline T frames2samples(T frames) const { + T frames2samples(T frames) const { DEBUG_ASSERT(getChannelCount().isValid()); return frames * getChannelCount(); } + + // Conversion: #frames / frame offset -> second offset + template + double frames2secs(T frames) const { + DEBUG_ASSERT(getSampleRate().isValid()); + return static_cast(frames) / getSampleRate(); + } + + // Conversion: second offset -> #frames / frame offset + double secs2frames(double seconds) const { + DEBUG_ASSERT(getSampleRate().isValid()); + return seconds * getSampleRate(); + } + + // Conversion: #frames / frame offset -> millisecond offset + template + double frames2millis(T frames) const { + return frames2secs(frames) * 1000; + } + + // Conversion: millisecond offset -> #frames / frame offset + double millis2frames(double milliseconds) const { + return secs2frames(milliseconds / 1000); + } + + // Conversion: #samples / sample offset -> second offset + // Only works for sample offsets on frame boundaries! + template + double samples2secs(T samples) const { + return frames2secs(samples2frames(samples)); + } + + // Conversion: second offset -> #samples / sample offset + // May return sample offsets that are not on frame boundaries! + template + double secs2samples(double seconds) const { + return frames2samples(secs2frames(seconds)); + } + + // Conversion: #samples / sample offset -> millisecond offset + // Only works for sample offsets on frame boundaries! + template + double samples2millis(T samples) const { + return frames2millis(samples2frames(samples)); + } + + // Conversion: millisecond offset -> #samples / sample offset + // May return sample offsets that are not on frame boundaries! + double millis2samples(double milliseconds) const { + return frames2samples(millis2frames(milliseconds)); + } }; bool operator==( diff --git a/src/sources/audiosource.h b/src/sources/audiosource.h index a5cb7379cd2f..834e5a435148 100644 --- a/src/sources/audiosource.h +++ b/src/sources/audiosource.h @@ -284,7 +284,7 @@ class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSour } inline double getDuration() const { DEBUG_ASSERT(hasDuration()); // prevents division by zero - return double(frameLength()) / double(getSignalInfo().getSampleRate()); + return getSignalInfo().frames2secs(frameLength()); } // Verifies various properties to ensure that the audio data is From 61f5a71c5fe63c18d08c429b1121360635ce43c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 21:43:12 +0200 Subject: [PATCH 128/393] Resort the palette editor buttons. Replace "Save" with "OK" --- src/preferences/colorpaletteeditor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index e6514976326b..91fbd86a7bd2 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -44,12 +44,12 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) pNameLayout->addWidget(m_pSaveAsEdit, 1); QDialogButtonBox* pPaletteButtonBox = new QDialogButtonBox(); + m_pResetButton = pPaletteButtonBox->addButton(QDialogButtonBox::Reset); m_pRemoveButton = pPaletteButtonBox->addButton( tr("Remove Palette"), - QDialogButtonBox::DestructiveRole); + QDialogButtonBox::ResetRole); m_pCloseButton = pPaletteButtonBox->addButton(QDialogButtonBox::Discard); - m_pResetButton = pPaletteButtonBox->addButton(QDialogButtonBox::Reset); - m_pSaveButton = pPaletteButtonBox->addButton(QDialogButtonBox::Save); + m_pSaveButton = pPaletteButtonBox->addButton(QDialogButtonBox::Ok); // Add widgets to dialog QVBoxLayout* pLayout = new QVBoxLayout(); From a166aa2ffc5bd9f336c47bf2653da2ad4a4e04d6 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 30 Mar 2020 21:55:29 +0200 Subject: [PATCH 129/393] Improve documentation of basic audio types --- src/audio/types.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/audio/types.h b/src/audio/types.h index 1da4c5a5b740..64b55f260e01 100644 --- a/src/audio/types.h +++ b/src/audio/types.h @@ -34,6 +34,7 @@ QDebug operator<<(QDebug dbg, ChannelLayout arg); class ChannelCount { private: + // The default value is invalid and indicates a missing or unknown value. static constexpr SINT kValueDefault = 0; public: @@ -100,6 +101,7 @@ QDebug operator<<(QDebug dbg, SampleLayout arg); class SampleRate { private: + // The default value is invalid and indicates a missing or unknown value. static constexpr SINT kValueDefault = 0; public: @@ -138,10 +140,14 @@ QDebug operator<<(QDebug dbg, SampleRate arg); // The bitrate is measured in kbit/s (kbps) and provides information // about the level of compression for lossily encoded audio streams. -// It depends on the metadata and decoder if a value for the bitrate -// is available, i.e. it might be invalid if it cannot be determined. +// +// The value can only be interpreted in the context of the corresponding +// codec. It is supposed to reflect the average bitrate in case of a +// variable bitrate encoding and serves as a rough estimate of the +// expected quality. class Bitrate { private: + // The default value is invalid and indicates a missing or unknown value. static constexpr SINT kValueDefault = 0; public: From f189feee589a898e93491bfa5cb402e80ed80a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 22:06:39 +0200 Subject: [PATCH 130/393] Shrink add and remove button to + and - and move it below the table. --- src/preferences/colorpaletteeditor.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 91fbd86a7bd2..4ef30cb6bc85 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -26,13 +26,21 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) m_pModel(make_parented(m_pTableView)) { // Create widgets QHBoxLayout* pColorButtonLayout = new QHBoxLayout(); - m_pAddColorButton = new QPushButton(tr("Add Color"), this); + QWidget* pExpander = new QWidget(this); + pExpander->setSizePolicy( + QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); + pColorButtonLayout->addWidget(pExpander); + m_pAddColorButton = new QPushButton("+", this); + m_pAddColorButton->setFixedWidth(32); + m_pAddColorButton->setToolTip(tr("Add Color")); pColorButtonLayout->addWidget(m_pAddColorButton); connect(m_pAddColorButton, &QPushButton::clicked, this, &ColorPaletteEditor::slotAddColor); - m_pRemoveColorButton = new QPushButton(tr("Remove Color"), this); + m_pRemoveColorButton = new QPushButton("-", this); + m_pRemoveColorButton->setFixedWidth(32); + m_pRemoveColorButton->setToolTip(tr("Remove Color")); pColorButtonLayout->addWidget(m_pRemoveColorButton); connect(m_pRemoveColorButton, &QPushButton::clicked, @@ -53,8 +61,8 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) // Add widgets to dialog QVBoxLayout* pLayout = new QVBoxLayout(); - pLayout->addLayout(pColorButtonLayout); pLayout->addWidget(m_pTableView, 1); + pLayout->addLayout(pColorButtonLayout); pLayout->addLayout(pNameLayout); pLayout->addWidget(pPaletteButtonBox); setLayout(pLayout); From 40302801d5b37be6d5ff9fa991503341ad4f3641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 23:12:22 +0200 Subject: [PATCH 131/393] Improve selection behaviour during palette edit --- src/preferences/colorpaletteeditor.cpp | 24 +++++++++++++++++++++--- src/preferences/colorpaletteeditor.h | 3 +++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 4ef30cb6bc85..21f3143a5a1e 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -41,6 +41,7 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) m_pRemoveColorButton = new QPushButton("-", this); m_pRemoveColorButton->setFixedWidth(32); m_pRemoveColorButton->setToolTip(tr("Remove Color")); + m_pRemoveColorButton->setDisabled(true); pColorButtonLayout->addWidget(m_pRemoveColorButton); connect(m_pRemoveColorButton, &QPushButton::clicked, @@ -99,6 +100,10 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) &QTableView::doubleClicked, this, &ColorPaletteEditor::slotTableViewDoubleClicked); + connect(m_pTableView->selectionModel(), + &QItemSelectionModel::selectionChanged, + this, + &ColorPaletteEditor::slotSelectionChanged); connect(m_pSaveAsEdit, &QLineEdit::textChanged, this, @@ -170,17 +175,23 @@ void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { void ColorPaletteEditor::slotAddColor() { m_pModel->appendRow(kDefaultPaletteColor); + m_pTableView->scrollToBottom(); + //m_pTableView->selectionModel()->select(QItemSelection + // m_pModel->index(m_pModel->rowCount() - 1, 0), + // QItemSelectionModel::ClearAndSelect ); + m_pTableView->setCurrentIndex( + m_pModel->index(m_pModel->rowCount() - 1, 0)); } void ColorPaletteEditor::slotRemoveColor() { QModelIndexList selection = m_pTableView->selectionModel()->selectedRows(); - - if (selection.count() > 0) { - QModelIndex index = selection.at(0); + for (const auto& index : selection) { //row selected int row = index.row(); m_pModel->removeRow(row); } + m_pRemoveColorButton->setDisabled( + !m_pTableView->selectionModel()->hasSelection()); } void ColorPaletteEditor::slotPaletteNameChanged(const QString& text) { @@ -251,3 +262,10 @@ void ColorPaletteEditor::slotResetButtonClicked() { m_pModel->setColorPalette(palette); slotUpdateButtons(); } + +void ColorPaletteEditor::slotSelectionChanged( + const QItemSelection& selected, + const QItemSelection& deselected) { + Q_UNUSED(deselected); + m_pRemoveColorButton->setDisabled(!selected.count()); +} diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index abc812e29c63..4d5293acdee0 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -31,6 +31,9 @@ class ColorPaletteEditor : public QDialog { void slotSaveButtonClicked(); void slotResetButtonClicked(); void slotRemoveButtonClicked(); + void slotSelectionChanged( + const QItemSelection& selected, + const QItemSelection& deselected); private: bool m_bPaletteExists; From 4b3f9b49e182aa23e59a24666523e63bbbc226a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 30 Mar 2020 23:21:35 +0200 Subject: [PATCH 132/393] Added a warning dialog when removing the palette. --- src/preferences/colorpaletteeditor.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 21f3143a5a1e..5f7d0c595f47 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -239,10 +239,20 @@ void ColorPaletteEditor::slotCloseButtonClicked() { void ColorPaletteEditor::slotRemoveButtonClicked() { QString paletteName = m_pSaveAsEdit->text().trimmed(); - ColorPaletteSettings colorPaletteSettings(m_pConfig); - colorPaletteSettings.removePalette(paletteName); - emit paletteRemoved(paletteName); - accept(); + + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Remove Palette")); + msgBox.setText(tr( + "Do you really want to remove the palette permanently?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + int ret = msgBox.exec(); + if (ret == QMessageBox::Ok) { + ColorPaletteSettings colorPaletteSettings(m_pConfig); + colorPaletteSettings.removePalette(paletteName); + emit paletteRemoved(paletteName); + accept(); + } } void ColorPaletteEditor::slotSaveButtonClicked() { From 5c2880233f7f1520c8717e79ceec59d969848c19 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 30 Mar 2020 11:42:10 +0200 Subject: [PATCH 133/393] scripts/line_length: Use temporary config file --- scripts/line_length.py | 90 +++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index a3129a62c341..326ac4df345d 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import argparse +import re +import os +import subprocess +import tempfile from typing import Optional from typing import Sequence -from subprocess import call # We recommend a maximum line length of 80, but do allow up to 100 characters # if deemed necessary by the developer. Lines that exceed that limit will @@ -17,48 +20,53 @@ def main(argv: Optional[Sequence[str]] = None) -> int: parser.add_argument("filenames", nargs="*", help="Filenames to check") args = parser.parse_args(argv) - for filename in args.filenames: - lineArguments = [] - with open(filename) as fd: - for lineno, line in enumerate(fd): - if len(line) <= LINE_LENGTH_THRESHOLD: + proc = subprocess.run( + ["clang-format", "--dump-config"], capture_output=True, text=True + ) + proc.check_returncode() + + with tempfile.TemporaryDirectory(prefix="clang-format") as tempdir: + # Create temporary config with ColumnLimit enabled + configfile = os.path.join(tempdir, ".clang-format") + with open(configfile, mode="w") as configfp: + configfp.write( + re.sub( + r"(ColumnLimit:\s*)\d+", + r"\g<1>{}".format(BREAK_BEFORE), + proc.stdout, + ) + ) + + for filename in args.filenames: + lineArguments = [] + with open(filename) as fd: + for lineno, line in enumerate(fd): + if len(line) <= LINE_LENGTH_THRESHOLD: + continue + humanlineno = lineno + 1 + print(f"{filename}:{humanlineno} Line is too long.") + lineArguments += [f"-lines={humanlineno}:{humanlineno}"] + + if not lineArguments: continue - humanlineno = lineno + 1 - print(f"{filename}:{humanlineno} Line is too long.") - lineArguments += [f"-lines={humanlineno}:{humanlineno}"] - if len(lineArguments) > 0: - call( - ["clang-format"] - + lineArguments - + [ - "-i", - "-style={" - "BasedOnStyle: Google, " - "IndentWidth: 4, " - "TabWidth: 8, " - "UseTab: Never," - " " + f"ColumnLimit: {BREAK_BEFORE}, " - # clang-format normally unbreaks all short lines, - # which is undesired. - # This does not happen here because line is too long - + "AccessModifierOffset: -2, " - "AlignAfterOpenBracket: DontAlign, " - "AlignOperands: false, " - "AllowShortFunctionsOnASingleLine: None, " - "AllowShortIfStatementsOnASingleLine: false, " - "AllowShortLoopsOnASingleLine: false, " - "BinPackArguments: false, " - "BinPackParameters: false, " - "ConstructorInitializerIndentWidth: 8, " - "ContinuationIndentWidth: 8, " - "IndentCaseLabels: false, " - "DerivePointerAlignment: false, " - "ReflowComments: false, " - "SpaceAfterTemplateKeyword: false, " - "SpacesBeforeTrailingComments: 1}", - f"{filename}", + + fd.seek(0) + cmd = [ + "clang-format", + "--style=file", + "--assume-filename={}".format( + os.path.join(tempdir, "file.cpp") + ), + *lineArguments, ] - ) + proc = subprocess.run( + cmd, stdin=fd, capture_output=True, text=True + ) + proc.check_returncode() + print(proc.stderr) + + with open(filename, mode="w+") as fp: + fp.write(proc.stdout) return 0 From 886cc24196ab8c754d4fb2bb3a543e7f9586cdd9 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Tue, 31 Mar 2020 00:34:56 +0200 Subject: [PATCH 134/393] scripts/line_length.py: Only operate on staged changed lines --- scripts/line_length.py | 150 ++++++++++++++++++++++++++++++++--------- 1 file changed, 117 insertions(+), 33 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index 326ac4df345d..fc66dd5237d7 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import argparse import re +import logging import os +import itertools import subprocess import tempfile -from typing import Optional -from typing import Sequence +import typing # We recommend a maximum line length of 80, but do allow up to 100 characters # if deemed necessary by the developer. Lines that exceed that limit will @@ -14,11 +14,90 @@ LINE_LENGTH_THRESHOLD = 100 BREAK_BEFORE = 80 +Line = typing.NamedTuple( + "Line", [("sourcefile", str), ("number", int), ("text", str)] +) +LineGenerator = typing.Generator[Line, None, None] +FileLines = typing.NamedTuple( + "FileLines", + [("filename", str), ("lines", typing.Sequence[typing.Tuple[int, int]])], +) -def main(argv: Optional[Sequence[str]] = None) -> int: - parser = argparse.ArgumentParser() - parser.add_argument("filenames", nargs="*", help="Filenames to check") - args = parser.parse_args(argv) + +def get_git_added_lines() -> LineGenerator: + proc = subprocess.run( + ["git", "diff", "--cached", "--unified=0"], + capture_output=True, + text=True, + ) + proc.check_returncode() + current_file = None + current_lineno = None + lines_left = 0 + for line in proc.stdout.splitlines(): + match_file = re.match(r"^\+\+\+ b/(.*)$", line) + if match_file: + current_file = match_file.group(1) + lines_left = 0 + continue + + match_lineno = re.match( + r"^@@ -\d+(?:,\d+)? \+([0-9]+(?:,[0-9]+)?) @@", line + ) + if match_lineno: + start, _, length = match_lineno.group(1).partition(",") + current_lineno = int(start) + lines_left = 1 + if length: + lines_left += int(length) + continue + + if lines_left and line.startswith("+"): + yield Line( + sourcefile=current_file, number=current_lineno, text=line[1:] + ) + lines_left -= 1 + current_lineno += 1 + + +def group_lines( + lines: LineGenerator, +) -> typing.Generator[FileLines, None, None]: + for filename, lines in itertools.groupby( + lines, key=lambda line: line.sourcefile + ): + grouped_linenumbers = [] + start_linenumber = None + last_linenumber = None + for line in lines: + if None not in (start_linenumber, last_linenumber): + if line.number != last_linenumber + 1: + grouped_linenumbers.append( + (start_linenumber, last_linenumber) + ) + + if start_linenumber is None: + start_linenumber = line.number + last_linenumber = line.number + + if None not in (start_linenumber, last_linenumber): + grouped_linenumbers.append((start_linenumber, last_linenumber)) + + if grouped_linenumbers: + yield FileLines(filename, grouped_linenumbers) + + +def main() -> int: + logging.basicConfig() + logger = logging.getLogger(__name__) + + all_lines = get_git_added_lines() + long_lines = ( + line + for line in all_lines + if (len(line.text) - 1) >= LINE_LENGTH_THRESHOLD + ) + changed_files = group_lines(long_lines) proc = subprocess.run( ["clang-format", "--dump-config"], capture_output=True, text=True @@ -37,35 +116,40 @@ def main(argv: Optional[Sequence[str]] = None) -> int: ) ) - for filename in args.filenames: - lineArguments = [] - with open(filename) as fd: - for lineno, line in enumerate(fd): - if len(line) <= LINE_LENGTH_THRESHOLD: - continue - humanlineno = lineno + 1 - print(f"{filename}:{humanlineno} Line is too long.") - lineArguments += [f"-lines={humanlineno}:{humanlineno}"] - - if not lineArguments: - continue - - fd.seek(0) - cmd = [ - "clang-format", - "--style=file", - "--assume-filename={}".format( - os.path.join(tempdir, "file.cpp") - ), - *lineArguments, - ] + for changed_file in changed_files: + line_arguments = [ + "--lines={}:{}".format(start, end) + for start, end in changed_file.lines + ] + + if not line_arguments: + continue + + cmd = [ + "clang-format", + "--style=file", + "--assume-filename={}".format( + os.path.join(tempdir, changed_file.filename) + ), + *line_arguments, + ] + + with open(changed_file.filename) as fp: proc = subprocess.run( - cmd, stdin=fd, capture_output=True, text=True + cmd, stdin=fp, capture_output=True, text=True ) - proc.check_returncode() - print(proc.stderr) + try: + proc.check_returncode() + except subprocess.CalledProcessError: + logger.error( + "Error while executing command %s: %s", + cmd, + proc.stderr, + ) + return 1 - with open(filename, mode="w+") as fp: + print(proc.stderr) + with open(changed_file.filename, mode="w+") as fp: fp.write(proc.stdout) return 0 From ed89500ac4c51a667e6618ba3073bb23f9109360 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Tue, 31 Mar 2020 00:59:14 +0200 Subject: [PATCH 135/393] scripts/line_length.py: Pass filename to dump-config --- scripts/line_length.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index fc66dd5237d7..87f43d6d20e8 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -100,7 +100,9 @@ def main() -> int: changed_files = group_lines(long_lines) proc = subprocess.run( - ["clang-format", "--dump-config"], capture_output=True, text=True + ["clang-format", "--dump-config", "src/mixxx.cpp"], + capture_output=True, + text=True, ) proc.check_returncode() From 718b8555a6d057c51d19874df375a23fa738332b Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Tue, 31 Mar 2020 01:03:24 +0200 Subject: [PATCH 136/393] scripts/line_length: Make sure that paths are correct --- scripts/line_length.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index 87f43d6d20e8..0a2572fc4317 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -99,8 +99,10 @@ def main() -> int: ) changed_files = group_lines(long_lines) + rootdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") + cpp_file = os.path.join(rootdir, "src/mixxx.cpp") proc = subprocess.run( - ["clang-format", "--dump-config", "src/mixxx.cpp"], + ["clang-format", "--dump-config", cpp_file], capture_output=True, text=True, ) @@ -136,7 +138,8 @@ def main() -> int: *line_arguments, ] - with open(changed_file.filename) as fp: + filename = os.path.join(rootdir, changed_file.filename) + with open(filename) as fp: proc = subprocess.run( cmd, stdin=fp, capture_output=True, text=True ) @@ -151,7 +154,7 @@ def main() -> int: return 1 print(proc.stderr) - with open(changed_file.filename, mode="w+") as fp: + with open(filename, mode="w+") as fp: fp.write(proc.stdout) return 0 From 55ce18135a39b1540b2b6c566b4c2163c995d71d Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Tue, 31 Mar 2020 01:07:19 +0200 Subject: [PATCH 137/393] scripts/line_length.py: Pull try-except block out of file ctx manager --- scripts/line_length.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index 0a2572fc4317..1b4ce8546d71 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -143,17 +143,16 @@ def main() -> int: proc = subprocess.run( cmd, stdin=fp, capture_output=True, text=True ) - try: - proc.check_returncode() - except subprocess.CalledProcessError: - logger.error( - "Error while executing command %s: %s", - cmd, - proc.stderr, - ) - return 1 + try: + proc.check_returncode() + except subprocess.CalledProcessError: + logger.error( + "Error while executing command %s: %s", cmd, proc.stderr, + ) + return 1 - print(proc.stderr) + if proc.stderr: + print(proc.stderr) with open(filename, mode="w+") as fp: fp.write(proc.stdout) return 0 From 69eb11e84f58c5c3e2477b9366a3312266d8c998 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 31 Mar 2020 01:58:22 +0200 Subject: [PATCH 138/393] Simpliy Travis CI config file --- .travis.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 705a359078c8..5287e8ede65d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,12 @@ -language: cpp +# Enable build config validation (opt-in) +version: ~> 1.0 + +# Default build environment os: linux +dist: bionic +# Default programming language +language: cpp # Build flags common to OS X and Linux. # Parallel builds are important for avoiding OSX build timeouts. @@ -19,8 +25,6 @@ jobs: include: - name: pre-commit if: type != pull_request - os: linux - dist: bionic language: python python: 3.7 # There are too many files in the repo that have formatting issues. We'll @@ -38,8 +42,6 @@ jobs: - name: pre-commit-pr if: type == pull_request - os: linux - dist: bionic language: python python: 3.7 cache: @@ -52,8 +54,6 @@ jobs: addons: [] - name: Ubuntu/gcc/SCons build - os: linux - dist: bionic compiler: gcc # Ubuntu Bionic build prerequisites before_install: @@ -67,8 +67,6 @@ jobs: - ./mixxx-test - name: Ubuntu/gcc/CMake build - os: linux - dist: bionic compiler: gcc cache: ccache # Ubuntu Bionic build prerequisites From a82bf80654c2c4d663317fcf63ba90f2c12fc41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 31 Mar 2020 08:25:35 +0200 Subject: [PATCH 139/393] Remove commented code --- src/preferences/colorpaletteeditor.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 5f7d0c595f47..dfeb671e9a94 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -176,9 +176,6 @@ void ColorPaletteEditor::slotTableViewDoubleClicked(const QModelIndex& index) { void ColorPaletteEditor::slotAddColor() { m_pModel->appendRow(kDefaultPaletteColor); m_pTableView->scrollToBottom(); - //m_pTableView->selectionModel()->select(QItemSelection - // m_pModel->index(m_pModel->rowCount() - 1, 0), - // QItemSelectionModel::ClearAndSelect ); m_pTableView->setCurrentIndex( m_pModel->index(m_pModel->rowCount() - 1, 0)); } From cfce95139b591d4491982aeb3eb1e97c95c516df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 31 Mar 2020 21:30:27 +0200 Subject: [PATCH 140/393] Swap "+" and "-" button --- src/preferences/colorpaletteeditor.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index dfeb671e9a94..30d4c946e6ef 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -30,14 +30,7 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) pExpander->setSizePolicy( QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); pColorButtonLayout->addWidget(pExpander); - m_pAddColorButton = new QPushButton("+", this); - m_pAddColorButton->setFixedWidth(32); - m_pAddColorButton->setToolTip(tr("Add Color")); - pColorButtonLayout->addWidget(m_pAddColorButton); - connect(m_pAddColorButton, - &QPushButton::clicked, - this, - &ColorPaletteEditor::slotAddColor); + m_pRemoveColorButton = new QPushButton("-", this); m_pRemoveColorButton->setFixedWidth(32); m_pRemoveColorButton->setToolTip(tr("Remove Color")); @@ -48,6 +41,15 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent) this, &ColorPaletteEditor::slotRemoveColor); + m_pAddColorButton = new QPushButton("+", this); + m_pAddColorButton->setFixedWidth(32); + m_pAddColorButton->setToolTip(tr("Add Color")); + pColorButtonLayout->addWidget(m_pAddColorButton); + connect(m_pAddColorButton, + &QPushButton::clicked, + this, + &ColorPaletteEditor::slotAddColor); + QHBoxLayout* pNameLayout = new QHBoxLayout(); pNameLayout->addWidget(new QLabel(tr("Name"))); pNameLayout->addWidget(m_pSaveAsEdit, 1); From ae71bf47a870431ff666c873ffa17bb9bebcb5c3 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 31 Mar 2020 22:25:02 +0200 Subject: [PATCH 141/393] fix powerwindow button release --- src/widget/wpushbutton.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/wpushbutton.cpp b/src/widget/wpushbutton.cpp index d2544cd1bb5f..079ec6cd37ca 100644 --- a/src/widget/wpushbutton.cpp +++ b/src/widget/wpushbutton.cpp @@ -463,13 +463,13 @@ void WPushButton::mouseReleaseEvent(QMouseEvent * e) { if (m_leftButtonMode == ControlPushButton::POWERWINDOW && m_iNoStates == 2) { if (leftClick) { - m_bPressed = false; const bool rightButtonDown = QApplication::mouseButtons() & Qt::RightButton; if (m_bPressed && !m_clickTimer.isActive() && !rightButtonDown) { // Release button after timer, but not if right button is clicked double emitValue = getControlParameterLeft() == 0.0 ? 1.0 : 0.0; setControlParameterLeftUp(emitValue); } + m_bPressed = false; } else if (rightClick) { m_bPressed = false; } From f29e3b8c40f94c9c8eeeeef516b507655bb647a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 31 Mar 2020 22:58:49 +0200 Subject: [PATCH 142/393] Added some comments --- src/preferences/colorpaletteeditor.h | 2 ++ src/preferences/colorpalettesettings.h | 1 + src/util/color/colorpalette.h | 3 +++ 3 files changed, 6 insertions(+) diff --git a/src/preferences/colorpaletteeditor.h b/src/preferences/colorpaletteeditor.h index 4d5293acdee0..cfe7b3d1101b 100644 --- a/src/preferences/colorpaletteeditor.h +++ b/src/preferences/colorpaletteeditor.h @@ -10,6 +10,8 @@ #include "preferences/usersettings.h" #include "util/parented_ptr.h" +// Widget for viewing, adding, editing and removing color palettes that can be +// used for track/hotcue colors. Used by the Edit buttons in DlgPrefColors. class ColorPaletteEditor : public QDialog { Q_OBJECT public: diff --git a/src/preferences/colorpalettesettings.h b/src/preferences/colorpalettesettings.h index 3816073f7446..df9f37d0787a 100644 --- a/src/preferences/colorpalettesettings.h +++ b/src/preferences/colorpalettesettings.h @@ -3,6 +3,7 @@ #include "preferences/usersettings.h" #include "util/color/colorpalette.h" +// Saves ColorPalettes to and loads ColorPalettes from the mixxx.cfg file class ColorPaletteSettings { public: explicit ColorPaletteSettings(UserSettingsPointer pConfig) diff --git a/src/util/color/colorpalette.h b/src/util/color/colorpalette.h index d52852a9439e..bdd0f859b4c3 100644 --- a/src/util/color/colorpalette.h +++ b/src/util/color/colorpalette.h @@ -4,6 +4,9 @@ #include "util/color/rgbcolor.h" +// An ordered list of colors that can be picked by the user from WColorPicker, +// used for cue and track colors. Also used by CueControl to map default +// colors to hotcues based on their hotcue number class ColorPalette final { public: ColorPalette( From 260f7a0b9217c667728eb8b7f08d9becd150ae2d Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 31 Mar 2020 12:53:54 +0200 Subject: [PATCH 143/393] Always finish WebTask with a signal --- src/network/webtask.cpp | 35 +++++++++++++++-------------------- src/network/webtask.h | 4 ++-- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index 4348660e2522..a9a05cf9681c 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -90,22 +90,11 @@ void WebTask::onAborted( void WebTask::onTimedOut( QUrl requestUrl) { DEBUG_ASSERT(m_status == Status::TimedOut); - const auto signal = QMetaMethod::fromSignal( - &WebTask::networkError); - if (isSignalConnected(signal)) { - emit networkError( - std::move(requestUrl), - QNetworkReply::TimeoutError, - QStringLiteral("Client-side timeout"), - QByteArray()); - } else { - kLogger.warning() - << "Aborting request after client-side timeout" - << requestUrl; - // Do not abort the request before deleting it, - // i.e. pretend it already has been finished. - deleteAfterFinished(); - } + onNetworkError( + requestUrl, + QNetworkReply::TimeoutError, + tr("Client-side network timeout"), + QByteArray()); } void WebTask::onNetworkError( @@ -181,8 +170,11 @@ void WebTask::slotStart(int timeoutMillis) { DEBUG_ASSERT(thread() == QThread::currentThread()); DEBUG_ASSERT(m_status != Status::Pending); VERIFY_OR_DEBUG_ASSERT(m_networkAccessManager) { - kLogger.warning() - << "No network access"; + onNetworkError( + QUrl(), + QNetworkReply::NetworkSessionFailedError, + tr("No network access"), + QByteArray()); return; } @@ -194,8 +186,11 @@ void WebTask::slotStart(int timeoutMillis) { // The callee is not supposed to abort a request // before it has beeen started successfully. DEBUG_ASSERT(m_status == Status::Idle); - kLogger.warning() - << "Start aborted"; + onNetworkError( + QUrl(), + QNetworkReply::OperationCanceledError, + tr("Start of network task has been aborted"), + QByteArray()); return; } // Still idle after the request has been started diff --git a/src/network/webtask.h b/src/network/webtask.h index f9b13c8f7637..3995ca203991 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -151,8 +151,8 @@ class WebTask : public QObject { }; // Handle status changes and ensure that the task eventually - // gets deleted. The default implementation simply deletes the - // task. + // gets deleted. The default implementations emit a signal + // if connected or otherwise implicitly delete the task. virtual void onAborted( QUrl requestUrl); virtual void onTimedOut( From fff19142c4ac139368122f0bf997e7d21e5ccef4 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 31 Mar 2020 17:05:58 +0200 Subject: [PATCH 144/393] Add debug output operators for requests and repsonses --- src/network/jsonwebtask.cpp | 8 ++++++++ src/network/jsonwebtask.h | 2 ++ src/network/webtask.cpp | 16 ++++++++++++++++ src/network/webtask.h | 6 ++++-- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index 8ccdcffddcb2..ca002487b02c 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -87,6 +87,14 @@ QNetworkRequest newRequest( qRegisterMetaType("mixxx::network::JsonWebResponse"); } +QDebug operator<<(QDebug dbg, const JsonWebResponse& arg) { + return dbg + << "CustomWebResponse{" + << static_cast(arg) + << arg.content + << '}'; +} + JsonWebTask::JsonWebTask( QNetworkAccessManager* networkAccessManager, QUrl baseUrl, diff --git a/src/network/jsonwebtask.h b/src/network/jsonwebtask.h index 879feb1cf6fa..d0491e05bc18 100644 --- a/src/network/jsonwebtask.h +++ b/src/network/jsonwebtask.h @@ -46,6 +46,8 @@ struct JsonWebResponse : public WebResponse { QJsonDocument content; }; +QDebug operator<<(QDebug dbg, const JsonWebResponse& arg); + class JsonWebTask : public WebTask { Q_OBJECT diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index a9a05cf9681c..8565665a2c2f 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -54,10 +54,26 @@ bool readStatusCode( qRegisterMetaType("mixxx::network::WebResponse"); } +QDebug operator<<(QDebug dbg, const WebResponse& arg) { + return dbg + << "WebResponse{" + << arg.replyUrl + << arg.statusCode + << '}'; +} + /*static*/ void CustomWebResponse::registerMetaType() { qRegisterMetaType("mixxx::network::CustomWebResponse"); } +QDebug operator<<(QDebug dbg, const CustomWebResponse& arg) { + return dbg + << "CustomWebResponse{" + << static_cast(arg) + << arg.content + << '}'; +} + WebTask::WebTask( QNetworkAccessManager* networkAccessManager) : m_networkAccessManager(networkAccessManager), diff --git a/src/network/webtask.h b/src/network/webtask.h index 3995ca203991..2c5c3babeaf0 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -58,6 +58,8 @@ struct WebResponse { HttpStatusCode statusCode; }; +QDebug operator<<(QDebug dbg, const WebResponse& arg); + struct CustomWebResponse : public WebResponse { public: static void registerMetaType(); @@ -76,11 +78,11 @@ struct CustomWebResponse : public WebResponse { CustomWebResponse& operator=(const CustomWebResponse&) = default; CustomWebResponse& operator=(CustomWebResponse&&) = default; - QUrl replyUrl; - HttpStatusCode statusCode; QByteArray content; }; +QDebug operator<<(QDebug dbg, const CustomWebResponse& arg); + // A transient task for performing a single HTTP network request // asynchronously. // From bf40170df323a5864fa7bfe96e1bb44b92b28099 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 31 Mar 2020 17:06:52 +0200 Subject: [PATCH 145/393] Remove implicit auto-delete from web tasks --- src/musicbrainz/tagfetcher.cpp | 85 ++++++++++++------- src/musicbrainz/tagfetcher.h | 20 ++--- src/musicbrainz/web/acoustidlookuptask.cpp | 24 +++--- src/musicbrainz/web/acoustidlookuptask.h | 5 +- .../web/musicbrainzrecordingstask.cpp | 58 ++++++++----- .../web/musicbrainzrecordingstask.h | 9 +- src/network/jsonwebtask.cpp | 29 ++++--- src/network/jsonwebtask.h | 11 +-- src/network/webtask.cpp | 67 +++++++-------- src/network/webtask.h | 39 +++++---- 10 files changed, 190 insertions(+), 157 deletions(-) diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp index 1435aa2fa503..3c80410a5266 100644 --- a/src/musicbrainz/tagfetcher.cpp +++ b/src/musicbrainz/tagfetcher.cpp @@ -36,30 +36,17 @@ void TagFetcher::startFetch( &TagFetcher::slotFingerprintReady); } -void TagFetcher::abortAcoustIdTask() { +void TagFetcher::cancel() { + m_pTrack.reset(); + m_fingerprintWatcher.cancel(); if (m_pAcoustIdTask) { - disconnect(m_pAcoustIdTask); - m_pAcoustIdTask->deleteBeforeFinished(); - m_pAcoustIdTask = nullptr; + m_pAcoustIdTask->invokeAbort(); } -} - -void TagFetcher::abortMusicBrainzTask() { if (m_pMusicBrainzTask) { - disconnect(m_pMusicBrainzTask); - m_pMusicBrainzTask->deleteBeforeFinished(); - m_pMusicBrainzTask = nullptr; + m_pMusicBrainzTask->invokeAbort(); } } -void TagFetcher::cancel() { - // qDebug()<< "Cancel tagfetching"; - m_fingerprintWatcher.cancel(); - abortAcoustIdTask(); - abortMusicBrainzTask(); - m_pTrack.reset(); -} - void TagFetcher::slotFingerprintReady() { if (!m_pTrack || !m_fingerprintWatcher.isFinished()) { @@ -74,14 +61,13 @@ void TagFetcher::slotFingerprintReady() { return; } - abortAcoustIdTask(); - emit fetchProgress(tr("Identifying track through Acoustid")); DEBUG_ASSERT(!m_pAcoustIdTask); - m_pAcoustIdTask = new mixxx::AcoustIdLookupTask( + m_pAcoustIdTask = make_parented( &m_network, fingerprint, - m_pTrack->getDurationInt()); + m_pTrack->getDurationInt(), + this); connect(m_pAcoustIdTask, &mixxx::AcoustIdLookupTask::succeeded, this, @@ -104,7 +90,12 @@ void TagFetcher::slotFingerprintReady() { void TagFetcher::slotAcoustIdTaskSucceeded( QList recordingIds) { - abortAcoustIdTask(); + VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { + return; + } + m_pAcoustIdTask->deleteAfterFinished(); + m_pAcoustIdTask = nullptr; + if (!m_pTrack) { return; } @@ -118,9 +109,10 @@ void TagFetcher::slotAcoustIdTaskSucceeded( emit fetchProgress(tr("Retrieving metadata from MusicBrainz")); DEBUG_ASSERT(!m_pMusicBrainzTask); - m_pMusicBrainzTask = new mixxx::MusicBrainzRecordingsTask( + m_pMusicBrainzTask = make_parented( &m_network, - std::move(recordingIds)); + std::move(recordingIds), + this); connect(m_pMusicBrainzTask, &mixxx::MusicBrainzRecordingsTask::succeeded, this, @@ -143,7 +135,11 @@ void TagFetcher::slotAcoustIdTaskSucceeded( void TagFetcher::slotAcoustIdTaskFailed( mixxx::network::JsonWebResponse response) { - cancel(); + VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { + return; + } + m_pAcoustIdTask->deleteAfterFinished(); + m_pAcoustIdTask = nullptr; emit networkError( response.statusCode, "AcoustID", @@ -152,7 +148,11 @@ void TagFetcher::slotAcoustIdTaskFailed( } void TagFetcher::slotAcoustIdTaskAborted() { - abortAcoustIdTask(); + VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { + return; + } + m_pAcoustIdTask->deleteAfterFinished(); + m_pAcoustIdTask = nullptr; } void TagFetcher::slotAcoustIdTaskNetworkError( @@ -162,7 +162,11 @@ void TagFetcher::slotAcoustIdTaskNetworkError( QByteArray errorContent) { Q_UNUSED(requestUrl); Q_UNUSED(errorContent); - cancel(); + VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { + return; + } + m_pAcoustIdTask->deleteAfterFinished(); + m_pAcoustIdTask = nullptr; emit networkError( mixxx::network::kHttpStatusCodeInvalid, "AcoustID", @@ -171,7 +175,11 @@ void TagFetcher::slotAcoustIdTaskNetworkError( } void TagFetcher::slotMusicBrainzTaskAborted() { - abortMusicBrainzTask(); + VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { + return; + } + m_pMusicBrainzTask->deleteAfterFinished(); + m_pMusicBrainzTask = nullptr; } void TagFetcher::slotMusicBrainzTaskNetworkError( @@ -181,7 +189,11 @@ void TagFetcher::slotMusicBrainzTaskNetworkError( QByteArray errorContent) { Q_UNUSED(requestUrl); Q_UNUSED(errorContent); - cancel(); + VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { + return; + } + m_pMusicBrainzTask->deleteAfterFinished(); + m_pMusicBrainzTask = nullptr; emit networkError( mixxx::network::kHttpStatusCodeInvalid, "MusicBrainz", @@ -193,7 +205,11 @@ void TagFetcher::slotMusicBrainzTaskFailed( mixxx::network::WebResponse response, int errorCode, QString errorMessage) { - cancel(); + VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { + return; + } + m_pMusicBrainzTask->deleteAfterFinished(); + m_pMusicBrainzTask = nullptr; emit networkError( response.statusCode, "MusicBrainz", @@ -204,7 +220,12 @@ void TagFetcher::slotMusicBrainzTaskFailed( void TagFetcher::slotMusicBrainzTaskSucceeded( QList guessedTrackReleases) { auto pOriginalTrack = std::move(m_pTrack); - abortMusicBrainzTask(); + DEBUG_ASSERT(!m_pTrack); + VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { + return; + } + m_pMusicBrainzTask->deleteAfterFinished(); + m_pMusicBrainzTask = nullptr; if (!pOriginalTrack) { // aborted return; diff --git a/src/musicbrainz/tagfetcher.h b/src/musicbrainz/tagfetcher.h index 622cebe11cfa..3caf4ac7e32e 100644 --- a/src/musicbrainz/tagfetcher.h +++ b/src/musicbrainz/tagfetcher.h @@ -2,23 +2,24 @@ #include #include -#include #include "musicbrainz/web/acoustidlookuptask.h" #include "musicbrainz/web/musicbrainzrecordingstask.h" #include "track/track.h" +#include "util/parented_ptr.h" class TagFetcher : public QObject { - Q_OBJECT + Q_OBJECT - // Implements retrieval of metadata in 3 subsequent stages: - // 1. Chromaprint -> AcoustID fingerprint - // 2. AcoustID -> MusicBrainz recording UUIDs - // 3. MusicBrainz -> MusicBrainz track releases + // Implements retrieval of metadata in 3 subsequent stages: + // 1. Chromaprint -> AcoustID fingerprint + // 2. AcoustID -> MusicBrainz recording UUIDs + // 3. MusicBrainz -> MusicBrainz track releases public: explicit TagFetcher( QObject* parent = nullptr); + ~TagFetcher() override = default; void startFetch( TrackPointer pTrack); @@ -66,16 +67,13 @@ class TagFetcher : public QObject { QByteArray errorContent); private: - void abortAcoustIdTask(); - void abortMusicBrainzTask(); - QNetworkAccessManager m_network; QFutureWatcher m_fingerprintWatcher; - QPointer m_pAcoustIdTask; + parented_ptr m_pAcoustIdTask; - QPointer m_pMusicBrainzTask; + parented_ptr m_pMusicBrainzTask; TrackPointer m_pTrack; }; diff --git a/src/musicbrainz/web/acoustidlookuptask.cpp b/src/musicbrainz/web/acoustidlookuptask.cpp index 6f8a2acdfd75..a9807f6b8bbf 100644 --- a/src/musicbrainz/web/acoustidlookuptask.cpp +++ b/src/musicbrainz/web/acoustidlookuptask.cpp @@ -67,11 +67,13 @@ network::JsonWebRequest lookupRequest() { AcoustIdLookupTask::AcoustIdLookupTask( QNetworkAccessManager* networkAccessManager, const QString& fingerprint, - int duration) + int duration, + QObject* parent) : network::JsonWebTask( networkAccessManager, kBaseUrl, - lookupRequest()), + lookupRequest(), + parent), m_urlQuery(lookupUrlQuery(fingerprint, duration)) { } @@ -110,7 +112,7 @@ QNetworkReply* AcoustIdLookupTask::sendNetworkRequest( } void AcoustIdLookupTask::onFinished( - network::JsonWebResponse response) { + network::JsonWebResponse&& response) { if (!response.isStatusCodeSuccess()) { kLogger.warning() << "Request failed with HTTP status code" @@ -198,7 +200,7 @@ void AcoustIdLookupTask::onFinished( const auto recordingId = QUuid(recordingObject.value(QLatin1String("id")).toString()); VERIFY_OR_DEBUG_ASSERT(!recordingId.isNull()) { - continue; + continue; } recordingIds.append(recordingId); } @@ -209,15 +211,15 @@ void AcoustIdLookupTask::onFinished( void AcoustIdLookupTask::emitSucceeded( QList&& recordingIds) { - const auto signal = QMetaMethod::fromSignal( - &AcoustIdLookupTask::succeeded); - DEBUG_ASSERT(receivers(signal.name()) <= 1); // unique connection - if (isSignalConnected(signal)) { - emit succeeded( - std::move(recordingIds)); - } else { + VERIFY_OR_DEBUG_ASSERT( + isSignalFuncConnected(&AcoustIdLookupTask::succeeded)) { + kLogger.warning() + << "Unhandled succeeded signal"; deleteLater(); + return; } + emit succeeded( + std::move(recordingIds)); } } // namespace mixxx diff --git a/src/musicbrainz/web/acoustidlookuptask.h b/src/musicbrainz/web/acoustidlookuptask.h index 34dc45749166..20a1a504a52b 100644 --- a/src/musicbrainz/web/acoustidlookuptask.h +++ b/src/musicbrainz/web/acoustidlookuptask.h @@ -15,7 +15,8 @@ class AcoustIdLookupTask : public network::JsonWebTask { AcoustIdLookupTask( QNetworkAccessManager* networkAccessManager, const QString& fingerprint, - int duration); + int duration, + QObject* parent = nullptr); ~AcoustIdLookupTask() override = default; signals: @@ -31,7 +32,7 @@ class AcoustIdLookupTask : public network::JsonWebTask { private: void onFinished( - network::JsonWebResponse response) override; + network::JsonWebResponse&& response) override; void emitSucceeded( QList&& recordingIds); diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp index cccdaf673c52..054338e68c03 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp +++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp @@ -45,7 +45,7 @@ QUrlQuery createUrlQuery() { } QNetworkRequest createNetworkRequest( - const QUuid& recordingId) { + const QUuid& recordingId) { DEBUG_ASSERT(kBaseUrl.isValid()); DEBUG_ASSERT(!recordingId.isNull()); QUrl url = kBaseUrl; @@ -65,14 +65,22 @@ QNetworkRequest createNetworkRequest( MusicBrainzRecordingsTask::MusicBrainzRecordingsTask( QNetworkAccessManager* networkAccessManager, - QList&& recordingIds) + QList&& recordingIds, + QObject* parent) : network::WebTask( - networkAccessManager), + networkAccessManager, + parent), m_queuedRecordingIds(std::move(recordingIds)), m_parentTimeoutMillis(0) { musicbrainz::registerMetaTypesOnce(); } +MusicBrainzRecordingsTask::~MusicBrainzRecordingsTask() { + VERIFY_OR_DEBUG_ASSERT(!m_pendingNetworkReply) { + m_pendingNetworkReply->deleteLater(); + } +} + bool MusicBrainzRecordingsTask::doStart( QNetworkAccessManager* networkAccessManager, int parentTimeoutMillis) { @@ -176,13 +184,13 @@ void MusicBrainzRecordingsTask::slotNetworkReplyFinished() { << "GET reply" << "statusCode:" << statusCode << "body:" << body; - const auto error = musicbrainz::Error(reader); + auto error = musicbrainz::Error(reader); emitFailed( network::WebResponse( networkReply->url(), statusCode), error.code, - error.message); + std::move(error.message)); return; } @@ -223,31 +231,35 @@ void MusicBrainzRecordingsTask::slotNetworkReplyFinished() { void MusicBrainzRecordingsTask::emitSucceeded( QList&& trackReleases) { - const auto signal = QMetaMethod::fromSignal( - &MusicBrainzRecordingsTask::succeeded); - DEBUG_ASSERT(receivers(signal.name()) <= 1); // unique connection - if (isSignalConnected(signal)) { - emit succeeded( - std::move(trackReleases)); - } else { + VERIFY_OR_DEBUG_ASSERT( + isSignalFuncConnected(&MusicBrainzRecordingsTask::succeeded)) { + kLogger.warning() + << "Unhandled succeeded signal"; deleteLater(); + return; } + emit succeeded( + std::move(trackReleases)); } + void MusicBrainzRecordingsTask::emitFailed( - network::WebResponse response, + network::WebResponse&& response, int errorCode, - QString errorMessage) { - const auto signal = QMetaMethod::fromSignal( - &MusicBrainzRecordingsTask::succeeded); - DEBUG_ASSERT(receivers(signal.name()) <= 1); // unique connection - if (isSignalConnected(signal)) { - emit failed( - response, - errorCode, - errorMessage); - } else { + QString&& errorMessage) { + VERIFY_OR_DEBUG_ASSERT( + isSignalFuncConnected(&MusicBrainzRecordingsTask::failed)) { + kLogger.warning() + << "Unhandled failed signal" + << response + << errorCode + << errorMessage; deleteLater(); + return; } + emit failed( + std::move(response), + errorCode, + std::move(errorMessage)); } } // namespace mixxx diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.h b/src/musicbrainz/web/musicbrainzrecordingstask.h index 614315d75840..e3f65e55a808 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.h +++ b/src/musicbrainz/web/musicbrainzrecordingstask.h @@ -17,8 +17,9 @@ class MusicBrainzRecordingsTask : public network::WebTask { public: MusicBrainzRecordingsTask( QNetworkAccessManager* networkAccessManager, - QList&& recordingIds); - ~MusicBrainzRecordingsTask() override = default; + QList&& recordingIds, + QObject* parent = nullptr); + ~MusicBrainzRecordingsTask() override; signals: void succeeded( @@ -41,9 +42,9 @@ class MusicBrainzRecordingsTask : public network::WebTask { void emitSucceeded( QList&& trackReleases); void emitFailed( - network::WebResponse response, + network::WebResponse&& response, int errorCode, - QString errorMessage); + QString&& errorMessage); void continueWithNextRequest(); diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index ca002487b02c..2d4b76309f36 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -97,10 +97,11 @@ QDebug operator<<(QDebug dbg, const JsonWebResponse& arg) { JsonWebTask::JsonWebTask( QNetworkAccessManager* networkAccessManager, - QUrl baseUrl, - JsonWebRequest request) - : WebTask(networkAccessManager), - m_baseUrl(std::move(baseUrl)), + const QUrl& baseUrl, + JsonWebRequest&& request, + QObject* parent) + : WebTask(networkAccessManager, parent), + m_baseUrl(baseUrl), m_request(std::move(request)), m_pendingNetworkReply(nullptr) { std::call_once(registerMetaTypesOnceFlag, registerMetaTypesOnce); @@ -114,7 +115,7 @@ JsonWebTask::~JsonWebTask() { } void JsonWebTask::onFinished( - JsonWebResponse response) { + JsonWebResponse&& response) { kLogger.info() << "Response received" << response.replyUrl @@ -124,7 +125,7 @@ void JsonWebTask::onFinished( } void JsonWebTask::onFinishedCustom( - CustomWebResponse response) { + CustomWebResponse&& response) { kLogger.info() << "Custom response received" << response.replyUrl @@ -320,16 +321,16 @@ void JsonWebTask::slotNetworkReplyFinished() { } void JsonWebTask::emitFailed( - network::JsonWebResponse response) { - const auto signal = QMetaMethod::fromSignal( - &JsonWebTask::failed); - DEBUG_ASSERT(receivers(signal.name()) <= 1); // unique connection - if (isSignalConnected(signal)) { - emit failed( - std::move(response)); - } else { + network::JsonWebResponse&& response) { + VERIFY_OR_DEBUG_ASSERT( + isSignalFuncConnected(&JsonWebTask::failed)) { + kLogger.warning() + << "Unhandled failed signal" + << response; deleteLater(); + return; } + emit failed(std::move(response)); } } // namespace network diff --git a/src/network/jsonwebtask.h b/src/network/jsonwebtask.h index d0491e05bc18..9c653d7c6bd6 100644 --- a/src/network/jsonwebtask.h +++ b/src/network/jsonwebtask.h @@ -54,8 +54,9 @@ class JsonWebTask : public WebTask { public: JsonWebTask( QNetworkAccessManager* networkAccessManager, - QUrl baseUrl, - JsonWebRequest request); + const QUrl& baseUrl, + JsonWebRequest&& request, + QObject* parent = nullptr); ~JsonWebTask() override; signals: @@ -74,16 +75,16 @@ class JsonWebTask : public WebTask { const QJsonDocument& content); void emitFailed( - network::JsonWebResponse response); + network::JsonWebResponse&& response); private: // Handle the response and ensure that the task eventually // gets deleted. The default implementation discards the // response and deletes the task. virtual void onFinished( - JsonWebResponse response); + JsonWebResponse&& response); virtual void onFinishedCustom( - CustomWebResponse response); + CustomWebResponse&& response); bool doStart( QNetworkAccessManager* networkAccessManager, diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index 8565665a2c2f..c9998b537646 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -1,6 +1,5 @@ #include "network/webtask.h" -#include #include #include #include // std::once_flag @@ -75,8 +74,10 @@ QDebug operator<<(QDebug dbg, const CustomWebResponse& arg) { } WebTask::WebTask( - QNetworkAccessManager* networkAccessManager) - : m_networkAccessManager(networkAccessManager), + QNetworkAccessManager* networkAccessManager, + QObject* parent) + : QObject(parent), + m_networkAccessManager(networkAccessManager), m_timeoutTimerId(kInvalidTimerId), m_status(Status::Idle) { std::call_once(registerMetaTypesOnceFlag, registerMetaTypesOnce); @@ -89,52 +90,51 @@ WebTask::~WebTask() { } void WebTask::onAborted( - QUrl requestUrl) { + QUrl&& requestUrl) { DEBUG_ASSERT(m_status == Status::Aborted); - const auto signal = QMetaMethod::fromSignal( - &WebTask::aborted); - if (isSignalConnected(signal)) { - emit aborted(requestUrl); - } else { - kLogger.info() - << "Request aborted" + VERIFY_OR_DEBUG_ASSERT( + isSignalFuncConnected(&WebTask::aborted)) { + kLogger.warning() + << "Unhandled abort signal" << requestUrl; - deleteAfterFinished(); + deleteLater(); + return; } + emit aborted( + std::move(requestUrl)); } void WebTask::onTimedOut( - QUrl requestUrl) { + QUrl&& requestUrl) { DEBUG_ASSERT(m_status == Status::TimedOut); onNetworkError( - requestUrl, + std::move(requestUrl), QNetworkReply::TimeoutError, tr("Client-side network timeout"), QByteArray()); } void WebTask::onNetworkError( - QUrl requestUrl, + QUrl&& requestUrl, QNetworkReply::NetworkError errorCode, - QString errorString, - QByteArray errorContent) { - const auto signal = QMetaMethod::fromSignal( - &WebTask::networkError); - if (isSignalConnected(signal)) { - emit networkError( - std::move(requestUrl), - errorCode, - std::move(errorString), - std::move(errorContent)); - } else { + QString&& errorString, + QByteArray&& errorContent) { + VERIFY_OR_DEBUG_ASSERT( + isSignalFuncConnected(&WebTask::networkError)) { kLogger.warning() - << "Network error" + << "Unhandled network error signal" << requestUrl << errorCode << errorString << errorContent; - deleteAfterFinished(); + deleteLater(); + return; } + emit networkError( + std::move(requestUrl), + errorCode, + std::move(errorString), + std::move(errorContent)); } void WebTask::invokeStart(int timeoutMillis) { @@ -165,15 +165,6 @@ void WebTask::invokeAbort() { ); } -void WebTask::deleteBeforeFinished() { - // Might be called from any thread so we must not - // access any member variables! - // Do not disconnect any connections, because otherwise - // the destroyed() signal is not received! - invokeAbort(); - deleteLater(); -} - void WebTask::deleteAfterFinished() { // Might be called from any thread so we must not // access any member variables! @@ -260,7 +251,7 @@ QUrl WebTask::abort() { kLogger.debug() << "Aborting..."; QUrl url = doAbort(); - onAborted(url); + onAborted(QUrl(url)); return url; } diff --git a/src/network/webtask.h b/src/network/webtask.h index 2c5c3babeaf0..09ce1413796b 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -100,7 +101,8 @@ class WebTask : public QObject { public: explicit WebTask( - QNetworkAccessManager* networkAccessManager); + QNetworkAccessManager* networkAccessManager, + QObject* parent = nullptr); ~WebTask() override; // timeoutMillis <= 0: No timeout (unlimited) @@ -114,10 +116,6 @@ class WebTask : public QObject { // Cancel a pending request from the event loop thread. QUrl abort(); - // Abort the pending request while suppressing any signals - // and mark the task for deletion. - void deleteBeforeFinished(); - // Disconnect from all signals after receiving a reply // and mark the task for deletion. void deleteAfterFinished(); @@ -142,6 +140,13 @@ class WebTask : public QObject { QByteArray errorContent); protected: + template + bool isSignalFuncConnected( + S signalFunc) { + const QMetaMethod signal = QMetaMethod::fromSignal(signalFunc); + return isSignalConnected(signal); + } + void timerEvent(QTimerEvent* event) final; enum class Status { @@ -152,29 +157,29 @@ class WebTask : public QObject { Finished, }; + QUrl abortPendingNetworkReply( + QNetworkReply* pendingNetworkReply); + QUrl timeOutPendingNetworkReply( + QNetworkReply* pendingNetworkReply); + + QPair receiveNetworkReply(); + // Handle status changes and ensure that the task eventually // gets deleted. The default implementations emit a signal // if connected or otherwise implicitly delete the task. virtual void onAborted( - QUrl requestUrl); + QUrl&& requestUrl); virtual void onTimedOut( - QUrl requestUrl); - - QUrl abortPendingNetworkReply( - QNetworkReply* pendingNetworkReply); - QUrl timeOutPendingNetworkReply( - QNetworkReply* pendingNetworkReply); + QUrl&& requestUrl); // Handle the abort and ensure that the task eventually // gets deleted. The default implementation logs a warning // and deletes the task. virtual void onNetworkError( - QUrl requestUrl, + QUrl&& requestUrl, QNetworkReply::NetworkError errorCode, - QString errorString, - QByteArray errorContent); - - QPair receiveNetworkReply(); + QString&& errorString, + QByteArray&& errorContent); private: // Try to compose and send the actual network request. If From 019739326fce10b4679a1dbc160f8a107d337b5e Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 1 Apr 2020 03:18:46 +0200 Subject: [PATCH 146/393] restore: no play when passthrough is enabled --- src/engine/enginebuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index ca73661aa6fe..27506c7a5927 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -619,7 +619,7 @@ bool EngineBuffer::updateIndicatorsAndModifyPlay(bool newPlay) { if ((!m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0) || (m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0 && m_filepos_play >= m_pTrackSamples->get() && - !atomicLoadRelaxed(m_iSeekQueued))) { + !atomicLoadRelaxed(m_iSeekQueued)) || m_pPassthroughEnabled->toBool()) { // play not possible playPossible = false; } From 21ee89d3e545082ba8ddba2ca550e5ae035c6a7f Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 1 Apr 2020 00:35:12 +0200 Subject: [PATCH 147/393] Don't pass parent to object in a different thread --- src/musicbrainz/tagfetcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp index 3c80410a5266..a79dd6a09396 100644 --- a/src/musicbrainz/tagfetcher.cpp +++ b/src/musicbrainz/tagfetcher.cpp @@ -25,8 +25,8 @@ void TagFetcher::startFetch( m_pTrack = pTrack; emit fetchProgress(tr("Fingerprinting track")); - const auto fingerprintTask = QtConcurrent::run([this, pTrack] { - return ChromaPrinter(this).getFingerprint(pTrack); + const auto fingerprintTask = QtConcurrent::run([pTrack] { + return ChromaPrinter().getFingerprint(pTrack); }); m_fingerprintWatcher.setFuture(fingerprintTask); connect( From 9abb8a6798aac08e89bb9f3f98b173ecbd275ae0 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Wed, 1 Apr 2020 16:08:16 +0530 Subject: [PATCH 148/393] widget/wtrackproperty: introduce new class to decouple context menu from wtracktableview --- CMakeLists.txt | 3 +- build/depends.py | 1 + src/widget/wtrackmenu.cpp | 199 ++++++++++++++++++++++++++++++ src/widget/wtrackmenu.h | 114 +++++++++++++++++ src/widget/wtracktableview.cpp | 215 +++++++++------------------------ src/widget/wtracktableview.h | 148 ++++++++++++----------- 6 files changed, 447 insertions(+), 233 deletions(-) create mode 100644 src/widget/wtrackmenu.cpp create mode 100644 src/widget/wtrackmenu.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5053489dca0d..1a33ba001d05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -717,6 +717,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/widget/wstarrating.cpp src/widget/wstatuslight.cpp src/widget/wtime.cpp + src/widget/wtrackmenu.cpp src/widget/wtrackproperty.cpp src/widget/wtracktableview.cpp src/widget/wtracktableviewheader.cpp @@ -836,7 +837,7 @@ elseif(UNIX) endif() # The mixxx executable -add_executable(mixxx WIN32 src/main.cpp) +add_executable(mixxx WIN32 src/main.cpp src/widget/random.cpp src/widget/random.h) target_link_libraries(mixxx PUBLIC mixxx-lib) # diff --git a/build/depends.py b/build/depends.py index cc6572f84521..36f225f6c9db 100644 --- a/build/depends.py +++ b/build/depends.py @@ -1018,6 +1018,7 @@ def sources(self, build): "src/widget/wlibrarytableview.cpp", "src/widget/wanalysislibrarytableview.cpp", "src/widget/wlibrarytextbrowser.cpp", + "src/widget/wtrackmenu.cpp", "src/database/mixxxdb.cpp", "src/database/schemamanager.cpp", diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp new file mode 100644 index 000000000000..f45fd07eb7c6 --- /dev/null +++ b/src/widget/wtrackmenu.cpp @@ -0,0 +1,199 @@ +#include "widget/wtrackmenu.h" + +#include +#include +#include +#include +#include "widget/wlibrarytableview.h" +#include "library/dao/trackdao.h" +#include "library/trackcollection.h" + + +WTrackMenu::WTrackMenu(QWidget *parent, TrackCollectionManager* pTrackCollectionManager) + : QMenu(parent), + m_pTrackCollectionManager(pTrackCollectionManager){ + m_pLoadToMenu = new QMenu(this); + m_pLoadToMenu->setTitle(tr("Load to")); + m_pDeckMenu = new QMenu(this); + m_pDeckMenu->setTitle(tr("Deck")); + m_pSamplerMenu = new QMenu(this); + m_pSamplerMenu->setTitle(tr("Sampler")); + + m_pPlaylistMenu = new QMenu(this); + m_pPlaylistMenu->setTitle(tr("Add to Playlist")); + connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulatePlaylistMenu())); + m_pCrateMenu = new QMenu(this); + m_pCrateMenu->setTitle(tr("Crates")); + connect(m_pCrateMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulateCrateMenu())); + + m_pMetadataMenu = new QMenu(this); + m_pMetadataMenu->setTitle(tr("Metadata")); + + m_pMetadataUpdateExternalCollectionsMenu = new QMenu(this); + m_pMetadataUpdateExternalCollectionsMenu->setTitle(tr("Update external collections")); + + m_pBPMMenu = new QMenu(this); + m_pBPMMenu->setTitle(tr("Adjust BPM")); + + m_pColorMenu = new QMenu(this); + m_pColorMenu->setTitle(tr("Select Color")); + + m_pClearMetadataMenu = new QMenu(this); + //: Reset metadata in right click track context menu in library + m_pClearMetadataMenu->setTitle(tr("Reset")); + + m_pCoverMenu = new WCoverArtMenu(this); + m_pCoverMenu->setTitle(tr("Cover Art")); + + connect(m_pCoverMenu, SIGNAL(coverInfoSelected(const CoverInfoRelative&)), + this, SLOT(slotCoverInfoSelected(const CoverInfoRelative&))); + connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), + this, SLOT(slotReloadCoverArt())); + createActions(); +} + +WTrackMenu::~WTrackMenu() { + delete m_pImportMetadataFromFileAct; + delete m_pImportMetadataFromMusicBrainzAct; + delete m_pExportMetadataAct; + delete m_pAddToPreviewDeck; + delete m_pAutoDJBottomAct; + delete m_pAutoDJTopAct; + delete m_pAutoDJReplaceAct; + delete m_pRemoveAct; + delete m_pRemovePlaylistAct; + delete m_pRemoveCrateAct; + delete m_pHideAct; + delete m_pUnhideAct; + delete m_pPropertiesAct; + delete m_pLoadToMenu; + delete m_pDeckMenu; + delete m_pSamplerMenu; + delete m_pPlaylistMenu; + delete m_pCrateMenu; + delete m_pMetadataMenu; + delete m_pClearMetadataMenu; + delete m_pCoverMenu; + delete m_pBpmLockAction; + delete m_pBpmUnlockAction; + delete m_pBpmDoubleAction; + delete m_pBpmHalveAction; + delete m_pBpmTwoThirdsAction; + delete m_pBpmThreeFourthsAction; + delete m_pBpmFourThirdsAction; + delete m_pBpmThreeHalvesAction; + delete m_pBPMMenu; + delete m_pColorMenu; + delete m_pClearBeatsAction; + delete m_pClearPlayCountAction; + delete m_pClearMainCueAction; + delete m_pClearHotCuesAction; + delete m_pClearIntroCueAction; + delete m_pClearOutroCueAction; + delete m_pClearLoopAction; + delete m_pClearReplayGainAction; + delete m_pClearWaveformAction; + delete m_pClearKeyAction; + delete m_pClearAllMetadataAction; + delete m_pPurgeAct; + delete m_pFileBrowserAct; +} + + +void WTrackMenu::createActions() { +// DEBUG_ASSERT(this); +// DEBUG_ASSERT(m_pSamplerMenu); +// +// m_pRemoveAct = new QAction(tr("Remove"), this); +// connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); +// +// m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); +// connect(m_pRemovePlaylistAct, SIGNAL(triggered()), this, SLOT(slotRemove())); +// +// m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); +// connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); +// +// m_pHideAct = new QAction(tr("Hide from Library"), this); +// connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); +// +// m_pUnhideAct = new QAction(tr("Unhide from Library"), this); +// connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); +// +// m_pPurgeAct = new QAction(tr("Purge from Library"), this); +// connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); +// +// m_pPropertiesAct = new QAction(tr("Properties"), this); +// connect(m_pPropertiesAct, SIGNAL(triggered()), +// this, SLOT(slotShowTrackInfo())); +// + m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); + connect(m_pFileBrowserAct, SIGNAL(triggered()), + this, SLOT(slotOpenInFileBrowser())); +// +// m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); +// connect(m_pAutoDJBottomAct, SIGNAL(triggered()), +// this, SLOT(slotAddToAutoDJBottom())); +// +// m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); +// connect(m_pAutoDJTopAct, SIGNAL(triggered()), +// this, SLOT(slotAddToAutoDJTop())); +// +// m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); +// connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), +// this, SLOT(slotAddToAutoDJReplace())); +// +// m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); +// connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), +// this, SLOT(slotImportTrackMetadataFromFileTags())); +// +// m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); +// connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), +// this, SLOT(slotShowDlgTagFetcher())); +// +// m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); +// connect(m_pExportMetadataAct, SIGNAL(triggered()), +// this, SLOT(slotExportTrackMetadataIntoFileTags())); +} + +void WTrackMenu::setTracks(TrackIdList trackIdList) { + m_pTrackIdList = std::move(trackIdList); + // Store the track pointers at each initialization of track ids. + trackIdsToTrackPointers(); + // Add actions to menu + setupActions(); +} + +void WTrackMenu::setTrack(TrackId trackId) { + // Create a QList of single track to maintain common functions + // for single and multi track selection. + TrackIdList singleItemTrackIdList; + singleItemTrackIdList.push_back(trackId); + setTracks(singleItemTrackIdList); +} + +void WTrackMenu::setupActions() { + addAction(m_pFileBrowserAct); +} + +void WTrackMenu::slotOpenInFileBrowser() { + TrackPointerList trackPointerList = getTrackPointerList(); + QStringList locations; + for (const TrackPointer& trackPointer : trackPointerList) { + locations << trackPointer->getLocation(); + } + mixxx::DesktopHelper::openInFileBrowser(locations); +} + +TrackPointerList WTrackMenu::getTrackPointerList() { + return m_pTrackPointerList; +} + +void WTrackMenu::trackIdsToTrackPointers() { + m_pTrackPointerList.clear(); + for (const auto trackId : m_pTrackIdList) { + TrackPointer trackPointer = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); + m_pTrackPointerList.push_back(trackPointer); + } +} diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h new file mode 100644 index 000000000000..de433062123e --- /dev/null +++ b/src/widget/wtrackmenu.h @@ -0,0 +1,114 @@ +#ifndef WTRACKMENU_H +#define WTRACKMENU_H + +#include +#include +#include +#include + + +#include "widget/wcoverartmenu.h" +#include "widget/wcolorpickeraction.h" + +typedef QList TrackIdList; +typedef QList TrackPointerList; + +class TrackCollectionManager; +class ExternalTrackCollection; + +class WTrackMenu : public QMenu { + Q_OBJECT + public: + WTrackMenu(QWidget *parent, TrackCollectionManager* pTrackCollectionManager); + ~WTrackMenu() override; + + void setTrack(TrackId track); + void setTracks(TrackIdList trackList); + + private slots: + void slotOpenInFileBrowser(); + +private: + void createActions(); + void setupActions(); + void trackIdsToTrackPointers(); + TrackPointerList getTrackPointerList(); + + // The selected tracks for which the context menu is created + TrackIdList m_pTrackIdList; + + // Context menu machinery + QMenu *m_pLoadToMenu; + QMenu *m_pDeckMenu; + QMenu *m_pSamplerMenu; + + QMenu *m_pPlaylistMenu; + QMenu *m_pCrateMenu; + QMenu *m_pMetadataMenu; + QMenu *m_pMetadataUpdateExternalCollectionsMenu; + QMenu *m_pClearMetadataMenu; + QMenu *m_pBPMMenu; + QMenu *m_pColorMenu; + + + WCoverArtMenu* m_pCoverMenu; + + // Reload Track Metadata Action: + QAction *m_pImportMetadataFromFileAct; + QAction *m_pImportMetadataFromMusicBrainzAct; + + // Save Track Metadata Action: + QAction *m_pExportMetadataAct; + + // Load Track to PreviewDeck + QAction* m_pAddToPreviewDeck; + + // Send to Auto-DJ Action + QAction *m_pAutoDJBottomAct; + QAction *m_pAutoDJTopAct; + QAction *m_pAutoDJReplaceAct; + + // Remove from table + QAction *m_pRemoveAct; + QAction *m_pRemovePlaylistAct; + QAction *m_pRemoveCrateAct; + QAction *m_pHideAct; + QAction *m_pUnhideAct; + QAction *m_pPurgeAct; + + // Show track-editor action + QAction *m_pPropertiesAct; + QAction *m_pFileBrowserAct; + + // BPM feature + QAction *m_pBpmLockAction; + QAction *m_pBpmUnlockAction; + QAction *m_pBpmDoubleAction; + QAction *m_pBpmHalveAction; + QAction *m_pBpmTwoThirdsAction; + QAction *m_pBpmThreeFourthsAction; + QAction *m_pBpmFourThirdsAction; + QAction *m_pBpmThreeHalvesAction; + + // Track color + WColorPickerAction *m_pColorPickerAction; + + // Clear track metadata actions + QAction* m_pClearBeatsAction; + QAction* m_pClearPlayCountAction; + QAction* m_pClearMainCueAction; + QAction* m_pClearHotCuesAction; + QAction* m_pClearIntroCueAction; + QAction* m_pClearOutroCueAction; + QAction* m_pClearLoopAction; + QAction* m_pClearWaveformAction; + QAction* m_pClearKeyAction; + QAction* m_pClearReplayGainAction; + QAction* m_pClearAllMetadataAction; + + TrackCollectionManager* m_pTrackCollectionManager; + TrackPointerList m_pTrackPointerList; +}; + + +#endif // WTRACKMENU_H diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index b3e581d60660..e3efe93bdcaa 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -68,51 +68,7 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_pNumPreviewDecks = new ControlProxy( "[Master]", "num_preview_decks", this); - m_pMenu = new QMenu(this); - - m_pLoadToMenu = new QMenu(this); - m_pLoadToMenu->setTitle(tr("Load to")); - m_pDeckMenu = new QMenu(this); - m_pDeckMenu->setTitle(tr("Deck")); - m_pSamplerMenu = new QMenu(this); - m_pSamplerMenu->setTitle(tr("Sampler")); - - m_pPlaylistMenu = new QMenu(this); - m_pPlaylistMenu->setTitle(tr("Add to Playlist")); - connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulatePlaylistMenu())); - m_pCrateMenu = new QMenu(this); - m_pCrateMenu->setTitle(tr("Crates")); - connect(m_pCrateMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulateCrateMenu())); - - m_pMetadataMenu = new QMenu(this); - m_pMetadataMenu->setTitle(tr("Metadata")); - - m_pMetadataUpdateExternalCollectionsMenu = new QMenu(this); - m_pMetadataUpdateExternalCollectionsMenu->setTitle(tr("Update external collections")); - - m_pBPMMenu = new QMenu(this); - m_pBPMMenu->setTitle(tr("Adjust BPM")); - - m_pColorMenu = new QMenu(this); - m_pColorMenu->setTitle(tr("Select Color")); - - m_pClearMetadataMenu = new QMenu(this); - //: Reset metadata in right click track context menu in library - m_pClearMetadataMenu->setTitle(tr("Reset")); - - m_pCoverMenu = new WCoverArtMenu(this); - m_pCoverMenu->setTitle(tr("Cover Art")); - - connect(m_pCoverMenu, SIGNAL(coverInfoSelected(const CoverInfoRelative&)), - this, SLOT(slotCoverInfoSelected(const CoverInfoRelative&))); - connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), - this, SLOT(slotReloadCoverArt())); - - // Create all the context m_pMenu->actions (stuff that shows up when you - // right-click) - createActions(); + m_pMenu = new WTrackMenu(this, m_pTrackCollectionManager); // Connect slots and signals to make the world go 'round. connect(this, SIGNAL(doubleClicked(const QModelIndex &)), @@ -145,50 +101,50 @@ WTrackTableView::~WTrackTableView() { pHeader->saveHeaderState(); } - delete m_pImportMetadataFromFileAct; - delete m_pImportMetadataFromMusicBrainzAct; - delete m_pExportMetadataAct; - delete m_pAddToPreviewDeck; - delete m_pAutoDJBottomAct; - delete m_pAutoDJTopAct; - delete m_pAutoDJReplaceAct; - delete m_pRemoveAct; - delete m_pRemovePlaylistAct; - delete m_pRemoveCrateAct; - delete m_pHideAct; - delete m_pUnhideAct; - delete m_pPropertiesAct; +// delete m_pImportMetadataFromFileAct; +// delete m_pImportMetadataFromMusicBrainzAct; +// delete m_pExportMetadataAct; +// delete m_pAddToPreviewDeck; +// delete m_pAutoDJBottomAct; +// delete m_pAutoDJTopAct; +// delete m_pAutoDJReplaceAct; +// delete m_pRemoveAct; +// delete m_pRemovePlaylistAct; +// delete m_pRemoveCrateAct; +// delete m_pHideAct; +// delete m_pUnhideAct; +// delete m_pPropertiesAct; delete m_pMenu; - delete m_pLoadToMenu; - delete m_pDeckMenu; - delete m_pSamplerMenu; - delete m_pPlaylistMenu; - delete m_pCrateMenu; - delete m_pMetadataMenu; - delete m_pClearMetadataMenu; - delete m_pCoverMenu; - delete m_pBpmLockAction; - delete m_pBpmUnlockAction; - delete m_pBpmDoubleAction; - delete m_pBpmHalveAction; - delete m_pBpmTwoThirdsAction; - delete m_pBpmThreeFourthsAction; - delete m_pBpmFourThirdsAction; - delete m_pBpmThreeHalvesAction; - delete m_pBPMMenu; - delete m_pColorMenu; - delete m_pClearBeatsAction; - delete m_pClearPlayCountAction; - delete m_pClearMainCueAction; - delete m_pClearHotCuesAction; - delete m_pClearIntroCueAction; - delete m_pClearOutroCueAction; - delete m_pClearLoopAction; - delete m_pClearReplayGainAction; - delete m_pClearWaveformAction; - delete m_pClearAllMetadataAction; - delete m_pPurgeAct; - delete m_pFileBrowserAct; +// delete m_pLoadToMenu; +// delete m_pDeckMenu; +// delete m_pSamplerMenu; +// delete m_pPlaylistMenu; +// delete m_pCrateMenu; +// delete m_pMetadataMenu; +// delete m_pClearMetadataMenu; +// delete m_pCoverMenu; +// delete m_pBpmLockAction; +// delete m_pBpmUnlockAction; +// delete m_pBpmDoubleAction; +// delete m_pBpmHalveAction; +// delete m_pBpmTwoThirdsAction; +// delete m_pBpmThreeFourthsAction; +// delete m_pBpmFourThirdsAction; +// delete m_pBpmThreeHalvesAction; +// delete m_pBPMMenu; +// delete m_pColorMenu; +// delete m_pClearBeatsAction; +// delete m_pClearPlayCountAction; +// delete m_pClearMainCueAction; +// delete m_pClearHotCuesAction; +// delete m_pClearIntroCueAction; +// delete m_pClearOutroCueAction; +// delete m_pClearLoopAction; +// delete m_pClearReplayGainAction; +// delete m_pClearWaveformAction; +// delete m_pClearAllMetadataAction; +// delete m_pPurgeAct; +// delete m_pFileBrowserAct; } void WTrackTableView::enableCachedOnly() { @@ -427,58 +383,9 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { } void WTrackTableView::createActions() { - DEBUG_ASSERT(m_pMenu); - DEBUG_ASSERT(m_pSamplerMenu); - - m_pRemoveAct = new QAction(tr("Remove"), this); - connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); - - m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); - connect(m_pRemovePlaylistAct, SIGNAL(triggered()), this, SLOT(slotRemove())); - - m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); - connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); - - m_pHideAct = new QAction(tr("Hide from Library"), this); - connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); - - m_pUnhideAct = new QAction(tr("Unhide from Library"), this); - connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); - - m_pPurgeAct = new QAction(tr("Purge from Library"), this); - connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); - - m_pPropertiesAct = new QAction(tr("Properties"), this); - connect(m_pPropertiesAct, SIGNAL(triggered()), - this, SLOT(slotShowTrackInfo())); - - m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); - connect(m_pFileBrowserAct, SIGNAL(triggered()), - this, SLOT(slotOpenInFileBrowser())); - - m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); - connect(m_pAutoDJBottomAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJBottom())); - - m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); - connect(m_pAutoDJTopAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJTop())); + /* - m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); - connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJReplace())); - m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); - connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), - this, SLOT(slotImportTrackMetadataFromFileTags())); - - m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); - connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), - this, SLOT(slotShowDlgTagFetcher())); - - m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); - connect(m_pExportMetadataAct, SIGNAL(triggered()), - this, SLOT(slotExportTrackMetadataIntoFileTags())); for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { UpdateExternalTrackCollection updateInExternalTrackCollection; @@ -580,7 +487,7 @@ void WTrackTableView::createActions() { connect(m_pColorPickerAction, &WColorPickerAction::colorPicked, this, - &WTrackTableView::slotColorPicked); + &WTrackTableView::slotColorPicked);*/ } // slot @@ -655,24 +562,6 @@ void WTrackTableView::slotPurge() { } } -void WTrackTableView::slotOpenInFileBrowser() { - TrackModel* trackModel = getTrackModel(); - if (!trackModel) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - - QStringList locations; - for (const QModelIndex& index : indices) { - if (!index.isValid()) { - continue; - } - locations << trackModel->getTrackLocation(index); - } - mixxx::DesktopHelper::openInFileBrowser(locations); -} - void WTrackTableView::slotHide() { QModelIndexList indices = selectionModel()->selectedRows(); if (indices.size() > 0) { @@ -828,6 +717,7 @@ void WTrackTableView::slotShowDlgTagFetcher() { } void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { + /* QModelIndexList indices = selectionModel()->selectedRows(); // Gray out some stuff if multiple songs were selected. @@ -1140,6 +1030,11 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pPropertiesAct->setEnabled(oneSongSelected); m_pMenu->addAction(m_pPropertiesAct); } +*/ + + // Update track ids in context menu + TrackIdList trackIds = getSelectedTrackIds(); + m_pMenu->setTracks(trackIds); //Create the right-click menu m_pMenu->popup(event->globalPos()); @@ -1646,7 +1541,7 @@ void WTrackTableView::slotClearPlayCount() { } } } - +/* void WTrackTableView::slotPopulatePlaylistMenu() { // The user may open the Playlist submenu, move their cursor away, then // return to the Playlist submenu before exiting the track context menu. @@ -1685,6 +1580,7 @@ void WTrackTableView::slotPopulatePlaylistMenu() { this, [this] { addSelectionToPlaylist(-1); }); m_bPlaylistMenuLoaded = true; } +*/ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { const QList trackIds = getSelectedTrackIds(); @@ -1736,7 +1632,7 @@ void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { m_pTrackCollectionManager->unhideTracks(trackIds); playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); } - +/* void WTrackTableView::slotPopulateCrateMenu() { // The user may open the Crate submenu, move their cursor away, then // return to the Crate submenu before exiting the track context menu. @@ -1797,6 +1693,7 @@ void WTrackTableView::slotPopulateCrateMenu() { connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); m_bCrateMenuLoaded = true; } + */ void WTrackTableView::updateSelectionCrates(QWidget* pWidget) { auto pCheckBox = qobject_cast(pWidget); diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index fd6beae9d6ae..ce312d98e5f3 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -12,6 +12,7 @@ #include "util/duration.h" #include "widget/wcolorpickeraction.h" #include "widget/wlibrarytableview.h" +#include "widget/wtrackmenu.h" class ControlProxy; class DlgTagFetcher; @@ -58,7 +59,6 @@ class WTrackTableView : public WLibraryTableView { private slots: void slotRemove(); void slotHide(); - void slotOpenInFileBrowser(); void slotShowTrackInfo(); void slotShowDlgTagFetcher(); void slotNextTrackInfo(); @@ -69,10 +69,10 @@ class WTrackTableView : public WLibraryTableView { void slotImportTrackMetadataFromFileTags(); void slotExportTrackMetadataIntoFileTags(); void slotUpdateExternalTrackCollection(ExternalTrackCollection*); - void slotPopulatePlaylistMenu(); + //void slotPopulatePlaylistMenu(); void addSelectionToPlaylist(int iPlaylistId); void updateSelectionCrates(QWidget* qc); - void slotPopulateCrateMenu(); + //void slotPopulateCrateMenu(); void addSelectionToNewCrate(); void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection, Qt::SortOrder sortOrder); @@ -142,76 +142,78 @@ class WTrackTableView : public WLibraryTableView { ControlProxy* m_pNumDecks; ControlProxy* m_pNumPreviewDecks; - // Context menu machinery - QMenu *m_pMenu; - - QMenu *m_pLoadToMenu; - QMenu *m_pDeckMenu; - QMenu *m_pSamplerMenu; - - QMenu *m_pPlaylistMenu; - QMenu *m_pCrateMenu; - QMenu *m_pMetadataMenu; - QMenu *m_pMetadataUpdateExternalCollectionsMenu; - QMenu *m_pClearMetadataMenu; - QMenu *m_pBPMMenu; - QMenu *m_pColorMenu; - - - WCoverArtMenu* m_pCoverMenu; - - // Reload Track Metadata Action: - QAction *m_pImportMetadataFromFileAct; - QAction *m_pImportMetadataFromMusicBrainzAct; - - // Save Track Metadata Action: - QAction *m_pExportMetadataAct; - - // Load Track to PreviewDeck - QAction* m_pAddToPreviewDeck; - - // Send to Auto-DJ Action - QAction *m_pAutoDJBottomAct; - QAction *m_pAutoDJTopAct; - QAction *m_pAutoDJReplaceAct; - - // Remove from table - QAction *m_pRemoveAct; - QAction *m_pRemovePlaylistAct; - QAction *m_pRemoveCrateAct; - QAction *m_pHideAct; - QAction *m_pUnhideAct; - QAction *m_pPurgeAct; - - // Show track-editor action - QAction *m_pPropertiesAct; - QAction *m_pFileBrowserAct; - - // BPM feature - QAction *m_pBpmLockAction; - QAction *m_pBpmUnlockAction; - QAction *m_pBpmDoubleAction; - QAction *m_pBpmHalveAction; - QAction *m_pBpmTwoThirdsAction; - QAction *m_pBpmThreeFourthsAction; - QAction *m_pBpmFourThirdsAction; - QAction *m_pBpmThreeHalvesAction; - - // Track color - WColorPickerAction *m_pColorPickerAction; - - // Clear track metadata actions - QAction* m_pClearBeatsAction; - QAction* m_pClearPlayCountAction; - QAction* m_pClearMainCueAction; - QAction* m_pClearHotCuesAction; - QAction* m_pClearIntroCueAction; - QAction* m_pClearOutroCueAction; - QAction* m_pClearLoopAction; - QAction* m_pClearWaveformAction; - QAction* m_pClearKeyAction; - QAction* m_pClearReplayGainAction; - QAction* m_pClearAllMetadataAction; + // Context menu container + WTrackMenu *m_pMenu; +// // Context menu machinery +// WTrackMenu *m_pMenu; +// +// QMenu *m_pLoadToMenu; +// QMenu *m_pDeckMenu; +// QMenu *m_pSamplerMenu; +// +// QMenu *m_pPlaylistMenu; +// QMenu *m_pCrateMenu; +// QMenu *m_pMetadataMenu; +// QMenu *m_pMetadataUpdateExternalCollectionsMenu; +// QMenu *m_pClearMetadataMenu; +// QMenu *m_pBPMMenu; +// QMenu *m_pColorMenu; +// +// +// WCoverArtMenu* m_pCoverMenu; +// +// // Reload Track Metadata Action: +// QAction *m_pImportMetadataFromFileAct; +// QAction *m_pImportMetadataFromMusicBrainzAct; +// +// // Save Track Metadata Action: +// QAction *m_pExportMetadataAct; +// +// // Load Track to PreviewDeck +// QAction* m_pAddToPreviewDeck; +// +// // Send to Auto-DJ Action +// QAction *m_pAutoDJBottomAct; +// QAction *m_pAutoDJTopAct; +// QAction *m_pAutoDJReplaceAct; +// +// // Remove from table +// QAction *m_pRemoveAct; +// QAction *m_pRemovePlaylistAct; +// QAction *m_pRemoveCrateAct; +// QAction *m_pHideAct; +// QAction *m_pUnhideAct; +// QAction *m_pPurgeAct; +// +// // Show track-editor action +// QAction *m_pPropertiesAct; +// QAction *m_pFileBrowserAct; +// +// // BPM feature +// QAction *m_pBpmLockAction; +// QAction *m_pBpmUnlockAction; +// QAction *m_pBpmDoubleAction; +// QAction *m_pBpmHalveAction; +// QAction *m_pBpmTwoThirdsAction; +// QAction *m_pBpmThreeFourthsAction; +// QAction *m_pBpmFourThirdsAction; +// QAction *m_pBpmThreeHalvesAction; +// +// // Track color +// WColorPickerAction *m_pColorPickerAction; +// +// // Clear track metadata actions +// QAction* m_pClearBeatsAction; +// QAction* m_pClearPlayCountAction; +// QAction* m_pClearMainCueAction; +// QAction* m_pClearHotCuesAction; +// QAction* m_pClearIntroCueAction; +// QAction* m_pClearOutroCueAction; +// QAction* m_pClearLoopAction; +// QAction* m_pClearWaveformAction; +// QAction* m_pClearKeyAction; +// QAction* m_pClearReplayGainAction; +// QAction* m_pClearAllMetadataAction; struct UpdateExternalTrackCollection { QPointer externalTrackCollection; From 1e22f8c36cc580a316f7a73a1b99f91568e2a9fa Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 1 Apr 2020 13:10:26 +0200 Subject: [PATCH 149/393] Cancel all tasks in TagFetcher after last signal --- src/musicbrainz/tagfetcher.cpp | 124 +++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp index a79dd6a09396..f447930ffed4 100644 --- a/src/musicbrainz/tagfetcher.cpp +++ b/src/musicbrainz/tagfetcher.cpp @@ -20,6 +20,7 @@ TagFetcher::TagFetcher(QObject* parent) void TagFetcher::startFetch( TrackPointer pTrack) { + DEBUG_ASSERT(thread() == QThread::currentThread()); cancel(); m_pTrack = pTrack; @@ -29,6 +30,7 @@ void TagFetcher::startFetch( return ChromaPrinter().getFingerprint(pTrack); }); m_fingerprintWatcher.setFuture(fingerprintTask); + DEBUG_ASSERT(!m_pAcoustIdTask); connect( &m_fingerprintWatcher, &QFutureWatcher::finished, @@ -37,22 +39,30 @@ void TagFetcher::startFetch( } void TagFetcher::cancel() { + DEBUG_ASSERT(thread() == QThread::currentThread()); m_pTrack.reset(); + m_fingerprintWatcher.disconnect(this); m_fingerprintWatcher.cancel(); if (m_pAcoustIdTask) { - m_pAcoustIdTask->invokeAbort(); + m_pAcoustIdTask->disconnect(this); + m_pAcoustIdTask->deleteLater(); + m_pAcoustIdTask = nullptr; } if (m_pMusicBrainzTask) { - m_pMusicBrainzTask->invokeAbort(); + m_pMusicBrainzTask->disconnect(this); + m_pMusicBrainzTask->deleteLater(); + m_pMusicBrainzTask = nullptr; } } void TagFetcher::slotFingerprintReady() { + DEBUG_ASSERT(thread() == QThread::currentThread()); if (!m_pTrack || !m_fingerprintWatcher.isFinished()) { return; } + DEBUG_ASSERT(m_fingerprintWatcher.isFinished()); const QString fingerprint = m_fingerprintWatcher.result(); if (fingerprint.isEmpty()) { emit resultAvailable( @@ -90,19 +100,15 @@ void TagFetcher::slotFingerprintReady() { void TagFetcher::slotAcoustIdTaskSucceeded( QList recordingIds) { - VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { - return; - } - m_pAcoustIdTask->deleteAfterFinished(); - m_pAcoustIdTask = nullptr; - - if (!m_pTrack) { - return; - } + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pAcoustIdTask.get() == + qobject_cast(sender())); if (recordingIds.isEmpty()) { + auto pTrack = std::move(m_pTrack); + cancel(); emit resultAvailable( - m_pTrack, + std::move(pTrack), QList()); return; } @@ -135,11 +141,12 @@ void TagFetcher::slotAcoustIdTaskSucceeded( void TagFetcher::slotAcoustIdTaskFailed( mixxx::network::JsonWebResponse response) { - VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { - return; - } - m_pAcoustIdTask->deleteAfterFinished(); - m_pAcoustIdTask = nullptr; + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pAcoustIdTask.get() == + qobject_cast(sender())); + + cancel(); + emit networkError( response.statusCode, "AcoustID", @@ -148,11 +155,16 @@ void TagFetcher::slotAcoustIdTaskFailed( } void TagFetcher::slotAcoustIdTaskAborted() { - VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { - return; - } - m_pAcoustIdTask->deleteAfterFinished(); - m_pAcoustIdTask = nullptr; + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pAcoustIdTask.get() == + qobject_cast(sender())); + + auto pTrack = std::move(m_pTrack); + cancel(); + + emit resultAvailable( + std::move(pTrack), + QList{}); } void TagFetcher::slotAcoustIdTaskNetworkError( @@ -162,11 +174,12 @@ void TagFetcher::slotAcoustIdTaskNetworkError( QByteArray errorContent) { Q_UNUSED(requestUrl); Q_UNUSED(errorContent); - VERIFY_OR_DEBUG_ASSERT(m_pAcoustIdTask) { - return; - } - m_pAcoustIdTask->deleteAfterFinished(); - m_pAcoustIdTask = nullptr; + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pAcoustIdTask.get() == + qobject_cast(sender())); + + cancel(); + emit networkError( mixxx::network::kHttpStatusCodeInvalid, "AcoustID", @@ -175,11 +188,16 @@ void TagFetcher::slotAcoustIdTaskNetworkError( } void TagFetcher::slotMusicBrainzTaskAborted() { - VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { - return; - } - m_pMusicBrainzTask->deleteAfterFinished(); - m_pMusicBrainzTask = nullptr; + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pMusicBrainzTask.get() == + qobject_cast(sender())); + + auto pTrack = std::move(m_pTrack); + cancel(); + + emit resultAvailable( + std::move(pTrack), + QList{}); } void TagFetcher::slotMusicBrainzTaskNetworkError( @@ -189,11 +207,12 @@ void TagFetcher::slotMusicBrainzTaskNetworkError( QByteArray errorContent) { Q_UNUSED(requestUrl); Q_UNUSED(errorContent); - VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { - return; - } - m_pMusicBrainzTask->deleteAfterFinished(); - m_pMusicBrainzTask = nullptr; + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pMusicBrainzTask.get() == + qobject_cast(sender())); + + cancel(); + emit networkError( mixxx::network::kHttpStatusCodeInvalid, "MusicBrainz", @@ -205,11 +224,12 @@ void TagFetcher::slotMusicBrainzTaskFailed( mixxx::network::WebResponse response, int errorCode, QString errorMessage) { - VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { - return; - } - m_pMusicBrainzTask->deleteAfterFinished(); - m_pMusicBrainzTask = nullptr; + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pMusicBrainzTask.get() == + qobject_cast(sender())); + + cancel(); + emit networkError( response.statusCode, "MusicBrainz", @@ -219,18 +239,14 @@ void TagFetcher::slotMusicBrainzTaskFailed( void TagFetcher::slotMusicBrainzTaskSucceeded( QList guessedTrackReleases) { - auto pOriginalTrack = std::move(m_pTrack); - DEBUG_ASSERT(!m_pTrack); - VERIFY_OR_DEBUG_ASSERT(m_pMusicBrainzTask) { - return; - } - m_pMusicBrainzTask->deleteAfterFinished(); - m_pMusicBrainzTask = nullptr; - if (!pOriginalTrack) { - // aborted - return; - } + DEBUG_ASSERT(thread() == QThread::currentThread()); + DEBUG_ASSERT(m_pMusicBrainzTask.get() == + qobject_cast(sender())); + + auto pTrack = std::move(m_pTrack); + cancel(); + emit resultAvailable( - pOriginalTrack, + std::move(pTrack), std::move(guessedTrackReleases)); } From b8bc7ba817b0811cc5e992bf2cd5069da8cb7a36 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 1 Apr 2020 20:39:12 +0200 Subject: [PATCH 150/393] Delete obsolete method deleteAfterFinished() --- src/network/jsonwebtask.cpp | 4 ++-- src/network/webtask.cpp | 8 -------- src/network/webtask.h | 4 ---- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp index 2d4b76309f36..8e8802c1ea48 100644 --- a/src/network/jsonwebtask.cpp +++ b/src/network/jsonwebtask.cpp @@ -121,7 +121,7 @@ void JsonWebTask::onFinished( << response.replyUrl << response.statusCode << response.content; - deleteAfterFinished(); + deleteLater(); } void JsonWebTask::onFinishedCustom( @@ -131,7 +131,7 @@ void JsonWebTask::onFinishedCustom( << response.replyUrl << response.statusCode << response.content; - deleteAfterFinished(); + deleteLater(); } QNetworkReply* JsonWebTask::sendNetworkRequest( diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp index c9998b537646..fc7f1b3f356d 100644 --- a/src/network/webtask.cpp +++ b/src/network/webtask.cpp @@ -165,14 +165,6 @@ void WebTask::invokeAbort() { ); } -void WebTask::deleteAfterFinished() { - // Might be called from any thread so we must not - // access any member variables! - // Do not disconnect any connections, because otherwise - // the destroyed() signal is not received! - deleteLater(); -} - void WebTask::slotStart(int timeoutMillis) { DEBUG_ASSERT(thread() == QThread::currentThread()); DEBUG_ASSERT(m_status != Status::Pending); diff --git a/src/network/webtask.h b/src/network/webtask.h index 09ce1413796b..3fc9beb91dbf 100644 --- a/src/network/webtask.h +++ b/src/network/webtask.h @@ -116,10 +116,6 @@ class WebTask : public QObject { // Cancel a pending request from the event loop thread. QUrl abort(); - // Disconnect from all signals after receiving a reply - // and mark the task for deletion. - void deleteAfterFinished(); - public slots: void slotStart( int timeoutMillis); From 7f065a1a1b6f47c5a95fdf292528feeaf7c074ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 2 Apr 2020 00:30:03 +0200 Subject: [PATCH 151/393] Added Ketan Lambat to the contributor list in the about box. Thank you very much. --- src/dialog/dlgabout.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dialog/dlgabout.cpp b/src/dialog/dlgabout.cpp index ec1a7bb851a3..f30766d37e58 100644 --- a/src/dialog/dlgabout.cpp +++ b/src/dialog/dlgabout.cpp @@ -98,7 +98,8 @@ DlgAbout::DlgAbout(QWidget* parent) : QDialog(parent), Ui::DlgAboutDlg() { << "YunQiang Su" << "Sebastian Hasler" << "Philip Gottschling" - << "Cristiano Lacerda"; + << "Cristiano Lacerda" + << "Ketan Lambat"; QStringList specialThanks; specialThanks From fb29a1f46ad0ad1844286b4ae2c5287f28246066 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 2 Apr 2020 01:43:51 +0200 Subject: [PATCH 152/393] Deere: suppress blue hotcue button border --- res/skins/Deere/style.qss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/skins/Deere/style.qss b/res/skins/Deere/style.qss index 505d62f51d7c..d4dff089bf01 100644 --- a/res/skins/Deere/style.qss +++ b/res/skins/Deere/style.qss @@ -1544,6 +1544,10 @@ WPushButton[value="2"]:hover { border: 1px solid #0080BE; } +#HotcueButton { + border: none; +} + #HotcueButton[light="true"] { color: #1f1e1e; } From 746a565239cdb84a9c104b34a87ac6d6b3682a5c Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Thu, 2 Apr 2020 16:01:59 +0530 Subject: [PATCH 153/393] widget/wtrackmenu: move remaining actions from wtracktableview --- src/widget/wtrackmenu.cpp | 1470 ++++++++++++++++++++++++++++++-- src/widget/wtrackmenu.h | 120 ++- src/widget/wtracktableview.cpp | 1234 +-------------------------- src/widget/wtracktableview.h | 148 +--- 4 files changed, 1544 insertions(+), 1428 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index f45fd07eb7c6..e8e15ac56369 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -1,17 +1,65 @@ #include "widget/wtrackmenu.h" +#include +#include +#include +#include +#include +#include + + #include #include -#include -#include -#include "widget/wlibrarytableview.h" + + +#include "control/controlobject.h" +#include "control/controlproxy.h" +#include "library/coverartutils.h" +#include "library/crate/cratefeaturehelper.h" #include "library/dao/trackdao.h" +#include "library/dao/trackschema.h" +#include "library/dlgtagfetcher.h" +#include "library/dlgtrackinfo.h" +#include "library/dlgtrackmetadataexport.h" +#include "library/externaltrackcollection.h" +#include "library/librarytablemodel.h" #include "library/trackcollection.h" +#include "library/trackmodel.h" +#include "mixer/playermanager.h" +#include "preferences/colorpalettesettings.h" +#include "sources/soundsourceproxy.h" +#include "util/desktophelper.h" +#include "widget/wlibrarytableview.h" +#include "util/desktophelper.h" +#include "util/parented_ptr.h" +#include "waveform/guitick.h" +#include "widget/wcolorpickeraction.h" +#include "widget/wcoverartmenu.h" +#include "widget/wskincolor.h" +#include "widget/wwidget.h" -WTrackMenu::WTrackMenu(QWidget *parent, TrackCollectionManager* pTrackCollectionManager) + + +WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager *pTrackCollectionManager) : QMenu(parent), - m_pTrackCollectionManager(pTrackCollectionManager){ + m_pConfig(std::move(pConfig)), + m_pTrackCollectionManager(pTrackCollectionManager), + m_bPlaylistMenuLoaded(false), + m_bCrateMenuLoaded(false), + m_iCoverSourceColumn(-1), + m_iCoverTypeColumn(-1), + m_iCoverLocationColumn(-1), + m_iCoverHashColumn(-1), + m_iCoverColumn(-1) { + std::cout << "Constructor started (stdout)\n"; + m_pNumSamplers = new ControlProxy( + "[Master]", "num_samplers", this); + m_pNumDecks = new ControlProxy( + "[Master]", "num_decks", this); + m_pNumPreviewDecks = new ControlProxy( + "[Master]", "num_preview_decks", this); + m_pLoadToMenu = new QMenu(this); m_pLoadToMenu->setTitle(tr("Load to")); m_pDeckMenu = new QMenu(this); @@ -52,9 +100,11 @@ WTrackMenu::WTrackMenu(QWidget *parent, TrackCollectionManager* pTrackCollection connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), this, SLOT(slotReloadCoverArt())); createActions(); + std::cout << "constructor end\n"; } WTrackMenu::~WTrackMenu() { + std::cout << "Destructor start\n"; delete m_pImportMetadataFromFileAct; delete m_pImportMetadataFromMusicBrainzAct; delete m_pExportMetadataAct; @@ -99,89 +149,545 @@ WTrackMenu::~WTrackMenu() { delete m_pClearAllMetadataAction; delete m_pPurgeAct; delete m_pFileBrowserAct; + delete m_pTrackCollectionManager; + delete m_pTrackModel; + std::cout << "Destructor end\n"; } - void WTrackMenu::createActions() { -// DEBUG_ASSERT(this); -// DEBUG_ASSERT(m_pSamplerMenu); -// -// m_pRemoveAct = new QAction(tr("Remove"), this); -// connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); -// -// m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); -// connect(m_pRemovePlaylistAct, SIGNAL(triggered()), this, SLOT(slotRemove())); -// -// m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); -// connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); -// -// m_pHideAct = new QAction(tr("Hide from Library"), this); -// connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); -// -// m_pUnhideAct = new QAction(tr("Unhide from Library"), this); -// connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); -// -// m_pPurgeAct = new QAction(tr("Purge from Library"), this); -// connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); -// -// m_pPropertiesAct = new QAction(tr("Properties"), this); -// connect(m_pPropertiesAct, SIGNAL(triggered()), -// this, SLOT(slotShowTrackInfo())); -// + std::cout << "create actions\n"; + DEBUG_ASSERT(this); + DEBUG_ASSERT(m_pSamplerMenu); + + m_pRemoveAct = new QAction(tr("Remove"), this); + connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + + m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); + connect(m_pRemovePlaylistAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + + m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); + connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + + m_pHideAct = new QAction(tr("Hide from Library"), this); + connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); + + m_pUnhideAct = new QAction(tr("Unhide from Library"), this); + connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); + + m_pPurgeAct = new QAction(tr("Purge from Library"), this); + connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); + + m_pPropertiesAct = new QAction(tr("Properties"), this); + connect(m_pPropertiesAct, SIGNAL(triggered()), + this, SLOT(slotShowTrackInfo())); + m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); connect(m_pFileBrowserAct, SIGNAL(triggered()), this, SLOT(slotOpenInFileBrowser())); -// -// m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); -// connect(m_pAutoDJBottomAct, SIGNAL(triggered()), -// this, SLOT(slotAddToAutoDJBottom())); -// -// m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); -// connect(m_pAutoDJTopAct, SIGNAL(triggered()), -// this, SLOT(slotAddToAutoDJTop())); -// -// m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); -// connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), -// this, SLOT(slotAddToAutoDJReplace())); -// -// m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); -// connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), -// this, SLOT(slotImportTrackMetadataFromFileTags())); -// -// m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); -// connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), -// this, SLOT(slotShowDlgTagFetcher())); -// -// m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); -// connect(m_pExportMetadataAct, SIGNAL(triggered()), -// this, SLOT(slotExportTrackMetadataIntoFileTags())); -} - -void WTrackMenu::setTracks(TrackIdList trackIdList) { + + m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); + connect(m_pAutoDJBottomAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJBottom())); + + m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); + connect(m_pAutoDJTopAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJTop())); + + m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); + connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJReplace())); + + m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); + connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), + this, SLOT(slotImportTrackMetadataFromFileTags())); + + m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); + connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), + this, SLOT(slotShowDlgTagFetcher())); + + m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); + connect(m_pExportMetadataAct, SIGNAL(triggered()), + this, SLOT(slotExportTrackMetadataIntoFileTags())); + + + for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { + UpdateExternalTrackCollection updateInExternalTrackCollection; + updateInExternalTrackCollection.externalTrackCollection = externalTrackCollection; + updateInExternalTrackCollection.action = new QAction(externalTrackCollection->name(), this); + updateInExternalTrackCollection.action->setToolTip(externalTrackCollection->description()); + m_updateInExternalTrackCollections += updateInExternalTrackCollection; + auto externalTrackCollectionPtr = updateInExternalTrackCollection.externalTrackCollection; + connect(updateInExternalTrackCollection.action, &QAction::triggered, + this, [this, externalTrackCollectionPtr] { + slotUpdateExternalTrackCollection(externalTrackCollectionPtr); + }); + } + + m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); + // currently there is only one preview deck so just map it here. + QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); +// connect(m_pAddToPreviewDeck, &QAction::triggered, +// this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); + + + // Clear metadata actions + m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); + connect(m_pClearBeatsAction, SIGNAL(triggered()), + this, SLOT(slotClearBeats())); + + m_pClearPlayCountAction = new QAction(tr("Play Count"), this); + connect(m_pClearPlayCountAction, SIGNAL(triggered()), + this, SLOT(slotClearPlayCount())); + + m_pClearMainCueAction = new QAction(tr("Cue Point"), this); + connect(m_pClearMainCueAction, SIGNAL(triggered()), + this, SLOT(slotClearMainCue())); + + m_pClearHotCuesAction = new QAction(tr("Hotcues"), this); + connect(m_pClearHotCuesAction, SIGNAL(triggered()), + this, SLOT(slotClearHotCues())); + + m_pClearIntroCueAction = new QAction(tr("Intro"), this); + connect(m_pClearIntroCueAction, SIGNAL(triggered()), + this, SLOT(slotClearIntroCue())); + + m_pClearOutroCueAction = new QAction(tr("Outro"), this); + connect(m_pClearOutroCueAction, SIGNAL(triggered()), + this, SLOT(slotClearOutroCue())); + + m_pClearLoopAction = new QAction(tr("Loop"), this); + connect(m_pClearLoopAction, SIGNAL(triggered()), + this, SLOT(slotClearLoop())); + + m_pClearKeyAction = new QAction(tr("Key"), this); + connect(m_pClearKeyAction, SIGNAL(triggered()), + this, SLOT(slotClearKey())); + + m_pClearReplayGainAction = new QAction(tr("ReplayGain"), this); + connect(m_pClearReplayGainAction, SIGNAL(triggered()), + this, SLOT(slotClearReplayGain())); + + m_pClearWaveformAction = new QAction(tr("Waveform"), this); + connect(m_pClearWaveformAction, SIGNAL(triggered()), + this, SLOT(slotClearWaveform())); + + m_pClearAllMetadataAction = new QAction(tr("All"), this); + connect(m_pClearAllMetadataAction, SIGNAL(triggered()), + this, SLOT(slotClearAllMetadata())); + + + m_pBpmLockAction = new QAction(tr("Lock BPM"), this); + m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), this); + connect(m_pBpmLockAction, SIGNAL(triggered()), + this, SLOT(slotLockBpm())); + connect(m_pBpmUnlockAction, SIGNAL(triggered()), + this, SLOT(slotUnlockBpm())); + + //BPM edit actions + m_pBpmDoubleAction = new QAction(tr("Double BPM"), this); + m_pBpmHalveAction = new QAction(tr("Halve BPM"), this); + m_pBpmTwoThirdsAction = new QAction(tr("2/3 BPM"), this); + m_pBpmThreeFourthsAction = new QAction(tr("3/4 BPM"), this); + m_pBpmFourThirdsAction = new QAction(tr("4/3 BPM"), this); + m_pBpmThreeHalvesAction = new QAction(tr("3/2 BPM"), this); + + connect(m_pBpmDoubleAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::DOUBLE); }); + connect(m_pBpmHalveAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::HALVE); }); + connect(m_pBpmTwoThirdsAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::TWOTHIRDS); }); + connect(m_pBpmThreeFourthsAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::THREEFOURTHS); }); + connect(m_pBpmFourThirdsAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::FOURTHIRDS); }); + connect(m_pBpmThreeHalvesAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::THREEHALVES); }); + + ColorPaletteSettings colorPaletteSettings(m_pConfig); + m_pColorPickerAction = new WColorPickerAction(WColorPicker::Option::AllowNoColor, colorPaletteSettings.getTrackColorPalette(), this); + m_pColorPickerAction->setObjectName("TrackColorPickerAction"); + connect(m_pColorPickerAction, + &WColorPickerAction::colorPicked, + this, + &WTrackMenu::slotColorPicked); +} + +void WTrackMenu::setTrackIds(TrackIdList trackIdList) { + // Clean all forms of track store + clearTrackSelection(); + m_pTrackIdList = std::move(trackIdList); // Store the track pointers at each initialization of track ids. - trackIdsToTrackPointers(); + for (const auto trackId : m_pTrackIdList) { + TrackPointer trackPointer = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); + m_pTrackPointerList.push_back(trackPointer); + } // Add actions to menu setupActions(); } -void WTrackMenu::setTrack(TrackId trackId) { +void WTrackMenu::setTrackId(TrackId trackId) { // Create a QList of single track to maintain common functions // for single and multi track selection. TrackIdList singleItemTrackIdList; singleItemTrackIdList.push_back(trackId); - setTracks(singleItemTrackIdList); + // Use setTrackIds to set a list of single element. + setTrackIds(singleItemTrackIdList); +} + +void WTrackMenu::setTrackIndexList(QModelIndexList indexList) { + std::cout << "setindexlist\n"; + clearTrackSelection(); + + m_pSelectedTrackIndices = std::move(indexList); + for (auto index : m_pSelectedTrackIndices) { + TrackPointer trackPointer = m_pTrackModel->getTrack(index); + m_pTrackPointerList.push_back(trackPointer); + TrackId trackId = trackPointer->getId(); + m_pTrackIdList.push_back(trackId); + } + + std::cout << "After initialization of indices: \n"; + std::cout << "size: " << m_pSelectedTrackIndices.size() << std::endl; + for (auto iter : m_pSelectedTrackIndices) { + std::cout << "printing index: "; + std::cout << iter.row() << " " << iter.internalId() << " " << iter.isValid() << std::endl; + } + std::cout << std::endl; + for (auto iter : m_pTrackIdList) { + std::cout << iter << " "; + } + std::cout << std::endl; + std::cout << "setindexlist " << m_pTrackPointerList.size() << " " << m_pSelectedTrackIndices.size() << std::endl; + setupActions(); } void WTrackMenu::setupActions() { + std::cout << "Setup Actions:"; + std::cout << m_pTrackModel; + std::cout << std::endl; + + QModelIndexList indices = m_pSelectedTrackIndices; + + // Gray out some stuff if multiple songs were selected. + bool oneSongSelected = indices.size() == 1; + TrackModel* trackModel = m_pTrackModel; + + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { + addAction(m_pAutoDJBottomAct); + addAction(m_pAutoDJTopAct); + addAction(m_pAutoDJReplaceAct); + addSeparator(); + } + + m_pLoadToMenu->clear(); + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { + int iNumDecks = m_pNumDecks->get(); + m_pDeckMenu->clear(); + if (iNumDecks > 0) { + for (int i = 1; i <= iNumDecks; ++i) { + // PlayerManager::groupForDeck is 0-indexed. + QString deckGroup = PlayerManager::groupForDeck(i - 1); + bool deckPlaying = ControlObject::get( + ConfigKey(deckGroup, "play")) > 0.0; + bool loadTrackIntoPlayingDeck = m_pConfig->getValue( + ConfigKey("[Controls]", "AllowTrackLoadToPlayingDeck")); + bool deckEnabled = (!deckPlaying || loadTrackIntoPlayingDeck) && oneSongSelected; + QAction* pAction = new QAction(tr("Deck %1").arg(i), this); + pAction->setEnabled(deckEnabled); + m_pDeckMenu->addAction(pAction); +// connect(pAction, &QAction::triggered, +// this, [this, deckGroup] { loadSelectionToGroup(deckGroup); }); + } + } + m_pLoadToMenu->addMenu(m_pDeckMenu); + } + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOSAMPLER)) { + int iNumSamplers = m_pNumSamplers->get(); + if (iNumSamplers > 0) { + m_pSamplerMenu->clear(); + for (int i = 1; i <= iNumSamplers; ++i) { + // PlayerManager::groupForSampler is 0-indexed. + QString samplerGroup = PlayerManager::groupForSampler(i - 1); + bool samplerPlaying = ControlObject::get( + ConfigKey(samplerGroup, "play")) > 0.0; + bool samplerEnabled = !samplerPlaying && oneSongSelected; + QAction* pAction = new QAction(tr("Sampler %1").arg(i), m_pSamplerMenu); + pAction->setEnabled(samplerEnabled); + m_pSamplerMenu->addAction(pAction); +// connect(pAction, &QAction::triggered, +// this, [this, samplerGroup] {loadSelectionToGroup(samplerGroup); } ); + + } + m_pLoadToMenu->addMenu(m_pSamplerMenu); + } + } + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOPREVIEWDECK) && + m_pNumPreviewDecks->get() > 0.0) { + m_pLoadToMenu->addAction(m_pAddToPreviewDeck); + } + + addMenu(m_pLoadToMenu); + addSeparator(); + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST)) { + // Playlist menu is lazy loaded on hover by slotPopulatePlaylistMenu + // to avoid unnecessary database queries + m_bPlaylistMenuLoaded = false; + addMenu(m_pPlaylistMenu); + } + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) { + // Crate menu is lazy loaded on hover by slotPopulateCrateMenu + // to avoid unnecessary database queries + m_bCrateMenuLoaded = false; + addMenu(m_pCrateMenu); + } + + // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks + bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { + m_pRemoveAct->setEnabled(!locked); + addAction(m_pRemoveAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST)) { + m_pRemovePlaylistAct->setEnabled(!locked); + addAction(m_pRemovePlaylistAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE)) { + m_pRemoveCrateAct->setEnabled(!locked); + addAction(m_pRemoveCrateAct); + } + + addSeparator(); + m_pMetadataMenu->clear(); + m_pMetadataUpdateExternalCollectionsMenu->clear(); + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); + m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); + m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); + m_pMetadataMenu->addAction(m_pExportMetadataAct); + + for (const auto& updateInExternalTrackCollection : m_updateInExternalTrackCollections) { + ExternalTrackCollection* externalTrackCollection = + updateInExternalTrackCollection.externalTrackCollection; + if (externalTrackCollection) { + updateInExternalTrackCollection.action->setEnabled( + externalTrackCollection->isConnected()); + m_pMetadataUpdateExternalCollectionsMenu->addAction( + updateInExternalTrackCollection.action); + } + } + if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { + m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); + } + + for (const auto& updateInExternalTrackCollection : m_updateInExternalTrackCollections) { + ExternalTrackCollection* externalTrackCollection = + updateInExternalTrackCollection.externalTrackCollection; + if (externalTrackCollection) { + updateInExternalTrackCollection.action->setEnabled( + externalTrackCollection->isConnected()); + m_pMetadataUpdateExternalCollectionsMenu->addAction( + updateInExternalTrackCollection.action); + } + } + if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { + m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); + } + + m_pClearMetadataMenu->clear(); + + if (trackModel == nullptr) { + return; + } + bool allowClear = true; + int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); + for (int i = 0; i < indices.size() && allowClear; ++i) { + int row = indices.at(i).row(); + QModelIndex index = indices.at(i).sibling(row,column); + if (index.data().toBool()) { + allowClear = false; + } + } + m_pClearBeatsAction->setEnabled(allowClear); + m_pClearMetadataMenu->addAction(m_pClearBeatsAction); + } + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RESETPLAYED)) { + m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); + } + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + // FIXME: Why is clearing the loop not working? + m_pClearMetadataMenu->addAction(m_pClearMainCueAction); + m_pClearMetadataMenu->addAction(m_pClearHotCuesAction); + m_pClearMetadataMenu->addAction(m_pClearIntroCueAction); + m_pClearMetadataMenu->addAction(m_pClearOutroCueAction); + //m_pClearMetadataMenu->addAction(m_pClearLoopAction); + m_pClearMetadataMenu->addAction(m_pClearKeyAction); + m_pClearMetadataMenu->addAction(m_pClearReplayGainAction); + m_pClearMetadataMenu->addAction(m_pClearWaveformAction); + m_pClearMetadataMenu->addSeparator(); + m_pClearMetadataMenu->addAction(m_pClearAllMetadataAction); + + // Cover art menu only applies if at least one track is selected. + if (indices.size()) { + // We load a single track to get the necessary context for the cover (we use + // last to be consistent with selectionChanged above). + QModelIndex last = indices.last(); + CoverInfo info; + info.source = static_cast( + last.sibling(last.row(), m_iCoverSourceColumn).data().toInt()); + info.type = static_cast( + last.sibling(last.row(), m_iCoverTypeColumn).data().toInt()); + info.hash = last.sibling(last.row(), m_iCoverHashColumn).data().toUInt(); + info.trackLocation = last.sibling( + last.row(), m_iTrackLocationColumn).data().toString(); + info.coverLocation = last.sibling( + last.row(), m_iCoverLocationColumn).data().toString(); + m_pCoverMenu->setCoverArt(info); + m_pMetadataMenu->addMenu(m_pCoverMenu); + } + + addMenu(m_pMetadataMenu); + addMenu(m_pClearMetadataMenu); + + m_pBPMMenu->addAction(m_pBpmDoubleAction); + m_pBPMMenu->addAction(m_pBpmHalveAction); + m_pBPMMenu->addAction(m_pBpmTwoThirdsAction); + m_pBPMMenu->addAction(m_pBpmThreeFourthsAction); + m_pBPMMenu->addAction(m_pBpmFourThirdsAction); + m_pBPMMenu->addAction(m_pBpmThreeHalvesAction); + m_pBPMMenu->addSeparator(); + m_pBPMMenu->addAction(m_pBpmLockAction); + m_pBPMMenu->addAction(m_pBpmUnlockAction); + m_pBPMMenu->addSeparator(); + if (oneSongSelected) { + if (trackModel == nullptr) { + return; + } + int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); + QModelIndex index = indices.at(0).sibling(indices.at(0).row(),column); + if (index.data().toBool()) { //BPM is locked + m_pBpmUnlockAction->setEnabled(true); + m_pBpmLockAction->setEnabled(false); + m_pBpmDoubleAction->setEnabled(false); + m_pBpmHalveAction->setEnabled(false); + m_pBpmTwoThirdsAction->setEnabled(false); + m_pBpmThreeFourthsAction->setEnabled(false); + m_pBpmFourThirdsAction->setEnabled(false); + m_pBpmThreeHalvesAction->setEnabled(false); + } else { //BPM is not locked + m_pBpmUnlockAction->setEnabled(false); + m_pBpmLockAction->setEnabled(true); + m_pBpmDoubleAction->setEnabled(true); + m_pBpmHalveAction->setEnabled(true); + m_pBpmTwoThirdsAction->setEnabled(true); + m_pBpmThreeFourthsAction->setEnabled(true); + m_pBpmFourThirdsAction->setEnabled(true); + m_pBpmThreeHalvesAction->setEnabled(true); + } + } else { + bool anyLocked = false; //true if any of the selected items are locked + int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); + for (int i = 0; i < indices.size() && !anyLocked; ++i) { + int row = indices.at(i).row(); + QModelIndex index = indices.at(i).sibling(row,column); + if (index.data().toBool()) { + anyLocked = true; + } + } + if (anyLocked) { + m_pBpmLockAction->setEnabled(false); + m_pBpmUnlockAction->setEnabled(true); + m_pBpmDoubleAction->setEnabled(false); + m_pBpmHalveAction->setEnabled(false); + m_pBpmTwoThirdsAction->setEnabled(false); + m_pBpmThreeFourthsAction->setEnabled(false); + m_pBpmFourThirdsAction->setEnabled(false); + m_pBpmThreeHalvesAction->setEnabled(false); + } else { + m_pBpmLockAction->setEnabled(true); + m_pBpmUnlockAction->setEnabled(false); + m_pBpmDoubleAction->setEnabled(true); + m_pBpmHalveAction->setEnabled(true); + m_pBpmTwoThirdsAction->setEnabled(true); + m_pBpmThreeFourthsAction->setEnabled(true); + m_pBpmFourThirdsAction->setEnabled(true); + m_pBpmThreeHalvesAction->setEnabled(true); + } + } + addMenu(m_pBPMMenu); + + // Track color menu only appears if at least one track is selected + if (!indices.empty()) { + m_pColorPickerAction->setColorPalette( + ColorPaletteSettings(m_pConfig).getTrackColorPalette()); + + // Get color of first selected track + int column = trackModel->fieldIndex(LIBRARYTABLE_COLOR); + QModelIndex index = indices.at(0).sibling(indices.at(0).row(), column); + auto trackColor = mixxx::RgbColor::fromQVariant(index.data()); + + // Check if all other selected tracks have the same color + bool multipleTrackColors = false; + for (int i = 1; i < indices.size(); ++i) { + int row = indices.at(i).row(); + QModelIndex index = indices.at(i).sibling(row, column); + + if (trackColor != mixxx::RgbColor::fromQVariant(index.data())) { + trackColor = mixxx::RgbColor::nullopt(); + multipleTrackColors = true; + break; + } + } + + if (multipleTrackColors) { + m_pColorPickerAction->resetSelectedColor(); + } else { + m_pColorPickerAction->setSelectedColor(trackColor); + } + m_pColorMenu->addAction(m_pColorPickerAction); + addMenu(m_pColorMenu); + } + } + + addSeparator(); + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE)) { + m_pHideAct->setEnabled(!locked); + addAction(m_pHideAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE)) { + m_pUnhideAct->setEnabled(!locked); + addAction(m_pUnhideAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE)) { + m_pPurgeAct->setEnabled(!locked); + addAction(m_pPurgeAct); + } addAction(m_pFileBrowserAct); + + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + addSeparator(); + m_pPropertiesAct->setEnabled(oneSongSelected); + addAction(m_pPropertiesAct); + } } void WTrackMenu::slotOpenInFileBrowser() { + std::cout << "slotopeninfilebrowser " << m_pTrackPointerList.size() << " " << m_pSelectedTrackIndices.size() << std::endl; + DEBUG_ASSERT(!getTrackPointerList().empty()); TrackPointerList trackPointerList = getTrackPointerList(); QStringList locations; for (const TrackPointer& trackPointer : trackPointerList) { locations << trackPointer->getLocation(); + std::cout << trackPointer->getLocation().toStdString() << std::endl; } mixxx::DesktopHelper::openInFileBrowser(locations); } @@ -190,10 +696,842 @@ TrackPointerList WTrackMenu::getTrackPointerList() { return m_pTrackPointerList; } -void WTrackMenu::trackIdsToTrackPointers() { - m_pTrackPointerList.clear(); - for (const auto trackId : m_pTrackIdList) { - TrackPointer trackPointer = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); - m_pTrackPointerList.push_back(trackPointer); +bool WTrackMenu::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const { + if (!m_pTrackModel) { + return false; + } + TrackModel* trackModel = m_pTrackModel; + return trackModel && + (trackModel->getCapabilities() & capabilities) == capabilities; +} + + + +void WTrackMenu::slotImportTrackMetadataFromFileTags() { + if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + return; + } + + const QModelIndexList indices = m_pSelectedTrackIndices; + + TrackModel* trackModel = m_pTrackModel;; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + // The user has explicitly requested to reload metadata from the file + // to override the information within Mixxx! Custom cover art must be + // reloaded separately. + SoundSourceProxy(pTrack).updateTrackFromSource( + SoundSourceProxy::ImportTrackMetadataMode::Again); + } + } +} + +void WTrackMenu::slotExportTrackMetadataIntoFileTags() { + if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + return; + } + + TrackModel* pTrackModel = m_pTrackModel;; + if (!pTrackModel) { + return; + } + + const QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.isEmpty()) { + return; + } + + mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = pTrackModel->getTrack(index); + if (pTrack) { + // Export of metadata is deferred until all references to the + // corresponding track object have been dropped. Otherwise + // writing to files that are still used for playback might + // cause crashes or at least audible glitches! + mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); + pTrack->markForMetadataExport(); + } + } +} + +void WTrackMenu::slotUpdateExternalTrackCollection( + ExternalTrackCollection* externalTrackCollection) { + VERIFY_OR_DEBUG_ASSERT(externalTrackCollection) { + return; + } + + if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + return; + } + + TrackModel* pTrackModel = m_pTrackModel;; + if (!pTrackModel) { + return; + } + + const QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.isEmpty()) { + return; + } + + QList trackRefs; + trackRefs.reserve(indices.size()); + for (const QModelIndex& index : indices) { + trackRefs.append( + TrackRef::fromFileInfo( + pTrackModel->getTrackLocation(index), + pTrackModel->getTrackId(index))); + } + + externalTrackCollection->updateTracks(std::move(trackRefs)); +} + +//slot for reset played count, sets count to 0 of one or more tracks +void WTrackMenu::slotClearPlayCount() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel;; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->resetPlayCounter(); + } + } +} + +void WTrackMenu::slotPopulatePlaylistMenu() { + // The user may open the Playlist submenu, move their cursor away, then + // return to the Playlist submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bPlaylistMenuLoaded) { + return; + } + m_pPlaylistMenu->clear(); + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + QMap playlists; + int numPlaylists = playlistDao.playlistCount(); + for (int i = 0; i < numPlaylists; ++i) { + int iPlaylistId = playlistDao.getPlaylistId(i); + playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); + } + QMapIterator it(playlists); + while (it.hasNext()) { + it.next(); + if (!playlistDao.isHidden(it.value())) { + // No leak because making the menu the parent means they will be + // auto-deleted + auto pAction = new QAction(it.key(), m_pPlaylistMenu); + bool locked = playlistDao.isPlaylistLocked(it.value()); + pAction->setEnabled(!locked); + m_pPlaylistMenu->addAction(pAction); + int iPlaylistId = it.value(); + connect(pAction, &QAction::triggered, + this, [this, iPlaylistId] { addSelectionToPlaylist(iPlaylistId); }); + + } + } + m_pPlaylistMenu->addSeparator(); + QAction* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); + m_pPlaylistMenu->addAction(newPlaylistAction); + connect(newPlaylistAction, &QAction::triggered, + this, [this] { addSelectionToPlaylist(-1); }); + m_bPlaylistMenuLoaded = true; +} + +void WTrackMenu::addSelectionToPlaylist(int iPlaylistId) { + const TrackIdList trackIds = m_pTrackIdList; + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for playlist"; + return; + } + + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + + if (iPlaylistId == -1) { // i.e. a new playlist is suppose to be created + QString name; + bool validNameGiven = false; + + do { + bool ok = false; + name = QInputDialog::getText(nullptr, + tr("Create New Playlist"), + tr("Enter name for new playlist:"), + QLineEdit::Normal, + tr("New Playlist"), + &ok).trimmed(); + if (!ok) { + return; + } + if (playlistDao.getPlaylistIdFromName(name) != -1) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("A playlist by that name already exists.")); + } else if (name.isEmpty()) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("A playlist cannot have a blank name.")); + } else { + validNameGiven = true; + } + } while (!validNameGiven); + iPlaylistId = playlistDao.createPlaylist(name);//-1 is changed to the new playlist ID return from the DAO + if (iPlaylistId == -1) { + QMessageBox::warning(nullptr, + tr("Playlist Creation Failed"), + tr("An unknown error occurred while creating playlist: ") + +name); + return; + } + } + + // TODO(XXX): Care whether the append succeeded. + m_pTrackCollectionManager->unhideTracks(trackIds); + playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); +} + + +void WTrackMenu::slotPopulateCrateMenu() { + // The user may open the Crate submenu, move their cursor away, then + // return to the Crate submenu before exiting the track context menu. + // Avoid querying the database multiple times in that case. + if (m_bCrateMenuLoaded) { + return; } + m_pCrateMenu->clear(); + const TrackIdList trackIds = m_pTrackIdList; + + CrateSummarySelectResult allCrates(m_pTrackCollectionManager->internalCollection()->crates().selectCratesWithTrackCount(trackIds)); + + CrateSummary crate; + while (allCrates.populateNext(&crate)) { + auto pAction = make_parented(m_pCrateMenu); + auto pCheckBox = make_parented(m_pCrateMenu); + + pCheckBox->setText(crate.getName()); + pCheckBox->setProperty("crateId", + QVariant::fromValue(crate.getId())); + pCheckBox->setEnabled(!crate.isLocked()); + // Strangely, the normal styling of QActions does not automatically + // apply to QWidgetActions. The :selected pseudo-state unfortunately + // does not work with QWidgetAction. :hover works for selecting items + // with the mouse, but not with the keyboard. :focus works for the + // keyboard but with the mouse, the last clicked item keeps the style + // after the mouse cursor is moved to hover over another item. + + // ronso0 Disabling this stylesheet allows to override the OS style + // of the :hover and :focus state. +// pCheckBox->setStyleSheet( +// QString("QCheckBox {color: %1;}").arg( +// pCheckBox->palette().text().color().name()) + "\n" + +// QString("QCheckBox:hover {background-color: %1;}").arg( +// pCheckBox->palette().highlight().color().name())); + pAction->setEnabled(!crate.isLocked()); + pAction->setDefaultWidget(pCheckBox.get()); + + if (crate.getTrackCount() == 0) { + pCheckBox->setChecked(false); + } else if (crate.getTrackCount() == (uint)trackIds.length()) { + pCheckBox->setChecked(true); + } else { + pCheckBox->setTristate(true); + pCheckBox->setCheckState(Qt::PartiallyChecked); + } + + m_pCrateMenu->addAction(pAction.get()); + connect(pAction.get(), &QAction::triggered, + this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); + connect(pCheckBox.get(), &QCheckBox::stateChanged, + this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); + + } + m_pCrateMenu->addSeparator(); + QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); + m_pCrateMenu->addAction(newCrateAction); + connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); + m_bCrateMenuLoaded = true; +} + + +void WTrackMenu::updateSelectionCrates(QWidget* pWidget) { + auto pCheckBox = qobject_cast(pWidget); + VERIFY_OR_DEBUG_ASSERT(pCheckBox) { + qWarning() << "crateId is not of CrateId type"; + return; + } + CrateId crateId = pCheckBox->property("crateId").value(); + + const TrackIdList trackIds = m_pTrackIdList; + + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for crate"; + return; + } + + // we need to disable tristate again as the mixed state will now be gone and can't be brought back + pCheckBox->setTristate(false); + if(!pCheckBox->isChecked()) { + if (crateId.isValid()) { + m_pTrackCollectionManager->internalCollection()->removeCrateTracks(crateId, trackIds); + } + } else { + if (!crateId.isValid()) { // i.e. a new crate is suppose to be created + crateId = CrateFeatureHelper( + m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); + } + if (crateId.isValid()) { + m_pTrackCollectionManager->unhideTracks(trackIds); + m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); + } + } +} + +void WTrackMenu::addSelectionToNewCrate() { + const TrackIdList trackIds = m_pTrackIdList; + + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for crate"; + return; + } + + CrateId crateId = CrateFeatureHelper( + m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); + + if (crateId.isValid()) { + m_pTrackCollectionManager->unhideTracks(trackIds); + m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); + } + +} + +void WTrackMenu::setTrackModel(TrackModel* trackModel) { + std::cout << "settrackmodel\n"; + m_pTrackModel = trackModel; + + // Move this to another function + m_iCoverSourceColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_SOURCE); + m_iCoverTypeColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_TYPE); + m_iCoverLocationColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_LOCATION); + m_iCoverHashColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_HASH); + m_iCoverColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART); + m_iTrackLocationColumn = trackModel->fieldIndex(TRACKLOCATIONSTABLE_LOCATION); +} + + +void WTrackMenu::slotLockBpm() { + lockBpm(true); +} + +void WTrackMenu::slotUnlockBpm() { + lockBpm(false); +} + +void WTrackMenu::slotScaleBpm(int scale) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel == nullptr) { + return; + } + + const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; + for (const auto& index : selectedTrackIndices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack && !pTrack->isBpmLocked()) { + BeatsPointer pBeats = pTrack->getBeats(); + if (pBeats) { + pBeats->scale(static_cast(scale)); + } + } + } +} + + +void WTrackMenu::lockBpm(bool lock) { + TrackModel* trackModel = m_pTrackModel;; + if (trackModel == nullptr) { + return; + } + + const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; + // TODO: This should be done in a thread for large selections + for (const auto& index : selectedTrackIndices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->setBpmLocked(lock); + } + } +} + +void WTrackMenu::slotColorPicked(mixxx::RgbColor::optional_t color) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel == nullptr) { + return; + } + + const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; + // TODO: This should be done in a thread for large selections + for (const auto& index : selectedTrackIndices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->setColor(color); + } + } + + hide(); +} + +void WTrackMenu::loadSelectionToGroup(QString group, bool play) { +// QModelIndexList indices = m_pSelectedTrackIndices; +// if (indices.size() > 0) { +// // If the track load override is disabled, check to see if a track is +// // playing before trying to load it +// if (!(m_pConfig->getValueString( +// ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { +// // TODO(XXX): Check for other than just the first preview deck. +// if (group != "[PreviewDeck1]" && +// ControlObject::get(ConfigKey(group, "play")) > 0.0) { +// return; +// } +// } +// QModelIndex index = indices.at(0); +// TrackModel* trackModel = m_pTrackModel; +// TrackPointer pTrack; +// if (trackModel && +// (pTrack = trackModel->getTrack(index))) { +// emit loadTrackToPlayer(pTrack, group, play); +// } +// } +} + + + +void WTrackMenu::slotClearBeats() { + TrackModel* trackModel = m_pTrackModel; + if (trackModel == nullptr) { + return; + } + + const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; + // TODO: This should be done in a thread for large selections + for (const auto& index : selectedTrackIndices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack && !pTrack->isBpmLocked()) { + pTrack->setBeats(BeatsPointer()); + } + } +} + +void WTrackMenu::slotClearMainCue() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel;; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->removeCuesOfType(mixxx::CueType::MainCue); + } + } +} + +void WTrackMenu::slotClearOutroCue() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel;; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->removeCuesOfType(mixxx::CueType::HotCue); + } + } +} + +void WTrackMenu::slotClearIntroCue() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel;; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->removeCuesOfType(mixxx::CueType::Intro); + } + } +} + +void WTrackMenu::slotClearKey() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel;; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->resetKeys(); + } + } +} + +void WTrackMenu::slotClearReplayGain() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel;; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->setReplayGain(mixxx::ReplayGain()); + } + } +} + +void WTrackMenu::slotClearWaveform() { + TrackModel* trackModel = m_pTrackModel;; + if (trackModel == nullptr) { + return; + } + + AnalysisDao& analysisDao = m_pTrackCollectionManager->internalCollection()->getAnalysisDAO(); + const QModelIndexList indices = m_pSelectedTrackIndices; + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (!pTrack) { + continue; + } + analysisDao.deleteAnalysesForTrack(pTrack->getId()); + pTrack->setWaveform(WaveformPointer()); + pTrack->setWaveformSummary(WaveformPointer()); + } +} + +void WTrackMenu::slotClearLoop() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->removeCuesOfType(mixxx::CueType::Loop); + } + } +} + +void WTrackMenu::slotClearHotCues() { + const QModelIndexList indices = m_pSelectedTrackIndices; + TrackModel* trackModel = m_pTrackModel; + + if (trackModel == nullptr) { + return; + } + + for (const QModelIndex& index : indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->removeCuesOfType(mixxx::CueType::HotCue); + } + } +} + + +void WTrackMenu::slotClearAllMetadata() { + slotClearBeats(); + slotClearMainCue(); + slotClearHotCues(); + slotClearIntroCue(); + slotClearOutroCue(); + slotClearLoop(); + slotClearKey(); + slotClearReplayGain(); + slotClearWaveform(); +} + + + +void WTrackMenu::slotRemove() { + std::cout << "Slot remove\n"; + if (m_pTrackModel->getCapabilities() == TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST) { + std::cout << "Can remove from playlist\n"; + }if (m_pTrackModel->getCapabilities() == TrackModel::TRACKMODELCAPS_REMOVE_CRATE) { + std::cout << "Can remove from crate\n"; + } + QModelIndexList indices = m_pSelectedTrackIndices; + std::cout << "Removal size: " << indices.size() << std::endl; + if (!indices.empty()) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel) { + trackModel->removeTracks(indices); + } + } +} + + + +void WTrackMenu::slotHide() { + QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.size() > 0) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel) { + trackModel->hideTracks(indices); + } + } +} + + + + +void WTrackMenu::slotTrackInfoClosed() { + DlgTrackInfo* pTrackInfo = m_pTrackInfo.take(); + // We are in a slot directly invoked from DlgTrackInfo. Delete it + // later. + if (pTrackInfo != nullptr) { + pTrackInfo->deleteLater(); + } +} + +void WTrackMenu::slotTagFetcherClosed() { + DlgTagFetcher* pTagFetcher = m_pTagFetcher.take(); + // We are in a slot directly invoked from DlgTagFetcher. Delete it + // later. + if (pTagFetcher != nullptr) { + pTagFetcher->deleteLater(); + } +} + +void WTrackMenu::slotShowTrackInfo() { + QModelIndexList indices = m_pSelectedTrackIndices; + + if (indices.size() > 0) { + showTrackInfo(indices[0]); + } +} + +void WTrackMenu::slotNextTrackInfo() { + QModelIndex nextRow = currentTrackInfoIndex.sibling( + currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); + if (nextRow.isValid()) { + showTrackInfo(nextRow); + if (!m_pTagFetcher.isNull()) { + showDlgTagFetcher(nextRow); + } + } +} + +void WTrackMenu::slotPrevTrackInfo() { + QModelIndex prevRow = currentTrackInfoIndex.sibling( + currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); + if (prevRow.isValid()) { + showTrackInfo(prevRow); + if (!m_pTagFetcher.isNull()) { + showDlgTagFetcher(prevRow); + } + } +} + +void WTrackMenu::showTrackInfo(QModelIndex index) { + TrackModel* trackModel = m_pTrackModel; + + if (!trackModel) { + return; + } + + if (m_pTrackInfo.isNull()) { + // Give a NULL parent because otherwise it inherits our style which can + // make it unreadable. Bug #673411 + m_pTrackInfo.reset(new DlgTrackInfo(m_pConfig, nullptr)); + + connect(m_pTrackInfo.data(), SIGNAL(next()), + this, SLOT(slotNextTrackInfo())); + connect(m_pTrackInfo.data(), SIGNAL(previous()), + this, SLOT(slotPrevTrackInfo())); + connect(m_pTrackInfo.data(), SIGNAL(showTagFetcher(TrackPointer)), + this, SLOT(slotShowTrackInTagFetcher(TrackPointer))); + connect(m_pTrackInfo.data(), SIGNAL(finished(int)), + this, SLOT(slotTrackInfoClosed())); + } + TrackPointer pTrack = trackModel->getTrack(index); + m_pTrackInfo->loadTrack(pTrack); // NULL is fine. + currentTrackInfoIndex = index; + m_pTrackInfo->show(); +} + +void WTrackMenu::slotNextDlgTagFetcher() { + QModelIndex nextRow = currentTrackInfoIndex.sibling( + currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); + if (nextRow.isValid()) { + showDlgTagFetcher(nextRow); + if (!m_pTrackInfo.isNull()) { + showTrackInfo(nextRow); + } + } +} + +void WTrackMenu::slotPrevDlgTagFetcher() { + QModelIndex prevRow = currentTrackInfoIndex.sibling( + currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); + if (prevRow.isValid()) { + showDlgTagFetcher(prevRow); + if (!m_pTrackInfo.isNull()) { + showTrackInfo(prevRow); + } + } +} + +void WTrackMenu::showDlgTagFetcher(QModelIndex index) { + TrackModel* trackModel = m_pTrackModel; + + if (!trackModel) { + return; + } + + TrackPointer pTrack = trackModel->getTrack(index); + currentTrackInfoIndex = index; + slotShowTrackInTagFetcher(pTrack); +} + +void WTrackMenu::slotShowTrackInTagFetcher(TrackPointer pTrack) { + if (m_pTagFetcher.isNull()) { + m_pTagFetcher.reset(new DlgTagFetcher(nullptr)); + connect(m_pTagFetcher.data(), SIGNAL(next()), + this, SLOT(slotNextDlgTagFetcher())); + connect(m_pTagFetcher.data(), SIGNAL(previous()), + this, SLOT(slotPrevDlgTagFetcher())); + connect(m_pTagFetcher.data(), SIGNAL(finished(int)), + this, SLOT(slotTagFetcherClosed())); + } + + // NULL is fine + m_pTagFetcher->loadTrack(pTrack); + m_pTagFetcher->show(); +} + +void WTrackMenu::slotShowDlgTagFetcher() { + QModelIndexList indices = m_pSelectedTrackIndices; + + if (indices.size() > 0) { + showDlgTagFetcher(indices[0]); + } +} + + +void WTrackMenu::slotAddToAutoDJBottom() { + // append to auto DJ + addToAutoDJ(PlaylistDAO::AutoDJSendLoc::BOTTOM); +} + +void WTrackMenu::slotAddToAutoDJTop() { + addToAutoDJ(PlaylistDAO::AutoDJSendLoc::TOP); +} + +void WTrackMenu::slotAddToAutoDJReplace() { + addToAutoDJ(PlaylistDAO::AutoDJSendLoc::REPLACE); +} + +void WTrackMenu::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { + if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { + return; + } + + const TrackIdList trackIds = m_pTrackIdList; + if (trackIds.isEmpty()) { + qWarning() << "No tracks selected for AutoDJ"; + return; + } + + PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); + + // TODO(XXX): Care whether the append succeeded. + m_pTrackCollectionManager->unhideTracks(trackIds); + playlistDao.addTracksToAutoDJQueue(trackIds, loc); +} + + +void WTrackMenu::slotCoverInfoSelected(const CoverInfoRelative& coverInfo) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel == nullptr) { + return; + } + const QModelIndexList selection = m_pSelectedTrackIndices; + for (const QModelIndex& index : selection) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->setCoverInfo(coverInfo); + } + } +} + +void WTrackMenu::slotReloadCoverArt() { + TrackModel* trackModel = m_pTrackModel; + if (!trackModel) { + return; + } + const QModelIndexList selection = m_pSelectedTrackIndices; + if (selection.isEmpty()) { + return; + } + QList selectedTracks; + selectedTracks.reserve(selection.size()); + for (const QModelIndex& index : selection) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + selectedTracks.append(pTrack); + } + } + guessTrackCoverInfoConcurrently(selectedTracks); +} + +void WTrackMenu::slotPurge() { + QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.size() > 0) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel) { + trackModel->purgeTracks(indices); + } + } +} + +void WTrackMenu::slotUnhide() { + QModelIndexList indices = m_pSelectedTrackIndices; + + if (indices.size() > 0) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel) { + trackModel->unhideTracks(indices); + } + } +} + +void WTrackMenu::clearTrackSelection() { + m_pTrackIdList.clear(); + m_pTrackPointerList.clear(); + m_pSelectedTrackIndices.clear(); } diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index de433062123e..2b121f5bed91 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -4,7 +4,12 @@ #include #include #include -#include +#include +#include +#include "library/trackcollectionmanager.h" +#include "library/trackmodel.h" +#include "library/dao/playlistdao.h" + #include "widget/wcoverartmenu.h" @@ -13,27 +18,100 @@ typedef QList TrackIdList; typedef QList TrackPointerList; +class WCoverArtMenu; + class TrackCollectionManager; class ExternalTrackCollection; class WTrackMenu : public QMenu { Q_OBJECT public: - WTrackMenu(QWidget *parent, TrackCollectionManager* pTrackCollectionManager); + WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager); ~WTrackMenu() override; - void setTrack(TrackId track); - void setTracks(TrackIdList trackList); + void setTrackId(TrackId track); + void setTrackIds(TrackIdList trackList); + void setTrackIndexList(QModelIndexList indexList); + void setTrackModel(TrackModel* trackModel); private slots: void slotOpenInFileBrowser(); + void slotLockBpm(); + void slotUnlockBpm(); + void slotScaleBpm(int); + void slotColorPicked(mixxx::RgbColor::optional_t color); + + void slotClearBeats(); + void slotClearPlayCount(); + void slotClearMainCue(); + void slotClearHotCues(); + void slotClearIntroCue(); + void slotClearOutroCue(); + void slotClearLoop(); + void slotClearKey(); + void slotClearReplayGain(); + void slotClearWaveform(); + void slotClearAllMetadata(); + + void lockBpm(bool lock); + void slotNextTrackInfo(); + void slotNextDlgTagFetcher(); + void slotPrevTrackInfo(); + void slotPrevDlgTagFetcher(); + void slotShowTrackInTagFetcher(TrackPointer track); + + void slotPopulatePlaylistMenu(); + void slotPopulateCrateMenu(); + void addSelectionToNewCrate(); + + void slotImportTrackMetadataFromFileTags(); + void slotExportTrackMetadataIntoFileTags(); + void slotUpdateExternalTrackCollection(ExternalTrackCollection *externalTrackCollection); + void slotRemove(); + void slotHide(); + void slotTrackInfoClosed(); + void slotTagFetcherClosed(); + void slotShowTrackInfo(); + void slotShowDlgTagFetcher(); + + void slotAddToAutoDJBottom(); + void slotAddToAutoDJTop(); + void slotAddToAutoDJReplace(); + + void slotCoverInfoSelected(const CoverInfoRelative &coverInfo); + + + void slotReloadCoverArt(); + + void slotUnhide(); + + void slotPurge(); private: void createActions(); void setupActions(); void trackIdsToTrackPointers(); + void setTrackPointerList(TrackPointerList trackPointerList); TrackPointerList getTrackPointerList(); + bool modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const; + + void addSelectionToPlaylist(int iPlaylistId); + void updateSelectionCrates(QWidget* pWidget); + + void showTrackInfo(QModelIndex index); + void showDlgTagFetcher(QModelIndex index); + + void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc); + + + void loadSelectionToGroup(QString group, bool play); + void clearTrackSelection(); + + ControlProxy* m_pNumSamplers; + ControlProxy* m_pNumDecks; + ControlProxy* m_pNumPreviewDecks; + // The selected tracks for which the context menu is created TrackIdList m_pTrackIdList; @@ -106,9 +184,43 @@ class WTrackMenu : public QMenu { QAction* m_pClearReplayGainAction; QAction* m_pClearAllMetadataAction; + + const UserSettingsPointer m_pConfig; + TrackCollectionManager* m_pTrackCollectionManager; TrackPointerList m_pTrackPointerList; + TrackModel* m_pTrackModel; + + QModelIndexList m_pSelectedTrackIndices; + + QScopedPointer m_pTrackInfo; + QScopedPointer m_pTagFetcher; + + QModelIndex currentTrackInfoIndex; + + struct UpdateExternalTrackCollection { + QPointer externalTrackCollection; + QAction* action{}; + }; + QList m_updateInExternalTrackCollections; + + bool m_bPlaylistMenuLoaded; + bool m_bCrateMenuLoaded; + + // Column numbers + int m_iCoverSourceColumn; // cover art source + int m_iCoverTypeColumn; // cover art type + int m_iCoverLocationColumn; // cover art location + int m_iCoverHashColumn; // cover art hash + int m_iCoverColumn; // visible cover art + int m_iTrackLocationColumn; + + void trackIndicesToTrackPointers(); }; + + + + #endif // WTRACKMENU_H diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index e3efe93bdcaa..7b957304bc72 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1,7 +1,6 @@ #include "widget/wtracktableview.h" #include -#include #include #include #include @@ -9,11 +8,9 @@ #include #include #include -#include #include "control/controlobject.h" #include "control/controlproxy.h" -#include "library/coverartutils.h" #include "library/crate/cratefeaturehelper.h" #include "library/dao/trackschema.h" #include "library/dlgtagfetcher.h" @@ -41,6 +38,8 @@ #include "widget/wtracktableviewheader.h" #include "widget/wwidget.h" +#include + WTrackTableView::WTrackTableView(QWidget * parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager, @@ -51,24 +50,10 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_pConfig(pConfig), m_pTrackCollectionManager(pTrackCollectionManager), m_sorting(sorting), - m_iCoverSourceColumn(-1), - m_iCoverTypeColumn(-1), - m_iCoverLocationColumn(-1), - m_iCoverHashColumn(-1), - m_iCoverColumn(-1), m_selectionChangedSinceLastGuiTick(true), - m_loadCachedOnly(false), - m_bPlaylistMenuLoaded(false), - m_bCrateMenuLoaded(false) { + m_loadCachedOnly(false) { - m_pNumSamplers = new ControlProxy( - "[Master]", "num_samplers", this); - m_pNumDecks = new ControlProxy( - "[Master]", "num_decks", this); - m_pNumPreviewDecks = new ControlProxy( - "[Master]", "num_preview_decks", this); - m_pMenu = new WTrackMenu(this, m_pTrackCollectionManager); // Connect slots and signals to make the world go 'round. connect(this, SIGNAL(doubleClicked(const QModelIndex &)), @@ -92,6 +77,8 @@ WTrackTableView::WTrackTableView(QWidget * parent, QKeySequence(tr("ESC", "Focus")), this); connect(setFocusShortcut, SIGNAL(activated()), this, SLOT(setFocus())); + + m_pMenu = new WTrackMenu(this, pConfig, m_pTrackCollectionManager); } WTrackTableView::~WTrackTableView() { @@ -101,50 +88,7 @@ WTrackTableView::~WTrackTableView() { pHeader->saveHeaderState(); } -// delete m_pImportMetadataFromFileAct; -// delete m_pImportMetadataFromMusicBrainzAct; -// delete m_pExportMetadataAct; -// delete m_pAddToPreviewDeck; -// delete m_pAutoDJBottomAct; -// delete m_pAutoDJTopAct; -// delete m_pAutoDJReplaceAct; -// delete m_pRemoveAct; -// delete m_pRemovePlaylistAct; -// delete m_pRemoveCrateAct; -// delete m_pHideAct; -// delete m_pUnhideAct; -// delete m_pPropertiesAct; delete m_pMenu; -// delete m_pLoadToMenu; -// delete m_pDeckMenu; -// delete m_pSamplerMenu; -// delete m_pPlaylistMenu; -// delete m_pCrateMenu; -// delete m_pMetadataMenu; -// delete m_pClearMetadataMenu; -// delete m_pCoverMenu; -// delete m_pBpmLockAction; -// delete m_pBpmUnlockAction; -// delete m_pBpmDoubleAction; -// delete m_pBpmHalveAction; -// delete m_pBpmTwoThirdsAction; -// delete m_pBpmThreeFourthsAction; -// delete m_pBpmFourThirdsAction; -// delete m_pBpmThreeHalvesAction; -// delete m_pBPMMenu; -// delete m_pColorMenu; -// delete m_pClearBeatsAction; -// delete m_pClearPlayCountAction; -// delete m_pClearMainCueAction; -// delete m_pClearHotCuesAction; -// delete m_pClearIntroCueAction; -// delete m_pClearOutroCueAction; -// delete m_pClearLoopAction; -// delete m_pClearReplayGainAction; -// delete m_pClearWaveformAction; -// delete m_pClearAllMetadataAction; -// delete m_pPurgeAct; -// delete m_pFileBrowserAct; } void WTrackTableView::enableCachedOnly() { @@ -238,12 +182,7 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { // by slotLoadCoverArt(). As this value will not change when the model // still the same, we must avoid doing hundreds of "fieldIndex" calls // when it is completely unnecessary... - m_iCoverSourceColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_SOURCE); - m_iCoverTypeColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_TYPE); - m_iCoverLocationColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_LOCATION); - m_iCoverHashColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_HASH); - m_iCoverColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART); - m_iTrackLocationColumn = trackModel->fieldIndex(TRACKLOCATIONSTABLE_LOCATION); + setVisible(false); @@ -382,114 +321,6 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { // scrollbar positions with respect to different models are backed by map } -void WTrackTableView::createActions() { - /* - - - - for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { - UpdateExternalTrackCollection updateInExternalTrackCollection; - updateInExternalTrackCollection.externalTrackCollection = externalTrackCollection; - updateInExternalTrackCollection.action = new QAction(externalTrackCollection->name(), this); - updateInExternalTrackCollection.action->setToolTip(externalTrackCollection->description()); - m_updateInExternalTrackCollections += updateInExternalTrackCollection; - auto externalTrackCollectionPtr = updateInExternalTrackCollection.externalTrackCollection; - connect(updateInExternalTrackCollection.action, &QAction::triggered, - this, [this, externalTrackCollectionPtr] { - slotUpdateExternalTrackCollection(externalTrackCollectionPtr); - }); - } - - m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); - // currently there is only one preview deck so just map it here. - QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); - connect(m_pAddToPreviewDeck, &QAction::triggered, - this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); - - - // Clear metadata actions - m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); - connect(m_pClearBeatsAction, SIGNAL(triggered()), - this, SLOT(slotClearBeats())); - - m_pClearPlayCountAction = new QAction(tr("Play Count"), this); - connect(m_pClearPlayCountAction, SIGNAL(triggered()), - this, SLOT(slotClearPlayCount())); - - m_pClearMainCueAction = new QAction(tr("Cue Point"), this); - connect(m_pClearMainCueAction, SIGNAL(triggered()), - this, SLOT(slotClearMainCue())); - - m_pClearHotCuesAction = new QAction(tr("Hotcues"), this); - connect(m_pClearHotCuesAction, SIGNAL(triggered()), - this, SLOT(slotClearHotCues())); - - m_pClearIntroCueAction = new QAction(tr("Intro"), this); - connect(m_pClearIntroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearIntroCue())); - - m_pClearOutroCueAction = new QAction(tr("Outro"), this); - connect(m_pClearOutroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearOutroCue())); - - m_pClearLoopAction = new QAction(tr("Loop"), this); - connect(m_pClearLoopAction, SIGNAL(triggered()), - this, SLOT(slotClearLoop())); - - m_pClearKeyAction = new QAction(tr("Key"), this); - connect(m_pClearKeyAction, SIGNAL(triggered()), - this, SLOT(slotClearKey())); - - m_pClearReplayGainAction = new QAction(tr("ReplayGain"), this); - connect(m_pClearReplayGainAction, SIGNAL(triggered()), - this, SLOT(slotClearReplayGain())); - - m_pClearWaveformAction = new QAction(tr("Waveform"), this); - connect(m_pClearWaveformAction, SIGNAL(triggered()), - this, SLOT(slotClearWaveform())); - - m_pClearAllMetadataAction = new QAction(tr("All"), this); - connect(m_pClearAllMetadataAction, SIGNAL(triggered()), - this, SLOT(slotClearAllMetadata())); - - - m_pBpmLockAction = new QAction(tr("Lock BPM"), this); - m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), this); - connect(m_pBpmLockAction, SIGNAL(triggered()), - this, SLOT(slotLockBpm())); - connect(m_pBpmUnlockAction, SIGNAL(triggered()), - this, SLOT(slotUnlockBpm())); - - //BPM edit actions - m_pBpmDoubleAction = new QAction(tr("Double BPM"), this); - m_pBpmHalveAction = new QAction(tr("Halve BPM"), this); - m_pBpmTwoThirdsAction = new QAction(tr("2/3 BPM"), this); - m_pBpmThreeFourthsAction = new QAction(tr("3/4 BPM"), this); - m_pBpmFourThirdsAction = new QAction(tr("4/3 BPM"), this); - m_pBpmThreeHalvesAction = new QAction(tr("3/2 BPM"), this); - - connect(m_pBpmDoubleAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::DOUBLE); }); - connect(m_pBpmHalveAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::HALVE); }); - connect(m_pBpmTwoThirdsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::TWOTHIRDS); }); - connect(m_pBpmThreeFourthsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::THREEFOURTHS); }); - connect(m_pBpmFourThirdsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::FOURTHIRDS); }); - connect(m_pBpmThreeHalvesAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::THREEHALVES); }); - - ColorPaletteSettings colorPaletteSettings(m_pConfig); - m_pColorPickerAction = new WColorPickerAction(WColorPicker::Option::AllowNoColor, colorPaletteSettings.getTrackColorPalette(), this); - m_pColorPickerAction->setObjectName("TrackColorPickerAction"); - connect(m_pColorPickerAction, - &WColorPickerAction::colorPicked, - this, - &WTrackTableView::slotColorPicked);*/ -} - // slot void WTrackTableView::slotMouseDoubleClicked(const QModelIndex &index) { // Read the current TrackLoadAction settings @@ -519,16 +350,17 @@ void WTrackTableView::slotMouseDoubleClicked(const QModelIndex &index) { } } + void WTrackTableView::loadSelectionToGroup(QString group, bool play) { QModelIndexList indices = selectionModel()->selectedRows(); if (indices.size() > 0) { // If the track load override is disabled, check to see if a track is // playing before trying to load it if (!(m_pConfig->getValueString( - ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { + ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { // TODO(XXX): Check for other than just the first preview deck. if (group != "[PreviewDeck1]" && - ControlObject::get(ConfigKey(group, "play")) > 0.0) { + ControlObject::get(ConfigKey(group, "play")) > 0.0) { return; } } @@ -536,21 +368,13 @@ void WTrackTableView::loadSelectionToGroup(QString group, bool play) { TrackModel* trackModel = getTrackModel(); TrackPointer pTrack; if (trackModel && - (pTrack = trackModel->getTrack(index))) { + (pTrack = trackModel->getTrack(index))) { emit loadTrackToPlayer(pTrack, group, play); } } } -void WTrackTableView::slotRemove() { - QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.size() > 0) { - TrackModel* trackModel = getTrackModel(); - if (trackModel) { - trackModel->removeTracks(indices); - } - } -} + void WTrackTableView::slotPurge() { QModelIndexList indices = selectionModel()->selectedRows(); @@ -562,16 +386,6 @@ void WTrackTableView::slotPurge() { } } -void WTrackTableView::slotHide() { - QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.size() > 0) { - TrackModel* trackModel = getTrackModel(); - if (trackModel) { - trackModel->hideTracks(indices); - } - } -} - void WTrackTableView::slotUnhide() { QModelIndexList indices = selectionModel()->selectedRows(); @@ -583,458 +397,15 @@ void WTrackTableView::slotUnhide() { } } -void WTrackTableView::slotTrackInfoClosed() { - DlgTrackInfo* pTrackInfo = m_pTrackInfo.take(); - // We are in a slot directly invoked from DlgTrackInfo. Delete it - // later. - if (pTrackInfo != nullptr) { - pTrackInfo->deleteLater(); - } -} - -void WTrackTableView::slotTagFetcherClosed() { - DlgTagFetcher* pTagFetcher = m_pTagFetcher.take(); - // We are in a slot directly invoked from DlgTagFetcher. Delete it - // later. - if (pTagFetcher != nullptr) { - pTagFetcher->deleteLater(); - } -} - -void WTrackTableView::slotShowTrackInfo() { - QModelIndexList indices = selectionModel()->selectedRows(); - - if (indices.size() > 0) { - showTrackInfo(indices[0]); - } -} - -void WTrackTableView::slotNextTrackInfo() { - QModelIndex nextRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); - if (nextRow.isValid()) { - showTrackInfo(nextRow); - if (!m_pTagFetcher.isNull()) { - showDlgTagFetcher(nextRow); - } - } -} - -void WTrackTableView::slotPrevTrackInfo() { - QModelIndex prevRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); - if (prevRow.isValid()) { - showTrackInfo(prevRow); - if (!m_pTagFetcher.isNull()) { - showDlgTagFetcher(prevRow); - } - } -} - -void WTrackTableView::showTrackInfo(QModelIndex index) { - TrackModel* trackModel = getTrackModel(); - - if (!trackModel) { - return; - } - - if (m_pTrackInfo.isNull()) { - // Give a NULL parent because otherwise it inherits our style which can - // make it unreadable. Bug #673411 - m_pTrackInfo.reset(new DlgTrackInfo(m_pConfig, nullptr)); - - connect(m_pTrackInfo.data(), SIGNAL(next()), - this, SLOT(slotNextTrackInfo())); - connect(m_pTrackInfo.data(), SIGNAL(previous()), - this, SLOT(slotPrevTrackInfo())); - connect(m_pTrackInfo.data(), SIGNAL(showTagFetcher(TrackPointer)), - this, SLOT(slotShowTrackInTagFetcher(TrackPointer))); - connect(m_pTrackInfo.data(), SIGNAL(finished(int)), - this, SLOT(slotTrackInfoClosed())); - } - TrackPointer pTrack = trackModel->getTrack(index); - m_pTrackInfo->loadTrack(pTrack); // NULL is fine. - currentTrackInfoIndex = index; - m_pTrackInfo->show(); -} - -void WTrackTableView::slotNextDlgTagFetcher() { - QModelIndex nextRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); - if (nextRow.isValid()) { - showDlgTagFetcher(nextRow); - if (!m_pTrackInfo.isNull()) { - showTrackInfo(nextRow); - } - } -} - -void WTrackTableView::slotPrevDlgTagFetcher() { - QModelIndex prevRow = currentTrackInfoIndex.sibling( - currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); - if (prevRow.isValid()) { - showDlgTagFetcher(prevRow); - if (!m_pTrackInfo.isNull()) { - showTrackInfo(prevRow); - } - } -} - -void WTrackTableView::showDlgTagFetcher(QModelIndex index) { - TrackModel* trackModel = getTrackModel(); - - if (!trackModel) { - return; - } - - TrackPointer pTrack = trackModel->getTrack(index); - currentTrackInfoIndex = index; - slotShowTrackInTagFetcher(pTrack); -} - -void WTrackTableView::slotShowTrackInTagFetcher(TrackPointer pTrack) { - if (m_pTagFetcher.isNull()) { - m_pTagFetcher.reset(new DlgTagFetcher(nullptr)); - connect(m_pTagFetcher.data(), SIGNAL(next()), - this, SLOT(slotNextDlgTagFetcher())); - connect(m_pTagFetcher.data(), SIGNAL(previous()), - this, SLOT(slotPrevDlgTagFetcher())); - connect(m_pTagFetcher.data(), SIGNAL(finished(int)), - this, SLOT(slotTagFetcherClosed())); - } - - // NULL is fine - m_pTagFetcher->loadTrack(pTrack); - m_pTagFetcher->show(); -} - -void WTrackTableView::slotShowDlgTagFetcher() { - QModelIndexList indices = selectionModel()->selectedRows(); - - if (indices.size() > 0) { - showDlgTagFetcher(indices[0]); - } -} - void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { - /* - QModelIndexList indices = selectionModel()->selectedRows(); - - // Gray out some stuff if multiple songs were selected. - bool oneSongSelected = indices.size() == 1; - TrackModel* trackModel = getTrackModel(); - - m_pMenu->clear(); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { - m_pMenu->clear(); - m_pMenu->addAction(m_pAutoDJBottomAct); - m_pMenu->addAction(m_pAutoDJTopAct); - m_pMenu->addAction(m_pAutoDJReplaceAct); - m_pMenu->addSeparator(); - } - - m_pLoadToMenu->clear(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { - int iNumDecks = m_pNumDecks->get(); - m_pDeckMenu->clear(); - if (iNumDecks > 0) { - for (int i = 1; i <= iNumDecks; ++i) { - // PlayerManager::groupForDeck is 0-indexed. - QString deckGroup = PlayerManager::groupForDeck(i - 1); - bool deckPlaying = ControlObject::get( - ConfigKey(deckGroup, "play")) > 0.0; - bool loadTrackIntoPlayingDeck = m_pConfig->getValue( - ConfigKey("[Controls]", "AllowTrackLoadToPlayingDeck")); - bool deckEnabled = (!deckPlaying || loadTrackIntoPlayingDeck) && oneSongSelected; - QAction* pAction = new QAction(tr("Deck %1").arg(i), m_pMenu); - pAction->setEnabled(deckEnabled); - m_pDeckMenu->addAction(pAction); - connect(pAction, &QAction::triggered, - this, [this, deckGroup] { loadSelectionToGroup(deckGroup); }); - } - } - m_pLoadToMenu->addMenu(m_pDeckMenu); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOSAMPLER)) { - int iNumSamplers = m_pNumSamplers->get(); - if (iNumSamplers > 0) { - m_pSamplerMenu->clear(); - for (int i = 1; i <= iNumSamplers; ++i) { - // PlayerManager::groupForSampler is 0-indexed. - QString samplerGroup = PlayerManager::groupForSampler(i - 1); - bool samplerPlaying = ControlObject::get( - ConfigKey(samplerGroup, "play")) > 0.0; - bool samplerEnabled = !samplerPlaying && oneSongSelected; - QAction* pAction = new QAction(tr("Sampler %1").arg(i), m_pSamplerMenu); - pAction->setEnabled(samplerEnabled); - m_pSamplerMenu->addAction(pAction); - connect(pAction, &QAction::triggered, - this, [this, samplerGroup] {loadSelectionToGroup(samplerGroup); } ); - - } - m_pLoadToMenu->addMenu(m_pSamplerMenu); - } - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOPREVIEWDECK) && - m_pNumPreviewDecks->get() > 0.0) { - m_pLoadToMenu->addAction(m_pAddToPreviewDeck); - } - - m_pMenu->addMenu(m_pLoadToMenu); - m_pMenu->addSeparator(); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST)) { - // Playlist menu is lazy loaded on hover by slotPopulatePlaylistMenu - // to avoid unnecessary database queries - m_bPlaylistMenuLoaded = false; - m_pMenu->addMenu(m_pPlaylistMenu); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) { - // Crate menu is lazy loaded on hover by slotPopulateCrateMenu - // to avoid unnecessary database queries - m_bCrateMenuLoaded = false; - m_pMenu->addMenu(m_pCrateMenu); - } - - // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks - bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { - m_pRemoveAct->setEnabled(!locked); - m_pMenu->addAction(m_pRemoveAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST)) { - m_pRemovePlaylistAct->setEnabled(!locked); - m_pMenu->addAction(m_pRemovePlaylistAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE)) { - m_pRemoveCrateAct->setEnabled(!locked); - m_pMenu->addAction(m_pRemoveCrateAct); - } - - m_pMenu->addSeparator(); - m_pMetadataMenu->clear(); - m_pMetadataUpdateExternalCollectionsMenu->clear(); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); - m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); - m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); - m_pMetadataMenu->addAction(m_pExportMetadataAct); - - for (const auto& updateInExternalTrackCollection : m_updateInExternalTrackCollections) { - ExternalTrackCollection* externalTrackCollection = - updateInExternalTrackCollection.externalTrackCollection; - if (externalTrackCollection) { - updateInExternalTrackCollection.action->setEnabled( - externalTrackCollection->isConnected()); - m_pMetadataUpdateExternalCollectionsMenu->addAction( - updateInExternalTrackCollection.action); - } - } - if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { - m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); - } - - for (const auto& updateInExternalTrackCollection : m_updateInExternalTrackCollections) { - ExternalTrackCollection* externalTrackCollection = - updateInExternalTrackCollection.externalTrackCollection; - if (externalTrackCollection) { - updateInExternalTrackCollection.action->setEnabled( - externalTrackCollection->isConnected()); - m_pMetadataUpdateExternalCollectionsMenu->addAction( - updateInExternalTrackCollection.action); - } - } - if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { - m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); - } - - m_pClearMetadataMenu->clear(); + // Set the track model + m_pMenu->setTrackModel(getTrackModel()); - if (trackModel == nullptr) { - return; - } - bool allowClear = true; - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); - for (int i = 0; i < indices.size() && allowClear; ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row,column); - if (index.data().toBool()) { - allowClear = false; - } - } - m_pClearBeatsAction->setEnabled(allowClear); - m_pClearMetadataMenu->addAction(m_pClearBeatsAction); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RESETPLAYED)) { - m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - // FIXME: Why is clearing the loop not working? - m_pClearMetadataMenu->addAction(m_pClearMainCueAction); - m_pClearMetadataMenu->addAction(m_pClearHotCuesAction); - m_pClearMetadataMenu->addAction(m_pClearIntroCueAction); - m_pClearMetadataMenu->addAction(m_pClearOutroCueAction); - //m_pClearMetadataMenu->addAction(m_pClearLoopAction); - m_pClearMetadataMenu->addAction(m_pClearKeyAction); - m_pClearMetadataMenu->addAction(m_pClearReplayGainAction); - m_pClearMetadataMenu->addAction(m_pClearWaveformAction); - m_pClearMetadataMenu->addSeparator(); - m_pClearMetadataMenu->addAction(m_pClearAllMetadataAction); - - // Cover art menu only applies if at least one track is selected. - if (indices.size()) { - // We load a single track to get the necessary context for the cover (we use - // last to be consistent with selectionChanged above). - QModelIndex last = indices.last(); - CoverInfo info; - info.source = static_cast( - last.sibling(last.row(), m_iCoverSourceColumn).data().toInt()); - info.type = static_cast( - last.sibling(last.row(), m_iCoverTypeColumn).data().toInt()); - info.hash = last.sibling(last.row(), m_iCoverHashColumn).data().toUInt(); - info.trackLocation = last.sibling( - last.row(), m_iTrackLocationColumn).data().toString(); - info.coverLocation = last.sibling( - last.row(), m_iCoverLocationColumn).data().toString(); - m_pCoverMenu->setCoverArt(info); - m_pMetadataMenu->addMenu(m_pCoverMenu); - } - - m_pMenu->addMenu(m_pMetadataMenu); - m_pMenu->addMenu(m_pClearMetadataMenu); - - m_pBPMMenu->addAction(m_pBpmDoubleAction); - m_pBPMMenu->addAction(m_pBpmHalveAction); - m_pBPMMenu->addAction(m_pBpmTwoThirdsAction); - m_pBPMMenu->addAction(m_pBpmThreeFourthsAction); - m_pBPMMenu->addAction(m_pBpmFourThirdsAction); - m_pBPMMenu->addAction(m_pBpmThreeHalvesAction); - m_pBPMMenu->addSeparator(); - m_pBPMMenu->addAction(m_pBpmLockAction); - m_pBPMMenu->addAction(m_pBpmUnlockAction); - m_pBPMMenu->addSeparator(); - if (oneSongSelected) { - if (trackModel == nullptr) { - return; - } - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); - QModelIndex index = indices.at(0).sibling(indices.at(0).row(),column); - if (index.data().toBool()) { //BPM is locked - m_pBpmUnlockAction->setEnabled(true); - m_pBpmLockAction->setEnabled(false); - m_pBpmDoubleAction->setEnabled(false); - m_pBpmHalveAction->setEnabled(false); - m_pBpmTwoThirdsAction->setEnabled(false); - m_pBpmThreeFourthsAction->setEnabled(false); - m_pBpmFourThirdsAction->setEnabled(false); - m_pBpmThreeHalvesAction->setEnabled(false); - } else { //BPM is not locked - m_pBpmUnlockAction->setEnabled(false); - m_pBpmLockAction->setEnabled(true); - m_pBpmDoubleAction->setEnabled(true); - m_pBpmHalveAction->setEnabled(true); - m_pBpmTwoThirdsAction->setEnabled(true); - m_pBpmThreeFourthsAction->setEnabled(true); - m_pBpmFourThirdsAction->setEnabled(true); - m_pBpmThreeHalvesAction->setEnabled(true); - } - } else { - bool anyLocked = false; //true if any of the selected items are locked - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); - for (int i = 0; i < indices.size() && !anyLocked; ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row,column); - if (index.data().toBool()) { - anyLocked = true; - } - } - if (anyLocked) { - m_pBpmLockAction->setEnabled(false); - m_pBpmUnlockAction->setEnabled(true); - m_pBpmDoubleAction->setEnabled(false); - m_pBpmHalveAction->setEnabled(false); - m_pBpmTwoThirdsAction->setEnabled(false); - m_pBpmThreeFourthsAction->setEnabled(false); - m_pBpmFourThirdsAction->setEnabled(false); - m_pBpmThreeHalvesAction->setEnabled(false); - } else { - m_pBpmLockAction->setEnabled(true); - m_pBpmUnlockAction->setEnabled(false); - m_pBpmDoubleAction->setEnabled(true); - m_pBpmHalveAction->setEnabled(true); - m_pBpmTwoThirdsAction->setEnabled(true); - m_pBpmThreeFourthsAction->setEnabled(true); - m_pBpmFourThirdsAction->setEnabled(true); - m_pBpmThreeHalvesAction->setEnabled(true); - } - } - m_pMenu->addMenu(m_pBPMMenu); - - // Track color menu only appears if at least one track is selected - if (indices.size()) { - m_pColorPickerAction->setColorPalette( - ColorPaletteSettings(m_pConfig).getTrackColorPalette()); - - // Get color of first selected track - int column = trackModel->fieldIndex(LIBRARYTABLE_COLOR); - QModelIndex index = indices.at(0).sibling(indices.at(0).row(), column); - auto trackColor = mixxx::RgbColor::fromQVariant(index.data()); - - // Check if all other selected tracks have the same color - bool multipleTrackColors = false; - for (int i = 1; i < indices.size(); ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row, column); - - if (trackColor != mixxx::RgbColor::fromQVariant(index.data())) { - trackColor = mixxx::RgbColor::nullopt(); - multipleTrackColors = true; - break; - } - } - - if (multipleTrackColors) { - m_pColorPickerAction->resetSelectedColor(); - } else { - m_pColorPickerAction->setSelectedColor(trackColor); - } - m_pColorMenu->addAction(m_pColorPickerAction); - m_pMenu->addMenu(m_pColorMenu); - } - } - - m_pMenu->addSeparator(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE)) { - m_pHideAct->setEnabled(!locked); - m_pMenu->addAction(m_pHideAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE)) { - m_pUnhideAct->setEnabled(!locked); - m_pMenu->addAction(m_pUnhideAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE)) { - m_pPurgeAct->setEnabled(!locked); - m_pMenu->addAction(m_pPurgeAct); - } - m_pMenu->addAction(m_pFileBrowserAct); - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - m_pMenu->addSeparator(); - m_pPropertiesAct->setEnabled(oneSongSelected); - m_pMenu->addAction(m_pPropertiesAct); - } -*/ - - // Update track ids in context menu - TrackIdList trackIds = getSelectedTrackIds(); - m_pMenu->setTracks(trackIds); + // Update track indices in context menu + QModelIndexList indices = selectionModel()->selectedRows(); + std::cout << "passing this many indices: " << indices.size() << std::endl; + DEBUG_ASSERT(!indices.empty()); + m_pMenu->setTrackIndexList(selectionModel()->selectedRows()); //Create the right-click menu m_pMenu->popup(event->globalPos()); @@ -1317,7 +688,6 @@ void WTrackTableView::dropEvent(QDropEvent * event) { updateGeometries(); verticalScrollBar()->setValue(vScrollBarPos); } - TrackModel* WTrackTableView::getTrackModel() const { TrackModel* trackModel = dynamic_cast(model()); return trackModel; @@ -1351,18 +721,7 @@ void WTrackTableView::loadSelectedTrackToGroup(QString group, bool play) { loadSelectionToGroup(group, play); } -void WTrackTableView::slotAddToAutoDJBottom() { - // append to auto DJ - addToAutoDJ(PlaylistDAO::AutoDJSendLoc::BOTTOM); -} -void WTrackTableView::slotAddToAutoDJTop() { - addToAutoDJ(PlaylistDAO::AutoDJSendLoc::TOP); -} - -void WTrackTableView::slotAddToAutoDJReplace() { - addToAutoDJ(PlaylistDAO::AutoDJSendLoc::REPLACE); -} QList WTrackTableView::getSelectedTrackIds() const { QList trackIds; @@ -1438,313 +797,6 @@ void WTrackTableView::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { playlistDao.addTracksToAutoDJQueue(trackIds, loc); } -void WTrackTableView::slotImportTrackMetadataFromFileTags() { - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - // The user has explicitly requested to reload metadata from the file - // to override the information within Mixxx! Custom cover art must be - // reloaded separately. - SoundSourceProxy(pTrack).updateTrackFromSource( - SoundSourceProxy::ImportTrackMetadataMode::Again); - } - } -} - -void WTrackTableView::slotExportTrackMetadataIntoFileTags() { - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - return; - } - - TrackModel* pTrackModel = getTrackModel(); - if (!pTrackModel) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.isEmpty()) { - return; - } - - mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = pTrackModel->getTrack(index); - if (pTrack) { - // Export of metadata is deferred until all references to the - // corresponding track object have been dropped. Otherwise - // writing to files that are still used for playback might - // cause crashes or at least audible glitches! - mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession(); - pTrack->markForMetadataExport(); - } - } -} - -void WTrackTableView::slotUpdateExternalTrackCollection( - ExternalTrackCollection* externalTrackCollection) { - VERIFY_OR_DEBUG_ASSERT(externalTrackCollection) { - return; - } - - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { - return; - } - - TrackModel* pTrackModel = getTrackModel(); - if (!pTrackModel) { - return; - } - - const QModelIndexList indices = selectionModel()->selectedRows(); - if (indices.isEmpty()) { - return; - } - - QList trackRefs; - trackRefs.reserve(indices.size()); - for (const QModelIndex& index : indices) { - trackRefs.append( - TrackRef::fromFileInfo( - pTrackModel->getTrackLocation(index), - pTrackModel->getTrackId(index))); - } - - externalTrackCollection->updateTracks(std::move(trackRefs)); -} - -//slot for reset played count, sets count to 0 of one or more tracks -void WTrackTableView::slotClearPlayCount() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->resetPlayCounter(); - } - } -} -/* -void WTrackTableView::slotPopulatePlaylistMenu() { - // The user may open the Playlist submenu, move their cursor away, then - // return to the Playlist submenu before exiting the track context menu. - // Avoid querying the database multiple times in that case. - if (m_bPlaylistMenuLoaded) { - return; - } - m_pPlaylistMenu->clear(); - PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); - QMap playlists; - int numPlaylists = playlistDao.playlistCount(); - for (int i = 0; i < numPlaylists; ++i) { - int iPlaylistId = playlistDao.getPlaylistId(i); - playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); - } - QMapIterator it(playlists); - while (it.hasNext()) { - it.next(); - if (!playlistDao.isHidden(it.value())) { - // No leak because making the menu the parent means they will be - // auto-deleted - auto pAction = new QAction(it.key(), m_pPlaylistMenu); - bool locked = playlistDao.isPlaylistLocked(it.value()); - pAction->setEnabled(!locked); - m_pPlaylistMenu->addAction(pAction); - int iPlaylistId = it.value(); - connect(pAction, &QAction::triggered, - this, [this, iPlaylistId] { addSelectionToPlaylist(iPlaylistId); }); - - } - } - m_pPlaylistMenu->addSeparator(); - QAction* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); - m_pPlaylistMenu->addAction(newPlaylistAction); - connect(newPlaylistAction, &QAction::triggered, - this, [this] { addSelectionToPlaylist(-1); }); - m_bPlaylistMenuLoaded = true; -} -*/ - -void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { - const QList trackIds = getSelectedTrackIds(); - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for playlist"; - return; - } - - PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); - - if (iPlaylistId == -1) { // i.e. a new playlist is suppose to be created - QString name; - bool validNameGiven = false; - - do { - bool ok = false; - name = QInputDialog::getText(nullptr, - tr("Create New Playlist"), - tr("Enter name for new playlist:"), - QLineEdit::Normal, - tr("New Playlist"), - &ok).trimmed(); - if (!ok) { - return; - } - if (playlistDao.getPlaylistIdFromName(name) != -1) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("A playlist by that name already exists.")); - } else if (name.isEmpty()) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("A playlist cannot have a blank name.")); - } else { - validNameGiven = true; - } - } while (!validNameGiven); - iPlaylistId = playlistDao.createPlaylist(name);//-1 is changed to the new playlist ID return from the DAO - if (iPlaylistId == -1) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("An unknown error occurred while creating playlist: ") - +name); - return; - } - } - - // TODO(XXX): Care whether the append succeeded. - m_pTrackCollectionManager->unhideTracks(trackIds); - playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); -} -/* -void WTrackTableView::slotPopulateCrateMenu() { - // The user may open the Crate submenu, move their cursor away, then - // return to the Crate submenu before exiting the track context menu. - // Avoid querying the database multiple times in that case. - if (m_bCrateMenuLoaded) { - return; - } - m_pCrateMenu->clear(); - const QList trackIds = getSelectedTrackIds(); - - CrateSummarySelectResult allCrates(m_pTrackCollectionManager->internalCollection()->crates().selectCratesWithTrackCount(trackIds)); - - CrateSummary crate; - while (allCrates.populateNext(&crate)) { - auto pAction = make_parented(m_pCrateMenu); - auto pCheckBox = make_parented(m_pCrateMenu); - - pCheckBox->setText(crate.getName()); - pCheckBox->setProperty("crateId", - QVariant::fromValue(crate.getId())); - pCheckBox->setEnabled(!crate.isLocked()); - // Strangely, the normal styling of QActions does not automatically - // apply to QWidgetActions. The :selected pseudo-state unfortunately - // does not work with QWidgetAction. :hover works for selecting items - // with the mouse, but not with the keyboard. :focus works for the - // keyboard but with the mouse, the last clicked item keeps the style - // after the mouse cursor is moved to hover over another item. - - // ronso0 Disabling this stylesheet allows to override the OS style - // of the :hover and :focus state. -// pCheckBox->setStyleSheet( -// QString("QCheckBox {color: %1;}").arg( -// pCheckBox->palette().text().color().name()) + "\n" + -// QString("QCheckBox:hover {background-color: %1;}").arg( -// pCheckBox->palette().highlight().color().name())); - pAction->setEnabled(!crate.isLocked()); - pAction->setDefaultWidget(pCheckBox.get()); - - if (crate.getTrackCount() == 0) { - pCheckBox->setChecked(false); - } else if (crate.getTrackCount() == (uint)trackIds.length()) { - pCheckBox->setChecked(true); - } else { - pCheckBox->setTristate(true); - pCheckBox->setCheckState(Qt::PartiallyChecked); - } - - m_pCrateMenu->addAction(pAction.get()); - connect(pAction.get(), &QAction::triggered, - this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); - connect(pCheckBox.get(), &QCheckBox::stateChanged, - this, [this, pCheckBox{pCheckBox.get()}] { updateSelectionCrates(pCheckBox); }); - - } - m_pCrateMenu->addSeparator(); - QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); - m_pCrateMenu->addAction(newCrateAction); - connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); - m_bCrateMenuLoaded = true; -} - */ - -void WTrackTableView::updateSelectionCrates(QWidget* pWidget) { - auto pCheckBox = qobject_cast(pWidget); - VERIFY_OR_DEBUG_ASSERT(pCheckBox) { - qWarning() << "crateId is not of CrateId type"; - return; - } - CrateId crateId = pCheckBox->property("crateId").value(); - - const QList trackIds = getSelectedTrackIds(); - - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for crate"; - return; - } - - // we need to disable tristate again as the mixed state will now be gone and can't be brought back - pCheckBox->setTristate(false); - if(!pCheckBox->isChecked()) { - if (crateId.isValid()) { - m_pTrackCollectionManager->internalCollection()->removeCrateTracks(crateId, trackIds); - } - } else { - if (!crateId.isValid()) { // i.e. a new crate is suppose to be created - crateId = CrateFeatureHelper( - m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); - } - if (crateId.isValid()) { - m_pTrackCollectionManager->unhideTracks(trackIds); - m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); - } - } -} - -void WTrackTableView::addSelectionToNewCrate() { - const QList trackIds = getSelectedTrackIds(); - - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for crate"; - return; - } - - CrateId crateId = CrateFeatureHelper( - m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); - - if (crateId.isValid()) { - m_pTrackCollectionManager->unhideTracks(trackIds); - m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); - } - -} void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder) { TrackModel* trackModel = getTrackModel(); @@ -1828,258 +880,8 @@ void WTrackTableView::applySorting() { doSortByColumn(sortColumn, sortOrder); } -void WTrackTableView::slotLockBpm() { - lockBpm(true); -} - -void WTrackTableView::slotUnlockBpm() { - lockBpm(false); -} - -void WTrackTableView::slotScaleBpm(int scale) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack && !pTrack->isBpmLocked()) { - BeatsPointer pBeats = pTrack->getBeats(); - if (pBeats) { - pBeats->scale(static_cast(scale)); - } - } - } -} - -void WTrackTableView::lockBpm(bool lock) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setBpmLocked(lock); - } - } -} - -void WTrackTableView::slotColorPicked(mixxx::RgbColor::optional_t color) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setColor(color); - } - } - - m_pMenu->hide(); -} - -void WTrackTableView::slotClearBeats() { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - const QModelIndexList selectedTrackIndices = selectionModel()->selectedRows(); - // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack && !pTrack->isBpmLocked()) { - pTrack->setBeats(BeatsPointer()); - } - } -} - -void WTrackTableView::slotClearMainCue() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::MainCue); - } - } -} -void WTrackTableView::slotClearHotCues() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::HotCue); - } - } -} - -void WTrackTableView::slotClearIntroCue() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Intro); - } - } -} - -void WTrackTableView::slotClearOutroCue() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Outro); - } - } -} - -void WTrackTableView::slotClearLoop() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Loop); - } - } -} - -void WTrackTableView::slotClearKey() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->resetKeys(); - } - } -} - -void WTrackTableView::slotClearReplayGain() { - const QModelIndexList indices = selectionModel()->selectedRows(); - TrackModel* trackModel = getTrackModel(); - - if (trackModel == nullptr) { - return; - } - - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setReplayGain(mixxx::ReplayGain()); - } - } -} - -void WTrackTableView::slotClearWaveform() { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - - AnalysisDao& analysisDao = m_pTrackCollectionManager->internalCollection()->getAnalysisDAO(); - const QModelIndexList indices = selectionModel()->selectedRows(); - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (!pTrack) { - continue; - } - analysisDao.deleteAnalysesForTrack(pTrack->getId()); - pTrack->setWaveform(WaveformPointer()); - pTrack->setWaveformSummary(WaveformPointer()); - } -} - -void WTrackTableView::slotClearAllMetadata() { - slotClearBeats(); - slotClearMainCue(); - slotClearHotCues(); - slotClearIntroCue(); - slotClearOutroCue(); - slotClearLoop(); - slotClearKey(); - slotClearReplayGain(); - slotClearWaveform(); -} - -void WTrackTableView::slotCoverInfoSelected(const CoverInfoRelative& coverInfo) { - TrackModel* trackModel = getTrackModel(); - if (trackModel == nullptr) { - return; - } - const QModelIndexList selection = selectionModel()->selectedRows(); - for (const QModelIndex& index : selection) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setCoverInfo(coverInfo); - } - } -} - -void WTrackTableView::slotReloadCoverArt() { - TrackModel* trackModel = getTrackModel(); - if (!trackModel) { - return; - } - const QModelIndexList selection = selectionModel()->selectedRows(); - if (selection.isEmpty()) { - return; - } - QList selectedTracks; - selectedTracks.reserve(selection.size()); - for (const QModelIndex& index : selection) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - selectedTracks.append(pTrack); - } - } - guessTrackCoverInfoConcurrently(selectedTracks); -} void WTrackTableView::slotSortingChanged(int headerSection, Qt::SortOrder order) { diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index ce312d98e5f3..1590876a17be 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -18,7 +18,6 @@ class ControlProxy; class DlgTagFetcher; class DlgTrackInfo; class TrackCollectionManager; -class WCoverArtMenu; class ExternalTrackCollection; @@ -52,70 +51,29 @@ class WTrackTableView : public WLibraryTableView { void slotMouseDoubleClicked(const QModelIndex &); void slotUnhide(); void slotPurge(); - void slotAddToAutoDJBottom() override; - void slotAddToAutoDJTop() override; - void slotAddToAutoDJReplace() override; + private slots: - void slotRemove(); - void slotHide(); - void slotShowTrackInfo(); - void slotShowDlgTagFetcher(); - void slotNextTrackInfo(); - void slotNextDlgTagFetcher(); - void slotPrevTrackInfo(); - void slotPrevDlgTagFetcher(); - void slotShowTrackInTagFetcher(TrackPointer track); - void slotImportTrackMetadataFromFileTags(); - void slotExportTrackMetadataIntoFileTags(); - void slotUpdateExternalTrackCollection(ExternalTrackCollection*); - //void slotPopulatePlaylistMenu(); - void addSelectionToPlaylist(int iPlaylistId); - void updateSelectionCrates(QWidget* qc); - //void slotPopulateCrateMenu(); - void addSelectionToNewCrate(); + void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection, Qt::SortOrder sortOrder); void applySortingIfVisible(); void applySorting(); - void slotLockBpm(); - void slotUnlockBpm(); - void slotScaleBpm(int); - void slotColorPicked(mixxx::RgbColor::optional_t color); - - void slotClearBeats(); - void slotClearPlayCount(); - void slotClearMainCue(); - void slotClearHotCues(); - void slotClearIntroCue(); - void slotClearOutroCue(); - void slotClearLoop(); - void slotClearKey(); - void slotClearReplayGain(); - void slotClearWaveform(); - void slotClearAllMetadata(); // Signalled 20 times per second (every 50ms) by GuiTick. void slotGuiTick50ms(double); void slotScrollValueChanged(int); - void slotCoverInfoSelected(const CoverInfoRelative& coverInfo); - void slotReloadCoverArt(); - - void slotTrackInfoClosed(); - void slotTagFetcherClosed(); +// +// void slotTrackInfoClosed(); +// void slotTagFetcherClosed(); void slotSortingChanged(int headerSection, Qt::SortOrder order); void keyNotationChanged(); private: - void createActions(); - void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc); - void showTrackInfo(QModelIndex index); - void showDlgTagFetcher(QModelIndex index); void dragMoveEvent(QDragMoveEvent * event) override; void dragEnterEvent(QDragEnterEvent * event) override; void dropEvent(QDropEvent * event) override; - void lockBpm(bool lock); void enableCachedOnly(); void selectionChanged(const QItemSelection &selected, @@ -133,110 +91,16 @@ class WTrackTableView : public WLibraryTableView { TrackCollectionManager* const m_pTrackCollectionManager; - QScopedPointer m_pTrackInfo; - QScopedPointer m_pTagFetcher; - - QModelIndex currentTrackInfoIndex; - - ControlProxy* m_pNumSamplers; - ControlProxy* m_pNumDecks; - ControlProxy* m_pNumPreviewDecks; - // Context menu container WTrackMenu *m_pMenu; -// // Context menu machinery -// WTrackMenu *m_pMenu; -// -// QMenu *m_pLoadToMenu; -// QMenu *m_pDeckMenu; -// QMenu *m_pSamplerMenu; -// -// QMenu *m_pPlaylistMenu; -// QMenu *m_pCrateMenu; -// QMenu *m_pMetadataMenu; -// QMenu *m_pMetadataUpdateExternalCollectionsMenu; -// QMenu *m_pClearMetadataMenu; -// QMenu *m_pBPMMenu; -// QMenu *m_pColorMenu; -// -// -// WCoverArtMenu* m_pCoverMenu; -// -// // Reload Track Metadata Action: -// QAction *m_pImportMetadataFromFileAct; -// QAction *m_pImportMetadataFromMusicBrainzAct; -// -// // Save Track Metadata Action: -// QAction *m_pExportMetadataAct; -// -// // Load Track to PreviewDeck -// QAction* m_pAddToPreviewDeck; -// -// // Send to Auto-DJ Action -// QAction *m_pAutoDJBottomAct; -// QAction *m_pAutoDJTopAct; -// QAction *m_pAutoDJReplaceAct; -// -// // Remove from table -// QAction *m_pRemoveAct; -// QAction *m_pRemovePlaylistAct; -// QAction *m_pRemoveCrateAct; -// QAction *m_pHideAct; -// QAction *m_pUnhideAct; -// QAction *m_pPurgeAct; -// -// // Show track-editor action -// QAction *m_pPropertiesAct; -// QAction *m_pFileBrowserAct; -// -// // BPM feature -// QAction *m_pBpmLockAction; -// QAction *m_pBpmUnlockAction; -// QAction *m_pBpmDoubleAction; -// QAction *m_pBpmHalveAction; -// QAction *m_pBpmTwoThirdsAction; -// QAction *m_pBpmThreeFourthsAction; -// QAction *m_pBpmFourThirdsAction; -// QAction *m_pBpmThreeHalvesAction; -// -// // Track color -// WColorPickerAction *m_pColorPickerAction; -// -// // Clear track metadata actions -// QAction* m_pClearBeatsAction; -// QAction* m_pClearPlayCountAction; -// QAction* m_pClearMainCueAction; -// QAction* m_pClearHotCuesAction; -// QAction* m_pClearIntroCueAction; -// QAction* m_pClearOutroCueAction; -// QAction* m_pClearLoopAction; -// QAction* m_pClearWaveformAction; -// QAction* m_pClearKeyAction; -// QAction* m_pClearReplayGainAction; -// QAction* m_pClearAllMetadataAction; - - struct UpdateExternalTrackCollection { - QPointer externalTrackCollection; - QAction* action; - }; - QList m_updateInExternalTrackCollections; bool m_sorting; - // Column numbers - int m_iCoverSourceColumn; // cover art source - int m_iCoverTypeColumn; // cover art type - int m_iCoverLocationColumn; // cover art location - int m_iCoverHashColumn; // cover art hash - int m_iCoverColumn; // visible cover art - int m_iTrackLocationColumn; - // Control the delay to load a cover art. mixxx::Duration m_lastUserAction; bool m_selectionChangedSinceLastGuiTick; bool m_loadCachedOnly; - bool m_bPlaylistMenuLoaded; - bool m_bCrateMenuLoaded; + ControlProxy* m_pCOTGuiTick; ControlProxy* m_pKeyNotation; ControlProxy* m_pSortColumn; From 9db6ad21029d61642108280c22156271d260165c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 2 Apr 2020 16:41:06 +0200 Subject: [PATCH 154/393] Use the real hotcue colors when coloring the icon --- src/preferences/dialog/dlgprefcolors.cpp | 12 +++++++----- src/preferences/dialog/dlgprefcolors.h | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/preferences/dialog/dlgprefcolors.cpp b/src/preferences/dialog/dlgprefcolors.cpp index 7aa7e350da83..a3e09d090d97 100644 --- a/src/preferences/dialog/dlgprefcolors.cpp +++ b/src/preferences/dialog/dlgprefcolors.cpp @@ -172,16 +172,18 @@ QPixmap DlgPrefColors::drawPalettePreview(const QString& paletteName) { return QPixmap(); } -QIcon DlgPrefColors::drawPaletteIcon(const QString& paletteName) { +QIcon DlgPrefColors::drawHotcueColorByPaletteIcon(const QString& paletteName) { QPixmap pixmap(16, 16); QPainter painter(&pixmap); pixmap.fill(Qt::black); ColorPalette palette = m_colorPaletteSettings.getHotcueColorPalette(paletteName); if (paletteName == palette.getName()) { - for (int i = 0; i < palette.size() && i < 4; ++i) { - painter.setPen(mixxx::RgbColor::toQColor(palette.at(i))); - painter.setBrush(mixxx::RgbColor::toQColor(palette.at(i))); + for (int i = 0; i < 4; ++i) { + QColor color = mixxx::RgbColor::toQColor( + palette.colorForHotcueIndex(i)); + painter.setPen(color); + painter.setBrush(color); painter.drawRect(0, i * 4, 16, 4); } return QIcon(pixmap); @@ -197,7 +199,7 @@ void DlgPrefColors::slotHotcuePaletteChanged(const QString& paletteName) { comboBoxHotcueDefaultColor->clear(); comboBoxHotcueDefaultColor->addItem(tr("By hotcue number"), -1); - QIcon icon = drawPaletteIcon(paletteName); + QIcon icon = drawHotcueColorByPaletteIcon(paletteName); comboBoxHotcueDefaultColor->setItemIcon(0, icon); QPixmap pixmap(16, 16); diff --git a/src/preferences/dialog/dlgprefcolors.h b/src/preferences/dialog/dlgprefcolors.h index caec1a0d7ab2..9ef61679cf7e 100644 --- a/src/preferences/dialog/dlgprefcolors.h +++ b/src/preferences/dialog/dlgprefcolors.h @@ -38,7 +38,7 @@ class DlgPrefColors : public DlgPreferencePage, public Ui::DlgPrefColorsDlg { const QString& paletteName, bool editHotcuePalette); QPixmap drawPalettePreview(const QString& paletteName); - QIcon drawPaletteIcon(const QString& paletteName); + QIcon drawHotcueColorByPaletteIcon(const QString& paletteName); void restoreComboBoxes( const QString& hotcueColors, const QString& trackColors, From 458a2be4dd61893d284719c801fd34c1b8ae9b71 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Fri, 3 Apr 2020 00:58:24 +0530 Subject: [PATCH 155/393] widget/wtrackmenu: fix multiple segfaults --- src/widget/wtrackmenu.cpp | 191 ++++++++++++--------------------- src/widget/wtrackmenu.h | 9 +- src/widget/wtracktableview.cpp | 5 +- 3 files changed, 78 insertions(+), 127 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index e8e15ac56369..226258590b4e 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -47,12 +47,11 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollec m_pTrackCollectionManager(pTrackCollectionManager), m_bPlaylistMenuLoaded(false), m_bCrateMenuLoaded(false), - m_iCoverSourceColumn(-1), - m_iCoverTypeColumn(-1), - m_iCoverLocationColumn(-1), - m_iCoverHashColumn(-1), - m_iCoverColumn(-1) { - std::cout << "Constructor started (stdout)\n"; + m_iCoverSourceColumn(-1), + m_iCoverTypeColumn(-1), + m_iCoverLocationColumn(-1), + m_iCoverHashColumn(-1), + m_iCoverColumn(-1) { m_pNumSamplers = new ControlProxy( "[Master]", "num_samplers", this); m_pNumDecks = new ControlProxy( @@ -60,6 +59,14 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollec m_pNumPreviewDecks = new ControlProxy( "[Master]", "num_preview_decks", this); + constructMenus(); +} + +WTrackMenu::~WTrackMenu() { + // Explicit destruction of heap allocated objects would cause segfault. +} + +void WTrackMenu::constructMenus() { m_pLoadToMenu = new QMenu(this); m_pLoadToMenu->setTitle(tr("Load to")); m_pDeckMenu = new QMenu(this); @@ -99,63 +106,11 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollec this, SLOT(slotCoverInfoSelected(const CoverInfoRelative&))); connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), this, SLOT(slotReloadCoverArt())); - createActions(); - std::cout << "constructor end\n"; } -WTrackMenu::~WTrackMenu() { - std::cout << "Destructor start\n"; - delete m_pImportMetadataFromFileAct; - delete m_pImportMetadataFromMusicBrainzAct; - delete m_pExportMetadataAct; - delete m_pAddToPreviewDeck; - delete m_pAutoDJBottomAct; - delete m_pAutoDJTopAct; - delete m_pAutoDJReplaceAct; - delete m_pRemoveAct; - delete m_pRemovePlaylistAct; - delete m_pRemoveCrateAct; - delete m_pHideAct; - delete m_pUnhideAct; - delete m_pPropertiesAct; - delete m_pLoadToMenu; - delete m_pDeckMenu; - delete m_pSamplerMenu; - delete m_pPlaylistMenu; - delete m_pCrateMenu; - delete m_pMetadataMenu; - delete m_pClearMetadataMenu; - delete m_pCoverMenu; - delete m_pBpmLockAction; - delete m_pBpmUnlockAction; - delete m_pBpmDoubleAction; - delete m_pBpmHalveAction; - delete m_pBpmTwoThirdsAction; - delete m_pBpmThreeFourthsAction; - delete m_pBpmFourThirdsAction; - delete m_pBpmThreeHalvesAction; - delete m_pBPMMenu; - delete m_pColorMenu; - delete m_pClearBeatsAction; - delete m_pClearPlayCountAction; - delete m_pClearMainCueAction; - delete m_pClearHotCuesAction; - delete m_pClearIntroCueAction; - delete m_pClearOutroCueAction; - delete m_pClearLoopAction; - delete m_pClearReplayGainAction; - delete m_pClearWaveformAction; - delete m_pClearKeyAction; - delete m_pClearAllMetadataAction; - delete m_pPurgeAct; - delete m_pFileBrowserAct; - delete m_pTrackCollectionManager; - delete m_pTrackModel; - std::cout << "Destructor end\n"; -} + void WTrackMenu::createActions() { - std::cout << "create actions\n"; DEBUG_ASSERT(this); DEBUG_ASSERT(m_pSamplerMenu); @@ -313,60 +268,25 @@ void WTrackMenu::createActions() { &WTrackMenu::slotColorPicked); } -void WTrackMenu::setTrackIds(TrackIdList trackIdList) { - // Clean all forms of track store - clearTrackSelection(); - - m_pTrackIdList = std::move(trackIdList); - // Store the track pointers at each initialization of track ids. - for (const auto trackId : m_pTrackIdList) { - TrackPointer trackPointer = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); - m_pTrackPointerList.push_back(trackPointer); - } - // Add actions to menu - setupActions(); -} - -void WTrackMenu::setTrackId(TrackId trackId) { - // Create a QList of single track to maintain common functions - // for single and multi track selection. - TrackIdList singleItemTrackIdList; - singleItemTrackIdList.push_back(trackId); - // Use setTrackIds to set a list of single element. - setTrackIds(singleItemTrackIdList); -} - -void WTrackMenu::setTrackIndexList(QModelIndexList indexList) { - std::cout << "setindexlist\n"; - clearTrackSelection(); - - m_pSelectedTrackIndices = std::move(indexList); - for (auto index : m_pSelectedTrackIndices) { - TrackPointer trackPointer = m_pTrackModel->getTrack(index); - m_pTrackPointerList.push_back(trackPointer); - TrackId trackId = trackPointer->getId(); - m_pTrackIdList.push_back(trackId); - } - - std::cout << "After initialization of indices: \n"; - std::cout << "size: " << m_pSelectedTrackIndices.size() << std::endl; - for (auto iter : m_pSelectedTrackIndices) { - std::cout << "printing index: "; - std::cout << iter.row() << " " << iter.internalId() << " " << iter.isValid() << std::endl; - } - std::cout << std::endl; - for (auto iter : m_pTrackIdList) { - std::cout << iter << " "; - } - std::cout << std::endl; - std::cout << "setindexlist " << m_pTrackPointerList.size() << " " << m_pSelectedTrackIndices.size() << std::endl; - setupActions(); +void WTrackMenu::teardownActions() +{ + clear(); + m_pLoadToMenu->clear(); + m_pDeckMenu->clear(); + m_pSamplerMenu->clear(); + m_pPlaylistMenu->clear(); + m_pCrateMenu->clear(); + m_pMetadataMenu->clear(); + m_pMetadataUpdateExternalCollectionsMenu->clear(); + m_pClearMetadataMenu->clear(); + m_pBPMMenu->clear(); + m_pColorMenu->clear(); + m_pCoverMenu->clear(); } void WTrackMenu::setupActions() { - std::cout << "Setup Actions:"; - std::cout << m_pTrackModel; - std::cout << std::endl; + teardownActions(); + createActions(); QModelIndexList indices = m_pSelectedTrackIndices; @@ -671,6 +591,7 @@ void WTrackMenu::setupActions() { m_pPurgeAct->setEnabled(!locked); addAction(m_pPurgeAct); } + addAction(m_pFileBrowserAct); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { @@ -680,14 +601,49 @@ void WTrackMenu::setupActions() { } } +void WTrackMenu::setTrackIds(TrackIdList trackIdList) { + // Clean all forms of track store + clearTrackSelection(); + + m_pTrackIdList = std::move(trackIdList); + // Store the track pointers at each initialization of track ids. + for (const auto trackId : m_pTrackIdList) { + TrackPointer trackPointer = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); + m_pTrackPointerList.push_back(trackPointer); + } + // Add actions to menu + setupActions(); +} + +void WTrackMenu::setTrackId(TrackId trackId) { + // Create a QList of single track to maintain common functions + // for single and multi track selection. + TrackIdList singleItemTrackIdList; + singleItemTrackIdList.push_back(trackId); + // Use setTrackIds to set a list of single element. + setTrackIds(singleItemTrackIdList); +} + +void WTrackMenu::setTrackIndexList(QModelIndexList indexList) { + clearTrackSelection(); + + m_pSelectedTrackIndices = std::move(indexList); + for (auto index : m_pSelectedTrackIndices) { + TrackPointer trackPointer = m_pTrackModel->getTrack(index); + m_pTrackPointerList.push_back(trackPointer); + TrackId trackId = trackPointer->getId(); + m_pTrackIdList.push_back(trackId); + } + + setupActions(); +} + + void WTrackMenu::slotOpenInFileBrowser() { - std::cout << "slotopeninfilebrowser " << m_pTrackPointerList.size() << " " << m_pSelectedTrackIndices.size() << std::endl; - DEBUG_ASSERT(!getTrackPointerList().empty()); TrackPointerList trackPointerList = getTrackPointerList(); QStringList locations; for (const TrackPointer& trackPointer : trackPointerList) { locations << trackPointer->getLocation(); - std::cout << trackPointer->getLocation().toStdString() << std::endl; } mixxx::DesktopHelper::openInFileBrowser(locations); } @@ -1016,7 +972,6 @@ void WTrackMenu::addSelectionToNewCrate() { } void WTrackMenu::setTrackModel(TrackModel* trackModel) { - std::cout << "settrackmodel\n"; m_pTrackModel = trackModel; // Move this to another function @@ -1265,6 +1220,7 @@ void WTrackMenu::slotClearHotCues() { void WTrackMenu::slotClearAllMetadata() { slotClearBeats(); + slotClearPlayCount(); slotClearMainCue(); slotClearHotCues(); slotClearIntroCue(); @@ -1278,14 +1234,7 @@ void WTrackMenu::slotClearAllMetadata() { void WTrackMenu::slotRemove() { - std::cout << "Slot remove\n"; - if (m_pTrackModel->getCapabilities() == TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST) { - std::cout << "Can remove from playlist\n"; - }if (m_pTrackModel->getCapabilities() == TrackModel::TRACKMODELCAPS_REMOVE_CRATE) { - std::cout << "Can remove from crate\n"; - } QModelIndexList indices = m_pSelectedTrackIndices; - std::cout << "Removal size: " << indices.size() << std::endl; if (!indices.empty()) { TrackModel* trackModel = m_pTrackModel; if (trackModel) { diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index 2b121f5bed91..8eed9e2608dc 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -8,9 +8,9 @@ #include #include "library/trackcollectionmanager.h" #include "library/trackmodel.h" +#include "track/track.h" #include "library/dao/playlistdao.h" - - +#include "preferences/usersettings.h" #include "widget/wcoverartmenu.h" #include "widget/wcolorpickeraction.h" @@ -18,6 +18,7 @@ typedef QList TrackIdList; typedef QList TrackPointerList; +class ControlProxy; class WCoverArtMenu; class TrackCollectionManager; @@ -88,6 +89,7 @@ class WTrackMenu : public QMenu { void slotPurge(); private: + void constructMenus(); void createActions(); void setupActions(); void trackIdsToTrackPointers(); @@ -104,6 +106,8 @@ class WTrackMenu : public QMenu { void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc); + void teardownActions(); + void loadSelectionToGroup(QString group, bool play); void clearTrackSelection(); @@ -222,5 +226,4 @@ class WTrackMenu : public QMenu { - #endif // WTRACKMENU_H diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 7b957304bc72..0aa5f9e052fe 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -403,12 +403,11 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { // Update track indices in context menu QModelIndexList indices = selectionModel()->selectedRows(); - std::cout << "passing this many indices: " << indices.size() << std::endl; - DEBUG_ASSERT(!indices.empty()); - m_pMenu->setTrackIndexList(selectionModel()->selectedRows()); + m_pMenu->setTrackIndexList(indices); //Create the right-click menu m_pMenu->popup(event->globalPos()); + } void WTrackTableView::onSearch(const QString& text) { From a88c5b0766c5aacc0c7a80b3cb44146e8d6c3bbd Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 2 Apr 2020 23:35:02 +0200 Subject: [PATCH 156/393] engine/enginedeck: Fix broken vinyl passthrough In commit 0f5d4b358f023b7431009e13e6b9370794c759ab we connected a valueChangeRequest that sets the value on the same object that we expect to send the valueChanged signal. If a value is set for a control object, that object doesn't emit the valueChanged signal though - it emits the valueChangedFromEngine signal instead. --- src/engine/enginedeck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/enginedeck.cpp b/src/engine/enginedeck.cpp index 579ad6b3c040..b017974a0c98 100644 --- a/src/engine/enginedeck.cpp +++ b/src/engine/enginedeck.cpp @@ -50,7 +50,7 @@ EngineDeck::EngineDeck(const ChannelHandleAndGroup& handle_group, Qt::DirectConnection); // Set up passthrough toggle button - connect(m_pPassing, SIGNAL(valueChanged(double)), + connect(m_pPassing, SIGNAL(valueChangedFromEngine(double)), this, SLOT(slotPassingToggle(double)), Qt::DirectConnection); From 87509272cab4f6ceed35ed450e9e349efe5dced8 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Fri, 3 Apr 2020 03:28:14 +0530 Subject: [PATCH 157/393] widget/wtrackmenu: connect signal from wtrackmenu to wtracktableview --- src/widget/wtrackmenu.cpp | 63 ++++++++++++++++------------------ src/widget/wtrackmenu.h | 11 ++++-- src/widget/wtracktableview.cpp | 2 ++ src/widget/wtracktableview.h | 4 +-- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 226258590b4e..31c8a6d6c85a 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -22,6 +22,7 @@ #include "library/dlgtrackinfo.h" #include "library/dlgtrackmetadataexport.h" #include "library/externaltrackcollection.h" +#include "library/library.h" #include "library/librarytablemodel.h" #include "library/trackcollection.h" #include "library/trackmodel.h" @@ -30,7 +31,8 @@ #include "sources/soundsourceproxy.h" #include "util/desktophelper.h" #include "widget/wlibrarytableview.h" - +#include "track/track.h" +#include "track/trackref.h" #include "util/desktophelper.h" #include "util/parented_ptr.h" #include "waveform/guitick.h" @@ -181,8 +183,8 @@ void WTrackMenu::createActions() { m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); // currently there is only one preview deck so just map it here. QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); -// connect(m_pAddToPreviewDeck, &QAction::triggered, -// this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); + connect(m_pAddToPreviewDeck, &QAction::triggered, + this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); // Clear metadata actions @@ -302,10 +304,8 @@ void WTrackMenu::setupActions() { addSeparator(); } - m_pLoadToMenu->clear(); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { int iNumDecks = m_pNumDecks->get(); - m_pDeckMenu->clear(); if (iNumDecks > 0) { for (int i = 1; i <= iNumDecks; ++i) { // PlayerManager::groupForDeck is 0-indexed. @@ -318,8 +318,8 @@ void WTrackMenu::setupActions() { QAction* pAction = new QAction(tr("Deck %1").arg(i), this); pAction->setEnabled(deckEnabled); m_pDeckMenu->addAction(pAction); -// connect(pAction, &QAction::triggered, -// this, [this, deckGroup] { loadSelectionToGroup(deckGroup); }); + connect(pAction, &QAction::triggered, + this, [this, deckGroup] { loadSelectionToGroup(deckGroup); }); } } m_pLoadToMenu->addMenu(m_pDeckMenu); @@ -328,7 +328,6 @@ void WTrackMenu::setupActions() { if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOSAMPLER)) { int iNumSamplers = m_pNumSamplers->get(); if (iNumSamplers > 0) { - m_pSamplerMenu->clear(); for (int i = 1; i <= iNumSamplers; ++i) { // PlayerManager::groupForSampler is 0-indexed. QString samplerGroup = PlayerManager::groupForSampler(i - 1); @@ -338,8 +337,8 @@ void WTrackMenu::setupActions() { QAction* pAction = new QAction(tr("Sampler %1").arg(i), m_pSamplerMenu); pAction->setEnabled(samplerEnabled); m_pSamplerMenu->addAction(pAction); -// connect(pAction, &QAction::triggered, -// this, [this, samplerGroup] {loadSelectionToGroup(samplerGroup); } ); + connect(pAction, &QAction::triggered, + this, [this, samplerGroup] {loadSelectionToGroup(samplerGroup); } ); } m_pLoadToMenu->addMenu(m_pSamplerMenu); @@ -384,8 +383,6 @@ void WTrackMenu::setupActions() { } addSeparator(); - m_pMetadataMenu->clear(); - m_pMetadataUpdateExternalCollectionsMenu->clear(); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); @@ -421,8 +418,6 @@ void WTrackMenu::setupActions() { m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); } - m_pClearMetadataMenu->clear(); - if (trackModel == nullptr) { return; } @@ -1046,26 +1041,26 @@ void WTrackMenu::slotColorPicked(mixxx::RgbColor::optional_t color) { } void WTrackMenu::loadSelectionToGroup(QString group, bool play) { -// QModelIndexList indices = m_pSelectedTrackIndices; -// if (indices.size() > 0) { -// // If the track load override is disabled, check to see if a track is -// // playing before trying to load it -// if (!(m_pConfig->getValueString( -// ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { -// // TODO(XXX): Check for other than just the first preview deck. -// if (group != "[PreviewDeck1]" && -// ControlObject::get(ConfigKey(group, "play")) > 0.0) { -// return; -// } -// } -// QModelIndex index = indices.at(0); -// TrackModel* trackModel = m_pTrackModel; -// TrackPointer pTrack; -// if (trackModel && -// (pTrack = trackModel->getTrack(index))) { -// emit loadTrackToPlayer(pTrack, group, play); -// } -// } + QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.size() > 0) { + // If the track load override is disabled, check to see if a track is + // playing before trying to load it + if (!(m_pConfig->getValueString( + ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { + // TODO(XXX): Check for other than just the first preview deck. + if (group != "[PreviewDeck1]" && + ControlObject::get(ConfigKey(group, "play")) > 0.0) { + return; + } + } + QModelIndex index = indices.at(0); + TrackModel* trackModel = m_pTrackModel; + TrackPointer pTrack; + if (trackModel && + (pTrack = trackModel->getTrack(index))) { + emit loadTrackToPlayer(pTrack, group, play); + } + } } diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index 8eed9e2608dc..21d81093a674 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -11,6 +11,8 @@ #include "track/track.h" #include "library/dao/playlistdao.h" #include "preferences/usersettings.h" +#include "widget/wlibrarytableview.h" +#include "control/controlproxy.h" #include "widget/wcoverartmenu.h" #include "widget/wcolorpickeraction.h" @@ -20,7 +22,8 @@ typedef QList TrackPointerList; class ControlProxy; class WCoverArtMenu; - +class DlgTagFetcher; +class DlgTrackInfo; class TrackCollectionManager; class ExternalTrackCollection; @@ -87,6 +90,10 @@ class WTrackMenu : public QMenu { void slotUnhide(); void slotPurge(); +public: + signals: + void loadTrackToPlayer(TrackPointer pTrack, QString group, + bool play = false); private: void constructMenus(); @@ -109,7 +116,7 @@ class WTrackMenu : public QMenu { void teardownActions(); - void loadSelectionToGroup(QString group, bool play); + void loadSelectionToGroup(QString group, bool play = false); void clearTrackSelection(); ControlProxy* m_pNumSamplers; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 0aa5f9e052fe..36edd439df56 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -79,6 +79,8 @@ WTrackTableView::WTrackTableView(QWidget * parent, this, SLOT(setFocus())); m_pMenu = new WTrackMenu(this, pConfig, m_pTrackCollectionManager); + // Receive signal from menu and emit from here. + connect(m_pMenu, &WTrackMenu::loadTrackToPlayer, this, &WTrackTableView::loadTrackToPlayer); } WTrackTableView::~WTrackTableView() { diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 1590876a17be..5b773d0b38b2 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -63,9 +63,7 @@ class WTrackTableView : public WLibraryTableView { // Signalled 20 times per second (every 50ms) by GuiTick. void slotGuiTick50ms(double); void slotScrollValueChanged(int); -// -// void slotTrackInfoClosed(); -// void slotTagFetcherClosed(); + void slotSortingChanged(int headerSection, Qt::SortOrder order); void keyNotationChanged(); From 5407844c049da01084ac405a1c27e54b0021f9b8 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 2 Apr 2020 23:58:15 +0200 Subject: [PATCH 158/393] Clarify function usage comments --- src/audio/signalinfo.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio/signalinfo.h b/src/audio/signalinfo.h index df08b1eb6cef..0da1eef6b9c7 100644 --- a/src/audio/signalinfo.h +++ b/src/audio/signalinfo.h @@ -44,7 +44,7 @@ class SignalInfo final { SignalInfo& operator=(const SignalInfo&) = default; // Conversion: #samples / sample offset -> #frames / frame offset - // Only works for sample offsets on frame boundaries! + // Only works for integer sample offsets on frame boundaries! template T samples2frames(T samples) const { DEBUG_ASSERT(getChannelCount().isValid()); @@ -84,7 +84,7 @@ class SignalInfo final { } // Conversion: #samples / sample offset -> second offset - // Only works for sample offsets on frame boundaries! + // Only works for integer sample offsets on frame boundaries! template double samples2secs(T samples) const { return frames2secs(samples2frames(samples)); @@ -98,7 +98,7 @@ class SignalInfo final { } // Conversion: #samples / sample offset -> millisecond offset - // Only works for sample offsets on frame boundaries! + // Only works for integer sample offsets on frame boundaries! template double samples2millis(T samples) const { return frames2millis(samples2frames(samples)); From f212a299ee3d0071589bcc44b41e7ee1ff842fab Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Fri, 3 Apr 2020 04:26:19 +0530 Subject: [PATCH 159/393] widget/wtrackmenu: remove m_pCoverMenu->clear() call --- src/widget/wtrackmenu.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 31c8a6d6c85a..faa68cb3e6d3 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -283,7 +283,6 @@ void WTrackMenu::teardownActions() m_pClearMetadataMenu->clear(); m_pBPMMenu->clear(); m_pColorMenu->clear(); - m_pCoverMenu->clear(); } void WTrackMenu::setupActions() { From 7af708071a0837976d0700ff5d37a60d30350378 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 3 Apr 2020 10:18:54 +0200 Subject: [PATCH 160/393] scripts/line_length: Fix redefinition of variable --- scripts/line_length.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index 1b4ce8546d71..079736d4d70f 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -63,13 +63,13 @@ def get_git_added_lines() -> LineGenerator: def group_lines( lines: LineGenerator, ) -> typing.Generator[FileLines, None, None]: - for filename, lines in itertools.groupby( + for filename, file_lines in itertools.groupby( lines, key=lambda line: line.sourcefile ): grouped_linenumbers = [] start_linenumber = None last_linenumber = None - for line in lines: + for line in file_lines: if None not in (start_linenumber, last_linenumber): if line.number != last_linenumber + 1: grouped_linenumbers.append( From c2bba36d3bf5b4c7a2d9a252a8cc04feaf8ad64d Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Fri, 3 Apr 2020 11:53:06 +0200 Subject: [PATCH 161/393] Delete invalid debug assertion --- src/sources/audiosource.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sources/audiosource.h b/src/sources/audiosource.h index 834e5a435148..5ca3ec752bf2 100644 --- a/src/sources/audiosource.h +++ b/src/sources/audiosource.h @@ -233,7 +233,6 @@ class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSour virtual void close() = 0; const audio::SignalInfo& getSignalInfo() const { - DEBUG_ASSERT(m_signalInfo.isValid()); return m_signalInfo; } From 2a4446134b120d16848854e564b403150162bfbc Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Fri, 3 Apr 2020 11:50:40 +0200 Subject: [PATCH 162/393] Move debug assertion into base class --- src/sources/audiosource.cpp | 1 + src/sources/soundsourcemp3.cpp | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sources/audiosource.cpp b/src/sources/audiosource.cpp index 2dca3e0aa871..91ab264d91ca 100644 --- a/src/sources/audiosource.cpp +++ b/src/sources/audiosource.cpp @@ -28,6 +28,7 @@ AudioSource::OpenResult AudioSource::open( OpenMode mode, const OpenParams& params) { close(); // reopening is not supported + DEBUG_ASSERT(!getSignalInfo().isValid()); OpenResult result; try { diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 892b86a79c1b..b01b37f478ed 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -192,8 +192,6 @@ void SoundSourceMp3::finishDecoding() { SoundSource::OpenResult SoundSourceMp3::tryOpen( OpenMode /*mode*/, const OpenParams& /*config*/) { - DEBUG_ASSERT(!getSignalInfo().isValid()); - DEBUG_ASSERT(!m_file.isOpen()); if (!m_file.open(QIODevice::ReadOnly)) { kLogger.warning() << "Failed to open file:" << m_file.fileName(); From 891292346ecff615dfaeb20c0cb6638508ff4771 Mon Sep 17 00:00:00 2001 From: OsZ <58949409+toszlanyi@users.noreply.github.com> Date: Fri, 3 Apr 2020 11:58:55 +0200 Subject: [PATCH 163/393] Update controller name Co-Authored-By: Jan Holthuis --- res/controllers/Denon-MC7000.midi.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index 42eec7693335..0aa79f6cd8aa 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -1,7 +1,7 @@ - Denon MC7000 beta for MIXXX 2.2.x + Denon MC7000 OsZ Denon MC7000 mapping for testing. Check your Linux Kernel version to get the Audio Interface working - see WIKI page. https://www.mixxx.org/forums/ From b4be4f4138929ff0aebbdaa2bd7a76e3993a463f Mon Sep 17 00:00:00 2001 From: Tobias Date: Fri, 3 Apr 2020 13:07:06 +0200 Subject: [PATCH 164/393] changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ece5b56cb5ce..761be54aa50d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ * Add controller mapping for Native Instruments Traktor Kontrol S2 MK3 #2348 * Add controller mapping for Soundless joyMIDI #2425 * Add controller mapping for Hercules DJControl Inpulse 300 #2465 +* Add controller mapping for Denon MC7000 #2546 ==== 2.2.3 2019-11-24 ==== * Don't make users reconfigure sound hardware when it has not changed #2253 From 57a8048aea3ac6a5cd612aa1ccd1a94828d7db32 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 26 Mar 2020 17:01:17 +0100 Subject: [PATCH 165/393] mixxx: Finalize MixxxMainWindow on destruction --- src/mixxx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixxx.cpp b/src/mixxx.cpp index 09a10ad92539..03c6299d3c35 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -188,6 +188,7 @@ MixxxMainWindow::MixxxMainWindow(QApplication* pApp, const CmdlineArgs& args) } MixxxMainWindow::~MixxxMainWindow() { + finalize(); // SkinLoader depends on Config; delete m_pSkinLoader; } @@ -1387,7 +1388,6 @@ void MixxxMainWindow::closeEvent(QCloseEvent *event) { event->ignore(); return; } - finalize(); QMainWindow::closeEvent(event); } From f469f146e5c43523a27df4b773970502fc3f098a Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 26 Mar 2020 17:02:24 +0100 Subject: [PATCH 166/393] util/singleton: Reset m_instance pointer on destruction This should prevent double-free in case destroy() is called twice. --- src/util/singleton.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/singleton.h b/src/util/singleton.h index 122c4ea08bbd..9398f51507d9 100644 --- a/src/util/singleton.h +++ b/src/util/singleton.h @@ -14,8 +14,8 @@ class Singleton { } static T* instance() { - if (m_instance == NULL) { - qWarning() << "Singleton class has not been created yet, returning NULL"; + if (m_instance == nullptr) { + qWarning() << "Singleton class has not been created yet, returning nullptr"; } return m_instance; } @@ -23,6 +23,7 @@ class Singleton { static void destroy() { if (m_instance) { delete m_instance; + m_instance = nullptr; } } @@ -38,6 +39,7 @@ class Singleton { static T* m_instance; }; -template T* Singleton::m_instance = NULL; +template +T* Singleton::m_instance = nullptr; #endif // SINGLETON_H From 7baacd07f3b2ba9422c193bfb086eb3a6093461a Mon Sep 17 00:00:00 2001 From: ronso0 Date: Fri, 3 Apr 2020 16:59:03 +0200 Subject: [PATCH 167/393] add font: Open Sans bold --- res/fonts/OpenSans-Bold.ttf | Bin 0 -> 224592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/fonts/OpenSans-Bold.ttf diff --git a/res/fonts/OpenSans-Bold.ttf b/res/fonts/OpenSans-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fd79d43bea0293ac1b20e8aca1142627983d2c07 GIT binary patch literal 224592 zcmbTe349bq+CN^^*W8&qlRGn+9E1>Zk;HIi2qAQM&s|SFJ%kcM ztoRa0YJNqpo==B7){*c7z97W@SkL?(1tgw-mGBjZ&?~BEY2ON6wlN#$xK1AGSq zD5=XEgs-#_!XNKjk&?b;$_pWc&;z($J8bNb35hSKj3UIe4+De^oBEj3njH2FA(1*xUL`h==2ehvp%>%NZf8hd%rho_>j8a zE}aO%^E=~u)+jUtC2GrY{us_ zl92eM36q9Tcwf`}2q6&+zFUOhj)t!5_)^Ym4;wrGN;GOT5OOllv016VFM8pQzGbI& zxq3PJY6!<#@xguS)^auAJm@t4J5F5ciajAhZ>sOh+m47dPrUltPqjf1StrvwLw~6)2dGq)H|u z#QC5|Ejb{Dl4;@JZPe3A3a+ga zmJ=drO#Jn3}ACeJ4qc6{t&MC z?*Z;vn?PD`^J4)kp2Mq23Q8w77qJkqbs-ZOzUj8sCbU=c;UtIMuhNtD{xT4_@1o$H z;rtVF#4^kFTg{S_cX1vb$3N=A30MGwsa|W(+QU8Ei zh5A)S1K=UaUvCzVk~}S6bvgMU~%$87_zLY|bd|5$e- z(%oyIF~cdN>;1LrB$=i1*Vg9;8fLt=!_|qCP%jAa1?)|kQ$DrT;Yt7_c zkvS&spl?9#nd~w7zrTh|Z3d4X3-AErdB%5vx!r}ei5wJ^Lc>vi#dLwNiB{4bkn1LL zM%YI-;QXAhi5wK?x4zHhPSmz;lwN7wD1@SJY&|YTwl0#2T95O2ttS;(gRT?mf$x0C zCF2>u#%RyRW;A8=Q}mZ#&jHSIc1^sAcF2zKHgqj;#pWkn0^XtHR2&&A6+y>9E)^L| z2EHef5=K)VMNA|OBHBQc&B9W`DYAm=d^6f`UAPWC!D_}cS73QqzoSHA*A+SXfrO&Z zbftd|+Db?wd#2PM$A??@h89^Yhz=TkV16>>hji`if#hmijlzKS>UjgL^3&+n!#HP zw@1;2g1IvM66rANV&%MA%*L_brU+xf+u%oO9&iPFAkM+HTryLI{;Eqjeg)S~aqxU^;{80gNp`&oCKc{0ABThRL}q9B_x@e)M55urYv(&B6}wNGP7|mxn*r zid-=HfQ^S&qZDQf=^+xz3Rg*T=|K|8H~5MW2fOVeGlfhtljq1#=^UA&&4o^af57|( z?mBz~6rlk&M=MX`hmsNCS>^|ntK5KPCCQVR|By%$)j4FL2zoPK1n?=s$tb8hbQ~ArcpVx}qxe7QU&#u?Kf{&Sgt7IYgG@3Q z|0%wK-=0W~@+3U73eTLb-i*1UNb4ZS<4Lv32AgOjczSa%3Vh@{7g2xCiXt!IYlZ&c zFZDj?R~vkhp`b5tpjrpM9|5|b!#Qk)T8nYPZ_;?+pqbdzxL2jc^&p&2B+)9S8<>3h z^|lDU5ZJx`8b0bYO(OWZ(FdC{UNot`J1&!1X6G)DQNk3m4|u)-op&1Ll*2 z37E!!_pXB1e;|Tl;~D=$uk%-NegX6O1as*G_!nbr$S;#2=yu2&U}e7DDb#V`<(ue# z9(@`h7YA|uI_9<;&&TsL1apHtO4)!l7xLk^(TYACfw7tHhsPhNaWBJ>Rt5bdRl;8x zPsWO8$V?{xOa@UO5Gx@otI-cDn?TL<6Vo$H)%dq6yr54GWFbejQI+*DbrtcJ;6QEBM=AQ`N#CV_SsBqvGJ`Uznts06_LPDjRkjo9= z`65!H&WFC83Er#1oHqf!5uis2=3|09T!3Gc0y&)w`Yr{|PT|>qz{i8v&%6+~~ zKp*^HwZhj-cQZb}uV#KIbjU2|k7U%)NUUy7`(t5#3)i2RSm8g%dhY@m!T*f)9dtAb zTf;d}{$u?nrGc)OpyT~Mn&SU5ANan4b=3jb^W&&rM7|^Qcdu9*43UHWT)# zbt8@sw6^#PIY5?@-HMXM`j=1~>7fY_4`OXQ>>CRcsZO#{+yIrEo z>I!x_T`{hBS9@1Y3>PEC7-K9kVKGrLNil^nwK2ovKDZ;ut*tGst$^GKh@m&ghvZ}0 zhGx*AfOs=~6%gO%LKKLP1LA)GVsaPaCjs$O{8s*D{u4k zu2Hk2Hb=c5bt>vQASO<$)8TX~5T`qH{186|h_?dbm;V6qAV0+B`yj3Z!~_sGx3;!^ zMM&#ctw-=3D2?PDvX=~L?Zqh5x>}wuKPgXb9o0Ilb!h8gGO{(Fkd`y-TFYs9t_<#L zfkl(SeKHiatogd?>yWQzd|ginD_PCVn;l9KVKN%dg|tlgs=D@)N(2T;n&9fAi0iU-->@1HXdCgS*?%MB0`n(RMVM zwx=mHm8OB?GiV2zNwa7+eTe4Jj ztLb1`Lm#HKke8u!7_Fnj=?H!c9YsgeG4v7oC>=}3(eZQwok%Cq$@DQgg-+$Sa---5 zx{+?8&(qEPHhw$ZO1IJNbO(Kr8_kWOKhn$e3jK**rPsjA|EAaJFZ2fem3xHVq`z^i zxM#R$xz*fr+!}5zw~pSTzw+x2i4)HXVYI2%z3@$N!gL6dt(qqEl87>{bm zea*Fv9`qdXhn^l^PtV<+)a2|;IRf_XmvQ$;i$2Vd%_;trYltrxHdgH z?%{~qz=p+4dkm>-EG?_*kst1Id6V1qY7BDYNw`G1E01iHx;LtnM> zmn=JAB13DF^mXpKA=Ool{1Du`gzvFr$-+i+Qe&b!zcF#f*CD{s@WyuT{2q--?5VxW z?~c>^-jK9Wj5E2NOMWGoj{B!8n8$rBL;NjLoatA>E;e%A8)OT!xrmU$aZwWDZ9fT~QrpuBgZwQNvT zBNtcT95n>Uz<;jW^-#FWe76rC@ZT>JpasYQhFva(hNTBQWGGG=XO~s^&Yfgv_+H{k zN%A&wwd~5ffh+cY?8@xGmAkjsx$4|EG=$!H7;Ex-iMd2$fZho_t`;GsMp%J@%xg;Eo}+AlPU|*Rra{6!(Nin>)|P zMQC7P^%z}IrQG6c?a^rK-iRFn|6PqKJ#a5rzsC~BY5%XJoDEXWS>_$p5#zecs@^0S ztrz!naE8B@K{^m`KAzMV+#MVl-(yKt-H68M+VDEa=m=+3xU13Q1vhxzRl~iEMS;!4 zivSHDpa6VTS=GD3-MegH6*$1~TU|k3T%dT@~(o44Ac19jA6yapAld9ZhI( z7U000*BRf9syH=@3B*xa8I$LAc2?1F66g&u8WWv8hUfeHvGWHWiW5Grdtu;d5V!pwe(z4PNff+I)BqVFKc;au0WV-J_h1p3*9Y zB8DD?B7S5j^zl)!cV*T6XZIlsXd*6LRxsyBW@ACpT^usxHuhA`1Gol%J$SiS;Ieax z+TFWi38RGD|3CuBdo>cq?w*Itm^QQo;}|#ew9^FfSA>7b9*>6!K4T8&5_hkt(`5f; z+h;@WN*gJ@D+g7%Ad=3oli^EDKQT&qp@5c{zDf2h)wl|s{hXBV7hTBri{e|OON)b} z`}V1eE-9{yj_+XV7nc#+FVxx^trA+JC0y@Q92H$xOp6N)(bf!0KM}VI8MvLNMn0E+ zmFK121*Zy{3V3%$OuvYX@P5G=_I_q+>}Sd__IuTM#>k}_Da|1L#*CEkD%iKDY+$3bsFCy=IH+n5rB8Y1FJDgbB6~Nc zS5!4RBfY&F>u_L-+!IXlypty<;h%jb*Gztl)yfw;P(C3wh%Y#>Lf((>DdK+dGA5-uz7KWx1jCqI?J~78xt}|34oV3B%_baufTIN#rcqOF0~) zke|o}tO5wd&MH2!{=fcY2DwIO(C@hk+#>FE?n~au_vT0O_53FO5HAZ!!gS%1*jAh` zUX-*_z4W=ttSVGZR6VCUqK;C(qQ0&v*F2*+rIoaq+9ld|v_I>@bpv(Nb?@kI>pSab z>OV2W8lE)lGF%8U2aOGSHRvm2h_R!w$~eQg!}yl*qN$_lDbok$Nb_X#>y|Q0gXM3Q zTh?4_f9qcBPqtLsd|Q*|OO(f(DiJ$Pd9euwDj=$P-=;J6%;gum*LmqUfn+R(Q{ zzY41jdoJ7*J|g^J__^>45o05sj5ru^BeH$uyvQAq*P`4}`B6_qy&QEZIy1U=bZzw1 z=)XsQ7k$gAafUhDIlDMFICnbVbbjD$a{lC$T}G@A(_M32t6bY$ue*-B&bfYw35}T= zvoK~&%u6wUi}}zU_E6=l(u+PVDm7jj?}?eJl2(*bA{g$I&=z+{(BY z;`YQHiTgC}%eY_SJH$U7za{>)_@nXv6aRHW*MzyPS4C+Lg6i(eC4Rm)c!#Cnsx?!;;%3XC*IA{(JIw$-lK%w-0IGwtc7eyHe6q zx~Ej6Je=}K%Ht`^QZ}aiHRbJ;k5c}b@@>j*sj5^uw2#vMmG*NwO*f~7GfxU6Ye%d%d`+Mo47)`hHJvZd^x z?BMLI?5^1b*(KTivtP}ABm14~o7wV1(nI+V6+blbq3I7DedyFfXLGvc^vYSA)6&t{ z(bX}nW4DfF9fx*&tm8jB_2{&$v(R~b=QCY&U23~5>GE`!=B`55?5?}J?(5o|TbTQF z?!P>to&lb>yTx{!+U>J$-*vl{r_Kw>OU&z(_iWydyx;O#yQ{i~c6W7O(EVb5e13L* zVg8K#x%nsZ&*Y!$(WXagk0*P4T@YT-u3$*P^93&!yjt)^!8-*f3eFUq>*?y5*fXtX zUC)g@KP|Ks4laDT@cUk!dTs3WbFW)P1B>1$`g`w~-fer=_x@M0wK%qTQ1NrcU-k*^ z6V)fbPjR2hJ~R8w>+@ru>m_j|gG*MHeA?I1cWB>@eSawJUb?b$OPR4Ox@>IOl(PD= zhO+0%ekt!&{(O03`Mc#eDncu|R`ji?t(aIbx8j+K9Tjg>ykBv?;(Dd3GO4n-@{!6V zl`mIb>}T%Rt>1!v*ZW)g*Yy8=03DzikUe0;fJp-y2E12gta`Gl)jQAIJaE9k4+pgw zlsag^ppAp}4LUXG%j&Mx)2cUA|Ev0!!Lfsv4L&^hTuqyr+M3lhXKOChTz=U8aPGrH z9-jB`j)%Xfjjo+g`&RAOLyCv2{qw(}SB6y&8#`=D9j)tK_jcWxx{GyJ>TV4ehIbr3 zYk0%(=ZDK9YDYAVRE;!^3>_IWvSei4$SETij9fi($Ed_nPmcP>=r*ID8hv$4)|h!? zu02xy$lo9B`{?&$XOBxAcXiy&aq{?z@h^?PF`@H>k_ojFewa9M;@6Y)h;++1}Z2&54?`d#-10@!YDp%jX`QdupC)-oSY~ zpQKL?esar`U(HXMKV$x>1z`)y7c5#JFC4${=Ax;Ko>P~~9A*x|=!^wuGhAR#73gZgriqsW(D=JnDUomyXq7`dbyuae+ zO7+UHmB}kTEBmb+v+~%}&Zh@IJ^blYt2(XP{EYZa-7_bibv--v*)yvXR?k@d{&R-s z%AR{;jeSj$Y0WQd#kJvUQ`hFLtz0`|?O)a| zS-WBFD{J3f$E^!q7qhPYy4-c8>xQhGxNhFMXV<;B?#*?l)}3E>Z9QEdyuR)FPV0-; zSFNvGKVkjj>zA+Jw7zluk@f$x{@eApHfT3QY-qos`-TAUxj*mIt!NR&q}@FK@^YK_3F2!SfiFk5I&jyc1ek(O$8 znO)f^hxuO3Z;axmw=5L*-!2*@e9N9QrS%(nR(Xz*#Ct5fR?7*3$xKxSRi)Qp<#>{t zn`9=+^UN8_^QfD5(GFP|>A`lJ7!y4|<2`U6I)e@)T@$ih(>1K+@ewdz?N)dx~q0kM9#}c`>@FnhV`I$4Z z!k&W|wIGZ8kQWwB>OJ}Dh-kZD(`d8;#ddRuC`uM%kWSEAt+wE(NR=Qt93de#Nh>&A zYC)%qph3~ZXbiPmg7BwxSb0fn0RXufmK-d2F*$(2{*}r?9SnVz|Mm??RW3UqwYpi! zbY-JhGx!Wv>|#c?oBu9_a`L%8Uz8jvK38;=+EbdTt4~v(<0a=xer}0;FXcVH`1_CK zF?2O6AASD`eNG~e(?Gf8gWHZp+_L#)|lPDlz%aB1QseS{;Tuh-^~^rc==;w1*0ya2$10aMOQYpq-M_YirY!>EHJ5-oB4| zUwWNuZ2s(LK570R+XXVKzWMgd`ftDc=^{P((?4z(iTj&5U)wj|{d56sjN;|3S0sYD zMS|jKWTGc0+2GdF$Y7!kHdw6*prjwvX2& z2(DtUV5MN`+$0hLp|y~lkQ6pcg|s<}m@$pu<7q#|L3H#;OLe&tAj`3gqzYku(ygLd z*)B+G9K%62l_c6B9vHIQ99dZskrz&W=ifKvFQ>2So&UqpgBO;pqY*tj(5|shls3OR zXZRDt<$WEy(~*Ta-TOS;zk1^Qi|;HxT-kr);57&Tx^mhvuY7sRfrWDGuzCGQbfHD< zYPkiOT|Awt#-t9$Y8X0$ZcucF1xk(=IHoL4D|7HE3Pnly^aBTo-sU9*c+L$w3$)_K#1dCQjwfvSfDP5;B4IKlN1cXG=Oh742i*9 znJ#b-^q$#Go8)>ruZhl+>zlZ`Cb~eL(S-dR%t*dPPm!zGfwR8>(;ppRe#%ghx*SCx;XQ zp68h8+-El_bx}UQ<$`>fb1{pFJ2+C*dPOM2s#}M3b{mgP4<#*;kWh`iuUDhujeWhy z1r5mGT?-7paK#X_$>K^U)C5t=GMktj359p$J1uhSZ7Q@-z9n<;xJPS;JTkV1Ym(>4 zE9m3cW0^=z30ZiMPQ#N+U|~xYE!4#m6%j;L zB$x(AMF*=?oYtZ(@mf?Iji3=FUN(qN!}uy@DwXLnA!CDO(ym;lqAXMiT{&nI<}6@% zyGtl-=IHpXb?t_f_1ipP=c7;U9JTn<$9g_{=nz+bj!u4Y&bUrh{Ywf@R2L`K?R#wa zo`bvhfM9?Pn9l=j@nn!ECB^}*sNy3ckc44SCA4ux#YO@5A&wA7saGFD4SYz5HdbXY zX-$2-T1FBWyb##Gl!t2uD}V=_8VHpCPeGGnr7_&39GmW6=c*rQ60y9t#L3J@r?v}t36C34ETUKy$Xk?=tqvh3c9poD{ zMgD}QoZ}mtN8jpt#adn>KLQNb0mGSqD4g{7B*C0I_)wcINFBth`G`oHRb$n|%=Yz$ zBB{l04=M55B}w1cE8SywW^fd@LUBEP450wXED+Nn%w;5g#5yxOxEMN_d&*~LaU=fc zj{K?o-Hp|KKdqtRa#QB)HZ!yN(3YFw?k@a}t7m?dZ}p|Rwwd3bx9jt`ALQHeB~=jc zSO};~#S`!dVo5iTOS0(oF)<{wrS$P7+ZyC=zx`RhI)7FD zI4W5GGHUXqiL*ZYvhR>S!-tmCi6`ILGU3%8RqssN*Yx4v>W>ul-S^1GBXw^ezIuLkThsJS#7g25OwLlT$;1Z-hxPRa zt9W(k{o0r@XMo(8kR^w$I6&=~giHoJlNNYDaB2yNZi!Q-;hU6DBtIiJ%b~9b%iNZ0wT}+1t65Ob7s#b@|Dap;K}TP%DgO1Jm#KM;eBtnukB@nL zW+|h%f2D;iCuTX~Jyr{Zhma7Xz0zwm-8Er~)KH-0HI zb7kHtjK^&8S&SzU3oMn@pi)_RL4prw)tV~3T9Y8bGK0g|Xr?3SOqswyI}{7e-!~XN zWK^tmN?@?74xiQLngWA?pR33zAqT2UA_*RoNSXassRe}8!Pz3|qBs;7A;4E`DC8&D zDHU>>qxnlMmE7)AbbkZ=`Dgj4{2jwr72N_4h4HnD#Cptdb71P!B1>?=5*5$KGgAgL zAHfeha}y{^6@Q66l8Vz_n@^&kUIot1RBcwaU2-{zxq~GZX4OJjhwN-zm!uQbJI5DI z3N=0Y;+_ww{vZ9%baxbciWmH{;RE^a&m-|AWQ;uX@A4fa84dWHuB74@bl{<8vU^~S z){x<;U&{Tw;@YB~9p`-Z=2^7Z!z0U$2sdDljj#ny*yMI9n@teHqI9|#tow{cm)aC+3hm?7o8a%5Oh#f1EA|>K zB&67jyYH!Vh1qL!sy=(dV7x~F011o#A9Fyk_9ljq@Hw~Kl6Uav} zN%MrLtX3?>4GtS(7R6q(pc1uWu~)13?aVb({ILLd5QP}brFOx~6^qk`K$T?4a47e0Hv`e1~vS{{6D-=p#4xhQ~bSYdYYKkw2k!WT%AiyQ+i@hQ*7_ejp`Fsw+eS?EDVP&0g)?IFMhEtp(50@X8htgAY1YIV- zE!S?JPv3Chxq=zRKZz&Liq}5WYmo&v*y#y*TmBV4) z98a~yUba}j&lyo%(*P6@FU4tR3ofMyT=RausO1X1CYw1MhLzuu<%LYUVN_nms2bA2 z6Q<^Q9sCJOwQSJ)#$&4+g$bA$yf@1IgU}!3GkihIWeOd~23XkQqoSAqDu$6_PeNEo z2p429aGW*5s#b>wnRF&F8`utL)(IiOVld_=f~bm@syN(9_bAI0o$|*PuP!t618A!_ zTq;OBR^%|m*=85_6_>yK_qp=x@>cpR9eL@Kk(>W|^7_$(L+a$qd}fQbeH@*SWVE4l z*}z++^7XH;-my`(o@TTjpGZ&Ac}f5U+gVbQ?**uN<0n6e>vR$iEZs$tpI}PGFr`>p)R+%L7F8+8 z%7$(eOXbb-oOujgGw3o}C3D=UnwFbD*|6R8-z`|O`lKTlql;`#f(qJHqR^k1lwS_~ z)PR$#Jof-lUncIqQ-t(b}!S$PIsfNamPbPn|1Gr!(q`J2Bp+sHKBF3emFha1{P&}i%=D9C8E8KBh- z2BXQOF7}#uSfM}BHh1ldh$XhNUUdre>WGG?rp;Q;9g;tnf1VE}I*VY3otGv)I(F0t zS8li568C?@MxO>N$uMAq&z0wiVJ|i#GN=}`2yTa)wAwIU1rq~61Qn1Xs(_EmWZduXjZS-#=;QzXgq)-rh-E&Ov#iG>QL9Hhh(Z*@2XEn>CW zV0$R^g-#b@)#!<)4>YGvuLsife6UVonY&6F0bD=KrVvD~83Qp%1l;#*G?>_Dzlj45 z#?`u2%NkbK0D-%Z6CCx_Tv}8o@07Rl$wNnvs%n|uaz@<$`T02~b7boZ4(a}s)WAkN zpxqN-v0}o*!d%29+Vl{zHi}?-mm0F`Fs1>C|eg zMFTAZUh<2UFDB_1EwfD$z&uRz`WC=uv1X-w^>6aG^7}M%(Z)3}8Ocj7Sz;(rS!0t4K*mh-l>X5fPD*(R!UO zD#9x2_zCDve6gAoGVAbY9Tw)SM_H(8*KgyD6$3Al}vW0mfuVV;Ub~ z5%?GT%bVog_}fRnkvk&uy%QFAC2}U0*m91$&b`-ioeOG7^1*cz#pe}9}((y~=aQQ(fbQw86gOTOH4!5=rLBm?6+ zl<~1YgCK+kQ&kgHEF?7mfG_ftmg>kbV?WjG%D8ZWel15#6f4jE&OBP8=F~zL@omHy zl07zr6+}rugh7pKpp8o8Bs_@)NRj=ckU`Owz>gKi-i;~K{VV9TYjEGc=hXcE<|1xh z3tlv#t-#}3mn^u{khe9kYIT;PoekB+E3 z@SQYTaW=Ny!_NC$y|52hXemA(K3=fH&K=FYkx%>Q?iN08cP!67{QYod$@)X@cEbCS zY#+186K~P0^;}F$NJwm?TJ6?{_V()aqRnP3@Y>+hiO~gKF__pDo9bIEDVuxu+*ihW zY&GY?a$8cOSXf1~-AsQN0UP=VBPgqHencmApRMy=c=Pu=M_yU*`tZY-Fa0ckGIjpk zXU5E0Go<%{U3*{BNNKyuJ{tbs`z;O*IIbEvXU1^Aycsa>!+wF_4G=?#M;w~A1b-GxXB6eZ^9{oM8AxrTi~$5TDVor53nKJ>OeqP zSp~2qC?9nE;&&&GO|WPDK-2X4MlOYyB42iBS33)QIj~>}7Ii(nqKOy*S#SU{KhrYIiExZq=vOgQ zW)mzd)}9hKqU9!bJTynv4J>@T>(#4Ot9utcXXCoiNSa)HB{B{g_&`d!d?zIq_`$fs zL_dB!9+xKA1cy2(h#|^pwCjl(n`;VwObUMPLcxsbJ^TPe4hByQhYcgFdNbmgeQ|@Z z34n=hMkrR4k$@%1AnPO{t|lNBn+e<@R3| zZ4DsD59LrLE*K8W;N~rY5Nb9@TD01T5W9u96nS~(MUf(}!KAVmcvbhqsf1APx+Tmq zD4`yZ&4tCe;%8>06T97|?3^IBBXQ%0j8oIy+@vh|y8JN>z4a4Sx1@+G<__OCv~)ke zZsx>^Gn>odt(Xy9%aE^MeP-^{ZQEaIlfWb%)}5b%H)!povnR^NaVn^rU=bI&C-)w_?<6nAw#(bJ_Pu{>T{V1Bq-{`!r(a_H&Pi{)Zx-$d zxrGBua#Q>AeFlvgGw1`*ZZov@ zpTj4O%3@QoYG#&=p{&-R9Q>Ox!cO_jzS)!HGc*l5_cw(^;eFzT!$h!8n<}h zw*NT}9$y9Kxqz|pE ziXF5o8$_J-?6W=l-fT zN}(jkr>xVJjRwVAl=#4a1yd>udiCi^(>|J@3@h70f426o6n5Q7+kD_ z%qWQT^0)=qPDHLHQ8Wc<4FI8}IriU>e^%p>%zyWh~`mCAM(K zzi$<91jN{XWknRjeMivupjRRxo&Nz_u$?h){~E@<04C$LNk>0mgS~uQ0idkn> zFe|bKqw286#VTLY>%)oF8WybS=?yj+`JP_mU4`ru7{%WVY`{TcVC0|>xJ+iwf-Q8_ z*qJjPd35HCM|n}cD7U_F^GO;-c~o55j$JRkxMRom7v*d6hs&wDky>c#GWj-xVl%Yf zK0slGt?%xM34z;>sFo_yq%t|7If=nw>j?v)Hmbr&_&t;AM@1l&%}g)EFv z8L<2|PT~XB9;o7_V-rj!`OK}PphrGEesT1X^NO`UJ>1r?ELiR&6|LNX-S(eflW#5I zS1HDxmc!UHd;!vl3cj4oD+%T!d2Gal#%K^A4-0n~qk{Doi;C$RJ?ZJy-$mYkSY6*9 zbzH#6VoB%l+u&eF21}qccVK&j-1x^H701s!_lR)(;x{M8Z0f8I$NKRjgCT88)BGKA z`!>k0?A&n;UcU+G>`+?S@cVxHS(iu3Dt(f`PXnwbw!-8r3O|{dS~7?t^OxX*`=!Xo z;WXXBE7mH&;k>D9q9ZQz>8qR;GF5%-~G=A?^IVlUA%C>s(CXy(&F9wT+Ze;S+%jr zIq_N5(*``dwd#x5_Pr82cgn2(3xhWW@MhzeO6&wVCwjHfXtiq9oLOxSc4#d|OM%y* zHyTBd4j!35iRGyTM#vX6dst>?~+*3+ASkPMEagjTfKZS#=ak z<`caxxWCGH^Gz;%&WI~lziIiVTUQ3dI>;Pie30~XPY7o=+ibyD``axVBPRxlLCV;Q zhv8d;-CH6*;B$jW{xE>c%pXWJrR|%1?0uTXB%Y=u*YT$^B{WKVmhK*ybF~ zmHP`dE%T;7T05Vs_l*G+EFHrbkt|zM6tvJGk;LIZkXjWU9uX0Zg+Y{q1+b0AaLGtS zrhB1%fm00T^Q06Mvs6(Wuzx_nBTx4(7%UDD#WUT@AQH0sKc@OnJ|G{VsdJ*8k`QfL zLQCffW|M(Rn)ccSG)aD&E~HnRmkKqqdH#>Z+xu}LE#C+CA2K+i@J>5=4S-`64BV_% za8vAwl@K7&V5y0@L4_!cH@-Qsgqf#(-K;m>Zn+fqN z0lNtrO^As(HfXX4!FCh&eW4S>*;W(C=5qmjR!i{$6o?f2;1g~$3!?al$kuGWG=%JI zT5>jAE9snPXiqtz+rMAvTb$jkYN|5!e>Gi{I6}oRj2GK2KJ2 z+I`&NAC9+_VWeoR;XlI~KAJ^Ec$+#p+8h$%G(<$W1m0>jfSY0sdjE1>;Z$V=-%&;e z!#!+rCUz<^Suz9G26i)+d%<=)Q?+(TE{&d7|HRxQH-`0=zW%YO?#2-sG@$xgRk@mW zU0Z*NFUXfaI~1dL@6pJgNDZKe zh<>DGq}L;1!LJh(mF?$qOcypa6FM3}RPY9(#Xym8S)NV6G#@}YMRr;xIm%^;!x1E>^FYGkul*mtHZ*?@NmxI&~n;{$WUuv zuR5r&mx$_6{7K=V5;Bu~N$Z#(HWKg4O2XhQp)?kY@n!kV=w!Kz<`Cl!=$tWtE|OGv z+8Hf6PGc~H1qX8>rVxw86cw!x2@NUpYC=Pa6{g9Egvbyg0^kP{sC-FqzE>ug3RP$W zaQ*t{-U1XR%BF%}!MG_C8HQje?$FVrgEvktsif27#m{jc-T8iGpS^p(5l@wW>+x0` zZfe+7A~)Y^H>qxA)6tWkgJKhjEVzVSz-I#1$T23pRUgB124UTFM$apxAtBpCO)+L7@N+6ca!* z>~1?NE(P&GK0>vH2odJUbB^A;c~idh+i$yBd(6qF+*0w=$(Q&=K(ZTAV-d?1m+!tE&%?;l^=}=~> zXa{EQtSq5F5cg071iF@`dMHVJKC=T&p}2SPjL;4iF+h}mdSRFO7xwQuT%NzYq^xMz zq^;}fyXIaydFtu1;{`|J2A00DDIaoehgY15RYEi$q_GBBr%E}gXP`3CBYa3%t4@07s z;z}s51>Hp~JMeLmqGkh{#usf>;z|@*Oc}^xvfDW9g2i&#@C!21W7!J<_;nfVRL&YQ z=2Xs;Ie&Tb!;9xnJiDQ2VsribwB`d=j>ua+J|k~A`qVZWv1J<#-?~lyddoKXo2~ry zXXeS@M@7DqbNl=kzn1LmDF2|`BX9ZOn7rfTGj!l_*6qIA7-yw$KXV6=CFvk8WW`2> zsHnpcWl~|!M->(0HX01kI-Qk9Ww7{?t6V;IsJHif*wIoIDO%w?u4ZYrIU?PSz z3wVG!Y?6s04MMUs#K6xf2>L7Ht+=P4lh1E8{T=TxWE@s@15AfuEv(c*sS3Y)q*Uc} za+CZ6bu`J#VG<^N!H&O>pF(i=1ooNbZPVznuzQEhI+I(l&bi262=lPbC>1svk)W&C3#kKUd}^3B3o+zZ@yi;D_5RC%jH-2XO_v=bMm@n$rt`l&ZVul zC7U2g=OML$-59uYK7xV~8E&OJHw3+8JE^Sx`B0wu6G6yN3h`+0f?q_qMIXY5;(OUk z@liUk*bvit3LD>V&Z?_7*HphSc<|=ID^I?IAGR1csGDbyFp;%xsUG~oz!NJy1FO5{ z)>MN}t3bLOk%P`+c^@H0l?vHiIz1A9bUKsSpw}ViNJ1=0SOWc+wEJ|kLZ5sIkQdhy?ToLy+<`;}ukj1X2a0;o}5uyo-=8zTY z1ZFHbz|LLO?;9f<9tE@3_mW6eF7EA?=@&=jq_!y=HgnZmi#OHG8BJ@sqMc23-t_o3 zRcq$VpVDvjl!q~9CoYHEkNnU$($y1b({XTjh*NUvOp;EDPvC7fyIm9Ejt2&6cuviy@+$`hX6RpGEq$bRQ z-8O(8s&W{E!B0l~J|GLcvB0En@x(T40;}WuCk$JvWMZ-X8m2N691es95Du-Xc>+;@ z?~;sd|5DX;lv5O0e3X7NefnSDW6^-s{ra_U*KeczE`IBll8JJ&(175n5m9El&V(f| zCTlXQ)fDmFKHealr)02fc9zuco2ZFph+wKry4}c{#B$1%mEjT^Uf8jvYvn&q{quXS zT5gs~e{bY7EVu7afyHsBaEbIR)*Cuv_h?{%^}MFii`Tz=acjkUV0vD0@0C}nSh6{H zHsH=<@3aXKafC9kC)mN`Fd0}J3x>sJG8t?Jt0suOScY&o_yJ&oM{*wbgUdJuysErw z8Hg|?WM{xDpH##s@t|dfx>kg)>k=}Y(W@FV!7^)<_n!o$ zbl(5|Qxp>lCJ~Ga6&AoyKE(Lme~QcC3a|2FcxuU5n*0t|MBkq9aBSNyv*6j`7p8ya zF2QOtuO!-I2)x~8gi`_|dGGa6pE6aDthgiMeGW2r>5b>tzWLhLH3wyPx5C2Q+`__c zLiNjskG=TPkz+gRh7Yf+8#e3@R&SuEtqzeNWXvN84_nY`?34uEGkStz?5K#hn_>Kz zeqnR_Q=@k{9oJ#-@C}AQrZn<*MPDVXlb1KqVEM-;juG?dGz~uhSUpY73A=a5 zY*%~4kDdm$@MEpHIbYj|%Cf|HpU=)3Pf`;y1_o9L_B%b8eL z)^i}9+6WyJPo_jGPsMMn`<{Bx|I}pPQ-P^2@^t$S$JGrbfq`WXhx>J*&XnY1DW=4!4-x8Q~0m~o<`uyx7VEQxa-}pmDv5OS?;9w z(XlxLynXl8ju`sem@n=OX?Qr3wz;>uEgJe%pOUKFoT83x&p*`T@Jo+w8V&ce6YU?6 z5#_f%kx#Cg%*EpkCCrg@N8V#OQNM;g>3EWq`CocWC7=B7J!o&z-`6Aj!DrM4M!{8o z56go+`UiTDF-i~ZKAv+cUG71m_4koz>69vk#%{!QKx0q?A5|P^Y{cHccu!}^%A2gb zSuj&=P!RG#^w7a}q_5aaNWsz~!CH^k7J2p#0hO#8B`29joqzvSNDpTIh zyO-6VC<$gve3?kfu8NXM5A(@Ps0+JwZdF|KbFzK4e2i-lR=1o+2G4aa<4z=6Rg`QaGqcEE# zI9N}$+EAo3AcY>OMTp!W=UZ#x%q*)tAa{yky0;gv_(P14EMA0+MJ4MSw2Na7ff?&? zB-y7d_NUh?srHKn;p0!Y{`Av4dW0|M>X2jqSC(zhRWASjn!HYycl&52o>Vc8XQ_-T z%<$}kc<^P+DtKUqo=M&mr3V)kpoo%FdtZ;KwBUd50m(b+>){g`##1aWSAjzr1y}t& z!X6xjVcQ4C7^Mf3yd)ppVb$hPyy@uw>{R~@%J*1<^`5o86D&I%+`K{ckysafd)nPFOj|3S%Upu znd)2e>sCHBYtiF8_suOCuOfVNRqi}`#v`Ku7R%ETM<=5MgvBAep9pSWr-Q z`;?TdpfHz;BqXT9_>i_4ZF_n%NQ&JYQsg!Jx7QT^R{32Jrg(jj`InH)dNTNe@Wv3^ z{PQ`60rw3XguVk=-t^%Qy9X68LTI^&10hOOwFx!tqVSzh$S(1LN@7${HbWq>>Us_D3y86~# z&OP_6-^pwHxg7gkm;_0h_I77}1D&dB54OkdV1p6ZM0ez>cVKto4!weSznkp)CGcv9yGMT#MWQNN#YZ}YTDIq*1rL3kg#c3-Th|qh#-tVeH zh=35TYDAn_aTUek@v}7^0ncNNH2uY`ro&zq%Y_xkB9oa5J6#9$B`z7Mk!M_?MC5O4 zkQc>xwFVcmED8kEl`Q$Zdd%BTKK0g5Kfcje_rNnZymtDFnZ2LC?NcU1ixB&@f7hU0 z(Ox&*amNEU-X?}mxY$;4lJ~}mvl?G}hN2G}`t`1R@5Y6ZUdq|i2nQQ+CNE!1mgTFi zMjRsh;mnLXXw~8Orzk(nX_b1CvxWR5r}&96oEoZCYIu&XR(5Q)F8_QsyyjTVKl_{w zH1f|2+J2u_TWx<59fDZPlGtjutif|X;XU{n?{MlU2;spqm^IeMGMv62CfqT*rC-}S zTJFIe-?iSs1}g8Xceu1R2!CB%26IEMpgv_1zk~QyQ0)o05sxL&hq>fDJJJ=^S^|Mo zol&w#qUcIZwO9(WT(10}kR;+F+?h$D-;Y=UgquRR7VSAzjds5z4r~NCNUOm)76Yhi zSRfT5ml&T=#ca9~J1%nbD*fE2;6}n{I7{FO)`7}g93e3@8B&^=GPwH2hj0FT?B=zP zD*tZzMfn$#KRsM!>@)AHv7-C-$#bUPHe>E2U7$Q~Td&&tS5J6IS@DhXjdFmwzdOxb zW90R>KDahIVai{YJo3PU8;CyEffH}i)2( zHER30L6|Kp<|`on$sKw&5TO={d_ir2dcdE+hN_>Zw|xSwpxT2;_?#%ISX)2fKnb5B z2l@c`g9B^WF5>o^k+>}*_Bu^S4I;D^+@1_w(Ea%W(2}T97Hmtp1WS2h_BisRqYG<# z_a0EwtJlDq-hHa+H(*>&eqTGVen8dGdPyEwH>7{nka|3KRLc%TBQ4`nL%6NfkfTWr z6bB@Q`d=PS@_&^YN-N}56rgnHls>EbuA&&FyKkvnb;X0tO&-?0u;=Yl*kP-3D7WJF z$pF(qz*5nT6UVMa6ewbrIt`uDutsSbUCmAgo_TgiH>K3^99Eb|b?_A)p{_9J1S~B! z|7|~~72c@su|K&3D-1ys`#4SkY74Z2>JuhGWTqY1PF+FtfyWN;K8)ghn2r2Fw2;AX zzz*ecRl(L0=eep#*&1&zyg88HbF`&nw{Yl#yFa>nfGd@bYq`LY%uV{TSk$WCZMwzsyx z27b?52*)T=ZDAbx#{0kqu@h{5m~5Oi9tK2IRfE?1HYOvy+Y2qUr)j@_C@k3)}_E6E43IW-}u5XT7t z^if0&w|TZ6H(asA$7F4eMa(0pCbzsCjsoyNQZ0WMI?pb?`N=!~netq@IiS3a9H7!Q zYc58t6KbbTly6)#eb`tp%VZ);X10dG3vVnt@YGWWni>#AKX+y7w!7|oZpBI(DarYW zk-<9T^Es+(`Bj}|N5UM*V>pF#If2zR(OQe@&X2XgDO_!#zUA9LYJpR@+Cn{Fr^{Oy z5bwhvRR1U^?&4|F2fj|!0#Qp(wT(E%?ZR$AE|%H`-wiPGpm(E`d>L5+xQ=h~>pcZ8 zuKyk5PPB2<#%vt%eMzTYg8ap5VKTzFLowBib5eD@4W%pP#j9;#4|HL`<^Fx|#VcUyMDP2>zDK)j93Ow7HvV<$v*T8x zbtJhMHlyW%+8yO=iD22m!eKLfVgGi;>~Q)FXqq0_s)t1Ky@(v39JOEo0ZqVhGbIHK zwT*sqO$pWjUM4qE$W5_~xmLu>));lt_f*#vlswuwu(07pktwjYm50b-r5pFkD{5Z+ zE=tcvW<32RpFhy_5v9n>MF;ln+ZTOn#|}s)GMB)-LMrsoc5ZlUg>)n}5`k9!RDa|BFkL zT-t_P^L@4vV=Ll*WbuHQIy2dy{%W2&45a$SL8+FPDY8!@F8wT_vnezm- zjr!lUf&C}$<2*raqdwM;cpJSHFJTj?V}Yf4$gM<`g=*#kuGZq2xEi9Xuq49PNc%v2 z-XNht?X${3$d-WlILaf!v`BvkBOe%F57i#4M*8CxYEK!evw$Xfd$6t+K~AOX%fV%U zMTqP4bc0YQVpn0_fpQ?3_+BVdDP7TcV9c^Z85iQ$#0x|Ub_BOj-c{$U^|Zo1M4CLt z08a(&Lt!m{<~pS-WlZ2Y@lCzhWfmIXTEPW$)*V!`kMW>&Se^3*l92{!cZ_HE6Cbcz!BaUOpms1$peo=lv_s>pq1JCu zx>AOQ`dylp79F1z{#4z|>fP|-bY`y-f={=ci=O*>h|L$j1-aR@t8uz$MvX0&<4{wI|YBs+rD zVD6Bv0&D6(TP@PGFznmsF&!E^O0uenMs7(qvzCb(0cS7y2n# zt%j&~@XBO3z2n*kR#pd;3AGJQ*%#xKjl2}~n<{0i^pyBSNNwlTC&s0=b(|l^o~UQF z*cfXALgZMORz zQWE@?ZVjO%PqjKB7mxDEX-T!@V$~#o3pidh(2~klJdQk`=jhc-7jGVR&48)1P0dOi z55tJ?r5{y5ldFfx^%^op^Pb{O5T~piFj{&MLY~mU?vv}fcALwy&`uY4O1Ite z)_Z$++SX?Ahm%@1&!8(mI?%lJ#W#r-NaFdLpA4n6($I!9|3Li2=avF~GN_h5w<%Pe`1%Dsl6Kpm>1KA`q;5f{( zoJL4X%-8Dm<3>r2Rlq}TgB zes1eHW0(sH$`A@MOEV%@6nC^E$|g70*s)`p*V`%6Xe>+h1&e((jm5=+)7c(!i&L}% zTf6`{s7@gW!z%*G`!~v$8(tADq6KU4U!2;wu*J<~v_(jN$)teWSmG9i;!+|lqEOU8 zZhdP$XO)X+H znAkpXmUqpX2bl2%=3_;J>ef=et#STRB;3Np+E2>|+c$I0^m!UbspdQk0w?h>VVO#G zai0k|inN?l%$2wU8ZlT1I7-~cMjQ;08lXC~z&Y3s&cSdxQ63bo<9lsSCtbo5!Nbbb zC#J=?<}f^QSL+j5?c@B3{umGAcqY8h6rOn zfRYFQzm@W2R2UWtS5X!Cgkl0XA=HBvvOLrb9If<%>Otr%7cZ*#EWiFLHYh;*0!Rzs zJMeVsA7zZC3)e)7T_&$LDK*t(Np|=hHk0T#`7<)@0dJtHF@>uZNmhjMxV#QMQpmVR zgtElw!^IyuvSnHumh3Lr}ltfzsRrCw%fyL{|esbKvXj2Ha^u2k8kp9IsR0r z?Re@yE=pn!<9iKlB>I41zwoMU#8=@mo3CcSU~vzV+QM-3t{XiAaX4;m^r``aMuusZ zZ{j_L!I<-2jgQ5nd9Zt>&Ag}A;12LHbRGS4$JSbHfpk0G0_5_5+RwP9Ms0y~1Zn}2 zyRZ{oLmM$4)8)MYXZlfXBc{_5ztQ+H??sFsJ9sZhD#PbJ;fuBkSrMn%4(v>u1!?*H z8;ydj22+9^sLmr2yLjR@PCkG%h=b=VNA?_k^0xk?bVW;=M#?Haqb!{P zk!-{;BtxsP>da>3=cFYgyVahY3>=F9QhtFB1Dm;uw%`P6UP4%kD&uP=h1Nhs68hR8 zMfk{uD4yQ44MJbnd7C!FYH6A{$}YW;6=Q)9e5E-s!oy31AK6i zVKXAVDfYfdxHZz%rIQ1CuOT%pDU_6C5rnM#h$TNC8j$Gq8VJLt7+PnCfF3wo0RvJ% zy@errsyzo8{i?avR#r4h7RxXps=XhxLU=drvFrN^cSd^V<%ipc!(~N92x0EoGc75Of>Q+)oPO;q zD)PE@?Cp*Gf5YJj$w|&nO8@;nIk967NfjAKot@TMf?%2Vzar%zY&bSk2?=fnZPrLE z`=79MltX;pd>a-Vd2q&zdl%m{?cpbB0!uo!tN0&qc67yj0+S~8Ro_WDO8Bmp z;#`n>{dgO@aR+z{Gy$}rDgx*Q9772b4&;p{>f0#D8?EvUuD**e3%lhLGQ1721HgYt zczqBQp!$n;hiF;=qeO9OGHL*6+mQ}m9<@rOiZshg0LcV=Qo|E<^^YBTtq;2~+RgDO~P66uEUfmXG37(xe5@a3TsR91ZgV<}3 z2v~}^F*;JWaQNue|aDr@nCYuAOU(E0PE~Zn=C~LpJg31g|lfVcBTyM;yjv zImZ%F!Ap>B)gT|2YV_NATyti`0Sx#cP~S`$U_mAyFZV%6+I$U&ad4T3dym@?drcx8 zS9Am&>keE_qR88ZrEwi7&V_tjy~P8ovymdedE7VHQh?MpEmI%4X=O%0A(&`?Ok0aZ|4sO~h5=8QP1pG27X!QsN4_?!q^PQq| z0rEYP-@it66M=&GNRplJ(#%3r3X@Nirj|c1oYg^O2Q#|ZR#+aIT`;JwCY!%sb_>1N z^)9biwjq?4*@^!O!HguV1qBB|!6rnx=SN&NhubXck!vD&yf)nes1FGwjC_MnN-5`{ zmCcsLvJckwm=3Kg^UInu_jhJR!Glwfg>dEH3w-78R;0W+)mQlP5R8`{2krFLkH#U1 zEF)wW!6uBH2BT1{Wi)`tV^k_D&E=phMcVNB! z&}K^`e&BMYW~66i=v`L5H8YfvVwWUmP~q@7t?a-^Kh_eaW7PcfpIW_JSS2 zBl`>Fjcg3mz#5`Iwmc@-UKU-YKvdE75DMYh03Vqp<{>L#E=KOc^!LU$pTLe=ruzk! zseIuf?Yr;r?=nZJ!y$^X@6w&RU-+Gce{`o_pLnNqoZs1AX#Rbh33tAO*sLzN6Sk9~ zYQMi%`yDI|22G}Ti}ynHO5-hdV;NeDFQ~n=mIC9`7{Bu!+&P6>WV1&mw#c$ev$I{U z_+sU#9Vd{Z|}+$SXu<@OOmL?Ae57=h#&6h*3-P=?_HQJ;D*Bsn}d&3+}#G) z?{tVPwIgc{5XCy+mjnNrVi04|haf7heaQ{c)yjLioh?FB4Zei-5GxF@?mePj%#XKE zK0`fLgFfeyCjYy8>~q3l#^>;Gff~8>3RWXEilu)f2N;&H2#EkgHK$7GiB*Ehh@+12 z5LCi$HN>H8e{UYrjtID<2P4rOw1TjbvG(^)QyUX=Fy4&wI68@pfIv**=U-;~BF z%96`bZhvZO`st@;ev{c)a2bf$qK4vQC~#-Y;M=CRWLW{-5MB6U}qt6M-< zG&9=TqA##3s{vM zM8$}tMavK%(K3`sj`ZpfP}xK@10sX4wvq4$lmr14G-NaI;Q;(gqXVinfY>y@0!jTb z0~{8D+oOquxTLHu47>~FDE5F6XCYe#X;ZFxfMAtNUnn(Y+t@ow_=h5P(>0XbjOaRzpYuPTw3_K+-1X(hbj6VKZMhYv3A8< zCg8Bvuf)=&2$4vJPp3nH6AKat)9fg{C>veIx<;SNrBC)Cv6t8mRWVa7LJ6WenP6x{ z*w*BICc`g&QOEI%hUp6FS1SKhJ}OhbEkk(wNtU&8dj|7oY+cR5(sAW$<$_YncA$)# zpM9Z(o_IhqHeX!!DEk8a!wTu$;6jBQ_3UbBv4Kv|LflsS zP|G=a4?o==;VoR*S3BFZ@VRm=f(Vun@U?dEV7|D32qzC0QArGjmMBi2Jy|BeFYn>gKmTj? z4XZfam_Rxz~;C^j+=9!;aM}TkoS;vNk(UsRXO;FyQZT`XR ztQ+|(#)28cjAy{cR6zJ5S?TF!eO{h~C1QTgtI#7!lxY{=(gaFMf0o6Z4Yb(;YjI4@qXpC&h3G3)qR7nHvn+DWNcvFl^cpG6z_1Bem5ZU zJw1LJ(Ed~-Yi`Up4l+@o>X(m(WR?8Q|J;AF7 zNAm1Gx6dLF-X#RBtf0jbvIK*^j0~eOm)`6KLTQ#t>UEGV~cj zJV7Nd+to?Pf!>YCCLhXF`Ml{O)=c{8KIL$~L4(F2j05FPQy(f*{w)nZs9ao>H(-3P zjZfyR*n7>I;>-e19lP@=+t_R{f41{k97$gOaLgeM25$sZv{rtnDFOnH>Kw(IM>l?XyekamH z{0Q^+R5hBI1X6DU07zl|@r|jTXNl4^bZ6LNP~DA;Ch|()FP~F>Rn9Rh_#KYh=;yO0 z>y%1);+!edOza)wM9Hd5R%Wp^DAn>9yH}amTz>l8cW2~9$1Z$zie`vT2XGCW5Q04d zZCI^#o5O^FFq1}Uoh1lio0P$DfuRnV*PZ09ZcOr%pGBQk z*D5J|j97MH6<~}wZZVIfd(7nXh)M!LUqjsyieN&KQv_)k5at9VIyC@;crSGoeFa<} z=*sO1IUzEK^sLLj`Wa`AW1U94u}(RMS$*`&$5F$j5LIKADle@|*pD6H^)JoI%`GSh z%X$>1wCa-(u!|yR9aCWi2^AI=OF^I})PT-xxd5Xw*-U_u))=ZZPY7u8Fm3jb10FPc1U$r+Hf|0h5b8|;MvgG9A#}D`MOW6Zo`R=ae8#g__y7>Bg zH$JItaq}98~kB`g8M;?9-QOS*K z*xKargfS%y;?{Zl^emWPvGNJPi61$=T|R!M;$-+_Fnz<*V(;errCAHdosPa&2Kh`B z=3xC-g5SH~{R9FrrajY7n2{NFU=P}z<`gN|nu!tD?P2~uC*NeqSxcH!M%XP}vavGq;iuwsfvopTH zkXNv+Mll=9+V%X1=O^5GbLrSc&pv^5eRSvzSk`kWztVm1H@)}2RWrIvTKF%MR=xkv z>$3~J?M`d5qf@>PJSLgyD_Bi|fZYq2O(7L|4=GmE#RMaC$Sy5lL)+_dCK%r?Fo>!# zC?P0_SS;*p4w40`ls7GIdRA#xJ{NBlyDMMrXg+uA>|1W@+P8H5J?!KoU)+w|T%*|Y zv)9+J-SGIs(_b34f##|Jd`SRJxiMGCV0;EU5J#PMyGO7)?NyD=Hf)e9e;QxrTLtUb zh99DuRLCdJ9MEm>jLBBs6!9Sx%4+p^Q0)=e zg0e#ZxUit{-8kWDE2GNy9KjwuC{KlS0x2GWa7LXjT@N&%EI%-|(nCI@ zE(xXQQ|wlkwYm`^y(1k+eAQ|}gcvS3RdL`WNSto+Tai);21sW}07fFn!!dJto`k<8 z?U4ClQ@XsBTGhRz)0NZa{k78s%=oM9!ac#N&Yip7EKe=FY3`@&Y*er0 zM9OXFG8R9{s-i2TS?s#19-i|VL=}oxUj>Cch^VQr9g~aGq&U8nX{OZ_5ju&%fkhOYtPF{KBXPVQbyFjc z&5txiQQmd?+5&TjHMjorOvebznRml=!)jTuwqf+xc`PSVa?U$(;1JkW$@>A&g z(G6Q}xrgN`Cl=3q?rBsT(XUsOHK_RhF-{aK*Mku;q3XfHB;^;JEToI8Nf>0oRW)I{ zhik&Zq&)QwHRyou;O7!)({wJ8w%(g->+wu9wFT0)Rb9FP<}&Eo@!TXnhg=(9iSNVq zj!Y4LM?A}!>}?{q8NjMbQ3>4FPyN)eDLCgkrds4ss#?9OFEVMgD|`HlUfqh(&rN%`S}*X}xc zf+QvyR|9!F+4X!}vxP-!41*eHjZu*eGYl(TDoM;bt2-D>hpypvr%CY0OOnG6;NM2S z?0`MU(bg=TATe{R0y&%LjG#TMl{e&&fT(_zmn+q5{-;%(6J2CGxaV&_Pda=%Dsw%$ zoz?yKUp}2O{i+A$gKunBw(mm?%lt4EvHXHvwX3pYb51vmL95aQsRR*a_#2Dg#y>-VMWiDj0)7)TsJamqXqER7=uH$nIxlIhKnIq`IEB> z42c3n5)`1;^F%vx8rrYONd@J@Som z{f*;pgg==q$9yMI?f0J znI^f_4M0;2S3rYu4An0y#AGBF4QKEHG}X#G&`a1%LsQtshSs{&T*oAObrQMa6(dk~?snuMcaCmQh6C(s^@JxL zd347hB1ol@{A10aKrE&@gRLGn?QeM8L_P5w^wf;mfkzIKsE2a3P+Ly6$vA1PFp}Hg zIr3RiPr+o%bLlY{(5hPoCvA1o2xWAjwV5=mIcJ?*SSVAsl}e!uVf!JM`KD!?3Z#a& zlw-|Plw;z-%oW#&U6Iw8g_Ny9O|{Vm!j0FDKBWkUrR`de<32sCCw>g~qsK1fZsnVR zKPe%w!Ucpfqs46Yh=}uaxlz^@HBUegc8kkxkQtmxRC$x@aU{m5Jtq4Zmuh&I`E{@d zobl)`{vUfI8WkVx;V1C-2^K-tj}b+g1IlOkw?n)L@WO7W&qn`xM~&jCXbSy9KZ!FQ z2k%CnUL?mar=*ZY!EG?)hw`KV)Cjm#0_N=O^t#4uK;PG?1&6t$4^vSL$v`CqjeC&| z72sg10X39~GYN<`iFQ*c`FU`$0M=ylyMH@)93^xFhU4=6>_>qD3FP zxp*E+`rG#`O}=jFuAtt#^O5(y9mM3Kvg6lJ_-VwrfsMBw8CLf$?HkbarE86VnA-E_i;=odbZI243DAJ7Tl6vuJpt_xL8>1r? ztX;InYscl`s9XB_Qs!$~r_rhIQ@)S4Yx`KsdyMCMQGc#Of6R!sNCLt=D8Xt*?RD8= zfX_`f>e^P_15ILivA&wz8sf{!7gl$jvMzd#*rI4A!O$tbgm^feKb&KDP+cV` zx!tIf4CFAg*9~W(TQb6XXY?>^T5Z?HRiGHdxcpclAEL7QvO{Fe9~>Miwg1ke8uwV^ z^EO^h^?vFr_VYA;{*p`)_f4wzb5t=s=#b{QjbD&<6Y)>Xs)ur+L~tl1M>ug#8K49C zfbH%D__fZ1{7f9S@k*0?hsTGlDnNV>(e|-Z;WS?Nmy*!R0PxpE>2~Fc_aB)zWyHc^ z@)GXl~G`uKT}~V@>a?Ed4_Cx)@K#%)o1?16g2Z ziXwjSMa%~(Z+LmhO&vq=O-=$N%qJes{Kz0VUdvlB4(rTBdB>$1^|<14i89={7f*5^7PLns9W`@M*2Cm7==FG_(=JkU zkJW^;$>cWB*+>&fjJ}K^qD$RWq_z;j^PQeBqfC7=ruDZh_2ClaUO3Mqt+RWn1}6`# zs*2&}qr52K*~4iLq;(;H!of(#F`1C^2=NF}A#IAGYuqtTel!8Z7`a4;;U@|~D35*w zNA_@KnYdvtKQN2wsC;oSi9aNdw+cSsXV^RX#h4W{);vI3CoI$!pVu3t1VI@k=y>>t zLG~j)1*dmRO5-E|#vMNe<5Q#cDX~X1UGh5RD74KPtYRz@7s{jcLmq!{$(BCy@?&aoh3nsed)4|owJkYbnRWItBP>PH9%koWXf zpzK*aJjMhd(3>HK!uSqu1F*q|8^4kBLve zuGHm3uk{A6e-tp;Dj#F*!+%j;y7P|i21ohUu}8jAK5Y@;8Tec*IkCTDa-;7r;*;zy z_E33pzH-4);vM|x+@T|)XOth5kJ$9vSRo6S4k1ed_8X5PZzJhF?D^5@agqqr7k1qyN&k6H$43} zdGw0tir!t7ivvX6xKBINnSq{58_@GM5p+~qN6TSQXBHGpxrr`mgHog%kw>3uUX1UE zLYgvNSHi#T!S2q{(cv!&ZqaSQQQ$)vBh+{$v#-GtcI$8;z$#5+)=mSwi7i}Azvf3m zjtij)h$Z7^5xhC1SWBZ$1+S_@jMllRQ#m*Ky1yh#{tcgrt9L8gABjH9mMPQ487pv9 zWjEiYN&p<=L(efjaP)#RH=~~}Xwi9kgpWCZPsPh%24`iZ@P|6*^%A5dBKL0~hCWp63FaeRDfq zr|^6hGlih1|6dev~ z(uFb4Hj?=*R7py&hXd3unXR(TVX)GqwkA%ik_TxeE-x&=n7vLA`CRNkK$O{Z<%Bn? zD31VEpsGswL2@>&XRqISq8SwpiHq3c$N{}5zMdONItWEWI3eAK?k=W(-{>LKBH zL$DyTno)7UU@`099TbefM2K~WgpyW)UlR#pYYMub#|A6?*ncY}0DgC?e#}7O*=E%%+jEy3hDXiLvv_XI$ho=nbfQmYbJ{Rp6m2Jw#7PA5y+>RusN- z;$UHkNBNR%cr(4s6v}D;0$V5D)Z?Qmij`fQcA#(bLs+D6-Mjr)E}X_l{o?#^3wmV{ z$|6BbIig1y`SPs1S{E}*76)p1YQ)e+{^hb5^+9{Fqii!P!AuM%z60aQR?xv=r3{U0 z`y6PQI7^0N_0{LD!bdzt6Sd|{fG0H?WR%lIEgSy4__WihtUUwhY#+d-kM^6^Q=MG3 zZHjb2ok+AjjB61%r#LF3UZ~>FDhAK*&YzW!O7|47DVX#|QE$M;G{o3vECo!HbvY9a zmCN0gM=+#ioRLa%PGKYho`y@-n%Ev$bsvHkcHT7gx zd@jmzJZxKY%F-9y;yTx^#dp8tiH>z|r*%Rm0ad)i=wQ~Qb;7wZ7IXIp8kmFh6dXW4 zDIDJ`Z4E^5HYex{^4_PFbItXD!g-yQenv-uJeo=!<0*6T)OC1@n=~*;yGTC+dw?>E zL^`|K;6`;ynyudpyA&lyY8PIbD#o@f9`RTm5#pqsSHAo%C~^EF`I+ns5aXWC9bSE& zty8JbKzq?bo?@t7AbFSyWnw#e(P5Ms2$48|rdGC&O4GlH2gF{6&~NiGrd!p2>X7)b zaOz_A3{>|})4rg2k&@>kEf(>@c7&)|e>aJLqJ1UKBt6<=2yIZ#{&ueQ(d!cClsbS4 zhN|v0j*rwsx)){@m3mRvT?Z=%bJjGXDxbxII-C}V(N)uEq%kL2rV==xy5Gw(A*0do z%VWZgxbip?e^oru@07K2`S=O{_!#n)GI+yAWi2*LRP1$ya#54K^hM~28LR9^gm?&= zmeGq+yh(sDKmlW~PjJ+bx!-4U7}2v>U;aSpifO^r86*m!H?hv>=>5^1~tTQ2BUZ(17Jz&T_)~Z=!7~!$;}YCfIFd=E@ga)bgkI&7Y>Qo2{&r`9y%E)ZlCQ(AaN%{7| zr^>IX2WgQGr!m(*&$3`NXUt90$J{Vu`WQfMo>e}$c$$t9W#~W8DhHKco_+4QXP;L2!O1UQVCj{5^~eM20pHh5S4rAsEBKfK>gE%b8j!oYojW4PVXI3SQO< zGBbMk?=j5wXjn7br%k&Y%dGjk7vI$6J~cO-9p+=7oyMg;dn_xPPU#=`9splTa=Ku2 zPC*cgBqiIyiR^aZM_X`GItB0lvI%GxsP+Y|tYOUkS%GjpajEjHatbcK5Mp;WPo6l_ z!JbjBwoe;B=&$1*^s+D@7TdARniIWlpLb~CyzKVh4$!0(o<*uBlnkuna*P1A+;J+_ z(%&toKzPgB;2wVfG9*8>hI@>Av^wqhP8~kO!eeg{&p}le==|+Ohbt`&2Lk|)fv{rA zbV1pKO$>=-Rqzn%E!6}Rkp>-73$igh=uYBs;a6w8OMljFHNtc^8$gz%c_~Kw+2?)|dBJ z<)^A_NpfcFj7dcML!OJ<1K~Q|lBVJPnM`SZ1aABv;rUE@_hHq+;BP6;1qe6)TIR7-hP=7ZhcI-jocP9l$>* zNep9>)g0C_hY_bPwhqw`kN7D|uv@AOEsG7##X^%xc zKW$?+8~cZC5RS$bd=)3L>1cyEL0KF{MC}RTm5fyH5frq-@8L_W zgkrtR-~eboBo{yxXkGcZl#fQ8j8KmDo1IZ-1t#i@*gNUCHbL=;YJC#byhIII)c&%H zsQjg~nE?Q)w2>n$)<<#-vY8s*>wsO7BW+Z6R4U(ADhC-Vqx&mat+Xz^ zxwrIs`j6@CDdpO9T6bu$(r)y`C(MiVuv<)euS<}8!0?oHhzN0jhCDFpBP_%ZdeD>k z!C(MxtKDm{TWlVy)onB4rE>_Bw2LmP*to5|Oz0h76B6fQo#wV7{7qr?EXSvHJ+D&h zzb_{sP6ue|OO?v$&Hz@aEiR`2cV>Y~x>_t2W&D(ZGQ)H5>(ntC)NRVW_;rlNH3nrd z*2x7pV>~-S%ZNM&RD*^BfPI*dqCe)*`8(dT9*>Vn$n}?wHd11H7LOfa}BHH>0 zVZH02=>GoF zp0H0~4fd1enfp$iZ*nz)7OL%AED}X}mXjdCYY(DIBAf*|5Jo+J^Z^g{zWi>+x)zmQ zcU@@_dv*JkXLhj0=Ux)GH6QyyDqM8=bvC#?-PxY)-nMna4mRSsm!jpeFn?b2rMdII z`ugmD*uk^^MK0rJQN4ER>$9KX+P~GmzWj#&6`e$?*aPvCfNSvT*@XGoODz3&1#uIL;9lOgR-+I_U(h!G z0In+FP}EZo`1|SassL<6g0vuohQ;^{zF2i+6NREK{ig3-r zLQSqXRhr^8eHoTcT-JQ-d!W}KWgfSqzvIMa$&5OVZUI>_q(BR9liTAJOb85gWSG-c zgO*w#mW%ORGmEKYklB$QQaNYMsPdf?3gKkN@HwC&maWK*vcVgjGaOAeQ_ESpWkdP$iPz=^qN- z?|uEPYw8#F8(hoAE05#fW4fvus(U|npl&;UzSJEW`c+zZ`0$&y&D>ryB~NL-*lyC5 z+h=Z@`2j?CL=!->JB|4_BATE;xGB;tGc}x-m!6uF3LwBj1hP8{5xC}XvIU=g#cz_eWO$Fh%X8P!>HT zItn8r?V*Cn9dfdxCPN$dXaea!&dTbyHJ-^->kdOvUiBfrNRS-iGr!hk-k_Ns}&>~d|r>LaaUA`cXg}NAF=G({qp-e$29DD z`{~yY{j_srdESW8uir54lh=;ypI@>+IhC#1_59YAJCv3gr(cow-e22O*_F2m>jdr3 za1OhaYK9WJ?&wI7)06CFC=d+mg&{N9o9p!!Ap*L6I8cxlzzgY67O+nVZfC}$pD_Xw z&G;~Acj6UWEgbP$Hl_yC4dY?hO;Cae{-6jkQouY3s8)${+hB7?CTcL53OU&k^o!EPa?oYB%M@ct za=%82Tu^RZGZHhM`(l@ZrKTDYwB^dM*^G!LD=saDWY2kjnG4;Eoeb(GB2zgb zVsOX08{Ci*Bq}IP3-%Ul8G}ol^Tdyf?mNF?)z6EY-!9oWbm7ZGs>hsIUi$Qi=Wh=$ zXSyf;*7CrEO7t0JZF$wiy9(CaA>Pk?<-KPqr_uK-TE5Y51$9wvVmMauJ0g7)eGxX_ zx&KCN84#?Q3*4<-tx?<+cOS(LqPyW7;dfu)chACKm)|{-->tUY=5Db&1nJA~?t{A@ z*Lw1#*7obvw%8LEp*DA8j=O@3kAWOa(mCv~DOqmBg$h{)ApB)p(%~1f$UdK=f1}SJ zCne@6#!Tnh=?mV=Pa>o((H`#PCQd|*$l$^|H!F2l@^cuBNgngO^2we9AFcT9uM-;r z+bbR)zIo%fKSt+|-;`;uFRz+8Kw5=l#>h7xAKA(())btn=)xQ$m47!^Fz-J)b(->D zGgc98bV2+A@SqS4V<-G05lX%p(E=7AKRBE`gw?HQM9U=Nw007#6p954F^DjB5!zi| zZWs0{8_jk#6^j!^U;a69WV0oEyUu*+{5JV@$5r=5J4r&`T{!O$P(V1+;AcP)g^L%K z1%x7V3@dP#VtP=F8Q>JsteLVodO~?iUawT@=l!vT}YK>vx!ad(tj` zek^)WH-+SP?Q<0B7q)UMF!)=7b^tjz3e$p?h1c9}>E z#Hw>KfOr(i(BMr(f|so|Da6Ec^VFk-pO2tNKcpj7EQCmuWgtcX`AJu~bMdyhU6&79 zy5pq>w#}Tk>46uT7mcXT9bDSEUq5luRYL}k?A*6XJpADPZFSe|TK({wTQ|R=e6_f5 zpSo_{1~B8IC4;+MF_7jKmO7~xwg5jU#eozHGb$CB15gC41~~l@R-+OM^_j~n1n94Y zlK}0Xc8)Js+*;Oc|E{USukJH#->XxHH|o6Ay_)1#-z-q_9+WS>vNvDJ1=ktDu0d_S z-jKnr$4-Bn8R&OQh2VUFrS58}-I0piY!%22D=s`FchLoL`i+bf*_ zZ=@$+fY=FHOe~pyu<=_qc(8JvC@(xX>a4PL5POQPSI#!PadPts799T8iOnamr{QZs z54(dR%!rXmMpqCEgfbc6T6!Il63j*e{Idp3u*&M`$#{H1chEW#21V!#$` zEXimvnj{fwGHC&7$PbON4g12QiE2m^EQ0{)kq0Z?Z&tdqw{Cv*{Q2|ZXVH-OnfTw) zbhcU_2_Pr2fM$3oA zo!DhYAqASbhEWMitI+eH*2%UYm7@t9GI{Xu?ef=z2Dxn^wV`wsceX4<8wogg0atCvrR8-eg)6jEFJVO&6Hps`l=lF6AgPe`_9;zo2t`Ko@z zcD)|0_%<&g`X~^~#m{gKTVO%V1VW+>-tD#OMHRshPq&Bw6PS;lL#W z63ZznDecgqw4{jbR@QIemL9V6+_7N(+`03VGH&UiXIaG>@dD0Hio}3i<<=SOP{?os zi8|5awVMnky_>;w0NX*jlu4vW)DQm3`K;OvRdiQ5SlEXxUrszBRNko)<1~mD=ABdS zVt1XHr>xMM${q8?&mVhiwb(0qc*F29$x{MvfX*7kF5)4ag2^}qvteQL1_F{N2rx9G z9dNBuN-D%uU?PDi^+~>TD`r@YNF-)N+dj50=)L;4+8ek%HFgTVPHJ(MA&;;G4HazZ zLw|q$4k=6>l9Sf8LNmC1QW?Gmh z;rE~T`%~9^Ja!)mNJO@TNDOVCy%mFTeY$!r}*%^Zk2P z^qthZf%a%utl)9X9ndc$NYE0HR0oN|0C7Z=(gj*Fni8!mG&y8n15fs`)vQ6O6W1zl zpoJZ~RzwTHd}y2}xeQ2H;Z>24NAHt-IZ|OxA+&U7y4h*&P>f*j%*_1!6k8|@$23m} zi_Lp0f1+if)#0WWS_ea{KsN;MN>Wg{g%hW3o*pnhm;lsq#u~9jOE>kY9oRek#$!*t z-2bZiTfJ9w>Dq14jk5}iI_2h;w$mM&GqLB`!gk#Uw4b%&f!lgb>d<)SgxjOjin??u z8q&T!d%8HSpw#CnElexMx)p-5jzPM{`HPsIK(fw-2ntxo!r7LzG!R$7n_>LpMFWXz zE2c%&g!$reLo~{rsNh-XuXyaj*8?g_${x7iXzYLI?f30`bi*EHOL_0kT`Q~l%4JHk zV~>Bv!ZD97T(bFCxPPz8UOfx@3`2h!l)DUjb?0yjSkRvUH0TV--FeVPi-9m%2^7Pg zutE1n9OKo(WZ@R!4q*wiL>5Q7b<{Z=y}FkO*7}AUX3V(mx+zl@+&+H%?7L@Ao?Lx* z^@zI{FCNh#Sp)qFa?D=>btSM7w&z?Md;K0d~M_ES>8M5e> zQKJlQx3wrOh!U?>zb(5Yue5LX{yp-074}9PT<=~zx|f#NjI4cXlGVUEcgobua+mTD z33+NHHEn)+&yOg+m#y5ySQv_T`k5Es~)|2LudQ@L-h$X>;->5{DK_nkduW({B<9=z2L_$3M;N&ev53 zQwdqub0`ua7Qn9$hdnW?8qoJ?!-i2Ws+?E~n)m7(Sn->>4_L>kzX{Xd)Y;#f8Xdq~ zO`kKn@)NVmFHgl!(>~^G?l>J)!I<+_=DM_LI)3yDKKSe6`STYqo`2`P%FM<1VV`fA zI-^7Tt{tb2J~Fp=hwOI6U295mMzYbJQVWB_=S`GfXC0^S-8)_R$KKyWR=jWO)P2fP zdV*e=KK;@rC^8~*T*U)2{i=EVWYi|HQXi)YVHsfTK5Czr__#y@F93hi;MHda?e0Hx-N>FjN~#;W zru47sJ)pWzbw9ngd3f_x(sND4rM*gq-Z^6N>dx)955A(fd|*Z288uy%vpuT^^oR_o zRwHx7(-e83)$^+*fS5Ls7n%Il0aDsL$t zoIGhwNp07$cEzfZ;~m$JTDDyLkMi#N&)@!$rM7=W`s}$qox9w;;OoL7fIzK%W@Gup z!gi@?JBN=R(es}E0|$NeYeq=C<>&)Xy`h{`P7;C|esp@>Taa0*B3a1h5de)zAkzVu zW&BQ$2YF3iq}3YDI&)p4jAp)|HIIqWT6J}j| zW$*UE{PN^JJ1dm;l^`1|HQf8lw%#eHtc7{kDQh2$9uteD#aOE{)CZ#sA1|^qrbNok zNzOo0Ala8}l)~Z6Od|k(jBWriw6mdPRw$&iB{?lN1e)0(kmjc8ki{hEVh}D^7T#lZ zfnr)uD;;a>iVuc|V$yK|8xzzY;30)T%%apOFBCgBz=Swe>#EKUGY>uY>8YN|rLFpJ z6{Y8?vPko~9;3!L)Uu@7hVi4kAAZQ}&D)OxHtFd0X5}Z*`P|0`x2;?@bMcZTi)TIz z>3tW(qK^zzK^l0>(EDbyw^#$waJ)*@Itcr{iOPk++8yVxT(~1%K|zRa;#dY83mp}( zu4ZZ$?(pa)xoc~6YFx(`TUwNxVd?II%sY-jj{8SNf5QjRqoSL{dPvda<3;2L+`9zs z??Cxij_c2*N3J!zA3I%D`t&Cv`-}CzL-tozO}VW21hB{C32?F>2hr!?t;p}z9|xE9K)Ot)s)iOfGClPf4x5cq($d{X2?0OrlPsZxT`Yq!`2%hZ0@*S6>@+Ei|0cQ1DTLH$$jm@Rd5EM=4i}c`f})^jy07Di z9R6*(0w<0^T*=eGe%P2=om5Y<|?;GiUa1bVSTfDY-RJ_xU?hI^!AqV6?FoR#X8`-Q9%FV4{MUIrK9-P>zxhY;~?s~)wqk8;O zX57ZytYg1^ML+oOd17+%ywzHHxoAG<78RfjR1r26E6^oEXjAEW0K6U{jV0uc|xg!^!U@qR)qki(e*S6un&cMSM8rGW`!|AbA=po~5hC#N9dx!ZMj>i_ z9^T=2)frN-)qaLr$51BiDsXpvapv3avXbGW7u?KB`qyKIGpD@#;JSzEhJW;qcp^HJ zFh526EdTOb{A)Lj^EDr7X=(P$CzPw8r6Dry63$ax`G#l^48Zn~SUH2p_D-jW7) z3o4H_!D274<`eQ;$~yf9>;mu0I@D#dxI}2q2j9o!a)YwhUp7E~{5f`ow|GwJawyE3nCIo6g%+hAKHN^KfDtUoePh5ewC+td#Hi z<`Ww?HVoaUe7$mI|9X{|0(FWif#q{f*{9`pHTKRMizNiN1!e`|H&N0QMkq9>cf>2H zQf_QQ#c`p}bHr=Kc)hK4ly67em}kN1g+5l!i&Y$IoTT6e#_rHrN`*0zlC)HqRh_9) zVPSs0zhg(Uxf4IQ*y~ z1jN#1z=@6Q3t5fMZd2#85xaUA$6v!bz55U4+yO@=XCQ=^D12WhoCw z|NTCjrz`{?!4I<6C<8267zAV;w_m4Abug${X2i6^wg=${Ne9UQ)j{61(Iyy?$sfb8 zLhp)U-G`T-q8Z4DLh>H|(>8!F4hjSt3xoa_dp$K=>Rb2Tv)psfIrp3#i*WgH9KIB8KaZk$ z(k+-&Gank8sSE|P-4$b~&>FJWfb30ErI8g)G0&smjDIpp1bJwuwXM(i!`2ITUpiy| zBAoQUH}_3C=6|?WTq!C(eQ(Ox9it!ogHN6!FM8@vRh673twr=SkNM|_ZqrW&yFK;* zut6tj3URuDGX2+OyNj}r#0*i1$o&h0w#`^clxp1BNRM;Bu9uZ#=SIj)J<1RndK0h^|5J} zEm-&XuYAY8HBQxh__x}ML%rwE`{?SchJSOmNz;d6BTPr$kh2rzDl`x$jNuuXiVODf zbPv2gDC08}CxPrPIDw4pV9bLZ4~de*>Do1de~1g`2`^W`g91u@&}P|sXo~*rw72v_ za-CQzhfgjPU9b11Q?4@3SN8l*{go-A_3Rkwn8agr%7^SG@S0^v=}v2!LqYu(8%YK) zjE+e3=d|L)zSHK$86SmDktTa&vKGq*mIYBz1WzN(0d%;BdWD}LWT7&-ML$0LE);e+ zj9~6}vDa~njPmBfJeoK9pH8333x{tQJ%M_LwE_E^=dpt2N|zTiNEjB6wI4{^-67t;8OM0Yff)q^P3&|S8pTrtZPvMc_2Ydv z>wkG!KZt;Bhgkcxn8U~_|8i2cUmVaI`F}ouY#c_G*Z;XcTp-xZW0e^I>uD}Dzv~ix zF_zo{R0)uLdFgIGW&G4pK^714EM-)L7FN8P9R$wcDs6h-{cDB#sxA=#)=27hz z#g_^DaPJ5*T*KT&nB!rX>H0A$lIZkfjE)DH450uj=@MQfFXGYIUWJUS-csaOrNiCO z+!f(14Xx|8r*$VN_nl*Y&|UoF4dPSOGQ^2GtMAbJ_9!Yr(?!Zp7t>hGREw}m}$}dzcuD~sc&J` zvT^$RmYDut>*DImbbVspSz-NnQ;+P4V#W@Ay%@Xf*Ck^1%$fSyl3y>?H;drVzUg{z zu}AISqQ5IY_91pC#Fw*y% zF|q(uvT&{_uSCHZXPL8XNUK~}h-ZEINI*NZH4UpxTAIBC=d+v$>POK}DkDQ&%i=n@ zM0TbCfuxQG3Bc;b7ad7~uJOaa2tZDHu_ZMa`IksS$iwfdjUsg?SX%Cj3f} z{nz-(p|!0u*56p<9^3TPx7!bXb!yMHWY)pAUi{U0PcNEu?V+Z7T1HQ`eW%^!Twk{9 z>KmUy0>Y=)?mwypLu0Jg&?iuREZc5-bc5Y8bxtZBVbc(8(Y5(i_e^Q2$2LnU#{8CB zr{K)x)b>P`&_v?2K=Jy=OIenFcTB25W(X^J{i9~jF_0Zlu`t?Ar|HfEj zY*1@sqj%uIeiFHv2e(RzbL{=xMHK^Di}Jj|yaBDHrNN-L9#uQcMZSDyH?lv~MAiUakteA^4+TUMt;o#QuhqZM1RgpF-*8j;XpLpWaudllf8DHil6TSGukn zG3p}g6!*G}xn(z9c)`*FN&&NFdoUKOrrFnW9Cxq(-9w2!NCsnOipeQeFS#H;w0_!< zIA`O3=IlA%p9Z<#E>*=Mcd}Lw4hiaH_RQW)zVy^N5c?rd_-E?Jy zF?CWASu<}pT1oJZcBt~v!CrN-I2S)t+nOJI!P}POdwSb_&*sY?~8GP`!AxCR2rJh5fUu5Vk$ib27oE#XcP~iw>Di~3c z=h=VWQ?M7<3O!93s1VoIv9FyhQ!r4qeRScxOud(D&XmdXT;k=-*`2LYHzH}bbQ%81 z8|KfOH=igT|E!zYp5BLIL{!zD3Fi)SSh`$b_eBu1ihLJ*_eO^{?#&A=ru zFUBUHm0Bt->+OZgqaZzN-}*!~EP#(er|l|AYy4W%*7*1Ci@8^pLzZc<^`WX7%D`CC1nO>NW+EC)LWF`>Lyb2lDi}Cj2(Cq! zj8ng7>=b3J;r+MSAKI~?lf4f{;FYRf8XYq0{2|RX!IG#SM*jWSc;Ecsu(?-?F*na_ zDEP`-8$Wprw8*nW))@I}vc@QvTZ6F1(0R%b8*Gy2DG#xaXNB>MHdonyj zTQl+THr| zc{?A~220)&1NC5^?|;)WG~Vaj#buthKkGY@x9WNQDKP|z^!kaxCrdVMd-TEIJo>u_ zF?(x9%$q(=UUFTNpO(S2zl z@CwaHm!Hi@q_DgkGZKNQZK#7~x6U1aT{n=`VMb6b?EmbHbZ`Swq0M&IvuqGU-_A?7 zWfZ+7nt4u!4(pnrIXTfiWe2u)FcpI#X+{PwI&DTmrb+nWjuvC^CTY!!PF-v3YHNVD zTZPO%)!y>r++YUkRbbmwS}IA^h@cLY2Qz|5H1Ecd*Wz3|iicViM7{Tp?&qbuci#SX z%MKF)u_sn+7A00fK1vi&9z3iKS=l(V%P|pjU7@)v>2r&%D>RqCmCwJtQ2!9S`Fc^| z?%%Vq*&~WY!--`#ugN;QOWoPoB3u7DIBi-;KMH~Pl73Cqf&~>wJv9JCu{6*lQrkD6 zU#CC+>aoh|qN3_SuwmUibnsxLCZN22Ypov34vew}nU-snBCjha%~nxSUCr73>av{9 z+F(Vs3(ichmT^0{mY>J-w}j>n0lHReD&h@$q9q5vV$c_7F&33(aJ@>tyn{-A-f zc^A7?dcbD&eEVryb5qxWY40EV`={PHGyNa5om_FBw`BJW4G>2K~ee9*P^_1#E3Hey)X4+y@EBR(c&kry5^z6@L3H_eT>!0(bI7gYLyO@ zTBVJN;X{V?>@jj!BHG+MY*?0BSl|x9;uDfVppS|FLZMV#K@A%d9NN#;i`p({SF77sO(aIKGfAX4GqO!KK zWnjxlHu8phC5^lTM+|OB8hLw#*~oib|8CwzQ|AcJk01V^^m(}{lo9RTF!{2kKwUt8 za>9-FQJLL}ydHO6L1Q-@k7v)4ZI68fcIgS6A={J~(Z9JdHnh2>PoL&yr9911Dbr>m8~28Mg9r+~jqjm<62eV5}Kv`DKJ9@nt3BIqc_NM92>KG^01NO4ls<4{qCzIFSvt5!Ytgcy}sc&LZ|H#nn? z=zp3w&-OJTCDuK1_S9Y#gq7_R*VslmyB9N7V%=H|NeKUhy)XO|3F($Zb74X69^GSc z6nG&|MfUQ{TkRU}2 zD*j`g?0@dgQ2cN!m7D)J{-=EL?Bm17h^E$YaBi#|5s%#6a8|?Jk@)cco(JIQ7$jEd z*N$XIhdj4++jRWf#xu)459=n_qN=4;i9Y#hd1@$7SyWn9R$1wn3*bzL6C_{F%Y)uZ zrcS>=TKHNxL28OZp+I>tj+6xg)qYjDw7fjS8-iJZRf1*^^Tal$4m3<62>+9wX!=E1 zGcl(VTO9}>SVCD(Ya%~Mk&-Ob#-5T+Gbc9c)8IF$bi(ET&>GQuzuo8cSkq=s60z6b z*FRdhP7aQUH>_{q|A%A!y>{;9`Av`z3)^nb?$x`y|1NQZ2oQjqrg0A=vz=alY=B8F>(TN;S+-^ zWj|tMDOH>@7Tf&msOHIsUrrtO%-B7L#eLnLY|#(+k`+(Lh_C4%x<#c2^vLSb}{P{NIWkQA`=)NRW_?+g?`w^h^oqY_D2F5-R86?M^gipjsOGG8b zRxDa}t^StVi#6R&8HrF&?6<$&u&uHUenP}#pZW=Przu;tXQy5B`7Zb(-4l=DCCxvi{^E^cuwz~={5C5=M1|3X}5Jl zWnG|Y;e`#|V>w0fJ(3UyEqjnHV+{656$zJ9fi>FU@dCi?heGLZ7|dXdU?0sLMVw_K z7zBNxl*vu<+<`3!1L@yz)zjzO#&m1U&qE%fH%m=Hz-buJ@Md_ zBVtc*b}k%!gP6;lBYwl_i?AZ^T;7F3O=$VFvaC(-UgFiXy!=LzwGP;ob|Xr3EjpYB zy|YQ`k9F_@?8w(A1`UtZHrLkXTr{g&QJ`-iFi~;mr03+!nmB6Ks5x_|OdLIP`gxNk zO+Tkk_hIp~&+av3$XI(_WpOk=6e{b|#W!~B^yzjjJHuyD`BdiXk#Lk04t6WRdadCv z>{jST^4gHIbQ7co+!N;a@kvMgO8w3Am~G;!yvYllF7-zfrntjj6xQ*-@mc4uDPK&O zZy#puadu0@Iyimr_yxA)AJ!@IBlvT;wMTz^HpDvh#HXG9w0z#_&(l7&KAikCigthc zQ}Qov$+~vxgxcCr_~m~cn>L; z`T2qS`W#ttVr<`+h8Exhm@QFb4huI1810!MbzOa%RrHc42vdtJ?X*HyrOKQoXKA! z2a&M@D`3=&kU<^?Q`_T(k*tp_~}mHbiVSYogfKCJ)LSN8}2u@cLX^3(ntYQVB+f^SdP|28M#M zE(?MOP&PK*Mgy~BLseRWs+I{QGu@)O-2QZAvrYG>m75kA^0sx&qj4J9NX{_6-%~iA znb6r79Vc~Wp&m#L&rI?xYHrp4x;mNaIpqKQr@uKcT@~)M*7?(?IAj-cGY;VF&nH(< zYS&~2Xz}3-za4bv^_R3G>AKNGKVZ zSly*e3x3kwN;(#KM8@&rYN+PCpEf-FB&V3PdDleI5y@x?%Iwgwh?1z$-$@lxTD@8{ z8C4Lh#rg)0lql8jqGDbsr6l7tCvt*vJV!2e{UKX6a`@&uEZNxzH*w|JHHk8%B2L1A z6Kay=l3fUtaQWe*PR3qAp#EaCgqLE`@q_9WM$klgD&gG*L`WcnBbh&RgEE_=C9I9_ zzyGS zqH^kYV;v zX+<&W)Mt57;W1DaD9*{m0cD(0%Y^qI=PZi{wj!e}v@(nfIvF$jFN_a^fvU{wHjU17 zs?Z3LUA3G`)lxDRV;jdKRv{;H=IJ}w- zeo;Zq$g_A5kIhCVu2}@Di9EY; zkp8-|uI;{AckC7ocdt2d_nb9@R;*m}z^8IH1vfR{hYyJ^JC_WaSEX_~7HO z?4zD<)K$(RUnsRE>avii#*1U~QZc*=UN2SIQC)ei=GMGCup`a`_*`B;&aSG>troZ- z48ve*1jKY8zZ9o24N~tbxw~Bai);y{$-($Bm@JQF^^y;6yZO?(#SND&)Av@_>u)Iy zx-@9;eJ5_{|D>+3j0$^B(Svu12Ahs_<;TBw-l(6XoxFsKKpxCFF~^)l6BZNH;!z-h zv;5dpQrYATr!xydE2ue{?E=BM{B9Ik^Fsd@9=|6en5wcyOAO?%A%kg96l`j08W<2? zwm(7WfTEy>Dj_BEYruOYWGt)9vHYc!&{_A#Jc8qvjx zm>ruepU0+9F=Ll23n7Eq7-SZx2WOpdR>O^894w|jm&@blWX66qxoFb1In`sVn;iy| zx=kaaLfU5-35~BqMG3!9^>2RNxu}G7eS296{cc(*JC>F}%5L~KCPD_Ho*ZIDpMcN8 zt}GYGn9f5KfFt2rk6+RY)l2MKvc8T7*Lla2mPNZJC)4 zC(hfzd1Q4t%qoaR8AKje$Dy*-z*tX7ZqP6dl)>UYP=9Avr+R7)LQNtA%8gkXkNN~H z#@Yy+P@j;MS#ilo{-mb6kq;7{Zv+@-B2ORoWGJtK-5HL@IQ%xo0>}u`+Q7mubPFe+ zJM_$~_z*(z-ql9U2L**K+CFceUHtW7+isRO@UM|<^-32e!s&LW4LkEpB(=+MrE6NI z1CO92n#*rPniLY&c|XQQVX{5W!hS1TAwbEgAHaBn%W0OhOswX9+Eh?PEcG=hR>)drc9{EY zbQDw!ot8R?5D9_O^5q%=k2Z>ei`MQ!elIGc@$BKX7wR9=3@#NtR(wIlG=%!Nn6kKS zzp-jqD>0uwX;LEWMAVnV;`Sl%l?uurcq$9G-04W}E~Cz*9RXHQIzSvAPr54OkcJWM zW-MIBJo03+BsAt0!Uf&NIM7kjo1*9;W$(a$G`r;%C~~V;FA+B;CsSW>Emk*cc-1Oh z|9qSHo@P>>F|qAO3s}X-5BwuEyDVu&B8p_w86KMhX>l@u^qe%G!vY~FAh7X?2la$A z+&(|@HA2&)UT3&i!=v4?A;6hNS!*ECO|rtclazZz7-qj|4Ys2|UWcBOve#YT>$a?f zBX*45AF*5;^%r3S+F)Jqo8P>qZ`ZftsBV7SDzW!seUoJyNp4Ut)oCQ2Iv3S((yS9VO4H-^Z7NRm(dqG%W*4zvxdqKY;=t%%}`0-k&(QZON z(Ds56fF&BAF~uN4S207V?ZoFdNCr}VX6nnpLS-2yWqP+j@`^l}DD8yFL1Au_?SD~mW8Ol4F*#>cHz~=KNk>-Sg3Zr}vRk2X&R#Wq z#mZ~9zVk`9$q3SQ>91SHE9LG-^taz6rPG%WrSqLFuk7=;ouny6=V?{YRogq5$s!s@ zp2uVJCcN3CfR-R@X))AKP0iPuTl4d?kzIa3YmUpC{WJaZv;qi8semc=bR|zFk$z_l zlvBwk86gf8)b`He5AI+Ubiwj_?zCa*Q3*+Tclcf3U)TT1Q_?C^1-)Z~j&dYatw%4m z+@!Y=pDsGJ&H59p%poZzElKn)aVn8WxH?=>p=B3TS7Rk^D$umXim+52uC9#)YlpT5 z-9A_cd=}KGQmm0kX>n#T(1P0TD)hYGukhgnQ+39vO;Ai~x7R*2`kb*uAWFQAEo>G? z%fo-#=@3kLiQ&g%t1$F!D`%l&=e+gc-;2}J~vHJ#2P6nD=W!Fl!hI7vQShc6I)>v7dOtCAg;}v zs!qgAYeVPBG9Ke+&op0UP^<-p(FXCgKQ7@rhWL_v75n zyVh;73|leu;U}JXsB81JSB*}PJ`Dd5SG*12j3w*Ca#z-q$zOAX^kiv(abIl2Z;IC( z1uqKa-D*PRFIiUcC2R^Z(vc#SN_6v*V5|tqZKYwz$E^HpC6K@3Mc{RP0W$C`Fyy}l zbw+i{eHun)eyTW?jyy(%P8d2 zS39y<^GXhqy?-30h5gJYSXWxa@W=CtGn^G=+eM2$6xn>f0y8_#xuwUBN#eL6Tk&%0 z2iCfYiTZS#OHM-#53D3ubp zd~}LL!HES6Ho7hxV!(k6h<|Jp{dAzxMzz6wnRY+z*D+J3=udVE3X|DcVi?E~b0I>K z404>BQ)h9&!G2IbBFaynz)$pl3Hv9Vrx5B1_Hp|$gQPED4kgT38gzUdPlpPHG-7VM zgGxtPak2u6o+Cq5+)!+=rAzMU%w8J8ExCDu{tUh*J|dm&=dfDPX~kQ$r{sgnc)TbJ4zvmsX>D=1R}=YFOgv zl0Gx?yBP%F#Tgeq6H=}yrKM4^QeP*=zWbgStKa?^c<`I2j((+Yw%n-7-Q{w9gP78G zMBl__>E-$gLlW32m!C@-jJ)xZ)sesvopc#n7EN|%2H`AJY_b9y8%$9WWCI=XAdz6~ zRVXev>1+liXmqxgi6FVUo4>_66prhzUo7Y0p!8*iih$y7^}1wpG7=gbq{A z+%Xyh-n6P&F9fCZr=o)TB;q8h@N^71Z7F;g7bT)dnv)GHO#qp5sx=C92$fVwWqCMM zRO!x2q+_2^oSv4Rqu7zNg7WgUQrM^=!eM8El>s4Y`#?49lWC_Kq9H>asf<)0yEUa8 zPzXCQ%Akzn9Z{}Ytl@1#(rn1b@k z0;or?_uea#^d^Tr>tHa?jEi>Q@fjy-zvvO)E_=_8_+kJO(JDT$Y=Fp-3o- zCrc?F)T)>zacQi1G)_`YP5)CzE@>aHXrj__Nab!gUXjQHLl2hlz%gzl=(3{*o@gl* zS?^)ZZ_Bmbv1;aXlUrWAUT@Ri^6I-n2XiOMl@qT_6MN)E@?!`V{rk7KUvq0s-B-aX zVLkT2q_g9VEnh8IJnu@vCgdVvvjS1E%Myulq%#j?!$=0KR^{X%4k;r$yG{iW*=s9; z!Q9GxZ{?8Isx()^g7e8a77fLbB(J3?zbqedBV|A>4-qv4PseS0;F7sSlS+XV4`?<_ zUkGadB?L#A1dwI7)I_!N{Ezy3d1uD(^F#%+|JPOe;pGOd9aSh^o_Pi|6AdJKrRh^_ z3zPVUG$wpw!li2x{fbLUDl2`W5H1^Eg&)WBQIy{23prriuvV3n=i}&6R(VcoQOGuB56&lmqgCK48i)vh1DF1GZ%_ z+<~HLtynF_w7q26C&ITUymUs&`zu_D7k9T))gzX^XdQT4IW22b=G}Q z6&%*(mV^X~>qcC4 zbGrq0>=XJ|cOdCqo`3f?@l(#v%aeA6to%uXp zR@n|O75TP_w^9ZGW8qDm;RQ>jy5f`Q!~#2$H!#=e+p^JGgs z6UDNk(Dp<^y$PqeJrUTRC_#b4*-7sF8A6>e{WwZ7D>*pRKQd9BBc&m2wPN)kz%S2}ftnR?10$7~JD4S> z$u=w~9tbgyd19S6v#eqmhV_p4!jcRH6Jo5rfCafgj0mwNZ#n3zT;G`HMa{d-Vyq20 zGB%s^^*+*d!_+&(vQ&RyVcQtgsdxanGK^ri9Sv6t-wF27z%ODSf;ptTTZsEeSF`i8 zy|85<$xBhYOe?gxvR&4E4_vdS3pCE^fljhd;7naNg&HN+FINJ=AWgFu;S9ZGh&EfP z8O!#OyQ${YclXycc=eCPrr#Gu(@HCApAZ{O2{*sj+n4B0aeU5e{ZGQVrI!!gH}KAD z=ZNhPc4yWItSd<)fX2ugfn;a|a;k#kDCCG5&-Fa};|ZJGghUj`5!JCCC0d|blH7tc zQyRtR8Wp)bb*_=x=+2dAxeaM1YJyTl;j=nclC;AoiyB?OVoe3#u<6L=XOp(@->9u& z>BIf3GW(fnXfCd#=3?ZsqPnBaMn93z4gEChOv=(GwCI6mOy_CA1&Dc3)xun;Ed?5) zb>LOCXZR^C74K;%=_9W}Q0WKvfA}Y*8R5(RLMXVJ%RO`!9qPOe2=dBUh z4~ntkTK#rJ5N**Pu+(I{eaC})(%bTOSPqOED4ge=taG^%pgvZCW}#xc=%<9^;#qdMNbIIQYDQC;;f03ciM5Ejgzc1( zB&$qnG>UEW@hW-jMVG63FZr?_!iOFDC#R(E_3!1+t#@93(KEBgytw!Bg>#lEkHKH~ zt^S4nhDiSoJB~o)tJ(_C5YHrGT>g-70gQiRV!R=RaUKO>T>cwjLz)TIvO^7NL(_yV zidGC=RGKNqa4WJ1rdlzyQM4jh8^tqj+@kZa!4&08#C#p*b`W>My{R+X>5*!u1<#aI zZ#aMpy?N7UrI*nPBxF}SF%xo$VkfI?xw(Q;Q)gn?&CkrU;qFkBz;Y^19G-Q0tqNP& zU}Q7>5bf@Wlup}WBLsUTTz(?Uf+`}VCCR`ja!a9x)r_{!l$Z43-=n_yb4W|p_WKD( zXFUASAKqH3jKvmLdR3pI{^gZFd^1egYa1`VblL?NqSO_(@==gPdC~~PdFJJzuT~l9 zmbd=s-m3M z-8rL|QMm$k&-G(RrNR%Rm;^;nhBnXd`YUoTfx4`)27C>!B zO#D`+@XKv5ez^^4CfXn>DNj)eGSyi>qgHSh%%GAz+=?RCq*@`GUB^;t1ocFki3&?Z zvtg4;{$Kt8IuVrhe>Q&rFr31Te3glty^I@+68<@Ec-q+T1bc09#&pPDq-A>dTE*s!Xb<_=8cFk&umP zs9BUFP}Rin45qQC!GW!&!(*d>6l29~xA}G4by!BWtv77x)}5!MldN5#N#5zqRW?DI z{j7E5;Q6`3u~X|Qw}G-ruQQC{)1$2?ALoKIjuvs(}km z>|y;3y`Ie6;E`g9JVf8w?MN*qc@?-&;4-&5Ft>g{6kq^Ms6m8F< zC;6a|^AaHtq9qVO7>(OqRU%2jtX#D`w|oeSr+QJML@A`+S#zPipkwSg!1_;4L7F^F zP7>t?uE~~>>?j+q*^R<+=)CDmb{!$!<8ksceSv6HhK(BAA{xH?`jXwjn#Y!X{;j;F z`QFtN#*LgTcK+8t<%Sc_^zAckzPL%R+w*2i&)$QMipMU#WYV-gU<1~AZTk`9SR&O@ z?#ZIG3PhM8QWXBPocd}-smlPTA)sVL;SQQ-9nr5rYugWjUD9LVaEy>D&LYmQW zrvm&SVZkqs1~f%orW^!xX29>m;3xrD6E-jgu8g6H+wNO6_0pl+hmF1B@O1}{9r}6; zBkAKKCvW}Ldiilm5mj0CPJLkG>KD4I!55Y=a`wJ)_f1o$y-g#@g@tATa8iu3c=aA7 zD+`IBGU(8)s+Q)LAc;|i;`Ml;@}u%(VIeZ<6!cZ!AIJp9l-heY-FA}588jTEmgwh|^bGa>}y>gQVPfQK@Q zrYj>fsPJInzrBl0T6MW z2_3LBLxACjhaG?D3Hu_8+ z(+{z36;q$JBFRMIQsFYN9DFpIqolDtMTvv`?b2PVVm}ioe z|5=Z@dj_c_MM@ACFz_A{cZi7GaxS`E_r>2n`%>0t!N$w6$!)nr8kp=?5A1(=6lGh) z4Ab7_JIIhiM0=DL78GRqQ9c><9*bPC(jx>1t1fgir^Skl{8n>TY4Pp2`=)bW#Au~d zA_c}8%KuQ3%>R%i4y93k4rvUZL#*(M--)3g3E#)FE`4dn3y0r*d;Mej8l`b5PR6Z6 zUW)0Th>=gt`l)NRe*DX$`onkG(M!@CjzL!BQZI9Ja-3dR3cPlEUIxO1v4rMQ!553_ zwjiOL#hl51(@W|kZ3T@!+Bio=W2t;6YHK{NQ?Y=vMeW~S89(@`<_GT){uQ_Bf05Ts zXnO*Bf(EaAmr-Aq2OoY?fA+GAh(@VO4e3)bJfm7JwgHH^BPz|-aE8%ClxcT*5K7ce zl}Y8rlvONvIXtX0AreL6NB(N?s4+uC!`Gi4{l*M?Owr%oHx9h0^yctK^dyX)3;f>0iWLo6V@3^u zBJuGT-^d8Fn)r!sF_nhBZ@l)3EYlW58Ut({O6m%pf-HL_`J~DU?e^-dva(R6*X{NW zg)h1osk;!4;bq6v=@!d&O~JJ8uwqY29WAPs_Sjcw!e)XGipwZ1si54Nje)?*8-BX~ zaiLtk>i2K1T=~xDOICk*bo17$McReCk*Di@edilb-~Y5_(!~#t>o@F^MduElHs;uj z3#VV$5Z1pQ_T}rhKJB&TK7+B4<*gEwR{;+o3fjROiTHVIK|uhuQY*a`a)k^pdj)ET z6eGIFodJhnhB>z>-MPa^AdYyBNzxnLrtB#G_Fgx}5Nu%t_L4TFG;g`}HX+UZaLhJj zANo=M?CM{?BgE0UpN<>x+ebfId5iq`+x^d3TwnFW(1$k7y1qbdICRsx+NyU3&gdJ5 zzU|iCHloaccnWC2uFrx6PC&;F5m#1;A zKs;fXM$xv>#-U^z$|7qi?|v28C=o$>R%%7JEl;+hEV5vyjNPvyhf-FA)7!biT5PtH zpWH>CdK>L@@6gT_G!Aa3OXqeRgHrujoa#@z#h3cC#dbW^pT(B_W;;DjYiDw*oh8;n zvp==T{_wcE+uf;lmZZkjr@b8=IN=H!m}4m@2BR|V2+Sno27Sl*FKUnX_KYW6iP0W# zJ@~{adeQ<#lAgq2^5m1aVO{C)rh2#38ZvvA+qrkP-KpL!wH!1@S>N8f)7rVx%onN1 zCBN9tm1e$3MQ%K;oiVBYEK9Z1Zuw+dLg^}#mXu|w{v=Lor+wD1O0{E{#*)<3an`R& zwPV^+=)^Y9`ZC0hWnkxunTL-K`*7G4WF*p4%rq5IWEhLYQ^ij#@tJK$XX@K!3o^mm z+VtBh9i`7_V=#A`8DfNxXYy#nrmqTDX4K&JE$F zoMr!ciK>_mA*>*s4*Jg}B!6QcbgvB@@;|H=_(K(n>tpMqEo?D&ktSNPJzw6HV{rHYO$8 z5F<`~d&V|0%{FYPO7rs;{$VG+=U#xPpJxh`g&{l$Q|Je31ry(MKk$r^X;hKRk{CY) zn#5?@+quHB+iWLA$*11u@wB&dg~<^e70CC4Q_0_?e&@6itN{i*K_zBh3pjN#EN=_A|g z;}F;Xop3hYas(C&;LH$_c#h+&81IO)BCIOp5$lL|I3(mAjQ78rPrybx|aJW=XIpbb=*gMvmWLz_4Xh|DnIaUqpd$vXDpMA> z0?MLu-?X9_Bu>pHa4PFd}~KW>L9%jnPIRDarS{zMUvGi4e5 zS)A%mG({0cJ49JVJ4;giiFF*8WgSzN(asVJ)_WdTyBC{r!jy$}B*+!QiLhg3AxXayA<+l%+<)EkLSqA4sZw zhVf&(DP6>915(Xs1K&Sz#x@|;jB$t&DE`o?1$_ino-I&*OOyCAdO`T&UI1U1GioD6HAXK8U)%Aa`s12+_QEnkcxtM>d zQ)jfm^AEzL82}zpAI|t5-$%M`+=pJkC&B_!4+AgMC%z9~8RKibao;w|xQ|suXMEv# z#>TtcLy?gjC41@^U-%|J@c@rfS=|wANA^807~v%Jvq<>(5(} z9T1@GgGKCzhkG+-ImFmxeO~?wy$~oGF64BOEz6fznq|)-r5`vi%4gF|YIi zz5ZM#>Pn}C+76v_sF<%T?=~EW`+JklXaGNrLMN+(4J7c5LF6Mb=HOW zPaF2HQMBGQ{6<3;r5vgha_VyYM>cYzMg92miwEXibm@f;%$qK{H?AsZJ-_k$ejC?W z-W@z{YM=aT?reFa_~mhP{H+Vodnan(<(luvO{AmXGUbMmh3r6)`3Vu2E5Zjc45N9Z zJEWJz@vAYKm3=ad7xZYjs_KCYFP(Yu17q&mlsMLOp5=#Y?rh#vo8M>Zqyc*tjrPxj z!GXte>V4aB98@ZiDia!x+fy+;x5bj>Di8&^zSNL#5R5i{7};s1^|0IPJlN7@w&iNP zZ)Ht+W!8X+^Y?Wcvu)C)xjiqFiwpfl>T+AN9%>&udeeYW=Od&h8>a(837;lOI7Czm zhnYX$Rf;0Ar6{Wtjvkp1c>^g3Vf~^6c9?~-dKo)tq^HBanzUBRP-pXY!hjyH7~I^i zpsFTge%h?_Du*`r%?pJx=R0TKcJobR&%5KMt$l^3%hf$>#kobfr44hh$t%jM=z;_} z&kj6yP)q)%NoQZz-Rz&uwi{IDmw**i!%kQHu1t}MRFu>!Sm(%$WMaFL{WQU-aqOTi zJ|KK#{DN!6Tj_f?KJ={PPRD{VV=hgz9=~{!f6j$1_wCrWdGvV`krjbwA^8^27k2BJ zE@W>-!YyO${0l}6(jZ#iO0b?MrfQ;&oP=m#8^wvL5Jb8ce9d;Yz9Vc9JT_vKV5J_&Vz`n~3ZP}WYRfzW?vGE%dOx(K-)`6?T ztgQ$GdL#J<@T@uM+=+%rlWmq74R&uV%o;FiioU*6XKc%3eLSH#X*sze#|&G)b*WCJ zdgVqw2%MfsWFvuHSt2c?xh$&)NkObGXR%XuDsWR13A_ptaKBLlh#9925Yp-Wm1iV} zlLzcGC-2R?BW>415AIHvZA=a|RUn7S<5T32KEoz=AF$%qyY9cC0fbOdKV>l!!t8kv zJ@5wm#74*#5Blasv~5O)Ly~-60GR&M^O4092V)Hj6s0;f`6W9R2x2eA6(o%~>4Pl; zo*dS<{DyghM)Zi6*L@V5-M7c!;o<4av$uPy@E%w_Q=)CoGdC%{+Rt!&hOr1sR%x;yK}$uZiT0GiV;dK?y-HD7PO3Rvc zj+)YS&x%FR0wvoLpLFs97kya|lpEj)3A8;wdT3xwGH(b&=0+bupJ$6$e z!+*JXq+Al~re1dG)M*PYohpB*e|PdH{qMs2(?5kv?*7Q{e)q_>t&cnc`{)4?5rrZP zVRvs}mU?1raahRCI5zjc8yn@53&M*XNBRP5#(6n=?i9y_Lsr+UbM@C&(dY#2Of0=I zx;)BV0Y9}*NKyf!+wvhtWmQvKTD5?TzjIq!=~af&&G^-B>}U51O=zR&g{15O5_d`8 z9Qxodwe&uzhzj&Zl_6aJ52_Ny^nqHiBX6kbi!j0Mw2ecgJ=<}Tge`SmW)Pc}xf82D2W zS&A$&IQB|8qTp($0}fWJ-{uompLKX^n$M@W>}~}E;NWWZ3V~lcSg|4RFx3d$!> zoaxFmi9nD;ma@X5z!>?KQJu*S!!9DN<4y9kKb_G3sDJjuox}b(QRM6IHC|n{G_K^f z{g_|gQ=a_ITmR6H4-@Itjh`R!JNwFCz$W{lJrg<}#cmC^zJ|z#Tu3+}#r5Yc!!7d= z16!N$Bh|Q!2uFu(=fKMrI)hh14%pMY;jnERk(M9Hm2F{5ZrfqB^0=_Pd?J(dNP5R0 z>wdf=AMcQ*zWP?nAj?X;v66D`6k5GbXL&i)wLs8W;&zt|hlL$J;k;q3Spmf{yp<5w z!5eS9oe~T)(RsR(UI(X$c^ZY0hV`H^Mwuo&IOS)0=W(ua5W<3Qxu)O9@%QYT>2P*i zcj-%i5$T^k@b_!lzMXW*KG&V&7f!b;u5%d&E=|$=JHsmx@x`aa2jdbwIUsp|`K~Qoa2}s-FD<%Wn;F1IM zfuO*9PuFCsE>JW_;`2HICQKgBf!GL|6&;dpBcvtC$`VR>wpOl1z1}E(#mf~rcg@1N zV-}353zt`3(j2_{(hKIr&gv1Ytg9Y$@c^-Ev(2*U_{Oc@Zn4-NQ?^f;U-RpxV0nJP z)7YaWHsR9PvV3G5EUvAHPSC%SwYmC4`H(IPy)7)(EpGdk=pclsIpouT#vTV9*rg?6 z1Mero$iES{4#EA==6%Rc&+5hI{j$9$Xw`ODIllSV6ZP|R}`+xQQ zVU+H<=^-54PZwSh-E<4a%l)Svr?M8T1q%b@FLrvdD}; zPCA^5w(k6C(5X|WUd5E#X$P&`iSO8@eyHfqmNaK_SNAZU_`Jgtm!BHf`H52-v7%br zpAdufPmL#%XBtkmp^ZlSho+vvOa z&VMi)Neuq~`OfKWNNdp75wwG+e$f70*>22VYKFJL_IW%tJFwv>w9CfBlC(1-iF0gM zBVAlmg=Cc~PR$35LoQmSt_-^|?15F;D)m407~p_YD**>GY-Tv<)E?X!&hT7@Bl!M# zd_9t{&*$q=4976MhTnB9!|NEXWOzNpRSfUv_ddWaJjid~#PA`8n;AaLa0|oV@{?N` zKEiMt!$%qJVE7cnoeZC5xQpSl44>n^JTMQ2|e4F7r3=cDWm*IO1k1%|n-}NEGj~IT!@F#-8#?ZkqgQ1JQB{YU!hJJ?G z{An)3LWZRbD+rq+z_5nz)G-V*Y+x8=*vPPnpFE4OAi6Q^N8=St31R?^S}+X=rU5aE zKfQqAWQG?rT+B}{IdvG%FJ*WY-&w(LxrXmt%kVmes~E0kcq2b~Ge3C?U;monZ4B>b zxQR!-mEZLkUo)1)>)gVde4lY4-r?*2;%9i~#b5dQONL)F{D$9kjGz38XW%45ouG{E zBH(CxMjk`Y$gR`|c@lkBp3L`W^7Wmq!PJM+>m-GFV3|BE+&G06M+Zi&I%5U=Z zTm0R38UC5ZBEQGif8pyReEnCx{(ztV8()9S@H@V9jNuOil{9)@QTaNZubq6&^rLwA zP8Q$EW0=pdfbSPFETWMr#e7}D*QI=2#@FS1UCA)OcY=If#n&OeuIB3+zOLo#I=-&w z>o8w;;p+yzj_`GquVZ}O#JxI;VK;_77$z9@W!RtLAcjL2wlHM5q_9L%M)937497AY z&u}6`#-_s9RHhJ4m1zuTFr3No5{7da&Lyf)<}+Nt@D@;u$`Vy&iK?=I`|Efe1RfUJ1AW2kJB8jR>BvDm~ zB&sTrL{%k{sH#K~Rh3AhsuD?5RU(P1;v5=5lBlXg5>=H*qN)-}R8=C0s!CfKl0;P{ zlBlXg5>=ImR#l03O_fNZsuD?56%lg;NusI}NmNxLiKBvDm~B&sTrL{%l?VpSrEs!AkLRf#03Dv?B0C6cJBM0~AEBvDm~ zB&sTrL{%k{sH#K~Rh3Ahsw`1eK@wFJBvDaFiN3`WRTU&rk-VNENmNykL{$YzR8^2f zRRu{@l_jbwNTRBOB&sS%qN;)*u>QB{_xDoa$AC9292 zRb`2)3X-U*Ac?99lBlX6iK+^cs47cTRggqg1xZv@kVI7lNmNykL{$YzR8^2fRRu{@ zRggqg1xZv@kVI7lNmNykL{$YzR8^2fRRu{@RWO!WqN;)*u>QB^?_Rb`2)vP4x`qN*%WRhFnKOH@^!N$toaQB|2F zsw$I2Rav5{GD%cbCW)%bBvDnFB&sTtL{(*`QkJNyOcGU zs!S49l}VziGD%cbCW)%bBvDnFB&sU^K#(P>$`VyoNTR9=NmNxKiK?-`5 zqN)l>R8=8~swyN=RfQy~s*prg6_Ti`LK0O~NTR9=NmNxKiK;3jQB{Q`s;ZDgRTYw` zszMS~RY;<$3Q1H|A&IIgBvDm`B&w>gTw;l;vP4xClBlXe5>-`5qN)l>R8=8~sAL`|3KpaHw2z^V0s)eIvHV+`XM zmrFVe_;N{I@kW&^qFhzvXXz&+zXI+xSx+osvP%D2z>oRtlHGI92@gq>@eVRdV?H9EO7#4rMru z;kgXYV|YHp(F|J|j$=51AH ze}crDnnb**NyM9)M7*g<#G9H#ys1gVo0>$tsY%3}nnb**NyM9)M7*gh*u zcvA!J=t<&DO%QKtf~Z$x-qd8`O^ta|W8T!5H#M1fQ)AxLWa3RtCf?L!;!RB^-qd8` zO-&}=)MVmK4HSa@FmGxy@unsdZ)%_xx=*~R$;6u)^QI;fZ)!5}rX~|_YBKSrCi7UB zH#M1fQh)L z@usE_Z)ythrlt^YY6|hDrVwvx3h}0<5N~P<@usE_Z)ythrlt^YYRsD&^QOkUsVT&p znnJv(Da4zaLcFOd#G9Hzys0U~n;JM}KFse{hAP7Vo~!`Z5Udl}Ie>TQny9OSd8Yzg z^BAtXF{Bl(0$f9%-p=q2hIcZ&i{Uzk>lqSvRDe6^`PUh~!SGFnr1MmOI|!1_QvvP( z1a}a0GxRX@F(mG&VD6{@<)c?j`4vq06`*|j)=zx@BtxAbQ(%Q`r|-(7FI9jJ@gy)p za4^H642Lm1m*IH~&u2KAVJpLN3@0!&+M#)_;CZg#d9L7ju9RGOd!?i?tibb?n9n_c zIE4q;jbV4b--EAvG9)d!68kBF*D)jxt;BwcAZgT9HNQpl=Ieg^t&M#B6T_1XQKDU{#Jmx7Q2%8o!!h(cbV!253`-f7GpuA7 zWEf&t!?2EFm|+9MD8sqbKIV*c;Nfh$@2MCW1s2LCAAJrivg_MUbf?C=*o#nJR)z6+!GK=nhdu z5c>#%L={2o9S9Ot1hHQrNK_HT9)TcHMG#U8Z(*tkGF1e@O?wbET!%cfA30 zxt=Mxo+-JWDY>30xt=Mxo+-JWDY>30xt=Mxo+-JWDY>30xt=Mxo+-JWDY>30xt=Mx zo+-JWDY>30xt=Mxo+-JWDY>30xt=Mxo+-JWDY>30IgE8=JZi_O42fq^I)NeaY?wzH z25r+dNue+(oUXeu>;asHc^<+%4`H5%FwaAn=ON7V5axLZL;Io@HZZ)G;e8D6=XQwS z!#qo2o~1C)QkZ8c%(E2cSqk$kg?W~cx{toavlQl83WHPV4vz)e7v2c%i(oy&E({|K zV+@=4`Ln1G;3k43F~Z;)f+rd31i>}<6l)M>3~LZUk_KVuLTH~c5@w8q86#oFNSHAa zW{iXxBVoo!m@yJ&jD#5@VFM%34XCw?7*1t4o#Dj{XEB`3a4z)+Tt;vK!&}gw2ur63 zOQ#5PR)nQfgr!r2rBj5ZQ-q~cgr!r2xi7-p7h&#;F!x26`y$MJ5tdF7=D`T_V1#)v z!qO?i(ka5yDZbc8uN!WNx8e?;vJ zjbR1!v?$L~l;kA7kvt82d5CevGjnW9-Km`!U9T zjIkeM?8g}UF~)w3u^(gX#~Ax@%-c#>+uhKS;+Q?UrnM=KIh+qj@+8i(G0v+%oMmGi z_vo4akOpyBR1tkoIeF*z*ZK!f+eIM;Y#5NPD+9?D+(rX1I&tvkYk;8izff z;0p|CHy4*);?a>+KMt!t&EzY5y_c_F6)~} zIIR72eTd=P49VIYhqa#|S#jgA_7gn9khID;Z2okIbjvs_|8z||nmFwLbp0`3f5O*4 zQO|{qp@U%tL)!Jkg^QlywJ9z%z9x+{F1&nAdTCtv`8u1wmCLY@XaL@U|Bs}%50CSz z&;0ew)8Eo9G^?s~Q+2p5Xh|jyU>rkR*&fR^7ix%0T0vqaS)xE%#qWU_*#))Rb>H-Y(C@xK`RBgA z*U>Z2Ip_YKbD!@y&(S$Xe;wG$*MY72I^ewA-?s+t?^^@+_pO2Z`&NA&FnWyM8dST5 z1b+a22=q6Ct@=hFR`rd*=+SMfz7#m!tFl{zHB#1%v6z1q3&CFk9a(K9vf8SZcIBo1 zsNZU(U2kIq+^Y3by|N_Z?*j{Y_Cpl)#|j!wCC5 z*zd*m>h#ud6Sh~Uw`z6TZ@3$K3-$-FKZxzs>8)Cw_A^!3+rT7P4SopxF!+a{XOUY& zdMhLNR;^U~J)R?M4QsJIQ`pLAzBQy5Gp271>BWrfTeXhuUwQ7ZRjb*y>C556*!1P_ z5p2&UwrYLb&p3+Ms#R{=Z*uqlAb+oo!>S}aswq3fqAp8jEcDuc7H>h*uWPcpI zAAErHJ=mYXPGRdmYxSgSv0a=vuO_e=OoJ_825jZYHn1J+06W1hup9g~xqJug1N*@N za1cBUeis}9kAO$P95@1=0KG=KUCL=Z1&)HJ$uSR(f#cvLWj@O@UW47PFQNWz5qk=| z#FKxEJ&pYX>>2Erv1hSg!G0C|A$T5K055=-z$@TYex?6FRgSNNKLURYz5!kbe*$_Q zx?P%S{AKW0!P`K$;qB5)|J5ydyP|sAzmENO?BBq?9a|?l>91~?+ohRCx4rGsNu%4| zcIl*%wkMsmU1MgXhkifpN_yyY+LiQBw%Wg1soKBsAyWR6e<}vJjw$(fI zS1F$Usy<@-UD&^k{X5w2#=aADEU{e?i=T|J--GR$*LL+Fr*Fc3KlWzqyRp4`yj^|D z&v=fuT~Uqg-^2D?X1l(}89hhauJ3Wi4}l*Bsk>$?F8N1Exexn~vHt|S7W-q^^e-u% z-$VbB;@PHuN%3sMk`&MOC;645m+hL}I2T7R+rv*`e;OMOrFc$%lf%#O*ZtsU!Owwz z3VvRgL_LzIN0QhqiFzbakECKVKV$Vsq8>@qBZ+z>6_qV0-Cj;=kA$DGdL$K<8C|=i zqB7gAT~bk*ZL3ET^+=)~Nz@~$xNBJHR*$6OE~C{WskqC}TRoDByNp(kq~b24)g!66 z%V_mTD(*5`J(Ai-VYGTAwU5GR^+;+Th0*GfRNQ5>dL*@v!f5qKY9EEs>XAe}lBh>g z`zV}Z^++o2GFm;7+DBoudL$Kb`5CK667@);9!c$^aJtnasff$=G`7_vsff$zR*$42 zF56a*q#`b()g!6>6-KK^QW2NY>XB5$Wwd%E6>%A@9!W)9Myp3s5tq^Gk<`8mqtzpc zdL&VgWMK73Y8}96^+=)~Nz@~WdL&VgWMK73D&q1pR*z(0^+=)~Nz@~WdL&VgB?&q2dgvmV=4k0k1mL_Lz40Xg03kyN~7+v<@- zJ(8$L67@);9!bSZ{;Sm^iFzbak0k1m)Yp8cTRoDfM-uf&DqeCetR6|lOSY{ZNv(d^ zrhlOxNz@~WdL&VgB1Nb_E!%9y_EG3qrHBLn>jkX6|6Fw1c_Q4(3Wbm@DmIuC#->(hlZIJ6Olt z!MtM!Gmah1HFmH)04!OJBTWG5Krz9GfsC5xr6AjTH2V_&M?ngtI@`4X`^k|x|&*7Q|oGK zT`g_wR{BRk?*yopHX1!5R7)Gr3cY`q9ul&`o zS_)}w2EDtcS_*0O9-C^|u7>Su*sg}{YS^xZ?P}PrmO?tO?|^+^KR5smf``HHg8s^1 zErm251#{pCcmniS#cC;}(cea^rI1GNG^=K(S+x|>ws)FUOCeq6vpiG4{yugQdkVWm z-TxMQ8v6&>GuSU<&tkuV{VMoF@I1HxUH~tFS3u86tEG^}*TElwKL+0buY*4Uy)&&^ z3TgDtv}!4&@izZe3aOgX8mpy{w%sPHrI5DWGOMMKw!LGmS_)}&Y*Q_TGwG`6m zU24@*NTYYDRkKU2S~F0;=MO+f9o5oIaVX97n`v*f|7vNbQ$Ff9OEdM1G}HKy-zLqp z?eC}6^pt98rfu)5td?f#uhLAVNHcA}3)^$&YH6m^-;I4Im>}gY>0Evfy9(R}Cc$d(L*R!&>P}CumS*}5_n8@KrtLq$uEqWs z_WdCJ4h5(V>38TrwKUVe@*J~TnrYi{X|*)dww0k;nrWL}Db2L)IcBvq)ApMjdT(mA zG}GvvRMpZ|{*Y$!M~ZabzbW$RDsD><{$?WJxD@{faU67VxdYdq}^B z^m|y5yN4CId&#xBi-|pUcb60*a}uVMeX1CA#jcKbtCV;C&v4kzkfvk{qUZ$ zk7!q^F!mNu{~)F_fn2KH<*m! zZgSsE?z_qTkCgkD%Kk{X3zfU^t-|}r{XTNPk6+!#uWE^e*s{{Y^90PjD*`yY7o z{dj*5x$GgAJ>;^7T=tO59&*`3E_=vj54r3imp$aNhg|lM%N}ysLoR#BWe>Te)W#kt zOR0?+9X+OKMX55kq7)*DO!GtmY<^K zr)c>pT7HU_pQ7cbX!$8xeu|c#qUEP(`6*g{ik6?ES#rEw4yp%Q5~(Qj#gAhE2^Uv)zOOT zXft)RnL64`9c`wLHd9BNsiV!*(PrvsGj+6?I@(MfZKIC1QAgXTqixjD9_sLZ9loo> zcXjx#4&T+`yE=SVhwtj}T^+uw!*_M~t`6VT;k!C~SBLNF@Le6gtHXDX!1g1st-ZJ6 zUpwYN`{)taegw83f$c|N`_qi7jnbC-vPNl((Q}YSMG;2tPirjuAhug$W107MHkNsR zT4R~_r!|&&e_A6mg+^uyjm#7pnJF|fQ)pzS(8x@oQQsx~yyNgjW@nAe&KjAWH8MME zBz|sWcGk%3tWlAMo+ln|3>*nJD$+3edtalzW*WUetugSf=|+9cG+MP9_0`hp-k;VO zcz;@BtjvFv`sr6vKcgeAMxwq(;=M*9y+*|z=QKR0;W-V@X?RYcx4?4?Jh#Ae3+=fDo?GC#1)f{rxdonE;JF2!Tj03`o?GC#1)f{rxdonE z;JF2!Tj03`o?GC#1)f{rxdonE;JF2!Tj03`o?GC#1)f{rxdonE;JF2!Tj03`o?GC# z1)f{rxdonE;JF2!Tj03`o?GC#1)f{rxdonE;JF2!Tj03`o?GC#1)ekToPlSZwWIc& zf#(c7XW%&l&lz~mz;gzkGw_^&=L|e&;5h@&8F@SK6?3_NGxIRnobc+S9c z2A(tUoPp;IJZIoJ1J4Af#(c7XW%&l&lz~mz;gzkGw_^&=L|e&;5h@& z8F@SK6?3_NGxIRnobc+S9c2A(tUoPp=Bv9q)lhFf8{6>eMMwiOOr;jk4B zTj8)34qM@{6%JcruNC%MVXqbTT4Aph_F7@D74}+TuNC%MVXqbTTH&Xay0=pIR_fkL z-CL=9D|K(B?yc0lmAbc5_g3oOO5Izj``4-Ce+Qoi{~COz%(2-sWsc3B(W>u)(7TbJ zQCnIPCczz~dwt-UvYlWfDNSHAm@E1n48Ka~9XNV!6iTO+7Gg{>{z7zCrEfdgSR$#YlF8ocxz*)TpPT#!CPBs-rCqH*A|+$Hg?Lju~V*1 z--euG-rCgHlncDI!CRa9+S#&pcx#8Zc6e)tw|01IhqrckYlpXXcx#8Zc6e)tw|01I zhqrckYlpXXcx#8Zc6e)tw|01IhqrckYlpXXcx#8Zc6e)tw|01IhqrckYlpXXcx#8Z zc6e)tw|01IhqrckYlpXXcx#8Zc6e)tw|01IhqrckYlpXXcx#8Zc6e)tw+?vgfVU2K z>wvcocwvcocwvcocwvcocwvcocwvco zcwvdTcPI&8tw@!HLgttz3>x8#XcPI&8tw@!HL zgttz3>x8#XcPI&8tw@!HLgttz3>x8#XcPI&8tw@!HLgttz3>x8#Xc zPI&8tw@!HLgttz3>x8#XcPI&8tw@!HLgttz3>x8#XcPI&8tw=Q_=g10Vs z>w>o~cw>o~cw>o~cw>o~cw>o~cw>o~ zcw-7^Kdwe1{g)o-@l?C*q`2Rpz{unX)4y+pyy+ zyhkD%c#lMuJrY^=NMzX~kqx{@A{%&*L^kjqi7b00vh0z_>I<66qc3Pi?~%x|M(<4ZTMq8~O`=HuN5eY)G$U zk3^PPc9uO7S@uX|L+_EuhTbEQWsgKQ^d5;UdnB^#k$8ds8he5N8hb%{(p~lfvB3*O zQ!l8$JEaM12Gd{*m;ooj0$4OM;=jO%{{kca3qg(3`LD4T_^+`Sc%SQj#*bDNk z@!jB^;9cN*!1sag2k!?~wkTnA7`6?<2jB z^gh!2N$)4UpY(px2S^_veSq`<(g#T&Bz=(dLDGjvA0mB-Z_`73n;zoZ^bp^shxj%< z#JA}ozD*DDZF-1r(?fil9^%{d5Z|VUc>mCw@8{d}5pp>~E=S1a2)P^~mm}nIgj|l0 z%Mo%pLM}(hk`y93_{de$I0b5xf~~#WvwwEZ|OO)*;%Jvdvdx^5WMA=@VY%fu^NtLa>Y*J-2IzF5vKAdFTev&A0 zk|=Rfb3;Gl?@*JP8yfu`YLX~!k~QZ^)|@9NrW>=Y%@tzGs(*HBoWIbvC1S-$|Uj0q~?+;kLHp_f4`m#{QY`Tb4la9&P8)Z zr~CW$q~?r5%^CGqv8P{&J)1^6$(e*yjr@Lz!c0{j=?zX1OQ z_%FbJ0saf{Ux5Dt{1@QA0RIK}FTj5R{tNJ5fd2yg7vR4D{{{Fjz<&Y$3-Din{{s9M z;J*O>1^6$(e*yjr@Lz!c0{nj;{=X0Z--rM2!+#O}i|}8B|04Vs;lBv~Mffkme-ZwR z@Lz=gBK#NOzX<(U+FT#Hj{)_Nmg#RM^7vaAM|3&yO z!haF|i|}8B|04Vs;lBv~Mffkme-ZwR@Lz=gBK#NOzX<(U+FT#Hj{)_Nmg#RM^Pr?5b{7=FE6#SRqyaeYZ*e=0#306z6T7uOQtd?N41gj-j zEx~6AK1=Xfg3l6smf*7lpC$M#!Dk6ROYm8O&k~H4V50;ZCDo zrm5XDwVS4P)6{O7+D%itX=*o3?WU>SG_{+icGJ{un%YfMyJ>1SP3@+s-88kErgqcR zZkpQ7P`epwH$&}asND>;o1u0y)NY2_%}~1;YBxjeW~ki^wVR=KGt_Q|+RaeA8EQ8} z?PjRm47Hn~b~Ds&hT6?gyBTUXL+xg$-3+yxp>{LWZid>;P`epwH$&}asND>;o27QM z)NYpA%~HErYBx*mW~tpQwVS1Ov(#>u+RakCS!y>+?PjUnEVY}ZcC*xOmfFoyyIE>C zOYLT<-7K}6rFOH_ZkF23QoC7dH%skisogBKo27QM)NYpA%~HErYBxuOFh_(ir`g;> z*<6{|e&@=(_B$tiGy0q0oK()X*M8?T%X5mq3C?L%$LMdVb6V9g`kUY!Yrk_^EwNny zi$>{{-Y>l}`djLp^vXzH%mn9{3C;!nmO7`Ipnv6WsdKFT&aw787xy7> ze@mTX?e|rptXGM$Ue$e5-z`s^Y~&OU(Dl+d3-UCFXr*ZJieI67xP-J zS}2>x7xVaH9$(Dki+Ox8k1yu&#XP>4#~1VXVjf@2`s^Y~&OU(Dl+d3T>jHUQAg>GLb%DGtlGi2jxE|J$I^14J`m&of9d0ir}OXPKlye^T~CGxsVURTKL3VB^2uPfwrg}kni*A?=* zLS9$M>k4^YA+Iasb%ngHkk=LRxM%9;5 z^<`9j8C73K)t6EAWmJ6`RbNKcmr?a)RDBs$Uq;oJQT1h1eHm3>M%9;5^<`9j8C73K z)t6EAWmJ6`RbNKcmr?a)RDBs$Uq;oJQT1h1eHm3>M%7oehQCm@Qs%kOO4)AE|G}}s z4)7IrfUmFve1#q0E9?MYVF&mMJHS`i0lvZx@D+A|udoArg&p84>;PY32lxs*z*pD- zzQPXh6?TBHumgOB9pEeM0AFDT_)6I&N^->*_zT)f;4f$^!LNhNG}VF&mMJHS`8n(7q)-^vO*z*oXrY-$~T0^9$$vceAVmC*lhyAt~U z^jFvczQPXhRd`#4w^evsW#{}Vysg69D!i@2+bX=R!rLmmt-{+Xysg69D!i@2+bX=R z!rLmmt-{+Xysg69D!i@2+bX=R!rLmmt(KX$Rd`#4w^evs4b0mrysg69D!i@2+bX=R z!rLmmt-{+Xysg69D!i@2+iGauR^e@xo%5^kwhC{n@U{wXtMIm_UiL`YntGWrq2I32 zv)1TYYxJx&jb%>vSihzb&9=R{rm^g-(BFX8=yz-MyEXdV8vSmKez!)yTjNx{HBQxA z)0xJ8TQBHS_15T>YxK%BdgU6ua*bZOMz36>SFX`3*XWgN^vX4QEid)XABwi_6$EWM~bRD0rE_;ekguH(~ne7cTL*YW8(K3xy&({+5hj!)O|={i1L$EWM~bRD0rE_;ekguH(~ne7cTL*YW8(K3&JB>-cmXpRVK6b$q&xPuKD3IzC;;r|bB19iOi2 z6lIlJrzjik)Ai6kU00+e-tg%|UCz^5DdbOWDm;L{C!x`9u9=eYBFUM1PUryKZm1D|f-(+zyOfloK^=>|UCz^5Dd zbOWDm;L{C!x`9tO@aYCV-N2_C_;drGZs5}me7b>8H}L5OKHb2l8~AhspKjpO4Sc$R zPdD)C20q=uryKZm1D|f-(+zyOfloK^=>|UCz^5DdbOWDm;L{C!x`9tO@aYCV-N2_C z_;drGZs5}me7b>8H}L5OKHb2l8~Aj?KGnr!`hP>A{@+ljnTYUrK+QyCYbGMpOhl-e zh)^>Tp=KgN%|wKMXWKIoq5l6a2;5GD(sQ9SP^kZA5`GZW|JTTto(rYtLg~3sdM=cn z3#I2mebX1}o4!!r^o9DSFVr`Eq1uU1?L>G8l%C6$o(t9Yh3fl4eM1-O>$yZ1@5TAzlG{mPNJ`M3{=)SLa+NU8t4e@D+PeXhf;?oeH zhWIqZry)KK@o9)pLwp*#?+2m#zR*4m@o9)pL-&29*ry>r4e@D+PeXhf;?oeHhWIqZ zry)KK@o9)pLwp+I(-5DA_%y_)q5FOi;?vN5U$%W3y6+3^(-5DA_%y_)AwCW9X^2ll z_kBHSpN9A}#HS%X4e@D+PeXhf;?rN2PnUGfqUNExhWRa^zE%gfmFb(5P~W74X6ZJ0 z_N-8=Izp|q2(_vs)T)k9t2#oh>Ik)}BWwn>sw2Av%z)bItrV^52n%4*C|~IP@`X{W zIzlVMZQ|Lu102s*X^rI>J@(=b%<~g4@KuQL8#aeLE9sRVTR3D+xla>Ik)} zBh;#nP%8<-yFjh#$kwWkP^&t^yFsn$$kwWkP^&sZt?CH1sw4cSN?s0c=jF>mGYRt|6F@K&yN>vZ#04sYe~Rt|6F@Kz3Q2kZm;!2xg( zJPdvp90HGkN5LF80-gX*g5LvAfurDA@cZBt__yE>z?Z>Sz*oT^g6F{n@B*m0zsj#U ztneB&`VsgB_!DFBAN<$g*T7!~e*=UWpBSLO!U++}?L;iM`^13YcF%l++kIky@Lk~V zfC(@H9m(7tZUQ%hIwedgj%{w&4lbid|Jyxx7CNfA-E(K5cCQIP3{sx(qu758YRy4^ z#YZ7N^4wV|)~?&bhe545$o@3w(pKXAO1xi*_bc&!WtqKSiT5k zMF{QvN}m@YwD&8$f+4i`E4_jtwD&8$f+4i`E4_jtwD&8$f+4i`EAf7%&x??4?^pV~ zh@cYhSK|H3z}~M6?EOlg7a_FwEAf6M-mk>_m3Y4r?^ojeO1xj`^CE)E(B7{M?fpva z?lao^l|C;*Xzy3z{Yt!FiT5k!aw4?%EAf6M-mk>_ zm3Y4r?^lNQekI z>U2k$&R`enjBDXXc=BFQXIv{qr#cID2D|WnP^UM`-h-{vn`Ni4bq2d^?W+`8Kkq2B zM&02Rc%j?s9a@2R*>ncGP-n0Uo53{L0%pKgo^J!&!49w!>;k(%ox!g6=nQtD&R`en z40fT;U>E8PcA?H-7wQal;ShKP)EVqb(HZPQoxv{D8SFxx!7ltBs597Q>kM|G&R`ej zL7l-aTW7EfCn$-|V3(~k*o6gboxv`>cV0wqM8A8SJuujIA@+Wnahs3HGW>9{fA- zm%(2JZ}T(iJ9?g3(jDqSwkt^28SJv(j;%A;W#7)PDnadNc&Wu6^G2T?oiKly3Sx1uJPM- zV@zkT3q$aiK<)ijiuQgBA97xb$MjqEbiIwa*d6NcPTA+bDo&HFzOTP3TC=S)*o8WS zU8pnIg*t;>cqgbc*k#{^tuxqV>kM|G&R`e54_jxj%hnm}LY=`b)EVqToxv{D8SFxx z!7kJp>_VNvF4P(9LY=`b)EVqToxv{D8SFxx!7ltm@Q++~X^2zq!`2z>vi}5IXRyou z820@jy_nhM9a0$oHATA4V3++6HY_pUyhEDfbo#QC$M&aqMrW|g_Nu}iQX$)Ka`+jT z+z);h{2chF;OD_V<5xO^J-AaNmr*CV1$Rn~jXLQ_t;d>{K+_UvS^`Z=plRHbrN4R< zPM~QCG%bOqCD614nwHQASg&W?W)f&x0!>SxX$g&nPPe8dG$z`%rX|X(X$dqffu<$U zv;>-#K+_UvS^`Z=plJy-EuqWrg3|h(3-~WT|#Rbw|5DxX$g(Ue%_jv zK+_UvS^`Z=plJy-ErF&b(6od`WdF*VmO#@IXj%eIOQ2~9G%cYK+0R(h5@=ciO-uM* z38h%m5@=dNb0t4xO-rC@360&ht!W93;I^%4360}Uv8E+7n%lOfB{Zhnwx%UCvfH+% zCD614nwCJ*5*pc^ZcR&|X$dqffu?b1nbNIk+*~HKrg49n(3+Oe|EH+rXj(%5pJLmZ z#_eW8YZ`Z)39V@fG%canz_v9lp$NgYH7yZX(-MI-ErF&b(6of|QOiWr5@=ciO-rC@ z2{bK%rX~E9Sx;Ki5@=ciO-rC@2{bK%rg6`jo}qoBX$dqf5m?g_Xj%eIOT?^ciI_Dl zfu<$Uv;>-#K+_UvS^`Z=plJy-ErF&b(6j`amO#@IXj%eIOQ2~9#X0gUnwC(sW80dR zK+_UvT0)VJ)2(RyU?_|(6k6m zi_o+PO^eX92u+KK(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%Z5YA~Y>R z(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%aEkH$u}QR&gUVEn*cnLenBN zEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%Z5YA~Y>R z(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^G zG%Z5YA~Y>R(<0)u2u+KK(;_r2B2J6YvR z(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^G z;er-Xo72|C3YXQ{y|q-v-|e z-U;3X>c7AB%=xmVx}2NT#*8lKCd#=enR0HXoSP}e znR0HXoSP}r78oLea8 z7RtGWa&DoVTPWui%DIJdZlRo8DCZW+xrK6Wp`2SN=N8Jjg>r78oLea87RtGWa&DoV zAEcZgq@2FJaVhZ4j7GOl-^^&V-+eQq(SG-xj7Iz2cQOhor*BbQ2z-m8(e2Z>C>q^9 zeT$;ee)lbkM*H2jC>q^9eT$;ee)lbkMz>GjGq@mhKk_|;PH{PXhoI5r^c{jmm(zC$ z8eLA`9%ytqeH);Va{9hMy^V7EzCWc?PT%)u+vW6qe@2(n_x%}NPT%)ubUA(BpV8&4 zQaN>|hRSL5YEqR-J1k7_SAYAiQj}u!m+mUXCksNatX7qI)v>Bm}jl+pp}))?7cU^nPh z?kd(ft61l(Vx6;!b}v0Tg94fmEsEjm3F1LLblq!+NavT@gY)lYm98i`BjP>{ESz~ zs}woNR`1YXS>3HtAF=%||5ZK3w%7Tq)L(4t))=AJPOH>wY)9C-HAc2q9;(!XY;VHW ztueAUW8aPKF9KESQ%?7~Yn6JI?cc-px1lOU4#p(tZ$njz9E=|Z=~IdvT=I{Sav$~| zWB&0gQ*{091$A_v>_FIJMP6gl{9{+3jw$icQ(YpWDF*!F5|l_Ce*{<>79 z$iX%oDsr&>8E`+STVs^+IZ(I8$o{-=t7bqtUA@e^iF`xou+X~*xEnOs%BuTT`A#XU zz5AZT;lTIt>GV%MX|(2SljhjA7q&4o+9u7hZ4S3db9Ca5G{<-^xEnNA+oU;Xg*s_O zs1px_I%!0x6Ay$sX+)^oScJNbMd)=c-vH-aT^&PMe)JY>s82|^t!=O$YQTh<5lSX781#_TI8d1s# zP$!MZ{vLP=90he7i_-I;Zex+H+gOA;X+(IIXLQntY@IYB)JY@4DeMw8(n%vq@k-7% zDUa1yCoA=&w3yM5vQSgkGKACgm}{4(g;4*oMv!mSCHd$Ee#_ggR+Nc$@#KS&!bytY@2)$94tjzmENO?BBq?onPss5k2Ex zzfHkoirlU zNh87}sFOxy>!cCkhe7JjEM}Xu$mw2f-lkcMZLc37Uxwn>ZpSFbj2 zlNQsXHffP{-EqU~srZ_*rAinVAv3bP$8+Kv`&M~i%e;c$>di;`$jk|%v@o=zo3i+pR|f^2J% zZ_P8BAK#j1v=$}H+%|k`p8sks@~wGBbK+a`Y?~9`nrGWuyVkxt&pBF)e0QGF zTI9R)jMgIGo#!XpK(8(P?mVN{mVI}g(QC`TJI{Gpi+p#U(OTrY^NiLa-<@Z)7WwWx zqqWF)=NYXCx9N#1w8*#V`TdT!eVd+bYf%y{ z@@;y05-m!iMZQf>&!9!VP0zObk#Ez}lW37|)3a?Y@@;yytwp{~&$hM5x9K^>T9ibK ze4CzaYmsl$vu!Q%ZF;t?MZQhXwzbH&>Djgx`8GY<)*|1gXS5diHa(-Y$hYYktwp{~ z&-h2KHCp7`^lV#;e4CzaYmsl$vu!O(hV(nM$hYbFuht^prf1t)~o^5MU z5-sv=dbX`azD>_I9HK=@#YJY)TIAdGjMgIGrf0Mk`8K^J;d?>Po_0t*jGoK*Ha(-~ zGQLgE=(&t<(=&Q5s?zD>{Q*xtA486D62Ha(+b^&M)TM#tyAO;1RK z?A!EYi$kNMO5dhubUf+X^o))neVd-qaiee3GdfoEZF)-q_g)EIJMO&_x^~=qC3Nk$ z_e$v6aqpGTwd39^p=-yzS3=j0d#{A99rs=dT|4f*3b^-5=-P4bmC&{0-m8FnuLAD9 z61sNWdnI)3xc5rv+Hvod(6!^%3yQtkRe76hV?V@(O@ZBzIw+r9x!gssy-7adk z3*YUccDwN1E^4<6-|eDyyYSsEYPSpD?V@%y+5=o4)RgJu7NO(q8r9UOn@5Bm>1tG8 zqfTxS>f{!oPHqwEYP{)Sw|Xs7DRDQ6oP6ytSeRm8d}+i|lVI7w%0H{)$@8A1Zg_TZQ+qhx1y(lv8VToJ!6%h5qn1RVh8NK?0~&D*hP8`zw(aj zd)WbdFFRoG6{|`St47c4?j?@8SL2Ry(fDF~C+Hp5_i8jT-U;3Xz6X3C_lrJ zCzMhL|8?+R7d)uv>jJlmx?msZnQvY2IQE}m*DLorz2E6yApI%q2JmY}*rLO!KdXTp_n(mRrn~rdlcV2itiq!){p8v^}(a~?ooXAXkgzx zitir9caP$`NAcZb`0g=$_ZYr=4BtJ5?;gWzkKv=o@W^9$WG}wh%QxA*cx11B`$(`? zzcuayJ*Mx~8=P_g`$_SzR~~WtpR1gE_;EjN?6Z{lvy}O>uKBdwC#G@c75QsT~FJt58Srv1GnvZ+IBr{yIz{( zH@I!r)3)max9$4CZM!~j+pZ7Xw(Duz^|bB!z-_xeaNDjA+_vijx9$4CZM!~j+pZ7X zw(A48?fSrNyPmdPPus4iZP(Mb>uKBdwC#G@_CeS<2oDFvLpnGp9*kDqgS3Z(;=#66 z_aN=(AgX&1)jf#n9z=Byiifj$#;SWzJah})yAO(iVWCy`pcpV(bq|UGqgD4Hbw8-O z+qUW+RNehotL{P7+O}2qplWTj>K;_BZCiB@QpbbT@gS=Eq-xh4JgM3VX_HUVCZD8D zKB+c&R_PwOo}^7asWv$*`ylCGP+NLZtpgnE@e@k_0{nadX1)M3U!Z4w zfu8jg+&%@jPbv4|;3?&9+y`0>o>J~ku_io4`JbZvUsOpJf-llLzDSSwBJIC{cQ)|O z2K?24zZ&pY1LbMJUk#M1fl@W#uLk_pfWI2>R|EcPz+Vmcs{wyC;I9V!)quYm@K*!= zYQSF&_^SbbHQ=uX{MCTJ8t_*G{%XKq4fv}8e>LE*2I|{DeH-xCp9f#l`u3NkNJ~OT zgI|))7#$6MS?R;VUopb-m*M%#dPXUFW;pl?Z}_TM8VAi)TM>Gv``n{Dm5HrC}D;YX2kh|Qe3Kx*fzR68L_&cI{!cFpjlGqspN7s0``g>oECsnEX0SejO&i z4wFw)>!+#p(aIGLcQPkHsQ1U z_E~=WEWdr0-#*K4pXIl{dFO1(MjAL64ph20h}l zCl~+^>kU1Dch2_&W7scYd-bm;xJ=3w&}(r$!FBLoje5V{ruQ2^4}RIdjeQl{O4}3b z1fK^7!SDLbu_4}Yg6B_SdzG>$b_)A%ut%}Sczzsvg7h=klr!cTQ%~$1>F2Tk4O|9S zz*TS!{5iPJ^S`hiI_~NTf7L1BZQ{8nv{v_o<=D47_wYCMq;CXB3;ll_z864tkac>{ zr{{YE|Nla7FpTYb^#-m>Z*UsSOHX=(asM`$;jdmP?G0vmkN4m926H_5Dmc$ye+asE zy}@~&{NLDKLG2A*<5w53FM{5q+bgd*mn+~^Qm*mN*Lmhg;E%yK!0SBu6YNFs2G8Hb z{yXu|8~l{_ERpi}*#AIzZh`+q`oDnx%G>@8d&Q_$t@029^b!O3HUzP!R&D&8-xm8X zJo%^2H|FuZH&#!|LH_C)dT;D0QvNIW=b&Tw-k6oMH};o2>0Z_w^IW?(=DBun%=^82 zW1egG#=PIVH|9vJH`a>nn5Q@P-$5%|Z|tv0cU03Gb0pIn>jhunuipmoc+4yFy=uv> z-BE13A3KKqB2OOYZT}0~|H;xDJK>fPJISw3fumraXJ{d@GrVCEEP!5z?~QqPcW=z= z@V&7T_7C_KZ7DWO`YUkdRrlT)?I(5~n>G{kUhdu)?Ih;^Pw9=l4leSZo8V9F@fg02 z{R6-KKcxR7PyS!*TiE}E{mj%Kb)MevZQyM@>G82Qbkx)vR=8f_+p&Lx-~LCQ{7vT){uXwezrI6HX6c(* z`e2qmm{spvP`Y(28*@v`(sHu2lPoPHOWVlC+$yp$w}))Ze$2+~!L0mZwB}@E-v_5a zD^E6Nma;MPlZ}~~tm-aQ9gWuNtQ5xRF(Mnf7B7(U0x3R!emL;?^Fma*50&l>Asj%x(}7^i&>@n zVpi$Cm{qzDmF|mK&HB`0{kHF6TiNZ~zVm z;BWvA2jFl34hP_H01gM>(Dx(gop3k+hXZgp0EYu`H~@zOa5w;m18_J1hXZgp0EYu` zH~@zOa5w;m18_J1hXZgp0EYu`H~@zOa5w;m18_J1hXZgp0EYu`H~@zOa5w;m18_J1 zhXZgp0EYu`H~@!(=+7YfGl>2SqCbOTY#|tgpFvb;P&_CFEgD3N2GOEHv}h158bpf* z(V{`LXi&BDudGOes-4lgG$>u_7J7^rR85`Y9yf?O4WdqisMDb8<#cP*AR0A@Mh&7! zgDBD<8Z{VJDh57G{BxMd=dfz06e6F)L_UX!d=3ZJio>e4|LVPVhgIvd!oS6ymG=(^ zuPDdEif3%UhJ6XV3R=MqE8Z~vtZcvV@3H@ZH{9Y^|Lk<4h{Hq?hZRNWdGV|5CXg#FLh z_S#`a&%+u$ZF}@QtkKi9N6f<-F~xv9sJevzk@VjJJxU%9-=V+yMv{7=$KN6K^lqV} znj!UZr&!O2=;K4`<4(U!ihVRh@Amy8dQ$z`_<5((uZQT@L-gw*^=qd;4-SIg_1oyz zL+aQ5t;d%k_3O0oZ@@90p$zopAzJ$oeR&ABhiL6XwDuwT^$@LnC`JimZ}3i!BSW+Za%f9V z+M-;TkK|(im3MkRl8gNX_!XCy`A9DISJIq z&Cf9($;CV$$uS?vF(1jrUf`XckK|&WkK~w-2o{!|D1InHGNG|kzB*%Ot$9yD*qUM;7E5Sz zig^1};Hd8uY@E`F>}RGx?=m_iO*Y;#!u%w+Uz1!&M?v#4B@h9L7p7|Hh zGr?2D-lvGVPr>l1(Bto^(4*}sTF5C{$SL(cr&Rd28n2CUQr;nZl&^B5e3cs&&*@-P z+;$6xvCZwMSarI~KdSN0f3+fwiZ>x&=tlWMH!9wo@+9c*`=hWl3J;^ydQ>W>w@Kyv zq{qHd)z^3hbbUvu%P4gjRht=BiswP2s*8TBy6Ab;!uC64pQaa_rWc&17o4USoTmMs zrv0C$<)5bIpQh!Xrsbce<)5bIpQh!Xrsbce&7Y>VpC&#zO?+~ic7B?6ewucEns$Dg z7Jix*ewr43nihVV7Jix*o+kpy6M^K3K=PD4PXv+|^YuYq%y$btFUV7)JT=M_f#ium z@^u=j9^K9pf#ium@}bK1d=BL$rFL(i9qs1AY=5_G5YD4`sq?IMn4^+pN`Q_$LObH>Zksz=kH_cp=X7j zzmKVJ4hubhA5-tM?fLtddYSPm=$Y4;dWX^T_c8Sj+n&FVsdxBSp1+UL=ErFBW6a;j z)Y|n7EqqKZTu-VcJLNC^2DM(>X>8Bm$JBbA?)m$eTCZ)--^bK$jh?@cF@GP6dHz18 z7U*=(-^bJfZF~MchBl6&jbmuznA(+UjY5twe;=bQjWK^8Lo>%{Ib*b(F}D#q;+uwGG?9FyiYm=I>+pdW`w|82%lTf5m`2t6wpHACpi0D@T=M z%-_d}F2;#2#uZ&G1mp6laR5B5T8zuTPH}WG9(V*C4_*WvU5pc5j0gTNfN{peam5u* zp8_3Mj1yOk6IYBASBw)^j1yOk6IYBYuF$WDD8`8>#u+QeWB-%%?}Cmf#uZT*{|0nK zF|LTh=qO@bJR2QFj1xbMi(#iYei$cy7>{{AI3D{K@ZWgP3bj)(9Vc=aCvq4k zau_Fa7+2(=HxN0Di^-*6f{|;25o$s$e?ci;d6*#9nqcIeK#eDeJSWiD2^4h#EuBC` zC(zFclyd^bn?UO(h?gd4X%j?E6STJpbZi0zn;`O>pmj~4FcWCY1gbJYTr@$;nP6O= zU|gPHT%KTDo1Rpz?S#XDZzmM0Ev1G2MqCIS zqZU~8EvTK?_NsnC?ZkieicmpgtI>VGpwZLl`B#Be-vXOX@OPWg4&W&#MoJ(zY!P2n{9u6D6nee z8wQ61-!LeI!y+6O;jjpYMK~Vs+7=CBBdMK~)VtVw(Ys?6f=b>ackQ%jw$;76k0ch)=i;xQ}q2Q z6mJT}n?muXP`oKAp5NdZ(-h24!TA(yPl;!}8Qq&g_omRjDRgfN-J3%9rkF8Jp?g#4 zUJ15Kuw8=f5^R@Xy9C=M*e=0#3ARhHU4rcrY?olW1luLpF2QyQwo9;Gg6$G)mteaD z+a=g8!FCC@OR!yn?GkL4V7mm{CD<;(b_upiuw8=f5^R@Xy9C=M*e=0#3ARhHU4rcr zY?olW1luLpF2QyQwo9;Gg6$G)mteaD+a=g8!FCC@OR!yn?GkL4V0&8nuoO&7AB5`H zM(-$_3I0y&LNCh~-NJHt^JRHNws<=$^l139*t7i_wpWs0R>WoeDfsu`EkDCPqL1%m`#+doR%~PRI>XC~XM~Dj zjBf)g{G=ilr~DT94yDXe_gU&bOWkLw`zxfsLi#JDze4&PapoKm<{S~`91-Rm3OPrF zIY(?cNAx&HlsHFxI7ehSM@%?J95_eRH%GiThqBF~X>+ln*q)1(K#vY{L~(P(Z*xR$ zbHr?O=-3?5+8lA(98uXEQQ50_@Em16N14x2=5v(!9A!R7na@$?bCmfUWj;rl&r#-c zl=&QGK1Z3)QRZ`$`5a|FN14x2=5wg=Im&#FGM}T&=P2_z%6yJ8pQFs@DDyeW{2I)^ z2J^2m*M3bS(Ngf5Mk3=ivGJNpZu>g;uSR_I8a{fBIpAv=Wt@Hz^t$nD8b_RR2Al-F zGW?oK>vXT~zNWFm_A2;u@E1mAL$5I#dQBsRQ_8{L6kepv7b){a%6ySBU!=?zDf30j ze33F=)EKH?QRa)3`66Y$NSQBE=8G|x`66Y$NSQB+XaCA&zDSubQs#@4`66Y$s4>pZ zxXc$R^F_*hkuqPT%ojDHEd`h9*O%$nm+9A+>DQO(*O%$nm+9A+)n@d(+Kkct`m$P! z(f#@|{rWQf`ZE3cGX458{rWQf`ZE3cGX46p+LeB*c4c(GzN~g-bick#zrHLz^qbwU zFVn9t)2}bnuP@WDFVn9tOVjiu{rWQf`m!|bY;c7UvJ?puG&R;?2 zuc&NJ@%-_M-e!D<@G3sNichcN)2sOODn7l6Pp{(BtN8RPKD~-huj13I`1C41y^2q- z;?t}6^eR5RichcN)2sOODn7l6Pp{(BtN8RPKD~-huj13I`1C41y^2q-(bKQd)34Ff zugPl*!8LmNHG29rdiphb`Zap`HG29rdiphb`Zap`HG29rdiphb`Zap`HG29rdiphb z`Zap`HG29rdiphb`Zap`HG29rdiphb`Zap`>oEK}48IO8>GKM`gNtJ zgX>Bc;*sm}NVn`O^7wT;a$PkYmVKS{e|5Uvpy%}l;{ma8J?7P?>#ROqXHDrkp1Lki z>230q@f5!u1>F;`%WF++-V7k&mm zUdNBu7wFozri(Kq^671bdj1aQqx6hx=2kIsp%p$U8JUq)O1lT*6(pm z7pdtYHC?2pi_~d~COR}x@H>l|i)zr4t{sz5_`!oaY(-iuf z;|+S*4SLxPYH@>Jc0;w$ujpkrDESR~*$qm5gI;!nUUq|Cc7tAagEHTs%s1#|H|S+I z=w&x)pEqfrH!1T?%6yYD-=xepDf3Ore3LTYq$S^^CEujXH!1T?%6yYD-=xepDf3Or ze3LTYq|7%d^G(WplQQ3=%r`0XP0D3YLL@H4$(Ay{IhSYo7DVx(ANq*!94SYo8mcqpEi7%7$lj}%Lc6ibX0ONET9?G}CQ7Jcm&eeD)~?H1m@ zMPIx1|C99o;c;E{x$n##TU*ce$W)etO$i7g6d{BVLLqg1eR6&J^f~m`ZJ~R@~b@_Y~qtHc60w#D*x2U1^xuNdP4zI0jmNsYZ|@%XSLa zAWP$sXEZx|?)!fD=Y77;tu3K}B{Z-^ zTU(;7Ez#DBTxpRjEpnwruC&ON7P-)Uq{u~QT26HeH~R_N7W_z%93x<>6m*JyMfQQ{g)x;n>RPgSC?EYVk%=qpRQE|t?)mgp-> z^pz$0%4PDIW%8M2@|k7wnPu{sW%8M2@|k7wnPu{sW%8M2@|m*suqR#SzF1lNQOxXO znfdmzM$`AH#P`L@Y0qwznRP5P>saQ#Seg4`W$ufWxi41MnRq{5nNvnlW$ufW)4nfO z=Dt`t?K!Tp&emIcWllNmdmLpk?|)q&_R5^HYQZI5nNyY~jb52kMw#WbSLT#eJ4W9Z zE2q6Or!1{{ORvl+b6>2S_DpP<`(ov^SLT#eo8FJ_i zeX%n4#mdatmZdK5$C*i)mQ@aR6Z=VlGIP0Qsm}2*<$hA29E=b@0(xb5S!#5-SLT$t zFIMKhSeX`D=Dt`t@XDMr_r=O;v$|gHiIQPZM+!re|Yh6~0^OlQj>6JNU zX0Xd@eU3ddT$bDT{Jk=#EVnUwWlovJ{Qw+47sj=ZM|l-*H?bJ~rqGp?v-cIfBg^R7m6dcI=;s+%(hZ>JRrtcV(6cLiXI!|B zxXH)jZ8m&!T(MW$tfb!tz5{FlJHaln8|(pl!4HFeQ|JnB3SCM6C-(di%F$ICUC~lj zXeleaDRf0|CegomQ|Jos(&3BeD!I%5Bz`~TepBcQZwg(}8?hXJkn$el4-xxKp(~kNiEjfp zft$fC;8yUrK-v`V0^&RD`tR>@6~D)?zfb%J#D7Rk`^wP1GPJJ@?JGn3;!U9|nNP4~ zAOHF!@twqfO8hC}PZR$c@t+g_1@W&q{68uA4EW!`yFuTDU*S!m`sRD;kJ9?)JLt8_ zRq~=LPNMIdyPS+jUpXiC>g5>EiSeA6En_?<#&cpkC&qJPy|d`5wl_a<%G!wWoH%7| z#CkiQf3@etDRUC1%t@RwCvnQ0#3^$Ur|da#%AOOa>^U)>6XQ8?%AOOa>^U)>6Z2N6 z7|)4Q_MDivLdAL;l4E;L%v+)2lszZLb7DLvPT6zflszX-*>mEQJtx+C`Hc3Qn70$h zcut(M=fo*{PMn$q?KyEO5889$l<#B4cutJx#3_4DjOWBDdrpk!#3_4DoU-S{DSJ+g z=fo*{PMosm#3_4DoU-S{cut(M=fo*{PR!ehV>~C;d-+^$&xunrpgku}`JQ`>=frqU zjOWBDdrpk!#CT4O=frqUjOWC7PK@Woyq!44bKamJn# zXY4sKo)hCaamJn#XY4sKo)hCaF`g4=>^X79o)c&6IWe9SXY4s~#-0;r>^ZUC!e_MS z#Ci*#kv4_r#F>v$V$X>)_MA9l&xtekoR~L~#TnWco)hCaF`g6SIWe9S<2f;JCyq1r zoH%38iFsdHoU!M`8GBBgvFF4Ydrr*TiDTYQ9P2H7F0tptdJCV?o)haWe8zi)Z{j&` z;yG{1NhVa{=Of<4N#4YB-o$g>#B<(M4NjyJJSV|(61Gh6oCMEF@SFtCN${Km&q?r{ z1kXwEoCMEF@SFtCN${Km&q?r{1kXwEoCMEF@SFtCN${Km&q?r{1kXwEoCMEF@SFtC zN${Km&q?r{1kXwEoCMEF@SFtCN${Km&q?r{1kXwEoCMEF@SFtCN${M6JSV|(67rk` z&q?r{1kXwEoCMEF@SFtCN${Km&q?r{1kXwEoCMEF@SFtCN${Km&q?r{1kXwEoCMEF z@SFtCNyu{&JSV|(5RkfYB)&^&#B=#H9V(g&k0i6a*t3mJVLz@UHBei zWt)oM2gS2^2n5}za1TZmPDo>26Y!ENXdDOdRvQ7Zy^?HR$r(U9YW3R z3$>y{xQ@7qcs=n=#Ci*{e$`uug?bCIP;Vg?>Mg`Vy@goV4fcS&;DLTo~37PBPAO@;r+;q9DmZ=%Zs$VyvVWMLL8*zMf%lmAr|T_ z#KLzG>n+5J-%YHy5G&SOh=sRMqPGw$elKwcvED+g61|03_v-*TZwN2H-VeME#Ox0w?MAl?+ewQKT65(68|3Y?-Tz4@gEX@45Z!3i~KLzoxI5L zpRna8i2H~?Nqi^qpAvtHm{ut-@-@Pt-$JbTR~+h1Uy45i{x?u>Ay$drLM+rJleQNL^%g0i##};sQIPr#V_I`N!dt-mz(e3s@GPhigtqyy&N&q+|PeopMN{{k%>&q>uG5xEs`rv`U6R z%}6VLoa26xJ&beo!{8CnoY%>vjZcAkkB8zD;FrKJgHMD11nPMxZ8HVx`D?{r0iOfy z(sjxYjdtleT)Iv!?em%DC@&GeOstt_mA^v#P4@5__$^R#&ieIV!0&;&TcMH}@B%ms z{sjCtjyMO-gO@?OZXK>$hwIkix^?nf?M+T=Yyxir?XGpWYn|NHvEHjG)Yp)O`f{ky zPFsi5*5R~u%JE!cr>*n*-i5cZ3;zK7x)->m~+sb zCjF{s--Oh1+I?l6=C@r=U1)yW@xQVS%xiwz+y5N=1^7#jrI~z{ufRYICPwv#;(;Ed zPCbaWQ4gZCQs1Hd(09}YcN1^t*B#)4wX6DdC*{sq>H_B~b(#q`{vCJ>k~3ZZ-OI?b7Td-vLPfnBan^XD$neJJ5|qwgsQwaP`PXKjS; z4eN9t!MKi+w-Wz4sAu$4elz9z8nxng`byOsI(`?izDBM1-NgDDwcxg>ew{R=?Ti|bl9|eC8`~whQ zQcvgY_0~WHN;Uv<;Fg)9W}&JL*>Sf?Wmzf+&+pOHPndPXh#h-;x^h*LmV~Kh}&D90PU!u z@?N7IHPndP=(ZN(sG;&+$9B|EBW|M|HN;Uv95uvILmV~4QNxrSHB{c~`=FHPmR@Xh#h-f;QSwLmV~4Q9~Rx z#8E>WHN;Uvjg+;Yv>i3XQ9~Rx#8E>WHN;Uv95uvILmV~4Q9~Rx#8E>WHPn}dR0}w2 zh@*x$YKWtTIBJNahB#`7qlRfaYN)r28ttf|-YGgGw4;VNYUuZfDz>ABX*+6&qlRfa zYN+>!8ttfI+Kw8g?WiG+8m8^2VcL!wrtPSq@7)XSs3DFT;;12x8sexSjvC^qq3#%Z zOFL?aqlRfaYN&hSK9(Id#8E@ds;U)e9!rR$hB#`7qlP$Yh@*x$YKWtTfgLpr?5JU2 zM-6e*FtDSBIBJNahB#`ddy6hdr8sJcqlP$Yh@*x$YKWtTIBJNahB#`7qlP$Yh@*x$ zYKWtTIBJNahB#`7qlP$Yh@*zON2znrJxZe;HN;UvjW2W+IBJNahB#`dxek}xQA5pj zIJTpP8eh1?jv8uw;n^a~&?RqlOw;811N`W;TrY5{??;s3DFT;;12x8ftXlE$yfwjvC^q zA&wg2s3DFT;;12x8sexSjv8j{sG-088q}ve&ZeHW54c6`s8TH6Qz)koCY6~_;FZ^0n(F1E>myD1q0 zhrtnWKX}|n*C@t&cosYj>K#fd@k~p-o`~@g^$sQB^OV0p`HLL!tCVvs!Pkjj244Zc z&o=Hs>a~``ds_sH;G6u))#;fRZR6D!LTBCeUV|a@Y(~9CQaT@vq#XZe@OQ!A2mcWK z82EAUkHJrXo`0y<{GRdCAnaveFY|wx!_5B${@>t#2mc57m*6(e=RR-;`1d-Wh}>R$UP!*4~@*V&xp}nMD7uhdqm_O5xGZ1?h%oD zMC2Y3xkp6q5s`aD>R z$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnO zBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_C zh}>R$UP!*kBHnOBKL^MJ@hRd>xA4RBKL^M zJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R z$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnO zBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_C zh}>R$UP!*kBHnOBKL^MJtA_Ch}2gRJg^8l8J&btly5+#{QE z?vYJ7_sFK4dt_72J+dk19$DQ5@HWmpvYPofI`_zudt^0x@7TFVHswA#OYV^+_sEia zWYf+)vYJuU7b(a+vg96Fa*r&zM>g%;BTMd)CHKgZdt}Kyvg96Fa*r&zN0!_pOYV^+ z_sFK5dt}qjJ+f)%9@(^WkF4e`eO%`rS}Aw)v7*! zM>cTokqw-CWCQ0O*}%C+mfRyt?vd5nQSZUIM>gQ<$UU;;9$9jaEV)OP+#^fwktO%Y zl6z!z7gBr3IQPhAoO@(5&ONdj=N?(j@Ee_bWHZh^vg96Fa*wR$@Ll5EBTMd)CHKf? zoO@(5&ONdj=N{RNbB}DsxkonR+#{QD?vd3@vc7a8Jw73d*ZUpnn~Z-C{sZ{`^6UR1 z{v5HshoWr`fR9l=N<3!Nb?H2HT}G`N4Eprcz+;RAi#W$R08RkO;qs#%yA zX^(wD=xYf))7Z!TwLb1o^+})Fv-D~7-Kak4)9Cw9ecXZSlRh2$u2Y}%Y4n|@KIzkF z)%BseK2+C->OSeGGlEZwXQ7U6)br-Sol@6?(D#+@l)7ew-p`%NYK&T?Beb^fOn;X6 zKGox$sQ1ovKjj0ITgi8(hmERb?M=08w6^~u^`CWr>Mv5C06&@fjN&`NKLtMpzGzIh zd7E@Q_#2c|!8eV;tu7Bf41OB?Gh^nn{x$P=#Qz|?OGo^S@Dreqco#>!E9E2JmGTkq zO8JO)>4?U5&_}#WYa5L|;$1qT@x$P!!9O!{#JhAv$NwO_8;0+O;k#k@ZWz7?hVOyl zd*t(1gL{<4%?R}t3!zpF3QvF&;7RaVew`%tJ(qjZ&lA50ehqwyZC(MjqEFlW09-Os z3-?e9_XOL$y)rbH-w!@O$u8m?_&6m`66-A%+H%sz^?MbBdW(hd4BI?U%$fMT3MzMN zx<|R3QST@aexF!t2o=wQKLLHO{GQ+qwkd%(D5-!|@J*xNs~`-)22d*oRid?mLao~o zYGt7C?UdZCm~Vawt>?c~K0G7*0k~v@6TaT1cn2llF=}tdn}zqP9w&s6QT3$~)s)e9 zFz;3WYkY~4AA@#@dsX8qSB)DV0C$PKd)1#BkF$+y`ChT=ZG4yWUUBPR^#&xN>-S#u zlExR=hGU6IZ~vxI{oSbWE#L-k$vw(#I_EvXHl4H3&jblQFR?BC6YY7MpXCwiDP5uS zgl&GxEZC-V*7iDQqx+6+eilaP+0kvmBj6LD=NY!qvu_K&!hfFw&+@B#_HBO3OsEyA z!f#OSwHe#|yopdx=?bq9{~Ro{2hTQd3u4d?xh+%YUo-WfXBD>5r*ESl|E!;%3O=js z$Y?cwR(X+e&Zw-$I0)_rhrnTQ1l$ik3!VnQ3Vt1Y8GHq_az3jJ#JC6+!NjOhhH(w( zKH{@FPvcF(?W&Rcgzne2OGTr?qoC)JwsX$gr6rfY1X@emxr*(oBgLv6ZK>KZ-b={? z-cotmgwXx<_P~AI_FxP=26{ElcI9R+cRscqrEMo4+fF{VT}tyd=54!_=GY8uS6g;$ z9=0nVbL=%X+oeFG+1T#)X$$AUHz_w8+cO!k!AJBHf5NvAo0IKor`iKr-2pQ@U}lHL zsx!e3jZ%%Xpmn;-i(CF1AzW}Y_9k8?mmUd_~s$Ws^4#uH7G!Au% zRlNgM@6edk@m_ErXr1rS$kb?^@6h{y6Tr-jRyVbsvlkTqpN;&)vvcm z>9}$*Z)08cqpN;&)sL?F(N#aX>PJ`o=&B!G^`onPbk&cp`q5QCy6Q(){phM6UG>ZB zd@Sp#A6@m!_wG|{UG<}@e%e|;y6Q()128iHGXv;q09_5Bs{#CQ09_5Bs{wR1fUXA6 z)d0F0z#9i(X#kc6(A5CC8bDVA=xP964WO$5bTxpk1~k_(6Aa*=1L$f1T@9eC0dzHh zmkyw-0dzGW-bVE=>uLa94d9{!=xP964QTemd$z6yG~;2kt_IN60J<7LR|Dv309_5B zs{wR1fUXA6)c}qZ3||fYO59!z{)YH(i0>zRxu5>@etOUQ$tdoZTCN87OD#rc z2=|jA+^_%61o!K|GrVioXnP&S zxZT^+Q$L{WLfb35@HQ7Hp9Oznlp7n}|307$L0i(NKcL*ev3vFhrI4#a=fDrrOFk%7 zIQ|LfUh+Yy!X=l$Wuuf}bhi9p@PxNfZ91L;-2*_j0uQHZ}RxhL?KC56h4t_ly!Np?vU z_X#J6C&91qtDa|6xmVckN`I4BZ*x`bmbFV7aO@s=7tXm0x7?+xc8UAnU4EOZ(7o_3 z%^`blOZ-c3a}E4mOPz_7V|0(aOIew=k&1LaQjzwr49z7w`E@t3`{iAladNqP=Utk2 za%?Z%6^sz?2i;Te^4na6dYh}zYTt#s?$S(@WB1*==zVt4@9a{);}X5iRrm_=tHhos z+$GidteoNOVitOrROhq(324{dr99Akw)gH*Ht5)XwM(;7j_t#{=(Bb)|GW$D-NpR# zE*yB5w5MZ9dpee~MEwgN-h~72k`Ddr&HD8rTKz+``iF4Hhj7S;aL9*fn-9@8AHo|S z!WAFF@gBnKa;PGQDsreIhbnTYB8Mt+@`uqNr(F7~(5lFxiX5uQ>9`Xru_|(?B8Mt+ zs3M0da;PGQDsreIhbnTYB8Mt+e#dW+LlrqxkwXq>=9ID8niX5uQp^6-;$f>uT)_$yt z9ID8niX5uQp^6-;$k7+)P(=<^Hn6RPivXcoR6%V6|-RxmEd)Un$cC&}w z>|r;1*v%exvxnX6VK;l&%^v*q*onYjj}@|qJ?vo*d(gdKm$L_Dud0zf>|qak*ux%% z*uxNe7@|iTqDLE|M;oF?8=^-Wl1gWSA$qhSzh_dhd$b{Xv>|%5A$qhSdbA;Byxzt= z+K|r8=pJoI=O*ObhB&t&dbA;(q02oVGDMFyM2|K^k2XY)Hbjp$M2|KUxJMhJM;oF? z8=^-WqDLE|M;oF?8=^-Wl8SV;V!-?VKCyeWA$qhSdbA;Wv>|%5A$qhSdbA;Wv>|%5 zA?3sR7d_e#J=zdG+7Lb35Ix$E7}inf(T3>JhQzks<3o?OmmX~|J=$J+w7v9bd+E{k z(xdIAN83w}wwE4lFFo2`dbGXtXnX0=_R^#6rAOOKkG7W{Z7)6AUbPn;mmX~|J=$J+ zw7v9bd+E{k(xdIAN83w}wwE4lFFo2Y3K>Qr!zg4Jg$$#RVH7fqLWWVuFbWw)A;TzS z7=;X@kYN-uj6#M{$S?{SMj^u}WEh1EqmW?~GK@loQOGa~8Ac()C}bFg45N@?6f%rL zhEd2c3K>Qr!zg4Jg$$#RVH7fqLWWVuFbWw)A;aW6!zg4Jg$$#RVH7fqLWWVuFbWw) zA;TzS7=;X@kYN-uj6#M{$S?{SMj^u}WEh3)Lm~T6$UYRZ4~6VQA^T9sJ`}PKh3rEi z`%uU}6tWM6>_Z{@P{=+MvJZvqLm~T6$UYRZ4~6VQA^T9sJ`}PKh3rEi`%uU}6tWM6 z>_Z{@Pzc|E4)_LiFoHrxP{;@h89^ZBmBPe79g^Zw( z5fn0lLPk&sUz!g1(zI~9TIC1|89^ZBmBPe79g^Zw( z5fn0lLPk)?2nrcNAtNYc1ci*CkP#FzfBslegYTER{ObbfS-bt}i;bR7*w3uperE0VOII%WHSi|k=ln+W;B$T>dhj`Z{T#o3 zo;LD%+Q{co{^wD?zr;Ne_)FYE>2OAP6!cv2qf(UdB}#q_dS>rY?Lp<*gVA%vk4iJs zLeCXHDxG*6&$K)$wfI-h6+bG?_*c&rKPt7jo|Z99MGBTUpi-_=ZX(#e#7W-&;gyZ%RN_oKy_#I?AZb3T}F?I4yXoQ?osoB zz$1RQpY$3QRyi4GKxQs;?JYh#VGzfia(Fy&!g}^D*m+}_#efeNAc%T{CO0A9)VmCYQ!a0`xq`UhD(h3 z9cU`?SanQaByfD5_**UAMUMD}6z5>3%mj6!tbN>7Pah_Kx`2{6j<2@#~^nQxO z*V(2-d;_c)gTQ+WJeNKetOD!(FS)DnZtpGlA8hG69bv_-aS;jQhbL{?QOk+LA%fyL~qBof7UwVU?vCieR zmoc?^|LR_2Ol{w>@0E|y>#+?WOL_OH^FOFUO|kn#9I zM&btlt%t}Z4#@)*%l%X?_cOYOJwy+Ch#vNk zdRUjAU`vnb4$;FNQV;9@7Qp8zcMp3=S(*3nRbuzEhsZh((c2!9qP&g!+(YCThsdlB zsW!D|@~cCtQ^y-X_rhP`8o$6beu1<70%!XLH1Gv9z?bWT$H__fe!XJnPX2!Vw9r@W zuh>rrebs!+Uid##|3UbmJe=>^3*FxNuD#H0obTETol`vy4}9HTv0LKfu<^Kh2gmNI z{iS>3%RVms_2X)Hj@@fNE(ZLoTL)jg7rI6GtM@ABiulsKVr%$u@{lK_%&Wl@(xh<~ zd>!-*{1Z~9@fSwHm3+jDwivJEg1^yH0dH5&j(Voz`IsPel4fJfl6H=qm z^Q%utlg2FQobUYqddPg46| z^cMkwar(Y-YIB@EZk*a2r#8o_&2egToZ1{GD;}pd$EnS6Jbawm9LKlEsm*b`dYsxE zr#8pQlgFveacXm%+8n1g$EnS6YIB_09H%zNsm*a}bDY{7$K}SU&2egToZ1|RpK)q) zoZ380Z62mJ4^x|m4^x|msm&wQ#Sv=Z2(@s8@yHRz zfJbnWBk+HOG2IcEKLYbdVEzcqAA$KJFnrwptD1LqvKR=3}9~J-J<`n2z^rKwGQO^G;YB`Edj&kNl@%f|p z{84=VC_aCbGe3&kAI0sD;`2xG`D5^Z4E~S7|1tPK2LH!6=VS1H4E~S7|1tPK2LH$4 z{}}uqga2dje+>SQ!T&MN{22TnsJu{|Wd%0skksmJ{gz1pJ>s z|0m%81pJ?X{}br{1pJ?X{}b@fx8?)BGA}g$C(u9NmsjjO=LGzpK>sJ;pRddZC(!>1 z^nU{WPr(0|=moySJj0i`qAzhpUuLZLWyX46W(4^@^{{i9#8oT z??#WOo=$r_(Jf3=*@sz*k?%3lgzUMCVc*@^%cZu`frx{N@9e6zD zue!Tr0`z!_ueu98p5m+SLXW5Ts=LtRsizrF@eOyy9#8oj?*6aGQ~rj#V~?l&4R^;L zPx%|}jy<08H{AU%kEi?%cOyz*JoPl=DSyM=?0G!pZ@Bwk9#8QNcOmu2c#5yLtHk3e zzTPhMc#5yL3q79V>+M31r~IvU9g*=A-)dJ}?>#e~^0(TxJ>w~VtKG44Ie)9&=<$@l z)$aXxJjJ)#g&t2m&3MY+YIp4Ml)u$J8cfjcCTMpPw7UsLRTE@86O5`R$ayBD_N&2! z)NXX{Iw7qZJ*v|Ca&!)Q^9^Vn>M2t1A01AR5lt}8njkOIJv9GUDl$4RnviCU&Wk2g zvqq1zCd9B~=Ry*F5_Ri zJn(GiNoG4wGTV7l*X1oe&v`Pi$DU*z*U7*$oG0n~PICSyIrEd8^GVM3BD;Fd{(tEBRmRvock=Z$Imj#c$QJdv&;`aD?arrBaUYoaXibM@Ux6No@LzeEVI6+ z=u=M7r<`Jz?3Auo-}7Mp@Ko9}U8i*IF7f=~DdrDP(fgdD_c_J<;VE6Oe%19F?I)*n zrH(z?I;AT$+A&V)8XddUpJM*-6!V9tm_IzFYuB&Lhn%AQo)Ql(@v5g&;^As=n*8K6 zJ=bY^uG8cvr>UdUc=c%<_%u1mX>yX&Br*XW~c-?7g znz6FFCgl%`IrB;N$fJtA0%uZv zvExb5I+^5(CYjru)OG2<>V>_{w?NM!Ps(BZU(aez(hEiZ5V@yzq2dSRp2u}so(CspGDY}EH1gl^rF>X#i~0o^Z8 zs$X`AzV9IXIs3Uv$uEe1$@wgSMaoOyGJCs0TrtWIwSW1caTVxQLzD7D~0*V})dJ^N|IN%hpmC-~Jp^`v@gm)JQcalA?O(T=Ho&3QWZoadxG+^3;*?>h*c z(M_tSFjxOVjDDnfm!lc=+Wr?hQLpV--**tY{Z29qI;p!bs&Vz*{?{)-k60(w?|4hk zg-)vXaqO9&N%cXFJr_DD2k`m(NvKIVfYH7Aq`&VV48S^Xi5pC+#p%E5zl?U^N%dmJ zwQTA8JCo|ojq7|}{=S3IGfR`|S zh4PFG@{9=bj0o~_p@|@mujX-~JY#`;`W)pR3*;FK}^JM;cGXK14WKWRCPx7h}ZBL$`$4~O)`FZmEJb8Ye zJU>sKpC`}HE6>-N(97gikKTjlQS+)z$99jr>eR8_Bd?luZ1>31`{Y&2E^(fpC(qB5 z=jX}u^W^q^&cN?3R}%eMDync|CXU5`A4l z=sqG(KA$I_&(nwIrCx7uPsmHdj_nC~DcN!2f04=O$>j5D?=Fe_tJ=HcEU~lqJiTq6 z>^)ERo+o?H)86xH1^$<3!18Jfj-9>d$=<)B7BLZgMJ+-oJzo_%Gx>^Iu}}(8xfEjb zO0chpE1`~J^y;YsZK^<J07Z3^QP7 zXf9P+MSR*;Vc=#S?S*;9`B!(=8YcjpOs3D9`B!( zK8+q{oz=M+J@!9Kws4kg;Vjw0S)HNE$r;X)Go0mY&vLeB$sEp-IXq7-JkLDj^KA1x z+q}S-@dd_=FEA2(fsx>gZ2uzLzsUA4vi(=t{;O>NRkr^s+fS=}I+#|ukg@G_`lwi% z*4)A#q3@+mGs4yPgemuvIMZ;YueMUM>=Nx$d(b|OzMrdA{KPrXE2gHYab>CAGj*;0 zO+A9#7xap$X=;C3v(w&#`JC1~gJZKgt@#AU=60HK>onumX~wP7nqly-=5|`6Y{f7z zt+BRa&oWIj;+{qcFX`GxgO_yeLay#5jlX6T`?_AzC|juO(yzKMqZRuSXZ}*)apFrF z1sJW^mvlbH6)-mH=o3PZJzwI8FN@Wg;AQ3tUS_V~WwGHBKcDroco6E$jc*ZtgIfCr zwe}5a?HknEE9~bL_VWt+d4>JF!hT+1Kd-Q#SJ=<1?B`YX^D6s!mHnKfrJtjXpQDYR zQyZTN&T*IFoN8Gyt@|8z8P3tJ&(W^W(XP+YuFuh~&(W^W(W1}MqR(-c;hgHx`}rE^ z`J!`-jLtD8I>%jxbE;F9d(3ihv6~`y77wnp*cv z@S0k;(RX=XQ|mVRT3%D@Hu^5lYiiv_-{pBtt=qU4^z$sQsdXEDm+dvRZlkZ_HR;f3 zFM16ZdW|!GO)cFe*FfLpc}*?d=rey!E!~&}eV6AoweFeVJgx6Mt?#^A?q`DY)a-d` z;XJMHyp%bua-aWsDN`|R?L2MmyvE&ERpJ(RUV6|r!9$efh#w|?1U$iit+(^iu=nHD z6z8R6@4;&-&P&gZefQx!ZSXu-eV#UWo@+mk8=r@P^SJSOcsNhnJP#Y^X^H2#_Vdhl zomYKnpUirlSFJhTDEt=J^(~I_E%yH{_V6wK`)%Cp+ql`csqt@94yr3G~BlP-GZe?gH)Zg6hlV zZg&@Ww)XER4;<*es0A!q_Z~&BE9$jLpK>ER4;<*es0A z!q_Z~&BE9$jLpK>ER6ja#(oTAKZda%!`SPTzfSqt#uWN5`Q#ic?dRw(=IEj3=%MCVi#r!Q={=}+jGiH%qtBY7 z&zcLodTLJf;$L4RcCRrPoCCiNdJH&6uQ8`g-oM)C=aju0y@Gd+mG*P2w4Y<8{TwUp z=U8b!r~1-1tn!^>mG2xLHHSORDeLyH9%Ig_zKn0D#N*A2)Xqg}=OS~z7o}L0D|0Y< z&i10z>)3Pk7nMZ_smF`d*G1+eFG`s%_gwwOz*)gX>Cmxro{Q3+(es2Cr8mb{h|9z) zU<^9jxyW4oMP}zNN_8&rT>V8U?P@TOLgrD(JPMgdA@e9?9)-+nygU=kqmX$NGM~0W z=26Hz3YkYC^C)BC}bXm%%hNb6f%!O=26Hz z3YkYC^C)BG5LN1|@ODNOu8;>_ArHJl9(aX3@Cte0 z74pC<8s%ww^1v(Pfmg@_uW%JtIR7hw^S~?QKUX;OE98M!$OErv#OGg~2VUXKuaE~` zArHKwQJ%`l1K*&9zCjCpgI4nft>z6{#~ZYcH)sWK&nP+p3b~F#uA`9a zDC9Z{xsF1vqmb(;nP+p3b~F#uA`9aDC9Z{xsF1v zqmb(;^DWQ-O3MrwG5(+7ykP-?hp^y>^DWQ-O3MrwG5(+7ykP-?hp^y>^ zDWQ-O3MrwG5(+7ykP-?hp^y>^DWQ-O3MrwG5(+7ykP-?hp^y>^DWQ-O3MrwG5(+7y zkP-?hp^y>^DWQ-O3MrwG5(+7ykP-?hp^y>^DWQ-O3MrwG5(+7ykP-?hp^y>^DWQ-O z3MrwGWfZcELY7g;G74EnA()mb5+EN*>OFUb6BiK#sIl~*ueqHXF!yC$h z9XoHlp-kAZ-+Xd|9P)@}Y^q&jgW)%mQPE#Hvpe4bt(azm;!dN%TgvS{zwbCNfdOFMQ(dP5ntW9OweR9`}6 z)cP0M=?&%8jy+d-L)z1^q&*!=*|q*f)_Q}i^@eolUvJj0WmU-qly?+#Hb=h z75?t?o}k96PJjJbF*6u7v{6GFHMCJf8#QJ&YSPAxw)7M0H7R65cwT-|V^wEOXX5>M zRcDQ!w3hxCw)d*eTH34fYH81z*L1et(yKaaX|K?$Nm2fnS9R934#a3TuBk8fQM{_N zCZ)N=GlaFYSM1cJI-^&0)>ze9Qy=Xuy{fa8_NvZW`VF@Cs?J*4t2%2^srTcRel@Ao zu~&80#GcWsI%`@PV)Uxcn%0Ln_NvYrt2%3}>a4M<(_hNgztD3{de+`p)mdYZ`@%97k?XN+v>a6L$zj4rKE9HAX4-r2MdZwtRR^aj>Vz26~F>6#)D{wqQ zxmR`8)E10h)maO?sbamICH46s(V%zN;v&RXDmIyL63YHDlVgI9Id z)Ycq(wzkHq&Km8mCf%AVuj;I66^ii(Aex~i)@X?}T4GHt(fjdCZ;e%*HCA=jSk+l$ zRcDQvv6|Ycw_IdPuj;HZOIA}mb?iIWHRWc$7O(28DK|5ERcB4BP>f#HStC!Yk*C#Y z*EO|k?SXb(Q@eJ&mMy)ivql@QsU_;VSk+l$&aI}F?k&Blv!?d$*ttPXt#wbv-(DXT z>YkBM8AT?gwl*!)Ouq03#Q#dHXEapuI;fQxinS6$sAn`XDX;1j>KP5;Dp1d8DAsB; z;oaU+`t+815?QD<8$zww5NgeaP-`}XTC*Y4nhl|z(GY5#hEUIF2$w)TqmfC84Wphr z7OwM_x>Cn_MnibBN-|tshO5hPbs0bTmC4wVgokSrLcK{ts3*UK6O?G3r(&%l5XzT? z@+G0NT%q2iA=Jt=q1FltmGudg^$Gu(?d492wenM_m7hYb{1i%KLaqE1Y7L?AU-_3- zek#_JUqU_kCDiIcp&Uu5^`AmHl2GeEh4Y}+e=64cPoca>s3*UKKj*(%|EX9{3<>q* zm++UwT2-jHNUSHn6qktgNVkZ zbEsPOmIIXN$uGrv@=K^Ezl3rjp`QE_%6Wu(@=GY^5$ee=p;m+n_2idO&LfoP2=(Nb zP%A=(dXt7wPkssY1V8d1esBPx{t2=(NbQ2ry- zlV3uuDi_LagnE;PP;MiX+X%I4RH#vaP@@2$Mgc;N0)%oKA=;4JcyC$>EYy=S zLumI{jZ3WdIt0abgVngfYJ6a|@&}h_RkrYwW7@)MueDSBpj>D*YF~}cSEKONXnQrP zUX7|(t7r6ot;5wQaJ70p$1i|dYo}Oim4$keOZW}SwboAYtHc^*E4C_DqleWfVYO;s z<<#bCjq&v>b+=l5!A!>At-dO>x7VpIjP~|AU6IiaU#A)o;@)-mb{)R$Z%n(~zFjBg zjrQ$2e7jC9#j);_3U!B6Xb<<7qmA}(e>vKy`(GJybeDPsf&RI(86<75F}{#}>iiwQ zEIo(LC4}FnL}O>gW;=xK5Vk|l6saUJs%071fNo78R}{j3=(}KMl*yu z6SX6kYXy!_Yp8^IumI|7(JHZ@uhE>Lw|8&8M)huVy{}QdJ9fRV3CNm3;qOjFT4h?UZeR#$L>SdX#UXnCTLZx(JZ1$aNsqXOLS}>UgH@>p_yOfc}1c5 zU!xgCou^vsbS4Y`S@_SAV`SkltMSWBCJU4PmYHKSnbr70F??p>GfV&I?~dtL_{qYC zzbxhwGvKd@8GY6Mf|${LP*(Y>QR^p#G3YDGaxGb|g71i_M7gNY=iI*uXV5pqYlinrT2Y z4QQqT%`~8y26$*dGYzoOfMy!tqyfz|Xoguw53HF6SZY8s4RFSzZU-2!v9)0TnmS5(ac(yTnm$HVR9{+Sqq+*22SD z7+A}-ujQ)Oa=mN0uC-`pEt*-2X4Y~=Yq^%ST*X?Qzutd`X4Z1fZ=tt+i&y<+-on+r zh3on?UCY(XujyKZIzyvYSY;Zy#zwBO5sfvXu|_o3h{hVxSR)#1L}QI;tPzbhqOnH! zX@s9fm}!KWMwn@YlSVjcL}QI;tPzbhqOnFa)`-R$;jIykHNsva8f%2ZMl{xl#v0LB zBdj)}u|~LUL}QIG+=#{+;kglwHNtiy8f%2}Ml{xl#v0LBBN}U@E*jBTBN}T&V~uF6 zks4`4V~x~HBN}U@b{f%GBel?o#u}-MMl{w)eXWE4b@0Cq4%flqIyANpCfC8_I+$FC z#@4~-I`~`%SLp15o&bf(mZbCCnXr>9xG@+R$G}DA;n$S!WnrT8aO=zZx>uutCo4C>@uC$3O zZQ>f6xW*7sn$S!WY&4;nCOBzAGfilw3C%RYQWKhKf~zJp z(*$EpXr>9?n$S!W>@}g8COB+DGfilw3C%R2nI^bxLNiThrU}h7p_wKaZbCCn@Z5xE znqa#L%`~BzCN$H8W}47U6a24-|Ml>{9uC*T;d(T)9wyhrtSg<{H%wK_3*GB2G(=!>$&RnT=+ zqM69tq!Eo!3b`sgsb1zLDa7dP@g~oX3O$CpNj0u*RLjO3_&D24f_kPv<$9(-=oyBa zR7b`OlsFr|Ni|};>~gVf425qMdq$66-YWL43ccl9#hX!U8Z&PdSNc_48K0o!EchCD z1uTPFuc&RbUNQ4lF<`6*osGPey4#>5PUsyU9w%;4d(m?mYA;5QTsFYN2Jzq$^RR)O ze1qDEOMXm=$4478 z@&#g!F6F!2d~T51jarE+^mucFST&l}4brm^Za2W~2GqL&-ENR#wP)4-OlBjB-H2j0 zqS%cnb|Z@2h+;RQ*o`Q5BZ}RKVmG4LjVN{_irt7}H=@{$D0U-?-H2j0qS%cnb|Z@2 zh+;RQ*o`Q5BZ}RKVmG4LjVN{_irt7}-^Tg8jXl4OJ->~8zK#FBoqqQ1^s{fLwZ5IY zzFAj#HFLABRH*Yex}SZA%I_0u22yy7IC)3veW1QHs`x|1w}Sf8s7myuQQ>BAi{>=m z;V+E}{}KEg_<2w(>Q#OK90m1NVwHGo`;L_B@g1r2;J3lo!JmQ`L96Q>X)E>}{?e#$ z9k?FUSBX_}3wWE)$6p#1ZU#TXHkyf5iDqJj9yPziUm6wai2$MQx(ff?Tl!0*!rujd zAN)h`W8lZZKL$SmeiHms@YCS0_Os3V&)f%U-d^$F>nQJp|98UwJK_JG@ZSvo&G6q0 z|IP5<>@U4$n&H3MUwTz+{+r>y8UCA7=D!*Ko8iAXW&WG}rB|W(Z-)P7f9X}R`EQ2* zX83Q0|K^nWZ%=9KwwPMQDal=*Ll|7Q4ahW}=N=~dy8UCB$zZw3UGv>e9UwRdq|K^POZ_b$i=8XAo&Y1sZf9X|d z{+l!Azd2+6o8iCNUwTz+{@(@v?}Gn#!T-D9zXkqV;J*d_Tj0M1{#)R`1^!#$zXkqV z;J*d_Tj0M1{#)R`1^!#$zXkqV;J*d_Tj0M1{#)R`1^!#$zXkqV;J*d_Tj0M1{#)R` z1^!#$zXkqV;J*d_Tj0M1{#)R`1^!#$zXkqV;J*d_Tj0M1{#)R`1^!#$zXkqV;J*d_ zTj0M1{#)R`1^!#$zXkqV;J*d_Tj2lQ@c(Z3e>eQU8~$72zZL#l;lCCBTj9SI{#)U{ z75-b{zZL#l;lCCBTj9SI{#)U{75-b{zZL#l;lCCBTj9SI{#)U{75-b{zZL#l;lCCB zTj9SI{#)U{75-b{zZL#l;lCCBTj9SI{#)U{75-b{zZL#l;lCCBTj9SI{#)U{75-b{ zzZL#l;lCCBTj9SI{#)U{75-b{zZL#l;lCCB-vj^ef&cfw|9jxS4gTBUzYYG|;J*$2 z+u*+q{@dWc4gTBUzYYG|;J*$2+u*+q{@dWc4gTBUzYYG|;J*$2+u*+q{@dWc4gTBU zzYYG|;J*$2+u*+q{@dWc4gTBUzYYG|;J*$2+u*+q{@dWc4gTBUzYYG|;J*$2+u*+q z{@dWc4gTBUzYYG|;J*$2+u*+q{@dWc4gTBUzYYG|;Qto*zXkqpf&W|Jza9SD;lCaJ z+u^?*{@dZd9sb+lza9SD;lCaJ+u^?*{@dZd9sb+lza9SD;lCaJ+u^?*{@dZd9sb+l zza9SD;lCaJ+u^?*{@dZd9sb+lza9SD;lCaJ+u^?*{@dZd9sb+lza9SD;lCaJ+u^?* z{@dZd9sb+lza9SD;lCaJ+u^?*{@dZd9sb+lza9SD;lCaJ+u{Gc@c&-;e=q#M7ydio zzXSd|;J*X@JK(c z|9<#?Km5NR{=4A63;w&{zYG4m;J*w0yWqbI{=4A63;w&{zYG4m;J*w0yWqbI{=4A6 z3;w&{zYG4m;J*w0yWqbI{=4A63;w&{zYG4m;J*w0yWqbI{=4A63;w&{zYG4m;J*w0 zyWqbI{=4A63;w&{zYG4m;J*w0yWqbI{=4A63;w&{zYG4m;J*w0yWqbI{=4A63;w&{ zzYG390RJC={|~_b2jIUO{=4D78~(fDzZ?F$;lCUHyWzhZ{=4D78~(fDzZ?F$;lCUH zyWzhZ{=4D78~(fDzZ?F$;lCUHyWzhZ{=4D78~(fDzZ?F$;lCUHyWzhZ{=4D78~(fD zzZ?F$;lCUHyWzhZ{=4D78~(fDzZ?F$;lCUHyWzhZ{=4D78~(fDzZ?F$;lCUHyWzhZ z{=4D78~(fD|AX-VLHPe5{C^Psd*HtZ{(IoR2mX8DzX$$%;J*j{d*HtZ{(IoR2mX8D zzX$$%;J*j{d*HtZ{(IoR2mX8DzX$$%;J*j{d*HtZ{(IoR2mX8DzX$$%;J*j{d*HtZ z{(IoR2mX8DzX$$%;J*j{d*HtZ{(IoR2mX8DzX$$%;J*j{d*HtZ{(IoR2mX8DzX$$% z;J*j{d*HtZ{(IoR2mU_<{~v{;lCIDd*Qzq z{(IrS7yf(UzZd>{;lCIDd*Qzq{(IrS7yf(UzZd>{;lCIDd*Qzq{(IrS7yf(UzZd>{ z;lCIDd*Qzq{(IrS7yf(UzZd>{;lCIDd*Qzq{(IrS7yf(UzZd>{;lCIDd*Qzq{(IrS z7yf(UzZd>{;lCIDd*Qzq{(Is7R`|aa{%?io+;pW{$O) zV{PVGn>p5IjpyU;+d0voQHJIA`6W8KcNZs%CHbFAAr*8k42wsNek9BV7b z+RCxEa;&W!Yb(dv%CWX`tgRetE63W(v9@xotsHAB$J)xVwsNfh$gzIw|F7=L!=otj z_q(b(lN-=*2m%hsC6LgQJBmk6$T19I7{C}}Cdnk3FquwIPq@4wD5$8x1J_$rM8$hO zR$Y%3Z(Vg=&(-z7WA&@9_kHc}Q*YNyqVDc@pM9S1A3u2VsZSqOZ}t1Bdb_K-W(HUl zz^VXN1+XfBRROFDU{wIC0$3HmssL66uquF60jvsORRF63Se3x40#+5Us(@7mtSVqt z0jmmFRluqORu!&oDqvLss|r|Ez^VdPttQ_u)N1nmLajE9+G;K7*aKwCs14VW zj!An6_RAJts~rj}=gez0TE|QFMA(yHH^Xj$rBCtD(LL}Vgq16ZYDt%~Q#%$R*z<)RJCFE8iN_l3tM|y^>bG0jb3skXpV%o-ZcB9soN9_CVO_ zurpx~f}I1K3p)?i16u%V%JNCCq^0j0Bs)kouS0a|5S=JXheM5hkXsY7(?5uJKOrykL%M|A2Doq9y49?_|%d(DTU9?_}S zWOV8goq9y4UX#(O*JO0+H5r|Hy4T3_WpwH_8J&7fMyDRpsYi6`5uJKOrykL%M|A3$ zj7~kGQ_o~{>Y0pAJ)%>O=+q-R^@vVAqEnCP)FV3eh)%tb(Ww_QI`u+Er(VeD)C(D% zdLg4zFJyG;g^W(UkkP3ZGCK7_MyHjQ0@gh22M8}Khco7{hqT@w$ zyoin$(eWZWUPQ->=y(wwFQVf`bi9a;7t!$|I$lJ_i|BX}9WSEeMRdG~ju+AKB063~ z$BXEA5gjk0<3)75h>jQ0@gh22M8}Khco7{hqT@w$yoin$(eWZWUPQ->=y(wwFQVf` zbi9a;7t!$|IzI5`18+X?<^yj&@a6+=KJexPZ$9wm18+X?<^yj&@a6+=KJexPZ$9wm z18+X?<^yj&@a6+=KJexPZ$9wm18+X?<^yj&@a6+=KJexPZ$9wm18+X?<^yj&@a6+= zKJexPZ$9wm18+X?<^yj&@a6+=KJexPZ$9wm18+X?<^yky;H?q7HG;QB@YV?48o^s5 zcxwc2jo_^jyfuQiM)1}M-WtJMBY0~BZ;jxs5xg~mw?^>R2;LgOTO)XD1aFPttr5I6 zg11KS)(GAj!CNDEYXonN;H?q7HG;QB@YV?48o^s5cxwc2jo_^jyfuQiM)1}M-WtJM zBY0~BZ;jxs5xg~mw?^>h2XB7x<_B+n@a6|^e(>f8Z+`IR2XB7x<_B+n@a6|^e(>f8 zZ+`IR2XB7x<_B+n@a6|^e(>f8Z+`IR2XB7x<_B+n@a6|^e(>f8Z+`IR2XB7x<_B+n z@a6|^e(>f8Z+`IR2XB7x<_B+n@a6|^e(>f8Z+`IR2XB7x<_B+n@D>1X0q_<8ZvpTY z0B-^C765Mn@D>1X0q_<8ZvpTY0B-^C765Mn@D>1X0q_<8ZvpTY0B-^C765Mn@D>1X z0q_<8ZvpTY0B-^C765Mn@D>1X0q_<8ZvpTY0B-^C765Mn@D>1X0q_<8ZvpTY0B-^C z765Mn@D>1X0q_<8ZvpVOLaWu5h!xs$*dw)0@|$3ff^CMqPg*uzp2KFsmSE5Kus=%6 z3*~-Z1iKQpMOx7(kBZabZzJ0T#3mp%X%evsh)tSAY|l;|wg9mOh%G>D0b&afTY%UC#1y# zEkJAmVha#kf!GSfRv@+lu@#7|Kx_qKD-c_O*b2l}AhrUr6^N}sYz1N~5Lla-%f3Q}*9R<4H+Qa_URLD+|-rG7z{T0WIS zEnnIdau2n9X%B#%0(&6rbl91&2f@yP&4rx@>wzsG+eyrHl3flf+gm4?=>#*KV5Sqy zbP_Xkl$eoLwzp0&(+Orei5c2M%t*TuwnbVn(@D%oe;e6uAa(<>8?opHVmA=Gf!Gbi zZXk98u^WipK;Yl~h!G%0fEWQ{1c(tJMt~RrVg!g0AVz=~0b&G* z5gteUA+(ds)>tASkryAZYx=P#zQ zn5i|wF2R03{H5>%@R!lp(9~AImn$($>QfSN6YPnwC&6xp-2y9D4Vjp=GcjvtV%E;Y zteuHjI}@{Zrgk~7u7IUao>HzWVd+~y$-fHrYFPTlPTI2@_F7o_Tq^Ck9`;t)+hA{p z{T=Kbuy?`AmEoo)*SwjUT!Ch4auvNv3LvlO0i5$7eEInilkTc=|0D3_x+YV548C05 zWNJ^qmus9%jedEAtXyehYVuQKrbeGpB}<=CB`ZHQW@_{qRkHMnX0r4<5oBM5l`C^h z?R8kWKF8GLCjv~&QkvRZ@ZW}g2lhKzXXb*HpSLnuHmqDpWit86P!sc+CX=5GH8HDc zvK;twZI8*uz?UoKOg0{Nl8iE&0y_tLa$)DedSDA+i(u!YoWo#`fUT5VF|;>^_J%o9 zlcBw_I$1I)DxE`$D(!Mv4(Y1&kCgS{N6T_}EBrQjl=PO4ipg>hsjl?h@Tb6^3jaX( z(_v>|&rJBU;2#8kHvBp8=fcm0p9g;)d;`7*em?vH_=WI`VM}2ThMf<41Z)K?t{pSI zrl`Xnx!%ka4e;fvCsQnfFV{YqnEN#a`W9IP)FKOS5G-hs1ue3m zMHaNkf)-iOA`4n%X;OQj08Tk;SAI zSq!wAwB1@oGLW?ZCv9h2=7PQC`Qj07hwa5}ui!33v$P!YE zEFrZh1}%y~i(;fOH_#qAyB>qW#GpknXi*GW6r%{9rVTB!p+z>d$c7f# z&>|aJWJ8N=Xps#qvY|ybw8(}Q+0Y^zT4Y0uY-o`UEwZ6SHnhlw7TM4u8(L&Ti)?6- z4K1>vMK-j^h8Ee-A{$y{LyK%^kqs@fp+z>d$c7f#&>|aJWJ8N=Xps#qvY|ybw8(}Q z+0Y^zT4Y0uY-o`UEwZ6SHnhlw7TM4u8(L&Ti)?6-4K1>vMK-j^h8Ee-A{$y{LyK%^ zkqs@fp+z>d$c7f#&>|aJWJ8N=Xps#qvY|ybw8(}Q+0Y^zT4Y0uY-o`UEwZ6SHnhlw z7TM4u8(L&Ti)?6-4K1>vMK-j^h8Ee-A{$y{LyK%^kqs@fp+z>d$c7f#&>|aJWJ8N= zXps#qvY|ybw8(}Q+0Y^zT4Y0uY-o`UEwZ6SHnhlw7TM4u8(L&Ti)?6-4K1>vMK-j^ zh8Ee-A{);|Hnhlw7TM4u8(L&Ti)?6-4K1>vMK-j^h8Ee-A{$y{LyK%^kqs@fp+z>d z$c7f#&>|aJWJ8N=Xps#qvY|ybw8(}Q+0Y^zT4Y0uY-o`UEwZ6SHnhlw7TM4u8(L&T zi)?6-4K1>vMK-j^h8Ee-A{$y{LyK%^kqs@fp+z>d$c7f#&>|aJWJ8N=Xps#qvY|zB zXi*$m6o(eYp+#{hOdMJihZe=5MR91+>6Ggj8nb0cOXo)>w@7R*v1bZSZeajH-*$hkHGDN-{-wi^?2BBkv z(6K@2*dTOl5IQzUIwsG*0`=bsdnN2H*sEZ#hP?)7?uNY<_Bz<>Vd?vYs2uq@@j=ot zX@3WM2kc$2zsJ?x4NJe>K|4!DN)J)nm*lbui${Qp#qjR8UgQRBCm!F*(BsG)19OVs?nn`~g za*c>8?{pC z+aT$iM6UyuT+1=Yew3EJqlWTw`V~~NI;|&rJBU;2#8kHvBp8=fcm0p9g;)d;`7*em?vH z_=WI`;g`Z53_Bn82-phPD%e_i?WBIPwsKTBNa`njIVv0^^^?9F6%IoE21)(o9{IVL zK~g{I%TLD)lKM$sK8X#I`bl3ti4BtaNnbvR4U+mvUp|QqlKM$sjtU1!{iN@3*a0Rx zVY^{_U?Z?mSh=cakXqtFH20JOn;|V}nzWhPL7MzGfgPqXc$!v5spB&(owgjGYr0mU ze4!nv)hJ)rMre)7Ptmfpu<~76w$`tF8hdI3%1_fqYtJb^Q=6#0r~E7}kLk+KCO=>K zBedD9Rim%E($lG4F?Oo*nYJ(cLHS%u<0F(Wv>HBE`MNfd&sBbkwjXa+zDt|H#D+C;dpp`sKeb6j0fHAX5YG4 zxT`zvzQ&zzc#K>!p1JM?rr8wI&QOP>d=Zj=I}Jv3QWup8i-(0)#SCCz4QLvvG}^aP)EoPcSWiGdEN2eNJE&e)h7IYTyeZRP7zQTsl*$qSn_%e{uR-uZ6S@%}swn+76P}u9;e& zwoZ%TtZv%t)~=zye9h22^hYbH;d!*Vw08lWZBl6wD#xu=k{_eo(#rC}0U^%0ZubZF(|x8qm`*%+OXOMkyU-<`}My?!cL=60h-hPDiOZG|WI=HW=9 zoCIoavXsP;p&Gd9 zRF1qRhv6`;UtVjB_6G?~^3g-rBAK?SoG>ww;LN5g_%%2GoL?EEI5B*%HjnEXw$M$36 z*#x#fo5&`y$?O2;W>eTyMxV1|^eHbklg(lWvDs`6o6B-p9-GGu=3)7)fEBVLMk`WT zDJx?KvqRW?b|^cH9nOwm<*b5LvMN^1YS;p{kkzs}R?ixkmn~vGwwN`tCCty3vH)Ag zma`S?NY=!TV$EzNYhg#TRu*J!tethR5bI=Jteb^d4_n0|td~WZ$@-YZV$5c7*3VY6 zHEb@0RR zJBMv&=d$0h^Vs?90(K$0h+WJsVVANU>@s#ayMpaxSF&B~Dt0xyhV5q8vg_FO>;`rt zyNTV*e#>rQx3b&V?TmiEn%&9nV!vm1vwPUR>^}Afc0b$0=$9nfL+oMp2z!)0#vW%+ zus^aV*;DLk_9ylX`!jo%J;$DBFR&NcOYAS~Wk$aS#$IKwvDeufY%hD0y~W;U@36nI zcNzWa345P?z&>PuXCJYT*(Z#CNrHXOzF=Rnuh`e@8}=>xj(yL5U_Y{-*gv?&8Rwk7 zXOO3G7fr48kLMHk{(K^z#3%CuxSLPm zQ~7~>8lTQ*@R@uTKZwufbNF1I%k%g=Zg3CJ=LNiw7x7|V!b^D>KbRlF=kr7PVf=7@ z1TW_mypmV(YF@(^@P)jV*YSGZz`cAC_wmKNkuTwXzLW>}GQOOz;79T%eiWzo0ckyl><~@8BkMLd|voUkKxDiMyVqcLXM$%+hlu&&P;rA5;u$Aid)33;x=)+ z_?@^z+$ru7zZZ9ld&IrsKJf=}zt|%l5D$un#KYnd@u+xAJT9IPe-uxOr^M6ZPvRNz zXYs6fPCPGO5HE_C#9zeA;uY~%@v3-Dye{4ld&QgLE%CN^NBm8^E8Y|TBiC^QY z`b>S6evm#}pQF#!bM-uZo^I$KJzp=-3-uzsSTE5_^)mfn{SbY=eyDz!ez<;wUanW@ zm3oz4t=H%a^o4q@UZ>aV4Z2rfr2F*6dZWHX_v=gbfWAy$uCLIK)SL99^k#jf-l89^ zx9UN?O>fsb^pM`Ecj?`FSnttS=@GqGkLsr0r(1eVxAnN*udmkE=xgzqi@&G)qkU( zr=PE1pkJt8q+hIGqF<`-&@a<3*RRlb>R0N!^sDr%^=tIq`n9QfQ8V5V>eQqCk;tg1 z8I_OY;b>PZ6z`8kQ*3&U4Y@*V+atl=G^^i?hdSCK8PWdUHu;F(6?Ju(kw`F>+82t^ zlX5&g*h`*G&+wTz*`|^rq4d6BEEJ7|I^!AeFz84N%18UKy-M$k^xG-)Y~CAmb+7B| z4n!alZGGRaCkx)ukEVw%K-#ibb%hE#^HBns#DNPa|p`&T-{V@tm`#P~M6s4lgRiP*q zn`(zJxJv5?)7Z-n+v&-XVx}4E=p3;IO)!uVF}uR;!3edc)c$DLFv_bk zhQ@E{=4xsd^4eV;A&LPt2?{!u({E=|SnYm9JeG~{Cbm+7?fvl(@9mfECrF(2b&qgb zhP(7-dc+w>uc=bRgAEJ1fyH zBw4O<#K~2TdSsS6aqNtQ*1F2Ubb5J$p_K9%@yW}p(<)V!T-8qIYA18`Q0DaNM6Qfl zoFkiCMlH^h?#SAqbLDn+ZQ^{nC931yu0?=%`5XwJ1K}IW=JGl9^Cfbo_4Y^N;l9W^ zmk+Jf6>zc!oUDPNtm%P79@mjLn6k9nj72lLLa|=z)@>2nB}1_~~ar%*c0%@uae2|MSg{v&x$4=2tMA)+Db&|o^J zo6hOxP?0XvsgIe+3_Z55`lgRXu1HMb^hv?bIWLB%_)N3ljCwct$AcrdF%kg;edToq|J1 zG8YWhD6?zGcP(&A?n0t3kag`MleGZK?Vx0J4co|EIFviHd&t)plE&!WQom{uAK9#3 zG%-a@C$rYM4!J8e?D!eAfOO7+n_27B+&RnfN7N=SQ0`8nEh+n9S8bi#=HZ^qx}iE` z_6+%1^}{Niwd!XZQmUd6YWLxGSA!G82$HOZ;pat$ZMYg7IwMH*21!(ejH|(EbP*?6 zp`372%bj~jYEX%$BjGrf z=_nceEdMb6vgl?@4^*o257jlr0e9$10Zr-2s_Bv`mqS7d2+kVLQ%c;7ns#>LVzHG` z9_i~2!c7m-y`P>kV}| zC+-ubEI-3b0iIqFiU(5`1nCO#uu)J}n5r=KCOSkn$spag$V-!;5-+7b*w+`NPTbqp z5#-)}zNnwC2vg@lE%@TFSki5#_`_YjL9sO0pQ>sk7I%k5CH*b7!wyt=*(h0NqE@o( zk?Q2czKmd!l&sLOD@&saIaJB(f;YO~IiARhohit#x6$b-UGgGRIzo|nFjbXBPsDVI z@*vGe$Ah{oEM*lgkDk)$@|=oRRPty)UmH#})iw5oMXcL&(PMjW(365h#OYF16+|Ch zW;^{+jZ#b*gDhEcqGlt1QPV6{tP? zQB)+EO>Py-t>pP7Nv|~Nl_k7lBk3iviW69#!lJ}cPqGwGvJ_9Ul>B5V`N>l96QvmW zg^5z~ljY?XC-)^w&QF${pDei`S#m+Lr#@e zOG&cil4Qvx$&yQwC6^>iE=`tPn!Mi9Bt~fxqcn+8n#3qgVw5H^N|P9+NsO{2Mp+W0 zEQwK;#3)N*lqE6Bk{D%4jFO>J5*XA|R2%XbiK8APf#ER{7#<^m;V}{z9wUL_F%lRa zBXJ!bBZ=WjVhq)}D6!8dD|Ib*9*fsF$#UFWTx*b|FHgup`kI92I?_3Dos-bJK^nEY zLcKD<)lm00O0$QCzch$Q$Ls)R1mIzGHezVXpe<;JvYjLJSRJD`a5OS=CLd%b=Ma^H ze91#Hm#ZV#MPrq8C!teZZ8WhFil>DlkuWKnok>GTdYbQ-lN2sF0*!}UG~hL(9T?DJ z#zJCX=!h+Vq^m)6TO_TM9+l+WL3%VX=1ieo(RSxzOUnv9G=X7DWv=dznb=$G$yZ&7 zJk_L%Jq1Pi>B(21DY)&W%V~*Tnxg1Prx66^G`h1eDt3H3Q%-v*KMOM-i32o9j)#Mh zj&NsZN)_%GeKE76zdfFA_tQN@);I+t?o@i~Du?Q6;b@`Z zO48)rT+W%K%jal#S!(tK8>ta{wLvQgN@`CXi02k$q9?ZhOBA6VHhe! z&1pMdZ5ODtP^CpGEmmoXN=sE*rqXhiR>-ubx~2wcO--drt7K|;YJh9xS75(UPz8KS zfln!LJkDV!{Ss z`=v!q3Y-#+0;fbu3-oT&TqQ%+7K)f_a8{u@tI%-p3X7cj7Z$1QVzpiDTt{KCa~*}n zsD)8j3?3;3kCfv2Ch1*EmgRs3b#z*lqno~1eWcquuS2UDV#EOon;EIOyQL&yfTGXrtr!X zUYWuxQ+Va-I?L5{mMi>ng7?a)n>6@XHl`xxz13_!SDj zLg7~^{0fC%q3|mdeucuXQ1}%Jze3?xDEtbAuli$Qg~G2;_!Tvo;I-0_kDw06m8uSv zst%Q^4wb47m8uSvst%Q^4wb47m8uSvst%Q^4wZ_7N>zu-YK2`hgk7b0P`Xj5bfd6J z)uBq&p-R=k(G#PvO4XrC)uBq&p-R=EO4XrC)uBq&p-S;krQ%Sn@T(OE)rx~^gTJM84iAt;aq=_(%T}#!7nnL>o4*+{1o4*+*I(oTzNgyhrJN=Zl6~5C?XTj5uNA5XQ@M=4d;@AOgHR@d+JQQB75 zUs)pi8f}**`WpFVYG3)VeX1T#U!`+YJt`Fsm8u?2KlM~Q{ghHw52v5fwyKBIPib4# z!|A8At>WPHQ`%N>aQZ22t2j9Ql(rSV(@#CsPCuno#lh*Pw5{Uc^i$eaad7%6ZL2sq z{gk#<9Grei+v@tAeoEWw`kj7C+v@tAe(I@q`YEOAdYyhr+v<9qeo5QvdYyimZ>al1 zfl-q)G<}6Z2t6#e4cmudhupXC(0TGX<`<`7K1=Q!fd|uMNzP`nkZc@Hn<(>*z<@+lW7 zJ>bgY^lU4S=vx^y{X zh6y2EdRfvWhL38%YZN60vPKbFhEeGBWJ(h~TeXnNa*bBp(n?zT#whw7X{^4@q6K4P z>5t1TaSd6Ww#ZjDLo3vZH5LiRqm*xy#=X7EZ(&+Dd@y&9E6|AyOSz9@V9SNUck zUj=fc(+b-(EtAe4q3uH}hR10Wus?%p;G$K@SyYRW+P>NtZ9i>)btFYgrO#_*Q*B3S zIaKfQREJ5Ca92>f3HEl_yCc+Sv^}to!afx-+aua@urHhP=o_%_!G0nenf49rPtr2o zmf_5R-4}L(Z5tjo6?QgkKG}SBFzgYqHDn7|1MCvm6>(Ww3v4HB6#U3_&T{4TkWGa@ z+@@iVw6uhaxcYUPh?NsDY2`{l8N2^99g6c%%;i3YQXI=^O}PtY4o@?P{mI%4TGLpp z9Y!m~7twmh7L{wUO4qA&w@M#Y=|?L4!b#aEm5xgd0w zy-xp`wwL~M?JfE*w0G#gPFs1@mgZ__XoK1|?M&@`?L+M&?Gx=Y?F(8}*hT9DFJqU} z3apoCHPQk)o6(w|YjAZFw2XhYN=TkVZH?MGdCob%Tt{>w9T9X+4xKZG&SbRSXn>+C zdm8zapHZEVo6Z?c=ZvM+z8|&T3DmMD(h)|;rvZU-&>572@~Ir;{kt3^G+nM@Vs18t za&R_{O{W0T8cSKO?7LJ8xoUqjAx+h0(Kl8IT1D!k@2`+)1APLDYqh+HtdAFxZII}6 zME;YW(;l=jEwnuvd$lpkjDayrTW6H3?^w9@| zmz{mdLr>hi@u7u(eShw!NA3FLM)!_^eO4L+V!biI+ja=K)^Swn^cTPTCV1l|Uu=7K z#%(V&J#S|S$jBK%OVY#iCL=o6T^nuB^UN_0k_SZ2 z)WiYz(r|Cc?T^!H9eU&A_J?AtX_30^t}HhukIObnjA9Qx=gNOgQ~1V>+Jm=!wq(rRCvR>& zXl&h4^XkuBwdKsuiRVUXZsc^Q=Ynb58qcMcvltQs161_nU#;EB^9i#pk(y=zs0>Yacmd(gl%* zwC$5$d*$PvOrFMs&r-2aS#Q8-=bkY7Z$Xsf_8f zj2XtX#J0gU@25TnV7A+Rc{F($rZ+87`Pl;_ix`=d!`MG`ZfHh<+&Vy?Ys@xg?U=D+ z+UBV$V|y&}vwZX7ww>D^%%lCrSea?MEF#OuNPvnoV}#s4QtsCUcm2VbEVuX3sZH$6 zG4}C{BwEn~Tuc4s9{Qli&zq7MU`$*5Q2nFRHoX3J_Ov~(O}P0oe%Y>p+^kJwHkM=z zjF@{<)41SmFQxo+@++U7dFCz4CLFQrgR|}}ExW0|IDYVXzPC&6I{%c%w^vNM>CQ)X zoWA^x@1F0_D~cAeX9_MK_o#RG%tt=kySagvdN-|o_y_+RAH3n(^6JiWFKNj-@69Xk zy>H;82U_>Oe*LVM9=Wo<=MoqAcp)_JF1@WI`wQx2~xPfxuqG;!M9 z3!kmp@WEwIKgMqz{q)jD#$K_Z`P+4et&AOa^0-UpR$l(N|Ec2+{c85XclgfHuI$a& zwDQ{){}=n+vV3IU1K)pn{q`Lr#=Z97*bS|ZjhqwRtbh8!7i(uelk(6;vvr@V}E&CX3l75hr5zysl%N# zO)KYZJqH+*ocN7Zhh+!06a80p+XCvTG+meIwsNx|(QW@vv|@lw9nOmEt^;hOW(=?l zE?d+!kNzJCA6`?`YnT+uP{<==n%c)_(N8mFu{{+;jB$9#5V)e`C(o3*1k6?Cj;aPp&O2 z*uL-i``-Cl_q^SIduz|Br_Fv~>y#~>_iS3R-0Z(^{_YuDRz5yz%=}AFe1GZv8PNxS zdZ_NzJ5%@h&D3LGIc(-Llh=NDspqjz|2B2}D-YhXpz?wVNAH-t?d_H?KRWi)?#SPq_}tO2bi-*Y!<$NFIaEZp)JfC-orEwKX&NMi zTISC2#<)cL7CBiYA=J=Hb3M651;gV{_as3&UAOAn_gCzxn&>%Y-T8CQy>H+;_QJ%5 zCvQ4+MfCNwgRW?K^zpNE-qn|6f4pGkJgxMmw;wxu(FM;qEzX z`E=GLpZw;hInN%lWdHq3XMTNnoiU*QKz||Ck3&ml?EP)tetVA`dC*DMO?|id7d1AH z9@dTj-CeZAC?Z0rhvrq%vQ*n$;9lI{Mz1;Ci(~Y{CbsV9J=R^`FPDtb%N5*F%Z#FY zdbyYAnsSrgi92`Hjko`YHBcgunKO3imKy7~()2mY%rKO0$EW7U(V}9~S6MNm*{b zd{p?~y8pdhlWuL@O{*oAjXSpUv6ju*&wqN|Ro136wx040EnXhux-0j@Dcj$E=Ay+7 z-~V~)JuiRu+oD4z4?d9o{SP^LP4}I0?4gHDpZwM5D~`G73wGPX8Fy@W>fV|0VAo7< z&pRJ(46ayr#T(;B?K@`XO?$nCC(K#kuDbKJ&S{;eKIyybfQL#SUAk>=?5pve{XKWq zc0XOZE_=6e^(%8*7hdw>?$PX;O)r+7X_V!hD-J&Ph^4n*|Hi!iH~sbMeU==?Z+UUf zk5g{Hs`-kMnb+(ZwRl8fp7*M!9&4HY+-Y}z9zFJ;yV=SgJX@NcKkcNh6(^71abM9V z@6Wkn%DdaX+VsxVe{Wsea!l){AD3sYEYEIV+WJD}l#iY6Kfqogre4Ht;Fn#wPj)f4 z9yYp+kSnuw^Fp@yn`)LiP8iFKS&6$lXJaS+%lNhT*~aYTncSE>;a{IivHtmU0(J7< zL|&Gq2aTgITe;&X<49w<5g40Jt-32TuPfY1Pp=0Usp?@^A3gZavlBL7clsHt9(Z~~ z&vOg+n|sXoJY)LU1J%zDCuRT}*0hABK^msJYaPORKO!Q1J_M;ut_V+=PFQ`lkT#-}d z5%iIQ(ZBiAs;g)8J$l*U=Nyoh`(V+9J1?93*)P;Y>fcuJhgZ*esr>R__@*&euYJUL zc-#ErE54p~``erBakP$q)J2!Q`Q65OAN=9*s&`{=EWSN3%DpLi?pbeN(AaZpiRZ6Z z=6-OR_Q)jPyWae}PWooiz29{OLZ9^9yZ-Tx(g~wRt*u_|`F!(wF|u{fEh{hnYHR1} zf&Gu%yXl>KYOgF^_ReeNYeuXXX$+*~&~wc9O8+|F`lM^!<73A5F7}%zkDsvV7d1Bi zzm*>vsgP)uaNdrRqy=UsWkC*x}~r%v|!_EheAeCsvKK8`MQU7a$f zY;yY{)ArqV=Xp;*o>p{R?5zdMe_Z#(sdeuj(U)7)e!>kKo__Ggm%jh{u^&!(>BXHp zUP=G#^3RU>_{skJzYZ<#n0oO&$K8GX9rb@)Q9Z)@%#jV=gxYN)wL5`^yVSvQFwXhe z&?I$(Q6&!@pdV_`?P^EKj-t&4iE)qJUO+<=d3(wIRoAWbS0)ak2cs0XQ_9b!lXM^< zDgRbFr5yf~2O(kfC$iJvPD=BCHn=-Hk;~x!^ooMv;Lm08XIB)883#yS4%Df2Z20F= zvyUs?xkXe^#b}tPkD`V&G%vBgj%P%T9ut4j$1%KH^tY*7UhgU&Gw#hle{}qar}mux zLi;_R+;;XmJ+ZdEU;cT1=|@)|ns)SE3pNfMeN=tz7n9uIjXXR3Pm{(ks~UIt$)A6J z`o%YVon7?W{H*J$&S|Zlec?|Rnhl5CT-en&YV$vu%9@+riVR#^bZn^p$Mbq`8*@(W zMJM--y7$nV+qU2O%B|16{NBao+m8R~xI1^A^+EKrsi!`?rr3LGQ>H(~^X!~iH!j(G z`ubJ3t*N>v>-sNNPM`bMlcySo)r*VPJ-%zv4VioXI4w2v{&NPt8ujU2+it(`f|r`- z=Y0A;ckMURJO323;M8X~zxF>X3i=QI;Oy|5Rpufs^7Qj3y}WGK1-lM^qG3w+w$EDc z+WVesW!aRR{J(n!L>I|oJ7>(;_{9IWvd2gLO3;Vqsdn@kqlUGJERURL7@wRqN*A82 zVTX0nxuNEh=@~&26hqr%j01-<>K?lPeOpp?^T9d(^#zZNT(z()>l`EXsfOV#jmCfa z#tp|6ei}Whd~mJN%-iydj(TQwdd9B$rxvEYQnY^6*&qDRjOjPE6^yxL`<`0^$IZLs zk7tTS8z0@e`X8tCO#a)IcW%A-(ks3RZ@D~e^ZO-F-@GjAv{}c$zvZjVRc|ltfBJ%X zUAuihy0gCgaLNszUh~(9xB3o#>F%!%3(URuVD^t0kt*NqgXgcAQ3X8&ZeZdg=S(&I&9l96JNncPTa6oTZD6wgSyrO}E$qsUWSL@$b) zeiedGcV2q--ffxBzFdAxvvoq-q3`jk!R^h($ literal 0 HcmV?d00001 From 60929c184ea5a5333efe1dec8f0ba2d1294f7251 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Fri, 3 Apr 2020 17:35:42 +0200 Subject: [PATCH 168/393] fix cropped Mic/Aux labels in LateNight --- res/skins/LateNight/aux_unit.xml | 2 +- res/skins/LateNight/aux_unit_unconfigured.xml | 2 +- res/skins/LateNight/mic_unit.xml | 2 +- res/skins/LateNight/mic_unit_unconfigured.xml | 2 +- res/skins/LateNight/style.qss | 6 ++++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/res/skins/LateNight/aux_unit.xml b/res/skins/LateNight/aux_unit.xml index 3d6fd78357cc..1b0ebc22e40c 100644 --- a/res/skins/LateNight/aux_unit.xml +++ b/res/skins/LateNight/aux_unit.xml @@ -148,7 +148,7 @@ diff --git a/res/skins/LateNight/aux_unit_unconfigured.xml b/res/skins/LateNight/aux_unit_unconfigured.xml index bac05768b4ac..b39999ed7095 100644 --- a/res/skins/LateNight/aux_unit_unconfigured.xml +++ b/res/skins/LateNight/aux_unit_unconfigured.xml @@ -24,7 +24,7 @@ diff --git a/res/skins/LateNight/mic_unit.xml b/res/skins/LateNight/mic_unit.xml index 454b6be64c04..c8414a7f5817 100644 --- a/res/skins/LateNight/mic_unit.xml +++ b/res/skins/LateNight/mic_unit.xml @@ -148,7 +148,7 @@ diff --git a/res/skins/LateNight/mic_unit_unconfigured.xml b/res/skins/LateNight/mic_unit_unconfigured.xml index 0c78643c2001..5e1d0f04871f 100644 --- a/res/skins/LateNight/mic_unit_unconfigured.xml +++ b/res/skins/LateNight/mic_unit_unconfigured.xml @@ -24,7 +24,7 @@ diff --git a/res/skins/LateNight/style.qss b/res/skins/LateNight/style.qss index 97c0a547b37b..12eae2b8da2d 100644 --- a/res/skins/LateNight/style.qss +++ b/res/skins/LateNight/style.qss @@ -1652,10 +1652,12 @@ WBeatSpinBox, padding: 0px 0px 3px 1px; } #MicAuxPlayButtonBox { - margin: 0px 0px 2px 1px; + qproperty-alignment: 'AlignCenter | AlignBottom'; + padding: 0px 0px 2px 1px; } #MicAuxAddBox { - margin: 6px 0px 8px 1px; + qproperty-alignment: 'AlignCenter | AlignBottom'; + padding: 0px 0px 8px 1px; } #MicAuxSubControlsFrame { From 128e3faec7ec94bee40fd81228f5626a40621056 Mon Sep 17 00:00:00 2001 From: nuess0r Date: Fri, 3 Apr 2020 23:00:11 +0200 Subject: [PATCH 169/393] Improvements suggested by Holzhaus - Changed class name to uppercase (DJC4 instead of djc4) - Improved controller description --- res/controllers/Stanton-DJC-4-scripts.js | 115 ++++++------ res/controllers/Stanton-DJC-4.midi.xml | 230 +++++++++++------------ 2 files changed, 172 insertions(+), 173 deletions(-) diff --git a/res/controllers/Stanton-DJC-4-scripts.js b/res/controllers/Stanton-DJC-4-scripts.js index a70ae4a78974..c0c2ebc70cb9 100644 --- a/res/controllers/Stanton-DJC-4-scripts.js +++ b/res/controllers/Stanton-DJC-4-scripts.js @@ -1,5 +1,5 @@ /** - * Stanton DJC4 controller script v1.0 for Mixxx v2.2.3 + * Stanton DJC.4 controller script v1.0 for Mixxx v2.2.3 * * Written by Martin Bruset Solberg * Adopted for v2.2.3 by Christoph Zimmermann @@ -14,18 +14,17 @@ * **/ -var djc4 = {}; +var DJC4 = {}; ///////////////// // Tweakables. // ///////////////// -djc4.tempoRange = [0.08, 0.16, 0.5]; // not used yet! -djc4.autoShowFourDecks = false; -djc4.showMasterVu = true; // if set to false, show channel VU meter +DJC4.autoShowFourDecks = false; +DJC4.showMasterVu = true; // if set to false, show channel VU meter // amount the dryWetKnob changes the value for each increment -djc4.dryWetAdjustValue = 0.05; +DJC4.dryWetAdjustValue = 0.05; /////////// // Code. // @@ -34,7 +33,7 @@ djc4.dryWetAdjustValue = 0.05; // ---------- Global variables ---------- // MIDI Reception commands (from spec) -djc4.leds = { +DJC4.leds = { loopminus: 2, loopplus: 3, loopin: 4, @@ -78,20 +77,20 @@ djc4.leds = { // ---------- Functions ---------- // Called when the MIDI device is opened & set up. -djc4.init = function() { +DJC4.init = function() { var i; // Put all LEDs to default state. - djc4.allLed2Default(); + DJC4.allLed2Default(); - engine.makeConnection("[Channel3]", "track_loaded", djc4.autoShowDecks); - engine.makeConnection("[Channel4]", "track_loaded", djc4.autoShowDecks); + engine.makeConnection("[Channel3]", "track_loaded", DJC4.autoShowDecks); + engine.makeConnection("[Channel4]", "track_loaded", DJC4.autoShowDecks); if (engine.getValue("[Master]", "num_samplers") < 8) { engine.setValue("[Master]", "num_samplers", 8); } - djc4.browseEncoder = new components.Encoder({ + DJC4.browseEncoder = new components.Encoder({ group: "[Library]", inKey: "Move", input: function(channel, control, value) { @@ -109,38 +108,38 @@ djc4.init = function() { }, }); - djc4.deck = []; + DJC4.deck = []; for (i = 0; i < 4; i++) { - djc4.deck[i] = new djc4.Deck(i + 1); - djc4.deck[i].setCurrentDeck("[Channel" + (i + 1) + "]"); + DJC4.deck[i] = new DJC4.Deck(i + 1); + DJC4.deck[i].setCurrentDeck("[Channel" + (i + 1) + "]"); } - djc4.effectUnit = []; + DJC4.effectUnit = []; for (i = 0; i <= 3; i++) { - djc4.effectUnit[i] = new components.EffectUnit([i + 1]); - djc4.effectUnit[i].shiftOffset = 0x32; - djc4.effectUnit[i].shiftControl = true; - djc4.effectUnit[i].enableButtons[1].midi = [0x90 + i, 0x1F]; - djc4.effectUnit[i].enableButtons[2].midi = [0x90 + i, 0x20]; - djc4.effectUnit[i].enableButtons[3].midi = [0x90 + i, 0x21]; - djc4.effectUnit[i].effectFocusButton.midi = [0x90 + i, 0x1D]; - djc4.effectUnit[i].knobs[1].midi = [0xB0 + i, 0x09]; - djc4.effectUnit[i].knobs[2].midi = [0xB0 + i, 0x0A]; - djc4.effectUnit[i].knobs[3].midi = [0xB0 + i, 0x0B]; - djc4.effectUnit[i].dryWetKnob.midi = [0xB0 + i, 0x08]; - djc4.effectUnit[i].dryWetKnob.input = function(channel, control, value) { + DJC4.effectUnit[i] = new components.EffectUnit([i + 1]); + DJC4.effectUnit[i].shiftOffset = 0x32; + DJC4.effectUnit[i].shiftControl = true; + DJC4.effectUnit[i].enableButtons[1].midi = [0x90 + i, 0x1F]; + DJC4.effectUnit[i].enableButtons[2].midi = [0x90 + i, 0x20]; + DJC4.effectUnit[i].enableButtons[3].midi = [0x90 + i, 0x21]; + DJC4.effectUnit[i].effectFocusButton.midi = [0x90 + i, 0x1D]; + DJC4.effectUnit[i].knobs[1].midi = [0xB0 + i, 0x09]; + DJC4.effectUnit[i].knobs[2].midi = [0xB0 + i, 0x0A]; + DJC4.effectUnit[i].knobs[3].midi = [0xB0 + i, 0x0B]; + DJC4.effectUnit[i].dryWetKnob.midi = [0xB0 + i, 0x08]; + DJC4.effectUnit[i].dryWetKnob.input = function(channel, control, value) { if (value === 0x41) { - this.inSetParameter(this.inGetParameter() + djc4.dryWetAdjustValue); + this.inSetParameter(this.inGetParameter() + DJC4.dryWetAdjustValue); } else if (value === 0x3F) { - this.inSetParameter(this.inGetParameter() - djc4.dryWetAdjustValue); + this.inSetParameter(this.inGetParameter() - DJC4.dryWetAdjustValue); } }; - djc4.effectUnit[i].init(); + DJC4.effectUnit[i].init(); } // === Master VU Meter === - if (djc4.showMasterVu === true) { - djc4.vuMeter = new components.Component({ + if (DJC4.showMasterVu === true) { + DJC4.vuMeter = new components.Component({ midi: [0xB0, 0x03], group: "[Master]", outKey: "VuMeterL", @@ -156,7 +155,7 @@ djc4.init = function() { }, }); - djc4.vuMeter = new components.Component({ + DJC4.vuMeter = new components.Component({ midi: [0xB0, 0x04], group: "[Master]", outKey: "VuMeterR", @@ -175,12 +174,12 @@ djc4.init = function() { }; // Called when the MIDI device is closed -djc4.shutdown = function() { +DJC4.shutdown = function() { // Put all LEDs to default state. - djc4.allLed2Default(); + DJC4.allLed2Default(); }; -djc4.Deck = function(deckNumber) { +DJC4.Deck = function(deckNumber) { components.Deck.call(this, deckNumber); // === Instantiate controls === @@ -206,8 +205,8 @@ djc4.Deck = function(deckNumber) { } // === Channel VU Meter === - if (djc4.showMasterVu === false) { - djc4.vuMeter = new components.Component({ + if (DJC4.showMasterVu === false) { + DJC4.vuMeter = new components.Component({ midi: [0xB0+deckNumber-1, 0x02], group: "[Channel" + deckNumber + "]", outKey: "VuMeter", @@ -231,7 +230,7 @@ djc4.Deck = function(deckNumber) { if (value === 0x7F) { // Toggle setting this.scratchMode = !this.scratchMode; - djc4.setLed(script.deckFromGroup(this.currentDeck), djc4.leds["scratch"], this.scratchMode); + DJC4.setLed(script.deckFromGroup(this.currentDeck), DJC4.leds["scratch"], this.scratchMode); } }; @@ -278,7 +277,7 @@ djc4.Deck = function(deckNumber) { // song duration in order for the jog wheel to cover the same amount // of time given a constant turning angle. var duration = engine.getValue(this.currentDeck, "duration"); - var newPos = Math.max(0, oldPos + (newValue * djc4.stripSearchScaling / duration)); + var newPos = Math.max(0, oldPos + (newValue * DJC4.stripSearchScaling / duration)); engine.setValue(this.currentDeck, "playposition", newPos); // Strip search } else { engine.setValue(this.currentDeck, "jog", newValue); // Pitch bend @@ -288,12 +287,12 @@ djc4.Deck = function(deckNumber) { // === FOR MANAGING LEDS === -djc4.allLed2Default = function() { +DJC4.allLed2Default = function() { // All LEDs OFF for deck 1 to 4 var i = 0; for (i = 1; i <= 4; i++) { - for (var led in djc4.leds) { - djc4.setLed(i, djc4.leds[led], 0); + for (var led in DJC4.leds) { + DJC4.setLed(i, DJC4.leds[led], 0); } // Channel VU meter midi.sendShortMsg(0xB0 + (i - 1), 2, 0); @@ -304,7 +303,7 @@ djc4.allLed2Default = function() { }; // Set leds function -djc4.setLed = function(deck, led, status) { +DJC4.setLed = function(deck, led, status) { var ledStatus = 0x00; // Default OFF switch (status) { case 0: @@ -327,37 +326,37 @@ djc4.setLed = function(deck, led, status) { // === MISC COMMON === -djc4.autoShowDecks = function() { +DJC4.autoShowDecks = function() { var anyLoaded = engine.getValue("[Channel3]", "track_loaded") || engine.getValue("[Channel4]", "track_loaded"); - if (!djc4.autoShowFourDecks) { + if (!DJC4.autoShowFourDecks) { return; } engine.setValue("[Master]", "show_4decks", anyLoaded); }; -djc4.shiftButton = function(channel, control, value) { +DJC4.shiftButton = function(channel, control, value) { var i; if (value === 0x7F) { - djc4.browseEncoder.shift(); + DJC4.browseEncoder.shift(); for (i = 0; i < 4; i++) { - djc4.deck[i].shift(); - djc4.effectUnit[i].shift(); + DJC4.deck[i].shift(); + DJC4.effectUnit[i].shift(); } } else { - djc4.browseEncoder.unshift(); + DJC4.browseEncoder.unshift(); for (i = 0; i < 4; i++) { - djc4.deck[i].unshift(); - djc4.effectUnit[i].unshift(); + DJC4.deck[i].unshift(); + DJC4.effectUnit[i].unshift(); } } }; -djc4.crossfaderCurve = function(channel, control, value) { - script.crossfaderCurve(value, 0, 127); +DJC4.crossfaderCurve = function(channel, control, value) { + script.crossfaderCurve(value, 0, 0x7F); }; // === Sampler Volume Control === -djc4.samplerVolume = function(channel, control, value) { +DJC4.samplerVolume = function(channel, control, value) { // check if the Sampler Volume is at Zero and if so hide the sampler bank if (value > 0x00) { engine.setValue("[Samplers]", "show_samplers", true); @@ -376,4 +375,4 @@ djc4.samplerVolume = function(channel, control, value) { // give your custom Deck all the methods of the generic Deck in the Components library -djc4.Deck.prototype = Object.create(components.Deck.prototype); +DJC4.Deck.prototype = Object.create(components.Deck.prototype); diff --git a/res/controllers/Stanton-DJC-4.midi.xml b/res/controllers/Stanton-DJC-4.midi.xml index 0caf19fc5034..e095496d818b 100644 --- a/res/controllers/Stanton-DJC-4.midi.xml +++ b/res/controllers/Stanton-DJC-4.midi.xml @@ -3,19 +3,19 @@ Stanton DJC.4 Martin Bruset Solberg, Christoph Zimmermann - The Stanton DJC.4 is a four-deck control surface with large, touch-sensitive jog wheels and built-in audio interface (2 inputs, 2 outputs). Configured as four-deck, four-fx and master VU meter controller + The Stanton DJC.4 is a 4 deck controller with large, touch-sensitive jog wheels and a built-in audio interface (2 inputs, 2 outputs). It features 4 FX units and a master VU meter. https://mixxx.org/wiki/doku.php/stanton_djc.4 - + [Channel1] - djc4.deck[0].beatLoopEncoder.input + DJC4.deck[0].beatLoopEncoder.input 0xB0 0x01 @@ -33,7 +33,7 @@ [Channel2] - djc4.deck[1].beatLoopEncoder.input + DJC4.deck[1].beatLoopEncoder.input 0xB1 0x01 @@ -51,7 +51,7 @@ [Channel3] - djc4.deck[2].beatLoopEncoder.input + DJC4.deck[2].beatLoopEncoder.input 0xB2 0x01 @@ -69,7 +69,7 @@ [Channel4] - djc4.deck[3].beatLoopEncoder.input + DJC4.deck[3].beatLoopEncoder.input 0xB3 0x01 @@ -87,7 +87,7 @@ [Channel1] - djc4.deck[0].wheelTurn + DJC4.deck[0].wheelTurn 0xB0 0x02 @@ -105,7 +105,7 @@ [Channel2] - djc4.deck[1].wheelTurn + DJC4.deck[1].wheelTurn 0xB1 0x02 @@ -123,7 +123,7 @@ [Channel3] - djc4.deck[2].wheelTurn + DJC4.deck[2].wheelTurn 0xB2 0x02 @@ -141,7 +141,7 @@ [Channel4] - djc4.deck[3].wheelTurn + DJC4.deck[3].wheelTurn 0xB3 0x02 @@ -483,7 +483,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[0].dryWetKnob.input + DJC4.effectUnit[0].dryWetKnob.input 0xB0 0x08 @@ -492,7 +492,7 @@ [EffectRack1_EffectUnit3] - djc4.effectUnit[1].dryWetKnob.input + DJC4.effectUnit[1].dryWetKnob.input 0xB1 0x08 @@ -501,7 +501,7 @@ [EffectRack1_EffectUnit3] - djc4.effectUnit[2].dryWetKnob.input + DJC4.effectUnit[2].dryWetKnob.input 0xB2 0x08 @@ -510,7 +510,7 @@ [EffectRack1_EffectUnit3] - djc4.effectUnit[3].dryWetKnob.input + DJC4.effectUnit[3].dryWetKnob.input 0xB3 0x08 @@ -591,7 +591,7 @@ [EffectRack1_EffectUnit1_Effect1] - djc4.effectUnit[0].knobs[1].input + DJC4.effectUnit[0].knobs[1].input 0xB0 0x09 @@ -600,7 +600,7 @@ [EffectRack1_EffectUnit2_Effect1] - djc4.effectUnit[1].knobs[1].input + DJC4.effectUnit[1].knobs[1].input 0xB1 0x09 @@ -609,7 +609,7 @@ [EffectRack1_EffectUnit1_Effect1] - djc4.effectUnit[2].knobs[1].input + DJC4.effectUnit[2].knobs[1].input 0xB2 0x09 @@ -618,7 +618,7 @@ [EffectRack1_EffectUnit2_Effect1] - djc4.effectUnit[3].knobs[1].input + DJC4.effectUnit[3].knobs[1].input 0xB3 0x09 @@ -663,7 +663,7 @@ [EffectRack1_EffectUnit1_Effect2] - djc4.effectUnit[0].knobs[2].input + DJC4.effectUnit[0].knobs[2].input 0xB0 0x0A @@ -672,7 +672,7 @@ [EffectRack1_EffectUnit2_Effect2] - djc4.effectUnit[1].knobs[2].input + DJC4.effectUnit[1].knobs[2].input 0xB1 0x0A @@ -681,7 +681,7 @@ [EffectRack1_EffectUnit1_Effect2] - djc4.effectUnit[2].knobs[2].input + DJC4.effectUnit[2].knobs[2].input 0xB2 0x0A @@ -690,7 +690,7 @@ [EffectRack1_EffectUnit2_Effect2] - djc4.effectUnit[3].knobs[2].input + DJC4.effectUnit[3].knobs[2].input 0xB3 0x0A @@ -735,7 +735,7 @@ [EffectRack1_EffectUnit1_Effect3] - djc4.effectUnit[0].knobs[3].input + DJC4.effectUnit[0].knobs[3].input 0xB0 0x0B @@ -744,7 +744,7 @@ [EffectRack1_EffectUnit2_Effect3] - djc4.effectUnit[1].knobs[3].input + DJC4.effectUnit[1].knobs[3].input 0xB1 0x0B @@ -753,7 +753,7 @@ [EffectRack1_EffectUnit1_Effect3] - djc4.effectUnit[2].knobs[3].input + DJC4.effectUnit[2].knobs[3].input 0xB2 0x0B @@ -762,7 +762,7 @@ [EffectRack1_EffectUnit2_Effect3] - djc4.effectUnit[3].knobs[3].input + DJC4.effectUnit[3].knobs[3].input 0xB3 0x0B @@ -771,7 +771,7 @@ [Sampler1] - djc4.deck[0].samplerButtons[0].input + DJC4.deck[0].samplerButtons[0].input 0x90 0x0C @@ -780,7 +780,7 @@ [Sampler5] - djc4.deck[1].samplerButtons[0].input + DJC4.deck[1].samplerButtons[0].input 0x91 0x0C @@ -789,7 +789,7 @@ [Sampler1] - djc4.deck[2].samplerButtons[0].input + DJC4.deck[2].samplerButtons[0].input 0x92 0x0C @@ -798,7 +798,7 @@ [Sampler5] - djc4.deck[3].samplerButtons[0].input + DJC4.deck[3].samplerButtons[0].input 0x93 0x0C @@ -807,7 +807,7 @@ [Sampler2] - djc4.deck[0].samplerButtons[1].input + DJC4.deck[0].samplerButtons[1].input 0x90 0x0D @@ -816,7 +816,7 @@ [Sampler6] - djc4.deck[1].samplerButtons[1].input + DJC4.deck[1].samplerButtons[1].input 0x91 0x0D @@ -825,7 +825,7 @@ [Sampler2] - djc4.deck[2].samplerButtons[1].input + DJC4.deck[2].samplerButtons[1].input 0x92 0x0D @@ -834,7 +834,7 @@ [Sampler6] - djc4.deck[3].samplerButtons[1].input + DJC4.deck[3].samplerButtons[1].input 0x93 0x0D @@ -843,7 +843,7 @@ [Sampler] - djc4.samplerVolume + DJC4.samplerVolume 0xB0 0x0D @@ -852,7 +852,7 @@ [Library] - djc4.browseEncoder.input + DJC4.browseEncoder.input 0xB0 0x0E @@ -861,7 +861,7 @@ [Sampler3] - djc4.deck[0].samplerButtons[2].input + DJC4.deck[0].samplerButtons[2].input 0x90 0x0E @@ -870,7 +870,7 @@ [Sampler7] - djc4.deck[1].samplerButtons[2].input + DJC4.deck[1].samplerButtons[2].input 0x91 0x0E @@ -879,7 +879,7 @@ [Sampler3] - djc4.deck[2].samplerButtons[2].input + DJC4.deck[2].samplerButtons[2].input 0x92 0x0E @@ -888,7 +888,7 @@ [Sampler7] - djc4.deck[3].samplerButtons[2].input + DJC4.deck[3].samplerButtons[2].input 0x93 0x0E @@ -897,7 +897,7 @@ [Sampler4] - djc4.deck[0].samplerButtons[3].input + DJC4.deck[0].samplerButtons[3].input 0x90 0x0F @@ -906,7 +906,7 @@ [Sampler8] - djc4.deck[1].samplerButtons[3].input + DJC4.deck[1].samplerButtons[3].input 0x91 0x0F @@ -915,7 +915,7 @@ [Sampler4] - djc4.deck[2].samplerButtons[3].input + DJC4.deck[2].samplerButtons[3].input 0x92 0x0F @@ -924,7 +924,7 @@ [Sampler8] - djc4.deck[3].samplerButtons[3].input + DJC4.deck[3].samplerButtons[3].input 0x93 0x0F @@ -978,7 +978,7 @@ [Master] - djc4.crossfaderCurve + DJC4.crossfaderCurve 0xB0 0x12 @@ -1113,7 +1113,7 @@ [Channel1] - djc4.deck[0].toggleScratchMode + DJC4.deck[0].toggleScratchMode 0x90 0x15 @@ -1122,7 +1122,7 @@ [Channel2] - djc4.deck[1].toggleScratchMode + DJC4.deck[1].toggleScratchMode 0x91 0x15 @@ -1131,7 +1131,7 @@ [Channel3] - djc4.deck[2].toggleScratchMode + DJC4.deck[2].toggleScratchMode 0x92 0x15 @@ -1140,7 +1140,7 @@ [Channel4] - djc4.deck[3].toggleScratchMode + DJC4.deck[3].toggleScratchMode 0x93 0x15 @@ -1401,7 +1401,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[0].effectFocusButton.input + DJC4.effectUnit[0].effectFocusButton.input 0x90 0x1D @@ -1410,7 +1410,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[1].effectFocusButton.input + DJC4.effectUnit[1].effectFocusButton.input 0x91 0x1D @@ -1419,7 +1419,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[2].effectFocusButton.input + DJC4.effectUnit[2].effectFocusButton.input 0x92 0x1D @@ -1428,7 +1428,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[3].effectFocusButton.input + DJC4.effectUnit[3].effectFocusButton.input 0x93 0x1D @@ -1473,7 +1473,7 @@ [EffectRack1_EffectUnit1_Effect1] - djc4.effectUnit[0].enableButtons[1].input + DJC4.effectUnit[0].enableButtons[1].input 0x90 0x1F @@ -1482,7 +1482,7 @@ [EffectRack1_EffectUnit2_Effect1] - djc4.effectUnit[1].enableButtons[1].input + DJC4.effectUnit[1].enableButtons[1].input 0x91 0x1F @@ -1491,7 +1491,7 @@ [EffectRack1_EffectUnit3_Effect1] - djc4.effectUnit[2].enableButtons[1].input + DJC4.effectUnit[2].enableButtons[1].input 0x92 0x1F @@ -1500,7 +1500,7 @@ [EffectRack1_EffectUnit4_Effect1] - djc4.effectUnit[3].enableButtons[1].input + DJC4.effectUnit[3].enableButtons[1].input 0x93 0x1F @@ -1509,7 +1509,7 @@ [EffectRack1_EffectUnit1_Effect2] - djc4.effectUnit[0].enableButtons[2].input + DJC4.effectUnit[0].enableButtons[2].input 0x90 0x20 @@ -1518,7 +1518,7 @@ [EffectRack1_EffectUnit2_Effect2] - djc4.effectUnit[1].enableButtons[2].input + DJC4.effectUnit[1].enableButtons[2].input 0x91 0x20 @@ -1527,7 +1527,7 @@ [EffectRack1_EffectUnit3_Effect2] - djc4.effectUnit[2].enableButtons[2].input + DJC4.effectUnit[2].enableButtons[2].input 0x92 0x20 @@ -1536,7 +1536,7 @@ [EffectRack1_EffectUnit4_Effect2] - djc4.effectUnit[3].enableButtons[2].input + DJC4.effectUnit[3].enableButtons[2].input 0x93 0x20 @@ -1545,7 +1545,7 @@ [Channel1] - djc4.deck[0].wheelTurn + DJC4.deck[0].wheelTurn 0xB0 0x20 @@ -1554,7 +1554,7 @@ [Channel2] - djc4.deck[1].wheelTurn + DJC4.deck[1].wheelTurn 0xB1 0x20 @@ -1563,7 +1563,7 @@ [Channel3] - djc4.deck[2].wheelTurn + DJC4.deck[2].wheelTurn 0xB2 0x20 @@ -1572,7 +1572,7 @@ [Channel4] - djc4.deck[3].wheelTurn + DJC4.deck[3].wheelTurn 0xB3 0x20 @@ -1581,7 +1581,7 @@ [EffectRack1_EffectUnit1_Effect3] - djc4.effectUnit[0].enableButtons[3].input + DJC4.effectUnit[0].enableButtons[3].input 0x90 0x21 @@ -1590,7 +1590,7 @@ [EffectRack1_EffectUnit2_Effect3] - djc4.effectUnit[1].enableButtons[3].input + DJC4.effectUnit[1].enableButtons[3].input 0x91 0x21 @@ -1599,7 +1599,7 @@ [EffectRack1_EffectUnit1_Effect3] - djc4.effectUnit[2].enableButtons[3].input + DJC4.effectUnit[2].enableButtons[3].input 0x92 0x21 @@ -1608,7 +1608,7 @@ [EffectRack1_EffectUnit2_Effect3] - djc4.effectUnit[3].enableButtons[3].input + DJC4.effectUnit[3].enableButtons[3].input 0x93 0x21 @@ -1689,7 +1689,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[0].dryWetKnob.input + DJC4.effectUnit[0].dryWetKnob.input 0xB0 0x26 @@ -1698,7 +1698,7 @@ [EffectRack1_EffectUnit2] - djc4.effectUnit[1].dryWetKnob.input + DJC4.effectUnit[1].dryWetKnob.input 0xB1 0x26 @@ -1707,7 +1707,7 @@ [EffectRack1_EffectUnit2] - djc4.effectUnit[2].dryWetKnob.input + DJC4.effectUnit[2].dryWetKnob.input 0xB2 0x26 @@ -1716,7 +1716,7 @@ [EffectRack1_EffectUnit3] - djc4.effectUnit[3].dryWetKnob.input + DJC4.effectUnit[3].dryWetKnob.input 0xB3 0x26 @@ -1725,7 +1725,7 @@ [Channel1] - djc4.deck[0].wheelTouch + DJC4.deck[0].wheelTouch 0x90 0x26 @@ -1734,7 +1734,7 @@ [Channel2] - djc4.deck[1].wheelTouch + DJC4.deck[1].wheelTouch 0x91 0x26 @@ -1743,7 +1743,7 @@ [Channel3] - djc4.deck[2].wheelTouch + DJC4.deck[2].wheelTouch 0x92 0x26 @@ -1752,7 +1752,7 @@ [Channel4] - djc4.deck[3].wheelTouch + DJC4.deck[3].wheelTouch 0x93 0x26 @@ -1770,7 +1770,7 @@ [Library] - djc4.browseEncoder.input + DJC4.browseEncoder.input 0xB0 0x2C @@ -1779,7 +1779,7 @@ [Master] - djc4.shiftButton + DJC4.shiftButton 0x90 0x2D @@ -2076,7 +2076,7 @@ [Sampler1] - djc4.deck[0].samplerButtons[0].input + DJC4.deck[0].samplerButtons[0].input 0x90 0x3E @@ -2085,7 +2085,7 @@ [Sampler5] - djc4.deck[1].samplerButtons[0].input + DJC4.deck[1].samplerButtons[0].input 0x91 0x3E @@ -2094,7 +2094,7 @@ [Sampler1] - djc4.deck[3].samplerButtons[0].input + DJC4.deck[3].samplerButtons[0].input 0x92 0x3E @@ -2103,7 +2103,7 @@ [Sampler5] - djc4.deck[3].samplerButtons[0].input + DJC4.deck[3].samplerButtons[0].input 0x93 0x3E @@ -2112,7 +2112,7 @@ [Sampler2] - djc4.deck[0].samplerButtons[1].input + DJC4.deck[0].samplerButtons[1].input 0x90 0x3F @@ -2121,7 +2121,7 @@ [Sampler6] - djc4.deck[1].samplerButtons[1].input + DJC4.deck[1].samplerButtons[1].input 0x91 0x3F @@ -2130,7 +2130,7 @@ [Sampler2] - djc4.deck[2].samplerButtons[1].input + DJC4.deck[2].samplerButtons[1].input 0x92 0x3F @@ -2139,7 +2139,7 @@ [Sampler6] - djc4.deck[3].samplerButtons[1].input + DJC4.deck[3].samplerButtons[1].input 0x93 0x3F @@ -2148,7 +2148,7 @@ [Sampler3] - djc4.deck[0].samplerButtons[2].input + DJC4.deck[0].samplerButtons[2].input 0x90 0x40 @@ -2157,7 +2157,7 @@ [Sampler7] - djc4.deck[1].samplerButtons[2].input + DJC4.deck[1].samplerButtons[2].input 0x91 0x40 @@ -2166,7 +2166,7 @@ [Sampler3] - djc4.deck[2].samplerButtons[2].input + DJC4.deck[2].samplerButtons[2].input 0x92 0x40 @@ -2175,7 +2175,7 @@ [Sampler7] - djc4.deck[3].samplerButtons[2].input + DJC4.deck[3].samplerButtons[2].input 0x93 0x40 @@ -2184,7 +2184,7 @@ [Sampler4] - djc4.deck[0].samplerButtons[3].input + DJC4.deck[0].samplerButtons[3].input 0x90 0x41 @@ -2193,7 +2193,7 @@ [Sampler8] - djc4.deck[1].samplerButtons[3].input + DJC4.deck[1].samplerButtons[3].input 0x91 0x41 @@ -2202,7 +2202,7 @@ [Sampler4] - djc4.deck[2].samplerButtons[3].input + DJC4.deck[2].samplerButtons[3].input 0x92 0x41 @@ -2211,7 +2211,7 @@ [Sampler8] - djc4.deck[3].samplerButtons[3].input + DJC4.deck[3].samplerButtons[3].input 0x93 0x41 @@ -2400,7 +2400,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[0].effectFocusButton.input + DJC4.effectUnit[0].effectFocusButton.input 0x90 0x4F @@ -2409,7 +2409,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[1].effectFocusButton.input + DJC4.effectUnit[1].effectFocusButton.input 0x91 0x4F @@ -2418,7 +2418,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[2].effectFocusButton.input + DJC4.effectUnit[2].effectFocusButton.input 0x92 0x4F @@ -2427,7 +2427,7 @@ [EffectRack1_EffectUnit1] - djc4.effectUnit[3].effectFocusButton.input + DJC4.effectUnit[3].effectFocusButton.input 0x93 0x4F @@ -2436,7 +2436,7 @@ [EffectRack1_EffectUnit1_Effect1] - djc4.effectUnit[0].enableButtons[1].input + DJC4.effectUnit[0].enableButtons[1].input 0x90 0x51 @@ -2445,7 +2445,7 @@ [EffectRack1_EffectUnit2_Effect1] - djc4.effectUnit[1].enableButtons[1].input + DJC4.effectUnit[1].enableButtons[1].input 0x91 0x51 @@ -2454,7 +2454,7 @@ [EffectRack1_EffectUnit1_Effect1] - djc4.effectUnit[2].enableButtons[1].input + DJC4.effectUnit[2].enableButtons[1].input 0x92 0x51 @@ -2463,7 +2463,7 @@ [EffectRack1_EffectUnit2_Effect1] - djc4.effectUnit[3].enableButtons[1].input + DJC4.effectUnit[3].enableButtons[1].input 0x93 0x51 @@ -2472,7 +2472,7 @@ [EffectRack1_EffectUnit1_Effect2] - djc4.effectUnit[0].enableButtons[2].input + DJC4.effectUnit[0].enableButtons[2].input 0x90 0x52 @@ -2481,7 +2481,7 @@ [EffectRack1_EffectUnit2_Effect2] - djc4.effectUnit[1].enableButtons[2].input + DJC4.effectUnit[1].enableButtons[2].input 0x91 0x52 @@ -2490,7 +2490,7 @@ [EffectRack1_EffectUnit1_Effect2] - djc4.effectUnit[2].enableButtons[2].input + DJC4.effectUnit[2].enableButtons[2].input 0x92 0x52 @@ -2499,7 +2499,7 @@ [EffectRack1_EffectUnit2_Effect2] - djc4.effectUnit[3].enableButtons[2].input + DJC4.effectUnit[3].enableButtons[2].input 0x93 0x52 @@ -2508,7 +2508,7 @@ [EffectRack1_EffectUnit1_Effect3] - djc4.effectUnit[0].enableButtons[3].input + DJC4.effectUnit[0].enableButtons[3].input 0x90 0x53 @@ -2517,7 +2517,7 @@ [EffectRack1_EffectUnit2_Effect3] - djc4.effectUnit[1].enableButtons[3].input + DJC4.effectUnit[1].enableButtons[3].input 0x91 0x53 @@ -2526,7 +2526,7 @@ [EffectRack1_EffectUnit1_Effect3] - djc4.effectUnit[2].enableButtons[3].input + DJC4.effectUnit[2].enableButtons[3].input 0x92 0x53 @@ -2535,7 +2535,7 @@ [EffectRack1_EffectUnit2_Effect3] - djc4.effectUnit[3].enableButtons[3].input + DJC4.effectUnit[3].enableButtons[3].input 0x93 0x53 @@ -2544,7 +2544,7 @@ [Channel1] - djc4.deck[0].wheelTouch + DJC4.deck[0].wheelTouch 0x90 0x58 @@ -2553,7 +2553,7 @@ [Channel2] - djc4.deck[1].wheelTouch + DJC4.deck[1].wheelTouch 0x91 0x58 @@ -2562,7 +2562,7 @@ [Channel3] - djc4.deck[2].wheelTouch + DJC4.deck[2].wheelTouch 0x92 0x58 @@ -2571,7 +2571,7 @@ [Channel4] - djc4.deck[3].wheelTouch + DJC4.deck[3].wheelTouch 0x93 0x58 From 0417746fd62ec2166772b435ecfd5a5d9ca509d4 Mon Sep 17 00:00:00 2001 From: nuess0r Date: Fri, 3 Apr 2020 23:32:47 +0200 Subject: [PATCH 170/393] Mapping [Library].MoveLeft and MoveRight to SHIFT + LOAD buttons --- res/controllers/Stanton-DJC-4.midi.xml | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/res/controllers/Stanton-DJC-4.midi.xml b/res/controllers/Stanton-DJC-4.midi.xml index e095496d818b..c91fe88fd7c0 100644 --- a/res/controllers/Stanton-DJC-4.midi.xml +++ b/res/controllers/Stanton-DJC-4.midi.xml @@ -2542,6 +2542,42 @@ + + [Library] + MoveLeft + 0x90 + 0x54 + + + + + + [Library] + MoveRight + 0x91 + 0x55 + + + + + + [Library] + MoveLeft + 0x92 + 0x54 + + + + + + [Library] + MoveRight + 0x93 + 0x55 + + + + [Channel1] DJC4.deck[0].wheelTouch From 1ef378079b2dab206acd2190917cfc33ccbe992e Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sat, 4 Apr 2020 11:07:08 +0200 Subject: [PATCH 171/393] Added Stanton DJC.4 to the 2.2.4 CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ece5b56cb5ce..9634f2aceeb5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ * Add controller mapping for Native Instruments Traktor Kontrol S2 MK3 #2348 * Add controller mapping for Soundless joyMIDI #2425 * Add controller mapping for Hercules DJControl Inpulse 300 #2465 +* Add controller mapping for Stanton DJC.4 #2607 ==== 2.2.3 2019-11-24 ==== * Don't make users reconfigure sound hardware when it has not changed #2253 From ffc51f147288066ab5b6310eeafd413b0153e950 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 4 Apr 2020 11:58:50 +0200 Subject: [PATCH 172/393] Add typedefs for value_t --- src/audio/types.h | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/audio/types.h b/src/audio/types.h index 64b55f260e01..72872d687021 100644 --- a/src/audio/types.h +++ b/src/audio/types.h @@ -33,13 +33,16 @@ typedef std::optional OptionalChannelLayout; QDebug operator<<(QDebug dbg, ChannelLayout arg); class ChannelCount { + public: + typedef SINT value_t; + private: // The default value is invalid and indicates a missing or unknown value. - static constexpr SINT kValueDefault = 0; + static constexpr value_t kValueDefault = 0; public: - static constexpr SINT kValueMin = 1; // lower bound (inclusive) - static constexpr SINT kValueMax = 255; // upper bound (inclusive, 8-bit unsigned integer) + static constexpr value_t kValueMin = 1; // lower bound (inclusive) + static constexpr value_t kValueMax = 255; // upper bound (inclusive, 8-bit unsigned integer) static constexpr ChannelCount min() { return ChannelCount(kValueMin); @@ -60,10 +63,12 @@ class ChannelCount { DEBUG_ASSERT(!"unreachable code"); } - explicit constexpr ChannelCount(SINT value = kValueDefault) + explicit constexpr ChannelCount( + value_t value = kValueDefault) : m_value(value) { } - explicit ChannelCount(ChannelLayout layout) + explicit ChannelCount( + ChannelLayout layout) : m_value(fromLayout(layout).m_value) { } @@ -72,12 +77,12 @@ class ChannelCount { (m_value <= kValueMax); } - /*implicit*/ constexpr operator SINT() const { + /*implicit*/ constexpr operator value_t() const { return m_value; } private: - SINT m_value; + value_t m_value; }; // Defines the ordering of how samples from multiple channels are @@ -100,13 +105,16 @@ typedef std::optional OptionalSampleLayout; QDebug operator<<(QDebug dbg, SampleLayout arg); class SampleRate { + public: + typedef SINT value_t; + private: // The default value is invalid and indicates a missing or unknown value. - static constexpr SINT kValueDefault = 0; + static constexpr value_t kValueDefault = 0; public: - static constexpr SINT kValueMin = 8000; // lower bound (inclusive, = minimum MP3 sample rate) - static constexpr SINT kValueMax = 192000; // upper bound (inclusive) + static constexpr value_t kValueMin = 8000; // lower bound (inclusive, = minimum MP3 sample rate) + static constexpr value_t kValueMax = 192000; // upper bound (inclusive) static constexpr SampleRate min() { return SampleRate(kValueMin); @@ -119,7 +127,8 @@ class SampleRate { return "Hz"; } - explicit constexpr SampleRate(SINT value = kValueDefault) + explicit constexpr SampleRate( + value_t value = kValueDefault) : m_value(value) { } @@ -128,12 +137,12 @@ class SampleRate { (m_value <= kValueMax); } - /*implicit*/ constexpr operator SINT() const { + /*implicit*/ constexpr operator value_t() const { return m_value; } private: - SINT m_value; + value_t m_value; }; QDebug operator<<(QDebug dbg, SampleRate arg); @@ -146,16 +155,20 @@ QDebug operator<<(QDebug dbg, SampleRate arg); // variable bitrate encoding and serves as a rough estimate of the // expected quality. class Bitrate { + public: + typedef SINT value_t; + private: // The default value is invalid and indicates a missing or unknown value. - static constexpr SINT kValueDefault = 0; + static constexpr value_t kValueDefault = 0; public: static constexpr const char* unit() { return "kbps"; } - explicit constexpr Bitrate(SINT value = kValueDefault) + explicit constexpr Bitrate( + value_t value = kValueDefault) : m_value(value) { } @@ -163,13 +176,13 @@ class Bitrate { return m_value > kValueDefault; } - /*implicit*/ operator SINT() const { + /*implicit*/ operator value_t() const { DEBUG_ASSERT(m_value >= kValueDefault); // unsigned value return m_value; } private: - SINT m_value; + value_t m_value; }; QDebug operator<<(QDebug dbg, Bitrate arg); From 201f00e93e0643600c95b1b27dcc56c2605c9fa2 Mon Sep 17 00:00:00 2001 From: ehendrikd Date: Sat, 4 Apr 2020 21:56:11 +1100 Subject: [PATCH 173/393] Rekordbox library feature nulls in strings crash fix (#2627) Remove nulls in strings --- src/library/rekordbox/rekordboxfeature.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index 9d24d8380771..a69563499941 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -266,21 +266,25 @@ QString toUnicode(std::string toConvert) { // getText is needed because the strings in the PDB file "have a variety of obscure representations". QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { + QString text; + if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_short_ascii_t* shortAsciiString = static_cast(deviceString->body()); - return QString::fromStdString(shortAsciiString->text()); + text = QString::fromStdString(shortAsciiString->text()); } else if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_long_ascii_t* longAsciiString = static_cast(deviceString->body()); - return QString::fromStdString(longAsciiString->text()); + text = QString::fromStdString(longAsciiString->text()); } else if (instanceof (deviceString->body())) { rekordbox_pdb_t::device_sql_long_utf16be_t* longUtf16beString = static_cast(deviceString->body()); - return toUnicode(longUtf16beString->text()); + text = toUnicode(longUtf16beString->text()); } - return QString(); + // Some strings read from Rekordbox *.PDB files contain random null characters + // which if not removed cause Mixxx to crash when attempting to read file paths + return text.remove('\x0'); } int createDevicePlaylist(QSqlDatabase& database, QString devicePath) { From 34603bca73cccab8fc4d132361997497494ead8c Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sat, 4 Apr 2020 19:30:07 +0530 Subject: [PATCH 174/393] widget/wtrackmenu: refactor slot functions to be generic --- src/widget/wtrackmenu.cpp | 362 +++++++++++++++------------------ src/widget/wtrackmenu.h | 13 +- src/widget/wtrackproperty.cpp | 217 +------------------- src/widget/wtrackproperty.h | 22 +- src/widget/wtracktableview.cpp | 11 - 5 files changed, 176 insertions(+), 449 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index faa68cb3e6d3..8a4cbccc4af9 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -45,15 +45,17 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager *pTrackCollectionManager) : QMenu(parent), - m_pConfig(std::move(pConfig)), - m_pTrackCollectionManager(pTrackCollectionManager), - m_bPlaylistMenuLoaded(false), - m_bCrateMenuLoaded(false), - m_iCoverSourceColumn(-1), - m_iCoverTypeColumn(-1), - m_iCoverLocationColumn(-1), - m_iCoverHashColumn(-1), - m_iCoverColumn(-1) { + m_pConfig(std::move(pConfig)), + m_pTrackCollectionManager(pTrackCollectionManager), + m_pTrackModel(nullptr), + m_bPlaylistMenuLoaded(false), + m_bCrateMenuLoaded(false), + m_iCoverSourceColumn(-1), + m_iCoverTypeColumn(-1), + m_iCoverLocationColumn(-1), + m_iCoverHashColumn(-1), + m_iCoverColumn(-1), + m_eFilters(Filter::None) { m_pNumSamplers = new ControlProxy( "[Master]", "num_samplers", this); m_pNumDecks = new ControlProxy( @@ -62,6 +64,7 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollec "[Master]", "num_preview_decks", this); constructMenus(); + createActions(); } WTrackMenu::~WTrackMenu() { @@ -120,7 +123,7 @@ void WTrackMenu::createActions() { connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); - connect(m_pRemovePlaylistAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + connect(m_pRemovePlaylistAct, &QAction::triggered, this, &WTrackMenu::slotRemove); m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); @@ -139,8 +142,8 @@ void WTrackMenu::createActions() { this, SLOT(slotShowTrackInfo())); m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); - connect(m_pFileBrowserAct, SIGNAL(triggered()), - this, SLOT(slotOpenInFileBrowser())); + connect(m_pFileBrowserAct, &QAction::triggered, + this, &WTrackMenu::slotOpenInFileBrowser); m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); connect(m_pAutoDJBottomAct, SIGNAL(triggered()), @@ -180,6 +183,7 @@ void WTrackMenu::createActions() { }); } + m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); // currently there is only one preview deck so just map it here. QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); @@ -270,8 +274,7 @@ void WTrackMenu::createActions() { &WTrackMenu::slotColorPicked); } -void WTrackMenu::teardownActions() -{ +void WTrackMenu::teardownActions() { clear(); m_pLoadToMenu->clear(); m_pDeckMenu->clear(); @@ -286,24 +289,21 @@ void WTrackMenu::teardownActions() } void WTrackMenu::setupActions() { + // TODO: Replace true with filters teardownActions(); - createActions(); - QModelIndexList indices = m_pSelectedTrackIndices; // Gray out some stuff if multiple songs were selected. - bool oneSongSelected = indices.size() == 1; - TrackModel* trackModel = m_pTrackModel; + bool oneSongSelected = m_pTrackIdList.size() == 1; - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { + if (true) { addAction(m_pAutoDJBottomAct); addAction(m_pAutoDJTopAct); addAction(m_pAutoDJReplaceAct); addSeparator(); } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { + if (true) { int iNumDecks = m_pNumDecks->get(); if (iNumDecks > 0) { for (int i = 1; i <= iNumDecks; ++i) { @@ -324,7 +324,7 @@ void WTrackMenu::setupActions() { m_pLoadToMenu->addMenu(m_pDeckMenu); } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOSAMPLER)) { + if (true) { int iNumSamplers = m_pNumSamplers->get(); if (iNumSamplers > 0) { for (int i = 1; i <= iNumSamplers; ++i) { @@ -344,22 +344,24 @@ void WTrackMenu::setupActions() { } } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOPREVIEWDECK) && + if (true && m_pNumPreviewDecks->get() > 0.0) { m_pLoadToMenu->addAction(m_pAddToPreviewDeck); } - addMenu(m_pLoadToMenu); - addSeparator(); + if (!m_pLoadToMenu->isEmpty()){ + addMenu(m_pLoadToMenu); + addSeparator(); + } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST)) { + if (true) { // Playlist menu is lazy loaded on hover by slotPopulatePlaylistMenu // to avoid unnecessary database queries m_bPlaylistMenuLoaded = false; addMenu(m_pPlaylistMenu); } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) { + if (true) { // Crate menu is lazy loaded on hover by slotPopulateCrateMenu // to avoid unnecessary database queries m_bCrateMenuLoaded = false; @@ -417,11 +419,12 @@ void WTrackMenu::setupActions() { m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); } - if (trackModel == nullptr) { + DEBUG_ASSERT(m_pTrackModel); + if (m_pTrackModel == nullptr) { return; } bool allowClear = true; - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); + int column = m_pTrackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); for (int i = 0; i < indices.size() && allowClear; ++i) { int row = indices.at(i).row(); QModelIndex index = indices.at(i).sibling(row,column); @@ -433,7 +436,7 @@ void WTrackMenu::setupActions() { m_pClearMetadataMenu->addAction(m_pClearBeatsAction); } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RESETPLAYED)) { + if (true) { m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); } @@ -483,10 +486,10 @@ void WTrackMenu::setupActions() { m_pBPMMenu->addAction(m_pBpmUnlockAction); m_pBPMMenu->addSeparator(); if (oneSongSelected) { - if (trackModel == nullptr) { + if (m_pTrackModel == nullptr) { return; } - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); + int column = m_pTrackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); QModelIndex index = indices.at(0).sibling(indices.at(0).row(),column); if (index.data().toBool()) { //BPM is locked m_pBpmUnlockAction->setEnabled(true); @@ -509,7 +512,7 @@ void WTrackMenu::setupActions() { } } else { bool anyLocked = false; //true if any of the selected items are locked - int column = trackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); + int column = m_pTrackModel->fieldIndex(LIBRARYTABLE_BPM_LOCK); for (int i = 0; i < indices.size() && !anyLocked; ++i) { int row = indices.at(i).row(); QModelIndex index = indices.at(i).sibling(row,column); @@ -545,7 +548,7 @@ void WTrackMenu::setupActions() { ColorPaletteSettings(m_pConfig).getTrackColorPalette()); // Get color of first selected track - int column = trackModel->fieldIndex(LIBRARYTABLE_COLOR); + int column = m_pTrackModel->fieldIndex(LIBRARYTABLE_COLOR); QModelIndex index = indices.at(0).sibling(indices.at(0).row(), column); auto trackColor = mixxx::RgbColor::fromQVariant(index.data()); @@ -586,9 +589,11 @@ void WTrackMenu::setupActions() { addAction(m_pPurgeAct); } - addAction(m_pFileBrowserAct); + if (true) { + addAction(m_pFileBrowserAct); + } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + if (true) { addSeparator(); m_pPropertiesAct->setEnabled(oneSongSelected); addAction(m_pPropertiesAct); @@ -619,6 +624,10 @@ void WTrackMenu::setTrackId(TrackId trackId) { } void WTrackMenu::setTrackIndexList(QModelIndexList indexList) { + DEBUG_ASSERT(m_pTrackModel); + if (!m_pTrackModel) { + return; + } clearTrackSelection(); m_pSelectedTrackIndices = std::move(indexList); @@ -647,6 +656,7 @@ TrackPointerList WTrackMenu::getTrackPointerList() { } bool WTrackMenu::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const { + DEBUG_ASSERT(m_pTrackModel); if (!m_pTrackModel) { return false; } @@ -746,19 +756,15 @@ void WTrackMenu::slotUpdateExternalTrackCollection( //slot for reset played count, sets count to 0 of one or more tracks void WTrackMenu::slotClearPlayCount() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel;; - - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->resetPlayCounter(); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->resetPlayCounter(); } + + } void WTrackMenu::slotPopulatePlaylistMenu() { @@ -962,7 +968,6 @@ void WTrackMenu::addSelectionToNewCrate() { m_pTrackCollectionManager->unhideTracks(trackIds); m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); } - } void WTrackMenu::setTrackModel(TrackModel* trackModel) { @@ -987,14 +992,11 @@ void WTrackMenu::slotUnlockBpm() { } void WTrackMenu::slotScaleBpm(int scale) { - TrackModel* trackModel = m_pTrackModel; - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); + for (const auto& pTrack : m_pTrackPointerList) { if (pTrack && !pTrack->isBpmLocked()) { BeatsPointer pBeats = pTrack->getBeats(); if (pBeats) { @@ -1006,32 +1008,26 @@ void WTrackMenu::slotScaleBpm(int scale) { void WTrackMenu::lockBpm(bool lock) { - TrackModel* trackModel = m_pTrackModel;; - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { + for (const auto& pTrack : m_pTrackPointerList) { + if (!pTrack->isBpmLocked()) { pTrack->setBpmLocked(lock); } } } void WTrackMenu::slotColorPicked(mixxx::RgbColor::optional_t color) { - TrackModel* trackModel = m_pTrackModel; - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { + for (const auto& pTrack : m_pTrackPointerList) { + if (!pTrack->isBpmLocked()) { pTrack->setColor(color); } } @@ -1040,139 +1036,98 @@ void WTrackMenu::slotColorPicked(mixxx::RgbColor::optional_t color) { } void WTrackMenu::loadSelectionToGroup(QString group, bool play) { - QModelIndexList indices = m_pSelectedTrackIndices; - if (indices.size() > 0) { - // If the track load override is disabled, check to see if a track is - // playing before trying to load it - if (!(m_pConfig->getValueString( - ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { - // TODO(XXX): Check for other than just the first preview deck. - if (group != "[PreviewDeck1]" && - ControlObject::get(ConfigKey(group, "play")) > 0.0) { - return; - } - } - QModelIndex index = indices.at(0); - TrackModel* trackModel = m_pTrackModel; - TrackPointer pTrack; - if (trackModel && - (pTrack = trackModel->getTrack(index))) { - emit loadTrackToPlayer(pTrack, group, play); + if (m_pTrackPointerList.empty()) { + return; + } + // If the track load override is disabled, check to see if a track is + // playing before trying to load it + if (!(m_pConfig->getValueString( + ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt())) { + // TODO(XXX): Check for other than just the first preview deck. + if (group != "[PreviewDeck1]" && + ControlObject::get(ConfigKey(group, "play")) > 0.0) { + return; } } + + TrackPointer pTrack = m_pTrackPointerList.at(0); + if (pTrack) { + emit loadTrackToPlayer(pTrack, group, play); + } } void WTrackMenu::slotClearBeats() { - TrackModel* trackModel = m_pTrackModel; - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - const QModelIndexList selectedTrackIndices = m_pSelectedTrackIndices; // TODO: This should be done in a thread for large selections - for (const auto& index : selectedTrackIndices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack && !pTrack->isBpmLocked()) { + for (const auto& pTrack : m_pTrackPointerList) { + if (!pTrack->isBpmLocked()) { pTrack->setBeats(BeatsPointer()); } } } void WTrackMenu::slotClearMainCue() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel;; - - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::MainCue); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->removeCuesOfType(mixxx::CueType::MainCue); } } void WTrackMenu::slotClearOutroCue() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel;; - - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::HotCue); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->removeCuesOfType(mixxx::CueType::Outro); } } -void WTrackMenu::slotClearIntroCue() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel;; - - if (trackModel == nullptr) { +void WTrackMenu::slotClearIntroCue() { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Intro); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->removeCuesOfType(mixxx::CueType::Intro); } } void WTrackMenu::slotClearKey() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel;; - - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->resetKeys(); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->resetKeys(); } } void WTrackMenu::slotClearReplayGain() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel;; - - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setReplayGain(mixxx::ReplayGain()); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->setReplayGain(mixxx::ReplayGain()); } } void WTrackMenu::slotClearWaveform() { - TrackModel* trackModel = m_pTrackModel;; - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } AnalysisDao& analysisDao = m_pTrackCollectionManager->internalCollection()->getAnalysisDAO(); - const QModelIndexList indices = m_pSelectedTrackIndices; - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (!pTrack) { - continue; - } + for (const auto& pTrack : m_pTrackPointerList) { analysisDao.deleteAnalysesForTrack(pTrack->getId()); pTrack->setWaveform(WaveformPointer()); pTrack->setWaveformSummary(WaveformPointer()); @@ -1180,34 +1135,22 @@ void WTrackMenu::slotClearWaveform() { } void WTrackMenu::slotClearLoop() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel; - - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::Loop); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->removeCuesOfType(mixxx::CueType::Loop); } } void WTrackMenu::slotClearHotCues() { - const QModelIndexList indices = m_pSelectedTrackIndices; - TrackModel* trackModel = m_pTrackModel; - - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - for (const QModelIndex& index : indices) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->removeCuesOfType(mixxx::CueType::HotCue); - } + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->removeCuesOfType(mixxx::CueType::HotCue); } } @@ -1228,24 +1171,28 @@ void WTrackMenu::slotClearAllMetadata() { void WTrackMenu::slotRemove() { + DEBUG_ASSERT(m_pTrackModel); + TrackModel* trackModel = m_pTrackModel; + if (!trackModel) { + return; + } QModelIndexList indices = m_pSelectedTrackIndices; if (!indices.empty()) { - TrackModel* trackModel = m_pTrackModel; - if (trackModel) { - trackModel->removeTracks(indices); - } + trackModel->removeTracks(indices); } } void WTrackMenu::slotHide() { + DEBUG_ASSERT(m_pTrackModel); + TrackModel* trackModel = m_pTrackModel; + if (!trackModel) { + return; + } QModelIndexList indices = m_pSelectedTrackIndices; if (indices.size() > 0) { - TrackModel* trackModel = m_pTrackModel; - if (trackModel) { - trackModel->hideTracks(indices); - } + trackModel->hideTracks(indices); } } @@ -1271,6 +1218,10 @@ void WTrackMenu::slotTagFetcherClosed() { } void WTrackMenu::slotShowTrackInfo() { + DEBUG_ASSERT(m_pTrackModel); + if (!m_pTrackModel) { + return; + } QModelIndexList indices = m_pSelectedTrackIndices; if (indices.size() > 0) { @@ -1279,6 +1230,10 @@ void WTrackMenu::slotShowTrackInfo() { } void WTrackMenu::slotNextTrackInfo() { + DEBUG_ASSERT(m_pTrackModel); + if (!m_pTrackModel) { + return; + } QModelIndex nextRow = currentTrackInfoIndex.sibling( currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); if (nextRow.isValid()) { @@ -1290,6 +1245,10 @@ void WTrackMenu::slotNextTrackInfo() { } void WTrackMenu::slotPrevTrackInfo() { + DEBUG_ASSERT(m_pTrackModel); + if (!m_pTrackModel) { + return; + } QModelIndex prevRow = currentTrackInfoIndex.sibling( currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); if (prevRow.isValid()) { @@ -1301,6 +1260,7 @@ void WTrackMenu::slotPrevTrackInfo() { } void WTrackMenu::showTrackInfo(QModelIndex index) { + DEBUG_ASSERT(m_pTrackModel); TrackModel* trackModel = m_pTrackModel; if (!trackModel) { @@ -1328,6 +1288,10 @@ void WTrackMenu::showTrackInfo(QModelIndex index) { } void WTrackMenu::slotNextDlgTagFetcher() { + DEBUG_ASSERT(m_pTrackModel); + if (!m_pTrackModel) { + return; + } QModelIndex nextRow = currentTrackInfoIndex.sibling( currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); if (nextRow.isValid()) { @@ -1339,6 +1303,10 @@ void WTrackMenu::slotNextDlgTagFetcher() { } void WTrackMenu::slotPrevDlgTagFetcher() { + DEBUG_ASSERT(m_pTrackModel); + if (!m_pTrackModel) { + return; + } QModelIndex prevRow = currentTrackInfoIndex.sibling( currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); if (prevRow.isValid()) { @@ -1350,6 +1318,7 @@ void WTrackMenu::slotPrevDlgTagFetcher() { } void WTrackMenu::showDlgTagFetcher(QModelIndex index) { + DEBUG_ASSERT(m_pTrackModel); TrackModel* trackModel = m_pTrackModel; if (!trackModel) { @@ -1378,6 +1347,9 @@ void WTrackMenu::slotShowTrackInTagFetcher(TrackPointer pTrack) { } void WTrackMenu::slotShowDlgTagFetcher() { + if (m_pSelectedTrackIndices.empty()) { + return; + } QModelIndexList indices = m_pSelectedTrackIndices; if (indices.size() > 0) { @@ -1400,10 +1372,9 @@ void WTrackMenu::slotAddToAutoDJReplace() { } void WTrackMenu::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { - if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { + if (m_pTrackIdList.empty()) { return; } - const TrackIdList trackIds = m_pTrackIdList; if (trackIds.isEmpty()) { qWarning() << "No tracks selected for AutoDJ"; @@ -1419,56 +1390,43 @@ void WTrackMenu::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { void WTrackMenu::slotCoverInfoSelected(const CoverInfoRelative& coverInfo) { - TrackModel* trackModel = m_pTrackModel; - if (trackModel == nullptr) { + if (m_pTrackPointerList.empty()) { return; } - const QModelIndexList selection = m_pSelectedTrackIndices; - for (const QModelIndex& index : selection) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - pTrack->setCoverInfo(coverInfo); - } + for (const auto pTrack : m_pTrackPointerList) { + pTrack->setCoverInfo(coverInfo); } } void WTrackMenu::slotReloadCoverArt() { - TrackModel* trackModel = m_pTrackModel; - if (!trackModel) { + if (m_pTrackPointerList.empty()) { return; } - const QModelIndexList selection = m_pSelectedTrackIndices; - if (selection.isEmpty()) { - return; - } - QList selectedTracks; - selectedTracks.reserve(selection.size()); - for (const QModelIndex& index : selection) { - TrackPointer pTrack = trackModel->getTrack(index); - if (pTrack) { - selectedTracks.append(pTrack); - } - } - guessTrackCoverInfoConcurrently(selectedTracks); + guessTrackCoverInfoConcurrently(m_pTrackPointerList); } void WTrackMenu::slotPurge() { - QModelIndexList indices = m_pSelectedTrackIndices; - if (indices.size() > 0) { - TrackModel* trackModel = m_pTrackModel; - if (trackModel) { - trackModel->purgeTracks(indices); + DEBUG_ASSERT(m_pTrackModel); + if (m_pTrackModel) { + QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.size() > 0) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel) { + trackModel->purgeTracks(indices); + } } } } void WTrackMenu::slotUnhide() { - QModelIndexList indices = m_pSelectedTrackIndices; + if (m_pTrackModel) { + QModelIndexList indices = m_pSelectedTrackIndices; - if (indices.size() > 0) { - TrackModel* trackModel = m_pTrackModel; - if (trackModel) { - trackModel->unhideTracks(indices); + if (indices.size() > 0) { + TrackModel* trackModel = m_pTrackModel; + if (trackModel) { + trackModel->unhideTracks(indices); + } } } } diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index 21d81093a674..216bdd2dc68c 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -38,6 +38,14 @@ class WTrackMenu : public QMenu { void setTrackIndexList(QModelIndexList indexList); void setTrackModel(TrackModel* trackModel); + enum Filter { + None, + FileBrowser, + Playlist, + Crate + }; + Q_DECLARE_FLAGS(Filters, Filter) + private slots: void slotOpenInFileBrowser(); void slotLockBpm(); @@ -226,11 +234,10 @@ class WTrackMenu : public QMenu { int m_iCoverColumn; // visible cover art int m_iTrackLocationColumn; - void trackIndicesToTrackPointers(); + Filters m_eFilters; }; - - +Q_DECLARE_OPERATORS_FOR_FLAGS(WTrackMenu::Filters) #endif // WTRACKMENU_H diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 76859893ad03..0b26434bc2fc 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -20,6 +20,7 @@ #include "track/track.h" #include "util/dnd.h" #include "util/desktophelper.h" +#include WTrackProperty::WTrackProperty(const char* group, UserSettingsPointer pConfig, @@ -27,33 +28,15 @@ WTrackProperty::WTrackProperty(const char* group, TrackCollectionManager* pTrackCollectionManager) : WLabel(pParent), m_pGroup(group), - m_pConfig(std::move(pConfig)), - m_pTrackCollectionManager(pTrackCollectionManager), - m_bPlaylistMenuLoaded(false), - m_bCrateMenuLoaded(false) { + m_pConfig(std::move(pConfig)) { setAcceptDrops(true); // Setup context menu - m_pMenu = new QMenu(this); - m_pPlaylistMenu = new QMenu(this); - m_pPlaylistMenu->setTitle(tr("Add to Playlist")); - connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulatePlaylistMenu())); - m_pCrateMenu = new QMenu(this); - m_pCrateMenu->setTitle(tr("Crates")); - connect(m_pCrateMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulateCrateMenu())); - - // Create all the context m_pMenu->actions (stuff that shows up when you - // right-click) - createContextMenuActions(); + m_pMenu = new WTrackMenu(this, pConfig, pTrackCollectionManager); } WTrackProperty::~WTrackProperty() { delete m_pMenu; - delete m_pFileBrowserAct; - delete m_pPlaylistMenu; - delete m_pCrateMenu; } void WTrackProperty::setup(const QDomNode& node, const SkinContext& context) { @@ -113,201 +96,9 @@ void WTrackProperty::dropEvent(QDropEvent *event) { DragAndDropHelper::handleTrackDropEvent(event, *this, m_pGroup, m_pConfig); } -void WTrackProperty::slotOpenInFileBrowser() { - QString trackLocation = m_pCurrentTrack->getLocation(); - mixxx::DesktopHelper::openInFileBrowser(QStringList(trackLocation)); -} - -void WTrackProperty::slotPopulatePlaylistMenu() { - // The user may open the Playlist submenu, move their cursor away, then - // return to the Playlist submenu before exiting the track context menu. - // Avoid querying the database multiple times in that case. - if (m_bPlaylistMenuLoaded) { - return; - } - m_pPlaylistMenu->clear(); - PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); - QMap playlists; - int numPlaylists = playlistDao.playlistCount(); - for (int i = 0; i < numPlaylists; ++i) { - int iPlaylistId = playlistDao.getPlaylistId(i); - playlists.insert(playlistDao.getPlaylistName(iPlaylistId), iPlaylistId); - } - QMapIterator it(playlists); - while (it.hasNext()) { - it.next(); - if (!playlistDao.isHidden(it.value())) { - // No leak because making the menu the parent means they will be - // auto-deleted - auto pAction = new QAction(it.key(), m_pPlaylistMenu); - bool locked = playlistDao.isPlaylistLocked(it.value()); - pAction->setEnabled(!locked); - m_pPlaylistMenu->addAction(pAction); - int iPlaylistId = it.value(); - connect(pAction, &QAction::triggered, - this, [this, iPlaylistId] { slotAddToPlaylist(iPlaylistId); }); - } - } - m_pPlaylistMenu->addSeparator(); - auto* newPlaylistAction = new QAction(tr("Create New Playlist"), m_pPlaylistMenu); - m_pPlaylistMenu->addAction(newPlaylistAction); - connect(newPlaylistAction, &QAction::triggered, - this, [this] { slotAddToPlaylist(-1); }); - m_bPlaylistMenuLoaded = true; -} - -void WTrackProperty::slotAddToPlaylist(int iPlaylistId) { - const TrackId trackId = m_pCurrentTrack->getId(); - - PlaylistDAO& playlistDao = m_pTrackCollectionManager->internalCollection()->getPlaylistDAO(); - - if (iPlaylistId == -1) { // i.e. a new playlist is suppose to be created - QString name; - bool validNameGiven = false; - - do { - bool ok = false; - name = QInputDialog::getText(nullptr, - tr("Create New Playlist"), - tr("Enter name for new playlist:"), - QLineEdit::Normal, - tr("New Playlist"), - &ok).trimmed(); - if (!ok) { - return; - } - if (playlistDao.getPlaylistIdFromName(name) != -1) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("A playlist by that name already exists.")); - } else if (name.isEmpty()) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("A playlist cannot have a blank name.")); - } else { - validNameGiven = true; - } - } while (!validNameGiven); - iPlaylistId = playlistDao.createPlaylist(name);//-1 is changed to the new playlist ID return from the DAO - if (iPlaylistId == -1) { - QMessageBox::warning(nullptr, - tr("Playlist Creation Failed"), - tr("An unknown error occurred while creating playlist: ") - +name); - return; - } - } - - playlistDao.appendTrackToPlaylist(trackId, iPlaylistId); -} - -void WTrackProperty::slotPopulateCrateMenu() { - // The user may open the Crate submenu, move their cursor away, then - // return to the Crate submenu before exiting the track context menu. - // Avoid querying the database multiple times in that case. - if (m_bCrateMenuLoaded) { - return; - } - m_pCrateMenu->clear(); - const auto trackId = m_pCurrentTrack->getId(); - QList trackIds; - trackIds.push_back(trackId); - - CrateSummarySelectResult allCrates(m_pTrackCollectionManager->internalCollection()->crates().selectCratesWithTrackCount(trackIds)); - - CrateSummary crate; - while (allCrates.populateNext(&crate)) { - auto pAction = make_parented(m_pCrateMenu); - auto pCheckBox = make_parented(m_pCrateMenu); - - pCheckBox->setText(crate.getName()); - pCheckBox->setProperty("crateId", - QVariant::fromValue(crate.getId())); - pCheckBox->setEnabled(!crate.isLocked()); - - pAction->setEnabled(!crate.isLocked()); - pAction->setDefaultWidget(pCheckBox.get()); - - pCheckBox->setChecked(crate.getTrackCount() != 0); - - m_pCrateMenu->addAction(pAction.get()); - connect(pAction.get(), &QAction::triggered, - this, [this, pCheckBox{pCheckBox.get()}] { slotUpdateSelectionCrates(pCheckBox); }); - connect(pCheckBox.get(), &QCheckBox::stateChanged, - this, [this, pCheckBox{pCheckBox.get()}] { slotUpdateSelectionCrates(pCheckBox); }); - } - m_pCrateMenu->addSeparator(); - auto* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); - m_pCrateMenu->addAction(newCrateAction); - connect(newCrateAction, SIGNAL(triggered()), this, SLOT(slotAddSelectionToNewCrate())); - m_bCrateMenuLoaded = true; -} - -void WTrackProperty::slotUpdateSelectionCrates(QWidget *pWidget) { - auto pCheckBox = qobject_cast(pWidget); - VERIFY_OR_DEBUG_ASSERT(pCheckBox) { - qWarning() << "crateId is not of CrateId type"; - return; - } - auto crateId = pCheckBox->property("crateId").value(); - - const auto trackId = m_pCurrentTrack->getId(); - QList trackIds; - trackIds.push_back(trackId); - - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for crate"; - return; - } - - if(!pCheckBox->isChecked()) { - if (crateId.isValid()) { - m_pTrackCollectionManager->internalCollection()->removeCrateTracks(crateId, trackIds); - } - } else { - if (!crateId.isValid()) { // i.e. a new crate is suppose to be created - crateId = CrateFeatureHelper( - m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); - } - if (crateId.isValid()) { - m_pTrackCollectionManager->unhideTracks(trackIds); - m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); - } - } -} - -void WTrackProperty::slotAddSelectionToNewCrate() { - auto trackId = m_pCurrentTrack->getId(); - QList trackIds; - trackIds.push_back(trackId); - - if (trackIds.isEmpty()) { - qWarning() << "No tracks selected for crate"; - return; - } - - CrateId crateId = CrateFeatureHelper( - m_pTrackCollectionManager->internalCollection(), m_pConfig).createEmptyCrate(); - - if (crateId.isValid()) { - m_pTrackCollectionManager->unhideTracks(trackIds); - m_pTrackCollectionManager->internalCollection()->addCrateTracks(crateId, trackIds); - } -} - -void WTrackProperty::createContextMenuActions() { - m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); - connect(m_pFileBrowserAct, SIGNAL(triggered()), - this, SLOT(slotOpenInFileBrowser())); -} - void WTrackProperty::contextMenuEvent(QContextMenuEvent *event) { if (m_pCurrentTrack) { - m_pMenu->addMenu(m_pPlaylistMenu); - m_pMenu->addMenu(m_pCrateMenu); - m_pMenu->addSeparator(); - m_pMenu->addAction(m_pFileBrowserAct); - + m_pMenu->setTrackId(m_pCurrentTrack->getId()); // Create the right-click menu m_pMenu->popup(event->globalPos()); } diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index 01ed163735cb..7d208ae81c4f 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -10,8 +10,7 @@ #include "track/track.h" #include "widget/trackdroptarget.h" #include "widget/wlabel.h" - -class TrackCollectionManager; +#include "widget/wtrackmenu.h" class WTrackProperty : public WLabel, public TrackDropTarget { Q_OBJECT @@ -36,12 +35,6 @@ class WTrackProperty : public WLabel, public TrackDropTarget { private slots: void slotTrackChanged(TrackId); - void slotOpenInFileBrowser(); - void slotPopulatePlaylistMenu(); - void slotAddToPlaylist(int iPlaylistId); - void slotPopulateCrateMenu(); - void slotUpdateSelectionCrates(QWidget* pWidget); - void slotAddSelectionToNewCrate(); private: void dragEnterEvent(QDragEnterEvent *event) override; @@ -49,24 +42,13 @@ class WTrackProperty : public WLabel, public TrackDropTarget { void mouseMoveEvent(QMouseEvent *event) override; void updateLabel(); - void createContextMenuActions(); const char* m_pGroup; UserSettingsPointer m_pConfig; TrackPointer m_pCurrentTrack; QString m_property; - // Context menu machinery - QMenu *m_pMenu; - QMenu *m_pPlaylistMenu; - QMenu *m_pCrateMenu; - - QAction *m_pFileBrowserAct; - - TrackCollectionManager* const m_pTrackCollectionManager; - - bool m_bPlaylistMenuLoaded; - bool m_bCrateMenuLoaded; + WTrackMenu *m_pMenu; }; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 36edd439df56..93f14d6ea7ab 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -13,30 +13,19 @@ #include "control/controlproxy.h" #include "library/crate/cratefeaturehelper.h" #include "library/dao/trackschema.h" -#include "library/dlgtagfetcher.h" -#include "library/dlgtrackinfo.h" -#include "library/dlgtrackmetadataexport.h" #include "library/externaltrackcollection.h" #include "library/librarytablemodel.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" #include "mixer/playermanager.h" -#include "preferences/colorpalettesettings.h" #include "preferences/dialog/dlgpreflibrary.h" #include "sources/soundsourceproxy.h" #include "track/track.h" #include "track/trackref.h" #include "util/assert.h" -#include "util/desktophelper.h" #include "util/dnd.h" -#include "util/parented_ptr.h" #include "util/time.h" -#include "waveform/guitick.h" -#include "widget/wcolorpickeraction.h" -#include "widget/wcoverartmenu.h" -#include "widget/wskincolor.h" #include "widget/wtracktableviewheader.h" -#include "widget/wwidget.h" #include From 8206d078aa3b41671c50e2ab428cdd5c2d924aaf Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 4 Apr 2020 16:33:57 +0200 Subject: [PATCH 175/393] make Anylysis button checkable, unchecked by default --- src/library/dlganalysis.cpp | 4 +++- src/library/dlganalysis.ui | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index 1a9796d4e1a7..8ffe6f893863 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -63,6 +63,7 @@ DlgAnalysis::DlgAnalysis(QWidget* parent, &QPushButton::clicked, this, &DlgAnalysis::analyze); + pushButtonAnalyze->setEnabled(false); connect(pushButtonSelectAll, &QPushButton::clicked, @@ -165,10 +166,11 @@ void DlgAnalysis::slotAnalysisActive(bool bActive) { //qDebug() << this << "slotAnalysisActive" << bActive; m_bAnalysisActive = bActive; if (bActive) { - pushButtonAnalyze->setEnabled(true); + pushButtonAnalyze->setChecked(true); pushButtonAnalyze->setText(tr("Stop Analysis")); labelProgress->setEnabled(true); } else { + pushButtonAnalyze->setChecked(false); pushButtonAnalyze->setText(tr("Analyze")); labelProgress->setText(""); labelProgress->setEnabled(false); diff --git a/src/library/dlganalysis.ui b/src/library/dlganalysis.ui index e07bc1c92a56..395800c14f2d 100644 --- a/src/library/dlganalysis.ui +++ b/src/library/dlganalysis.ui @@ -84,6 +84,9 @@ Analyze + + true +
From 7b8d74b382795c6f28f7236b256994f186c904e3 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 4 Apr 2020 18:58:52 +0200 Subject: [PATCH 176/393] DlgAutoDJ: use clicked() signal for all QPushButtons This prevents inifit loop issues when real click events and programmatic QPushButton::setChecked() calls conflict: * clicked(bool checked) is only emitted on mouse clicks * toggled(bool checked) is emitted both on mouse clicks and setChecked() calls --- src/library/autodj/dlgautodj.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 97305e08acb4..e5d285788177 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -80,7 +80,10 @@ DlgAutoDJ::DlgAutoDJ( // Do not set this because it disables auto-scrolling //m_pTrackTableView->setDragDropMode(QAbstractItemView::InternalMove); - connect(pushButtonAutoDJ, &QPushButton::toggled, this, &DlgAutoDJ::toggleAutoDJButton); + connect(pushButtonAutoDJ, + &QPushButton::clicked, + this, + &DlgAutoDJ::toggleAutoDJButton); setupActionButton(pushButtonFadeNow, &DlgAutoDJ::fadeNowButton, tr("Fade")); setupActionButton(pushButtonSkipNext, &DlgAutoDJ::skipNextButton, tr("Skip")); @@ -171,7 +174,7 @@ DlgAutoDJ::DlgAutoDJ( &DlgAutoDJ::slotTransitionModeChanged); connect(pushButtonRepeatPlaylist, - &QPushButton::toggled, + &QPushButton::clicked, this, &DlgAutoDJ::slotRepeatPlaylistChanged); if (m_bShowButtonText) { @@ -190,6 +193,7 @@ DlgAutoDJ::DlgAutoDJ( &AutoDJProcessor::transitionTimeChanged, this, &DlgAutoDJ::transitionTimeChanged); + connect(m_pAutoDJProcessor, &AutoDJProcessor::autoDJStateChanged, this, From 6035a245e325027032a38e43959fd40fc768ec3a Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 4 Apr 2020 19:21:34 +0200 Subject: [PATCH 177/393] DlgRecording: use QPushButton::clicked() signal instead of toggled() --- src/library/recording/dlgrecording.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/recording/dlgrecording.cpp b/src/library/recording/dlgrecording.cpp index 1b833d3c752e..4dd3eb328767 100644 --- a/src/library/recording/dlgrecording.cpp +++ b/src/library/recording/dlgrecording.cpp @@ -74,7 +74,7 @@ DlgRecording::DlgRecording(QWidget* parent, UserSettingsPointer pConfig, m_pTrackTableView->loadTrackModel(&m_proxyModel); connect(pushButtonRecording, - &QPushButton::toggled, + &QPushButton::clicked, this, &DlgRecording::toggleRecording); label->setText(""); From db17fd1f2142ae73f8161140ece1d8a803aa6e69 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 4 Apr 2020 19:56:46 +0200 Subject: [PATCH 178/393] DlgRecording: separate Rec toggle & button update Redirect toggle request to RecordingManager::slotToggleRecording() and let slotRecordingEnabled() handle the button update. --- src/library/recording/dlgrecording.cpp | 16 +++++----------- src/library/recording/dlgrecording.h | 2 +- src/recording/recordingmanager.cpp | 4 ++-- src/recording/recordingmanager.h | 4 +--- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/library/recording/dlgrecording.cpp b/src/library/recording/dlgrecording.cpp index 4dd3eb328767..5f5ad27c5a03 100644 --- a/src/library/recording/dlgrecording.cpp +++ b/src/library/recording/dlgrecording.cpp @@ -77,6 +77,7 @@ DlgRecording::DlgRecording(QWidget* parent, UserSettingsPointer pConfig, &QPushButton::clicked, this, &DlgRecording::toggleRecording); + label->setText(""); label->setEnabled(false); } @@ -131,23 +132,16 @@ void DlgRecording::moveSelection(int delta) { void DlgRecording::toggleRecording(bool toggle) { Q_UNUSED(toggle); - if (!m_pRecordingManager->isRecordingActive()) //If recording is enabled - { - //pushButtonRecording->setText(tr("Stop Recording")); - m_pRecordingManager->startRecording(); - } - else if(m_pRecordingManager->isRecordingActive()) //If we disable recording - { - //pushButtonRecording->setText(tr("Start Recording")); - m_pRecordingManager->stopRecording(); - } + m_pRecordingManager->slotToggleRecording(true); } void DlgRecording::slotRecordingEnabled(bool isRecording) { if (isRecording) { + pushButtonRecording->setChecked(true); pushButtonRecording->setText((tr("Stop Recording"))); label->setEnabled(true); } else { + pushButtonRecording->setChecked(false); pushButtonRecording->setText((tr("Start Recording"))); label->setText(""); label->setEnabled(false); @@ -176,4 +170,4 @@ void DlgRecording::refreshLabel() { .arg(m_bytesRecordedStr) .arg(m_durationRecordedStr); label->setText(text); - } +} diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h index fc9045e8e4a0..484ce033da58 100644 --- a/src/library/recording/dlgrecording.h +++ b/src/library/recording/dlgrecording.h @@ -36,7 +36,6 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib inline const QString currentSearch() { return m_proxyModel.currentSearch(); } public slots: - void toggleRecording(bool toggle); void slotRecordingEnabled(bool); void slotBytesRecorded(int); void refreshBrowseModel(); @@ -56,6 +55,7 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib QString m_recordingDir; void refreshLabel(); + void toggleRecording(bool checked); QString m_bytesRecordedStr; QString m_durationRecordedStr; diff --git a/src/recording/recordingmanager.cpp b/src/recording/recordingmanager.cpp index af57a4369601..cf06dd7cf75d 100644 --- a/src/recording/recordingmanager.cpp +++ b/src/recording/recordingmanager.cpp @@ -81,8 +81,8 @@ void RecordingManager::slotSetRecording(bool recording) { } } -void RecordingManager::slotToggleRecording(double v) { - if (v > 0) { +void RecordingManager::slotToggleRecording(bool toggle) { + if (toggle) { if (isRecordingActive()) { stopRecording(); } else { diff --git a/src/recording/recordingmanager.h b/src/recording/recordingmanager.h index 3f9ac6b34bcd..38afe1e8233e 100644 --- a/src/recording/recordingmanager.h +++ b/src/recording/recordingmanager.h @@ -56,9 +56,7 @@ class RecordingManager : public QObject void slotBytesRecorded(int); void slotDurationRecorded(quint64); void slotSetRecording(bool recording); - - private slots: - void slotToggleRecording(double v); + void slotToggleRecording(bool toggle); private: QString formatDateTimeForFilename(QDateTime dateTime) const; From 2e66652ecd9d4b40c3a40c933d0af4247b888bf8 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 4 Apr 2020 20:09:57 +0200 Subject: [PATCH 179/393] DlgRecording: use unambiguous slot names --- src/library/recording/dlgrecording.cpp | 8 ++++---- src/library/recording/dlgrecording.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/library/recording/dlgrecording.cpp b/src/library/recording/dlgrecording.cpp index 5f5ad27c5a03..f32112f018cc 100644 --- a/src/library/recording/dlgrecording.cpp +++ b/src/library/recording/dlgrecording.cpp @@ -47,7 +47,7 @@ DlgRecording::DlgRecording(QWidget* parent, UserSettingsPointer pConfig, connect(m_pRecordingManager, &RecordingManager::isRecording, this, - &DlgRecording::slotRecordingEnabled); + &DlgRecording::slotRecordingStateChanged); connect(m_pRecordingManager, &RecordingManager::bytesRecorded, this, @@ -76,7 +76,7 @@ DlgRecording::DlgRecording(QWidget* parent, UserSettingsPointer pConfig, connect(pushButtonRecording, &QPushButton::clicked, this, - &DlgRecording::toggleRecording); + &DlgRecording::slotRecButtonClicked); label->setText(""); label->setEnabled(false); @@ -130,12 +130,12 @@ void DlgRecording::moveSelection(int delta) { m_pTrackTableView->moveSelection(delta); } -void DlgRecording::toggleRecording(bool toggle) { +void DlgRecording::slotRecButtonClicked(bool toggle) { Q_UNUSED(toggle); m_pRecordingManager->slotToggleRecording(true); } -void DlgRecording::slotRecordingEnabled(bool isRecording) { +void DlgRecording::slotRecordingStateChanged(bool isRecording) { if (isRecording) { pushButtonRecording->setChecked(true); pushButtonRecording->setText((tr("Stop Recording"))); diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h index 484ce033da58..1c8b22421bd0 100644 --- a/src/library/recording/dlgrecording.h +++ b/src/library/recording/dlgrecording.h @@ -36,7 +36,7 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib inline const QString currentSearch() { return m_proxyModel.currentSearch(); } public slots: - void slotRecordingEnabled(bool); + void slotRecordingStateChanged(bool); void slotBytesRecorded(int); void refreshBrowseModel(); void slotRestoreSearch(); @@ -55,7 +55,7 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib QString m_recordingDir; void refreshLabel(); - void toggleRecording(bool checked); + void slotRecButtonClicked(bool checked); QString m_bytesRecordedStr; QString m_durationRecordedStr; From 6be0ef823a380738ab8bdaa9ad9d8687dac3f8c0 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sat, 4 Apr 2020 20:10:43 +0200 Subject: [PATCH 180/393] recordingmanager.cpp: apply new signal/sot syntax --- src/recording/recordingmanager.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/recording/recordingmanager.cpp b/src/recording/recordingmanager.cpp index cf06dd7cf75d..010acb1a7ed5 100644 --- a/src/recording/recordingmanager.cpp +++ b/src/recording/recordingmanager.cpp @@ -36,8 +36,10 @@ RecordingManager::RecordingManager(UserSettingsPointer pConfig, EngineMaster* pE m_secondsRecorded(0), m_secondsRecordedSplit(0) { m_pToggleRecording = new ControlPushButton(ConfigKey(RECORDING_PREF_KEY, "toggle_recording")); - connect(m_pToggleRecording, SIGNAL(valueChanged(double)), - this, SLOT(slotToggleRecording(double))); + connect(m_pToggleRecording, + &ControlPushButton::valueChanged, + this, + &RecordingManager::slotToggleRecording); m_recReadyCO = new ControlObject(ConfigKey(RECORDING_PREF_KEY, "status")); m_recReady = new ControlProxy(m_recReadyCO->getKey(), this); @@ -49,12 +51,18 @@ RecordingManager::RecordingManager(UserSettingsPointer pConfig, EngineMaster* pE EngineSideChain* pSidechain = pEngine->getSideChain(); if (pSidechain) { EngineRecord* pEngineRecord = new EngineRecord(m_pConfig); - connect(pEngineRecord, SIGNAL(isRecording(bool, bool)), - this, SLOT(slotIsRecording(bool, bool))); - connect(pEngineRecord, SIGNAL(bytesRecorded(int)), - this, SLOT(slotBytesRecorded(int))); - connect(pEngineRecord, SIGNAL(durationRecorded(quint64)), - this, SLOT(slotDurationRecorded(quint64))); + connect(pEngineRecord, + &EngineRecord::isRecording, + this, + &RecordingManager::slotIsRecording); + connect(pEngineRecord, + &EngineRecord::bytesRecorded, + this, + &RecordingManager::slotBytesRecorded); + connect(pEngineRecord, + &EngineRecord::durationRecorded, + this, + &RecordingManager::slotDurationRecorded); pSidechain->addSideChainWorker(pEngineRecord); } } From 02e206c7dd5e5c089275861c1843a9cd3edb7871 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 4 Apr 2020 18:45:31 +0200 Subject: [PATCH 181/393] Use "left-side const" according to coding style --- src/util/macros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/macros.h b/src/util/macros.h index 080c1391844e..5af4e0267bee 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -14,7 +14,7 @@ // classes. #define PROPERTY_SET_BYVAL_GET_BYREF(TYPE, NAME, CAP_NAME) \ public: void set##CAP_NAME(TYPE NAME) { m_##NAME = std::move(NAME); } \ -public: constexpr TYPE const& get##CAP_NAME() const { return m_##NAME; } \ +public: constexpr const TYPE& get##CAP_NAME() const { return m_##NAME; } \ public: constexpr TYPE& ref##CAP_NAME() { return m_##NAME; } \ public: QDebug dbg##CAP_NAME(QDebug dbg) const { return dbg << #NAME ":" << m_##NAME; } \ private: TYPE m_##NAME; From fed9a73068b1ea8c0272663642498c86503a41f9 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 4 Apr 2020 18:44:31 +0200 Subject: [PATCH 182/393] Set properties efficiently by passing a universal reference --- src/util/macros.h | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/util/macros.h b/src/util/macros.h index 5af4e0267bee..54fe79cca25c 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -1,20 +1,24 @@ #pragma once -#include +#include +#include -// Helper for defining simple properties with setters and getters that are -// passed by value using move assignment in the setter. The getter returns -// a const reference. The type must have a default constructor for proper -// initialization during construction and a move assignment operator for -// efficient passing and setting by value. +// Helper macro for defining simple properties with setters and +// getters. +// +// Fundamental types and bool are passed and returned by value. +// Other types are passed by universal reference and returned +// by const reference. +// +// The refName() function returns a mutable reference. It is needed +// for direct and efficient access to deeply nested properties. // -// The refName() function returns a mutable reference. It is only needed -// for direct and efficient access to properties when nesting property -// classes. +// TODO: Adjust the name of this macro, e.g. DECL_MIXXX_PROPERTY #define PROPERTY_SET_BYVAL_GET_BYREF(TYPE, NAME, CAP_NAME) \ -public: void set##CAP_NAME(TYPE NAME) { m_##NAME = std::move(NAME); } \ -public: constexpr const TYPE& get##CAP_NAME() const { return m_##NAME; } \ +public: template typename std::enable_if<(std::is_fundamental::value || std::is_same::value) && std::is_same::value>::type set##CAP_NAME(T _val) { m_##NAME = _val; } \ +public: template typename std::enable_if::value || std::is_same::value) && std::is_assignable::value>::type set##CAP_NAME(T&& _val) { m_##NAME = std::forward(_val); } \ +public: constexpr std::conditional::value, TYPE, const TYPE&>::type get##CAP_NAME() const { return m_##NAME; } \ public: constexpr TYPE& ref##CAP_NAME() { return m_##NAME; } \ public: QDebug dbg##CAP_NAME(QDebug dbg) const { return dbg << #NAME ":" << m_##NAME; } \ private: TYPE m_##NAME; From 8942e91b59c10e6fdaa9d6a867b836b0a9e6e88d Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sat, 4 Apr 2020 23:56:13 +0530 Subject: [PATCH 183/393] widget/wtrackmenu: filter options --- src/widget/wtrackmenu.cpp | 601 ++++++++++++++++++---------------- src/widget/wtrackmenu.h | 30 +- src/widget/wtrackproperty.cpp | 3 +- 3 files changed, 341 insertions(+), 293 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 8a4cbccc4af9..0cf390311a90 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -43,7 +43,7 @@ -WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager *pTrackCollectionManager) +WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager *pTrackCollectionManager, Filters flags) : QMenu(parent), m_pConfig(std::move(pConfig)), m_pTrackCollectionManager(pTrackCollectionManager), @@ -55,7 +55,7 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollec m_iCoverLocationColumn(-1), m_iCoverHashColumn(-1), m_iCoverColumn(-1), - m_eFilters(Filter::None) { + m_eFilters(flags) { m_pNumSamplers = new ControlProxy( "[Master]", "num_samplers", this); m_pNumDecks = new ControlProxy( @@ -64,214 +64,240 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollec "[Master]", "num_preview_decks", this); constructMenus(); - createActions(); -} - -WTrackMenu::~WTrackMenu() { - // Explicit destruction of heap allocated objects would cause segfault. + // FIXME: createActions() would cause segfault in constructor. +// createActions(); } void WTrackMenu::constructMenus() { - m_pLoadToMenu = new QMenu(this); - m_pLoadToMenu->setTitle(tr("Load to")); - m_pDeckMenu = new QMenu(this); - m_pDeckMenu->setTitle(tr("Deck")); - m_pSamplerMenu = new QMenu(this); - m_pSamplerMenu->setTitle(tr("Sampler")); - - m_pPlaylistMenu = new QMenu(this); - m_pPlaylistMenu->setTitle(tr("Add to Playlist")); - connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulatePlaylistMenu())); - m_pCrateMenu = new QMenu(this); - m_pCrateMenu->setTitle(tr("Crates")); - connect(m_pCrateMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulateCrateMenu())); - - m_pMetadataMenu = new QMenu(this); - m_pMetadataMenu->setTitle(tr("Metadata")); - - m_pMetadataUpdateExternalCollectionsMenu = new QMenu(this); - m_pMetadataUpdateExternalCollectionsMenu->setTitle(tr("Update external collections")); - - m_pBPMMenu = new QMenu(this); - m_pBPMMenu->setTitle(tr("Adjust BPM")); - - m_pColorMenu = new QMenu(this); - m_pColorMenu->setTitle(tr("Select Color")); - - m_pClearMetadataMenu = new QMenu(this); - //: Reset metadata in right click track context menu in library - m_pClearMetadataMenu->setTitle(tr("Reset")); - - m_pCoverMenu = new WCoverArtMenu(this); - m_pCoverMenu->setTitle(tr("Cover Art")); - - connect(m_pCoverMenu, SIGNAL(coverInfoSelected(const CoverInfoRelative&)), - this, SLOT(slotCoverInfoSelected(const CoverInfoRelative&))); - connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), - this, SLOT(slotReloadCoverArt())); -} + if (optionIsEnabled(Filter::LoadTo)) { + m_pLoadToMenu = new QMenu(this); + m_pLoadToMenu->setTitle(tr("Load to")); + m_pDeckMenu = new QMenu(this); + m_pDeckMenu->setTitle(tr("Deck")); + m_pSamplerMenu = new QMenu(this); + m_pSamplerMenu->setTitle(tr("Sampler")); + } + + if (optionIsEnabled(Filter::Playlist)) { + m_pPlaylistMenu = new QMenu(this); + m_pPlaylistMenu->setTitle(tr("Add to Playlist")); + connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulatePlaylistMenu())); + } + if (optionIsEnabled(Filter::Crate)) { + m_pCrateMenu = new QMenu(this); + m_pCrateMenu->setTitle(tr("Crates")); + connect(m_pCrateMenu, SIGNAL(aboutToShow()), + this, SLOT(slotPopulateCrateMenu())); + } + + if (optionIsEnabled(Filter::Metadata)) { + m_pMetadataMenu = new QMenu(this); + m_pMetadataMenu->setTitle(tr("Metadata")); + m_pMetadataUpdateExternalCollectionsMenu = new QMenu(this); + m_pMetadataUpdateExternalCollectionsMenu->setTitle(tr("Update external collections")); + + m_pCoverMenu = new WCoverArtMenu(this); + m_pCoverMenu->setTitle(tr("Cover Art")); + connect(m_pCoverMenu, SIGNAL(coverInfoSelected(const CoverInfoRelative&)), + this, SLOT(slotCoverInfoSelected(const CoverInfoRelative&))); + connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), + this, SLOT(slotReloadCoverArt())); + } + + if (optionIsEnabled(Filter::BPM)) { + m_pBPMMenu = new QMenu(this); + m_pBPMMenu->setTitle(tr("Adjust BPM")); + } + + if (optionIsEnabled(Filter::Color)) { + m_pColorMenu = new QMenu(this); + m_pColorMenu->setTitle(tr("Select Color")); + } + + if (optionIsEnabled(Filter::Reset)) { + m_pClearMetadataMenu = new QMenu(this); + //: Reset metadata in right click track context menu in library + m_pClearMetadataMenu->setTitle(tr("Reset")); + } +} void WTrackMenu::createActions() { DEBUG_ASSERT(this); - DEBUG_ASSERT(m_pSamplerMenu); - m_pRemoveAct = new QAction(tr("Remove"), this); - connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + if (optionIsEnabled(Filter::Remove)) { + m_pRemoveAct = new QAction(tr("Remove"), this); + connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + + m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); + connect(m_pRemovePlaylistAct, &QAction::triggered, this, &WTrackMenu::slotRemove); + + m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); + connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + } + + if (optionIsEnabled(Filter::HideUnhidePurge)) { + m_pHideAct = new QAction(tr("Hide from Library"), this); + connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); + + m_pUnhideAct = new QAction(tr("Unhide from Library"), this); + connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); + + m_pPurgeAct = new QAction(tr("Purge from Library"), this); + connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); + } + + if (optionIsEnabled(Filter::Properties)) { + m_pPropertiesAct = new QAction(tr("Properties"), this); + connect(m_pPropertiesAct, SIGNAL(triggered()), + this, SLOT(slotShowTrackInfo())); + } - m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); - connect(m_pRemovePlaylistAct, &QAction::triggered, this, &WTrackMenu::slotRemove); + if (optionIsEnabled(Filter::FileBrowser)) { + m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); + connect(m_pFileBrowserAct, &QAction::triggered, + this, &WTrackMenu::slotOpenInFileBrowser); + } - m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); - connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + if (optionIsEnabled(Filter::AutoDJ)) { + m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); + connect(m_pAutoDJBottomAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJBottom())); - m_pHideAct = new QAction(tr("Hide from Library"), this); - connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); + m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); + connect(m_pAutoDJTopAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJTop())); - m_pUnhideAct = new QAction(tr("Unhide from Library"), this); - connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); + m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); + connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJReplace())); + } - m_pPurgeAct = new QAction(tr("Purge from Library"), this); - connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); + if (optionIsEnabled(Filter::Metadata)) { + m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); + connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), + this, SLOT(slotImportTrackMetadataFromFileTags())); - m_pPropertiesAct = new QAction(tr("Properties"), this); - connect(m_pPropertiesAct, SIGNAL(triggered()), - this, SLOT(slotShowTrackInfo())); + m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); + connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), + this, SLOT(slotShowDlgTagFetcher())); - m_pFileBrowserAct = new QAction(tr("Open in File Browser"), this); - connect(m_pFileBrowserAct, &QAction::triggered, - this, &WTrackMenu::slotOpenInFileBrowser); + m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); + connect(m_pExportMetadataAct, SIGNAL(triggered()), + this, SLOT(slotExportTrackMetadataIntoFileTags())); - m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); - connect(m_pAutoDJBottomAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJBottom())); - m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); - connect(m_pAutoDJTopAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJTop())); + for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { + UpdateExternalTrackCollection updateInExternalTrackCollection; + updateInExternalTrackCollection.externalTrackCollection = externalTrackCollection; + updateInExternalTrackCollection.action = new QAction(externalTrackCollection->name(), this); + updateInExternalTrackCollection.action->setToolTip(externalTrackCollection->description()); + m_updateInExternalTrackCollections += updateInExternalTrackCollection; + auto externalTrackCollectionPtr = updateInExternalTrackCollection.externalTrackCollection; + connect(updateInExternalTrackCollection.action, &QAction::triggered, + this, [this, externalTrackCollectionPtr] { + slotUpdateExternalTrackCollection(externalTrackCollectionPtr); + }); + } + } - m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); - connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJReplace())); - m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); - connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), - this, SLOT(slotImportTrackMetadataFromFileTags())); - - m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); - connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), - this, SLOT(slotShowDlgTagFetcher())); - - m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); - connect(m_pExportMetadataAct, SIGNAL(triggered()), - this, SLOT(slotExportTrackMetadataIntoFileTags())); - - - for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { - UpdateExternalTrackCollection updateInExternalTrackCollection; - updateInExternalTrackCollection.externalTrackCollection = externalTrackCollection; - updateInExternalTrackCollection.action = new QAction(externalTrackCollection->name(), this); - updateInExternalTrackCollection.action->setToolTip(externalTrackCollection->description()); - m_updateInExternalTrackCollections += updateInExternalTrackCollection; - auto externalTrackCollectionPtr = updateInExternalTrackCollection.externalTrackCollection; - connect(updateInExternalTrackCollection.action, &QAction::triggered, - this, [this, externalTrackCollectionPtr] { - slotUpdateExternalTrackCollection(externalTrackCollectionPtr); - }); - } - - - m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); - // currently there is only one preview deck so just map it here. - QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); - connect(m_pAddToPreviewDeck, &QAction::triggered, - this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); - - - // Clear metadata actions - m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); - connect(m_pClearBeatsAction, SIGNAL(triggered()), - this, SLOT(slotClearBeats())); - - m_pClearPlayCountAction = new QAction(tr("Play Count"), this); - connect(m_pClearPlayCountAction, SIGNAL(triggered()), - this, SLOT(slotClearPlayCount())); - - m_pClearMainCueAction = new QAction(tr("Cue Point"), this); - connect(m_pClearMainCueAction, SIGNAL(triggered()), - this, SLOT(slotClearMainCue())); - - m_pClearHotCuesAction = new QAction(tr("Hotcues"), this); - connect(m_pClearHotCuesAction, SIGNAL(triggered()), - this, SLOT(slotClearHotCues())); - - m_pClearIntroCueAction = new QAction(tr("Intro"), this); - connect(m_pClearIntroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearIntroCue())); - - m_pClearOutroCueAction = new QAction(tr("Outro"), this); - connect(m_pClearOutroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearOutroCue())); - - m_pClearLoopAction = new QAction(tr("Loop"), this); - connect(m_pClearLoopAction, SIGNAL(triggered()), - this, SLOT(slotClearLoop())); - - m_pClearKeyAction = new QAction(tr("Key"), this); - connect(m_pClearKeyAction, SIGNAL(triggered()), - this, SLOT(slotClearKey())); - - m_pClearReplayGainAction = new QAction(tr("ReplayGain"), this); - connect(m_pClearReplayGainAction, SIGNAL(triggered()), - this, SLOT(slotClearReplayGain())); - - m_pClearWaveformAction = new QAction(tr("Waveform"), this); - connect(m_pClearWaveformAction, SIGNAL(triggered()), - this, SLOT(slotClearWaveform())); - - m_pClearAllMetadataAction = new QAction(tr("All"), this); - connect(m_pClearAllMetadataAction, SIGNAL(triggered()), - this, SLOT(slotClearAllMetadata())); - - - m_pBpmLockAction = new QAction(tr("Lock BPM"), this); - m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), this); - connect(m_pBpmLockAction, SIGNAL(triggered()), - this, SLOT(slotLockBpm())); - connect(m_pBpmUnlockAction, SIGNAL(triggered()), - this, SLOT(slotUnlockBpm())); - - //BPM edit actions - m_pBpmDoubleAction = new QAction(tr("Double BPM"), this); - m_pBpmHalveAction = new QAction(tr("Halve BPM"), this); - m_pBpmTwoThirdsAction = new QAction(tr("2/3 BPM"), this); - m_pBpmThreeFourthsAction = new QAction(tr("3/4 BPM"), this); - m_pBpmFourThirdsAction = new QAction(tr("4/3 BPM"), this); - m_pBpmThreeHalvesAction = new QAction(tr("3/2 BPM"), this); - - connect(m_pBpmDoubleAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::DOUBLE); }); - connect(m_pBpmHalveAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::HALVE); }); - connect(m_pBpmTwoThirdsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::TWOTHIRDS); }); - connect(m_pBpmThreeFourthsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::THREEFOURTHS); }); - connect(m_pBpmFourThirdsAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::FOURTHIRDS); }); - connect(m_pBpmThreeHalvesAction, &QAction::triggered, - this, [this] { slotScaleBpm(Beats::THREEHALVES); }); - - ColorPaletteSettings colorPaletteSettings(m_pConfig); - m_pColorPickerAction = new WColorPickerAction(WColorPicker::Option::AllowNoColor, colorPaletteSettings.getTrackColorPalette(), this); - m_pColorPickerAction->setObjectName("TrackColorPickerAction"); - connect(m_pColorPickerAction, - &WColorPickerAction::colorPicked, - this, - &WTrackMenu::slotColorPicked); + if (optionIsEnabled(Filter::LoadTo)) { + m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); + // currently there is only one preview deck so just map it here. + QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); + connect(m_pAddToPreviewDeck, &QAction::triggered, + this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); + } + + if (optionIsEnabled(Filter::Reset)) { + // Clear metadata actions + m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); + connect(m_pClearBeatsAction, SIGNAL(triggered()), + this, SLOT(slotClearBeats())); + + m_pClearPlayCountAction = new QAction(tr("Play Count"), this); + connect(m_pClearPlayCountAction, SIGNAL(triggered()), + this, SLOT(slotClearPlayCount())); + + m_pClearMainCueAction = new QAction(tr("Cue Point"), this); + connect(m_pClearMainCueAction, SIGNAL(triggered()), + this, SLOT(slotClearMainCue())); + + m_pClearHotCuesAction = new QAction(tr("Hotcues"), this); + connect(m_pClearHotCuesAction, SIGNAL(triggered()), + this, SLOT(slotClearHotCues())); + + m_pClearIntroCueAction = new QAction(tr("Intro"), this); + connect(m_pClearIntroCueAction, SIGNAL(triggered()), + this, SLOT(slotClearIntroCue())); + + m_pClearOutroCueAction = new QAction(tr("Outro"), this); + connect(m_pClearOutroCueAction, SIGNAL(triggered()), + this, SLOT(slotClearOutroCue())); + + m_pClearLoopAction = new QAction(tr("Loop"), this); + connect(m_pClearLoopAction, SIGNAL(triggered()), + this, SLOT(slotClearLoop())); + + m_pClearKeyAction = new QAction(tr("Key"), this); + connect(m_pClearKeyAction, SIGNAL(triggered()), + this, SLOT(slotClearKey())); + + m_pClearReplayGainAction = new QAction(tr("ReplayGain"), this); + connect(m_pClearReplayGainAction, SIGNAL(triggered()), + this, SLOT(slotClearReplayGain())); + + m_pClearWaveformAction = new QAction(tr("Waveform"), this); + connect(m_pClearWaveformAction, SIGNAL(triggered()), + this, SLOT(slotClearWaveform())); + + m_pClearAllMetadataAction = new QAction(tr("All"), this); + connect(m_pClearAllMetadataAction, SIGNAL(triggered()), + this, SLOT(slotClearAllMetadata())); + } + + if (optionIsEnabled(Filter::BPM)) { + m_pBpmLockAction = new QAction(tr("Lock BPM"), this); + m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), this); + connect(m_pBpmLockAction, SIGNAL(triggered()), + this, SLOT(slotLockBpm())); + connect(m_pBpmUnlockAction, SIGNAL(triggered()), + this, SLOT(slotUnlockBpm())); + + //BPM edit actions + m_pBpmDoubleAction = new QAction(tr("Double BPM"), this); + m_pBpmHalveAction = new QAction(tr("Halve BPM"), this); + m_pBpmTwoThirdsAction = new QAction(tr("2/3 BPM"), this); + m_pBpmThreeFourthsAction = new QAction(tr("3/4 BPM"), this); + m_pBpmFourThirdsAction = new QAction(tr("4/3 BPM"), this); + m_pBpmThreeHalvesAction = new QAction(tr("3/2 BPM"), this); + + connect(m_pBpmDoubleAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::DOUBLE); }); + connect(m_pBpmHalveAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::HALVE); }); + connect(m_pBpmTwoThirdsAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::TWOTHIRDS); }); + connect(m_pBpmThreeFourthsAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::THREEFOURTHS); }); + connect(m_pBpmFourThirdsAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::FOURTHIRDS); }); + connect(m_pBpmThreeHalvesAction, &QAction::triggered, + this, [this] { slotScaleBpm(Beats::THREEHALVES); }); + } + + if (optionIsEnabled(Filter::Color)) { + ColorPaletteSettings colorPaletteSettings(m_pConfig); + m_pColorPickerAction = new WColorPickerAction(WColorPicker::Option::AllowNoColor, colorPaletteSettings.getTrackColorPalette(), this); + m_pColorPickerAction->setObjectName("TrackColorPickerAction"); + connect(m_pColorPickerAction, + &WColorPickerAction::colorPicked, + this, + &WTrackMenu::slotColorPicked); + } } void WTrackMenu::teardownActions() { @@ -291,19 +317,21 @@ void WTrackMenu::teardownActions() { void WTrackMenu::setupActions() { // TODO: Replace true with filters teardownActions(); + createActions(); QModelIndexList indices = m_pSelectedTrackIndices; // Gray out some stuff if multiple songs were selected. bool oneSongSelected = m_pTrackIdList.size() == 1; - if (true) { + if (optionIsEnabled(Filter::AutoDJ) && + (!m_pTrackModel | modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ))) { addAction(m_pAutoDJBottomAct); addAction(m_pAutoDJTopAct); addAction(m_pAutoDJReplaceAct); addSeparator(); } - if (true) { + if (optionIsEnabled(Filter::LoadTo)) { int iNumDecks = m_pNumDecks->get(); if (iNumDecks > 0) { for (int i = 1; i <= iNumDecks; ++i) { @@ -322,9 +350,8 @@ void WTrackMenu::setupActions() { } } m_pLoadToMenu->addMenu(m_pDeckMenu); - } - if (true) { + int iNumSamplers = m_pNumSamplers->get(); if (iNumSamplers > 0) { for (int i = 1; i <= iNumSamplers; ++i) { @@ -342,50 +369,53 @@ void WTrackMenu::setupActions() { } m_pLoadToMenu->addMenu(m_pSamplerMenu); } - } - if (true && - m_pNumPreviewDecks->get() > 0.0) { - m_pLoadToMenu->addAction(m_pAddToPreviewDeck); - } - if (!m_pLoadToMenu->isEmpty()){ - addMenu(m_pLoadToMenu); - addSeparator(); + if (m_pNumPreviewDecks->get() > 0.0) { + m_pLoadToMenu->addAction(m_pAddToPreviewDeck); + } + + if (!m_pLoadToMenu->isEmpty()){ + addMenu(m_pLoadToMenu); + addSeparator(); + } } - if (true) { + if (optionIsEnabled(Filter::Playlist)) { // Playlist menu is lazy loaded on hover by slotPopulatePlaylistMenu // to avoid unnecessary database queries m_bPlaylistMenuLoaded = false; addMenu(m_pPlaylistMenu); } - if (true) { + if (optionIsEnabled(Filter::Crate)) { // Crate menu is lazy loaded on hover by slotPopulateCrateMenu // to avoid unnecessary database queries m_bCrateMenuLoaded = false; addMenu(m_pCrateMenu); } - // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks - bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { - m_pRemoveAct->setEnabled(!locked); - addAction(m_pRemoveAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST)) { - m_pRemovePlaylistAct->setEnabled(!locked); - addAction(m_pRemovePlaylistAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE)) { - m_pRemoveCrateAct->setEnabled(!locked); - addAction(m_pRemoveCrateAct); + if (optionIsEnabled(Filter::Remove)) { + // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks + bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { + m_pRemoveAct->setEnabled(!locked); + addAction(m_pRemoveAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST)) { + m_pRemovePlaylistAct->setEnabled(!locked); + addAction(m_pRemovePlaylistAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE)) { + m_pRemoveCrateAct->setEnabled(!locked); + addAction(m_pRemoveCrateAct); + } } addSeparator(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + if (optionIsEnabled(Filter::Metadata) && + modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); @@ -418,7 +448,28 @@ void WTrackMenu::setupActions() { if (!m_pMetadataUpdateExternalCollectionsMenu->isEmpty()) { m_pMetadataMenu->addMenu(m_pMetadataUpdateExternalCollectionsMenu); } + // Cover art menu only applies if at least one track is selected. + if (indices.size()) { + // We load a single track to get the necessary context for the cover (we use + // last to be consistent with selectionChanged above). + QModelIndex last = indices.last(); + CoverInfo info; + info.source = static_cast( + last.sibling(last.row(), m_iCoverSourceColumn).data().toInt()); + info.type = static_cast( + last.sibling(last.row(), m_iCoverTypeColumn).data().toInt()); + info.hash = last.sibling(last.row(), m_iCoverHashColumn).data().toUInt(); + info.trackLocation = last.sibling( + last.row(), m_iTrackLocationColumn).data().toString(); + info.coverLocation = last.sibling( + last.row(), m_iCoverLocationColumn).data().toString(); + m_pCoverMenu->setCoverArt(info); + m_pMetadataMenu->addMenu(m_pCoverMenu); + } + addMenu(m_pMetadataMenu); + } + if (optionIsEnabled(Filter::Reset)) { DEBUG_ASSERT(m_pTrackModel); if (m_pTrackModel == nullptr) { return; @@ -434,13 +485,7 @@ void WTrackMenu::setupActions() { } m_pClearBeatsAction->setEnabled(allowClear); m_pClearMetadataMenu->addAction(m_pClearBeatsAction); - } - - if (true) { m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); - } - - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { // FIXME: Why is clearing the loop not working? m_pClearMetadataMenu->addAction(m_pClearMainCueAction); m_pClearMetadataMenu->addAction(m_pClearHotCuesAction); @@ -452,29 +497,10 @@ void WTrackMenu::setupActions() { m_pClearMetadataMenu->addAction(m_pClearWaveformAction); m_pClearMetadataMenu->addSeparator(); m_pClearMetadataMenu->addAction(m_pClearAllMetadataAction); - - // Cover art menu only applies if at least one track is selected. - if (indices.size()) { - // We load a single track to get the necessary context for the cover (we use - // last to be consistent with selectionChanged above). - QModelIndex last = indices.last(); - CoverInfo info; - info.source = static_cast( - last.sibling(last.row(), m_iCoverSourceColumn).data().toInt()); - info.type = static_cast( - last.sibling(last.row(), m_iCoverTypeColumn).data().toInt()); - info.hash = last.sibling(last.row(), m_iCoverHashColumn).data().toUInt(); - info.trackLocation = last.sibling( - last.row(), m_iTrackLocationColumn).data().toString(); - info.coverLocation = last.sibling( - last.row(), m_iCoverLocationColumn).data().toString(); - m_pCoverMenu->setCoverArt(info); - m_pMetadataMenu->addMenu(m_pCoverMenu); - } - - addMenu(m_pMetadataMenu); addMenu(m_pClearMetadataMenu); + } + if(optionIsEnabled(Filter::BPM)) { m_pBPMMenu->addAction(m_pBpmDoubleAction); m_pBPMMenu->addAction(m_pBpmHalveAction); m_pBPMMenu->addAction(m_pBpmTwoThirdsAction); @@ -541,59 +567,64 @@ void WTrackMenu::setupActions() { } } addMenu(m_pBPMMenu); + } - // Track color menu only appears if at least one track is selected - if (!indices.empty()) { - m_pColorPickerAction->setColorPalette( - ColorPaletteSettings(m_pConfig).getTrackColorPalette()); + // Track color menu only appears if at least one track is selected + if (optionIsEnabled(Filter::Color) && + m_pTrackModel != nullptr && + !indices.empty()) { + m_pColorPickerAction->setColorPalette( + ColorPaletteSettings(m_pConfig).getTrackColorPalette()); - // Get color of first selected track - int column = m_pTrackModel->fieldIndex(LIBRARYTABLE_COLOR); - QModelIndex index = indices.at(0).sibling(indices.at(0).row(), column); - auto trackColor = mixxx::RgbColor::fromQVariant(index.data()); + // Get color of first selected track + int column = m_pTrackModel->fieldIndex(LIBRARYTABLE_COLOR); + QModelIndex index = indices.at(0).sibling(indices.at(0).row(), column); + auto trackColor = mixxx::RgbColor::fromQVariant(index.data()); - // Check if all other selected tracks have the same color - bool multipleTrackColors = false; - for (int i = 1; i < indices.size(); ++i) { - int row = indices.at(i).row(); - QModelIndex index = indices.at(i).sibling(row, column); + // Check if all other selected tracks have the same color + bool multipleTrackColors = false; + for (int i = 1; i < indices.size(); ++i) { + int row = indices.at(i).row(); + QModelIndex index = indices.at(i).sibling(row, column); - if (trackColor != mixxx::RgbColor::fromQVariant(index.data())) { - trackColor = mixxx::RgbColor::nullopt(); - multipleTrackColors = true; - break; - } + if (trackColor != mixxx::RgbColor::fromQVariant(index.data())) { + trackColor = mixxx::RgbColor::nullopt(); + multipleTrackColors = true; + break; } + } - if (multipleTrackColors) { - m_pColorPickerAction->resetSelectedColor(); - } else { - m_pColorPickerAction->setSelectedColor(trackColor); - } - m_pColorMenu->addAction(m_pColorPickerAction); - addMenu(m_pColorMenu); + if (multipleTrackColors) { + m_pColorPickerAction->resetSelectedColor(); + } else { + m_pColorPickerAction->setSelectedColor(trackColor); } + m_pColorMenu->addAction(m_pColorPickerAction); + addMenu(m_pColorMenu); } addSeparator(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE)) { - m_pHideAct->setEnabled(!locked); - addAction(m_pHideAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE)) { - m_pUnhideAct->setEnabled(!locked); - addAction(m_pUnhideAct); - } - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE)) { - m_pPurgeAct->setEnabled(!locked); - addAction(m_pPurgeAct); + if (optionIsEnabled(Filter::HideUnhidePurge)) { + bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE)) { + m_pHideAct->setEnabled(!locked); + addAction(m_pHideAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE)) { + m_pUnhideAct->setEnabled(!locked); + addAction(m_pUnhideAct); + } + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE)) { + m_pPurgeAct->setEnabled(!locked); + addAction(m_pPurgeAct); + } } - if (true) { + if (optionIsEnabled(Filter::FileBrowser)) { addAction(m_pFileBrowserAct); } - if (true) { + if (optionIsEnabled(Filter::Properties)) { addSeparator(); m_pPropertiesAct->setEnabled(oneSongSelected); addAction(m_pPropertiesAct); @@ -1436,3 +1467,7 @@ void WTrackMenu::clearTrackSelection() { m_pTrackPointerList.clear(); m_pSelectedTrackIndices.clear(); } + +bool WTrackMenu::optionIsEnabled(Filter flag) { + return m_eFilters.testFlag(Filter::None) || m_eFilters.testFlag(flag); +} diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index 216bdd2dc68c..28db5cf0d6aa 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -30,21 +30,32 @@ class ExternalTrackCollection; class WTrackMenu : public QMenu { Q_OBJECT public: - WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager); - ~WTrackMenu() override; + enum Filter { + None, + AutoDJ, + LoadTo, + Playlist, + Crate, + Remove, + Metadata, + Reset, + BPM, + Color, + HideUnhidePurge, + FileBrowser, + Properties, + }; + Q_DECLARE_FLAGS(Filters, Filter) + + WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager, Filters flags = Filter::None); + ~WTrackMenu() {}; void setTrackId(TrackId track); void setTrackIds(TrackIdList trackList); void setTrackIndexList(QModelIndexList indexList); void setTrackModel(TrackModel* trackModel); - enum Filter { - None, - FileBrowser, - Playlist, - Crate - }; - Q_DECLARE_FLAGS(Filters, Filter) + private slots: void slotOpenInFileBrowser(); @@ -112,6 +123,7 @@ class WTrackMenu : public QMenu { TrackPointerList getTrackPointerList(); bool modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const; + bool optionIsEnabled(Filter flag); void addSelectionToPlaylist(int iPlaylistId); void updateSelectionCrates(QWidget* pWidget); diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 0b26434bc2fc..1fe7dd810baf 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -32,7 +32,8 @@ WTrackProperty::WTrackProperty(const char* group, setAcceptDrops(true); // Setup context menu - m_pMenu = new WTrackMenu(this, pConfig, pTrackCollectionManager); + WTrackMenu::Filters flags = WTrackMenu::Filter::Crate; + m_pMenu = new WTrackMenu(this, pConfig, pTrackCollectionManager, flags); } WTrackProperty::~WTrackProperty() { From 5cad360b9681c54584e22725b2626d86f7d03406 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sun, 5 Apr 2020 04:29:20 +0530 Subject: [PATCH 184/393] widget/wtrackmenu: initialize pointers and fix filters --- src/widget/wtrackmenu.cpp | 191 +++++++++++++++++++++++----------- src/widget/wtrackmenu.h | 128 +++++++++++------------ src/widget/wtrackproperty.cpp | 5 +- 3 files changed, 200 insertions(+), 124 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 0cf390311a90..9145104b8272 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -62,8 +62,7 @@ WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollec "[Master]", "num_decks", this); m_pNumPreviewDecks = new ControlProxy( "[Master]", "num_preview_decks", this); - - constructMenus(); +// constructMenus(); // FIXME: createActions() would cause segfault in constructor. // createActions(); } @@ -125,7 +124,27 @@ void WTrackMenu::constructMenus() { } void WTrackMenu::createActions() { - DEBUG_ASSERT(this); + if (optionIsEnabled(Filter::AutoDJ)) { + m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); + connect(m_pAutoDJBottomAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJBottom())); + + m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); + connect(m_pAutoDJTopAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJTop())); + + m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); + connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), + this, SLOT(slotAddToAutoDJReplace())); + } + + if (optionIsEnabled(Filter::LoadTo)) { + m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); + // currently there is only one preview deck so just map it here. + QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); + connect(m_pAddToPreviewDeck, &QAction::triggered, + this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); + } if (optionIsEnabled(Filter::Remove)) { m_pRemoveAct = new QAction(tr("Remove"), this); @@ -161,20 +180,6 @@ void WTrackMenu::createActions() { this, &WTrackMenu::slotOpenInFileBrowser); } - if (optionIsEnabled(Filter::AutoDJ)) { - m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); - connect(m_pAutoDJBottomAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJBottom())); - - m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); - connect(m_pAutoDJTopAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJTop())); - - m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); - connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJReplace())); - } - if (optionIsEnabled(Filter::Metadata)) { m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), @@ -203,15 +208,6 @@ void WTrackMenu::createActions() { } } - - if (optionIsEnabled(Filter::LoadTo)) { - m_pAddToPreviewDeck = new QAction(tr("Preview Deck"), this); - // currently there is only one preview deck so just map it here. - QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0); - connect(m_pAddToPreviewDeck, &QAction::triggered, - this, [this, previewDeckGroup] { loadSelectionToGroup(previewDeckGroup); }); - } - if (optionIsEnabled(Filter::Reset)) { // Clear metadata actions m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); @@ -302,29 +298,49 @@ void WTrackMenu::createActions() { void WTrackMenu::teardownActions() { clear(); - m_pLoadToMenu->clear(); - m_pDeckMenu->clear(); - m_pSamplerMenu->clear(); - m_pPlaylistMenu->clear(); - m_pCrateMenu->clear(); - m_pMetadataMenu->clear(); - m_pMetadataUpdateExternalCollectionsMenu->clear(); - m_pClearMetadataMenu->clear(); - m_pBPMMenu->clear(); - m_pColorMenu->clear(); + if (m_pLoadToMenu) { + m_pLoadToMenu->clear(); + } + if (m_pDeckMenu) { + m_pDeckMenu->clear(); + } + if (m_pSamplerMenu) { + m_pSamplerMenu->clear(); + } + if (m_pPlaylistMenu) { + m_pPlaylistMenu->clear(); + } + + if (m_pCrateMenu) { + m_pCrateMenu->clear(); + } + if (m_pMetadataMenu) { + m_pMetadataMenu->clear(); + } + if (m_pMetadataUpdateExternalCollectionsMenu) { + m_pMetadataUpdateExternalCollectionsMenu->clear(); + } + if (m_pClearMetadataMenu) { + m_pClearMetadataMenu->clear(); + } + if (m_pBPMMenu) { + m_pBPMMenu->clear(); + } + if (m_pColorMenu) { + m_pColorMenu->clear(); + } } void WTrackMenu::setupActions() { - // TODO: Replace true with filters teardownActions(); + constructMenus(); createActions(); QModelIndexList indices = m_pSelectedTrackIndices; // Gray out some stuff if multiple songs were selected. bool oneSongSelected = m_pTrackIdList.size() == 1; - if (optionIsEnabled(Filter::AutoDJ) && - (!m_pTrackModel | modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ))) { + if (optionIsEnabled(Filter::AutoDJ)) { addAction(m_pAutoDJBottomAct); addAction(m_pAutoDJTopAct); addAction(m_pAutoDJReplaceAct); @@ -414,8 +430,7 @@ void WTrackMenu::setupActions() { addSeparator(); - if (optionIsEnabled(Filter::Metadata) && - modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { + if (optionIsEnabled(Filter::Metadata)) { m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); @@ -570,9 +585,7 @@ void WTrackMenu::setupActions() { } // Track color menu only appears if at least one track is selected - if (optionIsEnabled(Filter::Color) && - m_pTrackModel != nullptr && - !indices.empty()) { + if (optionIsEnabled(Filter::Color) && !indices.empty()) { m_pColorPickerAction->setColorPalette( ColorPaletteSettings(m_pConfig).getTrackColorPalette()); @@ -635,12 +648,18 @@ void WTrackMenu::setTrackIds(TrackIdList trackIdList) { // Clean all forms of track store clearTrackSelection(); - m_pTrackIdList = std::move(trackIdList); // Store the track pointers at each initialization of track ids. - for (const auto trackId : m_pTrackIdList) { + for (const auto trackId : trackIdList) { TrackPointer trackPointer = m_pTrackCollectionManager->internalCollection()->getTrackById(trackId); - m_pTrackPointerList.push_back(trackPointer); + if (trackPointer) { + m_pTrackPointerList.push_back(trackPointer); + m_pTrackIdList.push_back(trackId); + } } + if (m_pTrackPointerList.empty()) { + return; + } + // Add actions to menu setupActions(); } @@ -661,12 +680,17 @@ void WTrackMenu::setTrackIndexList(QModelIndexList indexList) { } clearTrackSelection(); - m_pSelectedTrackIndices = std::move(indexList); - for (auto index : m_pSelectedTrackIndices) { + for (auto index : indexList) { TrackPointer trackPointer = m_pTrackModel->getTrack(index); - m_pTrackPointerList.push_back(trackPointer); - TrackId trackId = trackPointer->getId(); - m_pTrackIdList.push_back(trackId); + if (trackPointer) { + m_pSelectedTrackIndices.push_back(index); + m_pTrackPointerList.push_back(trackPointer); + TrackId trackId = trackPointer->getId(); + m_pTrackIdList.push_back(trackId); + } + } + if (m_pTrackPointerList.empty()) { + return; } setupActions(); @@ -687,7 +711,6 @@ TrackPointerList WTrackMenu::getTrackPointerList() { } bool WTrackMenu::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const { - DEBUG_ASSERT(m_pTrackModel); if (!m_pTrackModel) { return false; } @@ -696,8 +719,6 @@ bool WTrackMenu::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities (trackModel->getCapabilities() & capabilities) == capabilities; } - - void WTrackMenu::slotImportTrackMetadataFromFileTags() { if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { return; @@ -1083,12 +1104,12 @@ void WTrackMenu::loadSelectionToGroup(QString group, bool play) { TrackPointer pTrack = m_pTrackPointerList.at(0); if (pTrack) { + // TODO: load track from this class without depending on + // external slot to load track emit loadTrackToPlayer(pTrack, group, play); } } - - void WTrackMenu::slotClearBeats() { if (m_pTrackPointerList.empty()) { return; @@ -1469,5 +1490,59 @@ void WTrackMenu::clearTrackSelection() { } bool WTrackMenu::optionIsEnabled(Filter flag) { - return m_eFilters.testFlag(Filter::None) || m_eFilters.testFlag(flag); + bool optionIsAvailable = m_eFilters.testFlag(Filter::None) || m_eFilters.testFlag(flag); + bool optionIsValid = false; + + if (!optionIsAvailable) { + return false; + } + + Filters independentOptions = + Filter::LoadTo | + Filter::Playlist | + Filter::Crate | + Filter::FileBrowser | + Filter::Properties; + + // Some of these can be made independent of track table. + Filters trackTableDependentOptions = + Filter::AutoDJ | + Filter::Remove | + Filter::Metadata | + Filter::Reset | + Filter::BPM | + Filter::Color | + Filter::HideUnhidePurge; + + if (independentOptions.testFlag(flag)) { + return true; + } + + if (trackTableDependentOptions.testFlag(flag)) { + if (m_pTrackModel == nullptr) { + // Add to AutoDJ should be allowed from non-WTrackTableViews + if (flag == Filter::AutoDJ) { + return true; + } + else { + return false; + } + } + else { + optionIsValid = true; + if (flag == Filter::AutoDJ) { + optionIsValid = modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ); + } else if (flag == Filter::Remove) { + optionIsValid = modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE); + } else if (flag == Filter::HideUnhidePurge) { + optionIsValid = modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE); + } + } + } + + return optionIsValid; } diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index 28db5cf0d6aa..b9f572af0014 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -31,24 +31,24 @@ class WTrackMenu : public QMenu { Q_OBJECT public: enum Filter { - None, - AutoDJ, - LoadTo, - Playlist, - Crate, - Remove, - Metadata, - Reset, - BPM, - Color, - HideUnhidePurge, - FileBrowser, - Properties, + None = 0, + AutoDJ = 1, + LoadTo = 1 << 1, + Playlist = 1 << 2, + Crate = 1 << 3, + Remove = 1 << 4, + Metadata = 1 << 5, + Reset = 1 << 6, + BPM = 1 << 7, + Color = 1 << 8, + HideUnhidePurge = 1 << 9, + FileBrowser = 1 << 10, + Properties = 1 << 11, }; Q_DECLARE_FLAGS(Filters, Filter) WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager, Filters flags = Filter::None); - ~WTrackMenu() {}; + ~WTrackMenu() {} void setTrackId(TrackId track); void setTrackIds(TrackIdList trackList); @@ -139,88 +139,88 @@ class WTrackMenu : public QMenu { void loadSelectionToGroup(QString group, bool play = false); void clearTrackSelection(); - ControlProxy* m_pNumSamplers; - ControlProxy* m_pNumDecks; - ControlProxy* m_pNumPreviewDecks; + ControlProxy* m_pNumSamplers{}; + ControlProxy* m_pNumDecks{}; + ControlProxy* m_pNumPreviewDecks{}; // The selected tracks for which the context menu is created TrackIdList m_pTrackIdList; // Context menu machinery - QMenu *m_pLoadToMenu; - QMenu *m_pDeckMenu; - QMenu *m_pSamplerMenu; + QMenu *m_pLoadToMenu{}; + QMenu *m_pDeckMenu{}; + QMenu *m_pSamplerMenu{}; - QMenu *m_pPlaylistMenu; - QMenu *m_pCrateMenu; - QMenu *m_pMetadataMenu; - QMenu *m_pMetadataUpdateExternalCollectionsMenu; - QMenu *m_pClearMetadataMenu; - QMenu *m_pBPMMenu; - QMenu *m_pColorMenu; + QMenu *m_pPlaylistMenu{}; + QMenu *m_pCrateMenu{}; + QMenu *m_pMetadataMenu{}; + QMenu *m_pMetadataUpdateExternalCollectionsMenu{}; + QMenu *m_pClearMetadataMenu{}; + QMenu *m_pBPMMenu{}; + QMenu *m_pColorMenu{}; - WCoverArtMenu* m_pCoverMenu; + WCoverArtMenu* m_pCoverMenu{}; // Reload Track Metadata Action: - QAction *m_pImportMetadataFromFileAct; - QAction *m_pImportMetadataFromMusicBrainzAct; + QAction *m_pImportMetadataFromFileAct{}; + QAction *m_pImportMetadataFromMusicBrainzAct{}; // Save Track Metadata Action: - QAction *m_pExportMetadataAct; + QAction *m_pExportMetadataAct{}; // Load Track to PreviewDeck - QAction* m_pAddToPreviewDeck; + QAction* m_pAddToPreviewDeck{}; // Send to Auto-DJ Action - QAction *m_pAutoDJBottomAct; - QAction *m_pAutoDJTopAct; - QAction *m_pAutoDJReplaceAct; + QAction *m_pAutoDJBottomAct{}; + QAction *m_pAutoDJTopAct{}; + QAction *m_pAutoDJReplaceAct{}; // Remove from table - QAction *m_pRemoveAct; - QAction *m_pRemovePlaylistAct; - QAction *m_pRemoveCrateAct; - QAction *m_pHideAct; - QAction *m_pUnhideAct; - QAction *m_pPurgeAct; + QAction *m_pRemoveAct{}; + QAction *m_pRemovePlaylistAct{}; + QAction *m_pRemoveCrateAct{}; + QAction *m_pHideAct{}; + QAction *m_pUnhideAct{}; + QAction *m_pPurgeAct{}; // Show track-editor action - QAction *m_pPropertiesAct; - QAction *m_pFileBrowserAct; + QAction *m_pPropertiesAct{}; + QAction *m_pFileBrowserAct{}; // BPM feature - QAction *m_pBpmLockAction; - QAction *m_pBpmUnlockAction; - QAction *m_pBpmDoubleAction; - QAction *m_pBpmHalveAction; - QAction *m_pBpmTwoThirdsAction; - QAction *m_pBpmThreeFourthsAction; - QAction *m_pBpmFourThirdsAction; - QAction *m_pBpmThreeHalvesAction; + QAction *m_pBpmLockAction{}; + QAction *m_pBpmUnlockAction{}; + QAction *m_pBpmDoubleAction{}; + QAction *m_pBpmHalveAction{}; + QAction *m_pBpmTwoThirdsAction{}; + QAction *m_pBpmThreeFourthsAction{}; + QAction *m_pBpmFourThirdsAction{}; + QAction *m_pBpmThreeHalvesAction{}; // Track color - WColorPickerAction *m_pColorPickerAction; + WColorPickerAction *m_pColorPickerAction{}; // Clear track metadata actions - QAction* m_pClearBeatsAction; - QAction* m_pClearPlayCountAction; - QAction* m_pClearMainCueAction; - QAction* m_pClearHotCuesAction; - QAction* m_pClearIntroCueAction; - QAction* m_pClearOutroCueAction; - QAction* m_pClearLoopAction; - QAction* m_pClearWaveformAction; - QAction* m_pClearKeyAction; - QAction* m_pClearReplayGainAction; - QAction* m_pClearAllMetadataAction; + QAction* m_pClearBeatsAction{}; + QAction* m_pClearPlayCountAction{}; + QAction* m_pClearMainCueAction{}; + QAction* m_pClearHotCuesAction{}; + QAction* m_pClearIntroCueAction{}; + QAction* m_pClearOutroCueAction{}; + QAction* m_pClearLoopAction{}; + QAction* m_pClearWaveformAction{}; + QAction* m_pClearKeyAction{}; + QAction* m_pClearReplayGainAction{}; + QAction* m_pClearAllMetadataAction{}; const UserSettingsPointer m_pConfig; TrackCollectionManager* m_pTrackCollectionManager; TrackPointerList m_pTrackPointerList; - TrackModel* m_pTrackModel; + TrackModel* m_pTrackModel{}; QModelIndexList m_pSelectedTrackIndices; diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index 1fe7dd810baf..ba4082997ca1 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -28,11 +28,12 @@ WTrackProperty::WTrackProperty(const char* group, TrackCollectionManager* pTrackCollectionManager) : WLabel(pParent), m_pGroup(group), - m_pConfig(std::move(pConfig)) { + m_pConfig(pConfig) { setAcceptDrops(true); // Setup context menu - WTrackMenu::Filters flags = WTrackMenu::Filter::Crate; + WTrackMenu::Filters flags = WTrackMenu::Filter::Playlist | + WTrackMenu::Filter::Crate; m_pMenu = new WTrackMenu(this, pConfig, pTrackCollectionManager, flags); } From 35a6a1316fbf2fd9e577f3e75166307974047500 Mon Sep 17 00:00:00 2001 From: ehendrikd Date: Sun, 5 Apr 2020 11:38:54 +1000 Subject: [PATCH 185/393] Fix Windows CI build (#2629) --- src/library/rekordbox/rekordboxfeature.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/rekordbox/rekordboxfeature.cpp b/src/library/rekordbox/rekordboxfeature.cpp index a69563499941..4ce2a204f9cd 100644 --- a/src/library/rekordbox/rekordboxfeature.cpp +++ b/src/library/rekordbox/rekordboxfeature.cpp @@ -284,7 +284,7 @@ QString getText(rekordbox_pdb_t::device_sql_string_t* deviceString) { // Some strings read from Rekordbox *.PDB files contain random null characters // which if not removed cause Mixxx to crash when attempting to read file paths - return text.remove('\x0'); + return text.remove(QChar('\x0')); } int createDevicePlaylist(QSqlDatabase& database, QString devicePath) { From 4f397357a054372f7e755a4a1548397db77eb24a Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 4 Apr 2020 23:04:30 -0500 Subject: [PATCH 186/393] ColorPaletteEditor: use theme's list-add and list-remove icons --- src/preferences/colorpaletteeditor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 61848f1479fa..0a66cdafceaf 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -31,7 +31,7 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent, bool showHotcueNumbers) QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); pColorButtonLayout->addWidget(pExpander); - m_pRemoveColorButton = new QPushButton("-", this); + m_pRemoveColorButton = new QPushButton(QIcon::fromTheme("list-remove"), "", this); m_pRemoveColorButton->setFixedWidth(32); m_pRemoveColorButton->setToolTip(tr("Remove Color")); m_pRemoveColorButton->setDisabled(true); @@ -41,7 +41,7 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent, bool showHotcueNumbers) this, &ColorPaletteEditor::slotRemoveColor); - m_pAddColorButton = new QPushButton("+", this); + m_pAddColorButton = new QPushButton(QIcon::fromTheme("list-add"), "", this); m_pAddColorButton->setFixedWidth(32); m_pAddColorButton->setToolTip(tr("Add Color")); pColorButtonLayout->addWidget(m_pAddColorButton); From 8eb4adce8c59136c2bb4d7c483a460a201ec8c9b Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sun, 5 Apr 2020 13:58:00 +0530 Subject: [PATCH 187/393] widget/wtrackmenu: move options to track table dependent --- src/widget/wtrackmenu.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index 9145104b8272..f6600215b269 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -1498,21 +1498,21 @@ bool WTrackMenu::optionIsEnabled(Filter flag) { } Filters independentOptions = - Filter::LoadTo | Filter::Playlist | Filter::Crate | - Filter::FileBrowser | - Filter::Properties; + Filter::FileBrowser; // Some of these can be made independent of track table. Filters trackTableDependentOptions = - Filter::AutoDJ | + Filter::AutoDJ | + Filter::LoadTo | Filter::Remove | Filter::Metadata | Filter::Reset | Filter::BPM | Filter::Color | - Filter::HideUnhidePurge; + Filter::HideUnhidePurge | + Filter::Properties; if (independentOptions.testFlag(flag)) { return true; From 57ca3aa56e3404c0ace980c1a9da26befebe3854 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Sun, 5 Apr 2020 17:21:56 +0530 Subject: [PATCH 188/393] widget/wtrackmenu: code cleanup --- src/widget/wtrackmenu.cpp | 190 +++++++++++++-------------------- src/widget/wtrackmenu.h | 99 ++++++++--------- src/widget/wtrackproperty.cpp | 23 +--- src/widget/wtrackproperty.h | 1 - src/widget/wtracktableview.cpp | 26 +---- src/widget/wtracktableview.h | 9 +- 6 files changed, 132 insertions(+), 216 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index f6600215b269..f9113efed412 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -7,11 +7,6 @@ #include #include - -#include -#include - - #include "control/controlobject.h" #include "control/controlproxy.h" #include "library/coverartutils.h" @@ -29,25 +24,19 @@ #include "mixer/playermanager.h" #include "preferences/colorpalettesettings.h" #include "sources/soundsourceproxy.h" -#include "util/desktophelper.h" -#include "widget/wlibrarytableview.h" -#include "track/track.h" #include "track/trackref.h" #include "util/desktophelper.h" +#include "util/desktophelper.h" #include "util/parented_ptr.h" -#include "waveform/guitick.h" #include "widget/wcolorpickeraction.h" -#include "widget/wcoverartmenu.h" #include "widget/wskincolor.h" #include "widget/wwidget.h" - WTrackMenu::WTrackMenu(QWidget *parent, UserSettingsPointer pConfig, TrackCollectionManager *pTrackCollectionManager, Filters flags) : QMenu(parent), m_pConfig(std::move(pConfig)), m_pTrackCollectionManager(pTrackCollectionManager), - m_pTrackModel(nullptr), m_bPlaylistMenuLoaded(false), m_bCrateMenuLoaded(false), m_iCoverSourceColumn(-1), @@ -696,9 +685,20 @@ void WTrackMenu::setTrackIndexList(QModelIndexList indexList) { setupActions(); } +void WTrackMenu::setTrackModel(TrackModel* trackModel) { + m_pTrackModel = trackModel; + + // Move this to another function + m_iCoverSourceColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_SOURCE); + m_iCoverTypeColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_TYPE); + m_iCoverLocationColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_LOCATION); + m_iCoverHashColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_HASH); + m_iCoverColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART); + m_iTrackLocationColumn = trackModel->fieldIndex(TRACKLOCATIONSTABLE_LOCATION); +} void WTrackMenu::slotOpenInFileBrowser() { - TrackPointerList trackPointerList = getTrackPointerList(); + TrackPointerList trackPointerList = m_pTrackPointerList; QStringList locations; for (const TrackPointer& trackPointer : trackPointerList) { locations << trackPointer->getLocation(); @@ -706,19 +706,6 @@ void WTrackMenu::slotOpenInFileBrowser() { mixxx::DesktopHelper::openInFileBrowser(locations); } -TrackPointerList WTrackMenu::getTrackPointerList() { - return m_pTrackPointerList; -} - -bool WTrackMenu::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const { - if (!m_pTrackModel) { - return false; - } - TrackModel* trackModel = m_pTrackModel; - return trackModel && - (trackModel->getCapabilities() & capabilities) == capabilities; -} - void WTrackMenu::slotImportTrackMetadataFromFileTags() { if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { return; @@ -806,19 +793,6 @@ void WTrackMenu::slotUpdateExternalTrackCollection( externalTrackCollection->updateTracks(std::move(trackRefs)); } -//slot for reset played count, sets count to 0 of one or more tracks -void WTrackMenu::slotClearPlayCount() { - if (m_pTrackPointerList.empty()) { - return; - } - - for (const auto& pTrack : m_pTrackPointerList) { - pTrack->resetPlayCounter(); - } - - -} - void WTrackMenu::slotPopulatePlaylistMenu() { // The user may open the Playlist submenu, move their cursor away, then // return to the Playlist submenu before exiting the track context menu. @@ -909,7 +883,6 @@ void WTrackMenu::addSelectionToPlaylist(int iPlaylistId) { playlistDao.appendTracksToPlaylist(trackIds, iPlaylistId); } - void WTrackMenu::slotPopulateCrateMenu() { // The user may open the Crate submenu, move their cursor away, then // return to the Crate submenu before exiting the track context menu. @@ -971,7 +944,6 @@ void WTrackMenu::slotPopulateCrateMenu() { m_bCrateMenuLoaded = true; } - void WTrackMenu::updateSelectionCrates(QWidget* pWidget) { auto pCheckBox = qobject_cast(pWidget); VERIFY_OR_DEBUG_ASSERT(pCheckBox) { @@ -1022,19 +994,6 @@ void WTrackMenu::addSelectionToNewCrate() { } } -void WTrackMenu::setTrackModel(TrackModel* trackModel) { - m_pTrackModel = trackModel; - - // Move this to another function - m_iCoverSourceColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_SOURCE); - m_iCoverTypeColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_TYPE); - m_iCoverLocationColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_LOCATION); - m_iCoverHashColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART_HASH); - m_iCoverColumn = trackModel->fieldIndex(LIBRARYTABLE_COVERART); - m_iTrackLocationColumn = trackModel->fieldIndex(TRACKLOCATIONSTABLE_LOCATION); -} - - void WTrackMenu::slotLockBpm() { lockBpm(true); } @@ -1058,7 +1017,6 @@ void WTrackMenu::slotScaleBpm(int scale) { } } - void WTrackMenu::lockBpm(bool lock) { if (m_pTrackPointerList.empty()) { return; @@ -1110,6 +1068,19 @@ void WTrackMenu::loadSelectionToGroup(QString group, bool play) { } } +//slot for reset played count, sets count to 0 of one or more tracks +void WTrackMenu::slotClearPlayCount() { + if (m_pTrackPointerList.empty()) { + return; + } + + for (const auto& pTrack : m_pTrackPointerList) { + pTrack->resetPlayCounter(); + } + + +} + void WTrackMenu::slotClearBeats() { if (m_pTrackPointerList.empty()) { return; @@ -1206,7 +1177,6 @@ void WTrackMenu::slotClearHotCues() { } } - void WTrackMenu::slotClearAllMetadata() { slotClearBeats(); slotClearPlayCount(); @@ -1220,37 +1190,6 @@ void WTrackMenu::slotClearAllMetadata() { slotClearWaveform(); } - - -void WTrackMenu::slotRemove() { - DEBUG_ASSERT(m_pTrackModel); - TrackModel* trackModel = m_pTrackModel; - if (!trackModel) { - return; - } - QModelIndexList indices = m_pSelectedTrackIndices; - if (!indices.empty()) { - trackModel->removeTracks(indices); - } -} - - - -void WTrackMenu::slotHide() { - DEBUG_ASSERT(m_pTrackModel); - TrackModel* trackModel = m_pTrackModel; - if (!trackModel) { - return; - } - QModelIndexList indices = m_pSelectedTrackIndices; - if (indices.size() > 0) { - trackModel->hideTracks(indices); - } -} - - - - void WTrackMenu::slotTrackInfoClosed() { DlgTrackInfo* pTrackInfo = m_pTrackInfo.take(); // We are in a slot directly invoked from DlgTrackInfo. Delete it @@ -1409,7 +1348,6 @@ void WTrackMenu::slotShowDlgTagFetcher() { } } - void WTrackMenu::slotAddToAutoDJBottom() { // append to auto DJ addToAutoDJ(PlaylistDAO::AutoDJSendLoc::BOTTOM); @@ -1440,7 +1378,6 @@ void WTrackMenu::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { playlistDao.addTracksToAutoDJQueue(trackIds, loc); } - void WTrackMenu::slotCoverInfoSelected(const CoverInfoRelative& coverInfo) { if (m_pTrackPointerList.empty()) { return; @@ -1457,27 +1394,51 @@ void WTrackMenu::slotReloadCoverArt() { guessTrackCoverInfoConcurrently(m_pTrackPointerList); } -void WTrackMenu::slotPurge() { +void WTrackMenu::slotRemove() { DEBUG_ASSERT(m_pTrackModel); + TrackModel* trackModel = m_pTrackModel; + if (!trackModel) { + return; + } + QModelIndexList indices = m_pSelectedTrackIndices; + if (!indices.empty()) { + trackModel->removeTracks(indices); + } +} + +void WTrackMenu::slotHide() { + DEBUG_ASSERT(m_pTrackModel); + TrackModel* trackModel = m_pTrackModel; + if (!trackModel) { + return; + } + QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.size() > 0) { + trackModel->hideTracks(indices); + } +} + +void WTrackMenu::slotUnhide() { if (m_pTrackModel) { QModelIndexList indices = m_pSelectedTrackIndices; + if (indices.size() > 0) { TrackModel* trackModel = m_pTrackModel; if (trackModel) { - trackModel->purgeTracks(indices); + trackModel->unhideTracks(indices); } } } } -void WTrackMenu::slotUnhide() { +void WTrackMenu::slotPurge() { + DEBUG_ASSERT(m_pTrackModel); if (m_pTrackModel) { QModelIndexList indices = m_pSelectedTrackIndices; - if (indices.size() > 0) { TrackModel* trackModel = m_pTrackModel; if (trackModel) { - trackModel->unhideTracks(indices); + trackModel->purgeTracks(indices); } } } @@ -1489,9 +1450,17 @@ void WTrackMenu::clearTrackSelection() { m_pSelectedTrackIndices.clear(); } -bool WTrackMenu::optionIsEnabled(Filter flag) { +bool WTrackMenu::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const { + if (!m_pTrackModel) { + return false; + } + TrackModel* trackModel = m_pTrackModel; + return trackModel && + (trackModel->getCapabilities() & capabilities) == capabilities; +} + +bool WTrackMenu::optionIsEnabled(Filter flag) const { bool optionIsAvailable = m_eFilters.testFlag(Filter::None) || m_eFilters.testFlag(flag); - bool optionIsValid = false; if (!optionIsAvailable) { return false; @@ -1524,25 +1493,20 @@ bool WTrackMenu::optionIsEnabled(Filter flag) { if (flag == Filter::AutoDJ) { return true; } - else { - return false; - } + return false; } - else { - optionIsValid = true; - if (flag == Filter::AutoDJ) { - optionIsValid = modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ); - } else if (flag == Filter::Remove) { - optionIsValid = modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE) || - modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST) || - modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE); - } else if (flag == Filter::HideUnhidePurge) { - optionIsValid = modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE) || - modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE) || - modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE); - } + if (flag == Filter::AutoDJ) { + return modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ); + } else if (flag == Filter::Remove) { + return modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_PLAYLIST) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE_CRATE); + } else if (flag == Filter::HideUnhidePurge) { + return modelHasCapabilities(TrackModel::TRACKMODELCAPS_HIDE) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_UNHIDE) || + modelHasCapabilities(TrackModel::TRACKMODELCAPS_PURGE); } } - return optionIsValid; + return true; } diff --git a/src/widget/wtrackmenu.h b/src/widget/wtrackmenu.h index b9f572af0014..ea5b943be956 100644 --- a/src/widget/wtrackmenu.h +++ b/src/widget/wtrackmenu.h @@ -4,29 +4,20 @@ #include #include #include -#include -#include + +#include "control/controlproxy.h" +#include "library/dao/playlistdao.h" +#include "library/dlgtagfetcher.h" +#include "library/dlgtrackinfo.h" #include "library/trackcollectionmanager.h" #include "library/trackmodel.h" -#include "track/track.h" -#include "library/dao/playlistdao.h" #include "preferences/usersettings.h" -#include "widget/wlibrarytableview.h" -#include "control/controlproxy.h" - -#include "widget/wcoverartmenu.h" #include "widget/wcolorpickeraction.h" +#include "widget/wcoverartmenu.h" typedef QList TrackIdList; typedef QList TrackPointerList; -class ControlProxy; -class WCoverArtMenu; -class DlgTagFetcher; -class DlgTrackInfo; -class TrackCollectionManager; -class ExternalTrackCollection; - class WTrackMenu : public QMenu { Q_OBJECT public: @@ -55,15 +46,14 @@ class WTrackMenu : public QMenu { void setTrackIndexList(QModelIndexList indexList); void setTrackModel(TrackModel* trackModel); - - private slots: + // File void slotOpenInFileBrowser(); - void slotLockBpm(); - void slotUnlockBpm(); - void slotScaleBpm(int); + + // Row color void slotColorPicked(mixxx::RgbColor::optional_t color); + // Reset void slotClearBeats(); void slotClearPlayCount(); void slotClearMainCue(); @@ -76,54 +66,58 @@ class WTrackMenu : public QMenu { void slotClearWaveform(); void slotClearAllMetadata(); - void lockBpm(bool lock); + // BPM + void slotLockBpm(); + void slotUnlockBpm(); + void slotScaleBpm(int); + + // Info and metadata void slotNextTrackInfo(); void slotNextDlgTagFetcher(); void slotPrevTrackInfo(); void slotPrevDlgTagFetcher(); void slotShowTrackInTagFetcher(TrackPointer track); - - void slotPopulatePlaylistMenu(); - void slotPopulateCrateMenu(); - void addSelectionToNewCrate(); - - void slotImportTrackMetadataFromFileTags(); - void slotExportTrackMetadataIntoFileTags(); - void slotUpdateExternalTrackCollection(ExternalTrackCollection *externalTrackCollection); - void slotRemove(); - void slotHide(); void slotTrackInfoClosed(); void slotTagFetcherClosed(); void slotShowTrackInfo(); void slotShowDlgTagFetcher(); + void slotImportTrackMetadataFromFileTags(); + void slotExportTrackMetadataIntoFileTags(); + void slotUpdateExternalTrackCollection(ExternalTrackCollection *externalTrackCollection); + // Playlist and crate + void slotPopulatePlaylistMenu(); + void slotPopulateCrateMenu(); + void addSelectionToNewCrate(); + + // Auto DJ void slotAddToAutoDJBottom(); void slotAddToAutoDJTop(); void slotAddToAutoDJReplace(); + // Cover void slotCoverInfoSelected(const CoverInfoRelative &coverInfo); - - void slotReloadCoverArt(); + // Library management + void slotRemove(); + void slotHide(); void slotUnhide(); - void slotPurge(); + public: signals: void loadTrackToPlayer(TrackPointer pTrack, QString group, bool play = false); private: + void teardownActions(); void constructMenus(); void createActions(); void setupActions(); - void trackIdsToTrackPointers(); - void setTrackPointerList(TrackPointerList trackPointerList); - TrackPointerList getTrackPointerList(); bool modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const; - bool optionIsEnabled(Filter flag); + bool optionIsEnabled(Filter flag) const; void addSelectionToPlaylist(int iPlaylistId); void updateSelectionCrates(QWidget* pWidget); @@ -133,24 +127,26 @@ class WTrackMenu : public QMenu { void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc); - void teardownActions(); - + void lockBpm(bool lock); void loadSelectionToGroup(QString group, bool play = false); void clearTrackSelection(); + // Selected tracks + TrackIdList m_pTrackIdList; + TrackPointerList m_pTrackPointerList; + QModelIndexList m_pSelectedTrackIndices; + + TrackModel* m_pTrackModel{}; + ControlProxy* m_pNumSamplers{}; ControlProxy* m_pNumDecks{}; ControlProxy* m_pNumPreviewDecks{}; - // The selected tracks for which the context menu is created - TrackIdList m_pTrackIdList; - - // Context menu machinery + // Submenus QMenu *m_pLoadToMenu{}; QMenu *m_pDeckMenu{}; QMenu *m_pSamplerMenu{}; - QMenu *m_pPlaylistMenu{}; QMenu *m_pCrateMenu{}; QMenu *m_pMetadataMenu{}; @@ -158,8 +154,6 @@ class WTrackMenu : public QMenu { QMenu *m_pClearMetadataMenu{}; QMenu *m_pBPMMenu{}; QMenu *m_pColorMenu{}; - - WCoverArtMenu* m_pCoverMenu{}; // Reload Track Metadata Action: @@ -187,6 +181,8 @@ class WTrackMenu : public QMenu { // Show track-editor action QAction *m_pPropertiesAct{}; + + // Open file in default file browser QAction *m_pFileBrowserAct{}; // BPM feature @@ -215,14 +211,8 @@ class WTrackMenu : public QMenu { QAction* m_pClearReplayGainAction{}; QAction* m_pClearAllMetadataAction{}; - const UserSettingsPointer m_pConfig; - TrackCollectionManager* m_pTrackCollectionManager; - TrackPointerList m_pTrackPointerList; - TrackModel* m_pTrackModel{}; - - QModelIndexList m_pSelectedTrackIndices; QScopedPointer m_pTrackInfo; QScopedPointer m_pTagFetcher; @@ -234,7 +224,7 @@ class WTrackMenu : public QMenu { QAction* action{}; }; QList m_updateInExternalTrackCollections; - + bool m_bPlaylistMenuLoaded; bool m_bCrateMenuLoaded; @@ -246,6 +236,7 @@ class WTrackMenu : public QMenu { int m_iCoverColumn; // visible cover art int m_iTrackLocationColumn; + // Filter available options Filters m_eFilters; }; diff --git a/src/widget/wtrackproperty.cpp b/src/widget/wtrackproperty.cpp index ba4082997ca1..3a95a9cd8d09 100644 --- a/src/widget/wtrackproperty.cpp +++ b/src/widget/wtrackproperty.cpp @@ -1,26 +1,9 @@ -#include "widget/wtrackproperty.h" - -// Qt includes -#include -#include -#include -#include -#include +#include +#include -// std includes -#include - -// Project includes #include "control/controlobject.h" -#include -#include "library/dao/playlistdao.h" -#include "library/trackcollection.h" -#include "library/trackcollectionmanager.h" -#include "library/trackmodel.h" -#include "track/track.h" +#include "widget/wtrackproperty.h" #include "util/dnd.h" -#include "util/desktophelper.h" -#include WTrackProperty::WTrackProperty(const char* group, UserSettingsPointer pConfig, diff --git a/src/widget/wtrackproperty.h b/src/widget/wtrackproperty.h index 7d208ae81c4f..7332bbfcfe74 100644 --- a/src/widget/wtrackproperty.h +++ b/src/widget/wtrackproperty.h @@ -23,7 +23,6 @@ class WTrackProperty : public WLabel, public TrackDropTarget { void setup(const QDomNode& node, const SkinContext& context) override; void contextMenuEvent(QContextMenuEvent * event) override; - QList getSelectedTrackIds() const; signals: void trackDropped(QString filename, QString group) override; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 93f14d6ea7ab..d60cf7940ad6 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1,8 +1,6 @@ #include "widget/wtracktableview.h" -#include #include -#include #include #include #include @@ -10,10 +8,7 @@ #include #include "control/controlobject.h" -#include "control/controlproxy.h" -#include "library/crate/cratefeaturehelper.h" #include "library/dao/trackschema.h" -#include "library/externaltrackcollection.h" #include "library/librarytablemodel.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" @@ -27,8 +22,6 @@ #include "util/time.h" #include "widget/wtracktableviewheader.h" -#include - WTrackTableView::WTrackTableView(QWidget * parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager, @@ -42,8 +35,6 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_selectionChangedSinceLastGuiTick(true), m_loadCachedOnly(false) { - - // Connect slots and signals to make the world go 'round. connect(this, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(slotMouseDoubleClicked(const QModelIndex &))); @@ -310,6 +301,9 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { restoreVScrollBarPos(newModel); // restoring scrollBar position using model pointer as key // scrollbar positions with respect to different models are backed by map + + // Set the track model in context menu widget. + m_pMenu->setTrackModel(getTrackModel()); } // slot @@ -341,7 +335,6 @@ void WTrackTableView::slotMouseDoubleClicked(const QModelIndex &index) { } } - void WTrackTableView::loadSelectionToGroup(QString group, bool play) { QModelIndexList indices = selectionModel()->selectedRows(); if (indices.size() > 0) { @@ -365,8 +358,6 @@ void WTrackTableView::loadSelectionToGroup(QString group, bool play) { } } - - void WTrackTableView::slotPurge() { QModelIndexList indices = selectionModel()->selectedRows(); if (indices.size() > 0) { @@ -389,9 +380,6 @@ void WTrackTableView::slotUnhide() { } void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { - // Set the track model - m_pMenu->setTrackModel(getTrackModel()); - // Update track indices in context menu QModelIndexList indices = selectionModel()->selectedRows(); m_pMenu->setTrackIndexList(indices); @@ -678,6 +666,7 @@ void WTrackTableView::dropEvent(QDropEvent * event) { updateGeometries(); verticalScrollBar()->setValue(vScrollBarPos); } + TrackModel* WTrackTableView::getTrackModel() const { TrackModel* trackModel = dynamic_cast(model()); return trackModel; @@ -711,8 +700,6 @@ void WTrackTableView::loadSelectedTrackToGroup(QString group, bool play) { loadSelectionToGroup(group, play); } - - QList WTrackTableView::getSelectedTrackIds() const { QList trackIds; @@ -768,7 +755,6 @@ void WTrackTableView::setSelectedTracks(const QList& trackIds) { } } - void WTrackTableView::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { return; @@ -787,7 +773,6 @@ void WTrackTableView::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { playlistDao.addTracksToAutoDJQueue(trackIds, loc); } - void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder) { TrackModel* trackModel = getTrackModel(); QAbstractItemModel* itemModel = model(); @@ -870,9 +855,6 @@ void WTrackTableView::applySorting() { doSortByColumn(sortColumn, sortOrder); } - - - void WTrackTableView::slotSortingChanged(int headerSection, Qt::SortOrder order) { double sortOrder = static_cast(order); diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 5b773d0b38b2..a7426dc9c6ce 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -4,13 +4,12 @@ #include #include -#include "preferences/usersettings.h" #include "control/controlproxy.h" #include "library/dao/playlistdao.h" #include "library/trackmodel.h" // Can't forward declare enums +#include "preferences/usersettings.h" #include "track/track.h" #include "util/duration.h" -#include "widget/wcolorpickeraction.h" #include "widget/wlibrarytableview.h" #include "widget/wtrackmenu.h" @@ -18,13 +17,11 @@ class ControlProxy; class DlgTagFetcher; class DlgTrackInfo; class TrackCollectionManager; - class ExternalTrackCollection; const QString WTRACKTABLEVIEW_VSCROLLBARPOS_KEY = "VScrollBarPos"; /** ConfigValue key for QTable vertical scrollbar position */ const QString LIBRARY_CONFIGVALUE = "[Library]"; /** ConfigValue "value" (wtf) for library stuff */ - class WTrackTableView : public WLibraryTableView { Q_OBJECT public: @@ -52,9 +49,7 @@ class WTrackTableView : public WLibraryTableView { void slotUnhide(); void slotPurge(); - private slots: - void loadSelectionToGroup(QString group, bool play = false); void doSortByColumn(int headerSection, Qt::SortOrder sortOrder); void applySortingIfVisible(); @@ -83,6 +78,8 @@ class WTrackTableView : public WLibraryTableView { // Returns the current TrackModel, or returns NULL if none is set. TrackModel* getTrackModel() const; + + // Check if an operation is allowed on a model. bool modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) const; const UserSettingsPointer m_pConfig; From ba13ac6e0b32ab05d578d15296ca7bb28025247a Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 5 Apr 2020 09:58:24 -0500 Subject: [PATCH 189/393] ColorPaletteEditor: check if theme has list-add/list-remove icons --- src/preferences/colorpaletteeditor.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index 0a66cdafceaf..d6b1fc58c0b3 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -31,7 +31,12 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent, bool showHotcueNumbers) QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); pColorButtonLayout->addWidget(pExpander); - m_pRemoveColorButton = new QPushButton(QIcon::fromTheme("list-remove"), "", this); + QIcon removeIcon = QIcon::fromTheme("list-remove", QIcon()); + if (!removeIcon.isNull()) { + m_pRemoveColorButton = new QPushButton(removeIcon, "", this); + } else { + m_pRemoveButton = new QPushButton("-", this); + } m_pRemoveColorButton->setFixedWidth(32); m_pRemoveColorButton->setToolTip(tr("Remove Color")); m_pRemoveColorButton->setDisabled(true); @@ -41,7 +46,12 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent, bool showHotcueNumbers) this, &ColorPaletteEditor::slotRemoveColor); - m_pAddColorButton = new QPushButton(QIcon::fromTheme("list-add"), "", this); + QIcon addIcon = QIcon::fromTheme("list-add", QIcon()); + if (!addIcon.isNull()) { + m_pAddColorButton = new QPushButton(addIcon, "", this); + } else { + m_pAddColorButton = new QPushButton("+", this); + } m_pAddColorButton->setFixedWidth(32); m_pAddColorButton->setToolTip(tr("Add Color")); pColorButtonLayout->addWidget(m_pAddColorButton); From 5fe4c858eb5bbf0948bf8eadfc850e12936d7490 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 5 Apr 2020 10:26:27 -0500 Subject: [PATCH 190/393] update macOS build environment to Qt 5.14.1 --- build/osx/golden_environment | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/osx/golden_environment b/build/osx/golden_environment index 4cefc5cced12..f1d2db26b86b 100644 --- a/build/osx/golden_environment +++ b/build/osx/golden_environment @@ -1 +1 @@ -2.3-j00004-497fe02e-osx10.11-x86_64-release +2.3-j00006-b887bce2-osx10.11-x86_64-release From cfc3fa3f094370e010cad8050726f694d513bfa3 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sun, 5 Apr 2020 21:46:22 +0200 Subject: [PATCH 191/393] Fix wrong debug assertion --- src/track/track.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/track/track.cpp b/src/track/track.cpp index 833a5ad64f1e..29c5541e12cd 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -837,7 +837,10 @@ void Track::setCuePointsMarkDirtyAndUnlock( QMutexLocker* pLock, const QList& cuePoints) { DEBUG_ASSERT(pLock); - DEBUG_ASSERT(m_importCuesPending.isEmpty()); + // Prevent inconsistencies between cue infos that have been queued + // and are waiting to be imported and new cue points. At least one + // of these two collections must be empty. + DEBUG_ASSERT(cuePoints.isEmpty() || m_importCuesPending.isEmpty()); // disconnect existing cue points for (const auto& pCue: m_cuePoints) { disconnect(pCue.get(), 0, this, 0); From d2411ad88533bee19ef04f94d4c5649a4487d8a5 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 5 Apr 2020 15:08:23 -0500 Subject: [PATCH 192/393] ColorPaletteEditor: fix segfault --- src/preferences/colorpaletteeditor.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/preferences/colorpaletteeditor.cpp b/src/preferences/colorpaletteeditor.cpp index d6b1fc58c0b3..ca2004c97202 100644 --- a/src/preferences/colorpaletteeditor.cpp +++ b/src/preferences/colorpaletteeditor.cpp @@ -31,11 +31,9 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent, bool showHotcueNumbers) QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); pColorButtonLayout->addWidget(pExpander); - QIcon removeIcon = QIcon::fromTheme("list-remove", QIcon()); - if (!removeIcon.isNull()) { - m_pRemoveColorButton = new QPushButton(removeIcon, "", this); - } else { - m_pRemoveButton = new QPushButton("-", this); + m_pRemoveColorButton = new QPushButton(QIcon::fromTheme("list-remove"), "", this); + if (m_pRemoveColorButton->icon().isNull()) { + m_pRemoveColorButton->setText("-"); } m_pRemoveColorButton->setFixedWidth(32); m_pRemoveColorButton->setToolTip(tr("Remove Color")); @@ -46,11 +44,9 @@ ColorPaletteEditor::ColorPaletteEditor(QWidget* parent, bool showHotcueNumbers) this, &ColorPaletteEditor::slotRemoveColor); - QIcon addIcon = QIcon::fromTheme("list-add", QIcon()); - if (!addIcon.isNull()) { - m_pAddColorButton = new QPushButton(addIcon, "", this); - } else { - m_pAddColorButton = new QPushButton("+", this); + m_pAddColorButton = new QPushButton(QIcon::fromTheme("list-add"), "", this); + if (m_pAddColorButton->icon().isNull()) { + m_pAddColorButton->setText("+"); } m_pAddColorButton->setFixedWidth(32); m_pAddColorButton->setToolTip(tr("Add Color")); From c408b494dd959294c7d25ab80b5b421bba09570e Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 6 Apr 2020 02:41:15 +0200 Subject: [PATCH 193/393] library feature buttons accept click focus only --- src/library/autodj/dlgautodj.ui | 27 ++++++++++++++++++++++++++- src/library/dlganalysis.ui | 12 ++++++++++++ src/library/dlghidden.ui | 9 +++++++++ src/library/dlgmissing.ui | 6 ++++++ src/library/recording/dlgrecording.ui | 3 +++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/dlgautodj.ui b/src/library/autodj/dlgautodj.ui index 6af6d6de2a0e..ab549bd9e435 100644 --- a/src/library/autodj/dlgautodj.ui +++ b/src/library/autodj/dlgautodj.ui @@ -48,6 +48,9 @@ + + Qt::NoFocus + 0 @@ -77,6 +80,9 @@ + + Qt::NoFocus + 0 @@ -87,6 +93,9 @@ + + Qt::NoFocus + 0 @@ -99,10 +108,17 @@ - + + + Qt::ClickFocus + + + + Qt::ClickFocus + 0 @@ -145,6 +161,9 @@ + + Qt::NoFocus + 0 @@ -158,6 +177,9 @@ + + Qt::NoFocus + 0 @@ -168,6 +190,9 @@ + + Qt::NoFocus + 0 diff --git a/src/library/dlganalysis.ui b/src/library/dlganalysis.ui index e07bc1c92a56..e5d1bf1eb7ef 100644 --- a/src/library/dlganalysis.ui +++ b/src/library/dlganalysis.ui @@ -48,6 +48,9 @@ + + Qt::NoFocus + Shows tracks added to the library within the last 7 days. @@ -58,6 +61,9 @@ + + Qt::NoFocus + Shows all tracks in the library. @@ -68,6 +74,9 @@ + + Qt::NoFocus + Selects all tracks in the table below. @@ -78,6 +87,9 @@ + + Qt::NoFocus + Runs beatgrid, key, and ReplayGain detection on the selected tracks. Does not generate waveforms for the selected tracks to save disk space. diff --git a/src/library/dlghidden.ui b/src/library/dlghidden.ui index 44dc009dcbbc..7e8f3d47d66e 100644 --- a/src/library/dlghidden.ui +++ b/src/library/dlghidden.ui @@ -48,6 +48,9 @@ + + Qt::NoFocus + Selects all tracks in the table below. @@ -58,6 +61,9 @@ + + Qt::NoFocus + Purge selected tracks from the library. @@ -71,6 +77,9 @@ + + Qt::NoFocus + Unhide selected tracks from the library. diff --git a/src/library/dlgmissing.ui b/src/library/dlgmissing.ui index 93cf2d84f5e8..b65658eaeb88 100644 --- a/src/library/dlgmissing.ui +++ b/src/library/dlgmissing.ui @@ -48,6 +48,9 @@ + + Qt::NoFocus + Selects all tracks in the table below. @@ -58,6 +61,9 @@ + + Qt::NoFocus + Purge selected tracks from the library. diff --git a/src/library/recording/dlgrecording.ui b/src/library/recording/dlgrecording.ui index c0b0fd90e517..8f3748b88f01 100644 --- a/src/library/recording/dlgrecording.ui +++ b/src/library/recording/dlgrecording.ui @@ -48,6 +48,9 @@ + + Qt::NoFocus + Start Recording From 12a9aa573e2525249c9cdb91b21ea41bbdc5e400 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 6 Apr 2020 02:41:56 +0200 Subject: [PATCH 194/393] add focus border to WTextBrowser --- res/skins/Deere/style.qss | 3 ++- res/skins/LateNight/style.qss | 3 ++- res/skins/Shade/style.qss | 3 ++- res/skins/Shade/style_dark.qss | 3 ++- res/skins/Tango/style.qss | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/res/skins/Deere/style.qss b/res/skins/Deere/style.qss index 505d62f51d7c..fbe3f43b6f28 100644 --- a/res/skins/Deere/style.qss +++ b/res/skins/Deere/style.qss @@ -228,7 +228,8 @@ } #LibraryContainer QTableView:focus, -#LibraryContainer QTreeView:focus { +#LibraryContainer QTreeView:focus, +#LibraryContainer QTextBrowser:focus { border: 1px solid #FF6600; } diff --git a/res/skins/LateNight/style.qss b/res/skins/LateNight/style.qss index 97c0a547b37b..17667f8e06a5 100644 --- a/res/skins/LateNight/style.qss +++ b/res/skins/LateNight/style.qss @@ -2040,7 +2040,8 @@ WLibrary, } #LibraryContainer QTableView:focus, -#LibraryContainer QTreeView:focus { +#LibraryContainer QTreeView:focus, +#LibraryContainer QTextBrowser:focus { border: 1px solid #ff6600; } diff --git a/res/skins/Shade/style.qss b/res/skins/Shade/style.qss index 623624f6b901..71a26e6bca6c 100644 --- a/res/skins/Shade/style.qss +++ b/res/skins/Shade/style.qss @@ -493,8 +493,9 @@ WSearchLineEdit { Defined by SearchBox */ margin: 0px; } + #LibraryContainer QTableView:focus, #LibraryContainer QTreeView:focus, - #LibraryContainer QTableView:focus { /* + #LibraryContainer QTextBrowser:focus { /* New Library navigation COs only work if TreeView or TableView have focus. Clicking on buttons, sliders and visuals elsewhere removes focus from Library. In conjunction with [Library],MoveFocusBackward/..Forward, this helps a lot. */ diff --git a/res/skins/Shade/style_dark.qss b/res/skins/Shade/style_dark.qss index 00e54960c1d6..335b2ba9f12b 100644 --- a/res/skins/Shade/style_dark.qss +++ b/res/skins/Shade/style_dark.qss @@ -94,7 +94,8 @@ WCoverArtMenu::item { #LibraryContainer QTableView:focus, -#LibraryContainer QTreeView:focus { +#LibraryContainer QTreeView:focus, +#LibraryContainer QTextBrowser:focus { border: 1px solid #78C70B; } diff --git a/res/skins/Tango/style.qss b/res/skins/Tango/style.qss index fd41769564f3..f925fe4386f4 100644 --- a/res/skins/Tango/style.qss +++ b/res/skins/Tango/style.qss @@ -2415,8 +2415,9 @@ WCoverArtMenu::item:!enabled, Shift WSearchLineEdit instead */ margin: 0px; } + #LibraryContainer QTableView:focus, #LibraryContainer QTreeView:focus, - #LibraryContainer QTableView:focus { /* + #LibraryContainer QTextBrowser:focus { /* New Library navigation COs only work if TreeView or TableView have focus. Clicking on buttons, sliders and visuals elsewhere removes focus from Library. In conjunction with [Library],MoveFocusBackward/..Forward, some highlight From a3db3a3ea2f70891904f196ff8bf0108998a28ce Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 6 Apr 2020 14:16:57 +0200 Subject: [PATCH 195/393] Avoid string conversion for debug formatting --- src/audio/types.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio/types.cpp b/src/audio/types.cpp index f7addcc954d3..3d01580fbd9c 100644 --- a/src/audio/types.cpp +++ b/src/audio/types.cpp @@ -30,13 +30,13 @@ QDebug operator<<(QDebug dbg, SampleLayout arg) { QDebug operator<<(QDebug dbg, SampleRate arg) { return dbg - << QString::number(arg).toLocal8Bit().constData() + << static_cast(arg) << SampleRate::unit(); } QDebug operator<<(QDebug dbg, Bitrate arg) { return dbg - << QString::number(arg).toLocal8Bit().constData() + << static_cast(arg) << Bitrate::unit(); } From 8b88fb3cddbb6ad502edc4f19297d00ff788186f Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 6 Apr 2020 14:18:52 +0200 Subject: [PATCH 196/393] Rename getter and corresponding member --- .../bufferscalers/enginebufferscale.cpp | 10 ++-- src/engine/bufferscalers/enginebufferscale.h | 6 +- .../bufferscalers/enginebufferscalelinear.cpp | 60 +++++++++---------- .../enginebufferscalerubberband.cpp | 16 ++--- .../bufferscalers/enginebufferscalest.cpp | 14 ++--- src/engine/engine.h | 10 ++-- 6 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/engine/bufferscalers/enginebufferscale.cpp b/src/engine/bufferscalers/enginebufferscale.cpp index b250c48f37eb..028c30a3d467 100644 --- a/src/engine/bufferscalers/enginebufferscale.cpp +++ b/src/engine/bufferscalers/enginebufferscale.cpp @@ -4,7 +4,7 @@ #include "util/defs.h" EngineBufferScale::EngineBufferScale() - : m_audioSignal( + : m_outputSignal( mixxx::audio::SignalInfo( mixxx::kEngineChannelCount, mixxx::audio::SampleRate(), @@ -13,15 +13,15 @@ EngineBufferScale::EngineBufferScale() m_bSpeedAffectsPitch(false), m_dTempoRatio(1.0), m_dPitchRatio(1.0) { - DEBUG_ASSERT(!m_audioSignal.isValid()); + DEBUG_ASSERT(!m_outputSignal.isValid()); } void EngineBufferScale::setSampleRate( mixxx::audio::SampleRate sampleRate) { DEBUG_ASSERT(sampleRate.isValid()); - if (sampleRate != m_audioSignal.getSampleRate()) { - m_audioSignal.setSampleRate(sampleRate); + if (sampleRate != m_outputSignal.getSampleRate()) { + m_outputSignal.setSampleRate(sampleRate); onSampleRateChanged(); } - DEBUG_ASSERT(m_audioSignal.isValid()); + DEBUG_ASSERT(m_outputSignal.isValid()); } diff --git a/src/engine/bufferscalers/enginebufferscale.h b/src/engine/bufferscalers/enginebufferscale.h index f8ba5d827ed6..ced4530fab9e 100644 --- a/src/engine/bufferscalers/enginebufferscale.h +++ b/src/engine/bufferscalers/enginebufferscale.h @@ -51,8 +51,8 @@ class EngineBufferScale : public QObject { void setSampleRate( mixxx::audio::SampleRate sampleRate); - const mixxx::audio::SignalInfo& getAudioSignal() const { - return m_audioSignal; + const mixxx::audio::SignalInfo& getOutputSignal() const { + return m_outputSignal; } // Called from EngineBuffer when seeking, to ensure the buffers are flushed */ @@ -69,7 +69,7 @@ class EngineBufferScale : public QObject { SINT iOutputBufferSize) = 0; private: - mixxx::audio::SignalInfo m_audioSignal; + mixxx::audio::SignalInfo m_outputSignal; virtual void onSampleRateChanged() = 0; diff --git a/src/engine/bufferscalers/enginebufferscalelinear.cpp b/src/engine/bufferscalers/enginebufferscalelinear.cpp index e268cfee044f..89e19a2a60f1 100644 --- a/src/engine/bufferscalers/enginebufferscalelinear.cpp +++ b/src/engine/bufferscalers/enginebufferscalelinear.cpp @@ -79,11 +79,11 @@ double EngineBufferScaleLinear::scaleBuffer( // first half: rate goes from old rate to zero m_dOldRate = rate_add_old; m_dRate = 0.0; - frames_read += do_scale(pOutputBuffer, getAudioSignal().samples2frames(iOutputBufferSize)); + frames_read += do_scale(pOutputBuffer, getOutputSignal().samples2frames(iOutputBufferSize)); // reset m_floorSampleOld in a way as we were coming from // the other direction - SINT iNextSample = getAudioSignal().frames2samples(static_cast(ceil(m_dNextFrame))); + SINT iNextSample = getOutputSignal().frames2samples(static_cast(ceil(m_dNextFrame))); if (iNextSample + 1 < m_bufferIntSize) { m_floorSampleOld[0] = m_bufferInt[iNextSample]; m_floorSampleOld[1] = m_bufferInt[iNextSample + 1]; @@ -91,19 +91,19 @@ double EngineBufferScaleLinear::scaleBuffer( // if the buffer has extra samples, do a read so RAMAN ends up back where // it should be - SINT iCurSample = getAudioSignal().frames2samples(static_cast(ceil(m_dCurrentFrame))); - SINT extra_samples = m_bufferIntSize - iCurSample - getAudioSignal().getChannelCount(); + SINT iCurSample = getOutputSignal().frames2samples(static_cast(ceil(m_dCurrentFrame))); + SINT extra_samples = m_bufferIntSize - iCurSample - getOutputSignal().getChannelCount(); if (extra_samples > 0) { - if (extra_samples % getAudioSignal().getChannelCount() != 0) { + if (extra_samples % getOutputSignal().getChannelCount() != 0) { // extra samples should include the whole frame - extra_samples -= extra_samples % getAudioSignal().getChannelCount(); - extra_samples += getAudioSignal().getChannelCount(); + extra_samples -= extra_samples % getOutputSignal().getChannelCount(); + extra_samples += getOutputSignal().getChannelCount(); } //qDebug() << "extra samples" << extra_samples; SINT next_samples_read = m_pReadAheadManager->getNextSamples( rate_add_new, m_bufferInt, extra_samples); - frames_read += getAudioSignal().samples2frames(next_samples_read); + frames_read += getOutputSignal().samples2frames(next_samples_read); } // force a buffer read: m_bufferIntSize = 0; @@ -116,8 +116,8 @@ double EngineBufferScaleLinear::scaleBuffer( m_dOldRate = 0.0; m_dRate = rate_add_new; // pass the address of the frame at the halfway point - SINT frameOffset = getAudioSignal().samples2frames(iOutputBufferSize) / 2; - SINT sampleOffset = getAudioSignal().frames2samples(frameOffset); + SINT frameOffset = getOutputSignal().samples2frames(iOutputBufferSize) / 2; + SINT sampleOffset = getOutputSignal().frames2samples(frameOffset); frames_read += do_scale(pOutputBuffer + sampleOffset, iOutputBufferSize - sampleOffset); } else { frames_read += do_scale(pOutputBuffer, iOutputBufferSize); @@ -130,7 +130,7 @@ SINT EngineBufferScaleLinear::do_copy(CSAMPLE* buf, SINT buf_size) { CSAMPLE* write_buf = buf; // Use up what's left of the internal buffer. SINT iNextFrame = static_cast(ceil(m_dNextFrame)); - SINT iNextSample = math_max(getAudioSignal().frames2samples(iNextFrame), 0); + SINT iNextSample = math_max(getOutputSignal().frames2samples(iNextFrame), 0); SINT readSize = math_min(m_bufferIntSize - iNextSample, samples_needed); if (readSize > 0) { SampleUtil::copy(write_buf, &m_bufferInt[iNextSample], readSize); @@ -203,7 +203,7 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { // Simulate the loop to estimate how many frames we need double frames = 0; - const SINT bufferSizeFrames = getAudioSignal().samples2frames(buf_size); + const SINT bufferSizeFrames = getOutputSignal().samples2frames(buf_size); const double rate_delta = rate_diff / bufferSizeFrames; // use Gaussian sum formula (n(n+1))/2 for //for (int j = 0; j < bufferSizeFrames; ++j) { @@ -258,19 +258,19 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { floor_sample[1] = m_floorSampleOld[1]; ceil_sample[0] = m_bufferInt[0]; ceil_sample[1] = m_bufferInt[1]; - } else if (getAudioSignal().frames2samples(currentFrameFloor) + 3 < m_bufferIntSize) { + } else if (getOutputSignal().frames2samples(currentFrameFloor) + 3 < m_bufferIntSize) { // take floor_sample form the buffer of the previous run - floor_sample[0] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor)]; - floor_sample[1] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor) + 1]; - ceil_sample[0] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor) + 2]; - ceil_sample[1] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor) + 3]; + floor_sample[0] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor)]; + floor_sample[1] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor) + 1]; + ceil_sample[0] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor) + 2]; + ceil_sample[1] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor) + 3]; } else { // if we don't have the ceil_sample in buffer, load some more - if (getAudioSignal().frames2samples(currentFrameFloor) + 1 < m_bufferIntSize) { + if (getOutputSignal().frames2samples(currentFrameFloor) + 1 < m_bufferIntSize) { // take floor_sample form the buffer of the previous run - floor_sample[0] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor)]; - floor_sample[1] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor) + 1]; + floor_sample[0] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor)]; + floor_sample[1] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor) + 1]; } do { @@ -283,7 +283,7 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { SINT samples_to_read = math_min( kiLinearScaleReadAheadLength, - getAudioSignal().frames2samples(unscaled_frames_needed)); + getOutputSignal().frames2samples(unscaled_frames_needed)); m_bufferIntSize = m_pReadAheadManager->getNextSamples( rate_new == 0 ? rate_old : rate_new, @@ -297,13 +297,13 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { } } - frames_read += getAudioSignal().samples2frames(m_bufferIntSize); - unscaled_frames_needed -= getAudioSignal().samples2frames(m_bufferIntSize); + frames_read += getOutputSignal().samples2frames(m_bufferIntSize); + unscaled_frames_needed -= getOutputSignal().samples2frames(m_bufferIntSize); // adapt the m_dCurrentFrame the index of the new buffer - m_dCurrentFrame -= getAudioSignal().samples2frames(old_bufsize); + m_dCurrentFrame -= getOutputSignal().samples2frames(old_bufsize); currentFrameFloor = static_cast(floor(m_dCurrentFrame)); - } while (getAudioSignal().frames2samples(currentFrameFloor) + 3 >= m_bufferIntSize); + } while (getOutputSignal().frames2samples(currentFrameFloor) + 3 >= m_bufferIntSize); // I guess? if (read_failed_count > 1) { @@ -314,11 +314,11 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { // at the floor of our position. if (currentFrameFloor >= 0) { // the previous position is in the new buffer - floor_sample[0] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor)]; - floor_sample[1] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor) + 1]; + floor_sample[0] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor)]; + floor_sample[1] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor) + 1]; } - ceil_sample[0] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor) + 2]; - ceil_sample[1] = m_bufferInt[getAudioSignal().frames2samples(currentFrameFloor) + 3]; + ceil_sample[0] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor) + 2]; + ceil_sample[1] = m_bufferInt[getOutputSignal().frames2samples(currentFrameFloor) + 3]; } // For the current index, what percentage is it @@ -339,7 +339,7 @@ SINT EngineBufferScaleLinear::do_scale(CSAMPLE* buf, SINT buf_size) { // samples. This prevents the change from being discontinuous and helps // improve sound quality. rate_add += rate_delta_abs; - i += getAudioSignal().getChannelCount(); + i += getOutputSignal().getChannelCount(); } SampleUtil::clear(&buf[i], buf_size - i); diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.cpp b/src/engine/bufferscalers/enginebufferscalerubberband.cpp index c403d16bf6d8..2b88e9e28243 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.cpp +++ b/src/engine/bufferscalers/enginebufferscalerubberband.cpp @@ -98,13 +98,13 @@ void EngineBufferScaleRubberBand::setScaleParameters(double base_rate, } void EngineBufferScaleRubberBand::onSampleRateChanged() { - if (!getAudioSignal().isValid()) { + if (!getOutputSignal().isValid()) { m_pRubberBand.reset(); return; } m_pRubberBand = std::make_unique( - getAudioSignal().getSampleRate(), - getAudioSignal().getChannelCount(), + getOutputSignal().getSampleRate(), + getOutputSignal().getChannelCount(), RubberBandStretcher::OptionProcessRealTime); m_pRubberBand->setMaxProcessSize(kRubberBandBlockSize); // Setting the time ratio to a very high value will cause RubberBand @@ -159,7 +159,7 @@ double EngineBufferScaleRubberBand::scaleBuffer( SINT total_received_frames = 0; SINT total_read_frames = 0; - SINT remaining_frames = getAudioSignal().samples2frames(iOutputBufferSize); + SINT remaining_frames = getOutputSignal().samples2frames(iOutputBufferSize); CSAMPLE* read = pOutputBuffer; bool last_read_failed = false; bool break_out_after_retrieve_and_reset_rubberband = false; @@ -172,7 +172,7 @@ double EngineBufferScaleRubberBand::scaleBuffer( read, remaining_frames); remaining_frames -= received_frames; total_received_frames += received_frames; - read += getAudioSignal().frames2samples(received_frames); + read += getOutputSignal().frames2samples(received_frames); if (break_out_after_retrieve_and_reset_rubberband) { //qDebug() << "break_out_after_retrieve_and_reset_rubberband"; @@ -202,8 +202,8 @@ double EngineBufferScaleRubberBand::scaleBuffer( // are going forward or backward. (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio, m_buffer_back, - getAudioSignal().frames2samples(iLenFramesRequired)); - SINT iAvailFrames = getAudioSignal().samples2frames(iAvailSamples); + getOutputSignal().frames2samples(iLenFramesRequired)); + SINT iAvailFrames = getOutputSignal().samples2frames(iAvailSamples); if (iAvailFrames > 0) { last_read_failed = false; @@ -223,7 +223,7 @@ double EngineBufferScaleRubberBand::scaleBuffer( } if (remaining_frames > 0) { - SampleUtil::clear(read, getAudioSignal().frames2samples(remaining_frames)); + SampleUtil::clear(read, getOutputSignal().frames2samples(remaining_frames)); Counter counter("EngineBufferScaleRubberBand::getScaled underflow"); counter.increment(); } diff --git a/src/engine/bufferscalers/enginebufferscalest.cpp b/src/engine/bufferscalers/enginebufferscalest.cpp index 4e62432e599b..da1fb790d3a2 100644 --- a/src/engine/bufferscalers/enginebufferscalest.cpp +++ b/src/engine/bufferscalers/enginebufferscalest.cpp @@ -30,7 +30,7 @@ EngineBufferScaleST::EngineBufferScaleST(ReadAheadManager *pReadAheadManager) : m_pReadAheadManager(pReadAheadManager), m_pSoundTouch(std::make_unique()), m_bBackwards(false) { - m_pSoundTouch->setChannels(getAudioSignal().getChannelCount()); + m_pSoundTouch->setChannels(getOutputSignal().getChannelCount()); m_pSoundTouch->setRate(m_dBaseRate); m_pSoundTouch->setPitch(1.0); m_pSoundTouch->setSetting(SETTING_USE_QUICKSEEK, 1); @@ -87,11 +87,11 @@ void EngineBufferScaleST::setScaleParameters(double base_rate, void EngineBufferScaleST::onSampleRateChanged() { buffer_back.clear(); - if (!getAudioSignal().isValid()) { + if (!getOutputSignal().isValid()) { return; } - m_pSoundTouch->setSampleRate(getAudioSignal().getSampleRate()); - const auto bufferSize = getAudioSignal().frames2samples(kSeekOffsetFrames); + m_pSoundTouch->setSampleRate(getOutputSignal().getSampleRate()); + const auto bufferSize = getOutputSignal().frames2samples(kSeekOffsetFrames); if (bufferSize > buffer_back.size()) { // grow buffer buffer_back = mixxx::SampleBuffer(bufferSize); @@ -126,7 +126,7 @@ double EngineBufferScaleST::scaleBuffer( SINT total_received_frames = 0; SINT total_read_frames = 0; - SINT remaining_frames = getAudioSignal().samples2frames(iOutputBufferSize); + SINT remaining_frames = getOutputSignal().samples2frames(iOutputBufferSize); CSAMPLE* read = pOutputBuffer; bool last_read_failed = false; while (remaining_frames > 0) { @@ -135,7 +135,7 @@ double EngineBufferScaleST::scaleBuffer( DEBUG_ASSERT(remaining_frames >= received_frames); remaining_frames -= received_frames; total_received_frames += received_frames; - read += getAudioSignal().frames2samples(received_frames); + read += getOutputSignal().frames2samples(received_frames); if (remaining_frames > 0) { SINT iAvailSamples = m_pReadAheadManager->getNextSamples( @@ -144,7 +144,7 @@ double EngineBufferScaleST::scaleBuffer( (m_bBackwards ? -1.0 : 1.0) * m_dBaseRate * m_dTempoRatio, buffer_back.data(), buffer_back.size()); - SINT iAvailFrames = getAudioSignal().samples2frames(iAvailSamples); + SINT iAvailFrames = getOutputSignal().samples2frames(iAvailSamples); if (iAvailFrames > 0) { last_read_failed = false; diff --git a/src/engine/engine.h b/src/engine/engine.h index b7f3d780e687..e86a067c16fa 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -17,21 +17,21 @@ namespace mixxx { return m_framesPerBuffer; } SINT samplesPerBuffer() const { - return m_audioSignal.frames2samples(framesPerBuffer()); + return m_outputSignal.frames2samples(framesPerBuffer()); } audio::ChannelCount channelCount() const { - return m_audioSignal.getChannelCount(); + return m_outputSignal.getChannelCount(); } audio::SampleRate sampleRate() const { - return m_audioSignal.getSampleRate(); + return m_outputSignal.getSampleRate(); } explicit EngineParameters( audio::SampleRate sampleRate, SINT framesPerBuffer) - : m_audioSignal( + : m_outputSignal( kEngineChannelCount, sampleRate, kEngineSampleLayout), @@ -40,7 +40,7 @@ namespace mixxx { } private: - const audio::SignalInfo m_audioSignal; + const audio::SignalInfo m_outputSignal; const SINT m_framesPerBuffer; }; } From 2fec68c62365dc0c43c51c06601ad648b49c60fc Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 6 Apr 2020 14:35:59 +0200 Subject: [PATCH 197/393] Restore binding of replaygain_peak column --- src/library/dao/trackdao.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 89d69b7ce0d2..4639b663b01e 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -440,6 +440,7 @@ namespace { pTrackLibraryQuery->bindValue(":cuepoint", track.getCuePoint().getPosition()); pTrackLibraryQuery->bindValue(":bpm_lock", track.isBpmLocked()? 1 : 0); pTrackLibraryQuery->bindValue(":replaygain", track.getReplayGain().getRatio()); + pTrackLibraryQuery->bindValue(":replaygain_peak", track.getReplayGain().getPeak()); pTrackLibraryQuery->bindValue(":channels", track.getChannels()); pTrackLibraryQuery->bindValue(":samplerate", track.getSampleRate()); From da86f5fcc0f844ee96b4b59d94f2bd1fbb27e8f0 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 12 Mar 2020 00:24:55 +0100 Subject: [PATCH 198/393] Customize TrackTableBackgroundColorOpacity in skin node --- res/skins/Deere/library.xml | 1 + res/skins/LateNight/library.xml | 1 + res/skins/Shade/skin.xml | 3 ++- res/skins/Tango/library.xml | 1 + src/library/autodj/autodjfeature.cpp | 3 +-- src/library/autodj/dlgautodj.cpp | 12 +++++++----- src/library/autodj/dlgautodj.h | 6 +++--- src/library/basetracktablemodel.cpp | 16 +++++++++------- src/library/basetracktablemodel.h | 5 ++++- src/library/dlganalysis.cpp | 9 +++++++-- src/library/dlganalysis.h | 3 ++- src/library/dlghidden.cpp | 22 +++++++++++++++------- src/library/dlghidden.h | 3 ++- src/library/dlgmissing.cpp | 22 +++++++++++++++------- src/library/dlgmissing.h | 3 ++- src/library/library.cpp | 1 + src/library/recording/dlgrecording.cpp | 18 ++++++++++++++---- src/library/recording/dlgrecording.h | 3 ++- src/skin/skincontext.h | 8 ++++---- src/widget/wanalysislibrarytableview.cpp | 14 ++++++++++---- src/widget/wanalysislibrarytableview.h | 14 ++++++++------ src/widget/wlibrary.cpp | 17 +++++++++++++++-- src/widget/wlibrary.h | 19 +++++++++++-------- src/widget/wlibrarytableview.h | 5 +++-- src/widget/wtracktableview.cpp | 4 +++- src/widget/wtracktableview.h | 7 +++++++ 26 files changed, 150 insertions(+), 70 deletions(-) diff --git a/res/skins/Deere/library.xml b/res/skins/Deere/library.xml index 923f06cb50e6..fefcee7ac6ff 100644 --- a/res/skins/Deere/library.xml +++ b/res/skins/Deere/library.xml @@ -71,6 +71,7 @@ false + 0.125 diff --git a/res/skins/LateNight/library.xml b/res/skins/LateNight/library.xml index 64f2615c5f84..5aa0788c4fad 100644 --- a/res/skins/LateNight/library.xml +++ b/res/skins/LateNight/library.xml @@ -74,6 +74,7 @@ #585858 #eece33 false + 0.125 diff --git a/res/skins/Shade/skin.xml b/res/skins/Shade/skin.xml index 3ced0911fd62..9a94e72d179f 100644 --- a/res/skins/Shade/skin.xml +++ b/res/skins/Shade/skin.xml @@ -251,7 +251,7 @@ Above the maximized library there are minimal decks which contain overview waveforms. For the end-of-track warning in those overviews to work instances of scrolling waveforms are required. We load in the maximzed library view, as well, but they are invisible as - they are 0px tall. + they are 0px tall. ############################################################################################ ############################################################################################ --> @@ -296,6 +296,7 @@ false + 0.125 diff --git a/res/skins/Tango/library.xml b/res/skins/Tango/library.xml index a5eae792f7d1..fb938b790238 100644 --- a/res/skins/Tango/library.xml +++ b/res/skins/Tango/library.xml @@ -115,6 +115,7 @@ Description: #585858 #eece33 false + 0.125 diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index 8cd5a0dbb72f..f8992625ea15 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -122,8 +122,7 @@ void AutoDJFeature::bindLibraryWidget( m_pConfig, m_pLibrary, m_pAutoDJProcessor, - keyboard, - libraryWidget->getShowButtonText()); + keyboard); libraryWidget->registerView(kViewName, m_pAutoDJView); connect(m_pAutoDJView, &DlgAutoDJ::loadTrack, diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 97305e08acb4..f3227031f0b1 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -7,6 +7,7 @@ #include "util/assert.h" #include "util/compatibility.h" #include "util/duration.h" +#include "widget/wlibrary.h" #include "widget/wtracktableview.h" namespace { @@ -15,19 +16,20 @@ const char* kRepeatPlaylistPreference = "Requeue"; } // anonymous namespace DlgAutoDJ::DlgAutoDJ( - QWidget* parent, + WLibrary* parent, UserSettingsPointer pConfig, Library* pLibrary, AutoDJProcessor* pProcessor, - KeyboardEventFilter* pKeyboard, - bool showButtonText) + KeyboardEventFilter* pKeyboard) : QWidget(parent), Ui::DlgAutoDJ(), m_pConfig(pConfig), m_pAutoDJProcessor(pProcessor), m_pTrackTableView(new WTrackTableView(this, m_pConfig, - pLibrary->trackCollections(), /*no sorting*/ false)), - m_bShowButtonText(showButtonText), + pLibrary->trackCollections(), + parent->getTrackTableBackgroundColorOpacity(), + /*no sorting*/ false)), + m_bShowButtonText(parent->getShowButtonText()), m_pAutoDJTableModel(nullptr) { setupUi(this); diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index 5a0d2f95b2dd..a9b96daef6e2 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -14,17 +14,17 @@ #include "controllers/keyboard/keyboardeventfilter.h" class PlaylistTableModel; +class WLibrary; class WTrackTableView; class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { Q_OBJECT public: - DlgAutoDJ(QWidget* parent, + DlgAutoDJ(WLibrary* parent, UserSettingsPointer pConfig, Library* pLibrary, AutoDJProcessor* pProcessor, - KeyboardEventFilter* pKeyboard, - bool showButtonText); + KeyboardEventFilter* pKeyboard); ~DlgAutoDJ() override; void onShow() override; diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index ee92114ae146..688887db738f 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -15,7 +15,8 @@ #include "util/assert.h" #include "util/compatibility.h" #include "util/logger.h" -#include "widget/wlibrarytableview.h" +#include "widget/wlibrary.h" +#include "widget/wtracktableview.h" namespace { @@ -23,9 +24,6 @@ const mixxx::Logger kLogger("BaseTrackTableModel"); const QString kEmptyString = QStringLiteral(""); -// Alpha value for row color background (range 0 - 255) -constexpr int kTrackColorRowBackgroundOpacity = 0x20; // 12.5% opacity - const QStringList kDefaultTableColumns = { LIBRARYTABLE_ALBUM, LIBRARYTABLE_ALBUMARTIST, @@ -98,7 +96,8 @@ BaseTrackTableModel::BaseTrackTableModel( cloneDatabase(pTrackCollectionManager), settingsNamespace), m_pTrackCollectionManager(pTrackCollectionManager), - m_previewDeckGroup(PlayerManager::groupForPreviewDeck(0)) { + m_previewDeckGroup(PlayerManager::groupForPreviewDeck(0)), + m_backgroundColorOpacity(WLibrary::kDefaultTrackTableBackgroundColorOpacity) { connect(&pTrackCollectionManager->internalCollection()->getTrackDAO(), &TrackDAO::forceModelUpdate, this, @@ -356,10 +355,11 @@ bool BaseTrackTableModel::isColumnHiddenByDefault( QAbstractItemDelegate* BaseTrackTableModel::delegateForColumn( const int index, QObject* pParent) { - auto* pTableView = qobject_cast(pParent); + auto* pTableView = qobject_cast(pParent); VERIFY_OR_DEBUG_ASSERT(pTableView) { return nullptr; } + m_backgroundColorOpacity = pTableView->getBackgroundColorOpacity(); if (index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { return new StarDelegate(pTableView); } else if (index == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM)) { @@ -411,7 +411,9 @@ QVariant BaseTrackTableModel::data( } auto bgColor = mixxx::RgbColor::toQColor(trackColor); DEBUG_ASSERT(bgColor.isValid()); - bgColor.setAlpha(kTrackColorRowBackgroundOpacity); + DEBUG_ASSERT(m_backgroundColorOpacity >= 0.0); + DEBUG_ASSERT(m_backgroundColorOpacity <= 1.0); + bgColor.setAlphaF(m_backgroundColorOpacity); return QBrush(bgColor); } diff --git a/src/library/basetracktablemodel.h b/src/library/basetracktablemodel.h index 16aeff967fb6..9228edd2be03 100644 --- a/src/library/basetracktablemodel.h +++ b/src/library/basetracktablemodel.h @@ -10,6 +10,7 @@ class BaseCoverArtDelegate; class TrackCollectionManager; +class WLibraryTableView; class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { Q_OBJECT @@ -19,7 +20,7 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { explicit BaseTrackTableModel( const char* settingsNamespace, TrackCollectionManager* const pTrackCollectionManager, - QObject* parent = nullptr); + QObject* parent); ~BaseTrackTableModel() override = default; /////////////////////////////////////////////////////// @@ -200,6 +201,8 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { const QString m_previewDeckGroup; + double m_backgroundColorOpacity; + ColumnCache m_columnCache; struct ColumnHeader { diff --git a/src/library/dlganalysis.cpp b/src/library/dlganalysis.cpp index 1a9796d4e1a7..01007b8ccc50 100644 --- a/src/library/dlganalysis.cpp +++ b/src/library/dlganalysis.cpp @@ -8,9 +8,10 @@ #include "library/trackcollectionmanager.h" #include "library/dlganalysis.h" #include "library/library.h" +#include "widget/wlibrary.h" #include "util/assert.h" -DlgAnalysis::DlgAnalysis(QWidget* parent, +DlgAnalysis::DlgAnalysis(WLibrary* parent, UserSettingsPointer pConfig, Library* pLibrary) : QWidget(parent), @@ -20,7 +21,11 @@ DlgAnalysis::DlgAnalysis(QWidget* parent, m_songsButtonGroup.addButton(radioButtonRecentlyAdded); m_songsButtonGroup.addButton(radioButtonAllSongs); - m_pAnalysisLibraryTableView = new WAnalysisLibraryTableView(this, pConfig, pLibrary->trackCollections()); + m_pAnalysisLibraryTableView = new WAnalysisLibraryTableView( + this, + pConfig, + pLibrary->trackCollections(), + parent->getTrackTableBackgroundColorOpacity()); connect(m_pAnalysisLibraryTableView, &WAnalysisLibraryTableView::loadTrack, this, diff --git a/src/library/dlganalysis.h b/src/library/dlganalysis.h index 2c0b7cba0825..1aacad7478bb 100644 --- a/src/library/dlganalysis.h +++ b/src/library/dlganalysis.h @@ -13,11 +13,12 @@ class AnalysisLibraryTableModel; class WAnalysisLibraryTableView; class Library; +class WLibrary; class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual LibraryView { Q_OBJECT public: - DlgAnalysis(QWidget *parent, + DlgAnalysis(WLibrary *parent, UserSettingsPointer pConfig, Library* pLibrary); ~DlgAnalysis() override = default; diff --git a/src/library/dlghidden.cpp b/src/library/dlghidden.cpp index ffacd58f51ca..77dfcd94e907 100644 --- a/src/library/dlghidden.cpp +++ b/src/library/dlghidden.cpp @@ -2,16 +2,24 @@ #include "library/hiddentablemodel.h" #include "library/trackcollectionmanager.h" +#include "widget/wlibrary.h" #include "widget/wtracktableview.h" #include "util/assert.h" -DlgHidden::DlgHidden(QWidget* parent, UserSettingsPointer pConfig, - Library* pLibrary, - KeyboardEventFilter* pKeyboard) - : QWidget(parent), - Ui::DlgHidden(), - m_pTrackTableView( - new WTrackTableView(this, pConfig, pLibrary->trackCollections(), false)) { +DlgHidden::DlgHidden( + WLibrary* parent, + UserSettingsPointer pConfig, + Library* pLibrary, + KeyboardEventFilter* pKeyboard) + : QWidget(parent), + Ui::DlgHidden(), + m_pTrackTableView( + new WTrackTableView( + this, + pConfig, + pLibrary->trackCollections(), + parent->getTrackTableBackgroundColorOpacity(), + false)) { setupUi(this); m_pTrackTableView->installEventFilter(pKeyboard); diff --git a/src/library/dlghidden.h b/src/library/dlghidden.h index 37ae3c0340d2..4163fed8458a 100644 --- a/src/library/dlghidden.h +++ b/src/library/dlghidden.h @@ -8,6 +8,7 @@ #include "library/libraryview.h" #include "controllers/keyboard/keyboardeventfilter.h" +class WLibrary; class WTrackTableView; class HiddenTableModel; class QItemSelection; @@ -16,7 +17,7 @@ class DlgHidden : public QWidget, public Ui::DlgHidden, public LibraryView { Q_OBJECT public: - DlgHidden(QWidget* parent, UserSettingsPointer pConfig, + DlgHidden(WLibrary* parent, UserSettingsPointer pConfig, Library* pLibrary, KeyboardEventFilter* pKeyboard); ~DlgHidden() override; diff --git a/src/library/dlgmissing.cpp b/src/library/dlgmissing.cpp index 8126edd55dc8..d2d1240f3e9c 100644 --- a/src/library/dlgmissing.cpp +++ b/src/library/dlgmissing.cpp @@ -2,16 +2,24 @@ #include "library/missingtablemodel.h" #include "library/trackcollectionmanager.h" +#include "widget/wlibrary.h" #include "widget/wtracktableview.h" #include "util/assert.h" -DlgMissing::DlgMissing(QWidget* parent, UserSettingsPointer pConfig, - Library* pLibrary, - KeyboardEventFilter* pKeyboard) - : QWidget(parent), - Ui::DlgMissing(), - m_pTrackTableView( - new WTrackTableView(this, pConfig, pLibrary->trackCollections(), false)) { +DlgMissing::DlgMissing( + WLibrary* parent, + UserSettingsPointer pConfig, + Library* pLibrary, + KeyboardEventFilter* pKeyboard) + : QWidget(parent), + Ui::DlgMissing(), + m_pTrackTableView( + new WTrackTableView( + this, + pConfig, + pLibrary->trackCollections(), + parent->getTrackTableBackgroundColorOpacity(), + false)) { setupUi(this); m_pTrackTableView->installEventFilter(pKeyboard); diff --git a/src/library/dlgmissing.h b/src/library/dlgmissing.h index cedb9a77a443..9fd5985eec30 100644 --- a/src/library/dlgmissing.h +++ b/src/library/dlgmissing.h @@ -8,6 +8,7 @@ #include "library/libraryview.h" #include "controllers/keyboard/keyboardeventfilter.h" +class WLibrary; class WTrackTableView; class MissingTableModel; @@ -15,7 +16,7 @@ class DlgMissing : public QWidget, public Ui::DlgMissing, public LibraryView { Q_OBJECT public: - DlgMissing(QWidget* parent, UserSettingsPointer pConfig, + DlgMissing(WLibrary* parent, UserSettingsPointer pConfig, Library* pLibrary, KeyboardEventFilter* pKeyboard); ~DlgMissing() override; diff --git a/src/library/library.cpp b/src/library/library.cpp index 0c08e7df980a..d98c33d8f495 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -295,6 +295,7 @@ void Library::bindLibraryWidget(WLibrary* pLibraryWidget, pLibraryWidget, m_pConfig, m_pTrackCollectionManager, + pLibraryWidget->getTrackTableBackgroundColorOpacity(), true); pTrackTableView->installEventFilter(pKeyboard); connect(this, diff --git a/src/library/recording/dlgrecording.cpp b/src/library/recording/dlgrecording.cpp index 1b833d3c752e..763528dfcde5 100644 --- a/src/library/recording/dlgrecording.cpp +++ b/src/library/recording/dlgrecording.cpp @@ -5,15 +5,25 @@ #include "library/trackcollectionmanager.h" #include "widget/wwidget.h" #include "widget/wskincolor.h" +#include "widget/wlibrary.h" #include "widget/wtracktableview.h" #include "util/assert.h" -DlgRecording::DlgRecording(QWidget* parent, UserSettingsPointer pConfig, - Library* pLibrary, - RecordingManager* pRecordingManager, KeyboardEventFilter* pKeyboard) +DlgRecording::DlgRecording( + WLibrary* parent, + UserSettingsPointer pConfig, + Library* pLibrary, + RecordingManager* pRecordingManager, + KeyboardEventFilter* pKeyboard) : QWidget(parent), m_pConfig(pConfig), - m_pTrackTableView(new WTrackTableView(this, pConfig, pLibrary->trackCollections(), true)), + m_pTrackTableView( + new WTrackTableView( + this, + pConfig, + pLibrary->trackCollections(), + parent->getTrackTableBackgroundColorOpacity(), + true)), m_browseModel(this, pLibrary->trackCollections(), pRecordingManager), m_proxyModel(&m_browseModel), m_bytesRecordedStr("--"), diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h index fc9045e8e4a0..3cb73a86e9c0 100644 --- a/src/library/recording/dlgrecording.h +++ b/src/library/recording/dlgrecording.h @@ -14,12 +14,13 @@ class PlaylistTableModel; class QSqlTableModel; +class WLibrary; class WTrackTableView; class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual LibraryView { Q_OBJECT public: - DlgRecording(QWidget *parent, UserSettingsPointer pConfig, + DlgRecording(WLibrary *parent, UserSettingsPointer pConfig, Library* pLibrary, RecordingManager* pRecManager, KeyboardEventFilter* pKeyboard); ~DlgRecording() override; diff --git a/src/skin/skincontext.h b/src/skin/skincontext.h index 2095e26dcaf2..9cd85a6ee2cc 100644 --- a/src/skin/skincontext.h +++ b/src/skin/skincontext.h @@ -101,16 +101,16 @@ class SkinContext { return nodeToString(child); } - inline float selectFloat(const QDomNode& node, const QString& nodeName) const { + inline float selectFloat(const QDomNode& node, const QString& nodeName, float defaultValue = 0.0) const { bool ok = false; float conv = nodeToString(selectElement(node, nodeName)).toFloat(&ok); - return ok ? conv : 0.0f; + return ok ? conv : defaultValue; } - inline double selectDouble(const QDomNode& node, const QString& nodeName) const { + inline double selectDouble(const QDomNode& node, const QString& nodeName, double defaultValue = 0.0) const { bool ok = false; double conv = nodeToString(selectElement(node, nodeName)).toDouble(&ok); - return ok ? conv : 0.0; + return ok ? conv : defaultValue; } inline int selectInt(const QDomNode& node, const QString& nodeName, diff --git a/src/widget/wanalysislibrarytableview.cpp b/src/widget/wanalysislibrarytableview.cpp index 92719475f243..08d7af63de4f 100644 --- a/src/widget/wanalysislibrarytableview.cpp +++ b/src/widget/wanalysislibrarytableview.cpp @@ -1,10 +1,16 @@ #include "library/trackcollection.h" #include "widget/wanalysislibrarytableview.h" -WAnalysisLibraryTableView::WAnalysisLibraryTableView(QWidget* parent, - UserSettingsPointer pConfig, - TrackCollectionManager* pTrackCollectionManager) - : WTrackTableView(parent, pConfig, pTrackCollectionManager, true) { +WAnalysisLibraryTableView::WAnalysisLibraryTableView( + QWidget* parent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + double trackTableBackgroundColorOpacity) + : WTrackTableView(parent, + pConfig, + pTrackCollectionManager, + trackTableBackgroundColorOpacity, + true) { setDragDropMode(QAbstractItemView::DragOnly); setDragEnabled(true); //Always enable drag for now (until we have a model that doesn't support this.) } diff --git a/src/widget/wanalysislibrarytableview.h b/src/widget/wanalysislibrarytableview.h index a4012b09521f..c3733b8abbaf 100644 --- a/src/widget/wanalysislibrarytableview.h +++ b/src/widget/wanalysislibrarytableview.h @@ -6,13 +6,15 @@ #include "preferences/usersettings.h" #include "widget/wtracktableview.h" -class WAnalysisLibraryTableView : public WTrackTableView -{ - public: - WAnalysisLibraryTableView(QWidget* parent, UserSettingsPointer pConfig, - TrackCollectionManager* pTrackCollectionManager); +class WAnalysisLibraryTableView : public WTrackTableView { + public: + WAnalysisLibraryTableView( + QWidget* parent, + UserSettingsPointer pConfig, + TrackCollectionManager* pTrackCollectionManager, + double trackTableBackgroundColorOpacity); - void onSearch(const QString& text) override; + void onSearch(const QString& text) override; }; #endif diff --git a/src/widget/wlibrary.cpp b/src/widget/wlibrary.cpp index 0fb9b7802737..8e10fe512b13 100644 --- a/src/widget/wlibrary.cpp +++ b/src/widget/wlibrary.cpp @@ -7,17 +7,30 @@ #include "widget/wlibrary.h" #include "library/libraryview.h" #include "controllers/keyboard/keyboardeventfilter.h" -#include "wtracktableview.h" +#include "widget/wtracktableview.h" +#include "util/math.h" WLibrary::WLibrary(QWidget* parent) : QStackedWidget(parent), WBaseWidget(this), m_mutex(QMutex::Recursive), + m_trackTableBackgroundColorOpacity(kDefaultTrackTableBackgroundColorOpacity), m_bShowButtonText(true) { } void WLibrary::setup(const QDomNode& node, const SkinContext& context) { - m_bShowButtonText = context.selectBool(node, "ShowButtonText", true); + m_bShowButtonText = + context.selectBool( + node, + "ShowButtonText", + true); + m_trackTableBackgroundColorOpacity = math_clamp( + context.selectDouble( + node, + "TrackTableBackgroundColorOpacity", + kDefaultTrackTableBackgroundColorOpacity), + kMinTrackTableBackgroundColorOpacity, + kMaxTrackTableBackgroundColorOpacity); } bool WLibrary::registerView(QString name, QWidget* view) { diff --git a/src/widget/wlibrary.h b/src/widget/wlibrary.h index b7d1ad6dcb3c..5c6a4fad7ec9 100644 --- a/src/widget/wlibrary.h +++ b/src/widget/wlibrary.h @@ -1,8 +1,4 @@ -// wlibrary.h -// Created 8/28/2009 by RJ Ryan (rryan@mit.edu) - -#ifndef WLIBRARY_H -#define WLIBRARY_H +#pragma once #include #include @@ -33,6 +29,15 @@ class WLibrary : public QStackedWidget, public WBaseWidget { LibraryView* getActiveView() const; + // Alpha value for row color background + static constexpr double kDefaultTrackTableBackgroundColorOpacity = 0.125; // 12.5% opacity + static constexpr double kMinTrackTableBackgroundColorOpacity = 0.0; // 0% opacity + static constexpr double kMaxTrackTableBackgroundColorOpacity = 1.0; // 100% opacity + + double getTrackTableBackgroundColorOpacity() const { + return m_trackTableBackgroundColorOpacity; + } + bool getShowButtonText() const { return m_bShowButtonText; } @@ -51,8 +56,6 @@ class WLibrary : public QStackedWidget, public WBaseWidget { private: QMutex m_mutex; QMap m_viewMap; + double m_trackTableBackgroundColorOpacity; bool m_bShowButtonText; }; - -#endif /* WLIBRARY_H */ - diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h index ad142ef194e9..57601488e676 100644 --- a/src/widget/wlibrarytableview.h +++ b/src/widget/wlibrarytableview.h @@ -59,10 +59,11 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { void loadVScrollBarPosState(); void saveVScrollBarPosState(); + const UserSettingsPointer m_pConfig; + const ConfigKey m_vScrollBarPosKey; + QMap m_vScrollBarPosValues; - UserSettingsPointer m_pConfig; - ConfigKey m_vScrollBarPosKey; // The position of the vertical scrollbar slider, eg. before a search is // executed int m_noSearchVScrollBarPos; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index b3e581d60660..04c692f1b9b1 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -41,15 +41,17 @@ #include "widget/wtracktableviewheader.h" #include "widget/wwidget.h" -WTrackTableView::WTrackTableView(QWidget * parent, +WTrackTableView::WTrackTableView(QWidget* parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager, + double backgroundColorOpacity, bool sorting) : WLibraryTableView(parent, pConfig, ConfigKey(LIBRARY_CONFIGVALUE, WTRACKTABLEVIEW_VSCROLLBARPOS_KEY)), m_pConfig(pConfig), m_pTrackCollectionManager(pTrackCollectionManager), + m_backgroundColorOpacity(backgroundColorOpacity), m_sorting(sorting), m_iCoverSourceColumn(-1), m_iCoverTypeColumn(-1), diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index fd6beae9d6ae..e842473ced23 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -32,6 +32,7 @@ class WTrackTableView : public WLibraryTableView { QWidget* parent, UserSettingsPointer pConfig, TrackCollectionManager* pTrackCollectionManager, + double backgroundColorOpacity, bool sorting); ~WTrackTableView() override; void contextMenuEvent(QContextMenuEvent * event) override; @@ -46,6 +47,10 @@ class WTrackTableView : public WLibraryTableView { void saveCurrentVScrollBarPos(); void restoreCurrentVScrollBarPos(); + double getBackgroundColorOpacity() const { + return m_backgroundColorOpacity; + } + public slots: void loadTrackModel(QAbstractItemModel* model); void slotMouseDoubleClicked(const QModelIndex &); @@ -133,6 +138,8 @@ class WTrackTableView : public WLibraryTableView { TrackCollectionManager* const m_pTrackCollectionManager; + const double m_backgroundColorOpacity; + QScopedPointer m_pTrackInfo; QScopedPointer m_pTagFetcher; From 0a2b1cd88db458670d74f3909264e34d3e37a467 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 6 Apr 2020 15:32:58 +0200 Subject: [PATCH 199/393] Initialize Rubberband internals in constructor --- src/engine/bufferscalers/enginebufferscalerubberband.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.cpp b/src/engine/bufferscalers/enginebufferscalerubberband.cpp index 2b88e9e28243..fd100a6c14a6 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.cpp +++ b/src/engine/bufferscalers/enginebufferscalerubberband.cpp @@ -28,6 +28,9 @@ EngineBufferScaleRubberBand::EngineBufferScaleRubberBand( m_bBackwards(false) { m_retrieve_buffer[0] = SampleUtil::alloc(MAX_BUFFER_LEN); m_retrieve_buffer[1] = SampleUtil::alloc(MAX_BUFFER_LEN); + // Initialize the internal buffers to prevent re-allocations + // in the real-time thread. + onSampleRateChanged(); } EngineBufferScaleRubberBand::~EngineBufferScaleRubberBand() { @@ -98,6 +101,9 @@ void EngineBufferScaleRubberBand::setScaleParameters(double base_rate, } void EngineBufferScaleRubberBand::onSampleRateChanged() { + // TODO: Resetting the sample rate will cause internal + // memory allocations that may block the real-time thread. + // When is this function actually invoked?? if (!getOutputSignal().isValid()) { m_pRubberBand.reset(); return; From 5bfcbd624104b861a4d2a3013126672d620fd3f1 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 18:26:18 +0200 Subject: [PATCH 200/393] scripts/line_length: Only use files filtered by the pre-commit hook --- scripts/line_length.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/scripts/line_length.py b/scripts/line_length.py index 079736d4d70f..df9fb91e6743 100755 --- a/scripts/line_length.py +++ b/scripts/line_length.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import argparse import re import logging import os @@ -87,11 +88,35 @@ def group_lines( yield FileLines(filename, grouped_linenumbers) -def main() -> int: +def main(argv: typing.Optional[typing.List[str]] = None) -> int: logging.basicConfig() logger = logging.getLogger(__name__) + import sys + + print(sys.argv) + + parser = argparse.ArgumentParser() + parser.add_argument("files", nargs="*", help="only check these files") + args = parser.parse_args(argv) + print(args) + all_lines = get_git_added_lines() + + # Filter filenames + rootdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") + if args.files: + files = [ + os.path.abspath(os.path.join(rootdir, filename)) + for filename in args.files + ] + all_lines = ( + line + for line in all_lines + if os.path.abspath(os.path.join(line.sourcefile)) in files + ) + + # Only keep long lines long_lines = ( line for line in all_lines @@ -99,7 +124,6 @@ def main() -> int: ) changed_files = group_lines(long_lines) - rootdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") cpp_file = os.path.join(rootdir, "src/mixxx.cpp") proc = subprocess.run( ["clang-format", "--dump-config", cpp_file], From 60899c32960e04037f363bc771c4f3bfbd6adc66 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 18:29:05 +0200 Subject: [PATCH 201/393] pre-commit: Rename line_length to line-length for consistency --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea0f13baca78..05fd1f195811 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -113,8 +113,8 @@ repos: - manual - repo: local hooks: - - id: line_length - name: line_length + - id: line-length + name: line-length description: Check for lines longer 100 and brakes them before 80. entry: ./scripts/line_length.py stages: From 65905a5149094d1d97a5caf25dfcd371c5de4d09 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Mon, 6 Apr 2020 22:05:28 +0530 Subject: [PATCH 202/393] WTrackMenu: refactor to functor syntax --- src/widget/wtrackmenu.cpp | 119 ++++++++++++++------------------- src/widget/wtracktableview.cpp | 20 +++--- 2 files changed, 62 insertions(+), 77 deletions(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index f9113efed412..c6b0be9eb538 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -69,15 +69,15 @@ void WTrackMenu::constructMenus() { if (optionIsEnabled(Filter::Playlist)) { m_pPlaylistMenu = new QMenu(this); m_pPlaylistMenu->setTitle(tr("Add to Playlist")); - connect(m_pPlaylistMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulatePlaylistMenu())); + connect(m_pPlaylistMenu, &QMenu::aboutToShow, + this, &WTrackMenu::slotPopulatePlaylistMenu); } if (optionIsEnabled(Filter::Crate)) { m_pCrateMenu = new QMenu(this); m_pCrateMenu->setTitle(tr("Crates")); - connect(m_pCrateMenu, SIGNAL(aboutToShow()), - this, SLOT(slotPopulateCrateMenu())); + connect(m_pCrateMenu, &QMenu::aboutToShow, + this, &WTrackMenu::slotPopulateCrateMenu); } if (optionIsEnabled(Filter::Metadata)) { @@ -89,10 +89,10 @@ void WTrackMenu::constructMenus() { m_pCoverMenu = new WCoverArtMenu(this); m_pCoverMenu->setTitle(tr("Cover Art")); - connect(m_pCoverMenu, SIGNAL(coverInfoSelected(const CoverInfoRelative&)), - this, SLOT(slotCoverInfoSelected(const CoverInfoRelative&))); - connect(m_pCoverMenu, SIGNAL(reloadCoverArt()), - this, SLOT(slotReloadCoverArt())); + connect(m_pCoverMenu, &WCoverArtMenu::coverInfoSelected, + this, &WTrackMenu::slotCoverInfoSelected); + connect(m_pCoverMenu, &WCoverArtMenu::reloadCoverArt, + this, &WTrackMenu::slotReloadCoverArt); } if (optionIsEnabled(Filter::BPM)) { @@ -115,16 +115,16 @@ void WTrackMenu::constructMenus() { void WTrackMenu::createActions() { if (optionIsEnabled(Filter::AutoDJ)) { m_pAutoDJBottomAct = new QAction(tr("Add to Auto DJ Queue (bottom)"), this); - connect(m_pAutoDJBottomAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJBottom())); + connect(m_pAutoDJBottomAct, &QAction::triggered, + this, &WTrackMenu::slotAddToAutoDJBottom); m_pAutoDJTopAct = new QAction(tr("Add to Auto DJ Queue (top)"), this); - connect(m_pAutoDJTopAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJTop())); + connect(m_pAutoDJBottomAct, &QAction::triggered, + this, &WTrackMenu::slotAddToAutoDJTop); m_pAutoDJReplaceAct = new QAction(tr("Add to Auto DJ Queue (replace)"), this); - connect(m_pAutoDJReplaceAct, SIGNAL(triggered()), - this, SLOT(slotAddToAutoDJReplace())); + connect(m_pAutoDJBottomAct, &QAction::triggered, + this, &WTrackMenu::slotAddToAutoDJReplace); } if (optionIsEnabled(Filter::LoadTo)) { @@ -137,30 +137,29 @@ void WTrackMenu::createActions() { if (optionIsEnabled(Filter::Remove)) { m_pRemoveAct = new QAction(tr("Remove"), this); - connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + connect(m_pRemoveAct, &QAction::triggered, this, &WTrackMenu::slotRemove); m_pRemovePlaylistAct = new QAction(tr("Remove from Playlist"), this); connect(m_pRemovePlaylistAct, &QAction::triggered, this, &WTrackMenu::slotRemove); m_pRemoveCrateAct = new QAction(tr("Remove from Crate"), this); - connect(m_pRemoveCrateAct, SIGNAL(triggered()), this, SLOT(slotRemove())); + connect(m_pRemoveCrateAct, &QAction::triggered, this, &WTrackMenu::slotRemove); } if (optionIsEnabled(Filter::HideUnhidePurge)) { m_pHideAct = new QAction(tr("Hide from Library"), this); - connect(m_pHideAct, SIGNAL(triggered()), this, SLOT(slotHide())); + connect(m_pHideAct, &QAction::triggered, this, &WTrackMenu::slotHide); m_pUnhideAct = new QAction(tr("Unhide from Library"), this); - connect(m_pUnhideAct, SIGNAL(triggered()), this, SLOT(slotUnhide())); + connect(m_pUnhideAct, &QAction::triggered, this, &WTrackMenu::slotUnhide); m_pPurgeAct = new QAction(tr("Purge from Library"), this); - connect(m_pPurgeAct, SIGNAL(triggered()), this, SLOT(slotPurge())); + connect(m_pPurgeAct, &QAction::triggered, this, &WTrackMenu::slotPurge); } if (optionIsEnabled(Filter::Properties)) { m_pPropertiesAct = new QAction(tr("Properties"), this); - connect(m_pPropertiesAct, SIGNAL(triggered()), - this, SLOT(slotShowTrackInfo())); + connect(m_pPropertiesAct, &QAction::triggered, this, &WTrackMenu::slotShowTrackInfo); } if (optionIsEnabled(Filter::FileBrowser)) { @@ -171,16 +170,13 @@ void WTrackMenu::createActions() { if (optionIsEnabled(Filter::Metadata)) { m_pImportMetadataFromFileAct = new QAction(tr("Import From File Tags"), this); - connect(m_pImportMetadataFromFileAct, SIGNAL(triggered()), - this, SLOT(slotImportTrackMetadataFromFileTags())); + connect(m_pImportMetadataFromFileAct, &QAction::triggered, this, &WTrackMenu::slotImportTrackMetadataFromFileTags); m_pImportMetadataFromMusicBrainzAct = new QAction(tr("Import From MusicBrainz"),this); - connect(m_pImportMetadataFromMusicBrainzAct, SIGNAL(triggered()), - this, SLOT(slotShowDlgTagFetcher())); + connect(m_pImportMetadataFromMusicBrainzAct, &QAction::triggered, this, &WTrackMenu::slotShowDlgTagFetcher); m_pExportMetadataAct = new QAction(tr("Export To File Tags"), this); - connect(m_pExportMetadataAct, SIGNAL(triggered()), - this, SLOT(slotExportTrackMetadataIntoFileTags())); + connect(m_pExportMetadataAct, &QAction::triggered, this, &WTrackMenu::slotExportTrackMetadataIntoFileTags); for (const auto& externalTrackCollection : m_pTrackCollectionManager->externalCollections()) { @@ -200,57 +196,44 @@ void WTrackMenu::createActions() { if (optionIsEnabled(Filter::Reset)) { // Clear metadata actions m_pClearBeatsAction = new QAction(tr("BPM and Beatgrid"), this); - connect(m_pClearBeatsAction, SIGNAL(triggered()), - this, SLOT(slotClearBeats())); + connect(m_pClearBeatsAction, &QAction::triggered, this, &WTrackMenu::slotClearBeats); m_pClearPlayCountAction = new QAction(tr("Play Count"), this); - connect(m_pClearPlayCountAction, SIGNAL(triggered()), - this, SLOT(slotClearPlayCount())); + connect(m_pClearPlayCountAction, &QAction::triggered, this, &WTrackMenu::slotClearPlayCount); m_pClearMainCueAction = new QAction(tr("Cue Point"), this); - connect(m_pClearMainCueAction, SIGNAL(triggered()), - this, SLOT(slotClearMainCue())); + connect(m_pClearMainCueAction, &QAction::triggered, this, &WTrackMenu::slotClearMainCue); m_pClearHotCuesAction = new QAction(tr("Hotcues"), this); - connect(m_pClearHotCuesAction, SIGNAL(triggered()), - this, SLOT(slotClearHotCues())); + connect(m_pClearHotCuesAction, &QAction::triggered, this, &WTrackMenu::slotClearHotCues); m_pClearIntroCueAction = new QAction(tr("Intro"), this); - connect(m_pClearIntroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearIntroCue())); + connect(m_pClearIntroCueAction, &QAction::triggered, this, &WTrackMenu::slotClearIntroCue); m_pClearOutroCueAction = new QAction(tr("Outro"), this); - connect(m_pClearOutroCueAction, SIGNAL(triggered()), - this, SLOT(slotClearOutroCue())); + connect(m_pClearOutroCueAction, &QAction::triggered, this, &WTrackMenu::slotClearOutroCue); m_pClearLoopAction = new QAction(tr("Loop"), this); - connect(m_pClearLoopAction, SIGNAL(triggered()), - this, SLOT(slotClearLoop())); + connect(m_pClearLoopAction, &QAction::triggered, this, &WTrackMenu::slotClearLoop); m_pClearKeyAction = new QAction(tr("Key"), this); - connect(m_pClearKeyAction, SIGNAL(triggered()), - this, SLOT(slotClearKey())); + connect(m_pClearKeyAction, &QAction::triggered, this, &WTrackMenu::slotClearKey); m_pClearReplayGainAction = new QAction(tr("ReplayGain"), this); - connect(m_pClearReplayGainAction, SIGNAL(triggered()), - this, SLOT(slotClearReplayGain())); + connect(m_pClearReplayGainAction, &QAction::triggered, this, &WTrackMenu::slotClearReplayGain); m_pClearWaveformAction = new QAction(tr("Waveform"), this); - connect(m_pClearWaveformAction, SIGNAL(triggered()), - this, SLOT(slotClearWaveform())); + connect(m_pClearWaveformAction, &QAction::triggered, this, &WTrackMenu::slotClearWaveform); m_pClearAllMetadataAction = new QAction(tr("All"), this); - connect(m_pClearAllMetadataAction, SIGNAL(triggered()), - this, SLOT(slotClearAllMetadata())); + connect(m_pClearAllMetadataAction, &QAction::triggered, this, &WTrackMenu::slotClearAllMetadata); } if (optionIsEnabled(Filter::BPM)) { m_pBpmLockAction = new QAction(tr("Lock BPM"), this); m_pBpmUnlockAction = new QAction(tr("Unlock BPM"), this); - connect(m_pBpmLockAction, SIGNAL(triggered()), - this, SLOT(slotLockBpm())); - connect(m_pBpmUnlockAction, SIGNAL(triggered()), - this, SLOT(slotUnlockBpm())); + connect(m_pBpmLockAction, &QAction::triggered, this, &WTrackMenu::slotLockBpm); + connect(m_pBpmUnlockAction, &QAction::triggered, this, &WTrackMenu::slotUnlockBpm); //BPM edit actions m_pBpmDoubleAction = new QAction(tr("Double BPM"), this); @@ -940,7 +923,7 @@ void WTrackMenu::slotPopulateCrateMenu() { m_pCrateMenu->addSeparator(); QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); m_pCrateMenu->addAction(newCrateAction); - connect(newCrateAction, SIGNAL(triggered()), this, SLOT(addSelectionToNewCrate())); + connect(newCrateAction, &QAction::triggered, this, &WTrackMenu::addSelectionToNewCrate); m_bCrateMenuLoaded = true; } @@ -1263,14 +1246,14 @@ void WTrackMenu::showTrackInfo(QModelIndex index) { // make it unreadable. Bug #673411 m_pTrackInfo.reset(new DlgTrackInfo(m_pConfig, nullptr)); - connect(m_pTrackInfo.data(), SIGNAL(next()), - this, SLOT(slotNextTrackInfo())); - connect(m_pTrackInfo.data(), SIGNAL(previous()), - this, SLOT(slotPrevTrackInfo())); - connect(m_pTrackInfo.data(), SIGNAL(showTagFetcher(TrackPointer)), - this, SLOT(slotShowTrackInTagFetcher(TrackPointer))); - connect(m_pTrackInfo.data(), SIGNAL(finished(int)), - this, SLOT(slotTrackInfoClosed())); + connect(m_pTrackInfo.data(), &DlgTrackInfo::next, + this, &WTrackMenu::slotNextTrackInfo); + connect(m_pTrackInfo.data(), &DlgTrackInfo::previous, + this, &WTrackMenu::slotPrevTrackInfo); + connect(m_pTrackInfo.data(), &DlgTrackInfo::showTagFetcher, + this, &WTrackMenu::slotShowTrackInTagFetcher); + connect(m_pTrackInfo.data(), &DlgTrackInfo::finished, + this, &WTrackMenu::slotTrackInfoClosed); } TrackPointer pTrack = trackModel->getTrack(index); m_pTrackInfo->loadTrack(pTrack); // NULL is fine. @@ -1324,12 +1307,12 @@ void WTrackMenu::showDlgTagFetcher(QModelIndex index) { void WTrackMenu::slotShowTrackInTagFetcher(TrackPointer pTrack) { if (m_pTagFetcher.isNull()) { m_pTagFetcher.reset(new DlgTagFetcher(nullptr)); - connect(m_pTagFetcher.data(), SIGNAL(next()), - this, SLOT(slotNextDlgTagFetcher())); - connect(m_pTagFetcher.data(), SIGNAL(previous()), - this, SLOT(slotPrevDlgTagFetcher())); - connect(m_pTagFetcher.data(), SIGNAL(finished(int)), - this, SLOT(slotTagFetcherClosed())); + connect(m_pTagFetcher.data(), &DlgTagFetcher::next, + this, &WTrackMenu::slotNextDlgTagFetcher); + connect(m_pTagFetcher.data(), &DlgTagFetcher::previous, + this, &WTrackMenu::slotPrevDlgTagFetcher); + connect(m_pTagFetcher.data(), &DlgTagFetcher::finished, + this, &WTrackMenu::slotTagFetcherClosed); } // NULL is fine diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index d60cf7940ad6..ce040f872534 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -36,8 +36,8 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_loadCachedOnly(false) { // Connect slots and signals to make the world go 'round. - connect(this, SIGNAL(doubleClicked(const QModelIndex &)), - this, SLOT(slotMouseDoubleClicked(const QModelIndex &))); + connect(this, &WTrackTableView::doubleClicked, + this, &WTrackTableView::slotMouseDoubleClicked); m_pCOTGuiTick = new ControlProxy("[Master]", "guiTick50ms", this); m_pCOTGuiTick->connectValueChanged(this, &WTrackTableView::slotGuiTick50ms); @@ -50,16 +50,15 @@ WTrackTableView::WTrackTableView(QWidget * parent, m_pSortOrder = new ControlProxy("[Library]", "sort_order", this); m_pSortOrder->connectValueChanged(this, &WTrackTableView::applySortingIfVisible); - connect(this, SIGNAL(scrollValueChanged(int)), - this, SLOT(slotScrollValueChanged(int))); + connect(this, &WTrackTableView::scrollValueChanged, + this, &WTrackTableView::slotScrollValueChanged); QShortcut *setFocusShortcut = new QShortcut( QKeySequence(tr("ESC", "Focus")), this); - connect(setFocusShortcut, SIGNAL(activated()), - this, SLOT(setFocus())); + connect(setFocusShortcut, &QShortcut::activated, + this, qOverload<>(&WTrackTableView::setFocus)); m_pMenu = new WTrackMenu(this, pConfig, m_pTrackCollectionManager); - // Receive signal from menu and emit from here. connect(m_pMenu, &WTrackMenu::loadTrackToPlayer, this, &WTrackTableView::loadTrackToPlayer); } @@ -244,8 +243,11 @@ void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { if (m_sorting) { // NOTE: Should be a UniqueConnection but that requires Qt 4.6 - connect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), - this, SLOT(slotSortingChanged(int, Qt::SortOrder)), Qt::AutoConnection); + // But Qt::UniqueConnections do not work for lambdas, non-member functions + // and functors; they only apply to connecting to member functions. + // https://doc.qt.io/qt-5/qobject.html#connect + connect(horizontalHeader(), &QHeaderView::sortIndicatorChanged, + this, &WTrackTableView::slotSortingChanged, Qt::AutoConnection); int sortColumn; Qt::SortOrder sortOrder; From 96a6b8c8520846d0e7d0e2000a752e9bd12dc631 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 6 Apr 2020 18:35:33 +0200 Subject: [PATCH 203/393] searchbox: display search text after skin change or reload --- src/widget/wsearchlineedit.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/widget/wsearchlineedit.cpp b/src/widget/wsearchlineedit.cpp index d526cf8e48dc..c971b089b2af 100644 --- a/src/widget/wsearchlineedit.cpp +++ b/src/widget/wsearchlineedit.cpp @@ -182,8 +182,11 @@ void WSearchLineEdit::resizeEvent(QResizeEvent* e) { // we will resize the Clear button icon only if height has changed. if (m_clearButton->size().height() != m_innerHeight) { QSize newSize = QSize(m_innerHeight, m_innerHeight); - m_clearButton->resize(m_innerHeight, m_innerHeight); + m_clearButton->resize(newSize); m_clearButton->setIconSize(newSize); + // Note(ronso0): For some reason this ensures the search text + // is being displayed after skin change/reload. + updateEditBox(getSearchText()); } int top = rect().top() + m_frameWidth; if (layoutDirection() == Qt::LeftToRight) { From d94a183f79c93f7604692ce9f5f0f1e8cf63e1d4 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 15:13:55 +0200 Subject: [PATCH 204/393] controllers/controllermanager: Rename sanitizeString and move to anon NS --- src/controllers/controllermanager.cpp | 19 +++++++++++++------ src/controllers/controllermanager.h | 4 ---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 2d2f594e1880..a6ff633c0b27 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -39,6 +39,12 @@ const int kPollIntervalMillis = 5; const int kPollIntervalMillis = 1; #endif +// Strip slashes and spaces from device name, so that it can be used as config +// key or a filename. +QString sanitizeDeviceName(QString name) { + return name.replace(" ", "_").replace("/", "_").replace("\\", "_"); +} + } // anonymous namespace QString firstAvailableFilename(QSet& filenames, @@ -205,7 +211,7 @@ QList ControllerManager::getControllerList(bool bOutputDevices, boo } QString ControllerManager::getConfiguredPresetFileForDevice(QString name) { - return m_pConfig->getValueString(ConfigKey("[ControllerPreset]", sanitizeString(name))); + return m_pConfig->getValueString(ConfigKey("[ControllerPreset]", sanitizeDeviceName(name))); } void ControllerManager::slotSetUpDevices() { @@ -224,7 +230,7 @@ void ControllerManager::slotSetUpDevices() { } // The filename for this device name. - QString deviceName = sanitizeString(name); + QString deviceName = sanitizeDeviceName(name); if (m_pConfig->getValueString(ConfigKey("[Controller]", deviceName)) != "1") { continue; } @@ -352,7 +358,7 @@ void ControllerManager::openController(Controller* pController) { // Update configuration to reflect controller is enabled. m_pConfig->setValue( - ConfigKey("[Controller]", sanitizeString(pController->getName())), 1); + ConfigKey("[Controller]", sanitizeDeviceName(pController->getName())), 1); } } @@ -364,7 +370,7 @@ void ControllerManager::closeController(Controller* pController) { maybeStartOrStopPolling(); // Update configuration to reflect controller is disabled. m_pConfig->setValue( - ConfigKey("[Controller]", sanitizeString(pController->getName())), 0); + ConfigKey("[Controller]", sanitizeDeviceName(pController->getName())), 0); } bool ControllerManager::loadPreset(Controller* pController, @@ -376,7 +382,7 @@ bool ControllerManager::loadPreset(Controller* pController, // Save the file path/name in the config so it can be auto-loaded at // startup next time m_pConfig->set( - ConfigKey("[ControllerPreset]", sanitizeString(pController->getName())), + ConfigKey("[ControllerPreset]", sanitizeDeviceName(pController->getName())), preset->filePath()); return true; } @@ -392,10 +398,11 @@ void ControllerManager::slotSavePresets(bool onlyActive) { if (onlyActive && !pController->isOpen()) { continue; } + ControllerPresetPointer pPreset = pController->getPreset(); DEBUG_ASSERT(!pPreset); - QString deviceName = sanitizeString(pController->getName()); + QString deviceName = sanitizeDeviceName(pController->getName()); if (!pPreset->isDirty()) { qWarning() << "Preset for device" << deviceName diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index e291327dd2e6..dae740ee713c 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -89,10 +89,6 @@ class ControllerManager : public QObject { void stopPolling(); void maybeStartOrStopPolling(); - static QString sanitizeString(QString name) { - return name.replace(" ", "_").replace("/", "_").replace("\\", "_"); - } - private: UserSettingsPointer m_pConfig; ControllerLearningEventFilter* m_pControllerLearningEventFilter; From 3e0729ad7eb8edc1d139fe903a7e24ed1af2b456 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 18:55:32 +0200 Subject: [PATCH 205/393] controllers/controllermanager: Fix assertion if no preset is configured --- src/controllers/controllermanager.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index a6ff633c0b27..f965eae21527 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -399,10 +399,14 @@ void ControllerManager::slotSavePresets(bool onlyActive) { continue; } + QString deviceName = sanitizeDeviceName(pController->getName()); + ControllerPresetPointer pPreset = pController->getPreset(); - DEBUG_ASSERT(!pPreset); + if (!pPreset) { + qDebug() << "Device" << deviceName << "has no configurated preset"; + continue; + } - QString deviceName = sanitizeDeviceName(pController->getName()); if (!pPreset->isDirty()) { qWarning() << "Preset for device" << deviceName From 8a30f3a5214ebdaac5996db5fa96bbf38d79ea85 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 18:56:34 +0200 Subject: [PATCH 206/393] controllers/controllermanager: Use qDebug() instead of qWarning() --- src/controllers/controllermanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index f965eae21527..eaa860089f36 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -408,7 +408,7 @@ void ControllerManager::slotSavePresets(bool onlyActive) { } if (!pPreset->isDirty()) { - qWarning() + qDebug() << "Preset for device" << deviceName << "is not dirty, no need to save it to the user presets."; continue; From 126c264d8e419933f0e04a8e9b07cff78eb406a2 Mon Sep 17 00:00:00 2001 From: Harshit Maurya Date: Mon, 6 Apr 2020 22:29:36 +0530 Subject: [PATCH 207/393] WTrackMenu: rename action to reasonable name --- src/widget/wtrackmenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/wtrackmenu.cpp b/src/widget/wtrackmenu.cpp index c6b0be9eb538..7ffa7d32bbf2 100644 --- a/src/widget/wtrackmenu.cpp +++ b/src/widget/wtrackmenu.cpp @@ -921,7 +921,7 @@ void WTrackMenu::slotPopulateCrateMenu() { } m_pCrateMenu->addSeparator(); - QAction* newCrateAction = new QAction(tr("Create New Crate"), m_pCrateMenu); + QAction* newCrateAction = new QAction(tr("Add to New Crate"), m_pCrateMenu); m_pCrateMenu->addAction(newCrateAction); connect(newCrateAction, &QAction::triggered, this, &WTrackMenu::addSelectionToNewCrate); m_bCrateMenuLoaded = true; From a492fbda7029774b4041267564183af5ab555438 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 6 Apr 2020 19:06:20 +0200 Subject: [PATCH 208/393] Tango: don't set Searchbox font in skin --- res/skins/Tango/style.qss | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/res/skins/Tango/style.qss b/res/skins/Tango/style.qss index fd41769564f3..605a8e492384 100644 --- a/res/skins/Tango/style.qss +++ b/res/skins/Tango/style.qss @@ -23,9 +23,7 @@ WNumber, WTrackProperty, WBeatSpinBox, WEffectSelector, WEffectSelector QAbstractScrollArea, -WSearchLineEdit, WPushButton, -WSearchLineEdit, #LibraryContainer QPushButton, #LibraryContainer QLabel, #LibraryContainer QRadioButton, @@ -48,7 +46,6 @@ WPushButton { } WPushButton, -WSearchLineEdit, #LibraryContainer QPushButton, #LibraryContainer QLabel, #LibraryContainer QRadioButton, @@ -2873,7 +2870,7 @@ WSearchLineEdit { padding: 2px; border: 1px solid #656565; color: #cfcfcf; - background: #0f0f0f; + background-color: #0f0f0f; selection-color: #0f0f0f; selection-background-color: #d2d2d2; /* margin will increase the Qt widget size which is used to position the From e4e8abd6544f9094dbe7fc99ccf2d453ebb0daa2 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 6 Apr 2020 19:09:18 +0200 Subject: [PATCH 209/393] Shade: move Searchbox qss to style.qss --- res/skins/Shade/preview_deck.xml | 507 ++++++++++++++++--------------- res/skins/Shade/skin.xml | 33 +- res/skins/Shade/style.qss | 4 + 3 files changed, 271 insertions(+), 273 deletions(-) diff --git a/res/skins/Shade/preview_deck.xml b/res/skins/Shade/preview_deck.xml index 444c38973756..3356f10f77b2 100644 --- a/res/skins/Shade/preview_deck.xml +++ b/res/skins/Shade/preview_deck.xml @@ -1,263 +1,274 @@ diff --git a/res/skins/Shade/skin.xml b/res/skins/Shade/skin.xml index 3ced0911fd62..15f6dd36d9bb 100644 --- a/res/skins/Shade/skin.xml +++ b/res/skins/Shade/skin.xml @@ -444,33 +444,13 @@ vertical - - - - horizontal - -