From 2eeb5a666b16755f6ccb93d06f0e60847e93dc57 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 10 Dec 2019 15:19:22 -0600 Subject: [PATCH 001/203] 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 0d89508ab52..c0d5b596064 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 e0cb88ed5aa..39f20ce740c 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 d6f341b959d..a616322a2d0 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 b81763667116376e39a7480c82499d4b34be4b54 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 4 Mar 2020 22:10:56 +0100 Subject: [PATCH 002/203] 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 aa5fba2edf6..6b8310425aa 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 4740b2c5c08..59e842bd9a5 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 a45fa51965b..fea7a461229 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 55e3e0d38d7..be6d764be35 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 00000000000..0b5757a1cc1 --- /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 00000000000..78936b87f03 --- /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 732624df6d4..2eace687f1a 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 1d3e00ac8f3..3348624efdf 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 003/203] 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 6b8310425aa..6c5e73dfe2f 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 59e842bd9a5..8aecd5b5246 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 00000000000..afb31d03b34 --- /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 00000000000..cf7745705cd --- /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 2eace687f1a..eb807f45b49 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 3348624efdf..e92e9c3f6d0 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 004/203] 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 68c52afa6d0..d1b125a57e5 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 6e7149a710a..d6cad06cf5a 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 fd560fbc59b..0a2b17807cb 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 8d0321ff9c0..b1b19e9f005 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 6d4d552522a..68f2aa87948 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 16db84caabf..23ed68092c4 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 fea7a461229..2c953846534 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 be6d764be35..be574d22c57 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 29d7e7dbfd4..99a54c88105 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 b66b3519191..96e5b91a8ac 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 0b5757a1cc1..e09e4d1be0f 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 78936b87f03..16aeff967fb 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 eb807f45b49..a82f490e894 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 e92e9c3f6d0..dd4e31ed0ea 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 c56f9051c88..62d5a86d404 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 985e7609bd0..a8c8b2ec573 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 3948a1730d4..a596b38a23b 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 617b805d018..0176555ebf4 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 01f6fd23356..0ac0bdc3877 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 a6f4f07bc45..2ef5a410e94 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 870e35edb2e..2be31a0bbae 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 005/203] 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 e09e4d1be0f..d736c13e842 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 006/203] 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 d736c13e842..5ee40d6bfe1 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 007/203] 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 5ee40d6bfe1..ee92114ae14 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 008/203] 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 ec74976f7f5..51ded8c0669 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 009/203] 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 51ded8c0669..b9fa4373523 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 1fa873ba4bc..0f2c45d1265 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 010/203] 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 b9fa4373523..9a997652866 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 011/203] 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 9a997652866..8b89af3e5a3 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 0f2c45d1265..03275338946 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 6217a12f13a..729e041447e 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 012/203] 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 729e041447e..1ea7ad97ba0 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 013/203] 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 3e8c1d581f4..bf0051209af 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 014/203] 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 8b89af3e5a3..499798a690f 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 03275338946..e291327dd2e 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 1ea7ad97ba0..0e35ebd95a6 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 015/203] 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 499798a690f..2843f5371a8 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 016/203] 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 456e0556307..237cda87745 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 7dc3756e414..253aa412d6d 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 877897f00f1..2f613c954ba 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 12ef6f0a13d..4bc131daba7 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 32e01244768..dc90812b8f6 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 ac520908638..1e44a89e850 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 017/203] 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 2843f5371a8..2d2f594e188 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 ad4019632e1..19fcfefad9e 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 f50b8bd1b23..22e045bef52 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 4bc131daba7..1c25659059b 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 018/203] 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 0e35ebd95a6..92882863964 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 019/203] 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 08bf006daa6..eeebc7ffbf7 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 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 020/203] 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 5e1ca4d5561..9c8b12d4c29 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 021/203] 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 9c8b12d4c29..24c7333f2d7 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 022/203] 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 62d958fbe16..9335edd13b5 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 023/203] 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 00000000000..e37068b2e2f --- /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 00000000000..4ee83b3d2d1 --- /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 a32808fe0a2204ca7b657aeb14d3fad259fd60af Mon Sep 17 00:00:00 2001 From: Tobias Date: Fri, 13 Mar 2020 11:19:22 +0100 Subject: [PATCH 024/203] 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 00000000000..380f41d81bb --- /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 00000000000..eb68ffa6a6f --- /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 025/203] 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 380f41d81bb..5b79f577633 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 eb68ffa6a6f..65f0bdef4cd 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 026/203] 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 5b79f577633..b4efa1207b6 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 027/203] 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 b4efa1207b6..5a29a3ed7a6 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 028/203] 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 5a29a3ed7a6..07cef3af517 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 029/203] 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 07cef3af517..f022f2a6756 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 030/203] 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 f022f2a6756..32d2b9e0cb6 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 65f0bdef4cd..42eec769333 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 031/203] 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 6888e8db98b..2bd28aa27b4 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 eeebc7ffbf7..3f053f56275 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 db6deb0ed7b..26d59f5eccb 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 032/203] 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 15c3fa77af2..7869dd640a5 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 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 033/203] 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 2bd28aa27b4..9cce1909c11 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 9feb41a873b..fb77d862d3b 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 3f053f56275..023567dbc7d 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 034/203] 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 9cce1909c11..cc4948cd099 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 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 035/203] 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 9335edd13b5..62d958fbe16 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 036/203] 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 cc4948cd099..586d6b9ff9d 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 037/203] 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 fed31b32743..1b01ca9973b 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 39ce03e49e1..3816073f744 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 586d6b9ff9d..de37ec7e605 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 1354df10af7..410f9542af3 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 038/203] 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 de37ec7e605..12bfb81cd1a 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 fb77d862d3b..5ced4c710cf 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 039/203] 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 f5ff71be249..d6d57b15e6f 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 040/203] 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 12bfb81cd1a..cca242c7f43 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 023567dbc7d..5e621e0780a 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 041/203] 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 d6d57b15e6f..0e6dd4286b4 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 26d59f5eccb..7dabeb4273d 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 e07af9ebe45ee3d528ebfb69e0881f029f9cd0f5 Mon Sep 17 00:00:00 2001 From: Philip Gottschling Date: Wed, 25 Mar 2020 14:30:16 +0100 Subject: [PATCH 042/203] 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 a6f7f0802ea..9affe5f545a 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 59bfd428f48..35e53669736 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 2d406a7d439..b1fdad21cac 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 de18a0eb64f..f2932ffc8b6 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 27e04c8282f..9aa425c0124 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 78965cf65df..87de8eba241 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 043/203] 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 35e53669736..bb5f322a0e7 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 f2932ffc8b6..ed21f7473da 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 044/203] 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 26d59f5eccb..4a5a9ac7048 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 a1f910fc3be..87deb01f2f7 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 045/203] 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 24c7333f2d7..6e89c14f00d 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 9786c10b592..4da688b96ac 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 12bfb81cd1a..d2573ef8fee 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 5ced4c710cf..09a42b26fc2 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 023567dbc7d..7f3a6dd6884 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 046/203] 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 6e89c14f00d..2cc0b709e39 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 4da688b96ac..308154a1990 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 047/203] 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 a616322a2d0..b89a809c5eb 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 048/203] 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 b89a809c5eb..fed1df3aa65 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 049/203] 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 4c1d1e16945..57fb727f813 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 09a42b26fc2..e03b1bd7d8a 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 050/203] 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 57fb727f813..8bfe5ac1153 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 051/203] 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 51d49776a42..2a27b65008e 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 052/203] 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 308154a1990..d70c36f747a 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 053/203] 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 2cc0b709e39..f8eff4f3538 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 054/203] 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 8bfe5ac1153..5eac89521ce 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 e03b1bd7d8a..01fe4d660db 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 055/203] 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 5eac89521ce..d76a525570d 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 056/203] 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 f8eff4f3538..717e158693b 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 d70c36f747a..4c52d4a26f2 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 057/203] 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 e37068b2e2f..1b5ba44c3de 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 4ee83b3d2d1..e97d1091927 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 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 058/203] 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 62d958fbe16..291e9aef668 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 1b01ca9973b..238bff1dd88 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 0e6dd4286b4..9d4d7186a96 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 410f9542af3..d52852a9439 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 5fd3125a0b1..4ff3d79e57f 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 059/203] 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 32d2b9e0cb6..1a90df137f9 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 060/203] 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 fed1df3aa65..52342ce9903 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 061/203] 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 c0d5b596064..c7bbcb331a1 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 062/203] 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 c7bbcb331a1..34bbd94225a 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 39f20ce740c..58a0c81849d 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 f4b77bc83c533b9e2c73fcee61691a66d4cf1a6a Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sat, 28 Mar 2020 23:59:57 +0100 Subject: [PATCH 063/203] 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 1b5ba44c3de..7c3234ce14a 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 064/203] 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 a055030a648..3511d50931d 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 cf9b7c4108d..8bf25a23689 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 00000000000..020b8cd6c32 --- /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 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 065/203] 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 020b8cd6c32..c41453075f3 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 066/203] fix typo --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 3511d50931d..764c2547d9d 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 067/203] 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 8bf25a23689..ea0f13baca7 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 068/203] 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 c41453075f3..32e581c3bab 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 069/203] 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 bb5f322a0e7..b0b9a06372d 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 b1fdad21cac..517a511126c 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 b5284e239c030cb4f2cbf899dd57298aefe156ce Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sun, 29 Mar 2020 16:15:08 +0200 Subject: [PATCH 070/203] 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 7c3234ce14a..86c2a828726 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 a204f7270204c3c9a1f4ed33aa8c0c1d28a611ab Mon Sep 17 00:00:00 2001 From: nuess0r Date: Sun, 29 Mar 2020 16:45:51 +0200 Subject: [PATCH 071/203] 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 86c2a828726..fa2b06f18da 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 072/203] 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 fa2b06f18da..cf91b69acc9 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 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 073/203] 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 32e581c3bab..be1d9b4f71d 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 074/203] 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 cf91b69acc9..a70ae4a7897 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 e97d1091927..0caf19fc503 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 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 075/203] 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 be1d9b4f71d..a3129a62c34 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 076/203] 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 717e158693b..ad40f2527df 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 4c52d4a26f2..4871f0c6907 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 d76a525570d..240a4a9d897 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 01fe4d660db..739aafd95e6 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 2a27b65008e..c9d3f25a375 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 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 077/203] 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 ad40f2527df..2cd90ecd1d0 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 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 078/203] 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 b0b9a06372d..0b638185af4 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 079/203] 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 0b638185af4..a870b8444ad 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 080/203] 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 2cd90ecd1d0..75a326cbd99 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 081/203] 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 240a4a9d897..d373a15878f 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 739aafd95e6..eddc07cc6de 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 082/203] 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 d373a15878f..7fb144d0ced 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 eddc07cc6de..caec1a0d7ab 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 989d33556bd04da77daf9c4d425f906048ce66db Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 29 Mar 2020 19:53:34 -0500 Subject: [PATCH 083/203] 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 75a326cbd99..e6514976326 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 4871f0c6907..abc812e29c6 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 084/203] 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 75a326cbd99..f9a491f41f9 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 4871f0c6907..9996a2f1a9a 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 7fb144d0ced..7aa7e350da8 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 085/203] 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 1a90df137f9..f26d246d941 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 086/203] 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 b51f5d3657d..705a359078c 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 087/203] 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 5053489dca0..ed9f31a04b3 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 cc6572f8452..0f114321971 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 00000000000..a86f4490334 --- /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 00000000000..d2e8ffeddf9 --- /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 00000000000..d97c2c113ce --- /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 00000000000..8a70a6bd391 --- /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 00000000000..f7addcc954d --- /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 00000000000..497fa2102e6 --- /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 412c342cc15..51349d4ca5e 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 0d933f9032b..fae49b50dcd 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 accda62304a..080c1391844 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 5767794f366..6fe9139b0c3 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 088/203] 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 468ed741ed2..c936de1515c 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 3dd1ac7310e..c24442a90f5 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 8e3f977ad0c..b250c48f37e 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 609cf54fb58..f8ba5d827ed 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 c632158b202..e268cfee044 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 f30a951db8f..d46f342ba69 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 c17973804d2..c403d16bf6d 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 53d07370a7e..7ee14e084c2 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 59a19a8af8a..4e62432e599 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 7ac8d611003..8212ecb75d1 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 732cebc00da..9409ad97a18 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 9f44ec3d7b9..7e9409a9b23 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 24fef328179..8ef45930282 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 014b369b378..b7f3d780e68 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 ca73661aa6f..2f535ae4f9e 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 0f691a53eb4..72c13b84e0a 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 9f96a1b1d12..73aa03acee2 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 32d0beba39e..9ff89c781f3 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 4f0398b2882..4de6b7905e9 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 74ccc9a7c6c..2759b7aae5a 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 6678c3ae3df..26142824eae 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 d4ca66a6177..461b9ea2b0c 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 e82f6940bd8..f13c171c433 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 f50b09c82d7..7c1be958462 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 c1aaaf8b0a4..60becff8625 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 05518288448..d33bf680118 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 661c362cc3d..2332495c817 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 8a60cb03dbd..1d0f103a8a7 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 dcbf2075336..b4ed1a5ade2 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 f0980c3cf9d..47f2429fbde 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 26224002ffc..e8b87ae1d31 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 d1253761c77..aff28911ac6 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 36915f5195d..907cbce6db0 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 9fc62a86fd8..907b3265e4e 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 764db3e6b7c..81539368454 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 8154d6104d9..5fba867a677 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 de18a0eb64f..cb3a547a137 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 7ba0d6ba7c5..1cb4399392a 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 4a34c98524c..8d33143673e 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 9b899bc451a..4a28fa19ad9 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 72f78805e05..75fc8584382 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 62115a259f4..f0a8bee176d 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 232de95ea4e..fdd72f1fc2f 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 f4b45125aa0..4c9da32339f 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 7ba0ad99251..5cdb1fee6c3 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 14f605c28f7..fdda9fc0634 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 27e04c8282f..00c907f0bb6 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 78965cf65df..530a1d3e2a4 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 4adcdee6520..203bb1ce281 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 367a4eb53d3..9f784a181bd 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 b372430a186..8085e260b55 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 9ca165c4bed..07bdc30b22a 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 d70673fee28..d0c217cf474 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 50ac1204544..7df77ca474e 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 754157d2d58..21a17637b87 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 089/203] 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 ed9f31a04b3..61519430318 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 0f114321971..21c74600e89 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 a6f7f0802ea..88b714ddc2b 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 c936de1515c..d014aae440f 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 9409ad97a18..a24c9605d96 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 5f2182acc47..23645363881 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 c25acad437e..7775926b4b2 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 26142824eae..2dca3e0aa87 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 461b9ea2b0c..a5cb7379cd2 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 00000000000..33fa6978e76 --- /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 8571e203645..95bdd0d1cbd 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 f863af05654..48bd4a4b025 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 8995fe07434..766cdcf9f13 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 b3f39d9f057..8df108ad0ef 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 f13c171c433..d9648c97dae 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 78409509176..3f6b52c1c50 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 284aeb0d8c4..81033351618 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 60becff8625..ee798604afc 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 cac4d57a8b8..4def111afe6 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 d33bf680118..a31dc5bb91b 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 2332495c817..892b86a79c1 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 ce8fc27c414..1481102b59b 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 1d0f103a8a7..ce7f5542650 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 dde6d7760ef..cb15224844a 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 e1abfc08356..e0c26d1d3ae 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 1d7deabb64b..c97eddfeed2 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 88e551d0e1c..a39242cb158 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 fdda9fc0634..7eacf9d231a 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 7df77ca474e..00000000000 --- 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 21a17637b87..00000000000 --- 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 090/203] 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 00c907f0bb6..85b9ea1584c 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 530a1d3e2a4..6bc212a2bda 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 091/203] 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 497fa2102e6..1da4c5a5b74 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 092/203] 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 d2e8ffeddf9..df08b1eb6ce 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 a5cb7379cd2..834e5a43514 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 093/203] 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 e6514976326..91fbd86a7bd 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 094/203] 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 1da4c5a5b74..64b55f260e0 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 095/203] 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 91fbd86a7bd..4ef30cb6bc8 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 096/203] 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 4ef30cb6bc8..21f3143a5a1 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 abc812e29c6..4d5293acdee 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 097/203] 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 21f3143a5a1..5f7d0c595f4 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 098/203] 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 a3129a62c34..326ac4df345 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 099/203] 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 326ac4df345..fc66dd5237d 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 100/203] 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 fc66dd5237d..87f43d6d20e 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 101/203] 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 87f43d6d20e..0a2572fc431 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 102/203] 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 0a2572fc431..1b4ce8546d7 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 103/203] Simpliy Travis CI config file --- .travis.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 705a359078c..5287e8ede65 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 104/203] 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 5f7d0c595f4..dfeb671e9a9 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 105/203] 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 dfeb671e9a9..30d4c946e6e 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 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 106/203] 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 4d5293acdee..cfe7b3d1101 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 3816073f744..df9f37d0787 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 d52852a9439..bdd0f859b4c 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 019739326fce10b4679a1dbc160f8a107d337b5e Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 1 Apr 2020 03:18:46 +0200 Subject: [PATCH 107/203] 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 ca73661aa6f..27506c7a592 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 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 108/203] 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 ec1a7bb851a..f30766d37e5 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 109/203] 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 505d62f51d7..d4dff089bf0 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 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 110/203] 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 7aa7e350da8..a3e09d090d9 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 caec1a0d7ab..9ef61679cf7 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 a88c5b0766c5aacc0c7a80b3cb44146e8d6c3bbd Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Thu, 2 Apr 2020 23:35:02 +0200 Subject: [PATCH 111/203] 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 579ad6b3c04..b017974a0c9 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 5407844c049da01084ac405a1c27e54b0021f9b8 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 2 Apr 2020 23:58:15 +0200 Subject: [PATCH 112/203] 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 df08b1eb6ce..0da1eef6b9c 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 7af708071a0837976d0700ff5d37a60d30350378 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Fri, 3 Apr 2020 10:18:54 +0200 Subject: [PATCH 113/203] 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 1b4ce8546d7..079736d4d70 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 114/203] 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 834e5a43514..5ca3ec752bf 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 115/203] 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 2dca3e0aa87..91ab264d91c 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 892b86a79c1..b01b37f478e 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 116/203] 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 42eec769333..0aa79f6cd8a 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 117/203] changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ece5b56cb5c..761be54aa50 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 118/203] 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 09a10ad9253..03c6299d3c3 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 119/203] 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 122c4ea08bb..9398f51507d 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 120/203] 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 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 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 121/203] 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 3d6fd78357c..1b0ebc22e40 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 bac05768b4a..b39999ed709 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 454b6be64c0..c8414a7f581 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 0c78643c200..5e1d0f04871 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 97c0a547b37..12eae2b8da2 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 122/203] 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 a70ae4a7897..c0c2ebc70cb 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 0caf19fc503..e095496d818 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 123/203] 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 e095496d818..c91fe88fd7c 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 124/203] Added Stanton DJC.4 to the 2.2.4 CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ece5b56cb5c..9634f2aceeb 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 125/203] 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 64b55f260e0..72872d68702 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 126/203] 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 9d24d838077..a6956349994 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 02e206c7dd5e5c089275861c1843a9cd3edb7871 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 4 Apr 2020 18:45:31 +0200 Subject: [PATCH 127/203] 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 080c1391844..5af4e0267be 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 128/203] 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 5af4e0267be..54fe79cca25 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 35a6a1316fbf2fd9e577f3e75166307974047500 Mon Sep 17 00:00:00 2001 From: ehendrikd Date: Sun, 5 Apr 2020 11:38:54 +1000 Subject: [PATCH 129/203] 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 a6956349994..4ce2a204f9c 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 130/203] 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 61848f1479f..0a66cdafcea 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 ba13ac6e0b32ab05d578d15296ca7bb28025247a Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 5 Apr 2020 09:58:24 -0500 Subject: [PATCH 131/203] 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 0a66cdafcea..d6b1fc58c0b 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 132/203] 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 4cefc5cced1..f1d2db26b86 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 133/203] 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 833a5ad64f1..29c5541e12c 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 134/203] 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 d6b1fc58c0b..ca2004c9720 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 a3db3a3ea2f70891904f196ff8bf0108998a28ce Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 6 Apr 2020 14:16:57 +0200 Subject: [PATCH 135/203] 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 f7addcc954d..3d01580fbd9 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 136/203] 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 b250c48f37e..028c30a3d46 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 f8ba5d827ed..ced4530fab9 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 e268cfee044..89e19a2a60f 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 c403d16bf6d..2b88e9e2824 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 4e62432e599..da1fb790d3a 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 b7f3d780e68..e86a067c16f 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 137/203] 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 89d69b7ce0d..4639b663b01 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 0a2b1cd88db458670d74f3909264e34d3e37a467 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Mon, 6 Apr 2020 15:32:58 +0200 Subject: [PATCH 138/203] 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 2b88e9e2824..fd100a6c14a 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 d94a183f79c93f7604692ce9f5f0f1e8cf63e1d4 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 15:13:55 +0200 Subject: [PATCH 139/203] 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 2d2f594e188..a6ff633c0b2 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 e291327dd2e..dae740ee713 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 140/203] 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 a6ff633c0b2..f965eae2152 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 141/203] 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 f965eae2152..eaa860089f3 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 5277c500e8a724263c0e9ef82fb4328f972abc24 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 19:25:01 +0200 Subject: [PATCH 142/203] controllers/dlgprefcontroller: Move shared enumeration code to method --- src/controllers/dlgprefcontroller.cpp | 66 +++++++++++++-------------- src/controllers/dlgprefcontroller.h | 3 ++ 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 92882863964..e939147cbc7 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -239,46 +239,22 @@ void DlgPrefController::enumeratePresets() { // user has their controller plugged in) m_ui.comboBoxPreset->addItem("..."); - QList presets; PresetInfo match; - - // 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; - } - } + // Enumerate user presets + PresetInfo userPresetsMatch = enumeratePresetsFromEnumerator( + m_pControllerManager->getMainThreadUserPresetEnumerator()); + if (userPresetsMatch.isValid()) { + match = userPresetsMatch; } // Insert a separator between user presets (+ dummy item) and system presets m_ui.comboBoxPreset->insertSeparator(m_ui.comboBoxPreset->count()); - // 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; - } - } + // Enumerate system presets + PresetInfo systemPresetsMatch = enumeratePresetsFromEnumerator( + m_pControllerManager->getMainThreadSystemPresetEnumerator()); + if (systemPresetsMatch.isValid()) { + match = systemPresetsMatch; } QString configuredPresetFile = m_pControllerManager->getConfiguredPresetFileForDevice( @@ -296,6 +272,28 @@ void DlgPrefController::enumeratePresets() { } } +PresetInfo DlgPrefController::enumeratePresetsFromEnumerator( + QSharedPointer pPresetEnumerator) { + PresetInfo match; + + // Check if enumerator is ready. Should be rare. We will re-enumerate on + // the next open of the preferences. + if (!pPresetEnumerator.isNull()) { + // Making the list of presets in the alphabetical order + QList systemPresets = pPresetEnumerator->getPresetsByExtension( + m_pController->presetExtension()); + + for (const PresetInfo& preset : systemPresets) { + m_ui.comboBoxPreset->addItem(preset.getName(), preset.getPath()); + if (m_pController->matchPreset(preset)) { + match = preset; + } + } + } + + return match; +} + void DlgPrefController::slotUpdate() { enumeratePresets(); diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index e0cb88ed5aa..973478c398b 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -23,6 +23,7 @@ // Forward declarations class Controller; class ControllerManager; +class PresetInfoEnumerator; class DlgPrefController : public DlgPreferencePage { Q_OBJECT @@ -88,6 +89,8 @@ class DlgPrefController : public DlgPreferencePage { // Reload the mappings in the dropdown dialog void enumeratePresets(); + PresetInfo enumeratePresetsFromEnumerator( + QSharedPointer pPresetEnumerator); void enableDevice(); void disableDevice(); From 6367448993658ebacd7963b57c4503cbd75086b0 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 19:29:08 +0200 Subject: [PATCH 143/203] controllers/dlgprefcontroller: Improve some comments --- src/controllers/dlgprefcontroller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index e939147cbc7..f39deaa508e 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -276,10 +276,10 @@ PresetInfo DlgPrefController::enumeratePresetsFromEnumerator( QSharedPointer pPresetEnumerator) { PresetInfo match; - // Check if enumerator is ready. Should be rare. We will re-enumerate on - // the next open of the preferences. + // Check if enumerator is ready. Should be rare that it isn't. We will + // re-enumerate on the next open of the preferences. if (!pPresetEnumerator.isNull()) { - // Making the list of presets in the alphabetical order + // Get a list of presets in alphabetical order QList systemPresets = pPresetEnumerator->getPresetsByExtension( m_pController->presetExtension()); From c2e2db69d5ef68d1cc4355bef2f3cd3a3259831c Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 19:52:35 +0200 Subject: [PATCH 144/203] controllers/controllermanager: Improve device setup code --- src/controllers/controllermanager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index eaa860089f3..a1e0ab63800 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -231,10 +231,13 @@ void ControllerManager::slotSetUpDevices() { // The filename for this device name. QString deviceName = sanitizeDeviceName(name); - if (m_pConfig->getValueString(ConfigKey("[Controller]", deviceName)) != "1") { + + // Check if device is enabled + if (!m_pConfig->getValue(ConfigKey("[Controller]", deviceName), 0)) { continue; } + // Check if device has a configured preset QString presetFile = getConfiguredPresetFileForDevice(deviceName); if (presetFile.isEmpty()) { continue; From 08d52d93b4d04e2588aebcf5ef437b28446a3ff0 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Mon, 6 Apr 2020 19:53:16 +0200 Subject: [PATCH 145/203] controllers/controllermanager: Remove unused variable --- src/controllers/controllermanager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index a1e0ab63800..dfccefee22e 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -220,8 +220,6 @@ void ControllerManager::slotSetUpDevices() { updateControllerList(); QList deviceList = getControllerList(false, true); - QSet filenames; - foreach (Controller* pController, deviceList) { QString name = pController->getName(); From 1072d951a32ec212551255154fff3502cbd7f41d Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 6 Apr 2020 15:02:48 -0500 Subject: [PATCH 146/203] update hidapi to 0.9.0 The original maintainer of hidapi (signal11) stopped responding to communication for a long time, so hidapi has been forked. It is now hosted by libusb at https://github.com/libusb/hidapi 0.9.0 is the first stable release since the fork. --- CMakeLists.txt | 8 +- build/features.py | 4 +- lib/hidapi-0.8.0-rc1/doxygen/Doxyfile | 1630 ----------- .../.gitattributes | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/.gitignore | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/AUTHORS.txt | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/HACKING.txt | 0 .../LICENSE-bsd.txt | 0 .../LICENSE-gpl3.txt | 0 .../LICENSE-orig.txt | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/LICENSE.txt | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/Makefile.am | 6 +- .../README.txt => hidapi/README.md} | 355 +-- lib/hidapi/android/jni/Android.mk | 19 + lib/{hidapi-0.8.0-rc1 => hidapi}/bootstrap | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/configure.ac | 36 +- lib/hidapi/doxygen/Doxyfile | 2482 +++++++++++++++++ .../hidapi/hidapi.h | 80 +- .../hidtest/.gitignore | 0 .../hidtest/Makefile.am | 0 .../hidtest/hidtest.cpp | 2 +- .../libusb/.gitignore | 0 .../libusb/Makefile-manual | 0 .../libusb/Makefile.am | 9 +- .../libusb/Makefile.freebsd | 0 .../libusb/Makefile.linux | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/libusb/hid.c | 111 +- .../linux/.gitignore | 0 .../linux/Makefile-manual | 0 .../linux/Makefile.am | 0 .../linux/README.txt | 12 +- lib/{hidapi-0.8.0-rc1 => hidapi}/linux/hid.c | 43 +- .../m4/.gitignore | 0 .../m4/ax_pthread.m4 | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/m4/pkg.m4 | 0 .../mac/.gitignore | 0 .../mac/Makefile-manual | 0 .../mac/Makefile.am | 0 lib/{hidapi-0.8.0-rc1 => hidapi}/mac/hid.c | 281 +- .../pc/.gitignore | 0 .../pc/hidapi-hidraw.pc.in | 0 .../pc/hidapi-libusb.pc.in | 0 .../pc/hidapi.pc.in | 0 .../testgui/.gitignore | 0 .../testgui/Makefile-manual | 0 .../testgui/Makefile.am | 0 .../testgui/Makefile.freebsd | 0 .../testgui/Makefile.linux | 0 .../testgui/Makefile.mac | 0 .../testgui/Makefile.mingw | 0 .../TestGUI.app.in/Contents/Info.plist | 0 .../testgui/TestGUI.app.in/Contents/PkgInfo | 0 .../Resources/English.lproj/InfoPlist.strings | Bin .../Contents/Resources/Signal11.icns | Bin .../testgui/copy_to_bundle.sh | 0 .../testgui/mac_support.cpp | 0 .../testgui/mac_support.h | 0 .../testgui/mac_support_cocoa.m | 0 .../testgui/start.sh | 0 .../testgui/test.cpp | 0 .../testgui/testgui.sln | 0 .../testgui/testgui.vcproj | 0 .../udev/99-hid.rules | 7 +- .../windows/.gitignore | 0 .../windows/Makefile-manual | 0 .../windows/Makefile.am | 0 .../windows/Makefile.mingw | 0 .../windows/hid.c | 71 +- .../windows/hidapi.sln | 0 .../windows/hidapi.vcproj | 0 .../windows/hidtest.vcproj | 0 71 files changed, 3112 insertions(+), 2044 deletions(-) delete mode 100644 lib/hidapi-0.8.0-rc1/doxygen/Doxyfile rename lib/{hidapi-0.8.0-rc1 => hidapi}/.gitattributes (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/AUTHORS.txt (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/HACKING.txt (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/LICENSE-bsd.txt (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/LICENSE-gpl3.txt (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/LICENSE-orig.txt (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/LICENSE.txt (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/Makefile.am (95%) rename lib/{hidapi-0.8.0-rc1/README.txt => hidapi/README.md} (51%) create mode 100644 lib/hidapi/android/jni/Android.mk rename lib/{hidapi-0.8.0-rc1 => hidapi}/bootstrap (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/configure.ac (84%) create mode 100644 lib/hidapi/doxygen/Doxyfile rename lib/{hidapi-0.8.0-rc1 => hidapi}/hidapi/hidapi.h (83%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/hidtest/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/hidtest/Makefile.am (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/hidtest/hidtest.cpp (99%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/libusb/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/libusb/Makefile-manual (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/libusb/Makefile.am (67%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/libusb/Makefile.freebsd (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/libusb/Makefile.linux (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/libusb/hid.c (93%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/linux/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/linux/Makefile-manual (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/linux/Makefile.am (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/linux/README.txt (86%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/linux/hid.c (96%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/m4/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/m4/ax_pthread.m4 (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/m4/pkg.m4 (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/mac/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/mac/Makefile-manual (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/mac/Makefile.am (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/mac/hid.c (82%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/pc/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/pc/hidapi-hidraw.pc.in (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/pc/hidapi-libusb.pc.in (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/pc/hidapi.pc.in (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/Makefile-manual (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/Makefile.am (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/Makefile.freebsd (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/Makefile.linux (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/Makefile.mac (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/Makefile.mingw (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/TestGUI.app.in/Contents/Info.plist (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/TestGUI.app.in/Contents/PkgInfo (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/TestGUI.app.in/Contents/Resources/English.lproj/InfoPlist.strings (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/TestGUI.app.in/Contents/Resources/Signal11.icns (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/copy_to_bundle.sh (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/mac_support.cpp (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/mac_support.h (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/mac_support_cocoa.m (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/start.sh (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/test.cpp (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/testgui.sln (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/testgui/testgui.vcproj (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/udev/99-hid.rules (84%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/.gitignore (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/Makefile-manual (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/Makefile.am (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/Makefile.mingw (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/hid.c (91%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/hidapi.sln (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/hidapi.vcproj (100%) rename lib/{hidapi-0.8.0-rc1 => hidapi}/windows/hidtest.vcproj (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d8c5f9385b..a3f849873b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1985,16 +1985,16 @@ if(HID) if(HIDAPI_STATIC) message(STATUS "Linking internal libhidapi statically") add_library(mixxx-hidapi STATIC EXCLUDE_FROM_ALL) - target_include_directories(mixxx-hidapi SYSTEM PUBLIC lib/hidapi-0.8.0-rc1/hidapi) + target_include_directories(mixxx-hidapi SYSTEM PUBLIC lib/hidapi/hidapi) if(WIN32) - target_sources(mixxx-hidapi PRIVATE lib/hidapi-0.8.0-rc1/windows/hid.c) + target_sources(mixxx-hidapi PRIVATE lib/hidapi/windows/hid.c) elseif(APPLE) - target_sources(mixxx-hidapi PRIVATE lib/hidapi-0.8.0-rc1/mac/hid.c) + target_sources(mixxx-hidapi PRIVATE lib/hidapi/mac/hid.c) elseif(UNIX) if(NOT LibUSB_FOUND) message(FATAL_ERROR "USB HID controller support on Unix with statically linked libhidapi-libusb requires libusb 1.0 and its development headers.") endif() - target_sources(mixxx-hidapi PRIVATE lib/hidapi-0.8.0-rc1/libusb/hid.c) + target_sources(mixxx-hidapi PRIVATE lib/hidapi/libusb/hid.c) target_link_libraries(mixxx-hidapi PRIVATE LibUSB::LibUSB) else() message(FATAL_ERROR "USB HID controller support only possible on Windows/Mac OS/Linux/BSD.") diff --git a/build/features.py b/build/features.py index 993d6a2824b..b701b9c4fcf 100644 --- a/build/features.py +++ b/build/features.py @@ -45,7 +45,7 @@ def sources(self, build): class HID(Feature): INTERNAL_LINK = False - HIDAPI_INTERNAL_PATH = 'lib/hidapi-0.8.0-rc1' + HIDAPI_INTERNAL_PATH = 'lib/hidapi' def description(self): return "HID controller support" @@ -707,7 +707,7 @@ def configure(self, build, conf): # https://bugs.launchpad.net/mixxx/+bug/1833225 if not conf.CheckForPKG('shout', '2.4.4'): self.INTERNAL_LINK = True - + if not self.INTERNAL_LINK: self.INTERNAL_LINK = not conf.CheckLib(['libshout', 'shout']) diff --git a/lib/hidapi-0.8.0-rc1/doxygen/Doxyfile b/lib/hidapi-0.8.0-rc1/doxygen/Doxyfile deleted file mode 100644 index 9d983e9f27e..00000000000 --- a/lib/hidapi-0.8.0-rc1/doxygen/Doxyfile +++ /dev/null @@ -1,1630 +0,0 @@ -# Doxyfile 1.7.1 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project -# -# All text after a hash (#) is considered a comment and will be ignored -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. - -PROJECT_NAME = hidapi - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = YES - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this -# tag. The format is ext=language, where ext is a file extension, and language -# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, -# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions -# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen to replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = YES - -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. - -TYPEDEF_HIDES_STRUCT = NO - -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penality. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will rougly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols - -SYMBOL_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespace are hidden. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = YES - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen -# will sort the (brief and detailed) documentation of class members so that -# constructors and destructors are listed first. If set to NO (the default) -# the constructors will appear in the respective orders defined by -# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. -# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO -# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. - -SORT_BY_SCOPE_NAME = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy -# in the documentation. The default is NO. - -SHOW_DIRECTORIES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. -# This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. The create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. -# You can optionally specify a file name after the option, if omitted -# DoxygenLayout.xml will be used as the name of the layout file. - -LAYOUT_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = ../hidapi - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 - -FILE_PATTERNS = - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = NO - -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded -# from the input. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. -# If FILTER_PATTERNS is specified, this tag will be -# ignored. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. -# Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. -# The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER -# is applied to all files. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. -# Otherwise they will link to the documentation. - -REFERENCES_LINK_SOURCE = YES - -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = YES - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# stylesheet in the HTML output directory as well, or it will be erased! - -HTML_STYLESHEET = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. -# Doxygen will adjust the colors in the stylesheet and background images -# according to this color. Hue is specified as an angle on a colorwheel, -# see http://en.wikipedia.org/wiki/Hue for more information. -# For instance the value 0 represents red, 60 is yellow, 120 is green, -# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. -# The allowed range is 0 to 359. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of -# the colors in the HTML output. For a value of 0 the output will use -# grayscales only. A value of 255 will produce the most vivid colors. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to -# the luminance component of the colors in the HTML output. Values below -# 100 gradually make the output lighter, whereas values above 100 make -# the output darker. The value divided by 100 is the actual gamma applied, -# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, -# and 100 does not change the gamma. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. - -HTML_TIMESTAMP = YES - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox -# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). - -HTML_DYNAMIC_SECTIONS = NO - -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. - -GENERATE_DOCSET = NO - -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output directory. - -CHM_FILE = - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. - -CHM_INDEX_ENCODING = - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated -# that can be used as input for Qt's qhelpgenerator to generate a -# Qt Compressed Help (.qch) of the generated HTML documentation. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders - -QHP_VIRTUAL_FOLDER = doc - -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to -# add. For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see -# -# Qt Help Project / Custom Filters. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's -# filter section matches. -# -# Qt Help Project / Filter Attributes. - -QHP_SECT_FILTER_ATTRS = - -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before -# the help appears. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. - -DISABLE_INDEX = NO - -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 4 - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. - -GENERATE_TREEVIEW = NO - -# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, -# and Class Hierarchy pages using a tree view instead of an ordered list. - -USE_INLINE_TREES = NO - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open -# links to external symbols imported via tag files in a separate window. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are -# not supported properly for IE 6.0, but are supported on all modern browsers. -# Note that when changing this option you need to delete any form_*.png files -# in the HTML output before the changes have effect. - -FORMULA_TRANSPARENT = YES - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box -# for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using -# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets -# (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. - -SEARCHENGINE = YES - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a PHP enabled web server instead of at the web client -# using Javascript. Doxygen will generate the search PHP script and index -# file to put on the web server. The advantage of the server -# based approach is that it scales better to large projects and allows -# full text search. The disadvances is that it is more difficult to setup -# and does not have live searching capabilities. - -SERVER_BASED_SEARCH = NO - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = NO - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. -# Note that when enabling USE_PDFLATEX this option is only used for -# generating bitmaps for formulas in the HTML output, but not in the -# Makefile that is written to the output directory. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. - -PAPER_TYPE = a4wide - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -# If LATEX_SOURCE_CODE is set to YES then doxygen will include -# source code with syntax highlighting in the LaTeX output. -# Note that which sources are shown also depends on other settings -# such as SOURCE_BROWSER. - -LATEX_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that -# enabling this will significantly increase the size of the XML output. - -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. -# This is useful -# if you want to understand what is going on. -# On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_DEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator -# instead of the = operator. - -PREDEFINED = - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse -# the parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more -# powerful graphs. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see -# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is -# allowed to run in parallel. When set to 0 (the default) doxygen will -# base this on the number of processors available in the system. You can set it -# explicitly to a value larger than 0 to get control over the balance -# between CPU load and processing speed. - -DOT_NUM_THREADS = 0 - -# By default doxygen will write a font called FreeSans.ttf to the output -# directory and reference it in all dot files that doxygen generates. This -# font does not include all possible unicode characters however, so when you need -# these (or just want a differently looking font) you can specify the font name -# using DOT_FONTNAME. You need need to make sure dot is able to find the font, -# which can be done by putting it in a standard location or by setting the -# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory -# containing the font. - -DOT_FONTNAME = FreeSans.ttf - -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the output directory to look for the -# FreeSans.ttf font (which doxygen will put there itself). If you specify a -# different font using DOT_FONTNAME you can set the path where dot -# can find it using this tag. - -DOT_FONTPATH = - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = NO - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. - -DOT_IMAGE_FORMAT = png - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = YES - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES diff --git a/lib/hidapi-0.8.0-rc1/.gitattributes b/lib/hidapi/.gitattributes similarity index 100% rename from lib/hidapi-0.8.0-rc1/.gitattributes rename to lib/hidapi/.gitattributes diff --git a/lib/hidapi-0.8.0-rc1/.gitignore b/lib/hidapi/.gitignore similarity index 100% rename from lib/hidapi-0.8.0-rc1/.gitignore rename to lib/hidapi/.gitignore diff --git a/lib/hidapi-0.8.0-rc1/AUTHORS.txt b/lib/hidapi/AUTHORS.txt similarity index 100% rename from lib/hidapi-0.8.0-rc1/AUTHORS.txt rename to lib/hidapi/AUTHORS.txt diff --git a/lib/hidapi-0.8.0-rc1/HACKING.txt b/lib/hidapi/HACKING.txt similarity index 100% rename from lib/hidapi-0.8.0-rc1/HACKING.txt rename to lib/hidapi/HACKING.txt diff --git a/lib/hidapi-0.8.0-rc1/LICENSE-bsd.txt b/lib/hidapi/LICENSE-bsd.txt similarity index 100% rename from lib/hidapi-0.8.0-rc1/LICENSE-bsd.txt rename to lib/hidapi/LICENSE-bsd.txt diff --git a/lib/hidapi-0.8.0-rc1/LICENSE-gpl3.txt b/lib/hidapi/LICENSE-gpl3.txt similarity index 100% rename from lib/hidapi-0.8.0-rc1/LICENSE-gpl3.txt rename to lib/hidapi/LICENSE-gpl3.txt diff --git a/lib/hidapi-0.8.0-rc1/LICENSE-orig.txt b/lib/hidapi/LICENSE-orig.txt similarity index 100% rename from lib/hidapi-0.8.0-rc1/LICENSE-orig.txt rename to lib/hidapi/LICENSE-orig.txt diff --git a/lib/hidapi-0.8.0-rc1/LICENSE.txt b/lib/hidapi/LICENSE.txt similarity index 100% rename from lib/hidapi-0.8.0-rc1/LICENSE.txt rename to lib/hidapi/LICENSE.txt diff --git a/lib/hidapi-0.8.0-rc1/Makefile.am b/lib/hidapi/Makefile.am similarity index 95% rename from lib/hidapi-0.8.0-rc1/Makefile.am rename to lib/hidapi/Makefile.am index cf4f7ca4c99..a6e47e8ab3e 100644 --- a/lib/hidapi-0.8.0-rc1/Makefile.am +++ b/lib/hidapi/Makefile.am @@ -27,6 +27,10 @@ if OS_FREEBSD SUBDIRS += libusb endif +if OS_KFREEBSD +SUBDIRS += libusb +endif + if OS_WINDOWS SUBDIRS += windows endif @@ -40,7 +44,7 @@ endif EXTRA_DIST = udev doxygen dist_doc_DATA = \ - README.txt \ + README.md \ AUTHORS.txt \ LICENSE-bsd.txt \ LICENSE-gpl3.txt \ diff --git a/lib/hidapi-0.8.0-rc1/README.txt b/lib/hidapi/README.md similarity index 51% rename from lib/hidapi-0.8.0-rc1/README.txt rename to lib/hidapi/README.md index b087d1fb1e9..5243a0ba59b 100644 --- a/lib/hidapi-0.8.0-rc1/README.txt +++ b/lib/hidapi/README.md @@ -1,26 +1,48 @@ - HIDAPI library for Windows, Linux, FreeBSD and Mac OS X - ========================================================= - -About -====== +## HIDAPI library for Windows, Linux, FreeBSD and macOS HIDAPI is a multi-platform library which allows an application to interface -with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and Mac -OS X. HIDAPI can be either built as a shared library (.so or .dll) or +with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS. +HIDAPI can be either built as a shared library (`.so`, `.dll` or `.dylib`) or can be embedded directly into a target application by adding a single source file (per platform) and a single header. -HIDAPI has four back-ends: - * Windows (using hid.dll) - * Linux/hidraw (using the Kernel's hidraw driver) - * Linux/libusb (using libusb-1.0) - * FreeBSD (using libusb-1.0) - * Mac (using IOHidManager) +HIDAPI library was originally developed by Alan Ott ([signal11](https://github.com/signal11)). + +It was moved to [libusb/hidapi](https://github.com/libusb/hidapi) on June 4th, 2019, in order to merge important bugfixes and continue development of the library. + +## Table of Contents + +* [About](#about) +* [What Does the API Look Like?](#what-does-the-api-look-like) +* [License](#license) +* [Download](#download) +* [Build Instructions](#build-instructions) + * [Prerequisites](#prerequisites) + * [Linux](#linux) + * [FreeBSD](#freebsd) + * [Mac](#mac) + * [Windows](#windows) + * [Building HIDAPI into a shared library on Unix Platforms](#building-hidapi-into-a-shared-library-on-unix-platforms) + * [Building the manual way on Unix platforms](#building-the-manual-way-on-unix-platforms) + * [Building on Windows](#building-on-windows) +* [Cross Compiling](#cross-compiling) + * [Prerequisites](#prerequisites-1) + * [Building HIDAPI](#building-hidapi) + +## About + +HIDAPI has five back-ends: +* Windows (using `hid.dll`) +* Linux/hidraw (using the Kernel's hidraw driver) +* Linux/libusb (using libusb-1.0) +* FreeBSD (using libusb-1.0) +* Mac (using IOHidManager) On Linux, either the hidraw or the libusb back-end can be used. There are tradeoffs, and the functionality supported is slightly different. -Linux/hidraw (linux/hid.c): +__Linux/hidraw__ (`linux/hid.c`): + This back-end uses the hidraw interface in the Linux kernel. While this back-end will support both USB and Bluetooth, it has some limitations on kernels prior to 2.6.39, including the inability to send or receive feature @@ -29,23 +51,24 @@ hidraw nodes associated with them. Keyboards, mice, and some other devices which are blacklisted from having hidraw nodes will not work. Fortunately, for nearly all the uses of hidraw, this is not a problem. -Linux/FreeBSD/libusb (libusb/hid-libusb.c): +__Linux/FreeBSD/libusb__ (`libusb/hid.c`): + This back-end uses libusb-1.0 to communicate directly to a USB device. This back-end will of course not work with Bluetooth devices. HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses -Fox Toolkit (http://www.fox-toolkit.org). It will build on every platform +Fox Toolkit . It will build on every platform which HIDAPI supports. Since it relies on a 3rd party library, building it is optional but recommended because it is so useful when debugging hardware. -What Does the API Look Like? -============================= +## What Does the API Look Like? The API provides the the most commonly used HID functions including sending and receiving of input, output, and feature reports. The sample program, which communicates with a heavily hacked up version of the Microchip USB Generic HID sample looks like this (with error checking removed for simplicity): +```c #ifdef WIN32 #include #endif @@ -108,24 +131,18 @@ int main(int argc, char* argv[]) return 0; } +``` -If you have your own simple test programs which communicate with standard -hardware development boards (such as those from Microchip, TI, Atmel, -FreeScale and others), please consider sending me something like the above -for inclusion into the HIDAPI source. This will help others who have the -same hardware as you do. +## License +HIDAPI may be used by one of three licenses as outlined in [LICENSE.txt](LICENSE.txt). -License -======== -HIDAPI may be used by one of three licenses as outlined in LICENSE.txt. +## Download +HIDAPI can be downloaded from GitHub +```sh +git clone git://github.com/libusb/hidapi.git +``` -Download -========= -HIDAPI can be downloaded from github - git clone git://github.com/signal11/hidapi.git - -Build Instructions -=================== +## Build Instructions This section is long. Don't be put off by this. It's not long because it's complicated to build HIDAPI; it's quite the opposite. This section is long @@ -136,94 +153,103 @@ HIDAPI can be built in several different ways. If you elect to build a shared library, you will need to build it from the HIDAPI source distribution. If you choose instead to embed HIDAPI directly into your application, you can skip the building and look at the provided platform -Makefiles for guidance. These platform Makefiles are located in linux/ -libusb/ mac/ and windows/ and are called Makefile-manual. In addition, +Makefiles for guidance. These platform Makefiles are located in `linux/`, +`libusb/`, `mac/` and `windows/` and are called `Makefile-manual`. In addition, Visual Studio projects are provided. Even if you're going to embed HIDAPI into your project, it is still beneficial to build the example programs. -Prerequisites: ---------------- - - Linux: - ------- - On Linux, you will need to install development packages for libudev, - libusb and optionally Fox-toolkit (for the test GUI). On - Debian/Ubuntu systems these can be installed by running: - sudo apt-get install libudev-dev libusb-1.0-0-dev libfox-1.6-dev - - If you downloaded the source directly from the git repository (using - git clone), you'll need Autotools: - sudo apt-get install autotools-dev autoconf automake libtool - - FreeBSD: - --------- - On FreeBSD you will need to install GNU make, libiconv, and - optionally Fox-Toolkit (for the test GUI). This is done by running - the following: - pkg_add -r gmake libiconv fox16 - - If you downloaded the source directly from the git repository (using - git clone), you'll need Autotools: - pkg_add -r autotools - - Mac: - ----- - On Mac, you will need to install Fox-Toolkit if you wish to build - the Test GUI. There are two ways to do this, and each has a slight - complication. Which method you use depends on your use case. - - If you wish to build the Test GUI just for your own testing on your - own computer, then the easiest method is to install Fox-Toolkit - using ports: - sudo port install fox - - If you wish to build the TestGUI app bundle to redistribute to - others, you will need to install Fox-toolkit from source. This is - because the version of fox that gets installed using ports uses the - ports X11 libraries which are not compatible with the Apple X11 - libraries. If you install Fox with ports and then try to distribute - your built app bundle, it will simply fail to run on other systems. - To install Fox-Toolkit manually, download the source package from - http://www.fox-toolkit.org, extract it, and run the following from - within the extracted source: - ./configure && make && make install - - Windows: - --------- - On Windows, if you want to build the test GUI, you will need to get - the hidapi-externals.zip package from the download site. This - contains pre-built binaries for Fox-toolkit. Extract - hidapi-externals.zip just outside of hidapi, so that - hidapi-externals and hidapi are on the same level, as shown: - - Parent_Folder - | - +hidapi - +hidapi-externals - - Again, this step is not required if you do not wish to build the - test GUI. - - -Building HIDAPI into a shared library on Unix Platforms: ---------------------------------------------------------- - -On Unix-like systems such as Linux, FreeBSD, Mac, and even Windows, using -Mingw or Cygwin, the easiest way to build a standard system-installed shared +### Prerequisites: + +#### Linux: +On Linux, you will need to install development packages for libudev, +libusb and optionally Fox-toolkit (for the test GUI). On +Debian/Ubuntu systems these can be installed by running: +```sh +sudo apt-get install libudev-dev libusb-1.0-0-dev libfox-1.6-dev +``` + +If you downloaded the source directly from the git repository (using +git clone), you'll need Autotools: +```sh +sudo apt-get install autotools-dev autoconf automake libtool +``` + +#### FreeBSD: +On FreeBSD you will need to install GNU make, libiconv, and +optionally Fox-Toolkit (for the test GUI). This is done by running +the following: +```sh +pkg_add -r gmake libiconv fox16 +``` + +If you downloaded the source directly from the git repository (using +git clone), you'll need Autotools: +```sh +pkg_add -r autotools +``` + +#### Mac: +On Mac, you will need to install Fox-Toolkit if you wish to build +the Test GUI. There are two ways to do this, and each has a slight +complication. Which method you use depends on your use case. + +If you wish to build the Test GUI just for your own testing on your +own computer, then the easiest method is to install Fox-Toolkit +using ports: +```sh +sudo port install fox +``` + +If you wish to build the TestGUI app bundle to redistribute to +others, you will need to install Fox-toolkit from source. This is +because the version of fox that gets installed using ports uses the +ports X11 libraries which are not compatible with the Apple X11 +libraries. If you install Fox with ports and then try to distribute +your built app bundle, it will simply fail to run on other systems. +To install Fox-Toolkit manually, download the source package from +, extract it, and run the following from +within the extracted source: +```sh +./configure && make && make install +``` + +#### Windows: +On Windows, if you want to build the test GUI, you will need to get +the `hidapi-externals.zip` package from the download site. This +contains pre-built binaries for Fox-toolkit. Extract +`hidapi-externals.zip` just outside of hidapi, so that +hidapi-externals and hidapi are on the same level, as shown: +``` + Parent_Folder + | + +hidapi + +hidapi-externals +``` +Again, this step is not required if you do not wish to build the +test GUI. + + +### Building HIDAPI into a shared library on Unix Platforms: + +On Unix-like systems such as Linux, FreeBSD, macOS, and even Windows, using +MinGW or Cygwin, the easiest way to build a standard system-installed shared library is to use the GNU Autotools build system. If you checked out the source from the git repository, run the following: - ./bootstrap - ./configure - make - make install <----- as root, or using sudo +```sh +./bootstrap +./configure +make +make install # as root, or using sudo +``` -If you downloaded a source package (ie: if you did not run git clone), you -can skip the ./bootstrap step. +If you downloaded a source package (i.e.: if you did not run git clone), you +can skip the `./bootstrap` step. -./configure can take several arguments which control the build. The two most +`./configure` can take several arguments which control the build. The two most likely to be used are: +```sh --enable-testgui Enable build of the Test GUI. This requires Fox toolkit to be installed. Instructions for installing Fox-Toolkit on @@ -234,41 +260,44 @@ likely to be used are: be installed. The example above will put the headers in /usr/include and the binaries in /usr/lib. The default is to install into /usr/local which is fine on most systems. - -Building the manual way on Unix platforms: -------------------------------------------- +``` +### Building the manual way on Unix platforms: Manual Makefiles are provided mostly to give the user and idea what it takes to build a program which embeds HIDAPI directly inside of it. These should really be used as examples only. If you want to build a system-wide shared library, use the Autotools method described above. - To build HIDAPI using the manual makefiles, change to the directory - of your platform and run make. For example, on Linux run: - cd linux/ - make -f Makefile-manual +To build HIDAPI using the manual Makefiles, change to the directory +of your platform and run make. For example, on Linux run: +```sh +cd linux/ +make -f Makefile-manual +``` - To build the Test GUI using the manual makefiles: - cd testgui/ - make -f Makefile-manual +To build the Test GUI using the manual makefiles: +```sh +cd testgui/ +make -f Makefile-manual +``` -Building on Windows: ---------------------- +### Building on Windows: -To build the HIDAPI DLL on Windows using Visual Studio, build the .sln file -in the windows/ directory. +To build the HIDAPI DLL on Windows using Visual Studio, build the `.sln` file +in the `windows/` directory. -To build the Test GUI on windows using Visual Studio, build the .sln file in -the testgui/ directory. +To build the Test GUI on windows using Visual Studio, build the `.sln` file in +the `testgui/` directory. To build HIDAPI using MinGW or Cygwin using Autotools, use the instructions -in the section titled "Building HIDAPI into a shared library on Unix -Platforms" above. Note that building the Test GUI with MinGW or Cygwin will -require the Windows procedure in the Prerequisites section above (ie: -hidapi-externals.zip). +in the section [Building HIDAPI into a shared library on Unix Platforms](#building-hidapi-into-a-shared-library-on-unix-platforms) +above. Note that building the Test GUI with MinGW or Cygwin will +require the Windows procedure in the [Prerequisites](#prerequisites-1) section +above (i.e.: `hidapi-externals.zip`). To build HIDAPI using MinGW using the Manual Makefiles, see the section -"Building the manual way on Unix platforms" above. +[Building the manual way on Unix platforms](#building-the-manual-way-on-unix-platforms) +above. HIDAPI can also be built using the Windows DDK (now also called the Windows Driver Kit or WDK). This method was originally required for the HIDAPI build @@ -280,17 +309,16 @@ not. To build using the DDK: 2. From the Start menu, in the Windows Driver Kits folder, select Build Environments, then your operating system, then the x86 Free Build Environment (or one that is appropriate for your system). - 3. From the console, change directory to the windows/ddk_build/ directory, + 3. From the console, change directory to the `windows/ddk_build/` directory, which is part of the HIDAPI distribution. 4. Type build. 5. You can find the output files (DLL and LIB) in a subdirectory created by the build system which is appropriate for your environment. On - Windows 7, this directory is objfre_wxp_x86/i386. + Windows XP, this directory is `objfre_wxp_x86/i386`. -Cross Compiling -================ +## Cross Compiling -This section talks about cross compiling HIDAPI for Linux using autotools. +This section talks about cross compiling HIDAPI for Linux using Autotools. This is useful for using HIDAPI on embedded Linux targets. These instructions assume the most raw kind of embedded Linux build, where all prerequisites will need to be built first. This process will of course vary @@ -299,41 +327,38 @@ OpenEmbedded or Buildroot. For the purpose of this section, it will be assumed that the following environment variables are exported. +```sh +$ export STAGING=$HOME/out +$ export HOST=arm-linux +``` - $ export STAGING=$HOME/out - $ export HOST=arm-linux +`STAGING` and `HOST` can be modified to suit your setup. -STAGING and HOST can be modified to suit your setup. - -Prerequisites --------------- +### Prerequisites Note that the build of libudev is the very basic configuration. -Build Libusb. From the libusb source directory, run: - ./configure --host=$HOST --prefix=$STAGING - make - make install +Build libusb. From the libusb source directory, run: +```sh +./configure --host=$HOST --prefix=$STAGING +make +make install +``` Build libudev. From the libudev source directory, run: - ./configure --disable-gudev --disable-introspection --disable-hwdb \ - --host=$HOST --prefix=$STAGING - make - make install +```sh +./configure --disable-gudev --disable-introspection --disable-hwdb \ + --host=$HOST --prefix=$STAGING +make +make install +``` -Building HIDAPI ----------------- +### Building HIDAPI Build HIDAPI: - - PKG_CONFIG_DIR= \ - PKG_CONFIG_LIBDIR=$STAGING/lib/pkgconfig:$STAGING/share/pkgconfig \ - PKG_CONFIG_SYSROOT_DIR=$STAGING \ - ./configure --host=$HOST --prefix=$STAGING - - -Signal 11 Software - 2010-04-11 - 2010-07-28 - 2011-09-10 - 2012-05-01 - 2012-07-03 +``` +PKG_CONFIG_DIR= \ +PKG_CONFIG_LIBDIR=$STAGING/lib/pkgconfig:$STAGING/share/pkgconfig \ +PKG_CONFIG_SYSROOT_DIR=$STAGING \ +./configure --host=$HOST --prefix=$STAGING +``` diff --git a/lib/hidapi/android/jni/Android.mk b/lib/hidapi/android/jni/Android.mk new file mode 100644 index 00000000000..527b43fd6c1 --- /dev/null +++ b/lib/hidapi/android/jni/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH:= $(call my-dir) + +HIDAPI_ROOT_REL:= ../.. +HIDAPI_ROOT_ABS:= $(LOCAL_PATH)/../.. + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + $(HIDAPI_ROOT_REL)/libusb/hid.c + +LOCAL_C_INCLUDES += \ + $(HIDAPI_ROOT_ABS)/hidapi \ + $(HIDAPI_ROOT_ABS)/android + +LOCAL_SHARED_LIBRARIES := libusb1.0 + +LOCAL_MODULE := libhidapi + +include $(BUILD_SHARED_LIBRARY) diff --git a/lib/hidapi-0.8.0-rc1/bootstrap b/lib/hidapi/bootstrap similarity index 100% rename from lib/hidapi-0.8.0-rc1/bootstrap rename to lib/hidapi/bootstrap diff --git a/lib/hidapi-0.8.0-rc1/configure.ac b/lib/hidapi/configure.ac similarity index 84% rename from lib/hidapi-0.8.0-rc1/configure.ac rename to lib/hidapi/configure.ac index e5fdd4bebaf..b9f670eb885 100644 --- a/lib/hidapi-0.8.0-rc1/configure.ac +++ b/lib/hidapi/configure.ac @@ -2,9 +2,9 @@ AC_PREREQ(2.63) # Version number. This is currently the only place. m4_define([HIDAPI_MAJOR], 0) -m4_define([HIDAPI_MINOR], 8) +m4_define([HIDAPI_MINOR], 9) m4_define([HIDAPI_RELEASE], 0) -m4_define([HIDAPI_RC], -rc1) +m4_define([HIDAPI_RC], ) m4_define([VERSION_STRING], HIDAPI_MAJOR[.]HIDAPI_MINOR[.]HIDAPI_RELEASE[]HIDAPI_RC) AC_INIT([hidapi],[VERSION_STRING],[alan@signal11.us]) @@ -63,14 +63,14 @@ case $host in # HIDAPI/hidraw libs PKG_CHECK_MODULES([libudev], [libudev], true, [hidapi_lib_error libudev]) - LIBS_HIDRAW_PR+=" $libudev_LIBS" - CFLAGS_HIDRAW+=" $libudev_CFLAGS" + LIBS_HIDRAW_PR="${LIBS_HIDRAW_PR} $libudev_LIBS" + CFLAGS_HIDRAW="${CFLAGS_HIDRAW} $libudev_CFLAGS" # HIDAPI/libusb libs - AC_CHECK_LIB([rt], [clock_gettime], [LIBS_LIBUSB_PRIVATE+=" -lrt"], [hidapi_lib_error librt]) + AC_CHECK_LIB([rt], [clock_gettime], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lrt"], [hidapi_lib_error librt]) PKG_CHECK_MODULES([libusb], [libusb-1.0 >= 1.0.9], true, [hidapi_lib_error libusb-1.0]) - LIBS_LIBUSB_PRIVATE+=" $libusb_LIBS" - CFLAGS_LIBUSB+=" $libusb_CFLAGS" + LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} $libusb_LIBS" + CFLAGS_LIBUSB="${CFLAGS_LIBUSB} $libusb_CFLAGS" ;; *-darwin*) AC_MSG_RESULT([ (Mac OS X back-end)]) @@ -96,6 +96,17 @@ case $host in AC_CHECK_LIB([iconv], [iconv_open], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -liconv"], [hidapi_lib_error libiconv]) echo libs_priv: $LIBS_LIBUSB_PRIVATE ;; +*-kfreebsd*) + AC_MSG_RESULT([ (kFreeBSD back-end)]) + AC_DEFINE(OS_KFREEBSD, 1, [kFreeBSD implementation]) + AC_SUBST(OS_KFREEBSD) + backend="libusb" + os="kfreebsd" + threads="pthreads" + + AC_CHECK_LIB([usb], [libusb_init], [LIBS_LIBUSB_PRIVATE="${LIBS_LIBUSB_PRIVATE} -lusb"], [hidapi_lib_error libusb]) + echo libs_priv: $LIBS_LIBUSB_PRIVATE + ;; *-mingw*) AC_MSG_RESULT([ (Windows back-end, using MinGW)]) backend="windows" @@ -169,10 +180,10 @@ if test "x$testgui_enabled" != "xno"; then if test "x$foxconfig" = "xfalse"; then hidapi_prog_error fox-config "FOX Toolkit" fi - LIBS_TESTGUI+=`$foxconfig --libs` - LIBS_TESTGUI+=" -framework Cocoa -L/usr/X11R6/lib" - CFLAGS_TESTGUI+=`$foxconfig --cflags` - OBJCFLAGS+=" -x objective-c++" + LIBS_TESTGUI="${LIBS_TESTGUI} `$foxconfig --libs`" + LIBS_TESTGUI="${LIBS_TESTGUI} -framework Cocoa -L/usr/X11R6/lib" + CFLAGS_TESTGUI="${CFLAGS_TESTGUI} `$foxconfig --cflags`" + OBJCFLAGS="${OBJCFLAGS} -x objective-c++" elif test "x$os" = xwindows; then # On Windows, just set the paths for Fox toolkit if test "x$win_implementation" = xmingw; then @@ -185,7 +196,7 @@ if test "x$testgui_enabled" != "xno"; then fi else # On Linux and FreeBSD platforms, use pkg-config to find fox. - PKG_CHECK_MODULES([fox], [fox]) + PKG_CHECK_MODULES([fox], [fox17], [], [PKG_CHECK_MODULES([fox], [fox])]) LIBS_TESTGUI="${LIBS_TESTGUI} $fox_LIBS" if test "x$os" = xfreebsd; then LIBS_TESTGUI="${LIBS_TESTGUI} -L/usr/local/lib" @@ -201,6 +212,7 @@ AC_SUBST([backend]) AM_CONDITIONAL(OS_LINUX, test "x$os" = xlinux) AM_CONDITIONAL(OS_DARWIN, test "x$os" = xdarwin) AM_CONDITIONAL(OS_FREEBSD, test "x$os" = xfreebsd) +AM_CONDITIONAL(OS_KFREEBSD, test "x$os" = xkfreebsd) AM_CONDITIONAL(OS_WINDOWS, test "x$os" = xwindows) AC_CONFIG_HEADERS([config.h]) diff --git a/lib/hidapi/doxygen/Doxyfile b/lib/hidapi/doxygen/Doxyfile new file mode 100644 index 00000000000..b1ea0a22349 --- /dev/null +++ b/lib/hidapi/doxygen/Doxyfile @@ -0,0 +1,2482 @@ +# Doxyfile 1.8.15 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = hidapi + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = NO + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../hidapi + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /