From dffb164606ee15b320d8f457e96683aa497682ab Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 11 Feb 2024 21:43:28 +0100 Subject: [PATCH 001/124] build: Print a warning instead of aborting build if TagLib 2.0 is used --- CMakeLists.txt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6fd58918c..276dcc1f751 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2941,11 +2941,7 @@ target_link_libraries(mixxx-lib PRIVATE SoundTouch::SoundTouch) find_package(TagLib 1.11 REQUIRED) target_link_libraries(mixxx-lib PRIVATE TagLib::TagLib) if (NOT TagLib_VERSION VERSION_LESS 2.0.0) - message(FATAL_ERROR "Installed Taglib ${TagLib_VERSION} is not supported. Use Version >= 1.11 and < 2.0 and its development headers.") - # Dear package maintainer: Do not patch away this fatal error - # using taglib 2.0.0 will put user data at risk!! - # Mixxx is a complex application that needs to be adapted and tested thoroughly - # https://github.com/mixxxdj/mixxx/issues/12708 + message(WARNING "Installed Taglib ${TagLib_VERSION} is not supported and might lead to data loss (https://github.com/mixxxdj/mixxx/issues/12708). Use version >= 1.11 and < 2.0 instead.") endif() # Threads From df05612a2c2b38b4cee6dda473960455f03854ca Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 18 Feb 2024 22:40:29 +0100 Subject: [PATCH 002/124] fix(qml): Remove unused include from `qmlapplication.h` --- src/qml/qmlapplication.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qml/qmlapplication.h b/src/qml/qmlapplication.h index f0f4120bdf8..b13e8a0ed83 100644 --- a/src/qml/qmlapplication.h +++ b/src/qml/qmlapplication.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include "coreservices.h" From 5a792d8b0639671b0a6d9f5d3a354733c4f536d7 Mon Sep 17 00:00:00 2001 From: Jan Holthuis Date: Sun, 18 Feb 2024 22:40:56 +0100 Subject: [PATCH 003/124] fix(qml): Do not pass empty list to `QFileSystemWatcher::removePaths()` This fixes the following runtime warning: warning [Main] QFileSystemWatcher::removePaths: list is empty --- src/qml/qmlautoreload.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qml/qmlautoreload.cpp b/src/qml/qmlautoreload.cpp index 1011282e782..ef717223fbc 100644 --- a/src/qml/qmlautoreload.cpp +++ b/src/qml/qmlautoreload.cpp @@ -36,7 +36,10 @@ void QmlAutoReload::slotFileChanged(const QString& changedFile) { } void QmlAutoReload::clear() { - m_fileWatcher.removePaths(m_fileWatcher.files()); + const auto files = m_fileWatcher.files(); + if (!files.isEmpty()) { + m_fileWatcher.removePaths(files); + } } } // namespace qml From c3efa4d8bd8075544930f0f0bc3fe79958fc447d Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 11 Oct 2023 22:42:30 +0100 Subject: [PATCH 004/124] AUBackend: Stub out Audio Unit (AU)-based effects backend --- CMakeLists.txt | 8 ++++ src/effects/backends/au/aubackend.cpp | 38 +++++++++++++++++++ src/effects/backends/au/aubackend.h | 26 +++++++++++++ .../backends/effectsbackendmanager.cpp | 6 +++ src/effects/defs.h | 1 + 5 files changed, 79 insertions(+) create mode 100644 src/effects/backends/au/aubackend.cpp create mode 100644 src/effects/backends/au/aubackend.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cf44b174833..5e93d10a7ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1561,6 +1561,14 @@ if(APPLE) target_compile_definitions(mixxx-lib PUBLIC __MACOS_ITUNES_LIBRARY__) endif() endif() + + option(AU_EFFECTS "Audio Unit (AU) effects integration" ON) + if(AU_EFFECTS) + target_sources(mixxx-lib PRIVATE + src/effects/backends/au/aubackend.cpp + ) + target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) + endif() endif() # QML Debugging diff --git a/src/effects/backends/au/aubackend.cpp b/src/effects/backends/au/aubackend.cpp new file mode 100644 index 00000000000..92398d88fe1 --- /dev/null +++ b/src/effects/backends/au/aubackend.cpp @@ -0,0 +1,38 @@ +#include "effects/backends/au/aubackend.h" + +#include + +#include "effects/backends/au/aubackend.h" +#include "effects/defs.h" + +AUBackend::AUBackend() { +} + +AUBackend::~AUBackend() { +} + +const QList AUBackend::getEffectIds() const { + // TODO + return {}; +} + +EffectManifestPointer AUBackend::getManifest(const QString& effectId) const { + // TODO + return {}; +} + +const QList AUBackend::getManifests() const { + // TODO + return {}; +} + +bool AUBackend::canInstantiateEffect(const QString& effectId) const { + // TODO + return false; +} + +std::unique_ptr AUBackend::createProcessor( + const EffectManifestPointer pManifest) const { + // TODO + return {}; +} diff --git a/src/effects/backends/au/aubackend.h b/src/effects/backends/au/aubackend.h new file mode 100644 index 00000000000..7a760fc176a --- /dev/null +++ b/src/effects/backends/au/aubackend.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "effects/backends/effectprocessor.h" +#include "effects/backends/effectsbackend.h" +#include "effects/defs.h" + +/// An effects backend for Audio Unit (AU) plugins. macOS-only. +class AUBackend : public EffectsBackend { + public: + AUBackend(); + ~AUBackend() override; + + EffectBackendType getType() const override { + return EffectBackendType::AU; + }; + + const QList getEffectIds() const override; + EffectManifestPointer getManifest(const QString& effectId) const override; + const QList getManifests() const override; + bool canInstantiateEffect(const QString& effectId) const override; + std::unique_ptr createProcessor( + const EffectManifestPointer pManifest) const override; +}; diff --git a/src/effects/backends/effectsbackendmanager.cpp b/src/effects/backends/effectsbackendmanager.cpp index 1762a17d468..86f39aa85b9 100644 --- a/src/effects/backends/effectsbackendmanager.cpp +++ b/src/effects/backends/effectsbackendmanager.cpp @@ -3,6 +3,9 @@ #include "control/controlobject.h" #include "effects/backends/builtin/builtinbackend.h" #include "effects/backends/effectprocessor.h" +#ifdef __AU_EFFECTS__ +#include "effects/backends/au/aubackend.h" +#endif #ifdef __LILV__ #include "effects/backends/lv2/lv2backend.h" #endif @@ -14,6 +17,9 @@ EffectsBackendManager::EffectsBackendManager() { m_pNumEffectsAvailable->setReadOnly(); addBackend(EffectsBackendPointer(new BuiltInBackend())); +#ifdef __AU_EFFECTS__ + addBackend(EffectsBackendPointer(new AUBackend())); +#endif #ifdef __LILV__ addBackend(EffectsBackendPointer(new LV2Backend())); #endif diff --git a/src/effects/defs.h b/src/effects/defs.h index c333739ab98..d246695b234 100644 --- a/src/effects/defs.h +++ b/src/effects/defs.h @@ -17,6 +17,7 @@ enum class EffectEnableState { // The order of the enum values here is used to determine sort order in EffectManifest::alphabetize enum class EffectBackendType { BuiltIn, + AU, LV2, Unknown }; From e6b43fa9c0c6a6c33aad67e8994210799666c59f Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 11 Oct 2023 23:01:40 +0100 Subject: [PATCH 005/124] AUBackend: Move class into source file Making the class opaque lets us store Objective-C objects there. --- CMakeLists.txt | 2 +- src/effects/backends/au/aubackend.cpp | 38 ------------ src/effects/backends/au/aubackend.h | 18 +----- src/effects/backends/au/aubackend.mm | 60 +++++++++++++++++++ .../backends/effectsbackendmanager.cpp | 2 +- 5 files changed, 63 insertions(+), 57 deletions(-) delete mode 100644 src/effects/backends/au/aubackend.cpp create mode 100644 src/effects/backends/au/aubackend.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e93d10a7ab..95849b31329 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1565,7 +1565,7 @@ if(APPLE) option(AU_EFFECTS "Audio Unit (AU) effects integration" ON) if(AU_EFFECTS) target_sources(mixxx-lib PRIVATE - src/effects/backends/au/aubackend.cpp + src/effects/backends/au/aubackend.mm ) target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) endif() diff --git a/src/effects/backends/au/aubackend.cpp b/src/effects/backends/au/aubackend.cpp deleted file mode 100644 index 92398d88fe1..00000000000 --- a/src/effects/backends/au/aubackend.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "effects/backends/au/aubackend.h" - -#include - -#include "effects/backends/au/aubackend.h" -#include "effects/defs.h" - -AUBackend::AUBackend() { -} - -AUBackend::~AUBackend() { -} - -const QList AUBackend::getEffectIds() const { - // TODO - return {}; -} - -EffectManifestPointer AUBackend::getManifest(const QString& effectId) const { - // TODO - return {}; -} - -const QList AUBackend::getManifests() const { - // TODO - return {}; -} - -bool AUBackend::canInstantiateEffect(const QString& effectId) const { - // TODO - return false; -} - -std::unique_ptr AUBackend::createProcessor( - const EffectManifestPointer pManifest) const { - // TODO - return {}; -} diff --git a/src/effects/backends/au/aubackend.h b/src/effects/backends/au/aubackend.h index 7a760fc176a..1e9599f790b 100644 --- a/src/effects/backends/au/aubackend.h +++ b/src/effects/backends/au/aubackend.h @@ -7,20 +7,4 @@ #include "effects/backends/effectsbackend.h" #include "effects/defs.h" -/// An effects backend for Audio Unit (AU) plugins. macOS-only. -class AUBackend : public EffectsBackend { - public: - AUBackend(); - ~AUBackend() override; - - EffectBackendType getType() const override { - return EffectBackendType::AU; - }; - - const QList getEffectIds() const override; - EffectManifestPointer getManifest(const QString& effectId) const override; - const QList getManifests() const override; - bool canInstantiateEffect(const QString& effectId) const override; - std::unique_ptr createProcessor( - const EffectManifestPointer pManifest) const override; -}; +EffectsBackendPointer createAUBackend(); diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm new file mode 100644 index 00000000000..9e42f74e24c --- /dev/null +++ b/src/effects/backends/au/aubackend.mm @@ -0,0 +1,60 @@ +#include "effects/backends/au/aubackend.h" + +#include + +#include "effects/backends/au/aubackend.h" +#include "effects/defs.h" + +/// An effects backend for Audio Unit (AU) plugins. macOS-only. +class AUBackend : public EffectsBackend { + public: + AUBackend(); + ~AUBackend() override; + + EffectBackendType getType() const override { + return EffectBackendType::AU; + }; + + const QList getEffectIds() const override; + EffectManifestPointer getManifest(const QString& effectId) const override; + const QList getManifests() const override; + bool canInstantiateEffect(const QString& effectId) const override; + std::unique_ptr createProcessor( + const EffectManifestPointer pManifest) const override; +}; + +EffectsBackendPointer createAUBackend() { + return EffectsBackendPointer(new AUBackend()); +} + +AUBackend::AUBackend() { +} + +AUBackend::~AUBackend() { +} + +const QList AUBackend::getEffectIds() const { + // TODO + return {}; +} + +EffectManifestPointer AUBackend::getManifest(const QString& effectId) const { + // TODO + return {}; +} + +const QList AUBackend::getManifests() const { + // TODO + return {}; +} + +bool AUBackend::canInstantiateEffect(const QString& effectId) const { + // TODO + return false; +} + +std::unique_ptr AUBackend::createProcessor( + const EffectManifestPointer pManifest) const { + // TODO + return {}; +} diff --git a/src/effects/backends/effectsbackendmanager.cpp b/src/effects/backends/effectsbackendmanager.cpp index 86f39aa85b9..d27b94c960a 100644 --- a/src/effects/backends/effectsbackendmanager.cpp +++ b/src/effects/backends/effectsbackendmanager.cpp @@ -18,7 +18,7 @@ EffectsBackendManager::EffectsBackendManager() { addBackend(EffectsBackendPointer(new BuiltInBackend())); #ifdef __AU_EFFECTS__ - addBackend(EffectsBackendPointer(new AUBackend())); + addBackend(createAUBackend()); #endif #ifdef __LILV__ addBackend(EffectsBackendPointer(new LV2Backend())); From 356dabb2f01943469d60b2c3828b3a119fb5bee1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 11 Oct 2023 23:03:52 +0100 Subject: [PATCH 006/124] AUBackend: Move implementations into class --- src/effects/backends/au/aubackend.mm | 68 ++++++++++++---------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index 9e42f74e24c..3e0edc724a0 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -8,53 +8,43 @@ /// An effects backend for Audio Unit (AU) plugins. macOS-only. class AUBackend : public EffectsBackend { public: - AUBackend(); - ~AUBackend() override; + AUBackend() { + } + + ~AUBackend() override { + } EffectBackendType getType() const override { return EffectBackendType::AU; }; - const QList getEffectIds() const override; - EffectManifestPointer getManifest(const QString& effectId) const override; - const QList getManifests() const override; - bool canInstantiateEffect(const QString& effectId) const override; - std::unique_ptr createProcessor( - const EffectManifestPointer pManifest) const override; -}; - -EffectsBackendPointer createAUBackend() { - return EffectsBackendPointer(new AUBackend()); -} + const QList getEffectIds() const override { + // TODO + return {}; + } -AUBackend::AUBackend() { -} + EffectManifestPointer getManifest(const QString& effectId) const override { + // TODO + return {}; + } -AUBackend::~AUBackend() { -} + const QList getManifests() const override { + // TODO + return {}; + } -const QList AUBackend::getEffectIds() const { - // TODO - return {}; -} + bool canInstantiateEffect(const QString& effectId) const override { + // TODO + return false; + } -EffectManifestPointer AUBackend::getManifest(const QString& effectId) const { - // TODO - return {}; -} - -const QList AUBackend::getManifests() const { - // TODO - return {}; -} - -bool AUBackend::canInstantiateEffect(const QString& effectId) const { - // TODO - return false; -} + std::unique_ptr createProcessor( + const EffectManifestPointer pManifest) const override { + // TODO + return {}; + } +}; -std::unique_ptr AUBackend::createProcessor( - const EffectManifestPointer pManifest) const { - // TODO - return {}; +EffectsBackendPointer createAUBackend() { + return EffectsBackendPointer(new AUBackend()); } From 834feb04bbf8f586ad48e7dce1e63256ce2df77c Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 11 Oct 2023 23:14:21 +0100 Subject: [PATCH 007/124] AUBackend: Stub out effect processor and manifest --- CMakeLists.txt | 1 + src/effects/backends/au/aubackend.mm | 4 ++-- src/effects/backends/au/aueffectprocessor.h | 24 ++++++++++++++++++++ src/effects/backends/au/aueffectprocessor.mm | 24 ++++++++++++++++++++ src/effects/backends/au/aumanifest.h | 7 ++++++ 5 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/effects/backends/au/aueffectprocessor.h create mode 100644 src/effects/backends/au/aueffectprocessor.mm create mode 100644 src/effects/backends/au/aumanifest.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 95849b31329..476d05d5f83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1566,6 +1566,7 @@ if(APPLE) if(AU_EFFECTS) target_sources(mixxx-lib PRIVATE src/effects/backends/au/aubackend.mm + src/effects/backends/au/aueffectprocessor.mm ) target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) endif() diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index 3e0edc724a0..d8c08df8a14 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -3,6 +3,7 @@ #include #include "effects/backends/au/aubackend.h" +#include "effects/backends/au/aueffectprocessor.h" #include "effects/defs.h" /// An effects backend for Audio Unit (AU) plugins. macOS-only. @@ -40,8 +41,7 @@ bool canInstantiateEffect(const QString& effectId) const override { std::unique_ptr createProcessor( const EffectManifestPointer pManifest) const override { - // TODO - return {}; + return std::make_unique(pManifest); } }; diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h new file mode 100644 index 00000000000..49ab4d57cc1 --- /dev/null +++ b/src/effects/backends/au/aueffectprocessor.h @@ -0,0 +1,24 @@ +#pragma once + +#include "effects/backends/effectprocessor.h" + +class AUEffectGroupState final : public EffectState { + public: + AUEffectGroupState(const mixxx::EngineParameters& engineParameters); +}; + +class AUEffectProcessor final : public EffectProcessorImpl { + public: + AUEffectProcessor(const EffectManifestPointer pManifest); + + void loadEngineEffectParameters( + const QMap& parameters) + override; + + void processChannel(AUEffectGroupState* channelState, + const CSAMPLE* pInput, + CSAMPLE* pOutput, + const mixxx::EngineParameters& engineParameters, + const EffectEnableState enableState, + const GroupFeatureState& groupFeatures) override; +}; diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm new file mode 100644 index 00000000000..daf58d4c09a --- /dev/null +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -0,0 +1,24 @@ +#include "effects/backends/au/aueffectprocessor.h" + +AUEffectGroupState::AUEffectGroupState( + const mixxx::EngineParameters& engineParameters) + : EffectState(engineParameters) { +} + +AUEffectProcessor::AUEffectProcessor(const EffectManifestPointer pManifest) { + // TODO +} + +void AUEffectProcessor::loadEngineEffectParameters( + const QMap& parameters) { + // TODO +} + +void AUEffectProcessor::processChannel(AUEffectGroupState* channelState, + const CSAMPLE* pInput, + CSAMPLE* pOutput, + const mixxx::EngineParameters& engineParameters, + const EffectEnableState enableState, + const GroupFeatureState& groupFeatures) { + // TODO +} diff --git a/src/effects/backends/au/aumanifest.h b/src/effects/backends/au/aumanifest.h new file mode 100644 index 00000000000..35f67533e89 --- /dev/null +++ b/src/effects/backends/au/aumanifest.h @@ -0,0 +1,7 @@ +#pragma once + +#include "effects/backends/effectmanifest.h" +#include "effects/defs.h" + +class AUManifest : public EffectManifest { +}; From 0e396166d6c29a04c2a588da8a9c5b8a5a362bdf Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 11 Oct 2023 23:25:41 +0100 Subject: [PATCH 008/124] AUManifest: Store AVAudioUnitComponent --- CMakeLists.txt | 2 ++ src/effects/backends/au/aumanifest.h | 7 +++++++ src/effects/backends/au/aumanifest.mm | 5 +++++ 3 files changed, 14 insertions(+) create mode 100644 src/effects/backends/au/aumanifest.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 476d05d5f83..da153184114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1567,7 +1567,9 @@ if(APPLE) target_sources(mixxx-lib PRIVATE src/effects/backends/au/aubackend.mm src/effects/backends/au/aueffectprocessor.mm + src/effects/backends/au/aumanifest.mm ) + target_link_libraries(mixxx-lib PRIVATE "-weak_framework AVFAudio") target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) endif() endif() diff --git a/src/effects/backends/au/aumanifest.h b/src/effects/backends/au/aumanifest.h index 35f67533e89..1555476a0de 100644 --- a/src/effects/backends/au/aumanifest.h +++ b/src/effects/backends/au/aumanifest.h @@ -1,7 +1,14 @@ #pragma once +#import + #include "effects/backends/effectmanifest.h" #include "effects/defs.h" class AUManifest : public EffectManifest { + public: + AUManifest(AVAudioUnitComponent* component); + + private: + AVAudioUnitComponent* m_component; }; diff --git a/src/effects/backends/au/aumanifest.mm b/src/effects/backends/au/aumanifest.mm new file mode 100644 index 00000000000..115e9030ffb --- /dev/null +++ b/src/effects/backends/au/aumanifest.mm @@ -0,0 +1,5 @@ +#include "effects/backends/au/aumanifest.h" + +AUManifest::AUManifest(AVAudioUnitComponent* component) + : m_component(component) { +} From e48b11fa36d810ad28cfcf4e310807cd89ce25b1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 00:13:24 +0100 Subject: [PATCH 009/124] AUBackend: Find available audio units --- CMakeLists.txt | 2 +- src/effects/backends/au/aubackend.mm | 80 +++++++++++++++++++++++--- src/effects/backends/au/aumanifest.cpp | 6 ++ src/effects/backends/au/aumanifest.h | 7 +-- src/effects/backends/au/aumanifest.mm | 5 -- 5 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 src/effects/backends/au/aumanifest.cpp delete mode 100644 src/effects/backends/au/aumanifest.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index da153184114..79cb8d16271 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1567,7 +1567,7 @@ if(APPLE) target_sources(mixxx-lib PRIVATE src/effects/backends/au/aubackend.mm src/effects/backends/au/aueffectprocessor.mm - src/effects/backends/au/aumanifest.mm + src/effects/backends/au/aumanifest.cpp ) target_link_libraries(mixxx-lib PRIVATE "-weak_framework AVFAudio") target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index d8c08df8a14..0c720f172cb 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -1,15 +1,23 @@ #include "effects/backends/au/aubackend.h" +#import +#import +#import + +#include +#include #include #include "effects/backends/au/aubackend.h" #include "effects/backends/au/aueffectprocessor.h" +#include "effects/backends/au/aumanifest.h" #include "effects/defs.h" /// An effects backend for Audio Unit (AU) plugins. macOS-only. class AUBackend : public EffectsBackend { public: - AUBackend() { + AUBackend() : m_componentsById([[NSDictionary alloc] init]), m_nextId(0) { + loadAudioUnits(); } ~AUBackend() override { @@ -20,29 +28,83 @@ EffectBackendType getType() const override { }; const QList getEffectIds() const override { - // TODO - return {}; + QList effectIds; + + for (NSString* effectId in m_componentsById) { + effectIds.append(QString::fromNSString(effectId)); + } + + return effectIds; } EffectManifestPointer getManifest(const QString& effectId) const override { - // TODO - return {}; + AVAudioUnitComponent* component = + m_componentsById[effectId.toNSString()]; + return EffectManifestPointer(new AUManifest( + effectId, QString::fromNSString([component name]))); } const QList getManifests() const override { - // TODO - return {}; + QList manifests; + + for (NSString* effectId in m_componentsById) { + AVAudioUnitComponent* component = m_componentsById[effectId]; + manifests.append(EffectManifestPointer( + new AUManifest(QString::fromNSString(effectId), + QString::fromNSString([component name])))); + } + + return manifests; } bool canInstantiateEffect(const QString& effectId) const override { - // TODO - return false; + return [m_componentsById objectForKey:effectId.toNSString()] != nil; } std::unique_ptr createProcessor( const EffectManifestPointer pManifest) const override { return std::make_unique(pManifest); } + + private: + NSDictionary* m_componentsById; + int m_nextId; + + QString freshId() { + return QString::number(m_nextId++); + } + + void loadAudioUnits() { + // See + // https://developer.apple.com/documentation/audiotoolbox/audio_unit_v3_plug-ins/incorporating_audio_effects_and_instruments?language=objc + + // Create a query for audio components + AudioComponentDescription description = { + .componentType = kAudioUnitType_Effect, + .componentSubType = 0, + .componentManufacturer = 0, + .componentFlags = 0, + .componentFlagsMask = 0, + }; + + // Find the audio units + // TODO: Should we perform this asynchronously (e.g. using Qt's + // threading or GCD)? + auto manager = + [AVAudioUnitComponentManager sharedAudioUnitComponentManager]; + auto components = [manager componentsMatchingDescription:description]; + + // Assign ids to the components + NSMutableDictionary* componentsById = + [[NSMutableDictionary alloc] init]; + + for (AVAudioUnitComponent* component in components) { + QString effectId = freshId(); + componentsById[effectId.toNSString()] = component; + } + + m_componentsById = componentsById; + } }; EffectsBackendPointer createAUBackend() { diff --git a/src/effects/backends/au/aumanifest.cpp b/src/effects/backends/au/aumanifest.cpp new file mode 100644 index 00000000000..f9ecf270a9c --- /dev/null +++ b/src/effects/backends/au/aumanifest.cpp @@ -0,0 +1,6 @@ +#include "effects/backends/au/aumanifest.h" + +AUManifest::AUManifest(const QString& id, const QString& name) { + setId(id); + setName(name); +} diff --git a/src/effects/backends/au/aumanifest.h b/src/effects/backends/au/aumanifest.h index 1555476a0de..9203880bf52 100644 --- a/src/effects/backends/au/aumanifest.h +++ b/src/effects/backends/au/aumanifest.h @@ -1,14 +1,9 @@ #pragma once -#import - #include "effects/backends/effectmanifest.h" #include "effects/defs.h" class AUManifest : public EffectManifest { public: - AUManifest(AVAudioUnitComponent* component); - - private: - AVAudioUnitComponent* m_component; + AUManifest(const QString& id, const QString& name); }; diff --git a/src/effects/backends/au/aumanifest.mm b/src/effects/backends/au/aumanifest.mm deleted file mode 100644 index 115e9030ffb..00000000000 --- a/src/effects/backends/au/aumanifest.mm +++ /dev/null @@ -1,5 +0,0 @@ -#include "effects/backends/au/aumanifest.h" - -AUManifest::AUManifest(AVAudioUnitComponent* component) - : m_component(component) { -} From bdc350d375636489c003cd484f3def45a964c60d Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 00:17:23 +0100 Subject: [PATCH 010/124] AUBackend: Log found audio units --- src/effects/backends/au/aubackend.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index 0c720f172cb..a53fb2fce87 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -4,6 +4,7 @@ #import #import +#include #include #include #include @@ -75,6 +76,8 @@ QString freshId() { } void loadAudioUnits() { + qDebug() << "Loading audio units..."; + // See // https://developer.apple.com/documentation/audiotoolbox/audio_unit_v3_plug-ins/incorporating_audio_effects_and_instruments?language=objc @@ -99,6 +102,7 @@ void loadAudioUnits() { [[NSMutableDictionary alloc] init]; for (AVAudioUnitComponent* component in components) { + qDebug() << "Found audio unit" << [component name]; QString effectId = freshId(); componentsById[effectId.toNSString()] = component; } From 18805b41fac705472fac92a9c4b2d9a1d1087301 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 00:20:36 +0100 Subject: [PATCH 011/124] EffectsBackend: Add backend type name for AU (Audio Unit) --- src/effects/backends/effectsbackend.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/effects/backends/effectsbackend.cpp b/src/effects/backends/effectsbackend.cpp index 583814a3330..27069368908 100644 --- a/src/effects/backends/effectsbackend.cpp +++ b/src/effects/backends/effectsbackend.cpp @@ -3,6 +3,7 @@ #include namespace { +const QString backendTypeNameAU = QStringLiteral("Audio Unit"); const QString backendTypeNameLV2 = QStringLiteral("LV2"); // QString::tr requires const char[] rather than QString //: Backend type for effects that are built into Mixxx. @@ -14,6 +15,8 @@ constexpr char backendTypeNameUnknown[] = QT_TRANSLATE_NOOP("EffectsBackend", "U EffectBackendType EffectsBackend::backendTypeFromString(const QString& typeName) { if (typeName == backendTypeNameLV2) { return EffectBackendType::LV2; + } else if (typeName == backendTypeNameAU) { + return EffectBackendType::AU; } else { return EffectBackendType::BuiltIn; } @@ -23,6 +26,8 @@ QString EffectsBackend::backendTypeToString(EffectBackendType backendType) { switch (backendType) { case EffectBackendType::BuiltIn: return backendTypeNameBuiltIn; + case EffectBackendType::AU: + return backendTypeNameAU; case EffectBackendType::LV2: return backendTypeNameLV2; default: @@ -36,6 +41,8 @@ QString EffectsBackend::translatedBackendName(EffectBackendType backendType) { // Clazy's `tr-non-literal` check is a false positive, because the // source string has been marked `QT_TR_NOOP`. return QObject::tr(backendTypeNameBuiltIn); // clazy:exclude=tr-non-literal + case EffectBackendType::AU: + return backendTypeNameAU; case EffectBackendType::LV2: return backendTypeNameLV2; default: From 7c065270d55eb0eeba54d7a580cd59db56133173 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 00:22:32 +0100 Subject: [PATCH 012/124] AUManifest: Set correct backend type --- src/effects/backends/au/aumanifest.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/effects/backends/au/aumanifest.cpp b/src/effects/backends/au/aumanifest.cpp index f9ecf270a9c..7191832ab4c 100644 --- a/src/effects/backends/au/aumanifest.cpp +++ b/src/effects/backends/au/aumanifest.cpp @@ -1,6 +1,9 @@ #include "effects/backends/au/aumanifest.h" +#include "effects/defs.h" + AUManifest::AUManifest(const QString& id, const QString& name) { setId(id); setName(name); + setBackendType(EffectBackendType::AU); } From c6a84bd9985be16bc90043a031c24904404e4a6f Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 00:26:27 +0100 Subject: [PATCH 013/124] AUManifest: Pass component to constructor --- CMakeLists.txt | 2 +- src/effects/backends/au/aubackend.mm | 8 +++----- src/effects/backends/au/aumanifest.h | 4 +++- src/effects/backends/au/{aumanifest.cpp => aumanifest.mm} | 7 ++++--- 4 files changed, 11 insertions(+), 10 deletions(-) rename src/effects/backends/au/{aumanifest.cpp => aumanifest.mm} (50%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79cb8d16271..da153184114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1567,7 +1567,7 @@ if(APPLE) target_sources(mixxx-lib PRIVATE src/effects/backends/au/aubackend.mm src/effects/backends/au/aueffectprocessor.mm - src/effects/backends/au/aumanifest.cpp + src/effects/backends/au/aumanifest.mm ) target_link_libraries(mixxx-lib PRIVATE "-weak_framework AVFAudio") target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index a53fb2fce87..11b9b68ffb4 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -41,8 +41,7 @@ EffectBackendType getType() const override { EffectManifestPointer getManifest(const QString& effectId) const override { AVAudioUnitComponent* component = m_componentsById[effectId.toNSString()]; - return EffectManifestPointer(new AUManifest( - effectId, QString::fromNSString([component name]))); + return EffectManifestPointer(new AUManifest(effectId, component)); } const QList getManifests() const override { @@ -50,9 +49,8 @@ EffectManifestPointer getManifest(const QString& effectId) const override { for (NSString* effectId in m_componentsById) { AVAudioUnitComponent* component = m_componentsById[effectId]; - manifests.append(EffectManifestPointer( - new AUManifest(QString::fromNSString(effectId), - QString::fromNSString([component name])))); + manifests.append(EffectManifestPointer(new AUManifest( + QString::fromNSString(effectId), component))); } return manifests; diff --git a/src/effects/backends/au/aumanifest.h b/src/effects/backends/au/aumanifest.h index 9203880bf52..17820ed37b3 100644 --- a/src/effects/backends/au/aumanifest.h +++ b/src/effects/backends/au/aumanifest.h @@ -1,9 +1,11 @@ #pragma once +#import + #include "effects/backends/effectmanifest.h" #include "effects/defs.h" class AUManifest : public EffectManifest { public: - AUManifest(const QString& id, const QString& name); + AUManifest(const QString& id, AVAudioUnitComponent* component); }; diff --git a/src/effects/backends/au/aumanifest.cpp b/src/effects/backends/au/aumanifest.mm similarity index 50% rename from src/effects/backends/au/aumanifest.cpp rename to src/effects/backends/au/aumanifest.mm index 7191832ab4c..541bf8bcf67 100644 --- a/src/effects/backends/au/aumanifest.cpp +++ b/src/effects/backends/au/aumanifest.mm @@ -2,8 +2,9 @@ #include "effects/defs.h" -AUManifest::AUManifest(const QString& id, const QString& name) { - setId(id); - setName(name); +AUManifest::AUManifest(const QString& id, AVAudioUnitComponent* component) { setBackendType(EffectBackendType::AU); + + setId(id); + setName(QString::fromNSString([component name])); } From 4b7849e74d2b87c7ece46d7fbf3b953349db4e85 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 00:27:35 +0100 Subject: [PATCH 014/124] AUManifest: Expose more metadata about the components --- src/effects/backends/au/aumanifest.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/effects/backends/au/aumanifest.mm b/src/effects/backends/au/aumanifest.mm index 541bf8bcf67..a7ef3b1dd13 100644 --- a/src/effects/backends/au/aumanifest.mm +++ b/src/effects/backends/au/aumanifest.mm @@ -7,4 +7,7 @@ setId(id); setName(QString::fromNSString([component name])); + setVersion(QString::fromNSString([component versionString])); + setDescription(QString::fromNSString([component typeName])); + setAuthor(QString::fromNSString([component manufacturerName])); } From 8db7242ae0b007b08f6226488aa49336c13f928f Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 01:05:17 +0100 Subject: [PATCH 015/124] AUBackend: Store manifests --- src/effects/backends/au/aubackend.mm | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index 11b9b68ffb4..44609d9cfc2 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -5,6 +5,7 @@ #import #include +#include #include #include #include @@ -39,21 +40,11 @@ EffectBackendType getType() const override { } EffectManifestPointer getManifest(const QString& effectId) const override { - AVAudioUnitComponent* component = - m_componentsById[effectId.toNSString()]; - return EffectManifestPointer(new AUManifest(effectId, component)); + return m_manifestsById[effectId]; } const QList getManifests() const override { - QList manifests; - - for (NSString* effectId in m_componentsById) { - AVAudioUnitComponent* component = m_componentsById[effectId]; - manifests.append(EffectManifestPointer(new AUManifest( - QString::fromNSString(effectId), component))); - } - - return manifests; + return m_manifestsById.values(); } bool canInstantiateEffect(const QString& effectId) const override { @@ -67,6 +58,7 @@ bool canInstantiateEffect(const QString& effectId) const override { private: NSDictionary* m_componentsById; + QHash m_manifestsById; int m_nextId; QString freshId() { @@ -98,14 +90,19 @@ void loadAudioUnits() { // Assign ids to the components NSMutableDictionary* componentsById = [[NSMutableDictionary alloc] init]; + QHash manifestsById; for (AVAudioUnitComponent* component in components) { qDebug() << "Found audio unit" << [component name]; + QString effectId = freshId(); componentsById[effectId.toNSString()] = component; + manifestsById[effectId] = + EffectManifestPointer(new AUManifest(effectId, component)); } m_componentsById = componentsById; + m_manifestsById = manifestsById; } }; From 08f38a2bffc54515f7c66ee20bac2c6b48e145ea Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 01:32:22 +0100 Subject: [PATCH 016/124] EffectsBackend: Remove space from 'Audio Unit' Turns out, the parser unfortunately cannot really deal with this, `EffectsBackendManager::getManifestFromUniqueId` will then only split off a a part of the backend type name (in the case auf 'Audio Unit' just 'Unit'), which would then incorrectly parse to the built-in type and subsequently not find the manifest. --- src/effects/backends/effectsbackend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/effects/backends/effectsbackend.cpp b/src/effects/backends/effectsbackend.cpp index 27069368908..9635d8fc60f 100644 --- a/src/effects/backends/effectsbackend.cpp +++ b/src/effects/backends/effectsbackend.cpp @@ -3,7 +3,7 @@ #include namespace { -const QString backendTypeNameAU = QStringLiteral("Audio Unit"); +const QString backendTypeNameAU = QStringLiteral("AudioUnit"); const QString backendTypeNameLV2 = QStringLiteral("LV2"); // QString::tr requires const char[] rather than QString //: Backend type for effects that are built into Mixxx. From b640a74673781a5a39b77eeba56cd4628a08649b Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 19:57:06 +0100 Subject: [PATCH 017/124] AUBackend: Identify AU effects by their metadata --- src/effects/backends/au/aubackend.mm | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index 44609d9cfc2..98e057fbf94 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -18,7 +18,7 @@ /// An effects backend for Audio Unit (AU) plugins. macOS-only. class AUBackend : public EffectsBackend { public: - AUBackend() : m_componentsById([[NSDictionary alloc] init]), m_nextId(0) { + AUBackend() : m_componentsById([[NSDictionary alloc] init]) { loadAudioUnits(); } @@ -59,11 +59,6 @@ bool canInstantiateEffect(const QString& effectId) const override { private: NSDictionary* m_componentsById; QHash m_manifestsById; - int m_nextId; - - QString freshId() { - return QString::number(m_nextId++); - } void loadAudioUnits() { qDebug() << "Loading audio units..."; @@ -95,7 +90,11 @@ void loadAudioUnits() { for (AVAudioUnitComponent* component in components) { qDebug() << "Found audio unit" << [component name]; - QString effectId = freshId(); + QString effectId = QString::fromNSString( + [NSString stringWithFormat:@"%@~%@~%@", + [component manufacturerName], + [component name], + [component versionString]]); componentsById[effectId.toNSString()] = component; manifestsById[effectId] = EffectManifestPointer(new AUManifest(effectId, component)); From 762a864ff5dd19d3ab9f445b0ef95ba3638d5d03 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 21:17:04 +0100 Subject: [PATCH 018/124] AUEffectProcessor: Implement basic audio unit instantiation --- src/effects/backends/au/aubackend.mm | 4 +- src/effects/backends/au/aueffectprocessor.h | 27 +++++++- src/effects/backends/au/aueffectprocessor.mm | 70 +++++++++++++++++++- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/aubackend.mm index 98e057fbf94..0f0e7a55e11 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/aubackend.mm @@ -53,7 +53,9 @@ bool canInstantiateEffect(const QString& effectId) const override { std::unique_ptr createProcessor( const EffectManifestPointer pManifest) const override { - return std::make_unique(pManifest); + AVAudioUnitComponent* component = + m_componentsById[pManifest->id().toNSString()]; + return std::make_unique(component); } private: diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index 49ab4d57cc1..933ecb9ec9c 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -1,15 +1,37 @@ #pragma once +#import + +#include + #include "effects/backends/effectprocessor.h" class AUEffectGroupState final : public EffectState { public: AUEffectGroupState(const mixxx::EngineParameters& engineParameters); + + /// Fetches the audio unit if already instantiated. Non-blocking and + /// thread-safe. + AVAudioUnit* _Nullable getAudioUnit(); + + /// Initiates an asynchronous instantiation of the audio unit if not already + /// in progress. Non-blocking and thread-safe. + void instantiateAudioUnitAsyncIfNeeded(AVAudioUnitComponent* _Nullable component); + + private: + std::atomic m_isInstantiating; + std::atomic m_isInstantiated; + AVAudioUnit* m_audioUnit; + + void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); }; class AUEffectProcessor final : public EffectProcessorImpl { public: - AUEffectProcessor(const EffectManifestPointer pManifest); + AUEffectProcessor(AVAudioUnitComponent* component = nil); + + AUEffectGroupState* createSpecificState( + const mixxx::EngineParameters& engineParameters) override; void loadEngineEffectParameters( const QMap& parameters) @@ -21,4 +43,7 @@ class AUEffectProcessor final : public EffectProcessorImpl { const mixxx::EngineParameters& engineParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) override; + + private: + AVAudioUnitComponent* _Nullable m_component; }; diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index daf58d4c09a..ec8eb65a681 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -1,12 +1,76 @@ +#import + +#include +#include + #include "effects/backends/au/aueffectprocessor.h" AUEffectGroupState::AUEffectGroupState( const mixxx::EngineParameters& engineParameters) - : EffectState(engineParameters) { + : EffectState(engineParameters), + m_isInstantiating(false), + m_audioUnit(nil) { } -AUEffectProcessor::AUEffectProcessor(const EffectManifestPointer pManifest) { - // TODO +AVAudioUnit* _Nullable AUEffectGroupState::getAudioUnit() { + // We need to load this atomic flag to ensure that we don't get a partial + // read of the audio unit pointer (probably extremely uncommon, but not + // impossible: https://belkadan.com/blog/2023/10/Implicity-Atomic) + if (!m_isInstantiated.load()) { + return nil; + } + return m_audioUnit; +} + +void AUEffectGroupState::instantiateAudioUnitAsyncIfNeeded( + AVAudioUnitComponent* _Nullable component) { + // Ensure that we only instantiate once + if (!m_isInstantiating.exchange(true)) { + instantiateAudioUnitAsync(component); + } +} + +void AUEffectGroupState::instantiateAudioUnitAsync( + AVAudioUnitComponent* _Nullable component) { + // NOTE: The component can be null if the lookup failed in + // `AUBackend::createProcessor`, in which case the effect simply acts as an + // identity function on the audio. Same applies when `AUEffectProcessor` is + // default-initialized. + if (!component) { + return; + } + + auto description = [component audioComponentDescription]; + auto options = kAudioComponentInstantiation_LoadOutOfProcess; + + // TODO: Fix the weird formatting of blocks + // clang-format off + [AVAudioUnit instantiateWithComponentDescription:description + options:options + completionHandler:^(AVAudioUnit* _Nullable audioUnit, NSError* _Nullable error) { + if (error == nil) { + VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { + return; + } + m_audioUnit = audioUnit; + m_isInstantiated.store(true); + } else { + qWarning() << "Could not instantiate audio unit:" + << QString::fromNSString([error localizedDescription]); + } + }]; + // clang-format on +} + +AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* component) + : m_component(component) { +} + +AUEffectGroupState* AUEffectProcessor::createSpecificState( + const mixxx::EngineParameters& engineParameters) { + AUEffectGroupState* state = new AUEffectGroupState(engineParameters); + state->instantiateAudioUnitAsyncIfNeeded(m_component); + return state; } void AUEffectProcessor::loadEngineEffectParameters( From 9224ef435c04caa4defec97a25cc09e911f2d164 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 22:04:20 +0100 Subject: [PATCH 019/124] AUEffectProcessor: Move instantiation to AudioUnitManager --- CMakeLists.txt | 5 +- src/effects/backends/au/aueffectprocessor.h | 29 +++++++---- src/effects/backends/au/aueffectprocessor.mm | 52 +++++++++++--------- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da153184114..618ef63e5df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1569,7 +1569,10 @@ if(APPLE) src/effects/backends/au/aueffectprocessor.mm src/effects/backends/au/aumanifest.mm ) - target_link_libraries(mixxx-lib PRIVATE "-weak_framework AVFAudio") + target_link_libraries(mixxx-lib PRIVATE + "-weak_framework AudioToolbox" + "-weak_framework AVFAudio" + ) target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) endif() endif() diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index 933ecb9ec9c..37fd7f47abe 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -1,38 +1,48 @@ #pragma once #import +#import +#import #include #include "effects/backends/effectprocessor.h" -class AUEffectGroupState final : public EffectState { +/// Manages instantiation of an audio unit. Only for internal use. +class AudioUnitManager { public: - AUEffectGroupState(const mixxx::EngineParameters& engineParameters); + AudioUnitManager(AVAudioUnitComponent* component); /// Fetches the audio unit if already instantiated. Non-blocking and /// thread-safe. AVAudioUnit* _Nullable getAudioUnit(); - /// Initiates an asynchronous instantiation of the audio unit if not already - /// in progress. Non-blocking and thread-safe. - void instantiateAudioUnitAsyncIfNeeded(AVAudioUnitComponent* _Nullable component); + /// Fetches the underlying audio unit if already instantiated. + /// Non-blocking and thread-safe. + AudioUnit _Nullable getRawAudioUnit(); private: - std::atomic m_isInstantiating; std::atomic m_isInstantiated; AVAudioUnit* m_audioUnit; void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); }; +class AUEffectGroupState final : public EffectState { + public: + AUEffectGroupState(const mixxx::EngineParameters& engineParameters); + + AudioTimeStamp getTimestamp(); + void incrementTimestamp(); + + private: + AudioTimeStamp m_timestamp; +}; + class AUEffectProcessor final : public EffectProcessorImpl { public: AUEffectProcessor(AVAudioUnitComponent* component = nil); - AUEffectGroupState* createSpecificState( - const mixxx::EngineParameters& engineParameters) override; - void loadEngineEffectParameters( const QMap& parameters) override; @@ -46,4 +56,5 @@ class AUEffectProcessor final : public EffectProcessorImpl { private: AVAudioUnitComponent* _Nullable m_component; + AudioUnitManager m_manager; }; diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index ec8eb65a681..64c8918e806 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -1,18 +1,13 @@ #import +#import +#import #include #include #include "effects/backends/au/aueffectprocessor.h" -AUEffectGroupState::AUEffectGroupState( - const mixxx::EngineParameters& engineParameters) - : EffectState(engineParameters), - m_isInstantiating(false), - m_audioUnit(nil) { -} - -AVAudioUnit* _Nullable AUEffectGroupState::getAudioUnit() { +AVAudioUnit* _Nullable AudioUnitManager::getAudioUnit() { // We need to load this atomic flag to ensure that we don't get a partial // read of the audio unit pointer (probably extremely uncommon, but not // impossible: https://belkadan.com/blog/2023/10/Implicity-Atomic) @@ -22,15 +17,12 @@ return m_audioUnit; } -void AUEffectGroupState::instantiateAudioUnitAsyncIfNeeded( - AVAudioUnitComponent* _Nullable component) { - // Ensure that we only instantiate once - if (!m_isInstantiating.exchange(true)) { - instantiateAudioUnitAsync(component); - } +AudioUnit _Nullable AudioUnitManager::getRawAudioUnit() { + // NOTE: We use the fact that Obj-C calls to nil are simply nil + return [getAudioUnit() audioUnit]; } -void AUEffectGroupState::instantiateAudioUnitAsync( +void AudioUnitManager::instantiateAudioUnitAsync( AVAudioUnitComponent* _Nullable component) { // NOTE: The component can be null if the lookup failed in // `AUBackend::createProcessor`, in which case the effect simply acts as an @@ -62,15 +54,21 @@ // clang-format on } -AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* component) - : m_component(component) { +AUEffectGroupState::AUEffectGroupState( + const mixxx::EngineParameters& engineParameters) + : EffectState(engineParameters), + m_timestamp{ + .mSampleTime = 0, + .mFlags = kAudioTimeStampSampleTimeValid, + } { +} + +AudioTimeStamp AUEffectGroupState::getTimestamp() { + return m_timestamp; } -AUEffectGroupState* AUEffectProcessor::createSpecificState( - const mixxx::EngineParameters& engineParameters) { - AUEffectGroupState* state = new AUEffectGroupState(engineParameters); - state->instantiateAudioUnitAsyncIfNeeded(m_component); - return state; +void AUEffectGroupState::incrementTimestamp() { + m_timestamp.mSampleTime += 1; } void AUEffectProcessor::loadEngineEffectParameters( @@ -84,5 +82,13 @@ const mixxx::EngineParameters& engineParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) { - // TODO + AudioUnit _Nullable rawAudioUnit = m_manager.getRawAudioUnit(); + if (!rawAudioUnit) { + return; + } + + AudioTimeStamp timestamp = channelState->getTimestamp(); + channelState->incrementTimestamp(); + + // TODO: Render } From 8915e7a3343e750d0ee9300475d12bfb7d838330 Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 12 Oct 2023 22:32:37 +0100 Subject: [PATCH 020/124] AUEffectProcessor: Try implementing the AU effect render cycle --- src/effects/backends/au/aueffectprocessor.h | 10 +-- src/effects/backends/au/aueffectprocessor.mm | 89 +++++++++++++++++--- 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index 37fd7f47abe..f12f31ce278 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -13,17 +13,13 @@ class AudioUnitManager { public: AudioUnitManager(AVAudioUnitComponent* component); - /// Fetches the audio unit if already instantiated. Non-blocking and - /// thread-safe. - AVAudioUnit* _Nullable getAudioUnit(); - - /// Fetches the underlying audio unit if already instantiated. + /// Fetches the audio unit if already instantiated. /// Non-blocking and thread-safe. - AudioUnit _Nullable getRawAudioUnit(); + AUAudioUnit* _Nullable getAudioUnit(); private: std::atomic m_isInstantiated; - AVAudioUnit* m_audioUnit; + AUAudioUnit* m_audioUnit; void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); }; diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index 64c8918e806..e40fb88a152 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -7,7 +7,11 @@ #include "effects/backends/au/aueffectprocessor.h" -AVAudioUnit* _Nullable AudioUnitManager::getAudioUnit() { +AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* component) { + instantiateAudioUnitAsync(component); +} + +AUAudioUnit* _Nullable AudioUnitManager::getAudioUnit() { // We need to load this atomic flag to ensure that we don't get a partial // read of the audio unit pointer (probably extremely uncommon, but not // impossible: https://belkadan.com/blog/2023/10/Implicity-Atomic) @@ -17,11 +21,6 @@ return m_audioUnit; } -AudioUnit _Nullable AudioUnitManager::getRawAudioUnit() { - // NOTE: We use the fact that Obj-C calls to nil are simply nil - return [getAudioUnit() audioUnit]; -} - void AudioUnitManager::instantiateAudioUnitAsync( AVAudioUnitComponent* _Nullable component) { // NOTE: The component can be null if the lookup failed in @@ -35,11 +34,18 @@ auto description = [component audioComponentDescription]; auto options = kAudioComponentInstantiation_LoadOutOfProcess; + // Instantiate the audio unit asynchronously. + + // NOTE: We don't need AVAudioUnit here, since we want to render samples + // without using an `AUGraph` or `AVAudioEngine`. Hence the recommended + // approach is to simply instantiate the `AUAudioUnit` directly. See + // https://www.mail-archive.com/coreaudio-api@lists.apple.com/msg01084.html + // TODO: Fix the weird formatting of blocks // clang-format off - [AVAudioUnit instantiateWithComponentDescription:description + [AUAudioUnit instantiateWithComponentDescription:description options:options - completionHandler:^(AVAudioUnit* _Nullable audioUnit, NSError* _Nullable error) { + completionHandler:^(AUAudioUnit* _Nullable audioUnit, NSError* _Nullable error) { if (error == nil) { VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { return; @@ -71,6 +77,10 @@ m_timestamp.mSampleTime += 1; } +AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* component) + : m_component(component), m_manager(component) { +} + void AUEffectProcessor::loadEngineEffectParameters( const QMap& parameters) { // TODO @@ -82,13 +92,70 @@ const mixxx::EngineParameters& engineParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) { - AudioUnit _Nullable rawAudioUnit = m_manager.getRawAudioUnit(); - if (!rawAudioUnit) { + AUAudioUnit* _Nullable audioUnit = m_manager.getAudioUnit(); + if (!audioUnit) { + qWarning() << "Audio Unit not instantiated yet"; + return; + } + + NSError* error = nil; + [audioUnit allocateRenderResourcesAndReturnError:&error]; + if (error != nil) { + qWarning() << "Audio Unit could not allocate render resources" + << QString::fromNSString([error localizedDescription]); return; } AudioTimeStamp timestamp = channelState->getTimestamp(); channelState->incrementTimestamp(); - // TODO: Render + AudioUnitRenderActionFlags flags = 0; + AUAudioFrameCount framesToRender = 1; + NSInteger outputBusNumber = 0; + AudioBufferList outputData = {.mNumberBuffers = 1, + .mBuffers = {{ + .mNumberChannels = 1, + .mDataByteSize = sizeof(CSAMPLE), + .mData = pOutput, + }}}; + + // TODO: Fix the weird formatting of blocks + // clang-format off + AURenderPullInputBlock pullInput = ^( + AudioUnitRenderActionFlags *actionFlags, + const AudioTimeStamp *timestamp, + AUAudioFrameCount frameCount, + NSInteger inputBusNumber, + AudioBufferList *inputData + ) { + VERIFY_OR_DEBUG_ASSERT(inputData->mNumberBuffers >= 1) { + qWarning() << "AURenderPullInputBlock received empty input data"; + return 1; // TODO: Proper error-code + }; + + AudioBuffer* buffer = &inputData->mBuffers[0]; + + VERIFY_OR_DEBUG_ASSERT(buffer->mDataByteSize == sizeof(CSAMPLE)) { + qWarning() << "AURenderPullInputBlock encountered a buffer of size" << buffer->mDataByteSize << "(rather than the expected" << sizeof(CSAMPLE) << "bytes)"; + return 1; // TODO: Proper error-code + } + + CSAMPLE* data = static_cast(buffer->mData); + *data = *pInput; + + return 0; // No error + }; + // clang-format on + + // Apply the actual effect to the sample. + // See + // https://developer.apple.com/documentation/audiotoolbox/aurenderblock?language=objc + [audioUnit renderBlock](&flags, + ×tamp, + framesToRender, + outputBusNumber, + &outputData, + pullInput); + + [audioUnit deallocateRenderResources]; } From a2ae0e0a6ef0843254e3ad4fef8bc132c9a84d24 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 14 Oct 2023 14:12:01 +0100 Subject: [PATCH 021/124] AUEffectProcessor: Perform (de)allocation only once --- src/effects/backends/au/aueffectprocessor.h | 4 ++++ src/effects/backends/au/aueffectprocessor.mm | 25 ++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index f12f31ce278..ecd35fb4ca3 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -12,6 +12,10 @@ class AudioUnitManager { public: AudioUnitManager(AVAudioUnitComponent* component); + ~AudioUnitManager(); + + AudioUnitManager(const AudioUnitManager&) = delete; + AudioUnitManager& operator=(const AudioUnitManager&) = delete; /// Fetches the audio unit if already instantiated. /// Non-blocking and thread-safe. diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index e40fb88a152..b9f6f898dbb 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -11,6 +11,12 @@ instantiateAudioUnitAsync(component); } +AudioUnitManager::~AudioUnitManager() { + if (m_isInstantiated.load()) { + [m_audioUnit deallocateRenderResources]; + } +} + AUAudioUnit* _Nullable AudioUnitManager::getAudioUnit() { // We need to load this atomic flag to ensure that we don't get a partial // read of the audio unit pointer (probably extremely uncommon, but not @@ -50,6 +56,15 @@ VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { return; } + + NSError* error = nil; + [audioUnit allocateRenderResourcesAndReturnError:&error]; + if (error != nil) { + qWarning() << "Audio Unit failed to allocate render resources" + << QString::fromNSString([error localizedDescription]); + return; + } + m_audioUnit = audioUnit; m_isInstantiated.store(true); } else { @@ -98,14 +113,6 @@ return; } - NSError* error = nil; - [audioUnit allocateRenderResourcesAndReturnError:&error]; - if (error != nil) { - qWarning() << "Audio Unit could not allocate render resources" - << QString::fromNSString([error localizedDescription]); - return; - } - AudioTimeStamp timestamp = channelState->getTimestamp(); channelState->incrementTimestamp(); @@ -156,6 +163,4 @@ outputBusNumber, &outputData, pullInput); - - [audioUnit deallocateRenderResources]; } From 18db0522bcb03b054dfb7b5ad08549dc8f0a1047 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 14 Oct 2023 14:30:19 +0100 Subject: [PATCH 022/124] AUEffectProcessor: Check render status --- src/effects/backends/au/aueffectprocessor.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index b9f6f898dbb..01f5a5e272d 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -157,10 +157,15 @@ // Apply the actual effect to the sample. // See // https://developer.apple.com/documentation/audiotoolbox/aurenderblock?language=objc - [audioUnit renderBlock](&flags, + AUAudioUnitStatus status = [audioUnit renderBlock](&flags, ×tamp, framesToRender, outputBusNumber, &outputData, pullInput); + + if (status != 0) { + qWarning() << "Rendering Audio Unit failed with status" << status; + return; + } } From 2ee48183ceba019bccb362aa7ba09dbd53678961 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 14 Oct 2023 16:40:30 +0100 Subject: [PATCH 023/124] AUEffectProcessor: Add more nullability annotations --- src/effects/backends/au/aueffectprocessor.h | 12 ++++++------ src/effects/backends/au/aueffectprocessor.mm | 13 ++++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index ecd35fb4ca3..d4b0a0c0f2b 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -11,7 +11,7 @@ /// Manages instantiation of an audio unit. Only for internal use. class AudioUnitManager { public: - AudioUnitManager(AVAudioUnitComponent* component); + AudioUnitManager(AVAudioUnitComponent* _Nullable component); ~AudioUnitManager(); AudioUnitManager(const AudioUnitManager&) = delete; @@ -23,7 +23,7 @@ class AudioUnitManager { private: std::atomic m_isInstantiated; - AUAudioUnit* m_audioUnit; + AUAudioUnit* _Nullable m_audioUnit; void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); }; @@ -41,15 +41,15 @@ class AUEffectGroupState final : public EffectState { class AUEffectProcessor final : public EffectProcessorImpl { public: - AUEffectProcessor(AVAudioUnitComponent* component = nil); + AUEffectProcessor(AVAudioUnitComponent* _Nullable component = nil); void loadEngineEffectParameters( const QMap& parameters) override; - void processChannel(AUEffectGroupState* channelState, - const CSAMPLE* pInput, - CSAMPLE* pOutput, + void processChannel(AUEffectGroupState* _Nonnull channelState, + const CSAMPLE* _Nonnull pInput, + CSAMPLE* _Nonnull pOutput, const mixxx::EngineParameters& engineParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) override; diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index 01f5a5e272d..0a6f13723c3 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -6,8 +6,10 @@ #include #include "effects/backends/au/aueffectprocessor.h" +#include "engine/engine.h" +#include "util/assert.h" -AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* component) { +AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component) { instantiateAudioUnitAsync(component); } @@ -92,7 +94,7 @@ m_timestamp.mSampleTime += 1; } -AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* component) +AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* _Nullable component) : m_component(component), m_manager(component) { } @@ -101,9 +103,10 @@ // TODO } -void AUEffectProcessor::processChannel(AUEffectGroupState* channelState, - const CSAMPLE* pInput, - CSAMPLE* pOutput, +void AUEffectProcessor::processChannel( + AUEffectGroupState* _Nonnull channelState, + const CSAMPLE* _Nonnull pInput, + CSAMPLE* _Nonnull pOutput, const mixxx::EngineParameters& engineParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) { From d0b15031f3e1120527ffc5f8cc15a001565dad85 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 14 Oct 2023 16:47:49 +0100 Subject: [PATCH 024/124] AUEffectProcessor: Configure format --- src/effects/backends/au/aueffectprocessor.h | 1 + src/effects/backends/au/aueffectprocessor.mm | 24 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index d4b0a0c0f2b..4f003e53b59 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -56,5 +56,6 @@ class AUEffectProcessor final : public EffectProcessorImpl { private: AVAudioUnitComponent* _Nullable m_component; + std::atomic m_isConfigured; AudioUnitManager m_manager; }; diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index 0a6f13723c3..8aa443321a9 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -116,6 +116,30 @@ return; } + if (!m_isConfigured.exchange(true)) { + for (AUAudioUnitBusArray* buses in + @[ [audioUnit inputBusses], [audioUnit outputBusses] ]) { + for (AUAudioUnitBus* bus in buses) { + NSError* error = nil; + + auto format = [[AVAudioFormat alloc] + initWithCommonFormat:AVAudioPCMFormatFloat32 + sampleRate:engineParameters.sampleRate() + channels:engineParameters.channelCount() + interleaved:false]; + + [bus setFormat:format error:&error]; + + if (error != nil) { + qWarning() << "Could not set Audio Unit format:" + << QString::fromNSString( + [error localizedDescription]); + return; + } + } + } + } + AudioTimeStamp timestamp = channelState->getTimestamp(); channelState->incrementTimestamp(); From 844bb30ddb0d849aa62e87fb4ecb571450ab5529 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 14 Oct 2023 16:50:50 +0100 Subject: [PATCH 025/124] AUEffectProcessor: Configure audio unit busses --- src/effects/backends/au/aueffectprocessor.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index 8aa443321a9..021e09cee92 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -117,6 +117,10 @@ } if (!m_isConfigured.exchange(true)) { + qDebug() << "Configuring Audio Unit to use a sample rate of" + << engineParameters.sampleRate() << "and a channel count of" + << engineParameters.channelCount(); + for (AUAudioUnitBusArray* buses in @[ [audioUnit inputBusses], [audioUnit outputBusses] ]) { for (AUAudioUnitBus* bus in buses) { From a9f4627e1a2257368dc69732f8375d2a656e4608 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 14 Oct 2023 18:45:49 +0100 Subject: [PATCH 026/124] AUEffectProcessor: Factor out AudioUnitManager --- CMakeLists.txt | 1 + src/effects/backends/au/audiounitmanager.h | 25 +++++++ src/effects/backends/au/audiounitmanager.mm | 69 ++++++++++++++++++ src/effects/backends/au/aueffectprocessor.h | 22 +----- src/effects/backends/au/aueffectprocessor.mm | 73 ++------------------ 5 files changed, 100 insertions(+), 90 deletions(-) create mode 100644 src/effects/backends/au/audiounitmanager.h create mode 100644 src/effects/backends/au/audiounitmanager.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 618ef63e5df..316729ae695 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1566,6 +1566,7 @@ if(APPLE) if(AU_EFFECTS) target_sources(mixxx-lib PRIVATE src/effects/backends/au/aubackend.mm + src/effects/backends/au/audiounitmanager.mm src/effects/backends/au/aueffectprocessor.mm src/effects/backends/au/aumanifest.mm ) diff --git a/src/effects/backends/au/audiounitmanager.h b/src/effects/backends/au/audiounitmanager.h new file mode 100644 index 00000000000..0f46cdee509 --- /dev/null +++ b/src/effects/backends/au/audiounitmanager.h @@ -0,0 +1,25 @@ +#pragma once + +#import +#import +#import + +/// Manages instantiation of an audio unit. Only for internal use. +class AudioUnitManager { + public: + AudioUnitManager(AVAudioUnitComponent* _Nullable component); + ~AudioUnitManager(); + + AudioUnitManager(const AudioUnitManager&) = delete; + AudioUnitManager& operator=(const AudioUnitManager&) = delete; + + /// Fetches the audio unit if already instantiated. + /// Non-blocking and thread-safe. + AUAudioUnit* _Nullable getAudioUnit(); + + private: + std::atomic m_isInstantiated; + AUAudioUnit* _Nullable m_audioUnit; + + void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); +}; diff --git a/src/effects/backends/au/audiounitmanager.mm b/src/effects/backends/au/audiounitmanager.mm new file mode 100644 index 00000000000..71eead3dbcb --- /dev/null +++ b/src/effects/backends/au/audiounitmanager.mm @@ -0,0 +1,69 @@ +#include "effects/backends/au/audiounitmanager.h" + +AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component) { + instantiateAudioUnitAsync(component); +} + +AudioUnitManager::~AudioUnitManager() { + if (m_isInstantiated.load()) { + [m_audioUnit deallocateRenderResources]; + } +} + +AUAudioUnit* _Nullable AudioUnitManager::getAudioUnit() { + // We need to load this atomic flag to ensure that we don't get a partial + // read of the audio unit pointer (probably extremely uncommon, but not + // impossible: https://belkadan.com/blog/2023/10/Implicity-Atomic) + if (!m_isInstantiated.load()) { + return nil; + } + return m_audioUnit; +} + +void AudioUnitManager::instantiateAudioUnitAsync( + AVAudioUnitComponent* _Nullable component) { + // NOTE: The component can be null if the lookup failed in + // `AUBackend::createProcessor`, in which case the effect simply acts as an + // identity function on the audio. Same applies when `AUEffectProcessor` is + // default-initialized. + if (!component) { + return; + } + + auto description = [component audioComponentDescription]; + auto options = kAudioComponentInstantiation_LoadOutOfProcess; + + // Instantiate the audio unit asynchronously. + + // NOTE: We don't need AVAudioUnit here, since we want to render samples + // without using an `AUGraph` or `AVAudioEngine`. Hence the recommended + // approach is to simply instantiate the `AUAudioUnit` directly. See + // https://www.mail-archive.com/coreaudio-api@lists.apple.com/msg01084.html + + // TODO: Fix the weird formatting of blocks + // clang-format off + [AUAudioUnit instantiateWithComponentDescription:description + options:options + completionHandler:^(AUAudioUnit* _Nullable audioUnit, NSError* _Nullable error) { + if (error == nil) { + VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { + return; + } + + NSError* error = nil; + [audioUnit allocateRenderResourcesAndReturnError:&error]; + if (error != nil) { + qWarning() << "Audio Unit failed to allocate render resources" + << QString::fromNSString([error localizedDescription]); + return; + } + + m_audioUnit = audioUnit; + m_isInstantiated.store(true); + } else { + qWarning() << "Could not instantiate audio unit:" + << QString::fromNSString([error localizedDescription]); + } + }]; + // clang-format on +} diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index 4f003e53b59..f3563aba33e 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -6,28 +6,9 @@ #include +#include "effects/backends/au/audiounitmanager.h" #include "effects/backends/effectprocessor.h" -/// Manages instantiation of an audio unit. Only for internal use. -class AudioUnitManager { - public: - AudioUnitManager(AVAudioUnitComponent* _Nullable component); - ~AudioUnitManager(); - - AudioUnitManager(const AudioUnitManager&) = delete; - AudioUnitManager& operator=(const AudioUnitManager&) = delete; - - /// Fetches the audio unit if already instantiated. - /// Non-blocking and thread-safe. - AUAudioUnit* _Nullable getAudioUnit(); - - private: - std::atomic m_isInstantiated; - AUAudioUnit* _Nullable m_audioUnit; - - void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); -}; - class AUEffectGroupState final : public EffectState { public: AUEffectGroupState(const mixxx::EngineParameters& engineParameters); @@ -55,7 +36,6 @@ class AUEffectProcessor final : public EffectProcessorImpl { const GroupFeatureState& groupFeatures) override; private: - AVAudioUnitComponent* _Nullable m_component; std::atomic m_isConfigured; AudioUnitManager m_manager; }; diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index 021e09cee92..96ff627a7bb 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -9,74 +9,6 @@ #include "engine/engine.h" #include "util/assert.h" -AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component) { - instantiateAudioUnitAsync(component); -} - -AudioUnitManager::~AudioUnitManager() { - if (m_isInstantiated.load()) { - [m_audioUnit deallocateRenderResources]; - } -} - -AUAudioUnit* _Nullable AudioUnitManager::getAudioUnit() { - // We need to load this atomic flag to ensure that we don't get a partial - // read of the audio unit pointer (probably extremely uncommon, but not - // impossible: https://belkadan.com/blog/2023/10/Implicity-Atomic) - if (!m_isInstantiated.load()) { - return nil; - } - return m_audioUnit; -} - -void AudioUnitManager::instantiateAudioUnitAsync( - AVAudioUnitComponent* _Nullable component) { - // NOTE: The component can be null if the lookup failed in - // `AUBackend::createProcessor`, in which case the effect simply acts as an - // identity function on the audio. Same applies when `AUEffectProcessor` is - // default-initialized. - if (!component) { - return; - } - - auto description = [component audioComponentDescription]; - auto options = kAudioComponentInstantiation_LoadOutOfProcess; - - // Instantiate the audio unit asynchronously. - - // NOTE: We don't need AVAudioUnit here, since we want to render samples - // without using an `AUGraph` or `AVAudioEngine`. Hence the recommended - // approach is to simply instantiate the `AUAudioUnit` directly. See - // https://www.mail-archive.com/coreaudio-api@lists.apple.com/msg01084.html - - // TODO: Fix the weird formatting of blocks - // clang-format off - [AUAudioUnit instantiateWithComponentDescription:description - options:options - completionHandler:^(AUAudioUnit* _Nullable audioUnit, NSError* _Nullable error) { - if (error == nil) { - VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { - return; - } - - NSError* error = nil; - [audioUnit allocateRenderResourcesAndReturnError:&error]; - if (error != nil) { - qWarning() << "Audio Unit failed to allocate render resources" - << QString::fromNSString([error localizedDescription]); - return; - } - - m_audioUnit = audioUnit; - m_isInstantiated.store(true); - } else { - qWarning() << "Could not instantiate audio unit:" - << QString::fromNSString([error localizedDescription]); - } - }]; - // clang-format on -} - AUEffectGroupState::AUEffectGroupState( const mixxx::EngineParameters& engineParameters) : EffectState(engineParameters), @@ -95,7 +27,7 @@ } AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* _Nullable component) - : m_component(component), m_manager(component) { + : m_manager(component) { } void AUEffectProcessor::loadEngineEffectParameters( @@ -121,6 +53,9 @@ << engineParameters.sampleRate() << "and a channel count of" << engineParameters.channelCount(); + qWarning() << "Inputs:" << [[audioUnit inputBusses] count]; + qWarning() << "Outputs:" << [[audioUnit outputBusses] count]; + for (AUAudioUnitBusArray* buses in @[ [audioUnit inputBusses], [audioUnit outputBusses] ]) { for (AUAudioUnitBus* bus in buses) { From cf6ac1f41b6d63cc431471fbe3be3c85a58a13bc Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 14 Oct 2023 19:45:30 +0100 Subject: [PATCH 027/124] AUEffectProcessor: Use AUv2 API --- src/effects/backends/au/audiounitmanager.h | 4 +- src/effects/backends/au/audiounitmanager.mm | 43 +++-- src/effects/backends/au/aueffectprocessor.h | 19 ++- src/effects/backends/au/aueffectprocessor.mm | 169 +++++++++---------- 4 files changed, 117 insertions(+), 118 deletions(-) diff --git a/src/effects/backends/au/audiounitmanager.h b/src/effects/backends/au/audiounitmanager.h index 0f46cdee509..f11200c9cd2 100644 --- a/src/effects/backends/au/audiounitmanager.h +++ b/src/effects/backends/au/audiounitmanager.h @@ -15,11 +15,11 @@ class AudioUnitManager { /// Fetches the audio unit if already instantiated. /// Non-blocking and thread-safe. - AUAudioUnit* _Nullable getAudioUnit(); + AudioUnit _Nullable getAudioUnit(); private: std::atomic m_isInstantiated; - AUAudioUnit* _Nullable m_audioUnit; + AudioUnit _Nullable m_audioUnit; void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); }; diff --git a/src/effects/backends/au/audiounitmanager.mm b/src/effects/backends/au/audiounitmanager.mm index 71eead3dbcb..292ea1c4ea4 100644 --- a/src/effects/backends/au/audiounitmanager.mm +++ b/src/effects/backends/au/audiounitmanager.mm @@ -1,3 +1,5 @@ +#import + #include "effects/backends/au/audiounitmanager.h" AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component) { @@ -6,11 +8,11 @@ AudioUnitManager::~AudioUnitManager() { if (m_isInstantiated.load()) { - [m_audioUnit deallocateRenderResources]; + AudioUnitUninitialize(m_audioUnit); } } -AUAudioUnit* _Nullable AudioUnitManager::getAudioUnit() { +AudioUnit _Nullable AudioUnitManager::getAudioUnit() { // We need to load this atomic flag to ensure that we don't get a partial // read of the audio unit pointer (probably extremely uncommon, but not // impossible: https://belkadan.com/blog/2023/10/Implicity-Atomic) @@ -30,7 +32,6 @@ return; } - auto description = [component audioComponentDescription]; auto options = kAudioComponentInstantiation_LoadOutOfProcess; // Instantiate the audio unit asynchronously. @@ -42,28 +43,24 @@ // TODO: Fix the weird formatting of blocks // clang-format off - [AUAudioUnit instantiateWithComponentDescription:description - options:options - completionHandler:^(AUAudioUnit* _Nullable audioUnit, NSError* _Nullable error) { - if (error == nil) { - VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { - return; - } + AudioComponentInstantiate(component.audioComponent, options, ^(AudioUnit _Nullable audioUnit, OSStatus error) { + if (error != noErr) { + qWarning() << "Could not instantiate Audio Unit:" << error << "(Check https://www.osstatus.com for a description)"; + return; + } - NSError* error = nil; - [audioUnit allocateRenderResourcesAndReturnError:&error]; - if (error != nil) { - qWarning() << "Audio Unit failed to allocate render resources" - << QString::fromNSString([error localizedDescription]); - return; - } + VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { + return; + } - m_audioUnit = audioUnit; - m_isInstantiated.store(true); - } else { - qWarning() << "Could not instantiate audio unit:" - << QString::fromNSString([error localizedDescription]); + OSStatus initError = AudioUnitInitialize(audioUnit); + if (initError != noErr) { + qWarning() << "Audio Unit failed to initialize, i.e. allocate render resources:" << initError << "(Check https://www.osstatus.com for a description)"; + return; } - }]; + + m_audioUnit = audioUnit; + m_isInstantiated.store(true); + }); // clang-format on } diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index f3563aba33e..f8fa6737d1c 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -13,11 +13,26 @@ class AUEffectGroupState final : public EffectState { public: AUEffectGroupState(const mixxx::EngineParameters& engineParameters); - AudioTimeStamp getTimestamp(); - void incrementTimestamp(); + void render(AudioUnit _Nonnull audioUnit, + const CSAMPLE* _Nonnull pInput, + CSAMPLE* _Nonnull pOutput); private: AudioTimeStamp m_timestamp; + AudioBufferList m_inputBuffers; + AudioBufferList m_outputBuffers; + + static OSStatus renderCallbackUntyped(void* rawThis, + AudioUnitRenderActionFlags* inActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumFrames, + AudioBufferList* ioData); + OSStatus renderCallback(AudioUnitRenderActionFlags* inActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumFrames, + AudioBufferList* ioData) const; }; class AUEffectProcessor final : public EffectProcessorImpl { diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index 96ff627a7bb..39f5d4d549d 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -16,14 +16,83 @@ .mSampleTime = 0, .mFlags = kAudioTimeStampSampleTimeValid, } { + m_inputBuffers.mNumberBuffers = 1; + m_outputBuffers.mNumberBuffers = 1; } -AudioTimeStamp AUEffectGroupState::getTimestamp() { - return m_timestamp; +// static +OSStatus AUEffectGroupState::renderCallbackUntyped(void* rawThis, + AudioUnitRenderActionFlags* inActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumFrames, + AudioBufferList* ioData) { + return static_cast(rawThis)->renderCallback( + inActionFlags, inTimeStamp, inBusNumber, inNumFrames, ioData); } -void AUEffectGroupState::incrementTimestamp() { - m_timestamp.mSampleTime += 1; +OSStatus AUEffectGroupState::renderCallback( + AudioUnitRenderActionFlags* inActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumFrames, + AudioBufferList* ioData) const { + if (ioData->mNumberBuffers == 0) { + qWarning() << "Audio unit render callback failed, no buffers available " + "to write to."; + return noErr; + } + VERIFY_OR_DEBUG_ASSERT(m_inputBuffers.mNumberBuffers > 0) { + qWarning() << "Audio unit render callback failed, no buffers available " + "to read from."; + return noErr; + } + ioData->mBuffers[0].mData = m_inputBuffers.mBuffers[0].mData; + return noErr; +} + +void AUEffectGroupState::render(AudioUnit _Nonnull audioUnit, + const CSAMPLE* _Nonnull pInput, + CSAMPLE* _Nonnull pOutput) { + // Fill the input and output buffers. + // TODO: Assert the size + m_inputBuffers.mBuffers[0].mData = const_cast(pInput); + m_outputBuffers.mBuffers[0].mData = pOutput; + + // Set the render callback + AURenderCallbackStruct callback; + callback.inputProc = AUEffectGroupState::renderCallbackUntyped; + callback.inputProcRefCon = this; + + OSStatus setCallbackStatus = AudioUnitSetProperty(audioUnit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &callback, + sizeof(AURenderCallbackStruct)); + if (setCallbackStatus != noErr) { + qWarning() << "Setting Audio Unit render callback failed with status" + << setCallbackStatus; + return; + } + + // Apply the actual effect to the sample. + AudioUnitRenderActionFlags flags = 0; + AUAudioFrameCount framesToRender = 1; + NSInteger outputBusNumber = 0; + OSStatus renderStatus = AudioUnitRender(audioUnit, + &flags, + &m_timestamp, + outputBusNumber, + framesToRender, + &m_outputBuffers); + if (renderStatus != noErr) { + qWarning() << "Rendering Audio Unit failed with status" << renderStatus; + return; + } + + // Increment the timestamp + m_timestamp.mSampleTime += framesToRender; } AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* _Nullable component) @@ -42,96 +111,14 @@ const mixxx::EngineParameters& engineParameters, const EffectEnableState enableState, const GroupFeatureState& groupFeatures) { - AUAudioUnit* _Nullable audioUnit = m_manager.getAudioUnit(); + AudioUnit _Nullable audioUnit = m_manager.getAudioUnit(); if (!audioUnit) { - qWarning() << "Audio Unit not instantiated yet"; + qWarning() << "Audio Unit is not instantiated yet"; return; } - if (!m_isConfigured.exchange(true)) { - qDebug() << "Configuring Audio Unit to use a sample rate of" - << engineParameters.sampleRate() << "and a channel count of" - << engineParameters.channelCount(); - - qWarning() << "Inputs:" << [[audioUnit inputBusses] count]; - qWarning() << "Outputs:" << [[audioUnit outputBusses] count]; - - for (AUAudioUnitBusArray* buses in - @[ [audioUnit inputBusses], [audioUnit outputBusses] ]) { - for (AUAudioUnitBus* bus in buses) { - NSError* error = nil; - - auto format = [[AVAudioFormat alloc] - initWithCommonFormat:AVAudioPCMFormatFloat32 - sampleRate:engineParameters.sampleRate() - channels:engineParameters.channelCount() - interleaved:false]; + // TODO: Set format (even though Core Audio seems to default to 32-bit + // floats, 2 channels and 44.1kHz sample rate) - [bus setFormat:format error:&error]; - - if (error != nil) { - qWarning() << "Could not set Audio Unit format:" - << QString::fromNSString( - [error localizedDescription]); - return; - } - } - } - } - - AudioTimeStamp timestamp = channelState->getTimestamp(); - channelState->incrementTimestamp(); - - AudioUnitRenderActionFlags flags = 0; - AUAudioFrameCount framesToRender = 1; - NSInteger outputBusNumber = 0; - AudioBufferList outputData = {.mNumberBuffers = 1, - .mBuffers = {{ - .mNumberChannels = 1, - .mDataByteSize = sizeof(CSAMPLE), - .mData = pOutput, - }}}; - - // TODO: Fix the weird formatting of blocks - // clang-format off - AURenderPullInputBlock pullInput = ^( - AudioUnitRenderActionFlags *actionFlags, - const AudioTimeStamp *timestamp, - AUAudioFrameCount frameCount, - NSInteger inputBusNumber, - AudioBufferList *inputData - ) { - VERIFY_OR_DEBUG_ASSERT(inputData->mNumberBuffers >= 1) { - qWarning() << "AURenderPullInputBlock received empty input data"; - return 1; // TODO: Proper error-code - }; - - AudioBuffer* buffer = &inputData->mBuffers[0]; - - VERIFY_OR_DEBUG_ASSERT(buffer->mDataByteSize == sizeof(CSAMPLE)) { - qWarning() << "AURenderPullInputBlock encountered a buffer of size" << buffer->mDataByteSize << "(rather than the expected" << sizeof(CSAMPLE) << "bytes)"; - return 1; // TODO: Proper error-code - } - - CSAMPLE* data = static_cast(buffer->mData); - *data = *pInput; - - return 0; // No error - }; - // clang-format on - - // Apply the actual effect to the sample. - // See - // https://developer.apple.com/documentation/audiotoolbox/aurenderblock?language=objc - AUAudioUnitStatus status = [audioUnit renderBlock](&flags, - ×tamp, - framesToRender, - outputBusNumber, - &outputData, - pullInput); - - if (status != 0) { - qWarning() << "Rendering Audio Unit failed with status" << status; - return; - } + channelState->render(audioUnit, pInput, pOutput); } From 46e8af2faead4d3c32b9ea3d7c11678561191de2 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 00:25:19 +0100 Subject: [PATCH 028/124] AUEffectProcessor: Pass the actual sample count --- src/effects/backends/au/aueffectprocessor.h | 1 + src/effects/backends/au/aueffectprocessor.mm | 27 +++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/aueffectprocessor.h index f8fa6737d1c..0506573d45c 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/aueffectprocessor.h @@ -14,6 +14,7 @@ class AUEffectGroupState final : public EffectState { AUEffectGroupState(const mixxx::EngineParameters& engineParameters); void render(AudioUnit _Nonnull audioUnit, + SINT sampleCount, const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput); diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/aueffectprocessor.mm index 39f5d4d549d..d63254e0267 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/aueffectprocessor.mm @@ -31,11 +31,10 @@ inActionFlags, inTimeStamp, inBusNumber, inNumFrames, ioData); } -OSStatus AUEffectGroupState::renderCallback( - AudioUnitRenderActionFlags* inActionFlags, - const AudioTimeStamp* inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumFrames, +OSStatus AUEffectGroupState::renderCallback(AudioUnitRenderActionFlags*, + const AudioTimeStamp*, + UInt32, + UInt32, AudioBufferList* ioData) const { if (ioData->mNumberBuffers == 0) { qWarning() << "Audio unit render callback failed, no buffers available " @@ -52,12 +51,16 @@ } void AUEffectGroupState::render(AudioUnit _Nonnull audioUnit, + SINT sampleCount, const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput) { // Fill the input and output buffers. // TODO: Assert the size + SINT size = sizeof(CSAMPLE) * sampleCount; m_inputBuffers.mBuffers[0].mData = const_cast(pInput); + m_inputBuffers.mBuffers[0].mDataByteSize = size; m_outputBuffers.mBuffers[0].mData = pOutput; + m_outputBuffers.mBuffers[0].mDataByteSize = size; // Set the render callback AURenderCallbackStruct callback; @@ -78,13 +81,12 @@ // Apply the actual effect to the sample. AudioUnitRenderActionFlags flags = 0; - AUAudioFrameCount framesToRender = 1; NSInteger outputBusNumber = 0; OSStatus renderStatus = AudioUnitRender(audioUnit, &flags, &m_timestamp, outputBusNumber, - framesToRender, + sampleCount, &m_outputBuffers); if (renderStatus != noErr) { qWarning() << "Rendering Audio Unit failed with status" << renderStatus; @@ -92,7 +94,7 @@ } // Increment the timestamp - m_timestamp.mSampleTime += framesToRender; + m_timestamp.mSampleTime += sampleCount; } AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* _Nullable component) @@ -100,7 +102,7 @@ } void AUEffectProcessor::loadEngineEffectParameters( - const QMap& parameters) { + const QMap&) { // TODO } @@ -109,8 +111,8 @@ const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput, const mixxx::EngineParameters& engineParameters, - const EffectEnableState enableState, - const GroupFeatureState& groupFeatures) { + const EffectEnableState, + const GroupFeatureState&) { AudioUnit _Nullable audioUnit = m_manager.getAudioUnit(); if (!audioUnit) { qWarning() << "Audio Unit is not instantiated yet"; @@ -120,5 +122,6 @@ // TODO: Set format (even though Core Audio seems to default to 32-bit // floats, 2 channels and 44.1kHz sample rate) - channelState->render(audioUnit, pInput, pOutput); + channelState->render( + audioUnit, engineParameters.samplesPerBuffer(), pInput, pOutput); } From 6ad4db92c1eb54a678c69f8716596cb16362bbbb Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 00:40:49 +0100 Subject: [PATCH 029/124] AudioUnitManager: Dispose of audio unit properly --- src/effects/backends/au/audiounitmanager.h | 3 +++ src/effects/backends/au/audiounitmanager.mm | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/effects/backends/au/audiounitmanager.h b/src/effects/backends/au/audiounitmanager.h index f11200c9cd2..56213b09c26 100644 --- a/src/effects/backends/au/audiounitmanager.h +++ b/src/effects/backends/au/audiounitmanager.h @@ -4,6 +4,8 @@ #import #import +#include + /// Manages instantiation of an audio unit. Only for internal use. class AudioUnitManager { public: @@ -18,6 +20,7 @@ class AudioUnitManager { AudioUnit _Nullable getAudioUnit(); private: + QString m_name; std::atomic m_isInstantiated; AudioUnit _Nullable m_audioUnit; diff --git a/src/effects/backends/au/audiounitmanager.mm b/src/effects/backends/au/audiounitmanager.mm index 292ea1c4ea4..a82f5728898 100644 --- a/src/effects/backends/au/audiounitmanager.mm +++ b/src/effects/backends/au/audiounitmanager.mm @@ -1,14 +1,20 @@ +#import #import +#include + #include "effects/backends/au/audiounitmanager.h" -AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component) { +AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component) + : m_name(QString::fromNSString([component name])) { instantiateAudioUnitAsync(component); } AudioUnitManager::~AudioUnitManager() { if (m_isInstantiated.load()) { + qDebug() << "Uninitializing and disposing of Audio Unit" << m_name; AudioUnitUninitialize(m_audioUnit); + AudioComponentInstanceDispose(m_audioUnit); } } @@ -35,17 +41,13 @@ auto options = kAudioComponentInstantiation_LoadOutOfProcess; // Instantiate the audio unit asynchronously. - - // NOTE: We don't need AVAudioUnit here, since we want to render samples - // without using an `AUGraph` or `AVAudioEngine`. Hence the recommended - // approach is to simply instantiate the `AUAudioUnit` directly. See - // https://www.mail-archive.com/coreaudio-api@lists.apple.com/msg01084.html + qDebug() << "Instantiating Audio Unit" << m_name << "asynchronously"; // TODO: Fix the weird formatting of blocks // clang-format off AudioComponentInstantiate(component.audioComponent, options, ^(AudioUnit _Nullable audioUnit, OSStatus error) { if (error != noErr) { - qWarning() << "Could not instantiate Audio Unit:" << error << "(Check https://www.osstatus.com for a description)"; + qWarning() << "Could not instantiate Audio Unit" << m_name << ":" << error << "(Check https://www.osstatus.com for a description)"; return; } @@ -55,7 +57,7 @@ OSStatus initError = AudioUnitInitialize(audioUnit); if (initError != noErr) { - qWarning() << "Audio Unit failed to initialize, i.e. allocate render resources:" << initError << "(Check https://www.osstatus.com for a description)"; + qWarning() << "Audio Unit" << m_name << "failed to initialize, i.e. allocate render resources:" << initError << "(Check https://www.osstatus.com for a description)"; return; } From 4f1fe1d135568012ddc25f3f85c7eb0e76dbc2a3 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 00:55:18 +0100 Subject: [PATCH 030/124] AudioUnitManager: Add option to instantiate synchronously --- src/effects/backends/au/audiounitmanager.h | 7 +- src/effects/backends/au/audiounitmanager.mm | 74 +++++++++++++++------ 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/effects/backends/au/audiounitmanager.h b/src/effects/backends/au/audiounitmanager.h index 56213b09c26..19e9a6805b0 100644 --- a/src/effects/backends/au/audiounitmanager.h +++ b/src/effects/backends/au/audiounitmanager.h @@ -9,7 +9,7 @@ /// Manages instantiation of an audio unit. Only for internal use. class AudioUnitManager { public: - AudioUnitManager(AVAudioUnitComponent* _Nullable component); + AudioUnitManager(AVAudioUnitComponent* _Nullable component = nil, bool instantiateSync = false); ~AudioUnitManager(); AudioUnitManager(const AudioUnitManager&) = delete; @@ -24,5 +24,8 @@ class AudioUnitManager { std::atomic m_isInstantiated; AudioUnit _Nullable m_audioUnit; - void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nullable component); + void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nonnull component); + void instantiateAudioUnitSync(AVAudioUnitComponent* _Nonnull component); + + void initializeWith(AudioUnit _Nonnull audioUnit); }; diff --git a/src/effects/backends/au/audiounitmanager.mm b/src/effects/backends/au/audiounitmanager.mm index a82f5728898..d220386320a 100644 --- a/src/effects/backends/au/audiounitmanager.mm +++ b/src/effects/backends/au/audiounitmanager.mm @@ -1,13 +1,26 @@ #import #import +#include "util/assert.h" #include #include "effects/backends/au/audiounitmanager.h" -AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component) +AudioUnitManager::AudioUnitManager( + AVAudioUnitComponent* _Nullable component, bool instantiateSync) : m_name(QString::fromNSString([component name])) { - instantiateAudioUnitAsync(component); + // NOTE: The component can be null if the lookup failed in + // `AUBackend::createProcessor`, in which case the effect simply acts as an + // identity function on the audio. Same applies when `AudioUnitManager` is + // default-initialized. + if (!component) { + return; + } + + if (instantiateSync) { + } else { + instantiateAudioUnitAsync(component); + } } AudioUnitManager::~AudioUnitManager() { @@ -29,15 +42,7 @@ } void AudioUnitManager::instantiateAudioUnitAsync( - AVAudioUnitComponent* _Nullable component) { - // NOTE: The component can be null if the lookup failed in - // `AUBackend::createProcessor`, in which case the effect simply acts as an - // identity function on the audio. Same applies when `AUEffectProcessor` is - // default-initialized. - if (!component) { - return; - } - + AVAudioUnitComponent* _Nonnull component) { auto options = kAudioComponentInstantiation_LoadOutOfProcess; // Instantiate the audio unit asynchronously. @@ -51,18 +56,47 @@ return; } - VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { + VERIFY_OR_DEBUG_ASSERT(audioUnit != nil) { + qWarning() << "Could not instantiate Audio Unit" << m_name << "...but the error is noErr, what's going on?"; return; } - OSStatus initError = AudioUnitInitialize(audioUnit); - if (initError != noErr) { - qWarning() << "Audio Unit" << m_name << "failed to initialize, i.e. allocate render resources:" << initError << "(Check https://www.osstatus.com for a description)"; - return; - } - - m_audioUnit = audioUnit; - m_isInstantiated.store(true); + initializeWith(audioUnit); }); // clang-format on } + +void AudioUnitManager::instantiateAudioUnitSync( + AVAudioUnitComponent* _Nonnull component) { + AudioUnit _Nullable audioUnit = nil; + OSStatus error = + AudioComponentInstanceNew(component.audioComponent, &audioUnit); + if (error != noErr) { + qWarning() << "Audio Unit" << m_name + << "could not be instantiated:" << error + << "(Check https://www.osstatus.com for a description)"; + } + + initializeWith(audioUnit); +} + +void AudioUnitManager::initializeWith(AudioUnit _Nonnull audioUnit) { + VERIFY_OR_DEBUG_ASSERT(!m_isInstantiated.load()) { + qWarning() << "Audio Unit" << m_name + << "cannot be initialized after already having been " + "instantiated"; + return; + } + + OSStatus initError = AudioUnitInitialize(audioUnit); + if (initError != noErr) { + qWarning() << "Audio Unit" << m_name + << "failed to initialize, i.e. allocate render resources:" + << initError + << "(Check https://www.osstatus.com for a description)"; + return; + } + + m_audioUnit = audioUnit; + m_isInstantiated.store(true); +} From 63d9b5d3159dee214865255bcfe66404d24c8c07 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 01:08:38 +0100 Subject: [PATCH 031/124] AudioUnitManager: Enable sync instantiation --- src/effects/backends/au/audiounitmanager.h | 12 ++++++++++-- src/effects/backends/au/audiounitmanager.mm | 21 ++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/effects/backends/au/audiounitmanager.h b/src/effects/backends/au/audiounitmanager.h index 19e9a6805b0..c924834ffc0 100644 --- a/src/effects/backends/au/audiounitmanager.h +++ b/src/effects/backends/au/audiounitmanager.h @@ -6,10 +6,18 @@ #include +enum AudioUnitInstantiationType { + Sync, + AsyncInProcess, + AsyncOutOfProcess, +}; + /// Manages instantiation of an audio unit. Only for internal use. class AudioUnitManager { public: - AudioUnitManager(AVAudioUnitComponent* _Nullable component = nil, bool instantiateSync = false); + AudioUnitManager(AVAudioUnitComponent* _Nullable component = nil, + AudioUnitInstantiationType instantiationType = + AudioUnitInstantiationType::AsyncOutOfProcess); ~AudioUnitManager(); AudioUnitManager(const AudioUnitManager&) = delete; @@ -24,7 +32,7 @@ class AudioUnitManager { std::atomic m_isInstantiated; AudioUnit _Nullable m_audioUnit; - void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nonnull component); + void instantiateAudioUnitAsync(AVAudioUnitComponent* _Nonnull component, bool inProcess); void instantiateAudioUnitSync(AVAudioUnitComponent* _Nonnull component); void initializeWith(AudioUnit _Nonnull audioUnit); diff --git a/src/effects/backends/au/audiounitmanager.mm b/src/effects/backends/au/audiounitmanager.mm index d220386320a..c01c79ad651 100644 --- a/src/effects/backends/au/audiounitmanager.mm +++ b/src/effects/backends/au/audiounitmanager.mm @@ -6,8 +6,8 @@ #include "effects/backends/au/audiounitmanager.h" -AudioUnitManager::AudioUnitManager( - AVAudioUnitComponent* _Nullable component, bool instantiateSync) +AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component, + AudioUnitInstantiationType instantiationType) : m_name(QString::fromNSString([component name])) { // NOTE: The component can be null if the lookup failed in // `AUBackend::createProcessor`, in which case the effect simply acts as an @@ -17,9 +17,15 @@ return; } - if (instantiateSync) { - } else { - instantiateAudioUnitAsync(component); + switch (instantiationType) { + case Sync: + instantiateAudioUnitSync(component); + break; + case AsyncInProcess: + case AsyncOutOfProcess: + instantiateAudioUnitAsync( + component, instantiationType == AsyncInProcess); + break; } } @@ -42,8 +48,9 @@ } void AudioUnitManager::instantiateAudioUnitAsync( - AVAudioUnitComponent* _Nonnull component) { - auto options = kAudioComponentInstantiation_LoadOutOfProcess; + AVAudioUnitComponent* _Nonnull component, bool inProcess) { + auto options = inProcess ? kAudioComponentInstantiation_LoadInProcess + : kAudioComponentInstantiation_LoadOutOfProcess; // Instantiate the audio unit asynchronously. qDebug() << "Instantiating Audio Unit" << m_name << "asynchronously"; From 22d70b3de9be6b91e0b626ca23932cdb179e1899 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 01:14:29 +0100 Subject: [PATCH 032/124] AudioUnit: Rename AU -> AudioUnit --- CMakeLists.txt | 6 ++--- .../au/{aubackend.h => audiounitbackend.h} | 2 +- .../au/{aubackend.mm => audiounitbackend.mm} | 24 +++++++++---------- ...processor.h => audiouniteffectprocessor.h} | 10 ++++---- ...ocessor.mm => audiouniteffectprocessor.mm} | 23 +++++++++--------- src/effects/backends/au/audiounitmanager.mm | 6 ++--- .../au/{aumanifest.h => audiounitmanifest.h} | 4 ++-- .../{aumanifest.mm => audiounitmanifest.mm} | 5 ++-- .../backends/effectsbackendmanager.cpp | 4 ++-- 9 files changed, 43 insertions(+), 41 deletions(-) rename src/effects/backends/au/{aubackend.h => audiounitbackend.h} (77%) rename src/effects/backends/au/{aubackend.mm => audiounitbackend.mm} (82%) rename src/effects/backends/au/{aueffectprocessor.h => audiouniteffectprocessor.h} (79%) rename src/effects/backends/au/{aueffectprocessor.mm => audiouniteffectprocessor.mm} (82%) rename src/effects/backends/au/{aumanifest.h => audiounitmanifest.h} (50%) rename src/effects/backends/au/{aumanifest.mm => audiounitmanifest.mm} (69%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 316729ae695..235c64ba18b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1565,10 +1565,10 @@ if(APPLE) option(AU_EFFECTS "Audio Unit (AU) effects integration" ON) if(AU_EFFECTS) target_sources(mixxx-lib PRIVATE - src/effects/backends/au/aubackend.mm + src/effects/backends/au/audiounitbackend.mm src/effects/backends/au/audiounitmanager.mm - src/effects/backends/au/aueffectprocessor.mm - src/effects/backends/au/aumanifest.mm + src/effects/backends/au/audiouniteffectprocessor.mm + src/effects/backends/au/audiounitmanifest.mm ) target_link_libraries(mixxx-lib PRIVATE "-weak_framework AudioToolbox" diff --git a/src/effects/backends/au/aubackend.h b/src/effects/backends/au/audiounitbackend.h similarity index 77% rename from src/effects/backends/au/aubackend.h rename to src/effects/backends/au/audiounitbackend.h index 1e9599f790b..8148cf776dc 100644 --- a/src/effects/backends/au/aubackend.h +++ b/src/effects/backends/au/audiounitbackend.h @@ -7,4 +7,4 @@ #include "effects/backends/effectsbackend.h" #include "effects/defs.h" -EffectsBackendPointer createAUBackend(); +EffectsBackendPointer createAudioUnitBackend(); diff --git a/src/effects/backends/au/aubackend.mm b/src/effects/backends/au/audiounitbackend.mm similarity index 82% rename from src/effects/backends/au/aubackend.mm rename to src/effects/backends/au/audiounitbackend.mm index 0f0e7a55e11..b23b7caeec3 100644 --- a/src/effects/backends/au/aubackend.mm +++ b/src/effects/backends/au/audiounitbackend.mm @@ -1,4 +1,4 @@ -#include "effects/backends/au/aubackend.h" +#include "effects/backends/au/audiounitbackend.h" #import #import @@ -10,19 +10,19 @@ #include #include -#include "effects/backends/au/aubackend.h" -#include "effects/backends/au/aueffectprocessor.h" -#include "effects/backends/au/aumanifest.h" +#include "effects/backends/au/audiounitbackend.h" +#include "effects/backends/au/audiouniteffectprocessor.h" +#include "effects/backends/au/audiounitmanifest.h" #include "effects/defs.h" /// An effects backend for Audio Unit (AU) plugins. macOS-only. -class AUBackend : public EffectsBackend { +class AudioUnitBackend : public EffectsBackend { public: - AUBackend() : m_componentsById([[NSDictionary alloc] init]) { + AudioUnitBackend() : m_componentsById([[NSDictionary alloc] init]) { loadAudioUnits(); } - ~AUBackend() override { + ~AudioUnitBackend() override { } EffectBackendType getType() const override { @@ -55,7 +55,7 @@ bool canInstantiateEffect(const QString& effectId) const override { const EffectManifestPointer pManifest) const override { AVAudioUnitComponent* component = m_componentsById[pManifest->id().toNSString()]; - return std::make_unique(component); + return std::make_unique(component); } private: @@ -98,8 +98,8 @@ void loadAudioUnits() { [component name], [component versionString]]); componentsById[effectId.toNSString()] = component; - manifestsById[effectId] = - EffectManifestPointer(new AUManifest(effectId, component)); + manifestsById[effectId] = EffectManifestPointer( + new AudioUnitManifest(effectId, component)); } m_componentsById = componentsById; @@ -107,6 +107,6 @@ void loadAudioUnits() { } }; -EffectsBackendPointer createAUBackend() { - return EffectsBackendPointer(new AUBackend()); +EffectsBackendPointer createAudioUnitBackend() { + return EffectsBackendPointer(new AudioUnitBackend()); } diff --git a/src/effects/backends/au/aueffectprocessor.h b/src/effects/backends/au/audiouniteffectprocessor.h similarity index 79% rename from src/effects/backends/au/aueffectprocessor.h rename to src/effects/backends/au/audiouniteffectprocessor.h index 0506573d45c..6dd10113571 100644 --- a/src/effects/backends/au/aueffectprocessor.h +++ b/src/effects/backends/au/audiouniteffectprocessor.h @@ -9,9 +9,9 @@ #include "effects/backends/au/audiounitmanager.h" #include "effects/backends/effectprocessor.h" -class AUEffectGroupState final : public EffectState { +class AudioUnitEffectGroupState final : public EffectState { public: - AUEffectGroupState(const mixxx::EngineParameters& engineParameters); + AudioUnitEffectGroupState(const mixxx::EngineParameters& engineParameters); void render(AudioUnit _Nonnull audioUnit, SINT sampleCount, @@ -36,15 +36,15 @@ class AUEffectGroupState final : public EffectState { AudioBufferList* ioData) const; }; -class AUEffectProcessor final : public EffectProcessorImpl { +class AudioUnitEffectProcessor final : public EffectProcessorImpl { public: - AUEffectProcessor(AVAudioUnitComponent* _Nullable component = nil); + AudioUnitEffectProcessor(AVAudioUnitComponent* _Nullable component = nil); void loadEngineEffectParameters( const QMap& parameters) override; - void processChannel(AUEffectGroupState* _Nonnull channelState, + void processChannel(AudioUnitEffectGroupState* _Nonnull channelState, const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput, const mixxx::EngineParameters& engineParameters, diff --git a/src/effects/backends/au/aueffectprocessor.mm b/src/effects/backends/au/audiouniteffectprocessor.mm similarity index 82% rename from src/effects/backends/au/aueffectprocessor.mm rename to src/effects/backends/au/audiouniteffectprocessor.mm index d63254e0267..60ae84eada4 100644 --- a/src/effects/backends/au/aueffectprocessor.mm +++ b/src/effects/backends/au/audiouniteffectprocessor.mm @@ -5,11 +5,11 @@ #include #include -#include "effects/backends/au/aueffectprocessor.h" +#include "effects/backends/au/audiouniteffectprocessor.h" #include "engine/engine.h" #include "util/assert.h" -AUEffectGroupState::AUEffectGroupState( +AudioUnitEffectGroupState::AudioUnitEffectGroupState( const mixxx::EngineParameters& engineParameters) : EffectState(engineParameters), m_timestamp{ @@ -21,17 +21,17 @@ } // static -OSStatus AUEffectGroupState::renderCallbackUntyped(void* rawThis, +OSStatus AudioUnitEffectGroupState::renderCallbackUntyped(void* rawThis, AudioUnitRenderActionFlags* inActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList* ioData) { - return static_cast(rawThis)->renderCallback( + return static_cast(rawThis)->renderCallback( inActionFlags, inTimeStamp, inBusNumber, inNumFrames, ioData); } -OSStatus AUEffectGroupState::renderCallback(AudioUnitRenderActionFlags*, +OSStatus AudioUnitEffectGroupState::renderCallback(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32, UInt32, @@ -50,7 +50,7 @@ return noErr; } -void AUEffectGroupState::render(AudioUnit _Nonnull audioUnit, +void AudioUnitEffectGroupState::render(AudioUnit _Nonnull audioUnit, SINT sampleCount, const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput) { @@ -64,7 +64,7 @@ // Set the render callback AURenderCallbackStruct callback; - callback.inputProc = AUEffectGroupState::renderCallbackUntyped; + callback.inputProc = AudioUnitEffectGroupState::renderCallbackUntyped; callback.inputProcRefCon = this; OSStatus setCallbackStatus = AudioUnitSetProperty(audioUnit, @@ -97,17 +97,18 @@ m_timestamp.mSampleTime += sampleCount; } -AUEffectProcessor::AUEffectProcessor(AVAudioUnitComponent* _Nullable component) +AudioUnitEffectProcessor::AudioUnitEffectProcessor( + AVAudioUnitComponent* _Nullable component) : m_manager(component) { } -void AUEffectProcessor::loadEngineEffectParameters( +void AudioUnitEffectProcessor::loadEngineEffectParameters( const QMap&) { // TODO } -void AUEffectProcessor::processChannel( - AUEffectGroupState* _Nonnull channelState, +void AudioUnitEffectProcessor::processChannel( + AudioUnitEffectGroupState* _Nonnull channelState, const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput, const mixxx::EngineParameters& engineParameters, diff --git a/src/effects/backends/au/audiounitmanager.mm b/src/effects/backends/au/audiounitmanager.mm index c01c79ad651..9fbd62e03eb 100644 --- a/src/effects/backends/au/audiounitmanager.mm +++ b/src/effects/backends/au/audiounitmanager.mm @@ -10,9 +10,9 @@ AudioUnitInstantiationType instantiationType) : m_name(QString::fromNSString([component name])) { // NOTE: The component can be null if the lookup failed in - // `AUBackend::createProcessor`, in which case the effect simply acts as an - // identity function on the audio. Same applies when `AudioUnitManager` is - // default-initialized. + // `AudioUnitBackend::createProcessor`, in which case the effect simply acts + // as an identity function on the audio. Same applies when + // `AudioUnitManager` is default-initialized. if (!component) { return; } diff --git a/src/effects/backends/au/aumanifest.h b/src/effects/backends/au/audiounitmanifest.h similarity index 50% rename from src/effects/backends/au/aumanifest.h rename to src/effects/backends/au/audiounitmanifest.h index 17820ed37b3..25cb3f64976 100644 --- a/src/effects/backends/au/aumanifest.h +++ b/src/effects/backends/au/audiounitmanifest.h @@ -5,7 +5,7 @@ #include "effects/backends/effectmanifest.h" #include "effects/defs.h" -class AUManifest : public EffectManifest { +class AudioUnitManifest : public EffectManifest { public: - AUManifest(const QString& id, AVAudioUnitComponent* component); + AudioUnitManifest(const QString& id, AVAudioUnitComponent* component); }; diff --git a/src/effects/backends/au/aumanifest.mm b/src/effects/backends/au/audiounitmanifest.mm similarity index 69% rename from src/effects/backends/au/aumanifest.mm rename to src/effects/backends/au/audiounitmanifest.mm index a7ef3b1dd13..f4d1d33f0ec 100644 --- a/src/effects/backends/au/aumanifest.mm +++ b/src/effects/backends/au/audiounitmanifest.mm @@ -1,8 +1,9 @@ -#include "effects/backends/au/aumanifest.h" +#include "effects/backends/au/audiounitmanifest.h" #include "effects/defs.h" -AUManifest::AUManifest(const QString& id, AVAudioUnitComponent* component) { +AudioUnitManifest::AudioUnitManifest( + const QString& id, AVAudioUnitComponent* component) { setBackendType(EffectBackendType::AU); setId(id); diff --git a/src/effects/backends/effectsbackendmanager.cpp b/src/effects/backends/effectsbackendmanager.cpp index d27b94c960a..10440b99ddf 100644 --- a/src/effects/backends/effectsbackendmanager.cpp +++ b/src/effects/backends/effectsbackendmanager.cpp @@ -4,7 +4,7 @@ #include "effects/backends/builtin/builtinbackend.h" #include "effects/backends/effectprocessor.h" #ifdef __AU_EFFECTS__ -#include "effects/backends/au/aubackend.h" +#include "effects/backends/au/audiounitbackend.h" #endif #ifdef __LILV__ #include "effects/backends/lv2/lv2backend.h" @@ -18,7 +18,7 @@ EffectsBackendManager::EffectsBackendManager() { addBackend(EffectsBackendPointer(new BuiltInBackend())); #ifdef __AU_EFFECTS__ - addBackend(createAUBackend()); + addBackend(createAudioUnitBackend()); #endif #ifdef __LILV__ addBackend(EffectsBackendPointer(new LV2Backend())); From aceada01c4fd9ba3ad2e4d4eee9aaff6bbb6aca7 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 01:17:02 +0100 Subject: [PATCH 033/124] AudioUnitManager: Update description --- src/effects/backends/au/audiounitmanager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/effects/backends/au/audiounitmanager.h b/src/effects/backends/au/audiounitmanager.h index c924834ffc0..228847b6709 100644 --- a/src/effects/backends/au/audiounitmanager.h +++ b/src/effects/backends/au/audiounitmanager.h @@ -12,7 +12,7 @@ enum AudioUnitInstantiationType { AsyncOutOfProcess, }; -/// Manages instantiation of an audio unit. Only for internal use. +/// A RAII wrapper around an `AudioUnit`. class AudioUnitManager { public: AudioUnitManager(AVAudioUnitComponent* _Nullable component = nil, From 073b4f7fb518bb88a4f5fa6d94b3a86bf7280dc4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 01:25:20 +0100 Subject: [PATCH 034/124] AudioUnit: Use long form for everything Both to avoid clashes with Apple's AU-prefixed classes and to improve clarity, especially for contributors that might not be familiar with this acronym. --- CMakeLists.txt | 8 ++++---- .../backends/{au => audiounit}/audiounitbackend.h | 0 .../backends/{au => audiounit}/audiounitbackend.mm | 10 +++++----- .../{au => audiounit}/audiouniteffectprocessor.h | 2 +- .../{au => audiounit}/audiouniteffectprocessor.mm | 2 +- .../backends/{au => audiounit}/audiounitmanager.h | 0 .../backends/{au => audiounit}/audiounitmanager.mm | 2 +- .../backends/{au => audiounit}/audiounitmanifest.h | 0 .../{au => audiounit}/audiounitmanifest.mm | 4 ++-- src/effects/backends/effectsbackend.cpp | 14 +++++++------- src/effects/backends/effectsbackendmanager.cpp | 2 +- src/effects/defs.h | 2 +- 12 files changed, 23 insertions(+), 23 deletions(-) rename src/effects/backends/{au => audiounit}/audiounitbackend.h (100%) rename src/effects/backends/{au => audiounit}/audiounitbackend.mm (92%) rename src/effects/backends/{au => audiounit}/audiouniteffectprocessor.h (97%) rename src/effects/backends/{au => audiounit}/audiouniteffectprocessor.mm (98%) rename src/effects/backends/{au => audiounit}/audiounitmanager.h (100%) rename src/effects/backends/{au => audiounit}/audiounitmanager.mm (98%) rename src/effects/backends/{au => audiounit}/audiounitmanifest.h (100%) rename src/effects/backends/{au => audiounit}/audiounitmanifest.mm (78%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 235c64ba18b..ed77fa6bcec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1565,10 +1565,10 @@ if(APPLE) option(AU_EFFECTS "Audio Unit (AU) effects integration" ON) if(AU_EFFECTS) target_sources(mixxx-lib PRIVATE - src/effects/backends/au/audiounitbackend.mm - src/effects/backends/au/audiounitmanager.mm - src/effects/backends/au/audiouniteffectprocessor.mm - src/effects/backends/au/audiounitmanifest.mm + src/effects/backends/audiounit/audiounitbackend.mm + src/effects/backends/audiounit/audiounitmanager.mm + src/effects/backends/audiounit/audiouniteffectprocessor.mm + src/effects/backends/audiounit/audiounitmanifest.mm ) target_link_libraries(mixxx-lib PRIVATE "-weak_framework AudioToolbox" diff --git a/src/effects/backends/au/audiounitbackend.h b/src/effects/backends/audiounit/audiounitbackend.h similarity index 100% rename from src/effects/backends/au/audiounitbackend.h rename to src/effects/backends/audiounit/audiounitbackend.h diff --git a/src/effects/backends/au/audiounitbackend.mm b/src/effects/backends/audiounit/audiounitbackend.mm similarity index 92% rename from src/effects/backends/au/audiounitbackend.mm rename to src/effects/backends/audiounit/audiounitbackend.mm index b23b7caeec3..8e7b839b259 100644 --- a/src/effects/backends/au/audiounitbackend.mm +++ b/src/effects/backends/audiounit/audiounitbackend.mm @@ -1,4 +1,4 @@ -#include "effects/backends/au/audiounitbackend.h" +#include "effects/backends/audiounit/audiounitbackend.h" #import #import @@ -10,9 +10,9 @@ #include #include -#include "effects/backends/au/audiounitbackend.h" -#include "effects/backends/au/audiouniteffectprocessor.h" -#include "effects/backends/au/audiounitmanifest.h" +#include "effects/backends/audiounit/audiounitbackend.h" +#include "effects/backends/audiounit/audiouniteffectprocessor.h" +#include "effects/backends/audiounit/audiounitmanifest.h" #include "effects/defs.h" /// An effects backend for Audio Unit (AU) plugins. macOS-only. @@ -26,7 +26,7 @@ } EffectBackendType getType() const override { - return EffectBackendType::AU; + return EffectBackendType::AudioUnit; }; const QList getEffectIds() const override { diff --git a/src/effects/backends/au/audiouniteffectprocessor.h b/src/effects/backends/audiounit/audiouniteffectprocessor.h similarity index 97% rename from src/effects/backends/au/audiouniteffectprocessor.h rename to src/effects/backends/audiounit/audiouniteffectprocessor.h index 6dd10113571..c94ef2f130e 100644 --- a/src/effects/backends/au/audiouniteffectprocessor.h +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.h @@ -6,7 +6,7 @@ #include -#include "effects/backends/au/audiounitmanager.h" +#include "effects/backends/audiounit/audiounitmanager.h" #include "effects/backends/effectprocessor.h" class AudioUnitEffectGroupState final : public EffectState { diff --git a/src/effects/backends/au/audiouniteffectprocessor.mm b/src/effects/backends/audiounit/audiouniteffectprocessor.mm similarity index 98% rename from src/effects/backends/au/audiouniteffectprocessor.mm rename to src/effects/backends/audiounit/audiouniteffectprocessor.mm index 60ae84eada4..a9b5bca89ba 100644 --- a/src/effects/backends/au/audiouniteffectprocessor.mm +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.mm @@ -5,7 +5,7 @@ #include #include -#include "effects/backends/au/audiouniteffectprocessor.h" +#include "effects/backends/audiounit/audiouniteffectprocessor.h" #include "engine/engine.h" #include "util/assert.h" diff --git a/src/effects/backends/au/audiounitmanager.h b/src/effects/backends/audiounit/audiounitmanager.h similarity index 100% rename from src/effects/backends/au/audiounitmanager.h rename to src/effects/backends/audiounit/audiounitmanager.h diff --git a/src/effects/backends/au/audiounitmanager.mm b/src/effects/backends/audiounit/audiounitmanager.mm similarity index 98% rename from src/effects/backends/au/audiounitmanager.mm rename to src/effects/backends/audiounit/audiounitmanager.mm index 9fbd62e03eb..71793cae175 100644 --- a/src/effects/backends/au/audiounitmanager.mm +++ b/src/effects/backends/audiounit/audiounitmanager.mm @@ -4,7 +4,7 @@ #include -#include "effects/backends/au/audiounitmanager.h" +#include "effects/backends/audiounit/audiounitmanager.h" AudioUnitManager::AudioUnitManager(AVAudioUnitComponent* _Nullable component, AudioUnitInstantiationType instantiationType) diff --git a/src/effects/backends/au/audiounitmanifest.h b/src/effects/backends/audiounit/audiounitmanifest.h similarity index 100% rename from src/effects/backends/au/audiounitmanifest.h rename to src/effects/backends/audiounit/audiounitmanifest.h diff --git a/src/effects/backends/au/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm similarity index 78% rename from src/effects/backends/au/audiounitmanifest.mm rename to src/effects/backends/audiounit/audiounitmanifest.mm index f4d1d33f0ec..56fcc288b3a 100644 --- a/src/effects/backends/au/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -1,10 +1,10 @@ -#include "effects/backends/au/audiounitmanifest.h" +#include "effects/backends/audiounit/audiounitmanifest.h" #include "effects/defs.h" AudioUnitManifest::AudioUnitManifest( const QString& id, AVAudioUnitComponent* component) { - setBackendType(EffectBackendType::AU); + setBackendType(EffectBackendType::AudioUnit); setId(id); setName(QString::fromNSString([component name])); diff --git a/src/effects/backends/effectsbackend.cpp b/src/effects/backends/effectsbackend.cpp index 9635d8fc60f..1af9f35aef1 100644 --- a/src/effects/backends/effectsbackend.cpp +++ b/src/effects/backends/effectsbackend.cpp @@ -3,7 +3,7 @@ #include namespace { -const QString backendTypeNameAU = QStringLiteral("AudioUnit"); +const QString backendTypeNameAudioUnit = QStringLiteral("AudioUnit"); const QString backendTypeNameLV2 = QStringLiteral("LV2"); // QString::tr requires const char[] rather than QString //: Backend type for effects that are built into Mixxx. @@ -15,8 +15,8 @@ constexpr char backendTypeNameUnknown[] = QT_TRANSLATE_NOOP("EffectsBackend", "U EffectBackendType EffectsBackend::backendTypeFromString(const QString& typeName) { if (typeName == backendTypeNameLV2) { return EffectBackendType::LV2; - } else if (typeName == backendTypeNameAU) { - return EffectBackendType::AU; + } else if (typeName == backendTypeNameAudioUnit) { + return EffectBackendType::AudioUnit; } else { return EffectBackendType::BuiltIn; } @@ -26,8 +26,8 @@ QString EffectsBackend::backendTypeToString(EffectBackendType backendType) { switch (backendType) { case EffectBackendType::BuiltIn: return backendTypeNameBuiltIn; - case EffectBackendType::AU: - return backendTypeNameAU; + case EffectBackendType::AudioUnit: + return backendTypeNameAudioUnit; case EffectBackendType::LV2: return backendTypeNameLV2; default: @@ -41,8 +41,8 @@ QString EffectsBackend::translatedBackendName(EffectBackendType backendType) { // Clazy's `tr-non-literal` check is a false positive, because the // source string has been marked `QT_TR_NOOP`. return QObject::tr(backendTypeNameBuiltIn); // clazy:exclude=tr-non-literal - case EffectBackendType::AU: - return backendTypeNameAU; + case EffectBackendType::AudioUnit: + return backendTypeNameAudioUnit; case EffectBackendType::LV2: return backendTypeNameLV2; default: diff --git a/src/effects/backends/effectsbackendmanager.cpp b/src/effects/backends/effectsbackendmanager.cpp index 10440b99ddf..5ae021c04a5 100644 --- a/src/effects/backends/effectsbackendmanager.cpp +++ b/src/effects/backends/effectsbackendmanager.cpp @@ -4,7 +4,7 @@ #include "effects/backends/builtin/builtinbackend.h" #include "effects/backends/effectprocessor.h" #ifdef __AU_EFFECTS__ -#include "effects/backends/au/audiounitbackend.h" +#include "effects/backends/audiounit/audiounitbackend.h" #endif #ifdef __LILV__ #include "effects/backends/lv2/lv2backend.h" diff --git a/src/effects/defs.h b/src/effects/defs.h index d246695b234..ac26e2b6d2f 100644 --- a/src/effects/defs.h +++ b/src/effects/defs.h @@ -17,7 +17,7 @@ enum class EffectEnableState { // The order of the enum values here is used to determine sort order in EffectManifest::alphabetize enum class EffectBackendType { BuiltIn, - AU, + AudioUnit, LV2, Unknown }; From 876dae9028affa4ccd00cf807896bc6fdb627e99 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 02:09:26 +0100 Subject: [PATCH 035/124] AudioUnitManifest: Discover parameters --- .../backends/audiounit/audiounitmanifest.mm | 46 ++++++++++++++++++- .../backends/audiounit/audiounitutils.h | 18 ++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/effects/backends/audiounit/audiounitutils.h diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index 56fcc288b3a..b1440381e70 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -1,5 +1,10 @@ -#include "effects/backends/audiounit/audiounitmanifest.h" +#import + +#include +#include "effects/backends/audiounit/audiounitmanager.h" +#include "effects/backends/audiounit/audiounitmanifest.h" +#include "effects/backends/audiounit/audiounitutils.h" #include "effects/defs.h" AudioUnitManifest::AudioUnitManifest( @@ -11,4 +16,43 @@ setVersion(QString::fromNSString([component versionString])); setDescription(QString::fromNSString([component typeName])); setAuthor(QString::fromNSString([component manufacturerName])); + + // Try instantiating the unit in-process to fetch its properties quickly + + AudioUnitManager manager{component, AudioUnitInstantiationType::Sync}; + AudioUnit audioUnit = manager.getAudioUnit(); + + if (audioUnit) { + // Fetch number of parameters + UInt32 paramListBytes = 0; + AUDIO_UNIT_INFO(audioUnit, ParameterList, Global, 0, ¶mListBytes); + + // Fetch parameter ids + UInt32 paramCount = paramListBytes / sizeof(AudioUnitParameterID); + std::unique_ptr paramIds{ + new AudioUnitParameterID[paramCount]}; + AUDIO_UNIT_GET(audioUnit, + ParameterList, + Global, + 0, + paramIds.get(), + ¶mListBytes); + + // Resolve parameters + AudioUnitParameterInfo paramInfo; + UInt32 paramInfoSize = sizeof(AudioUnitParameterInfo); + for (UInt32 i = 0; i < paramCount; i++) { + AUDIO_UNIT_GET(audioUnit, + ParameterInfo, + Global, + paramIds[i], + ¶mInfo, + ¶mInfoSize); + + qDebug() << QString::fromNSString([component name]) + << "has parameter" + << QString::fromCFString(paramInfo.cfNameString); + // TODO: Declare it + } + } } diff --git a/src/effects/backends/audiounit/audiounitutils.h b/src/effects/backends/audiounit/audiounitutils.h new file mode 100644 index 00000000000..9a8c8923352 --- /dev/null +++ b/src/effects/backends/audiounit/audiounitutils.h @@ -0,0 +1,18 @@ +#pragma once + +#define AUDIO_UNIT_GET( \ + INSTANCE, PROPERTY, SCOPE, ELEMENT, OUT_VALUE, OUT_SIZE) \ + AudioUnitGetProperty(INSTANCE, \ + kAudioUnitProperty_##PROPERTY, \ + kAudioUnitScope_##SCOPE, \ + ELEMENT, \ + OUT_VALUE, \ + OUT_SIZE) + +#define AUDIO_UNIT_INFO(INSTANCE, PROPERTY, SCOPE, ELEMENT, OUT_VALUE) \ + AudioUnitGetPropertyInfo(INSTANCE, \ + kAudioUnitProperty_##PROPERTY, \ + kAudioUnitScope_##SCOPE, \ + ELEMENT, \ + OUT_VALUE, \ + nullptr) From c2ad423285d827806570bc640ae19efe705c65dd Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 02:22:00 +0100 Subject: [PATCH 036/124] AudioUnitManifest: Add parameters --- .../backends/audiounit/audiounitmanifest.mm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index b1440381e70..26dbe22d968 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -1,4 +1,5 @@ #import +#include "effects/backends/effectmanifestparameter.h" #include @@ -49,10 +50,18 @@ ¶mInfo, ¶mInfoSize); + QString paramName = QString::fromUtf8(paramInfo.name); qDebug() << QString::fromNSString([component name]) - << "has parameter" - << QString::fromCFString(paramInfo.cfNameString); - // TODO: Declare it + << "has parameter" << paramName; + + if (paramInfo.flags & kAudioUnitParameterFlag_IsWritable) { + EffectManifestParameterPointer manifestParam = addParameter(); + manifestParam->setId(paramName); + manifestParam->setName(paramName); + manifestParam->setRange(paramInfo.minValue, + paramInfo.defaultValue, + paramInfo.maxValue); + } } } } From d811bb4517be33424a0105cba1eb384132bc12b5 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 02:38:47 +0100 Subject: [PATCH 037/124] AudioUnitManifest: Apply scaling to parameters --- .../backends/audiounit/audiounitmanifest.mm | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index 26dbe22d968..90e4f66477e 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -51,16 +51,28 @@ ¶mInfoSize); QString paramName = QString::fromUtf8(paramInfo.name); + auto paramFlags = paramInfo.flags; + qDebug() << QString::fromNSString([component name]) << "has parameter" << paramName; - if (paramInfo.flags & kAudioUnitParameterFlag_IsWritable) { + // TODO: Check CanRamp too? + if (paramFlags & kAudioUnitParameterFlag_IsWritable) { EffectManifestParameterPointer manifestParam = addParameter(); manifestParam->setId(paramName); manifestParam->setName(paramName); manifestParam->setRange(paramInfo.minValue, paramInfo.defaultValue, paramInfo.maxValue); + + // TODO: Support more modes, e.g. squared, square root in Mixxx + if (paramFlags & kAudioUnitParameterFlag_DisplayLogarithmic) { + manifestParam->setValueScaler( + EffectManifestParameter::ValueScaler::Logarithmic); + } else { + manifestParam->setValueScaler( + EffectManifestParameter::ValueScaler::Linear); + } } } } From d0e847450e367b42319fcd5adcb37f8b4beb300b Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 02:53:54 +0100 Subject: [PATCH 038/124] AudioUnitEffectProcessor: Try implementing parameter loading --- .../audiounit/audiouniteffectprocessor.mm | 23 ++++++++++++++++--- .../backends/audiounit/audiounitmanifest.mm | 6 +++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.mm b/src/effects/backends/audiounit/audiouniteffectprocessor.mm index a9b5bca89ba..89de19e87d3 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.mm +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.mm @@ -6,6 +6,7 @@ #include #include "effects/backends/audiounit/audiouniteffectprocessor.h" +#include "engine/effects/engineeffectparameter.h" #include "engine/engine.h" #include "util/assert.h" @@ -103,8 +104,23 @@ } void AudioUnitEffectProcessor::loadEngineEffectParameters( - const QMap&) { - // TODO + const QMap& parameters) { + AudioUnit _Nullable audioUnit = m_manager.getAudioUnit(); + if (!audioUnit) { + qWarning() << "Cannot load engine effect parameters before the Audio " + "Unit is instantiated"; + return; + } + + for (auto parameter : parameters) { + AudioUnitParameterID parameterId = parameter->id().toInt(); + AudioUnitSetParameter(audioUnit, + parameterId, + kAudioUnitScope_Global, + 0, + static_cast(parameter->value()), + 0); + } } void AudioUnitEffectProcessor::processChannel( @@ -116,7 +132,8 @@ const GroupFeatureState&) { AudioUnit _Nullable audioUnit = m_manager.getAudioUnit(); if (!audioUnit) { - qWarning() << "Audio Unit is not instantiated yet"; + qWarning() + << "Cannot process channel before Audio Unit is instantiated"; return; } diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index 90e4f66477e..b30fdc8f999 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -43,10 +43,12 @@ AudioUnitParameterInfo paramInfo; UInt32 paramInfoSize = sizeof(AudioUnitParameterInfo); for (UInt32 i = 0; i < paramCount; i++) { + AudioUnitParameterID paramId = paramIds[i]; + AUDIO_UNIT_GET(audioUnit, ParameterInfo, Global, - paramIds[i], + paramId, ¶mInfo, ¶mInfoSize); @@ -59,7 +61,7 @@ // TODO: Check CanRamp too? if (paramFlags & kAudioUnitParameterFlag_IsWritable) { EffectManifestParameterPointer manifestParam = addParameter(); - manifestParam->setId(paramName); + manifestParam->setId(QString::number(paramId)); manifestParam->setName(paramName); manifestParam->setRange(paramInfo.minValue, paramInfo.defaultValue, From 7fe8ee115738315a94e13238faebb3990de3ddf1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 03:09:59 +0100 Subject: [PATCH 039/124] AudioUnitEffectProcessor: Implement parameter syncing --- .../audiounit/audiouniteffectprocessor.h | 6 ++ .../audiounit/audiouniteffectprocessor.mm | 55 +++++++++++++------ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.h b/src/effects/backends/audiounit/audiouniteffectprocessor.h index c94ef2f130e..372e54af7c7 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.h +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.h @@ -4,6 +4,7 @@ #import #import +#include #include #include "effects/backends/audiounit/audiounitmanager.h" @@ -54,4 +55,9 @@ class AudioUnitEffectProcessor final : public EffectProcessorImpl m_isConfigured; AudioUnitManager m_manager; + + QList m_parameters; + QList m_lastValues; + + void syncParameters(); }; diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.mm b/src/effects/backends/audiounit/audiouniteffectprocessor.mm index 89de19e87d3..f24ef00a961 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.mm +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.mm @@ -105,22 +105,7 @@ void AudioUnitEffectProcessor::loadEngineEffectParameters( const QMap& parameters) { - AudioUnit _Nullable audioUnit = m_manager.getAudioUnit(); - if (!audioUnit) { - qWarning() << "Cannot load engine effect parameters before the Audio " - "Unit is instantiated"; - return; - } - - for (auto parameter : parameters) { - AudioUnitParameterID parameterId = parameter->id().toInt(); - AudioUnitSetParameter(audioUnit, - parameterId, - kAudioUnitScope_Global, - 0, - static_cast(parameter->value()), - 0); - } + m_parameters = parameters.values(); } void AudioUnitEffectProcessor::processChannel( @@ -140,6 +125,44 @@ // TODO: Set format (even though Core Audio seems to default to 32-bit // floats, 2 channels and 44.1kHz sample rate) + // Update changed parameters + syncParameters(); + + // Render the effect into the output buffer channelState->render( audioUnit, engineParameters.samplesPerBuffer(), pInput, pOutput); } + +void AudioUnitEffectProcessor::syncParameters() { + AudioUnit _Nullable audioUnit = m_manager.getAudioUnit(); + DEBUG_ASSERT(audioUnit != nil); + + m_lastValues.reserve(m_parameters.size()); + + int i = 0; + for (auto parameter : m_parameters) { + if (m_lastValues.size() < i) { + m_lastValues.push_back(NAN); + } + DEBUG_ASSERT(m_lastValues.size() >= i); + + AudioUnitParameterID id = parameter->id().toInt(); + auto value = static_cast(parameter->value()); + + // Update parameter iff changed since the last sync + if (m_lastValues[i] != value) { + m_lastValues[i] = value; + + OSStatus status = AudioUnitSetParameter( + audioUnit, id, kAudioUnitScope_Global, 0, value, 0); + if (status != noErr) { + qWarning() + << "Could not set Audio Unit parameter" << id << ":" + << status + << "(Check https://www.osstatus.com for a description)"; + } + } + + i++; + } +} From 5bc3514feb01e6f8e52be175e617c92affee7781 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 03:36:06 +0100 Subject: [PATCH 040/124] AudioUnitEffectProcessor: Add missing nullability annotations --- .../backends/audiounit/audiouniteffectprocessor.h | 14 +++++++------- .../backends/audiounit/audiouniteffectprocessor.mm | 9 +++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.h b/src/effects/backends/audiounit/audiouniteffectprocessor.h index 372e54af7c7..9da410a9166 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.h +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.h @@ -24,17 +24,17 @@ class AudioUnitEffectGroupState final : public EffectState { AudioBufferList m_inputBuffers; AudioBufferList m_outputBuffers; - static OSStatus renderCallbackUntyped(void* rawThis, - AudioUnitRenderActionFlags* inActionFlags, - const AudioTimeStamp* inTimeStamp, + static OSStatus renderCallbackUntyped(void* _Nonnull rawThis, + AudioUnitRenderActionFlags* _Nonnull inActionFlags, + const AudioTimeStamp* _Nonnull inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, - AudioBufferList* ioData); - OSStatus renderCallback(AudioUnitRenderActionFlags* inActionFlags, - const AudioTimeStamp* inTimeStamp, + AudioBufferList* _Nonnull ioData); + OSStatus renderCallback(AudioUnitRenderActionFlags* _Nonnull inActionFlags, + const AudioTimeStamp* _Nonnull inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, - AudioBufferList* ioData) const; + AudioBufferList* _Nonnull ioData) const; }; class AudioUnitEffectProcessor final : public EffectProcessorImpl { diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.mm b/src/effects/backends/audiounit/audiouniteffectprocessor.mm index f24ef00a961..2ad64946caa 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.mm +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.mm @@ -22,12 +22,13 @@ } // static -OSStatus AudioUnitEffectGroupState::renderCallbackUntyped(void* rawThis, - AudioUnitRenderActionFlags* inActionFlags, - const AudioTimeStamp* inTimeStamp, +OSStatus AudioUnitEffectGroupState::renderCallbackUntyped( + void* _Nonnull rawThis, + AudioUnitRenderActionFlags* _Nonnull inActionFlags, + const AudioTimeStamp* _Nonnull inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, - AudioBufferList* ioData) { + AudioBufferList* _Nonnull ioData) { return static_cast(rawThis)->renderCallback( inActionFlags, inTimeStamp, inBusNumber, inNumFrames, ioData); } From 2fee192ccd6bc52686910eff33512271dd3370a4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 04:09:11 +0100 Subject: [PATCH 041/124] AudioUnitBackend: Remove redundant includes --- src/effects/backends/audiounit/audiounitbackend.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/effects/backends/audiounit/audiounitbackend.h b/src/effects/backends/audiounit/audiounitbackend.h index 8148cf776dc..03e34872e2d 100644 --- a/src/effects/backends/audiounit/audiounitbackend.h +++ b/src/effects/backends/audiounit/audiounitbackend.h @@ -1,10 +1,5 @@ #pragma once -#include -#include - -#include "effects/backends/effectprocessor.h" -#include "effects/backends/effectsbackend.h" #include "effects/defs.h" EffectsBackendPointer createAudioUnitBackend(); From 0eeb9ef2c747fabab6e6cc3b7f30dde9e96b473d Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 04:37:24 +0100 Subject: [PATCH 042/124] AudioUnitEffectProcessor: Remove redundant field --- src/effects/backends/audiounit/audiouniteffectprocessor.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.h b/src/effects/backends/audiounit/audiouniteffectprocessor.h index 9da410a9166..fa042d60cab 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.h +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.h @@ -53,7 +53,6 @@ class AudioUnitEffectProcessor final : public EffectProcessorImpl m_isConfigured; AudioUnitManager m_manager; QList m_parameters; From 29d7445c162a0b9b278e7d560e90c5ae264aa8a1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 13:36:19 +0100 Subject: [PATCH 043/124] AudioUnitManifest: Link the first parameter by default --- src/effects/backends/audiounit/audiounitmanifest.mm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/effects/backends/audiounit/audiounitmanifest.mm b/src/effects/backends/audiounit/audiounitmanifest.mm index b30fdc8f999..1ee8fd2c8f2 100644 --- a/src/effects/backends/audiounit/audiounitmanifest.mm +++ b/src/effects/backends/audiounit/audiounitmanifest.mm @@ -42,6 +42,7 @@ // Resolve parameters AudioUnitParameterInfo paramInfo; UInt32 paramInfoSize = sizeof(AudioUnitParameterInfo); + bool hasLinkedParam = false; for (UInt32 i = 0; i < paramCount; i++) { AudioUnitParameterID paramId = paramIds[i]; @@ -67,6 +68,15 @@ paramInfo.defaultValue, paramInfo.maxValue); + // Link the first parameter + // TODO: Figure out if AU plugins provide a better way to figure + // out the "default" parameter + if (!hasLinkedParam) { + manifestParam->setDefaultLinkType( + EffectManifestParameter::LinkType::Linked); + hasLinkedParam = true; + } + // TODO: Support more modes, e.g. squared, square root in Mixxx if (paramFlags & kAudioUnitParameterFlag_DisplayLogarithmic) { manifestParam->setValueScaler( From 4b90c4471ae18afef32fbaf5184d00a3d4a0465e Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 14:23:47 +0100 Subject: [PATCH 044/124] AudioUnitEffectProcessor: Set stream format explicitly --- .../audiounit/audiouniteffectprocessor.h | 5 ++ .../audiounit/audiouniteffectprocessor.mm | 48 +++++++++++++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.h b/src/effects/backends/audiounit/audiouniteffectprocessor.h index fa042d60cab..9137b6449c5 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.h +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.h @@ -7,8 +7,10 @@ #include #include +#include "audio/types.h" #include "effects/backends/audiounit/audiounitmanager.h" #include "effects/backends/effectprocessor.h" +#include "engine/engine.h" class AudioUnitEffectGroupState final : public EffectState { public: @@ -57,6 +59,9 @@ class AudioUnitEffectProcessor final : public EffectProcessorImpl m_parameters; QList m_lastValues; + mixxx::audio::ChannelCount m_lastChannelCount; + mixxx::audio::SampleRate m_lastSampleRate; void syncParameters(); + void syncStreamFormat(const mixxx::EngineParameters& engineParameters); }; diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.mm b/src/effects/backends/audiounit/audiouniteffectprocessor.mm index 2ad64946caa..0e2e20b7d7c 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.mm +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.mm @@ -1,6 +1,7 @@ #import #import #import +#include #include #include @@ -123,10 +124,10 @@ return; } - // TODO: Set format (even though Core Audio seems to default to 32-bit - // floats, 2 channels and 44.1kHz sample rate) + // Sync engine parameters with Audio Unit + syncStreamFormat(engineParameters); - // Update changed parameters + // Sync effect parameters with Audio Unit syncParameters(); // Render the effect into the output buffer @@ -167,3 +168,44 @@ i++; } } + +void AudioUnitEffectProcessor::syncStreamFormat( + const mixxx::EngineParameters& parameters) { + AudioUnit _Nullable audioUnit = m_manager.getAudioUnit(); + DEBUG_ASSERT(audioUnit != nil); + + if (parameters.sampleRate() != m_lastSampleRate || + parameters.channelCount() != m_lastChannelCount) { + auto sampleRate = parameters.sampleRate(); + auto channelCount = parameters.channelCount(); + + m_lastSampleRate = sampleRate; + m_lastChannelCount = channelCount; + + AVAudioFormat* audioFormat = [[AVAudioFormat alloc] + initWithCommonFormat:AVAudioPCMFormatFloat32 + sampleRate:sampleRate + channels:channelCount + interleaved:false]; + + const auto* streamFormat = [audioFormat streamDescription]; + + qDebug() << "Updating Audio Unit stream format to sample rate" + << sampleRate << "and channel count" << channelCount; + + OSStatus status = AudioUnitSetProperty(audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Global, + 0, + streamFormat, + sizeof(AudioStreamBasicDescription)); + + if (status != noErr) { + qWarning() + << "Could not set Audio Unit stream format to sample rate" + << sampleRate << "and channel count" << channelCount << ":" + << status + << "(Check https://www.osstatus.com for a description)"; + } + } +} From 6b30d0ee7c8e1442b1853da94d316a3d7115ebe1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 15 Oct 2023 14:35:50 +0100 Subject: [PATCH 045/124] AudioUnitEffectProcessor: Set stream format on input and output scope --- .../audiounit/audiouniteffectprocessor.mm | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.mm b/src/effects/backends/audiounit/audiouniteffectprocessor.mm index 0e2e20b7d7c..409c89e07a0 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.mm +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.mm @@ -193,19 +193,22 @@ qDebug() << "Updating Audio Unit stream format to sample rate" << sampleRate << "and channel count" << channelCount; - OSStatus status = AudioUnitSetProperty(audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Global, - 0, - streamFormat, - sizeof(AudioStreamBasicDescription)); - - if (status != noErr) { - qWarning() - << "Could not set Audio Unit stream format to sample rate" - << sampleRate << "and channel count" << channelCount << ":" - << status - << "(Check https://www.osstatus.com for a description)"; + for (auto scope : {kAudioUnitScope_Input, kAudioUnitScope_Output}) { + OSStatus status = AudioUnitSetProperty(audioUnit, + kAudioUnitProperty_StreamFormat, + scope, + 0, + streamFormat, + sizeof(AudioStreamBasicDescription)); + + if (status != noErr) { + qWarning() + << "Could not set Audio Unit stream format to sample " + "rate" + << sampleRate << "and channel count" << channelCount + << ":" << status + << "(Check https://www.osstatus.com for a description)"; + } } } } From f9cfd6295c86a4fcd87c3b87e5ea75b8d587ef3c Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 25 Feb 2024 21:34:30 +0100 Subject: [PATCH 046/124] AudioUnitEffectProcessor: Process buffer in chunks This fixes an issue where rendering the audio unit would fail if the audio buffer was > 10.7 ms. --- .../audiounit/audiouniteffectprocessor.h | 2 +- .../audiounit/audiouniteffectprocessor.mm | 117 ++++++++++++------ 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.h b/src/effects/backends/audiounit/audiouniteffectprocessor.h index 9137b6449c5..3bbf1d0c933 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.h +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.h @@ -17,7 +17,7 @@ class AudioUnitEffectGroupState final : public EffectState { AudioUnitEffectGroupState(const mixxx::EngineParameters& engineParameters); void render(AudioUnit _Nonnull audioUnit, - SINT sampleCount, + const mixxx::EngineParameters& engineParameters, const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput); diff --git a/src/effects/backends/audiounit/audiouniteffectprocessor.mm b/src/effects/backends/audiounit/audiouniteffectprocessor.mm index 409c89e07a0..621e119d69e 100644 --- a/src/effects/backends/audiounit/audiouniteffectprocessor.mm +++ b/src/effects/backends/audiounit/audiouniteffectprocessor.mm @@ -5,6 +5,7 @@ #include #include +#include #include "effects/backends/audiounit/audiouniteffectprocessor.h" #include "engine/effects/engineeffectparameter.h" @@ -54,50 +55,89 @@ } void AudioUnitEffectGroupState::render(AudioUnit _Nonnull audioUnit, - SINT sampleCount, + const mixxx::EngineParameters& engineParameters, const CSAMPLE* _Nonnull pInput, CSAMPLE* _Nonnull pOutput) { - // Fill the input and output buffers. - // TODO: Assert the size - SINT size = sizeof(CSAMPLE) * sampleCount; - m_inputBuffers.mBuffers[0].mData = const_cast(pInput); - m_inputBuffers.mBuffers[0].mDataByteSize = size; - m_outputBuffers.mBuffers[0].mData = pOutput; - m_outputBuffers.mBuffers[0].mDataByteSize = size; - - // Set the render callback - AURenderCallbackStruct callback; - callback.inputProc = AudioUnitEffectGroupState::renderCallbackUntyped; - callback.inputProcRefCon = this; - - OSStatus setCallbackStatus = AudioUnitSetProperty(audioUnit, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, + // Find the max number of samples per buffer that the Audio Unit can handle. + // + // Note that (confusingly) the Apple API refers to this count as "frames" + // even though empirical tests show that this has to be interpreted as a + // sample count, otherwise rendering the Audio Unit will fail with status + // -10874 = kAudioUnitErr_TooManyFramesToProcess. + // + // For documentation on this property, see + // https://developer.apple.com/documentation/audiotoolbox/kaudiounitproperty_maximumframesperslice + // + // TODO: Should we cache this? + UInt32 maxSamplesPerChunk = 0; + UInt32 maxSamplesPerChunkSize = sizeof(UInt32); + OSStatus maxSamplesPerSliceStatus = AudioUnitGetProperty(audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, 0, - &callback, - sizeof(AURenderCallbackStruct)); - if (setCallbackStatus != noErr) { - qWarning() << "Setting Audio Unit render callback failed with status" - << setCallbackStatus; + &maxSamplesPerChunk, + &maxSamplesPerChunkSize); + if (maxSamplesPerSliceStatus != noErr) { + qWarning() << "Fetching the maximum samples per slice for Audio Unit " + "failed " + "with status" + << maxSamplesPerSliceStatus; return; } - // Apply the actual effect to the sample. - AudioUnitRenderActionFlags flags = 0; - NSInteger outputBusNumber = 0; - OSStatus renderStatus = AudioUnitRender(audioUnit, - &flags, - &m_timestamp, - outputBusNumber, - sampleCount, - &m_outputBuffers); - if (renderStatus != noErr) { - qWarning() << "Rendering Audio Unit failed with status" << renderStatus; - return; - } + UInt32 totalSamples = engineParameters.samplesPerBuffer(); + + // Process the buffer in chunks + for (UInt32 offset = 0; offset < totalSamples; + offset += maxSamplesPerChunk) { + // Compute the size of the current slice. + UInt32 remainingSamples = totalSamples - offset; + UInt32 chunkSamples = std::min(remainingSamples, maxSamplesPerChunk); + UInt32 chunkSize = sizeof(CSAMPLE) * chunkSamples; + + // Fill the input and output buffers. + m_inputBuffers.mBuffers[0].mData = + const_cast(pInput) + offset; + m_inputBuffers.mBuffers[0].mDataByteSize = chunkSize; + m_outputBuffers.mBuffers[0].mData = pOutput + offset; + m_outputBuffers.mBuffers[0].mDataByteSize = chunkSize; + + // Set the render callback + AURenderCallbackStruct callback; + callback.inputProc = AudioUnitEffectGroupState::renderCallbackUntyped; + callback.inputProcRefCon = this; + + OSStatus setCallbackStatus = AudioUnitSetProperty(audioUnit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &callback, + sizeof(AURenderCallbackStruct)); + if (setCallbackStatus != noErr) { + qWarning() + << "Setting Audio Unit render callback failed with status" + << setCallbackStatus; + return; + } - // Increment the timestamp - m_timestamp.mSampleTime += sampleCount; + // Apply the actual effect to the sample. + AudioUnitRenderActionFlags flags = 0; + NSInteger outputBusNumber = 0; + OSStatus renderStatus = AudioUnitRender(audioUnit, + &flags, + &m_timestamp, + outputBusNumber, + chunkSamples, + &m_outputBuffers); + if (renderStatus != noErr) { + qWarning() << "Rendering Audio Unit failed with status" + << renderStatus; + return; + } + + // Increment the timestamp + m_timestamp.mSampleTime += chunkSamples; + } } AudioUnitEffectProcessor::AudioUnitEffectProcessor( @@ -131,8 +171,7 @@ syncParameters(); // Render the effect into the output buffer - channelState->render( - audioUnit, engineParameters.samplesPerBuffer(), pInput, pOutput); + channelState->render(audioUnit, engineParameters, pInput, pOutput); } void AudioUnitEffectProcessor::syncParameters() { From 440d1cf4b42656c86a0616abc710ea236dd53929 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 25 Feb 2024 21:58:14 +0100 Subject: [PATCH 047/124] AudioUnitManager: Add rationale for not using a mutex --- src/effects/backends/audiounit/audiounitmanager.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/effects/backends/audiounit/audiounitmanager.h b/src/effects/backends/audiounit/audiounitmanager.h index 228847b6709..47de73f9e3d 100644 --- a/src/effects/backends/audiounit/audiounitmanager.h +++ b/src/effects/backends/audiounit/audiounitmanager.h @@ -24,7 +24,10 @@ class AudioUnitManager { AudioUnitManager& operator=(const AudioUnitManager&) = delete; /// Fetches the audio unit if already instantiated. - /// Non-blocking and thread-safe. + /// + /// Non-blocking and thread-safe, since this method is intended to (also) be + /// called in a real-time context, e.g. from an audio thread, where we don't + /// want to e.g. block on a mutex. AudioUnit _Nullable getAudioUnit(); private: From b82a4275e748ec504f3d121aa5d16ba8961b9598 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 4 Mar 2024 02:50:10 +0100 Subject: [PATCH 048/124] SSE: Check !defined(__EMSCRIPTEN__) where unavailable --- src/soundio/sounddevicenetwork.cpp | 2 +- src/soundio/sounddeviceportaudio.cpp | 2 +- src/util/denormalsarezero.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/soundio/sounddevicenetwork.cpp b/src/soundio/sounddevicenetwork.cpp index c72879fe71b..e4b564895c4 100644 --- a/src/soundio/sounddevicenetwork.cpp +++ b/src/soundio/sounddevicenetwork.cpp @@ -445,7 +445,7 @@ void SoundDeviceNetwork::callbackProcessClkRef() { // This disables the denormals calculations, to avoid a // performance penalty of ~20 // https://github.com/mixxxdj/mixxx/issues/7747 -#ifdef __SSE__ +#if defined(__SSE__) && !defined(__EMSCRIPTEN__) if (!_MM_GET_DENORMALS_ZERO_MODE()) { qDebug() << "SSE: Enabling denormals to zero mode"; _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); diff --git a/src/soundio/sounddeviceportaudio.cpp b/src/soundio/sounddeviceportaudio.cpp index 443338f4a4c..34ebbd601e9 100644 --- a/src/soundio/sounddeviceportaudio.cpp +++ b/src/soundio/sounddeviceportaudio.cpp @@ -906,7 +906,7 @@ int SoundDevicePortAudio::callbackProcessClkRef( #endif m_bSetThreadPriority = true; -#ifdef __SSE__ +#if defined(__SSE__) && !defined(__EMSCRIPTEN__) // This disables the denormals calculations, to avoid a // performance penalty of ~20 // https://github.com/mixxxdj/mixxx/issues/7747 diff --git a/src/util/denormalsarezero.h b/src/util/denormalsarezero.h index 416adb202f6..09b44461e43 100644 --- a/src/util/denormalsarezero.h +++ b/src/util/denormalsarezero.h @@ -20,7 +20,7 @@ #define _MM_DENORMALS_ZERO_OFF 0x0000 #endif -#ifdef __SSE__ +#if defined(__SSE__) && !defined(__EMSCRIPTEN__) #include From 807d9085203db6815953fd37b096296b785efa58 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 12 Mar 2024 12:24:48 +0100 Subject: [PATCH 049/124] README: link to CONTRIBUTING.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 78277832a91..aac3c7fd05c 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ The Mixxx team uses [Github Issues][issues] to manage Mixxx development. Have a bug or feature request? [File a bug on Github][fileabug]. Want to get involved in Mixxx development? Assign yourself a bug from the [easy -bug list][easybugs] and get started! +bug list][easybugs] and get started! +Read [CONTRIBUTING](CONTRIBUTING.md) for more information. ## Building Mixxx From 23461229284c314ff8406ee1adbdcf2ef08d4f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 12 Mar 2024 23:52:24 +0100 Subject: [PATCH 050/124] Update changelog for 2.4.1 --- CHANGELOG.md | 14 ++++++++++- res/linux/org.mixxx.Mixxx.metainfo.xml | 35 ++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6319f375a56..5927b5f581d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ ### Controller Mappings -* Pioneer DDJ-FLX4 mapping improvements [#12842](https://github.com/mixxxdj/mixxx/pull/12842) +* Hercules Inpulse 200: Configure shift-browser knob to scroll the library (quick) [#12932](https://github.com/mixxxdj/mixxx/pull/12932) +* Pioneer DDJ-FLX4: Add waveform zoom and other mapping improvements + [#12896](https://github.com/mixxxdj/mixxx/pull/12896) + [#12842](https://github.com/mixxxdj/mixxx/pull/12842) +* Traktor F1: Fixes for hid-parser and related script [#12876](https://github.com/mixxxdj/mixxx/pull/12876) * Traktor S3: Fix mapping crash on macOS [#12840](https://github.com/mixxxdj/mixxx/pull/12840) ### Target Support @@ -13,6 +17,14 @@ [#12853](https://github.com/mixxxdj/mixxx/pull/12853) [#12847](https://github.com/mixxxdj/mixxx/pull/12847) [#12822](https://github.com/mixxxdj/mixxx/pull/12822) + [#12892](https://github.com/mixxxdj/mixxx/pull/12892) + +### Miscellaneous + +* Remove unnecessary unpolish operation of the style, before polish the new style [#12445](https://github.com/mixxxdj/mixxx/pull/12445) +* Developer Tools: initially sort controls by group name, ascending [#12884](https://github.com/mixxxdj/mixxx/pull/12884) +* History: show track count and duration in sidebar [#12811](https://github.com/mixxxdj/mixxx/pull/12811) +* Prevent removing tracks from locked playlists [#12927](https://github.com/mixxxdj/mixxx/pull/12927) ## [2.4.0](https://github.com/mixxxdj/mixxx/milestone/15?closed=1) (2024-02-16) diff --git a/res/linux/org.mixxx.Mixxx.metainfo.xml b/res/linux/org.mixxx.Mixxx.metainfo.xml index 7330403378b..9e3df41a677 100644 --- a/res/linux/org.mixxx.Mixxx.metainfo.xml +++ b/res/linux/org.mixxx.Mixxx.metainfo.xml @@ -96,16 +96,25 @@ Do not edit it manually. --> - +

Controller Mappings

  • - Pioneer DDJ-FLX4 mapping improvements + Hercules Inpulse 200: Configure shift-browser knob to scroll the library (quick) + #12932 +
  • +
  • + Pioneer DDJ-FLX4: Add waveform zoom and other mapping improvements + #12896 #12842
  • +
  • + Traktor F1: Fixes for hid-parser and related script + #12876 +
  • Traktor S3: Fix mapping crash on macOS #12840 @@ -120,6 +129,28 @@ #12853 #12847 #12822 + #12892 +
  • +
+

+ Miscellaneous +

+
    +
  • + Remove unnecessary unpolish operation of the style, before polish the new style + #12445 +
  • +
  • + Developer Tools: initially sort controls by group name, ascending + #12884 +
  • +
  • + History: show track count and duration in sidebar + #12811 +
  • +
  • + Prevent removing tracks from locked playlists + #12927
From 99cb6c0f03373b6b8f2dfe178a8eaaccdacb938a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 13 Mar 2024 00:02:26 +0100 Subject: [PATCH 051/124] Replace file:line that tricks sphinx linkcheck when building the manual --- CHANGELOG.md | 2 +- res/linux/org.mixxx.Mixxx.metainfo.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5927b5f581d..a9f4c4c2826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -521,7 +521,7 @@ [#11975](https://github.com/mixxxdj/mixxx/pull/11975) [#11957](https://github.com/mixxxdj/mixxx/issues/11957) * Fix 500ms blocking of the whole event loop, when holding mouse down on title bar on Windows [#12359](https://github.com/mixxxdj/mixxx/pull/12359) [#12358](https://github.com/mixxxdj/mixxx/issues/12358) [#12433](https://github.com/mixxxdj/mixxx/pull/12433) [#12458](https://github.com/mixxxdj/mixxx/pull/12458) -* change SKIN_WARNING to show the skin file:line first, then c++ context [#12253](https://github.com/mixxxdj/mixxx/pull/12253) +* change SKIN_WARNING to show the skin file and line first, then c++ context [#12253](https://github.com/mixxxdj/mixxx/pull/12253) * Fix style of selected QComboBox items on Windows [#12339](https://github.com/mixxxdj/mixxx/pull/12339) [#12323](https://github.com/mixxxdj/mixxx/issues/12323) * Fix reading the Spinny cover on Windows [#12103](https://github.com/mixxxdj/mixxx/pull/12103) [#11131](https://github.com/mixxxdj/mixxx/issues/11131) * Fix inconsistent/wrong musical keys in the UI [#12051](https://github.com/mixxxdj/mixxx/pull/12051) [#12044](https://github.com/mixxxdj/mixxx/issues/12044) diff --git a/res/linux/org.mixxx.Mixxx.metainfo.xml b/res/linux/org.mixxx.Mixxx.metainfo.xml index 9e3df41a677..e33cb3970ee 100644 --- a/res/linux/org.mixxx.Mixxx.metainfo.xml +++ b/res/linux/org.mixxx.Mixxx.metainfo.xml @@ -96,7 +96,7 @@ Do not edit it manually. --> - +

Controller Mappings @@ -1442,7 +1442,7 @@ #12458

  • - change SKIN_WARNING to show the skin file:line first, then c++ context + change SKIN_WARNING to show the skin file and line first, then c++ context #12253
  • From e2d41999e7c017c6a353f87967152d3bd2102354 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 13 Mar 2024 13:52:31 +0100 Subject: [PATCH 052/124] LateNight Classic: fix rate controls pushbutton bg margin bg color was overlapping defined area because of wrong qss selector --- res/skins/LateNight/style_classic.qss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/skins/LateNight/style_classic.qss b/res/skins/LateNight/style_classic.qss index eaa563ad2b0..af9b1e8ae24 100644 --- a/res/skins/LateNight/style_classic.qss +++ b/res/skins/LateNight/style_classic.qss @@ -1354,7 +1354,7 @@ WPushButton#PlayIndicator, WPushButton#CueDeck, #PlayCueMini WPushButton, WPushButton#LoopActivate, -WPushButton#RateControls WPushButton, +#RateControls WPushButton, WPushButton#SyncSampler, #MixerContainer WPushButton, #FxUnitContainer WPushButton, From a5b6a08e2692c2097b4e3b4dd40b3f6bc406110f Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 15 Mar 2024 08:19:35 +0100 Subject: [PATCH 053/124] fix(controller): fix this-reference in lambda --- res/controllers/Behringer-Extension-scripts.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index f83d6dd45d4..89cfcde501d 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -239,11 +239,12 @@ isEnabled: function() { return this.id !== 0; }, start: function() { this.reset(); + var timer = this; this.id = engine.beginTimer(this.timeout, () => { - if (this.oneShot) { - this.disable(); + if (timer.oneShot) { + timer.disable(); } - this.action.call(this.owner); + timer.action.call(timer.owner); }, this.oneShot); }, reset: function() { From ca71973d1be109527adb256874c82839266d7f4c Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 15 Mar 2024 08:46:50 +0100 Subject: [PATCH 054/124] style(controller): fix code style issue --- res/controllers/Behringer-Extension-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index 89cfcde501d..41f49b968da 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -239,7 +239,7 @@ isEnabled: function() { return this.id !== 0; }, start: function() { this.reset(); - var timer = this; + const timer = this; this.id = engine.beginTimer(this.timeout, () => { if (timer.oneShot) { timer.disable(); From ea078598bbac4027114a654c563246c82889a83f Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 15 Mar 2024 08:52:31 +0100 Subject: [PATCH 055/124] style(controller): fix code style issue --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aac3c7fd05c..fba0236afa8 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The Mixxx team uses [Github Issues][issues] to manage Mixxx development. Have a bug or feature request? [File a bug on Github][fileabug]. Want to get involved in Mixxx development? Assign yourself a bug from the [easy -bug list][easybugs] and get started! +bug list][easybugs] and get started! Read [CONTRIBUTING](CONTRIBUTING.md) for more information. ## Building Mixxx From 98468eeb2eb52ec32eab595f7bda96d70a0bf7f5 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 17 Mar 2024 11:18:52 +0100 Subject: [PATCH 056/124] Use HIDPacket.addOutput for outputs instead of HIDPacket.addControl, which is only for inputs. This fixes the same issue as reported in #12876 for the Traktor F1 mapping of the same author. --- res/controllers/Nintendo-Wiimote.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/res/controllers/Nintendo-Wiimote.js b/res/controllers/Nintendo-Wiimote.js index 9dfb9f576ff..bef8b580558 100644 --- a/res/controllers/Nintendo-Wiimote.js +++ b/res/controllers/Nintendo-Wiimote.js @@ -359,24 +359,24 @@ function WiimoteController() { this.registerOutputPackets = function() { packet = new HIDPacket("feedback", 0x11); - packet.addControl("state","rumble",1,"B",0x1); - packet.addControl("state","led_1",1,"B",0x10); - packet.addControl("state","led_2",1,"B",0x20); - packet.addControl("state","led_3",1,"B",0x40); - packet.addControl("state","led_4",1,"B",0x80); + packet.addOutput("state","rumble",1,"B",0x1); + packet.addOutput("state","led_1",1,"B",0x10); + packet.addOutput("state","led_2",1,"B",0x20); + packet.addOutput("state","led_3",1,"B",0x40); + packet.addOutput("state","led_4",1,"B",0x80); this.controller.registerOutputPacket(packet); packet = new HIDPacket("setreportmode", 0x12); - packet.addControl("reportmode","continuous",1,"B",0x4); - packet.addControl("reportmode","code",2,"B"); + packet.addOutput("reportmode","continuous",1,"B",0x4); + packet.addOutput("reportmode","code",2,"B"); this.controller.registerOutputPacket(packet); packet = new HIDPacket("ircamera", 0x13); - packet.addControl("ircontrol","enabled",1,"B",0x4); + packet.addOutput("ircontrol","enabled",1,"B",0x4); this.controller.registerOutputPacket(packet); packet = new HIDPacket("ircamerastate", 0x1a); - packet.addControl("irstate","enabled",1,"B",0x4); + packet.addOutput("irstate","enabled",1,"B",0x4); this.controller.registerOutputPacket(packet); } From c5a8601facddf762307fa8a3bf7d751be2c2f31c Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 17 Mar 2024 11:25:24 +0100 Subject: [PATCH 057/124] Use HIDPacket.addOutput for outputs instead of HIDPacket.addControl, which is only for inputs. --- res/controllers/Pioneer-CDJ-HID.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/controllers/Pioneer-CDJ-HID.js b/res/controllers/Pioneer-CDJ-HID.js index c01e0c67f3e..2c5513bec7b 100644 --- a/res/controllers/Pioneer-CDJ-HID.js +++ b/res/controllers/Pioneer-CDJ-HID.js @@ -143,7 +143,7 @@ function PioneerCDJController() { // bytes to get it working. Need to add a response packet // to input packets as well, if we receive acknowledgement packet = new HIDPacket("request_hid_mode", 0x1); - packet.addControl("hid","mode",0,"B",1); + packet.addOutput("hid","mode",0,"B",1); this.controller.registerOutputPacket(packet); // Control packet for screen text control @@ -159,7 +159,7 @@ function PioneerCDJController() { for (var i=0;i Date: Sun, 17 Mar 2024 12:35:20 +0100 Subject: [PATCH 058/124] Fixed local declaration of HIDPacket objects representing the HID reports --- res/controllers/Nintendo-Wiimote.js | 632 ++++++++++++++-------------- 1 file changed, 316 insertions(+), 316 deletions(-) diff --git a/res/controllers/Nintendo-Wiimote.js b/res/controllers/Nintendo-Wiimote.js index bef8b580558..e3eae5ced55 100644 --- a/res/controllers/Nintendo-Wiimote.js +++ b/res/controllers/Nintendo-Wiimote.js @@ -30,354 +30,354 @@ function WiimoteController() { this.controller.defaultPacket = "coreaccel"; // Core buttons input packet - packet = new HIDPacket("buttons", 0x30); - packet.addControl("buttons","arrow_left",1,"B",0x1); - packet.addControl("buttons","arrow_right",1,"B",0x2); - packet.addControl("buttons","arrow_down",1,"B",0x4); - packet.addControl("buttons","arrow_up",1,"B",0x8); - packet.addControl("buttons","button_plus",1,"B",0x10); - packet.addControl("buttons","button_1",2,"B",0x2); - packet.addControl("buttons","button_2",2,"B",0x1); - packet.addControl("buttons","button_bottom",2,"B",0x4); - packet.addControl("buttons","button_a",2,"B",0x8); - packet.addControl("buttons","button_minus",2,"B",0x10); - packet.addControl("buttons","button_home",2,"B",0x80); - this.controller.registerInputPacket(packet); + const InputReport0x30 = new HIDPacket("buttons", 0x30); + InputReport0x30.addControl("buttons", "arrow_left", 1, "B", 0x1); + InputReport0x30.addControl("buttons", "arrow_right", 1, "B", 0x2); + InputReport0x30.addControl("buttons", "arrow_down", 1, "B", 0x4); + InputReport0x30.addControl("buttons", "arrow_up", 1, "B", 0x8); + InputReport0x30.addControl("buttons", "button_plus", 1, "B", 0x10); + InputReport0x30.addControl("buttons", "button_1", 2, "B", 0x2); + InputReport0x30.addControl("buttons", "button_2", 2, "B", 0x1); + InputReport0x30.addControl("buttons", "button_bottom", 2, "B", 0x4); + InputReport0x30.addControl("buttons", "button_a", 2, "B", 0x8); + InputReport0x30.addControl("buttons", "button_minus", 2, "B", 0x10); + InputReport0x30.addControl("buttons", "button_home", 2, "B", 0x80); + this.controller.registerInputPacket(InputReport0x30); // Core buttons and accelerometer data - packet = new HIDPacket("coreaccel", 0x31); - packet.addControl("coreaccel","arrow_left",1,"B",0x1); - packet.addControl("coreaccel","arrow_right",1,"B",0x2); - packet.addControl("coreaccel","arrow_down",1,"B",0x4); - packet.addControl("coreaccel","arrow_up",1,"B",0x8); - packet.addControl("coreaccel","button_plus",1,"B",0x10); - packet.addControl("coreaccel","button_1",2,"B",0x2); - packet.addControl("coreaccel","button_2",2,"B",0x1); - packet.addControl("coreaccel","button_bottom",2,"B",0x4); - packet.addControl("coreaccel","button_a",2,"B",0x8); - packet.addControl("coreaccel","button_minus",2,"B",0x10); - packet.addControl("coreaccel","button_home",2,"B",0x80); - packet.addControl("coreaccel","accelerometer_x",3,"B"); - packet.addControl("coreaccel","accelerometer_y",4,"B"); - packet.addControl("coreaccel","accelerometer_z",5,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x31 = new HIDPacket("coreaccel", 0x31); + InputReport0x31.addControl("coreaccel", "arrow_left", 1, "B", 0x1); + InputReport0x31.addControl("coreaccel", "arrow_right", 1, "B", 0x2); + InputReport0x31.addControl("coreaccel", "arrow_down", 1, "B", 0x4); + InputReport0x31.addControl("coreaccel", "arrow_up", 1, "B", 0x8); + InputReport0x31.addControl("coreaccel", "button_plus", 1, "B", 0x10); + InputReport0x31.addControl("coreaccel", "button_1", 2, "B", 0x2); + InputReport0x31.addControl("coreaccel", "button_2", 2, "B", 0x1); + InputReport0x31.addControl("coreaccel", "button_bottom", 2, "B", 0x4); + InputReport0x31.addControl("coreaccel", "button_a", 2, "B", 0x8); + InputReport0x31.addControl("coreaccel", "button_minus", 2, "B", 0x10); + InputReport0x31.addControl("coreaccel", "button_home", 2, "B", 0x80); + InputReport0x31.addControl("coreaccel", "accelerometer_x", 3, "B"); + InputReport0x31.addControl("coreaccel", "accelerometer_y", 4, "B"); + InputReport0x31.addControl("coreaccel", "accelerometer_z", 5, "B"); + this.controller.registerInputPacket(InputReport0x31); // Core buttons and accelerometer data with 8 bytes // from extension module - packet = new HIDPacket("coreaccel_ext8", 0x32); - packet.addControl("coreaccel_ext8","arrow_left",1,"B",0x1); - packet.addControl("coreaccel_ext8","arrow_right",1,"B",0x2); - packet.addControl("coreaccel_ext8","arrow_down",1,"B",0x4); - packet.addControl("coreaccel_ext8","arrow_up",1,"B",0x8); - packet.addControl("coreaccel_ext8","button_plus",1,"B",0x10); - packet.addControl("coreaccel_ext8","button_1",2,"B",0x2); - packet.addControl("coreaccel_ext8","button_2",2,"B",0x1); - packet.addControl("coreaccel_ext8","button_bottom",2,"B",0x4); - packet.addControl("coreaccel_ext8","button_a",2,"B",0x8); - packet.addControl("coreaccel_ext8","button_minus",2,"B",0x10); - packet.addControl("coreaccel_ext8","button_home",2,"B",0x80); - packet.addControl("coreaccel_ext8","accelerometer_x",3,"B"); - packet.addControl("coreaccel_ext8","accelerometer_y",4,"B"); - packet.addControl("coreaccel_ext8","accelerometer_z",5,"B"); - packet.addControl("coreaccel_ext8","extension_1",6,"B"); - packet.addControl("coreaccel_ext8","extension_2",7,"B"); - packet.addControl("coreaccel_ext8","extension_3",8,"B"); - packet.addControl("coreaccel_ext8","extension_4",9,"B"); - packet.addControl("coreaccel_ext8","extension_5",10,"B"); - packet.addControl("coreaccel_ext8","extension_6",11,"B"); - packet.addControl("coreaccel_ext8","extension_7",12,"B"); - packet.addControl("coreaccel_ext8","extension_8",13,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x32 = new HIDPacket("coreaccel_ext8", 0x32); + InputReport0x32.addControl("coreaccel_ext8", "arrow_left", 1, "B", 0x1); + InputReport0x32.addControl("coreaccel_ext8", "arrow_right", 1, "B", 0x2); + InputReport0x32.addControl("coreaccel_ext8", "arrow_down", 1, "B", 0x4); + InputReport0x32.addControl("coreaccel_ext8", "arrow_up", 1, "B", 0x8); + InputReport0x32.addControl("coreaccel_ext8", "button_plus", 1, "B", 0x10); + InputReport0x32.addControl("coreaccel_ext8", "button_1", 2, "B", 0x2); + InputReport0x32.addControl("coreaccel_ext8", "button_2", 2, "B", 0x1); + InputReport0x32.addControl("coreaccel_ext8", "button_bottom", 2, "B", 0x4); + InputReport0x32.addControl("coreaccel_ext8", "button_a", 2, "B", 0x8); + InputReport0x32.addControl("coreaccel_ext8", "button_minus", 2, "B", 0x10); + InputReport0x32.addControl("coreaccel_ext8", "button_home", 2, "B", 0x80); + InputReport0x32.addControl("coreaccel_ext8", "accelerometer_x", 3, "B"); + InputReport0x32.addControl("coreaccel_ext8", "accelerometer_y", 4, "B"); + InputReport0x32.addControl("coreaccel_ext8", "accelerometer_z", 5, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_1", 6, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_2", 7, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_3", 8, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_4", 9, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_5", 10, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_6", 11, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_7", 12, "B"); + InputReport0x32.addControl("coreaccel_ext8", "extension_8", 13, "B"); + this.controller.registerInputPacket(InputReport0x32); // Core buttons and accelerometer data with 12 bytes // from IR camera - packet = new HIDPacket("coreaccel_ir12", 0x33); - packet.addControl("coreaccel_ir12","arrow_left",1,"B",0x1); - packet.addControl("coreaccel_ir12","arrow_right",1,"B",0x2); - packet.addControl("coreaccel_ir12","arrow_down",1,"B",0x4); - packet.addControl("coreaccel_ir12","arrow_up",1,"B",0x8); - packet.addControl("coreaccel_ir12","button_plus",1,"B",0x10); - packet.addControl("coreaccel_ir12","button_1",2,"B",0x2); - packet.addControl("coreaccel_ir12","button_2",2,"B",0x1); - packet.addControl("coreaccel_ir12","button_bottom",2,"B",0x4); - packet.addControl("coreaccel_ir12","button_a",2,"B",0x8); - packet.addControl("coreaccel_ir12","button_minus",2,"B",0x10); - packet.addControl("coreaccel_ir12","button_home",2,"B",0x80); - packet.addControl("coreaccel_ir12","accelerometer_x",3,"B"); - packet.addControl("coreaccel_ir12","accelerometer_y",4,"B"); - packet.addControl("coreaccel_ir12","accelerometer_z",5,"B"); - packet.addControl("coreaccel_ir12","ir_1",6,"B"); - packet.addControl("coreaccel_ir12","ir_2",7,"B"); - packet.addControl("coreaccel_ir12","ir_3",8,"B"); - packet.addControl("coreaccel_ir12","ir_4",9,"B"); - packet.addControl("coreaccel_ir12","ir_5",10,"B"); - packet.addControl("coreaccel_ir12","ir_6",11,"B"); - packet.addControl("coreaccel_ir12","ir_7",12,"B"); - packet.addControl("coreaccel_ir12","ir_8",13,"B"); - packet.addControl("coreaccel_ir12","ir_9",14,"B"); - packet.addControl("coreaccel_ir12","ir_10",15,"B"); - packet.addControl("coreaccel_ir12","ir_11",16,"B"); - packet.addControl("coreaccel_ir12","ir_12",17,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x33 = new HIDPacket("coreaccel_ir12", 0x33); + InputReport0x33.addControl("coreaccel_ir12", "arrow_left", 1, "B", 0x1); + InputReport0x33.addControl("coreaccel_ir12", "arrow_right", 1, "B", 0x2); + InputReport0x33.addControl("coreaccel_ir12", "arrow_down", 1, "B", 0x4); + InputReport0x33.addControl("coreaccel_ir12", "arrow_up", 1, "B", 0x8); + InputReport0x33.addControl("coreaccel_ir12", "button_plus", 1, "B", 0x10); + InputReport0x33.addControl("coreaccel_ir12", "button_1", 2, "B", 0x2); + InputReport0x33.addControl("coreaccel_ir12", "button_2", 2, "B", 0x1); + InputReport0x33.addControl("coreaccel_ir12", "button_bottom", 2, "B", 0x4); + InputReport0x33.addControl("coreaccel_ir12", "button_a", 2, "B", 0x8); + InputReport0x33.addControl("coreaccel_ir12", "button_minus", 2, "B", 0x10); + InputReport0x33.addControl("coreaccel_ir12", "button_home", 2, "B", 0x80); + InputReport0x33.addControl("coreaccel_ir12", "accelerometer_x", 3, "B"); + InputReport0x33.addControl("coreaccel_ir12", "accelerometer_y", 4, "B"); + InputReport0x33.addControl("coreaccel_ir12", "accelerometer_z", 5, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_1", 6, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_2", 7, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_3", 8, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_4", 9, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_5", 10, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_6", 11, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_7", 12, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_8", 13, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_9", 14, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_10", 15, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_11", 16, "B"); + InputReport0x33.addControl("coreaccel_ir12", "ir_12", 17, "B"); + this.controller.registerInputPacket(InputReport0x33); // Core buttons and 19 bytes from extension module, // no accelerometer data - packet = new HIDPacket("corebuttons_ext19", 0x34); - packet.addControl("corebuttons_ext19","arrow_left",1,"B",0x1); - packet.addControl("corebuttons_ext19","arrow_right",1,"B",0x2); - packet.addControl("corebuttons_ext19","arrow_down",1,"B",0x4); - packet.addControl("corebuttons_ext19","arrow_up",1,"B",0x8); - packet.addControl("corebuttons_ext19","button_plus",1,"B",0x10); - packet.addControl("corebuttons_ext19","button_1",2,"B",0x2); - packet.addControl("corebuttons_ext19","button_2",2,"B",0x1); - packet.addControl("corebuttons_ext19","button_bottom",2,"B",0x4); - packet.addControl("corebuttons_ext19","button_a",2,"B",0x8); - packet.addControl("corebuttons_ext19","button_minus",2,"B",0x10); - packet.addControl("corebuttons_ext19","button_home",2,"B",0x80); - packet.addControl("corebuttons_ext19","extension_1",3,"B"); - packet.addControl("corebuttons_ext19","extension_2",4,"B"); - packet.addControl("corebuttons_ext19","extension_3",5,"B"); - packet.addControl("corebuttons_ext19","extension_4",6,"B"); - packet.addControl("corebuttons_ext19","extension_5",7,"B"); - packet.addControl("corebuttons_ext19","extension_6",8,"B"); - packet.addControl("corebuttons_ext19","extension_7",9,"B"); - packet.addControl("corebuttons_ext19","extension_8",10,"B"); - packet.addControl("corebuttons_ext19","extension_9",11,"B"); - packet.addControl("corebuttons_ext19","extension_10",12,"B"); - packet.addControl("corebuttons_ext19","extension_11",13,"B"); - packet.addControl("corebuttons_ext19","extension_12",14,"B"); - packet.addControl("corebuttons_ext19","extension_13",15,"B"); - packet.addControl("corebuttons_ext19","extension_14",16,"B"); - packet.addControl("corebuttons_ext19","extension_15",17,"B"); - packet.addControl("corebuttons_ext19","extension_16",18,"B"); - packet.addControl("corebuttons_ext19","extension_17",19,"B"); - packet.addControl("corebuttons_ext19","extension_18",20,"B"); - packet.addControl("corebuttons_ext19","extension_19",21,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x34 = new HIDPacket("corebuttons_ext19", 0x34); + InputReport0x34.addControl("corebuttons_ext19", "arrow_left", 1, "B", 0x1); + InputReport0x34.addControl("corebuttons_ext19", "arrow_right", 1, "B", 0x2); + InputReport0x34.addControl("corebuttons_ext19", "arrow_down", 1, "B", 0x4); + InputReport0x34.addControl("corebuttons_ext19", "arrow_up", 1, "B", 0x8); + InputReport0x34.addControl("corebuttons_ext19", "button_plus", 1, "B", 0x10); + InputReport0x34.addControl("corebuttons_ext19", "button_1", 2, "B", 0x2); + InputReport0x34.addControl("corebuttons_ext19", "button_2", 2, "B", 0x1); + InputReport0x34.addControl("corebuttons_ext19", "button_bottom", 2, "B", 0x4); + InputReport0x34.addControl("corebuttons_ext19", "button_a", 2, "B", 0x8); + InputReport0x34.addControl("corebuttons_ext19", "button_minus", 2, "B", 0x10); + InputReport0x34.addControl("corebuttons_ext19", "button_home", 2, "B", 0x80); + InputReport0x34.addControl("corebuttons_ext19", "extension_1", 3, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_2", 4, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_3", 5, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_4", 6, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_5", 7, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_6", 8, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_7", 9, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_8", 10, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_9", 11, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_10", 12, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_11", 13, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_12", 14, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_13", 15, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_14", 16, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_15", 17, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_16", 18, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_17", 19, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_18", 20, "B"); + InputReport0x34.addControl("corebuttons_ext19", "extension_19", 21, "B"); + this.controller.registerInputPacket(InputReport0x34); // Core buttons, accelerometer and 16 bytes from // extension module - packet = new HIDPacket("coreaccel_ext16", 0x35); - packet.addControl("coreaccel_ext16","arrow_left",1,"B",0x1); - packet.addControl("coreaccel_ext16","arrow_right",1,"B",0x2); - packet.addControl("coreaccel_ext16","arrow_down",1,"B",0x4); - packet.addControl("coreaccel_ext16","arrow_up",1,"B",0x8); - packet.addControl("coreaccel_ext16","button_plus",1,"B",0x10); - packet.addControl("coreaccel_ext16","button_1",2,"B",0x2); - packet.addControl("coreaccel_ext16","button_2",2,"B",0x1); - packet.addControl("coreaccel_ext16","button_bottom",2,"B",0x4); - packet.addControl("coreaccel_ext16","button_a",2,"B",0x8); - packet.addControl("coreaccel_ext16","button_minus",2,"B",0x10); - packet.addControl("coreaccel_ext16","button_home",2,"B",0x80); - packet.addControl("coreaccel_ext16","accelerometer_x",3,"B"); - packet.addControl("coreaccel_ext16","accelerometer_y",4,"B"); - packet.addControl("coreaccel_ext16","accelerometer_z",5,"B"); - packet.addControl("coreaccel_ext16","extension_1",6,"B"); - packet.addControl("coreaccel_ext16","extension_2",7,"B"); - packet.addControl("coreaccel_ext16","extension_3",8,"B"); - packet.addControl("coreaccel_ext16","extension_4",9,"B"); - packet.addControl("coreaccel_ext16","extension_5",10,"B"); - packet.addControl("coreaccel_ext16","extension_6",11,"B"); - packet.addControl("coreaccel_ext16","extension_7",12,"B"); - packet.addControl("coreaccel_ext16","extension_8",13,"B"); - packet.addControl("coreaccel_ext16","extension_9",14,"B"); - packet.addControl("coreaccel_ext16","extension_10",15,"B"); - packet.addControl("coreaccel_ext16","extension_11",16,"B"); - packet.addControl("coreaccel_ext16","extension_12",17,"B"); - packet.addControl("coreaccel_ext16","extension_13",18,"B"); - packet.addControl("coreaccel_ext16","extension_14",19,"B"); - packet.addControl("coreaccel_ext16","extension_15",20,"B"); - packet.addControl("coreaccel_ext16","extension_16",21,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x35 = new HIDPacket("coreaccel_ext16", 0x35); + InputReport0x35.addControl("coreaccel_ext16", "arrow_left", 1, "B", 0x1); + InputReport0x35.addControl("coreaccel_ext16", "arrow_right", 1, "B", 0x2); + InputReport0x35.addControl("coreaccel_ext16", "arrow_down", 1, "B", 0x4); + InputReport0x35.addControl("coreaccel_ext16", "arrow_up", 1, "B", 0x8); + InputReport0x35.addControl("coreaccel_ext16", "button_plus", 1, "B", 0x10); + InputReport0x35.addControl("coreaccel_ext16", "button_1", 2, "B", 0x2); + InputReport0x35.addControl("coreaccel_ext16", "button_2", 2, "B", 0x1); + InputReport0x35.addControl("coreaccel_ext16", "button_bottom", 2, "B", 0x4); + InputReport0x35.addControl("coreaccel_ext16", "button_a", 2, "B", 0x8); + InputReport0x35.addControl("coreaccel_ext16", "button_minus", 2, "B", 0x10); + InputReport0x35.addControl("coreaccel_ext16", "button_home", 2, "B", 0x80); + InputReport0x35.addControl("coreaccel_ext16", "accelerometer_x", 3, "B"); + InputReport0x35.addControl("coreaccel_ext16", "accelerometer_y", 4, "B"); + InputReport0x35.addControl("coreaccel_ext16", "accelerometer_z", 5, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_1", 6, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_2", 7, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_3", 8, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_4", 9, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_5", 10, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_6", 11, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_7", 12, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_8", 13, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_9", 14, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_10", 15, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_11", 16, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_12", 17, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_13", 18, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_14", 19, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_15", 20, "B"); + InputReport0x35.addControl("coreaccel_ext16", "extension_16", 21, "B"); + this.controller.registerInputPacket(InputReport0x35); // Core buttons, no accelerometer and 10 IR bytes and // 9 bytes from extension module - packet = new HIDPacket("corebuttons_ir10_ext9", 0x36); - packet.addControl("corebuttons_ir10_ext9","arrow_left",1,"B",0x1); - packet.addControl("corebuttons_ir10_ext9","arrow_right",1,"B",0x2); - packet.addControl("corebuttons_ir10_ext9","arrow_down",1,"B",0x4); - packet.addControl("corebuttons_ir10_ext9","arrow_up",1,"B",0x8); - packet.addControl("corebuttons_ir10_ext9","button_plus",1,"B",0x10); - packet.addControl("corebuttons_ir10_ext9","button_1",2,"B",0x2); - packet.addControl("corebuttons_ir10_ext9","button_2",2,"B",0x1); - packet.addControl("corebuttons_ir10_ext9","button_bottom",2,"B",0x4); - packet.addControl("corebuttons_ir10_ext9","button_a",2,"B",0x8); - packet.addControl("corebuttons_ir10_ext9","button_minus",2,"B",0x10); - packet.addControl("corebuttons_ir10_ext9","button_home",2,"B",0x80); - packet.addControl("corebuttons_ir10_ext9","ir_1",3,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_2",4,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_3",5,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_4",6,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_5",7,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_6",8,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_7",9,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_8",10,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_9",11,"B"); - packet.addControl("corebuttons_ir10_ext9","ir_10",12,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_1",13,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_2",14,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_3",15,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_4",16,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_5",17,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_6",18,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_7",19,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_8",20,"B"); - packet.addControl("corebuttons_ir10_ext9","extension_9",21,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x36 = new HIDPacket("corebuttons_ir10_ext9", 0x36); + InputReport0x36.addControl("corebuttons_ir10_ext9", "arrow_left", 1, "B", 0x1); + InputReport0x36.addControl("corebuttons_ir10_ext9", "arrow_right", 1, "B", 0x2); + InputReport0x36.addControl("corebuttons_ir10_ext9", "arrow_down", 1, "B", 0x4); + InputReport0x36.addControl("corebuttons_ir10_ext9", "arrow_up", 1, "B", 0x8); + InputReport0x36.addControl("corebuttons_ir10_ext9", "button_plus", 1, "B", 0x10); + InputReport0x36.addControl("corebuttons_ir10_ext9", "button_1", 2, "B", 0x2); + InputReport0x36.addControl("corebuttons_ir10_ext9", "button_2", 2, "B", 0x1); + InputReport0x36.addControl("corebuttons_ir10_ext9", "button_bottom", 2, "B", 0x4); + InputReport0x36.addControl("corebuttons_ir10_ext9", "button_a", 2, "B", 0x8); + InputReport0x36.addControl("corebuttons_ir10_ext9", "button_minus", 2, "B", 0x10); + InputReport0x36.addControl("corebuttons_ir10_ext9", "button_home", 2, "B", 0x80); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_1", 3, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_2", 4, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_3", 5, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_4", 6, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_5", 7, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_6", 8, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_7", 9, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_8", 10, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_9", 11, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "ir_10", 12, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_1", 13, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_2", 14, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_3", 15, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_4", 16, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_5", 17, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_6", 18, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_7", 19, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_8", 20, "B"); + InputReport0x36.addControl("corebuttons_ir10_ext9", "extension_9", 21, "B"); + this.controller.registerInputPacket(InputReport0x36); // Core buttons, accelerometer and 10 IR bytes and // 6 bytes from extension module - packet = new HIDPacket("coreaccel_ir10_ext6", 0x37); - packet.addControl("coreaccel_ir10_ext6","arrow_left",1,"B",0x1); - packet.addControl("coreaccel_ir10_ext6","arrow_right",1,"B",0x2); - packet.addControl("coreaccel_ir10_ext6","arrow_down",1,"B",0x4); - packet.addControl("coreaccel_ir10_ext6","arrow_up",1,"B",0x8); - packet.addControl("coreaccel_ir10_ext6","button_plus",1,"B",0x10); - packet.addControl("coreaccel_ir10_ext6","button_1",2,"B",0x2); - packet.addControl("coreaccel_ir10_ext6","button_2",2,"B",0x1); - packet.addControl("coreaccel_ir10_ext6","button_bottom",2,"B",0x4); - packet.addControl("coreaccel_ir10_ext6","button_a",2,"B",0x8); - packet.addControl("coreaccel_ir10_ext6","button_minus",2,"B",0x10); - packet.addControl("coreaccel_ir10_ext6","button_home",2,"B",0x80); - packet.addControl("coreaccel_ir10_ext6","accelerometer_x",3,"B"); - packet.addControl("coreaccel_ir10_ext6","accelerometer_y",4,"B"); - packet.addControl("coreaccel_ir10_ext6","accelerometer_z",5,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_1",6,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_2",7,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_3",8,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_4",9,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_5",10,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_6",11,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_7",12,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_8",13,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_9",14,"B"); - packet.addControl("coreaccel_ir10_ext6","ir_10",15,"B"); - packet.addControl("coreaccel_ir10_ext6","extension_1",16,"B"); - packet.addControl("coreaccel_ir10_ext6","extension_2",17,"B"); - packet.addControl("coreaccel_ir10_ext6","extension_3",18,"B"); - packet.addControl("coreaccel_ir10_ext6","extension_4",19,"B"); - packet.addControl("coreaccel_ir10_ext6","extension_5",20,"B"); - packet.addControl("coreaccel_ir10_ext6","extension_6",21,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x37 = new HIDPacket("coreaccel_ir10_ext6", 0x37); + InputReport0x37.addControl("coreaccel_ir10_ext6", "arrow_left", 1, "B", 0x1); + InputReport0x37.addControl("coreaccel_ir10_ext6", "arrow_right", 1, "B", 0x2); + InputReport0x37.addControl("coreaccel_ir10_ext6", "arrow_down", 1, "B", 0x4); + InputReport0x37.addControl("coreaccel_ir10_ext6", "arrow_up", 1, "B", 0x8); + InputReport0x37.addControl("coreaccel_ir10_ext6", "button_plus", 1, "B", 0x10); + InputReport0x37.addControl("coreaccel_ir10_ext6", "button_1", 2, "B", 0x2); + InputReport0x37.addControl("coreaccel_ir10_ext6", "button_2", 2, "B", 0x1); + InputReport0x37.addControl("coreaccel_ir10_ext6", "button_bottom", 2, "B", 0x4); + InputReport0x37.addControl("coreaccel_ir10_ext6", "button_a", 2, "B", 0x8); + InputReport0x37.addControl("coreaccel_ir10_ext6", "button_minus", 2, "B", 0x10); + InputReport0x37.addControl("coreaccel_ir10_ext6", "button_home", 2, "B", 0x80); + InputReport0x37.addControl("coreaccel_ir10_ext6", "accelerometer_x", 3, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "accelerometer_y", 4, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "accelerometer_z", 5, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_1", 6, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_2", 7, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_3", 8, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_4", 9, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_5", 10, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_6", 11, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_7", 12, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_8", 13, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_9", 14, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "ir_10", 15, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "extension_1", 16, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "extension_2", 17, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "extension_3", 18, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "extension_4", 19, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "extension_5", 20, "B"); + InputReport0x37.addControl("coreaccel_ir10_ext6", "extension_6", 21, "B"); + this.controller.registerInputPacket(InputReport0x37); // No core buttons, no accelerometer, 21 bytes from // extension module - packet = new HIDPacket("ext_21", 0x3d); - packet.addControl("ext_21","extension_1",1,"B"); - packet.addControl("ext_21","extension_2",2,"B"); - packet.addControl("ext_21","extension_3",3,"B"); - packet.addControl("ext_21","extension_4",4,"B"); - packet.addControl("ext_21","extension_5",5,"B"); - packet.addControl("ext_21","extension_6",6,"B"); - packet.addControl("ext_21","extension_7",7,"B"); - packet.addControl("ext_21","extension_8",8,"B"); - packet.addControl("ext_21","extension_9",9,"B"); - packet.addControl("ext_21","extension_10",10,"B"); - packet.addControl("ext_21","extension_11",11,"B"); - packet.addControl("ext_21","extension_12",12,"B"); - packet.addControl("ext_21","extension_13",13,"B"); - packet.addControl("ext_21","extension_14",14,"B"); - packet.addControl("ext_21","extension_15",15,"B"); - packet.addControl("ext_21","extension_16",16,"B"); - packet.addControl("ext_21","extension_17",17,"B"); - packet.addControl("ext_21","extension_18",18,"B"); - packet.addControl("ext_21","extension_19",19,"B"); - packet.addControl("ext_21","extension_20",20,"B"); - packet.addControl("ext_21","extension_21",21,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x3d = new HIDPacket("ext_21", 0x3d); + InputReport0x3d.addControl("ext_21", "extension_1", 1, "B"); + InputReport0x3d.addControl("ext_21", "extension_2", 2, "B"); + InputReport0x3d.addControl("ext_21", "extension_3", 3, "B"); + InputReport0x3d.addControl("ext_21", "extension_4", 4, "B"); + InputReport0x3d.addControl("ext_21", "extension_5", 5, "B"); + InputReport0x3d.addControl("ext_21", "extension_6", 6, "B"); + InputReport0x3d.addControl("ext_21", "extension_7", 7, "B"); + InputReport0x3d.addControl("ext_21", "extension_8", 8, "B"); + InputReport0x3d.addControl("ext_21", "extension_9", 9, "B"); + InputReport0x3d.addControl("ext_21", "extension_10", 10, "B"); + InputReport0x3d.addControl("ext_21", "extension_11", 11, "B"); + InputReport0x3d.addControl("ext_21", "extension_12", 12, "B"); + InputReport0x3d.addControl("ext_21", "extension_13", 13, "B"); + InputReport0x3d.addControl("ext_21", "extension_14", 14, "B"); + InputReport0x3d.addControl("ext_21", "extension_15", 15, "B"); + InputReport0x3d.addControl("ext_21", "extension_16", 16, "B"); + InputReport0x3d.addControl("ext_21", "extension_17", 17, "B"); + InputReport0x3d.addControl("ext_21", "extension_18", 18, "B"); + InputReport0x3d.addControl("ext_21", "extension_19", 19, "B"); + InputReport0x3d.addControl("ext_21", "extension_20", 20, "B"); + InputReport0x3d.addControl("ext_21", "extension_21", 21, "B"); + this.controller.registerInputPacket(InputReport0x3d); // Interleaved packet 1: core buttons, accelerometer, // first 16 bytes from IR camera - packet = new HIDPacket("coreaccel_interleaved_1", 0x3e); - packet.addControl("coreaccel_interleaved_1","arrow_left",1,"B",0x1); - packet.addControl("coreaccel_interleaved_1","arrow_right",1,"B",0x2); - packet.addControl("coreaccel_interleaved_1","arrow_down",1,"B",0x4); - packet.addControl("coreaccel_interleaved_1","arrow_up",1,"B",0x8); - packet.addControl("coreaccel_interleaved_1","button_plus",1,"B",0x10); - packet.addControl("coreaccel_interleaved_1","button_1",2,"B",0x2); - packet.addControl("coreaccel_interleaved_1","button_2",2,"B",0x1); - packet.addControl("coreaccel_interleaved_1","button_bottom",2,"B",0x4); - packet.addControl("coreaccel_interleaved_1","button_a",2,"B",0x8); - packet.addControl("coreaccel_interleaved_1","button_minus",2,"B",0x10); - packet.addControl("coreaccel_interleaved_1","button_home",2,"B",0x80); - packet.addControl("coreaccel_interleaved_1","accelerometer_x",3,"B"); - packet.addControl("coreaccel_interleaved_1","accelerometer_y",4,"B"); - packet.addControl("coreaccel_interleaved_1","accelerometer_z",5,"B"); - packet.addControl("coreaccel_interleaved_1","ir_1",6,"B"); - packet.addControl("coreaccel_interleaved_1","ir_2",7,"B"); - packet.addControl("coreaccel_interleaved_1","ir_3",8,"B"); - packet.addControl("coreaccel_interleaved_1","ir_4",9,"B"); - packet.addControl("coreaccel_interleaved_1","ir_5",10,"B"); - packet.addControl("coreaccel_interleaved_1","ir_6",11,"B"); - packet.addControl("coreaccel_interleaved_1","ir_7",12,"B"); - packet.addControl("coreaccel_interleaved_1","ir_8",13,"B"); - packet.addControl("coreaccel_interleaved_1","ir_9",14,"B"); - packet.addControl("coreaccel_interleaved_1","ir_10",15,"B"); - packet.addControl("coreaccel_interleaved_1","ir_11",16,"B"); - packet.addControl("coreaccel_interleaved_1","ir_12",17,"B"); - packet.addControl("coreaccel_interleaved_1","ir_13",18,"B"); - packet.addControl("coreaccel_interleaved_1","ir_14",19,"B"); - packet.addControl("coreaccel_interleaved_1","ir_15",20,"B"); - packet.addControl("coreaccel_interleaved_1","ir_16",21,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x3e = new HIDPacket("coreaccel_interleaved_1", 0x3e); + InputReport0x3e.addControl("coreaccel_interleaved_1", "arrow_left", 1, "B", 0x1); + InputReport0x3e.addControl("coreaccel_interleaved_1", "arrow_right", 1, "B", 0x2); + InputReport0x3e.addControl("coreaccel_interleaved_1", "arrow_down", 1, "B", 0x4); + InputReport0x3e.addControl("coreaccel_interleaved_1", "arrow_up", 1, "B", 0x8); + InputReport0x3e.addControl("coreaccel_interleaved_1", "button_plus", 1, "B", 0x10); + InputReport0x3e.addControl("coreaccel_interleaved_1", "button_1", 2, "B", 0x2); + InputReport0x3e.addControl("coreaccel_interleaved_1", "button_2", 2, "B", 0x1); + InputReport0x3e.addControl("coreaccel_interleaved_1", "button_bottom", 2, "B", 0x4); + InputReport0x3e.addControl("coreaccel_interleaved_1", "button_a", 2, "B", 0x8); + InputReport0x3e.addControl("coreaccel_interleaved_1", "button_minus", 2, "B", 0x10); + InputReport0x3e.addControl("coreaccel_interleaved_1", "button_home", 2, "B", 0x80); + InputReport0x3e.addControl("coreaccel_interleaved_1", "accelerometer_x", 3, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "accelerometer_y", 4, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "accelerometer_z", 5, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_1", 6, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_2", 7, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_3", 8, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_4", 9, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_5", 10, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_6", 11, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_7", 12, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_8", 13, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_9", 14, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_10", 15, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_11", 16, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_12", 17, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_13", 18, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_14", 19, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_15", 20, "B"); + InputReport0x3e.addControl("coreaccel_interleaved_1", "ir_16", 21, "B"); + this.controller.registerInputPacket(InputReport0x3e); // Interleaved packet 2: core buttons, accelerometer, // last 16 bytes from IR camera - packet = new HIDPacket("coreaccel_interleaved_2", 0x3f); - packet.addControl("coreaccel_interleaved_2","arrow_left",1,"B",0x1); - packet.addControl("coreaccel_interleaved_2","arrow_right",1,"B",0x2); - packet.addControl("coreaccel_interleaved_2","arrow_down",1,"B",0x4); - packet.addControl("coreaccel_interleaved_2","arrow_up",1,"B",0x8); - packet.addControl("coreaccel_interleaved_2","button_plus",1,"B",0x10); - packet.addControl("coreaccel_interleaved_2","button_1",2,"B",0x2); - packet.addControl("coreaccel_interleaved_2","button_2",2,"B",0x1); - packet.addControl("coreaccel_interleaved_2","button_bottom",2,"B",0x4); - packet.addControl("coreaccel_interleaved_2","button_a",2,"B",0x8); - packet.addControl("coreaccel_interleaved_2","button_minus",2,"B",0x10); - packet.addControl("coreaccel_interleaved_2","button_home",2,"B",0x80); - packet.addControl("coreaccel_interleaved_2","accelerometer_x",3,"B"); - packet.addControl("coreaccel_interleaved_2","accelerometer_y",4,"B"); - packet.addControl("coreaccel_interleaved_2","accelerometer_z",5,"B"); - packet.addControl("coreaccel_interleaved_2","ir_17",6,"B"); - packet.addControl("coreaccel_interleaved_2","ir_18",7,"B"); - packet.addControl("coreaccel_interleaved_2","ir_19",8,"B"); - packet.addControl("coreaccel_interleaved_2","ir_20",9,"B"); - packet.addControl("coreaccel_interleaved_2","ir_21",10,"B"); - packet.addControl("coreaccel_interleaved_2","ir_22",11,"B"); - packet.addControl("coreaccel_interleaved_2","ir_23",12,"B"); - packet.addControl("coreaccel_interleaved_2","ir_24",13,"B"); - packet.addControl("coreaccel_interleaved_2","ir_25",14,"B"); - packet.addControl("coreaccel_interleaved_2","ir_26",15,"B"); - packet.addControl("coreaccel_interleaved_2","ir_27",16,"B"); - packet.addControl("coreaccel_interleaved_2","ir_28",17,"B"); - packet.addControl("coreaccel_interleaved_2","ir_29",18,"B"); - packet.addControl("coreaccel_interleaved_2","ir_30",19,"B"); - packet.addControl("coreaccel_interleaved_2","ir_31",20,"B"); - packet.addControl("coreaccel_interleaved_2","ir_32",21,"B"); - this.controller.registerInputPacket(packet); + const InputReport0x3f = new HIDPacket("coreaccel_interleaved_2", 0x3f); + InputReport0x3f.addControl("coreaccel_interleaved_2", "arrow_left", 1, "B", 0x1); + InputReport0x3f.addControl("coreaccel_interleaved_2", "arrow_right", 1, "B", 0x2); + InputReport0x3f.addControl("coreaccel_interleaved_2", "arrow_down", 1, "B", 0x4); + InputReport0x3f.addControl("coreaccel_interleaved_2", "arrow_up", 1, "B", 0x8); + InputReport0x3f.addControl("coreaccel_interleaved_2", "button_plus", 1, "B", 0x10); + InputReport0x3f.addControl("coreaccel_interleaved_2", "button_1", 2, "B", 0x2); + InputReport0x3f.addControl("coreaccel_interleaved_2", "button_2", 2, "B", 0x1); + InputReport0x3f.addControl("coreaccel_interleaved_2", "button_bottom", 2, "B", 0x4); + InputReport0x3f.addControl("coreaccel_interleaved_2", "button_a", 2, "B", 0x8); + InputReport0x3f.addControl("coreaccel_interleaved_2", "button_minus", 2, "B", 0x10); + InputReport0x3f.addControl("coreaccel_interleaved_2", "button_home", 2, "B", 0x80); + InputReport0x3f.addControl("coreaccel_interleaved_2", "accelerometer_x", 3, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "accelerometer_y", 4, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "accelerometer_z", 5, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_17", 6, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_18", 7, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_19", 8, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_20", 9, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_21", 10, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_22", 11, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_23", 12, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_24", 13, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_25", 14, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_26", 15, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_27", 16, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_28", 17, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_29", 18, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_30", 19, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_31", 20, "B"); + InputReport0x3f.addControl("coreaccel_interleaved_2", "ir_32", 21, "B"); + this.controller.registerInputPacket(InputReport0x3f); } this.registerOutputPackets = function() { - packet = new HIDPacket("feedback", 0x11); - packet.addOutput("state","rumble",1,"B",0x1); - packet.addOutput("state","led_1",1,"B",0x10); - packet.addOutput("state","led_2",1,"B",0x20); - packet.addOutput("state","led_3",1,"B",0x40); - packet.addOutput("state","led_4",1,"B",0x80); - this.controller.registerOutputPacket(packet); - - packet = new HIDPacket("setreportmode", 0x12); - packet.addOutput("reportmode","continuous",1,"B",0x4); - packet.addOutput("reportmode","code",2,"B"); - this.controller.registerOutputPacket(packet); - - packet = new HIDPacket("ircamera", 0x13); - packet.addOutput("ircontrol","enabled",1,"B",0x4); - this.controller.registerOutputPacket(packet); - - packet = new HIDPacket("ircamerastate", 0x1a); - packet.addOutput("irstate","enabled",1,"B",0x4); - this.controller.registerOutputPacket(packet); + const OutputReport0x11 = new HIDPacket("feedback", 0x11); + OutputReport0x11.addOutput("state", "rumble", 1, "B", 0x1); + OutputReport0x11.addOutput("state", "led_1", 1, "B", 0x10); + OutputReport0x11.addOutput("state", "led_2", 1, "B", 0x20); + OutputReport0x11.addOutput("state", "led_3", 1, "B", 0x40); + OutputReport0x11.addOutput("state", "led_4", 1, "B", 0x80); + this.controller.registerOutputPacket(OutputReport0x11); + + const OutputReport0x12 = new HIDPacket("setreportmode", 0x12); + OutputReport0x12.addOutput("reportmode", "continuous", 1, "B", 0x4); + OutputReport0x12.addOutput("reportmode", "code", 2, "B"); + this.controller.registerOutputPacket(OutputReport0x12); + + const OutputReport0x13 = new HIDPacket("ircamera", 0x13); + OutputReport0x13.addOutput("ircontrol", "enabled", 1, "B", 0x4); + this.controller.registerOutputPacket(OutputReport0x13); + + const OutputReport0x1a = new HIDPacket("ircamerastate", 0x1a); + OutputReport0x1a.addOutput("irstate", "enabled", 1, "B", 0x4); + this.controller.registerOutputPacket(OutputReport0x1a); } // No default scalers: all controls done with callbacks anyway From 2673ddc8aab1b25590c18b4054d5d96e80f59842 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sun, 17 Mar 2024 15:18:08 +0100 Subject: [PATCH 059/124] AutoDJ: reset of UI toggle if enable is rejected due to playing decks 3/4 --- src/library/autodj/autodjprocessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 27ea9b216a3..fe632109e5b 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -403,6 +403,7 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { for (int i = 2; i < m_decks.length(); ++i) { if (m_decks[i] && m_decks[i]->isPlaying()) { // Keep the current state. + emitAutoDJStateChanged(m_eState); emit autoDJError(ADJ_DECKS_3_4_PLAYING); return ADJ_DECKS_3_4_PLAYING; } From 83bfc3866c714074aeb550907118c6df0d93a44a Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 18 Mar 2024 12:05:51 +0100 Subject: [PATCH 060/124] fix(controller): document root cause QTBUG-95677 --- res/controllers/Behringer-Extension-scripts.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index 41f49b968da..cc912e64d3a 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -239,13 +239,12 @@ isEnabled: function() { return this.id !== 0; }, start: function() { this.reset(); - const timer = this; - this.id = engine.beginTimer(this.timeout, () => { - if (timer.oneShot) { - timer.disable(); + this.id = engine.beginTimer(this.timeout, function() { + if (this.oneShot) { + this.disable(); } - timer.action.call(timer.owner); - }, this.oneShot); + this.action.call(this.owner); + }.bind(this), this.oneShot); // .bind(this) is required instead of arrow function for Qt < 6.2.4 due to QTBUG-95677 }, reset: function() { if (this.isEnabled()) { From c1e6624b013cf696ee9d1f6722197de57439e914 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 18 Mar 2024 17:08:46 +0100 Subject: [PATCH 061/124] (fix) use track `info` for preview deck labels = artist+title / file info --- res/skins/Deere/preview_deck.xml | 2 +- res/skins/LateNight/decks/preview_deck.xml | 2 +- res/skins/Shade/preview_deck.xml | 2 +- res/skins/Tango/decks/preview_deck.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/skins/Deere/preview_deck.xml b/res/skins/Deere/preview_deck.xml index b12ea427f23..1806cb669f5 100644 --- a/res/skins/Deere/preview_deck.xml +++ b/res/skins/Deere/preview_deck.xml @@ -33,7 +33,7 @@ text me,min - titleInfo + info right diff --git a/res/skins/LateNight/decks/preview_deck.xml b/res/skins/LateNight/decks/preview_deck.xml index a5773193a5b..0a0841f3109 100644 --- a/res/skins/LateNight/decks/preview_deck.xml +++ b/res/skins/LateNight/decks/preview_deck.xml @@ -42,7 +42,7 @@ 0me,20f right - titleInfo + info PreviewBPM diff --git a/res/skins/Shade/preview_deck.xml b/res/skins/Shade/preview_deck.xml index 5588b3ad84b..aa132122d68 100644 --- a/res/skins/Shade/preview_deck.xml +++ b/res/skins/Shade/preview_deck.xml @@ -53,7 +53,7 @@ padding-top: 2px;} [PreviewDeck1] - titleInfo + info me,min right diff --git a/res/skins/Tango/decks/preview_deck.xml b/res/skins/Tango/decks/preview_deck.xml index 9afe526893d..f31cd98321f 100644 --- a/res/skins/Tango/decks/preview_deck.xml +++ b/res/skins/Tango/decks/preview_deck.xml @@ -61,7 +61,7 @@ Variables: text 1me,17f - titleInfo + info right From 132bf2b6d73886efca45f9a4452cfa7cac91677b Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 19 Mar 2024 08:27:31 +0100 Subject: [PATCH 062/124] Revert "style(controller): fix code style issue" This reverts commit ea078598bbac4027114a654c563246c82889a83f. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fba0236afa8..aac3c7fd05c 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The Mixxx team uses [Github Issues][issues] to manage Mixxx development. Have a bug or feature request? [File a bug on Github][fileabug]. Want to get involved in Mixxx development? Assign yourself a bug from the [easy -bug list][easybugs] and get started! +bug list][easybugs] and get started! Read [CONTRIBUTING](CONTRIBUTING.md) for more information. ## Building Mixxx From 637e34ecf6ed2df93be622b5e99ff45d056212a9 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 20 Mar 2024 01:50:44 +0100 Subject: [PATCH 063/124] SoundDevice: Update comments on DAZ/FTZ for Wasm --- src/soundio/sounddevicenetwork.cpp | 6 ++++++ src/soundio/sounddeviceportaudio.cpp | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/soundio/sounddevicenetwork.cpp b/src/soundio/sounddevicenetwork.cpp index e4b564895c4..8cb975fa68e 100644 --- a/src/soundio/sounddevicenetwork.cpp +++ b/src/soundio/sounddevicenetwork.cpp @@ -442,9 +442,15 @@ void SoundDeviceNetwork::callbackProcessClkRef() { if (!m_denormals) { m_denormals = true; + // This disables the denormals calculations, to avoid a // performance penalty of ~20 // https://github.com/mixxxdj/mixxx/issues/7747 + + // On Emscripten (WebAssembly) denormals-as-zero/flush-as-zero is not + // configurable, for discussion and links see + // https://github.com/mixxxdj/mixxx/pull/12917 + #if defined(__SSE__) && !defined(__EMSCRIPTEN__) if (!_MM_GET_DENORMALS_ZERO_MODE()) { qDebug() << "SSE: Enabling denormals to zero mode"; diff --git a/src/soundio/sounddeviceportaudio.cpp b/src/soundio/sounddeviceportaudio.cpp index 34ebbd601e9..c23c54b916e 100644 --- a/src/soundio/sounddeviceportaudio.cpp +++ b/src/soundio/sounddeviceportaudio.cpp @@ -906,10 +906,15 @@ int SoundDevicePortAudio::callbackProcessClkRef( #endif m_bSetThreadPriority = true; -#if defined(__SSE__) && !defined(__EMSCRIPTEN__) // This disables the denormals calculations, to avoid a // performance penalty of ~20 // https://github.com/mixxxdj/mixxx/issues/7747 + + // On Emscripten (WebAssembly) denormals-as-zero/flush-as-zero is not + // configurable, for discussion and links see + // https://github.com/mixxxdj/mixxx/pull/12917 + +#if defined(__SSE__) && !defined(__EMSCRIPTEN__) if (!_MM_GET_DENORMALS_ZERO_MODE()) { qDebug() << "SSE: Enabling denormals to zero mode"; _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); From 81d6e0317a83eb4c632064d15c13ef19355b23e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 20 Mar 2024 09:58:20 +0100 Subject: [PATCH 064/124] Improve capitalizations in CHANGELOG Co-authored-by: JoergAtGithub <64457745+JoergAtGithub@users.noreply.github.com> --- CHANGELOG.md | 8 ++++---- res/linux/org.mixxx.Mixxx.metainfo.xml | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f4c4c2826..5d688734f26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * Pioneer DDJ-FLX4: Add waveform zoom and other mapping improvements [#12896](https://github.com/mixxxdj/mixxx/pull/12896) [#12842](https://github.com/mixxxdj/mixxx/pull/12842) -* Traktor F1: Fixes for hid-parser and related script [#12876](https://github.com/mixxxdj/mixxx/pull/12876) +* Traktor Kontrol F1: Fixes for hid-parser and related script [#12876](https://github.com/mixxxdj/mixxx/pull/12876) * Traktor S3: Fix mapping crash on macOS [#12840](https://github.com/mixxxdj/mixxx/pull/12840) ### Target Support @@ -22,8 +22,8 @@ ### Miscellaneous * Remove unnecessary unpolish operation of the style, before polish the new style [#12445](https://github.com/mixxxdj/mixxx/pull/12445) -* Developer Tools: initially sort controls by group name, ascending [#12884](https://github.com/mixxxdj/mixxx/pull/12884) -* History: show track count and duration in sidebar [#12811](https://github.com/mixxxdj/mixxx/pull/12811) +* Developer Tools: Initially sort controls by group name, ascending [#12884](https://github.com/mixxxdj/mixxx/pull/12884) +* History: Show track count and duration in sidebar [#12811](https://github.com/mixxxdj/mixxx/pull/12811) * Prevent removing tracks from locked playlists [#12927](https://github.com/mixxxdj/mixxx/pull/12927) ## [2.4.0](https://github.com/mixxxdj/mixxx/milestone/15?closed=1) (2024-02-16) @@ -521,7 +521,7 @@ [#11975](https://github.com/mixxxdj/mixxx/pull/11975) [#11957](https://github.com/mixxxdj/mixxx/issues/11957) * Fix 500ms blocking of the whole event loop, when holding mouse down on title bar on Windows [#12359](https://github.com/mixxxdj/mixxx/pull/12359) [#12358](https://github.com/mixxxdj/mixxx/issues/12358) [#12433](https://github.com/mixxxdj/mixxx/pull/12433) [#12458](https://github.com/mixxxdj/mixxx/pull/12458) -* change SKIN_WARNING to show the skin file and line first, then c++ context [#12253](https://github.com/mixxxdj/mixxx/pull/12253) +* Change SKIN_WARNING to show the skin file and line first, then c++ context [#12253](https://github.com/mixxxdj/mixxx/pull/12253) * Fix style of selected QComboBox items on Windows [#12339](https://github.com/mixxxdj/mixxx/pull/12339) [#12323](https://github.com/mixxxdj/mixxx/issues/12323) * Fix reading the Spinny cover on Windows [#12103](https://github.com/mixxxdj/mixxx/pull/12103) [#11131](https://github.com/mixxxdj/mixxx/issues/11131) * Fix inconsistent/wrong musical keys in the UI [#12051](https://github.com/mixxxdj/mixxx/pull/12051) [#12044](https://github.com/mixxxdj/mixxx/issues/12044) diff --git a/res/linux/org.mixxx.Mixxx.metainfo.xml b/res/linux/org.mixxx.Mixxx.metainfo.xml index e33cb3970ee..cba1a1d08e1 100644 --- a/res/linux/org.mixxx.Mixxx.metainfo.xml +++ b/res/linux/org.mixxx.Mixxx.metainfo.xml @@ -96,7 +96,7 @@ Do not edit it manually. --> - +

    Controller Mappings @@ -112,7 +112,7 @@ #12842

  • - Traktor F1: Fixes for hid-parser and related script + Traktor Kontrol F1: Fixes for hid-parser and related script #12876
  • @@ -141,11 +141,11 @@ #12445
  • - Developer Tools: initially sort controls by group name, ascending + Developer Tools: Initially sort controls by group name, ascending #12884
  • - History: show track count and duration in sidebar + History: Show track count and duration in sidebar #12811
  • @@ -1442,7 +1442,7 @@ #12458
  • - change SKIN_WARNING to show the skin file and line first, then c++ context + Change SKIN_WARNING to show the skin file and line first, then c++ context #12253
  • From 764dc005052287d7510933183cd511a56c043dab Mon Sep 17 00:00:00 2001 From: Joerg Date: Thu, 21 Mar 2024 18:50:48 +0100 Subject: [PATCH 065/124] Applied Pre-Commit formating changes --- res/controllers/Pioneer-CDJ-HID.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/res/controllers/Pioneer-CDJ-HID.js b/res/controllers/Pioneer-CDJ-HID.js index 2c5513bec7b..a0c8bff449e 100644 --- a/res/controllers/Pioneer-CDJ-HID.js +++ b/res/controllers/Pioneer-CDJ-HID.js @@ -143,7 +143,7 @@ function PioneerCDJController() { // bytes to get it working. Need to add a response packet // to input packets as well, if we receive acknowledgement packet = new HIDPacket("request_hid_mode", 0x1); - packet.addOutput("hid","mode",0,"B",1); + packet.addOutput("hid", "mode", 0, "B", 1); this.controller.registerOutputPacket(packet); // Control packet for screen text control @@ -156,10 +156,10 @@ function PioneerCDJController() { var offset = 2; // Register 2 bytes for each letter, I expect UTF-8 output packet = new HIDPacket("display", 0x2, undefined, [0x2]); - for (var i=0;i Date: Thu, 23 Feb 2023 22:38:44 +0000 Subject: [PATCH 066/124] feat(Controller): add support for settings This commit adds basics for support of controller settings. --- CMakeLists.txt | 1 + src/controllers/dlgprefcontroller.cpp | 18 ++ src/controllers/dlgprefcontrollerdlg.ui | 238 +++++++++--------- .../legacyhidcontrollermappingfilehandler.cpp | 1 + src/controllers/legacycontrollermapping.h | 25 ++ .../legacycontrollermappingfilehandler.cpp | 29 +++ .../legacycontrollermappingfilehandler.h | 3 + src/controllers/legacycontrollersettings.cpp | 93 +++++++ src/controllers/legacycontrollersettings.h | 117 +++++++++ .../legacycontrollersettingsfactory.h | 77 ++++++ 10 files changed, 485 insertions(+), 117 deletions(-) create mode 100644 src/controllers/legacycontrollersettings.cpp create mode 100644 src/controllers/legacycontrollersettings.h create mode 100644 src/controllers/legacycontrollersettingsfactory.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c67775d256..5e7e40f500b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -731,6 +731,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/controllerlearningeventfilter.cpp src/controllers/controllermanager.cpp src/controllers/controllermappinginfo.cpp + src/controllers/legacycontrollersettings.cpp src/controllers/controllermappinginfoenumerator.cpp src/controllers/controllermappingtablemodel.cpp src/controllers/controlleroutputmappingtablemodel.cpp diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 2c6f09dee85..70e96200ce3 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -614,6 +614,8 @@ void DlgPrefController::slotMappingSelected(int chosenIndex) { } enableWizardAndIOTabs(false); } + + m_ui.groupBoxSettings->setVisible(false); } else { // User picked a mapping m_ui.chkEnabledDevice->setEnabled(true); @@ -823,6 +825,22 @@ void DlgPrefController::slotShowMapping(std::shared_ptr m_ui.labelLoadedMappingSupportLinks->setText(mappingSupportLinks(pMapping)); m_ui.labelLoadedMappingScriptFileLinks->setText(mappingFileLinks(pMapping)); + if (pMapping) { + const QList>& + settings = pMapping->getSettings(); + + qDeleteAll(m_ui.groupBoxSettings->findChildren("", Qt::FindDirectChildrenOnly)); + + foreach (std::shared_ptr setting, settings) { + QWidget* settingWidget = setting->buildWidget(this); + // connect(settingWidget, &AbstractLegacyControllerSetting::changed, + // this, [this] { setDirty(true); }); + m_ui.groupBoxSettings->layout()->addWidget(settingWidget); + } + + m_ui.groupBoxSettings->setVisible(!settings.isEmpty()); + } + // We mutate this mapping 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/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index 1d391eb06aa..4d971609ea1 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -6,8 +6,8 @@ 0 0 - 507 - 437 + 837 + 945 @@ -29,110 +29,8 @@ Controller Setup - - - 0 - 0 - - - - - - - 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 Mapping: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - comboBoxMapping - - - - + + @@ -191,6 +89,53 @@ + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Load Mapping: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + comboBoxMapping + + + + + + + true + + + + 0 + 0 + + + + + + + (device category goes here) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -207,6 +152,68 @@ + + + + Click to start the Controller Learning wizard. + + + + + + Learning Wizard (MIDI Only) + + + false + + + false + + + + + + + true + + + + 0 + 0 + + + + + 14 + 75 + true + + + + Controller Name + + + + + + + Enabled + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -421,18 +428,16 @@ - - - - Qt::Vertical + + + + Mapping settings - - - 20 - 40 - + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + + @@ -525,7 +530,6 @@ - Output Mappings diff --git a/src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp b/src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp index 1b715ea8cd6..08b4a66aa6b 100644 --- a/src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp +++ b/src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp @@ -24,6 +24,7 @@ LegacyHidControllerMappingFileHandler::load(const QDomElement& root, auto pMapping = std::make_shared(); pMapping->setFilePath(filePath); parseMappingInfo(root, pMapping); + parseMappingSettings(root, pMapping); addScriptFilesToMapping(controller, pMapping, systemMappingsPath); return pMapping; } diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index ca0e369b96c..21080bee88b 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -1,13 +1,18 @@ #pragma once +#include + #include #include +#include #include #include #include #include +#include #include +#include "controllers/legacycontrollersettings.h" #include "defs_urls.h" /// This class represents a controller mapping, containing the data elements that @@ -50,10 +55,29 @@ class LegacyControllerMapping { setDirty(true); } + /// Adds a setting option to the list of setting option for this mapping. + /// The option added must be a valid option. + /// @param option The option to add + void addSetting(std::shared_ptr option) { + // if (m_settings.contains(option->variableName())){ + // qWarning() << QString("Mapping setting duplication detected. + // Keeping the first version of '%1'.").arg(option->variableName()); + // return; + // } + VERIFY_OR_DEBUG_ASSERT(option->valid()) { + return; + } + m_settings.append(option); + } + const QList& getScriptFiles() const { return m_scripts; } + const QList>& getSettings() const { + return m_settings; + } + inline void setDirty(bool bDirty) { m_bDirty = bDirty; } @@ -191,5 +215,6 @@ class LegacyControllerMapping { QString m_schemaVersion; QString m_mixxxVersion; + QList> m_settings; QList m_scripts; }; diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 754c972ba78..fd821ce639d 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -108,6 +108,35 @@ void LegacyControllerMappingFileHandler::parseMappingInfo( mapping->setWikiLink(wiki.isNull() ? "" : wiki.text()); } +void LegacyControllerMappingFileHandler::parseMappingSettings( + const QDomElement& root, std::shared_ptr mapping) const { + if (root.isNull() || !mapping) { + return; + } + + QDomElement settings = root.firstChildElement("settings"); + if (settings.isNull()) { + return; + } + + for (QDomElement option = settings.firstChildElement("option"); + !option.isNull(); + option = option.nextSiblingElement("option")) { + AbstractLegacyControllerSetting* setting = LegacyControllerSettingBuilder::build(option); + if (setting == nullptr) { + qDebug() << "Could not parse the unknown controller setting. Ignoring it."; + continue; + } + if (!setting->valid()) { + qDebug() << "The parsed setting appears to be invalid. Discarding it."; + delete setting; + continue; + } + + mapping->addSetting(std::shared_ptr(setting)); + } +} + QDomElement LegacyControllerMappingFileHandler::getControllerNode( const QDomElement& root) { if (root.isNull()) { diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index c7bc6377b6d..ee23268ca74 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -41,6 +41,9 @@ class LegacyControllerMappingFileHandler { void parseMappingInfo(const QDomElement& root, std::shared_ptr mapping) const; + void parseMappingSettings(const QDomElement& root, + std::shared_ptr mapping) const; + /// Adds script files from XML to the LegacyControllerMapping. /// /// This function parses the supplied QDomElement structure, finds the diff --git a/src/controllers/legacycontrollersettings.cpp b/src/controllers/legacycontrollersettings.cpp new file mode 100644 index 00000000000..54e059f87bb --- /dev/null +++ b/src/controllers/legacycontrollersettings.cpp @@ -0,0 +1,93 @@ +#include "controllers/legacycontrollersettings.h" + +#include "moc_legacycontrollersettings.cpp" + +LegacyControllerSettingBuilder* LegacyControllerSettingBuilder::__self = nullptr; + +LegacyControllerSettingBuilder* LegacyControllerSettingBuilder::instance() { + if (__self == nullptr) + __self = new LegacyControllerSettingBuilder(); + + return __self; +} + +QWidget* LegacyControllerIntegerSetting::buildWidget(QWidget* parent) { + QWidget* root = new QWidget(parent); + root->setLayout(new QHBoxLayout()); + root->layout()->setContentsMargins(0, 0, 0, 0); + + QLabel* labelWidget = new QLabel(root); + labelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + labelWidget->setText(label()); + + if (!description().isEmpty()) { + auto layout = new QVBoxLayout(root); + + root->layout()->setContentsMargins(0, 0, 0, 0); + + auto descriptionWidget = new QLabel(root); + descriptionWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + descriptionWidget->setText(description()); + QFont descriptionFont; + descriptionFont.setPointSize(8); + descriptionFont.setItalic(true); + descriptionWidget->setFont(descriptionFont); + + layout->addWidget(labelWidget); + layout->addWidget(descriptionWidget); + root->layout()->addItem(layout); + } else { + root->layout()->addWidget(labelWidget); + } + + QSpinBox* spinBox = new QSpinBox(root); + spinBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + root->layout()->addWidget(spinBox); + + spinBox->setRange(this->m_minValue, this->m_maxValue); + spinBox->setSingleStep(this->m_stepValue); + spinBox->setValue(this->m_currentValue); + + connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, [this] { + emit changed(); + }); + + static_cast(root->layout())->setStretch(0, 3); + static_cast(root->layout())->setStretch(1, 1); + return root; +} + +AbstractLegacyControllerSetting* LegacyControllerIntegerSetting::createFrom( + const QDomElement& element) { + int defaultValue, minValue, maxValue, stepValue; + + bool isOk = false; + minValue = element.attribute("min").toInt(&isOk); + if (!isOk) { + minValue = std::numeric_limits::min(); + } + maxValue = element.attribute("max").toInt(&isOk); + if (!isOk) { + maxValue = std::numeric_limits::max(); + } + stepValue = element.attribute("step").toInt(&isOk); + if (!isOk) { + stepValue = 1; + } + defaultValue = element.attribute("default").toInt(&isOk); + if (!isOk) { + defaultValue = 0; + } + + return new LegacyControllerIntegerSetting( + element, defaultValue, defaultValue, minValue, maxValue, stepValue); +} + +bool LegacyControllerIntegerSetting::match(const QDomElement& element) { + return element.hasAttribute("type") && + QString::compare(element.attribute("type"), + "integer", + Qt::CaseInsensitive) == 0; +}; + +REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerIntegerSetting); diff --git a/src/controllers/legacycontrollersettings.h b/src/controllers/legacycontrollersettings.h new file mode 100644 index 00000000000..3c76939c142 --- /dev/null +++ b/src/controllers/legacycontrollersettings.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controllers/legacycontrollersettingsfactory.h" + +/// @brief The abstract controller setting. Any type of setting will have to +/// implement this base class +class AbstractLegacyControllerSetting : public QObject { + Q_OBJECT + public: + virtual ~AbstractLegacyControllerSetting() = default; + + /// @brief Build a widget that can be used to interact with this setting. It + /// shouldn't mutate the state of the setting. + /// @param parent The parent widget for which this widget is being created. + /// The parent widget will own the newly created widget + /// @return a new widget + virtual QWidget* buildWidget(QWidget* parent) = 0; + + // virtual void reset() const = 0; + + /// @brief Whether of not this setting definition is valid. Validity scope + /// includes things like default value within range' for example. + /// @return true if valid + virtual inline bool valid() const { + return !m_variableName.isEmpty(); + } + + /// @brief The variable name as perceived within the mapping definition. + /// @return a string + inline QString variableName() const { + return m_variableName; + } + + /// @brief The user-friendly label to be display in the UI + /// @return a string + inline const QString& label() const { + return m_label; + } + + /// @brief A description of what this setting does + /// @return a string + inline const QString& description() const { + return m_description; + } + + protected: + AbstractLegacyControllerSetting(QString variableName, QString label, QString description) + : m_variableName(variableName), m_label(label), m_description(description) { + } + AbstractLegacyControllerSetting(const QDomElement& element) { + m_variableName = element.attribute("variable").trimmed(); + m_label = element.attribute("label", m_variableName).trimmed(); + + QDomElement description = element.firstChildElement("description"); + if (description.isNull()) { + m_description = QString(); + return; + } + m_description = description.text().trimmed(); + } + + signals: + /// This signal will be emitted when the user has interacted with the + /// setting and changed its value + void changed(); + + private: + QString m_variableName; + QString m_label; + QString m_description; +}; + +class LegacyControllerIntegerSetting + : public LegacyControllerSettingFactory, + public AbstractLegacyControllerSetting { + public: + LegacyControllerIntegerSetting(const QDomElement& element, + int currentValue, + int defaultValue, + int minValue, + int maxValue, + int stepValue) + : AbstractLegacyControllerSetting(element), + m_currentValue(currentValue), + m_defaultValue(defaultValue), + m_minValue(minValue), + m_maxValue(maxValue), + m_stepValue(stepValue) { + } + + virtual ~LegacyControllerIntegerSetting() = default; + + QWidget* buildWidget(QWidget* parent); + + static AbstractLegacyControllerSetting* createFrom(const QDomElement& element); + static bool match(const QDomElement& element); + + private: + int m_currentValue; + int m_defaultValue; + int m_minValue; + int m_maxValue; + int m_stepValue; +}; diff --git a/src/controllers/legacycontrollersettingsfactory.h b/src/controllers/legacycontrollersettingsfactory.h new file mode 100644 index 00000000000..3b2a0b9ecbc --- /dev/null +++ b/src/controllers/legacycontrollersettingsfactory.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controllers/legacycontrollersettingsfactory.h" + +class AbstractLegacyControllerSetting; + +/// @brief This class defines an interface that a controller setting type must +/// implement so it can be used properly by the builder +/// @tparam T The class implementing this interface +template +class LegacyControllerSettingFactory { + inline static LegacyControllerSettingFactory* createFrom(const QDomElement& element) { + return T::createFrom(element); + } + inline static bool match(const QDomElement& element) { + return T::match(element); + } +}; + +/// @brief This class is used to dynamically instantiate a controller setting based on its type +class LegacyControllerSettingBuilder { + public: + static LegacyControllerSettingBuilder* instance(); + + /// @brief Register a new type of setting. This method is used by the + /// REGISTER macro, it shouldn't be used directly + /// @param match the match function of the new setting + /// @param creator the creator function of the new setting + /// @return Always true + inline bool registerType(bool (*match)(const QDomElement&), + AbstractLegacyControllerSetting* (*creator)(const QDomElement&)) { + m_supportedSettings.append(std::make_tuple(match, creator)); + return true; + } + + /// @brief instantiate a new setting from a an XML definition if any valid + /// setting was found. The caller is the owner of the instance + /// @param element The XML element to parse to build the new setting + /// @return an instance if a a supported setting has been found, null + /// otherwise + static AbstractLegacyControllerSetting* build(const QDomElement& element) { + foreach (auto settingType, instance()->m_supportedSettings) { + if (std::get<0>(settingType)(element)) { + return std::get<1>(settingType)(element); + } + } + + return nullptr; + } + + private: + LegacyControllerSettingBuilder() = default; + + QList> + m_supportedSettings; + + static LegacyControllerSettingBuilder* __self; +}; + +#define REGISTER_LEGACY_CONTROLLER_SETTING(S) \ + bool kSettingRegistered_##T = \ + LegacyControllerSettingBuilder::instance()->registerType( \ + S::match, S::createFrom) From 2c6f071933f5e858cf52f192b022c548daaa72bd Mon Sep 17 00:00:00 2001 From: Antoine C Date: Fri, 24 Feb 2023 11:20:20 +0000 Subject: [PATCH 067/124] feat(Controller): basic concept of setting injection in JS engine --- src/controllers/controller.cpp | 6 ++++++ src/controllers/legacycontrollersettings.h | 7 +++++++ .../scripting/legacy/controllerscriptenginelegacy.cpp | 9 +++++++++ .../scripting/legacy/controllerscriptenginelegacy.h | 5 +++++ 4 files changed, 27 insertions(+) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index c9cb45c7e5e..63b0c67e626 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -76,6 +76,12 @@ bool Controller::applyMapping() { } m_pScriptEngineLegacy->setScriptFiles(scriptFiles); + + const QList>& settings = + pMapping->getSettings(); + if (!settings.isEmpty()) { + m_pScriptEngineLegacy->setSettings(settings); + } return m_pScriptEngineLegacy->initialize(); } diff --git a/src/controllers/legacycontrollersettings.h b/src/controllers/legacycontrollersettings.h index 3c76939c142..5c9a56ed411 100644 --- a/src/controllers/legacycontrollersettings.h +++ b/src/controllers/legacycontrollersettings.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -29,6 +30,8 @@ class AbstractLegacyControllerSetting : public QObject { /// @return a new widget virtual QWidget* buildWidget(QWidget* parent) = 0; + virtual QJSValue value() const = 0; + // virtual void reset() const = 0; /// @brief Whether of not this setting definition is valid. Validity scope @@ -105,6 +108,10 @@ class LegacyControllerIntegerSetting QWidget* buildWidget(QWidget* parent); + inline QJSValue value() const { + return QJSValue(m_currentValue); + } + static AbstractLegacyControllerSetting* createFrom(const QDomElement& element); static bool match(const QDomElement& element); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index a5886204ec6..f880b169462 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -123,6 +123,15 @@ bool ControllerScriptEngineLegacy::initialize() { QJSValue engineGlobalObject = m_pJSEngine->globalObject(); ControllerScriptInterfaceLegacy* legacyScriptInterface = new ControllerScriptInterfaceLegacy(this, m_logger); + + // TODO(acolombier): move into a dedicated protected method + QJSValue settingsObject = m_pJSEngine->newObject(); + foreach (const std::shared_ptr& setting, m_settings) { + settingsObject.setProperty(setting->variableName(), setting->value()); + } + engineGlobalObject.setProperty( + "mixxxControllerSettings", settingsObject); + engineGlobalObject.setProperty( "engine", m_pJSEngine->newQObject(legacyScriptInterface)); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 70dea6a392e..b71c366cbd3 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -28,6 +28,10 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { public slots: void setScriptFiles(const QList& scripts); + inline void setSettings( + const QList>& settings) { + m_settings = settings; + } private: bool evaluateScriptFile(const QFileInfo& scriptFile); @@ -44,6 +48,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QList m_incomingDataFunctions; QHash m_scriptWrappedFunctionCache; QList m_scriptFiles; + QList> m_settings; QFileSystemWatcher m_fileWatcher; From 69a57d42a8012fa4ce1456a2431cdf28328d6d64 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 25 Feb 2023 00:04:18 +0000 Subject: [PATCH 068/124] feat(Controller): settings persistence and HID support --- CMakeLists.txt | 2 + src/controllers/controller.cpp | 6 +- src/controllers/controllermanager.cpp | 1 + src/controllers/dlgprefcontroller.cpp | 34 ++- src/controllers/legacycontrollermapping.cpp | 47 ++++ src/controllers/legacycontrollermapping.h | 14 +- .../legacycontrollermappingfilehandler.cpp | 10 +- src/controllers/legacycontrollersettings.cpp | 258 ++++++++++++++++-- src/controllers/legacycontrollersettings.h | 250 +++++++++++++++-- .../legacycontrollersettingsfactory.h | 12 +- .../legacy/controllerscriptenginelegacy.cpp | 5 +- .../legacy/controllerscriptenginelegacy.h | 8 +- 12 files changed, 571 insertions(+), 76 deletions(-) create mode 100644 src/controllers/legacycontrollermapping.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e7e40f500b..104ae222d31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -736,6 +736,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/controllermappingtablemodel.cpp src/controllers/controlleroutputmappingtablemodel.cpp src/controllers/controlpickermenu.cpp + src/controllers/legacycontrollermappingfilehandler.cpp + src/controllers/legacycontrollermapping.cpp src/controllers/delegates/controldelegate.cpp src/controllers/delegates/midibytedelegate.cpp src/controllers/delegates/midichanneldelegate.cpp diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 63b0c67e626..e37933858d1 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -77,11 +77,7 @@ bool Controller::applyMapping() { m_pScriptEngineLegacy->setScriptFiles(scriptFiles); - const QList>& settings = - pMapping->getSettings(); - if (!settings.isEmpty()) { - m_pScriptEngineLegacy->setSettings(settings); - } + m_pScriptEngineLegacy->setSettings(pMapping->getSettings()); return m_pScriptEngineLegacy->initialize(); } diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index 52501776ffb..a2562ed9484 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -282,6 +282,7 @@ void ControllerManager::slotSetUpDevices() { if (!pMapping) { continue; } + pMapping->restoreSettings(mappingFile, m_pConfig, pController->getName()); // This runs on the main thread but LegacyControllerMapping is not thread safe, so clone it. pController->setMapping(pMapping->clone()); diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 70e96200ce3..28a809fa5f3 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -22,6 +22,7 @@ #include "preferences/usersettings.h" #include "util/desktophelper.h" #include "util/string.h" +#include "util/parented_ptr.h" namespace { const QString kMappingExt(".midi.xml"); @@ -533,7 +534,7 @@ void DlgPrefController::slotApply() { applyMappingChanges(); // If no changes were made, do nothing - if (!(isDirty() || (m_pMapping && m_pMapping->isDirty()))) { + if (!(isDirty() || (m_pMapping && (m_pMapping->isDirty())))) { return; } @@ -558,8 +559,18 @@ void DlgPrefController::slotApply() { } QString mappingPath = mappingPathFromIndex(m_ui.comboBoxMapping->currentIndex()); + QFileInfo mappingFileInfo(mappingPath); + + m_pMapping->saveSettings(mappingFileInfo, m_pConfig, m_pController->getName()); + m_pMapping = LegacyControllerMappingFileHandler::loadMapping( - QFileInfo(mappingPath), QDir(resourceMappingsPath(m_pConfig))); + mappingFileInfo, QDir(resourceMappingsPath(m_pConfig))); + + if (m_pMapping) { + m_pMapping->restoreSettings(mappingFileInfo, m_pConfig, m_pController->getName()); + } + + slotShowMapping(m_pMapping); // Load the resulting mapping (which has been mutated by the input/output // table models). The controller clones the mapping so we aren't touching @@ -645,12 +656,14 @@ void DlgPrefController::slotMappingSelected(int chosenIndex) { } } + QFileInfo mappingFileInfo(mappingPath); std::shared_ptr pMapping = LegacyControllerMappingFileHandler::loadMapping( - QFileInfo(mappingPath), QDir(resourceMappingsPath(m_pConfig))); + mappingFileInfo, QDir(resourceMappingsPath(m_pConfig))); if (pMapping) { DEBUG_ASSERT(!pMapping->isDirty()); + pMapping->restoreSettings(mappingFileInfo, m_pConfig, m_pController->getName()); } if (previousMappingSaved) { @@ -826,16 +839,17 @@ void DlgPrefController::slotShowMapping(std::shared_ptr m_ui.labelLoadedMappingScriptFileLinks->setText(mappingFileLinks(pMapping)); if (pMapping) { - const QList>& - settings = pMapping->getSettings(); + auto settings = pMapping->getSettings(); qDeleteAll(m_ui.groupBoxSettings->findChildren("", Qt::FindDirectChildrenOnly)); - foreach (std::shared_ptr setting, settings) { - QWidget* settingWidget = setting->buildWidget(this); - // connect(settingWidget, &AbstractLegacyControllerSetting::changed, - // this, [this] { setDirty(true); }); - m_ui.groupBoxSettings->layout()->addWidget(settingWidget); + for (auto setting : settings) { + parented_ptr pSettingWidget(setting->buildWidget(m_ui.groupBoxSettings)); + connect(setting.get(), + &AbstractLegacyControllerSetting::changed, + this, + [this] { setDirty(true); }); + m_ui.groupBoxSettings->layout()->addWidget(pSettingWidget); } m_ui.groupBoxSettings->setVisible(!settings.isEmpty()); diff --git a/src/controllers/legacycontrollermapping.cpp b/src/controllers/legacycontrollermapping.cpp new file mode 100644 index 00000000000..7bfb8ce7595 --- /dev/null +++ b/src/controllers/legacycontrollermapping.cpp @@ -0,0 +1,47 @@ +#include "controllers/legacycontrollermapping.h" + +void LegacyControllerMapping::restoreSettings(const QFileInfo& mappingFile, + UserSettingsPointer pConfig, + const QString& controllerName) { + QString controllerKey = QString(CONTROLLER_SETTINGS_PREFERENCE_GROUP_KEY) + .arg(controllerName) + .arg(mappingFile.absoluteFilePath()); + for (auto setting : getSettings()) { + bool ok; + QString value = pConfig->getValueString(ConfigKey(controllerKey, setting->variableName())); + setting->parse(value, &ok); + if (!ok) { + qWarning() << "The setting" << setting->variableName() + << "for the mapping" << mappingFile.absoluteFilePath() + << "could not be restore. Removing"; + pConfig->remove(ConfigKey(controllerKey, setting->variableName())); + } + } + // TODO (acolombier): If there are other settings that aren't defined + // anymore, they should be removed. +} + +void LegacyControllerMapping::saveSettings(const QFileInfo& mappingFile, + UserSettingsPointer pConfig, + const QString& controllerName) { + QString controllerKey = QString(CONTROLLER_SETTINGS_PREFERENCE_GROUP_KEY) + .arg(controllerName) + .arg(mappingFile.absoluteFilePath()); + for (auto setting : getSettings()) { + if (!setting->isDirty()) { + continue; + } + setting->save(); + if (!setting->valid()) { + qWarning() << "Setting" << setting->variableName() + << "for controller" << controllerName + << "is invalid. Its value will not be saved."; + continue; + } + if (setting->isDefault()) { + pConfig->remove(ConfigKey(controllerKey, setting->variableName())); + } else { + pConfig->set(ConfigKey(controllerKey, setting->variableName()), setting->stringify()); + } + } +} diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 21080bee88b..158d70b04db 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -11,16 +11,21 @@ #include #include #include +#include #include "controllers/legacycontrollersettings.h" #include "defs_urls.h" +#include "preferences/usersettings.h" + +// TODO (acolombier) is it okay to keep it as it? Or shall we generate a UUID from that pair? +#define CONTROLLER_SETTINGS_PREFERENCE_GROUP_KEY "[ControllerSettings_%1_%2]" /// This class represents a controller mapping, containing the data elements that /// make it up. class LegacyControllerMapping { public: LegacyControllerMapping() - : m_bDirty(false) { + : m_bDirty(false), m_settings() { } virtual ~LegacyControllerMapping() = default; @@ -198,6 +203,13 @@ class LegacyControllerMapping { virtual bool isMappable() const = 0; + void restoreSettings(const QFileInfo& mappingFile, + UserSettingsPointer pConfig, + const QString& controllerName); + void saveSettings(const QFileInfo& mappingFile, + UserSettingsPointer pConfig, + const QString& controllerName); + // Optional list of controller device match details QList> m_productMatches; diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index fd821ce639d..9a8509bdcd3 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -122,18 +122,18 @@ void LegacyControllerMappingFileHandler::parseMappingSettings( for (QDomElement option = settings.firstChildElement("option"); !option.isNull(); option = option.nextSiblingElement("option")) { - AbstractLegacyControllerSetting* setting = LegacyControllerSettingBuilder::build(option); - if (setting == nullptr) { + std::shared_ptr pSetting( + LegacyControllerSettingBuilder::build(option)); + if (pSetting.get() == nullptr) { qDebug() << "Could not parse the unknown controller setting. Ignoring it."; continue; } - if (!setting->valid()) { + if (!pSetting->valid()) { qDebug() << "The parsed setting appears to be invalid. Discarding it."; - delete setting; continue; } - mapping->addSetting(std::shared_ptr(setting)); + mapping->addSetting(pSetting); } } diff --git a/src/controllers/legacycontrollersettings.cpp b/src/controllers/legacycontrollersettings.cpp index 54e059f87bb..76cf1013f42 100644 --- a/src/controllers/legacycontrollersettings.cpp +++ b/src/controllers/legacycontrollersettings.cpp @@ -1,6 +1,10 @@ #include "controllers/legacycontrollersettings.h" #include "moc_legacycontrollersettings.cpp" +#include + +#include +#include LegacyControllerSettingBuilder* LegacyControllerSettingBuilder::__self = nullptr; @@ -11,7 +15,7 @@ LegacyControllerSettingBuilder* LegacyControllerSettingBuilder::instance() { return __self; } -QWidget* LegacyControllerIntegerSetting::buildWidget(QWidget* parent) { +QWidget* AbstractLegacyControllerSetting::buildWidget(QWidget* parent) { QWidget* root = new QWidget(parent); root->setLayout(new QHBoxLayout()); root->layout()->setContentsMargins(0, 0, 0, 0); @@ -40,54 +44,254 @@ QWidget* LegacyControllerIntegerSetting::buildWidget(QWidget* parent) { root->layout()->addWidget(labelWidget); } - QSpinBox* spinBox = new QSpinBox(root); - spinBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - root->layout()->addWidget(spinBox); + root->layout()->addWidget(buildInputWidget(root)); - spinBox->setRange(this->m_minValue, this->m_maxValue); - spinBox->setSingleStep(this->m_stepValue); - spinBox->setValue(this->m_currentValue); + static_cast(root->layout())->setStretch(0, 3); + static_cast(root->layout())->setStretch(1, 1); + return root; +} + +LegacyControllerBooleanSetting::LegacyControllerBooleanSetting( + const QDomElement& element) + : AbstractLegacyControllerSetting(element) { + m_defaultValue = parseValue(element.attribute("default")); + m_currentValue = m_defaultValue; + m_dirtyValue = m_defaultValue; +} - connect(spinBox, QOverload::of(&QSpinBox::valueChanged), this, [this] { +QWidget* LegacyControllerBooleanSetting::buildWidget(QWidget* parent) { + QWidget* root = new QWidget(parent); + root->setLayout(new QHBoxLayout()); + root->layout()->setContentsMargins(0, 0, 0, 0); + + QLabel* labelWidget = new QLabel(root); + labelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + labelWidget->setText(label()); + + root->layout()->addWidget(buildInputWidget(root)); + + if (!description().isEmpty()) { + auto layout = new QVBoxLayout(); + + layout->setContentsMargins(0, 0, 0, 0); + + auto descriptionWidget = new QLabel(root); + descriptionWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + descriptionWidget->setText(description()); + QFont descriptionFont; + descriptionFont.setPointSize(8); + descriptionFont.setItalic(true); + descriptionWidget->setFont(descriptionFont); + + layout->addWidget(labelWidget); + layout->addWidget(descriptionWidget); + + root->layout()->addItem(layout); + } else { + root->layout()->addWidget(labelWidget); + } + + return root; +} + +QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* parent) { + QCheckBox* checkBox = new QCheckBox("", parent); + checkBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + if (m_currentValue) { + checkBox->setCheckState(Qt::Checked); + } + + connect(checkBox, &QCheckBox::stateChanged, this, [this](int state) { + m_dirtyValue = state == Qt::Checked; emit changed(); }); - static_cast(root->layout())->setStretch(0, 3); - static_cast(root->layout())->setStretch(1, 1); - return root; + return checkBox; } -AbstractLegacyControllerSetting* LegacyControllerIntegerSetting::createFrom( - const QDomElement& element) { - int defaultValue, minValue, maxValue, stepValue; +bool LegacyControllerBooleanSetting::match(const QDomElement& element) { + // TODO(acolombier) improve the function so it can detect the type from the + // spec if there is no type attribute + return element.hasAttribute("type") && + QString::compare(element.attribute("type"), + "boolean", + Qt::CaseInsensitive) == 0; +} + +REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerBooleanSetting); +template ValueSerializer, + Deserializer ValueDeserializer, + class InputWidget> +LegacyControllerNumberSetting::LegacyControllerNumberSetting(const QDomElement& element) + : AbstractLegacyControllerSetting(element) { bool isOk = false; - minValue = element.attribute("min").toInt(&isOk); + m_minValue = ValueDeserializer(element.attribute("min"), &isOk); if (!isOk) { - minValue = std::numeric_limits::min(); + m_minValue = std::numeric_limits::min(); } - maxValue = element.attribute("max").toInt(&isOk); + m_maxValue = ValueDeserializer(element.attribute("max"), &isOk); if (!isOk) { - maxValue = std::numeric_limits::max(); + m_maxValue = std::numeric_limits::max(); } - stepValue = element.attribute("step").toInt(&isOk); + m_stepValue = ValueDeserializer(element.attribute("step"), &isOk); if (!isOk) { - stepValue = 1; + m_stepValue = 1; } - defaultValue = element.attribute("default").toInt(&isOk); + m_defaultValue = ValueDeserializer(element.attribute("default"), &isOk); if (!isOk) { - defaultValue = 0; + m_defaultValue = 0; } + reset(); +} + +template ValueSerializer, + Deserializer ValueDeserializer, + class InputWidget> +QWidget* LegacyControllerNumberSetting::buildInputWidget(QWidget* parent) { + InputWidget* spinBox = new InputWidget(parent); + spinBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + spinBox->setRange(this->m_minValue, this->m_maxValue); + spinBox->setSingleStep(this->m_stepValue); + spinBox->setValue(this->m_currentValue); + + connect(spinBox, + QOverload::of(&InputWidget::valueChanged), + this, + [this](SettingType value) { + m_dirtyValue = value; + emit changed(); + }); + + return spinBox; +} - return new LegacyControllerIntegerSetting( - element, defaultValue, defaultValue, minValue, maxValue, stepValue); +template ValueSerializer, + Deserializer ValueDeserializer, + class InputWidget> +bool LegacyControllerNumberSetting::match(const QDomElement& element) { + return matchSetting(element); } -bool LegacyControllerIntegerSetting::match(const QDomElement& element) { +template<> +bool matchSetting(const QDomElement& element) { + // TODO(acolombier) improve the function so it can detect the type from the + // spec if there is no type attribute return element.hasAttribute("type") && QString::compare(element.attribute("type"), "integer", Qt::CaseInsensitive) == 0; -}; +} + +REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerNumberSetting); + +LegacyControllerRealSetting::LegacyControllerRealSetting(const QDomElement& element) + : LegacyControllerNumberSetting(element) { + bool isOk = false; + m_precisionValue = element.attribute("precision").toInt(&isOk); + if (!isOk) { + m_precisionValue = 2; + } +} + +QWidget* LegacyControllerRealSetting::buildInputWidget(QWidget* parent) { + QDoubleSpinBox* spinBox = dynamic_cast( + LegacyControllerNumberSetting::buildInputWidget(parent)); + VERIFY_OR_DEBUG_ASSERT(spinBox != nullptr) { + qWarning() << "Unable to set precision on the controller setting " + "input: tt does not appear to be a valid QDoubleSpinBox"; + return spinBox; + } + spinBox->setDecimals(m_precisionValue); + + return spinBox; +} + +template<> +bool matchSetting(const QDomElement& element) { + // TODO(acolombier) improve the function so it can detect the type from the + // spec if there is no type attribute + return element.hasAttribute("type") && + QString::compare(element.attribute("type"), + "real", + Qt::CaseInsensitive) == 0; +} + +REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerRealSetting); + +LegacyControllerEnumSetting::LegacyControllerEnumSetting( + const QDomElement& element) + : AbstractLegacyControllerSetting(element), m_options() { + for (QDomElement value = element.firstChildElement("value"); + !value.isNull(); + value = value.nextSiblingElement("value")) { + QString val = value.text(); + m_options.append(std::tuple(val, value.attribute("label", val))); + } + m_defaultValue = extractSettingIntegerValue(element.attribute("default")); + reset(); +} + +void LegacyControllerEnumSetting::parse(const QString& in, bool* ok) { + if (ok != nullptr) + *ok = false; + reset(); + + size_t pos = 0; + for (const auto& value : m_options) { + if (std::get<0>(value) == in) { + if (ok != nullptr) + *ok = true; + m_currentValue = pos; + m_dirtyValue = m_currentValue; + return; + } + pos++; + } +} + +QWidget* LegacyControllerEnumSetting::buildInputWidget(QWidget* parent) { + QComboBox* comboBox = new QComboBox(parent); + + for (const auto& value : m_options) { + comboBox->addItem(std::get<1>(value)); + } + comboBox->setCurrentIndex(m_currentValue); + + connect(comboBox, + QOverload::of(&QComboBox::currentIndexChanged), + this, + [this](int selected) { + m_dirtyValue = selected; + emit changed(); + }); + + return comboBox; +} + +bool LegacyControllerEnumSetting::match(const QDomElement& element) { + // TODO(acolombier) improve the function so it can detect the type from the + // spec if there is no type attribute + return element.hasAttribute("type") && + QString::compare(element.attribute("type"), + "enum", + Qt::CaseInsensitive) == 0; +} -REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerIntegerSetting); +REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerEnumSetting); diff --git a/src/controllers/legacycontrollersettings.h b/src/controllers/legacycontrollersettings.h index 5c9a56ed411..f1a08a0c6e2 100644 --- a/src/controllers/legacycontrollersettings.h +++ b/src/controllers/legacycontrollersettings.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -28,10 +29,18 @@ class AbstractLegacyControllerSetting : public QObject { /// @param parent The parent widget for which this widget is being created. /// The parent widget will own the newly created widget /// @return a new widget - virtual QWidget* buildWidget(QWidget* parent) = 0; + virtual QWidget* buildWidget(QWidget* parent); virtual QJSValue value() const = 0; + virtual QString stringify() const = 0; + virtual void parse(const QString&, bool*) = 0; + virtual bool isDefault() const = 0; + virtual bool isDirty() const = 0; + + virtual void save() = 0; + virtual void reset() = 0; + // virtual void reset() const = 0; /// @brief Whether of not this setting definition is valid. Validity scope @@ -75,6 +84,8 @@ class AbstractLegacyControllerSetting : public QObject { m_description = description.text().trimmed(); } + virtual QWidget* buildInputWidget(QWidget* parent) = 0; + signals: /// This signal will be emitted when the user has interacted with the /// setting and changed its value @@ -86,16 +97,133 @@ class AbstractLegacyControllerSetting : public QObject { QString m_description; }; -class LegacyControllerIntegerSetting - : public LegacyControllerSettingFactory, +class LegacyControllerBooleanSetting + : public LegacyControllerSettingFactory, + public AbstractLegacyControllerSetting { + public: + LegacyControllerBooleanSetting(const QDomElement& element); + + virtual ~LegacyControllerBooleanSetting() = default; + + QWidget* buildWidget(QWidget* parent) override; + + inline QJSValue value() const { + return QJSValue(m_currentValue); + } + + inline QString stringify() const { + return m_currentValue ? "true" : "false"; + } + inline void parse(const QString& in, bool* ok = nullptr) { + if (ok != nullptr) + *ok = true; + m_currentValue = parseValue(in); + m_dirtyValue = m_currentValue; + } + inline bool isDefault() const { + return m_currentValue == m_defaultValue; + } + inline bool isDirty() const { + return m_currentValue != m_dirtyValue; + } + + virtual void save() { + m_currentValue = m_dirtyValue; + } + + virtual void reset() { + m_currentValue = m_defaultValue; + m_dirtyValue = m_defaultValue; + } + + static inline AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { + return new LegacyControllerBooleanSetting(element); + } + static bool match(const QDomElement& element); + + protected: + LegacyControllerBooleanSetting(const QDomElement& element, + bool currentValue, + bool defaultValue) + : AbstractLegacyControllerSetting(element), + m_currentValue(currentValue), + m_defaultValue(defaultValue) { + } + + inline bool parseValue(const QString& in) { + return QString::compare(in, "true", Qt::CaseInsensitive) == 0 || in == "1"; + } + + virtual QWidget* buildInputWidget(QWidget* parent); + + private: + bool m_currentValue; + bool m_defaultValue; + bool m_dirtyValue; +}; + +template +using Serializer = QString (*)(const SettingType&); + +template +using Deserializer = SettingType (*)(const QString&, bool*); + +template ValueSerializer, + Deserializer ValueDeserializer, + class InputWidget> +class LegacyControllerNumberSetting + : public LegacyControllerSettingFactory< + LegacyControllerNumberSetting>, public AbstractLegacyControllerSetting { public: - LegacyControllerIntegerSetting(const QDomElement& element, - int currentValue, - int defaultValue, - int minValue, - int maxValue, - int stepValue) + LegacyControllerNumberSetting(const QDomElement& element); + + virtual ~LegacyControllerNumberSetting() = default; + + inline QJSValue value() const { + return QJSValue(m_currentValue); + } + + inline QString stringify() const { + return ValueSerializer(m_currentValue); + } + inline void parse(const QString& in, bool* ok) { + m_currentValue = ValueDeserializer(in, ok); + m_dirtyValue = m_currentValue; + } + + inline bool isDefault() const { + return m_currentValue == m_defaultValue; + } + inline bool isDirty() const { + return m_currentValue != m_dirtyValue; + } + + virtual void save() { + m_currentValue = m_dirtyValue; + } + + virtual void reset() { + m_currentValue = m_defaultValue; + m_dirtyValue = m_defaultValue; + } + + static inline AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { + return new LegacyControllerNumberSetting(element); + } + static bool match(const QDomElement& element); + + protected: + LegacyControllerNumberSetting(const QDomElement& element, + SettingType currentValue, + SettingType defaultValue, + SettingType minValue, + SettingType maxValue, + SettingType stepValue) : AbstractLegacyControllerSetting(element), m_currentValue(currentValue), m_defaultValue(defaultValue), @@ -104,21 +232,107 @@ class LegacyControllerIntegerSetting m_stepValue(stepValue) { } - virtual ~LegacyControllerIntegerSetting() = default; + virtual QWidget* buildInputWidget(QWidget* parent); + + private: + SettingType m_currentValue; + SettingType m_defaultValue; + SettingType m_minValue; + SettingType m_maxValue; + SettingType m_stepValue; + + SettingType m_dirtyValue; +}; + +template +bool matchSetting(const QDomElement& element); + +inline int extractSettingIntegerValue(const QString& str, bool* ok = nullptr) { + return str.toInt(ok); +} +inline double extractSettingDoubleValue(const QString& str, bool* ok = nullptr) { + return str.toDouble(ok); +} + +inline QString packSettingIntegerValue(const int& in) { + return QString::number(in); +} +inline QString packSettingDoubleValue(const double& in) { + return QString::number(in); +} + +class LegacyControllerRealSetting : public LegacyControllerNumberSetting { + public: + LegacyControllerRealSetting(const QDomElement& element); + + static inline AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { + return new LegacyControllerRealSetting(element); + } + + QWidget* buildInputWidget(QWidget* parent) override; - QWidget* buildWidget(QWidget* parent); + private: + int m_precisionValue; +}; + +class LegacyControllerEnumSetting + : public LegacyControllerSettingFactory, + public AbstractLegacyControllerSetting { + public: + LegacyControllerEnumSetting(const QDomElement& element); + + virtual ~LegacyControllerEnumSetting() = default; inline QJSValue value() const { - return QJSValue(m_currentValue); + return QJSValue(stringify()); + } + + QString stringify() const { + return std::get<0>(m_options.value(m_currentValue)); + } + void parse(const QString& in, bool* ok); + bool isDefault() const { + return m_currentValue == m_defaultValue; + } + inline bool isDirty() const { + return m_currentValue != m_dirtyValue; } - static AbstractLegacyControllerSetting* createFrom(const QDomElement& element); + virtual void save() { + m_currentValue = m_dirtyValue; + } + + virtual void reset() { + m_currentValue = m_defaultValue; + m_dirtyValue = m_defaultValue; + } + + static inline AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { + return new LegacyControllerEnumSetting(element); + } static bool match(const QDomElement& element); + protected: + LegacyControllerEnumSetting(const QDomElement& element, + QList> options, + size_t currentValue, + size_t defaultValue) + : AbstractLegacyControllerSetting(element), + m_options(options), + m_currentValue(currentValue), + m_defaultValue(defaultValue) { + } + + virtual QWidget* buildInputWidget(QWidget* parent); + private: - int m_currentValue; - int m_defaultValue; - int m_minValue; - int m_maxValue; - int m_stepValue; + // We use a QList instead of QHash here because we want to keep the natural order + QList> m_options; + size_t m_currentValue; + size_t m_defaultValue; + + size_t m_dirtyValue; }; diff --git a/src/controllers/legacycontrollersettingsfactory.h b/src/controllers/legacycontrollersettingsfactory.h index 3b2a0b9ecbc..151de97ac51 100644 --- a/src/controllers/legacycontrollersettingsfactory.h +++ b/src/controllers/legacycontrollersettingsfactory.h @@ -23,7 +23,7 @@ class AbstractLegacyControllerSetting; template class LegacyControllerSettingFactory { inline static LegacyControllerSettingFactory* createFrom(const QDomElement& element) { - return T::createFrom(element); + return new T(element); } inline static bool match(const QDomElement& element) { return T::match(element); @@ -52,7 +52,7 @@ class LegacyControllerSettingBuilder { /// @return an instance if a a supported setting has been found, null /// otherwise static AbstractLegacyControllerSetting* build(const QDomElement& element) { - foreach (auto settingType, instance()->m_supportedSettings) { + for (auto settingType : instance()->m_supportedSettings) { if (std::get<0>(settingType)(element)) { return std::get<1>(settingType)(element); } @@ -71,7 +71,9 @@ class LegacyControllerSettingBuilder { static LegacyControllerSettingBuilder* __self; }; -#define REGISTER_LEGACY_CONTROLLER_SETTING(S) \ - bool kSettingRegistered_##T = \ +#define CONCAT_(x, y) x##y +#define CONCAT(x, y) CONCAT_(x, y) +#define REGISTER_LEGACY_CONTROLLER_SETTING(...) \ + bool CONCAT(kSettingRegistered_, __COUNTER__) = \ LegacyControllerSettingBuilder::instance()->registerType( \ - S::match, S::createFrom) + __VA_ARGS__::match, __VA_ARGS__::createFrom) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index f880b169462..f760bbeabe3 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -124,10 +124,9 @@ bool ControllerScriptEngineLegacy::initialize() { ControllerScriptInterfaceLegacy* legacyScriptInterface = new ControllerScriptInterfaceLegacy(this, m_logger); - // TODO(acolombier): move into a dedicated protected method QJSValue settingsObject = m_pJSEngine->newObject(); - foreach (const std::shared_ptr& setting, m_settings) { - settingsObject.setProperty(setting->variableName(), setting->value()); + for (auto setting : m_settings) { + settingsObject.setProperty(std::get<0>(setting), std::get<1>(setting)); } engineGlobalObject.setProperty( "mixxxControllerSettings", settingsObject); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index b71c366cbd3..e0392a819bf 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -30,7 +30,11 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { void setScriptFiles(const QList& scripts); inline void setSettings( const QList>& settings) { - m_settings = settings; + m_settings.clear(); + for (auto pSetting : settings) { + m_settings.append(std::tuple( + pSetting->variableName(), pSetting->value())); + } } private: @@ -48,7 +52,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QList m_incomingDataFunctions; QHash m_scriptWrappedFunctionCache; QList m_scriptFiles; - QList> m_settings; + QList> m_settings; QFileSystemWatcher m_fileWatcher; From 0f01d3e7874d14e248bf902cf6ef1e490d623dd6 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sun, 26 Feb 2023 12:51:50 +0000 Subject: [PATCH 069/124] feat(Controller): UI improvement with custom layout support --- CMakeLists.txt | 2 + src/controllers/dlgprefcontroller.cpp | 22 +- src/controllers/legacycontrollermapping.cpp | 47 ++-- src/controllers/legacycontrollermapping.h | 39 +++- .../legacycontrollermappingfilehandler.cpp | 58 +++-- .../legacycontrollermappingfilehandler.h | 14 ++ src/controllers/legacycontrollersettings.cpp | 126 +++++------ src/controllers/legacycontrollersettings.h | 143 ++++++++---- .../legacycontrollersettingsfactory.h | 2 +- .../legacycontrollersettingslayout.cpp | 78 +++++++ .../legacycontrollersettingslayout.h | 108 +++++++++ ...legacymidicontrollermappingfilehandler.cpp | 1 + .../legacy/controllerscriptenginelegacy.cpp | 11 +- .../legacy/controllerscriptenginelegacy.h | 17 +- src/test/controller_mapping_settings_test.cpp | 214 ++++++++++++++++++ 15 files changed, 714 insertions(+), 168 deletions(-) create mode 100644 src/controllers/legacycontrollersettingslayout.cpp create mode 100644 src/controllers/legacycontrollersettingslayout.h create mode 100644 src/test/controller_mapping_settings_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 104ae222d31..70d4954448f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -732,6 +732,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/controllermanager.cpp src/controllers/controllermappinginfo.cpp src/controllers/legacycontrollersettings.cpp + src/controllers/legacycontrollersettingslayout.cpp src/controllers/controllermappinginfoenumerator.cpp src/controllers/controllermappingtablemodel.cpp src/controllers/controlleroutputmappingtablemodel.cpp @@ -2073,6 +2074,7 @@ add_executable(mixxx-test src/test/colorpalette_test.cpp src/test/configobject_test.cpp src/test/controller_mapping_validation_test.cpp + src/test/controller_mapping_settings_test.cpp src/test/controllerscriptenginelegacy_test.cpp src/test/controlobjecttest.cpp src/test/controlobjectaliastest.cpp diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 28a809fa5f3..8f385f97fcc 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -559,9 +559,12 @@ void DlgPrefController::slotApply() { } QString mappingPath = mappingPathFromIndex(m_ui.comboBoxMapping->currentIndex()); + QFileInfo mappingFileInfo(mappingPath); - m_pMapping->saveSettings(mappingFileInfo, m_pConfig, m_pController->getName()); + if (m_pMapping) { + m_pMapping->saveSettings(mappingFileInfo, m_pConfig, m_pController->getName()); + } m_pMapping = LegacyControllerMappingFileHandler::loadMapping( mappingFileInfo, QDir(resourceMappingsPath(m_pConfig))); @@ -840,16 +843,19 @@ void DlgPrefController::slotShowMapping(std::shared_ptr if (pMapping) { auto settings = pMapping->getSettings(); + const auto& layout = pMapping->getSettingsLayout(); qDeleteAll(m_ui.groupBoxSettings->findChildren("", Qt::FindDirectChildrenOnly)); - for (auto setting : settings) { - parented_ptr pSettingWidget(setting->buildWidget(m_ui.groupBoxSettings)); - connect(setting.get(), - &AbstractLegacyControllerSetting::changed, - this, - [this] { setDirty(true); }); - m_ui.groupBoxSettings->layout()->addWidget(pSettingWidget); + if (layout.get() != nullptr && !settings.isEmpty()) { + m_ui.groupBoxSettings->layout()->addWidget(layout->build(m_ui.groupBoxSettings)); + + for (const auto& setting : qAsConst(settings)) { + connect(setting.get(), + &AbstractLegacyControllerSetting::changed, + this, + [this] { setDirty(true); }); + } } m_ui.groupBoxSettings->setVisible(!settings.isEmpty()); diff --git a/src/controllers/legacycontrollermapping.cpp b/src/controllers/legacycontrollermapping.cpp index 7bfb8ce7595..1c6863e040e 100644 --- a/src/controllers/legacycontrollermapping.cpp +++ b/src/controllers/legacycontrollermapping.cpp @@ -4,29 +4,48 @@ void LegacyControllerMapping::restoreSettings(const QFileInfo& mappingFile, UserSettingsPointer pConfig, const QString& controllerName) { QString controllerKey = QString(CONTROLLER_SETTINGS_PREFERENCE_GROUP_KEY) - .arg(controllerName) - .arg(mappingFile.absoluteFilePath()); - for (auto setting : getSettings()) { - bool ok; - QString value = pConfig->getValueString(ConfigKey(controllerKey, setting->variableName())); - setting->parse(value, &ok); - if (!ok) { - qWarning() << "The setting" << setting->variableName() + .arg(controllerName, mappingFile.absoluteFilePath()); + + auto availableSettings = getSettings(); + QList definedSettings = pConfig->getKeysWithGroup(controllerKey); + + QList availableSettingKeys; + for (const auto& pSetting : qAsConst(availableSettings)) { + availableSettingKeys.append(pSetting->variableName()); + } + + bool ok; + for (const auto& key : definedSettings) { + if (!availableSettingKeys.contains(key.item)) { + qDebug() << "The setting" << key.item + << "does not seem to exist in the mapping" << mappingFile.absoluteFilePath() + << ". It may be invalid or may have been removed."; + pConfig->remove(key); + continue; + } + auto& pSetting = availableSettings.at(availableSettingKeys.indexOf(key.item)); + QString value = pConfig->getValueString(key); + if (!pSetting->valid()) { + qWarning() << "The setting" << pSetting->variableName() << "for the mapping" << mappingFile.absoluteFilePath() - << "could not be restore. Removing"; - pConfig->remove(ConfigKey(controllerKey, setting->variableName())); + << "appears to be invalid. Its saved value won't be restored."; + } + pSetting->parse(value, &ok); + if (!ok || !pSetting->valid()) { + qWarning() << "The setting" << pSetting->variableName() + << "for the mapping" << mappingFile.absoluteFilePath() + << "could not be restore. Removing and resetting the setting default value."; + pConfig->remove(key); + pSetting->reset(); } } - // TODO (acolombier): If there are other settings that aren't defined - // anymore, they should be removed. } void LegacyControllerMapping::saveSettings(const QFileInfo& mappingFile, UserSettingsPointer pConfig, const QString& controllerName) { QString controllerKey = QString(CONTROLLER_SETTINGS_PREFERENCE_GROUP_KEY) - .arg(controllerName) - .arg(mappingFile.absoluteFilePath()); + .arg(controllerName, mappingFile.absoluteFilePath()); for (auto setting : getSettings()) { if (!setting->isDirty()) { continue; diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index 158d70b04db..a0934281e7b 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -14,6 +14,7 @@ #include #include "controllers/legacycontrollersettings.h" +#include "controllers/legacycontrollersettingslayout.h" #include "defs_urls.h" #include "preferences/usersettings.h" @@ -25,7 +26,26 @@ class LegacyControllerMapping { public: LegacyControllerMapping() - : m_bDirty(false), m_settings() { + : m_bDirty(false) { + } + LegacyControllerMapping(const LegacyControllerMapping& other) + : m_productMatches(other.m_productMatches), + m_bDirty(other.m_bDirty), + m_deviceId(other.m_deviceId), + m_filePath(other.m_filePath), + m_name(other.m_name), + m_author(other.m_author), + m_description(other.m_description), + m_forumlink(other.m_forumlink), + m_manualPage(other.m_manualPage), + m_wikilink(other.m_wikilink), + m_schemaVersion(other.m_schemaVersion), + m_mixxxVersion(other.m_mixxxVersion), + m_settings(other.m_settings), + m_settingsLayout(other.m_settingsLayout.get() != nullptr + ? other.m_settingsLayout->clone() + : nullptr), + m_scripts(other.m_scripts) { } virtual ~LegacyControllerMapping() = default; @@ -75,14 +95,28 @@ class LegacyControllerMapping { m_settings.append(option); } + /// @brief Set a setting layout as they should be perceived when edited in + /// the preference dialog. + /// @param layout The layout root element + void setSettingLayout(std::unique_ptr&& layout) { + VERIFY_OR_DEBUG_ASSERT(layout.get()) { + return; + } + m_settingsLayout = std::move(layout); + } + const QList& getScriptFiles() const { return m_scripts; } - const QList>& getSettings() const { + inline const QList>& getSettings() const { return m_settings; } + inline const std::unique_ptr& getSettingsLayout() const { + return m_settingsLayout; + } + inline void setDirty(bool bDirty) { m_bDirty = bDirty; } @@ -228,5 +262,6 @@ class LegacyControllerMapping { QString m_mixxxVersion; QList> m_settings; + std::unique_ptr m_settingsLayout; QList m_scripts; }; diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index 9a8509bdcd3..a491149ae46 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -119,21 +119,53 @@ void LegacyControllerMappingFileHandler::parseMappingSettings( return; } - for (QDomElement option = settings.firstChildElement("option"); - !option.isNull(); - option = option.nextSiblingElement("option")) { - std::shared_ptr pSetting( - LegacyControllerSettingBuilder::build(option)); - if (pSetting.get() == nullptr) { - qDebug() << "Could not parse the unknown controller setting. Ignoring it."; - continue; - } - if (!pSetting->valid()) { - qDebug() << "The parsed setting appears to be invalid. Discarding it."; + std::unique_ptr settingLayout = + std::make_unique( + LegacyControllerSettingsLayoutContainer::Disposition:: + VERTICAL); + parseMappingSettingsElement(settings, mapping, settingLayout); + mapping->setSettingLayout(std::move(settingLayout)); +} + +void LegacyControllerMappingFileHandler::parseMappingSettingsElement( + const QDomElement& current, + std::shared_ptr mapping, + const std::unique_ptr& layout) + const { + // TODO (acolombier) Add test for the parser + for (QDomElement element = current.firstChildElement(); + !element.isNull(); + element = element.nextSiblingElement()) { + const QString& tagName = element.tagName(); + if (tagName == "option") { + std::shared_ptr pSetting( + LegacyControllerSettingBuilder::build(element)); + if (pSetting.get() == nullptr) { + qDebug() << "Could not parse the unknown controller setting. Ignoring it."; + continue; + } + if (!pSetting->valid()) { + qDebug() << "The parsed setting appears to be invalid. Discarding it."; + continue; + } + layout->addItem(pSetting); + mapping->addSetting(pSetting); + } else if (tagName == "row") { + std::unique_ptr row = + std::make_unique(); + parseMappingSettingsElement(element, mapping, row); + layout->addItem(std::move(row)); + } else if (tagName == "group") { + std::unique_ptr group = + std::make_unique( + element.attribute("label")); + parseMappingSettingsElement(element, mapping, group); + layout->addItem(std::move(group)); + } else { + qDebug() << "Unsupported tag" << tagName + << "for controller layout settings. Discarding it."; continue; } - - mapping->addSetting(pSetting); } } diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index ee23268ca74..8daf2f21602 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -41,9 +41,23 @@ class LegacyControllerMappingFileHandler { void parseMappingInfo(const QDomElement& root, std::shared_ptr mapping) const; + /// @brief Parse the setting definition block from the root node if any. + /// @param root The root node (MixxxControllerPreset) + /// @param mapping The mapping object to populate with the gathered data void parseMappingSettings(const QDomElement& root, std::shared_ptr mapping) const; + /// @brief Recursively parse setting definition and layout information + /// within a setting node + /// @param current The setting node (MixxxControllerPreset.settings) or any + /// children nodes + /// @param mapping The mapping object to populate with the gathered data + /// @param layout The currently active layout, on which new setting item + /// (leaf) should be attached + void parseMappingSettingsElement(const QDomElement& current, + std::shared_ptr mapping, + const std::unique_ptr& layout) const; + /// Adds script files from XML to the LegacyControllerMapping. /// /// This function parses the supplied QDomElement structure, finds the diff --git a/src/controllers/legacycontrollersettings.cpp b/src/controllers/legacycontrollersettings.cpp index 76cf1013f42..1772e2538a8 100644 --- a/src/controllers/legacycontrollersettings.cpp +++ b/src/controllers/legacycontrollersettings.cpp @@ -6,49 +6,50 @@ #include #include +// TODO (acolombier): remove "new" where possible and use parented_ptr + LegacyControllerSettingBuilder* LegacyControllerSettingBuilder::__self = nullptr; LegacyControllerSettingBuilder* LegacyControllerSettingBuilder::instance() { - if (__self == nullptr) + if (__self == nullptr) { __self = new LegacyControllerSettingBuilder(); + } return __self; } +AbstractLegacyControllerSetting::AbstractLegacyControllerSetting(const QDomElement& element) { + m_variableName = element.attribute("variable").trimmed(); + m_label = element.attribute("label", m_variableName).trimmed(); + + QDomElement description = element.firstChildElement("description"); + if (!description.isNull()) { + m_description = description.text().trimmed(); + } +} + QWidget* AbstractLegacyControllerSetting::buildWidget(QWidget* parent) { - QWidget* root = new QWidget(parent); - root->setLayout(new QHBoxLayout()); - root->layout()->setContentsMargins(0, 0, 0, 0); + QWidget* pRoot = new QWidget(parent); + QBoxLayout* pLayout = new QBoxLayout(QBoxLayout::LeftToRight); + pLayout->setContentsMargins(0, 0, 0, 0); - QLabel* labelWidget = new QLabel(root); - labelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - labelWidget->setText(label()); + QLabel* pLabelWidget = new QLabel(pRoot); + pLabelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + pLabelWidget->setText(label()); if (!description().isEmpty()) { - auto layout = new QVBoxLayout(root); - - root->layout()->setContentsMargins(0, 0, 0, 0); - - auto descriptionWidget = new QLabel(root); - descriptionWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - descriptionWidget->setText(description()); - QFont descriptionFont; - descriptionFont.setPointSize(8); - descriptionFont.setItalic(true); - descriptionWidget->setFont(descriptionFont); - - layout->addWidget(labelWidget); - layout->addWidget(descriptionWidget); - root->layout()->addItem(layout); - } else { - root->layout()->addWidget(labelWidget); + pRoot->setToolTip(QString("

    %1

    ").arg(description())); } - root->layout()->addWidget(buildInputWidget(root)); + pLayout->addWidget(pLabelWidget); + pLayout->addWidget(buildInputWidget(pRoot)); + + pLayout->setStretch(0, 3); + pLayout->setStretch(1, 1); - static_cast(root->layout())->setStretch(0, 3); - static_cast(root->layout())->setStretch(1, 1); - return root; + pRoot->setLayout(pLayout); + + return pRoot; } LegacyControllerBooleanSetting::LegacyControllerBooleanSetting( @@ -60,38 +61,24 @@ LegacyControllerBooleanSetting::LegacyControllerBooleanSetting( } QWidget* LegacyControllerBooleanSetting::buildWidget(QWidget* parent) { - QWidget* root = new QWidget(parent); - root->setLayout(new QHBoxLayout()); - root->layout()->setContentsMargins(0, 0, 0, 0); - - QLabel* labelWidget = new QLabel(root); - labelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - labelWidget->setText(label()); + QWidget* pRoot = new QWidget(parent); + QBoxLayout* pLayout = new QBoxLayout(QBoxLayout::LeftToRight); + pLayout->setContentsMargins(0, 0, 0, 0); - root->layout()->addWidget(buildInputWidget(root)); + QLabel* pLabelWidget = new QLabel(pRoot); + pLabelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + pLabelWidget->setText(label()); if (!description().isEmpty()) { - auto layout = new QVBoxLayout(); - - layout->setContentsMargins(0, 0, 0, 0); - - auto descriptionWidget = new QLabel(root); - descriptionWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - descriptionWidget->setText(description()); - QFont descriptionFont; - descriptionFont.setPointSize(8); - descriptionFont.setItalic(true); - descriptionWidget->setFont(descriptionFont); + pRoot->setToolTip(QString("

    %1

    ").arg(description())); + } - layout->addWidget(labelWidget); - layout->addWidget(descriptionWidget); + pLayout->addWidget(buildInputWidget(pRoot)); + pLayout->addWidget(pLabelWidget); - root->layout()->addItem(layout); - } else { - root->layout()->addWidget(labelWidget); - } + pRoot->setLayout(pLayout); - return root; + return pRoot; } QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* parent) { @@ -110,8 +97,6 @@ QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* parent) { } bool LegacyControllerBooleanSetting::match(const QDomElement& element) { - // TODO(acolombier) improve the function so it can detect the type from the - // spec if there is no type attribute return element.hasAttribute("type") && QString::compare(element.attribute("type"), "boolean", @@ -188,18 +173,13 @@ bool LegacyControllerNumberSetting bool matchSetting(const QDomElement& element) { - // TODO(acolombier) improve the function so it can detect the type from the - // spec if there is no type attribute return element.hasAttribute("type") && QString::compare(element.attribute("type"), "integer", Qt::CaseInsensitive) == 0; } -REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerNumberSetting); +REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerIntegerSetting); LegacyControllerRealSetting::LegacyControllerRealSetting(const QDomElement& element) : LegacyControllerNumberSetting(element) { @@ -225,8 +205,6 @@ QWidget* LegacyControllerRealSetting::buildInputWidget(QWidget* parent) { template<> bool matchSetting(const QDomElement& element) { - // TODO(acolombier) improve the function so it can detect the type from the - // spec if there is no type attribute return element.hasAttribute("type") && QString::compare(element.attribute("type"), "real", @@ -237,27 +215,33 @@ REGISTER_LEGACY_CONTROLLER_SETTING(LegacyControllerRealSetting); LegacyControllerEnumSetting::LegacyControllerEnumSetting( const QDomElement& element) - : AbstractLegacyControllerSetting(element), m_options() { + : AbstractLegacyControllerSetting(element), m_options(), m_defaultValue(0) { + size_t pos = 0; for (QDomElement value = element.firstChildElement("value"); !value.isNull(); value = value.nextSiblingElement("value")) { QString val = value.text(); m_options.append(std::tuple(val, value.attribute("label", val))); + if (value.hasAttribute("default")) { + m_defaultValue = pos; + } + pos++; } - m_defaultValue = extractSettingIntegerValue(element.attribute("default")); reset(); } void LegacyControllerEnumSetting::parse(const QString& in, bool* ok) { - if (ok != nullptr) + if (ok != nullptr) { *ok = false; + } reset(); size_t pos = 0; - for (const auto& value : m_options) { + for (const auto& value : qAsConst(m_options)) { if (std::get<0>(value) == in) { - if (ok != nullptr) + if (ok != nullptr) { *ok = true; + } m_currentValue = pos; m_dirtyValue = m_currentValue; return; @@ -269,7 +253,7 @@ void LegacyControllerEnumSetting::parse(const QString& in, bool* ok) { QWidget* LegacyControllerEnumSetting::buildInputWidget(QWidget* parent) { QComboBox* comboBox = new QComboBox(parent); - for (const auto& value : m_options) { + for (const auto& value : qAsConst(m_options)) { comboBox->addItem(std::get<1>(value)); } comboBox->setCurrentIndex(m_currentValue); @@ -286,8 +270,6 @@ QWidget* LegacyControllerEnumSetting::buildInputWidget(QWidget* parent) { } bool LegacyControllerEnumSetting::match(const QDomElement& element) { - // TODO(acolombier) improve the function so it can detect the type from the - // spec if there is no type attribute return element.hasAttribute("type") && QString::compare(element.attribute("type"), "enum", diff --git a/src/controllers/legacycontrollersettings.h b/src/controllers/legacycontrollersettings.h index f1a08a0c6e2..01c7bebe296 100644 --- a/src/controllers/legacycontrollersettings.h +++ b/src/controllers/legacycontrollersettings.h @@ -31,58 +31,74 @@ class AbstractLegacyControllerSetting : public QObject { /// @return a new widget virtual QWidget* buildWidget(QWidget* parent); + /// @brief Build a JSValue with the current setting value. The JSValue + /// variant will use the appropriate type + /// @return A QJSValue with the current value virtual QJSValue value() const = 0; + /// @brief Serialize the current value in a string format + /// @return A String with current setting value virtual QString stringify() const = 0; + + /// @brief Parse a string that contains the value in a compatible format. + /// @param in The string containing the data + /// @param ok A pointer to a boolean in which the result of the + /// deserialisation will be stored (true means the operation was successful) virtual void parse(const QString&, bool*) = 0; + + /// @brief Indicate if the setting is currently not using a user-specified value + /// @return Whether or not the setting is currently set to its default value virtual bool isDefault() const = 0; + + /// @brief Indicate if the setting is currently being mutated and if the + /// edited value is different than its its currently known value. This would + /// indicate that the user may need to save the changes or acknowledge + /// otherwise. + /// @return Whether or not the setting value is dirty virtual bool isDirty() const = 0; + /// @brief Commit the the edited value to become the currently known value. + /// Note that if `isDirty() == false`, this have no effect virtual void save() = 0; - virtual void reset() = 0; - // virtual void reset() const = 0; + /// @brief Reset the current value, as well as the editing value to use the + /// default, as specified in the spec + virtual void reset() = 0; /// @brief Whether of not this setting definition is valid. Validity scope /// includes things like default value within range' for example. /// @return true if valid - virtual inline bool valid() const { + virtual bool valid() const { return !m_variableName.isEmpty(); } /// @brief The variable name as perceived within the mapping definition. /// @return a string - inline QString variableName() const { + QString variableName() const { return m_variableName; } /// @brief The user-friendly label to be display in the UI /// @return a string - inline const QString& label() const { + const QString& label() const { return m_label; } /// @brief A description of what this setting does /// @return a string - inline const QString& description() const { + const QString& description() const { return m_description; } protected: - AbstractLegacyControllerSetting(QString variableName, QString label, QString description) - : m_variableName(variableName), m_label(label), m_description(description) { - } - AbstractLegacyControllerSetting(const QDomElement& element) { - m_variableName = element.attribute("variable").trimmed(); - m_label = element.attribute("label", m_variableName).trimmed(); - - QDomElement description = element.firstChildElement("description"); - if (description.isNull()) { - m_description = QString(); - return; - } - m_description = description.text().trimmed(); + AbstractLegacyControllerSetting(const QString& variableName, + const QString& label, + const QString& description) + : m_variableName(variableName), + m_label(label), + m_description(description) { } + AbstractLegacyControllerSetting(const QDomElement& element); virtual QWidget* buildInputWidget(QWidget* parent) = 0; @@ -107,36 +123,36 @@ class LegacyControllerBooleanSetting QWidget* buildWidget(QWidget* parent) override; - inline QJSValue value() const { + QJSValue value() const override { return QJSValue(m_currentValue); } - inline QString stringify() const { + QString stringify() const override { return m_currentValue ? "true" : "false"; } - inline void parse(const QString& in, bool* ok = nullptr) { + void parse(const QString& in, bool* ok = nullptr) override { if (ok != nullptr) *ok = true; m_currentValue = parseValue(in); m_dirtyValue = m_currentValue; } - inline bool isDefault() const { + bool isDefault() const override { return m_currentValue == m_defaultValue; } - inline bool isDirty() const { + bool isDirty() const override { return m_currentValue != m_dirtyValue; } - virtual void save() { + virtual void save() override { m_currentValue = m_dirtyValue; } - virtual void reset() { + virtual void reset() override { m_currentValue = m_defaultValue; m_dirtyValue = m_defaultValue; } - static inline AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { + static AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { return new LegacyControllerBooleanSetting(element); } static bool match(const QDomElement& element); @@ -150,16 +166,18 @@ class LegacyControllerBooleanSetting m_defaultValue(defaultValue) { } - inline bool parseValue(const QString& in) { + bool parseValue(const QString& in) { return QString::compare(in, "true", Qt::CaseInsensitive) == 0 || in == "1"; } - virtual QWidget* buildInputWidget(QWidget* parent); + virtual QWidget* buildInputWidget(QWidget* parent) override; private: bool m_currentValue; bool m_defaultValue; bool m_dirtyValue; + + friend class LegacyControllerMappingSettingsTest_booleanSettingEditing_Test; }; template @@ -184,35 +202,47 @@ class LegacyControllerNumberSetting virtual ~LegacyControllerNumberSetting() = default; - inline QJSValue value() const { + QJSValue value() const override { return QJSValue(m_currentValue); } - inline QString stringify() const { + QString stringify() const override { return ValueSerializer(m_currentValue); } - inline void parse(const QString& in, bool* ok) { + void parse(const QString& in, bool* ok) override { m_currentValue = ValueDeserializer(in, ok); m_dirtyValue = m_currentValue; } - inline bool isDefault() const { + bool isDefault() const override { return m_currentValue == m_defaultValue; } - inline bool isDirty() const { + bool isDirty() const override { return m_currentValue != m_dirtyValue; } - virtual void save() { + virtual void save() override { m_currentValue = m_dirtyValue; } - virtual void reset() { + virtual void reset() override { m_currentValue = m_defaultValue; m_dirtyValue = m_defaultValue; } - static inline AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { + /// @brief Whether of not this setting definition and its current state are + /// valid. Validity scope includes default/current/dirty value within range + /// and a strictly positive step, strictly less than max.. + /// @return true if valid + bool valid() const override { + return AbstractLegacyControllerSetting::valid() && + m_defaultValue >= m_minValue && m_currentValue >= m_minValue && + m_dirtyValue >= m_minValue && m_defaultValue <= m_maxValue && + m_currentValue <= m_maxValue && m_dirtyValue <= m_maxValue && + m_stepValue > 0 && m_stepValue < m_maxValue; + } + + static AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { return new LegacyControllerNumberSetting(element); } static bool match(const QDomElement& element); @@ -232,7 +262,7 @@ class LegacyControllerNumberSetting m_stepValue(stepValue) { } - virtual QWidget* buildInputWidget(QWidget* parent); + virtual QWidget* buildInputWidget(QWidget* parent) override; private: SettingType m_currentValue; @@ -261,6 +291,11 @@ inline QString packSettingDoubleValue(const double& in) { return QString::number(in); } +using LegacyControllerIntegerSetting = LegacyControllerNumberSetting; + class LegacyControllerRealSetting : public LegacyControllerNumberSetting(m_options.value(m_currentValue)); } - void parse(const QString& in, bool* ok); - bool isDefault() const { + void parse(const QString& in, bool* ok) override; + bool isDefault() const override { return m_currentValue == m_defaultValue; } - inline bool isDirty() const { + bool isDirty() const override { return m_currentValue != m_dirtyValue; } - virtual void save() { + virtual void save() override { m_currentValue = m_dirtyValue; } - virtual void reset() { + virtual void reset() override { m_currentValue = m_defaultValue; m_dirtyValue = m_defaultValue; } - static inline AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { + /// @brief Whether of not this setting definition and its current state are + /// valid. Validity scope includes a known default/current/dirty option. + /// @return true if valid + bool valid() const override { + return AbstractLegacyControllerSetting::valid() && + (int)m_defaultValue < m_options.size() && + (int)m_currentValue < m_options.size() && + (int)m_dirtyValue < m_options.size(); + } + + static AbstractLegacyControllerSetting* createFrom(const QDomElement& element) { return new LegacyControllerEnumSetting(element); } static bool match(const QDomElement& element); protected: LegacyControllerEnumSetting(const QDomElement& element, - QList> options, + const QList>& options, size_t currentValue, size_t defaultValue) : AbstractLegacyControllerSetting(element), @@ -326,7 +371,7 @@ class LegacyControllerEnumSetting m_defaultValue(defaultValue) { } - virtual QWidget* buildInputWidget(QWidget* parent); + virtual QWidget* buildInputWidget(QWidget* parent) override; private: // We use a QList instead of QHash here because we want to keep the natural order diff --git a/src/controllers/legacycontrollersettingsfactory.h b/src/controllers/legacycontrollersettingsfactory.h index 151de97ac51..92b8de77397 100644 --- a/src/controllers/legacycontrollersettingsfactory.h +++ b/src/controllers/legacycontrollersettingsfactory.h @@ -52,7 +52,7 @@ class LegacyControllerSettingBuilder { /// @return an instance if a a supported setting has been found, null /// otherwise static AbstractLegacyControllerSetting* build(const QDomElement& element) { - for (auto settingType : instance()->m_supportedSettings) { + for (const auto& settingType : qAsConst(instance()->m_supportedSettings)) { if (std::get<0>(settingType)(element)) { return std::get<1>(settingType)(element); } diff --git a/src/controllers/legacycontrollersettingslayout.cpp b/src/controllers/legacycontrollersettingslayout.cpp new file mode 100644 index 00000000000..01cfb6f2617 --- /dev/null +++ b/src/controllers/legacycontrollersettingslayout.cpp @@ -0,0 +1,78 @@ + +#include "controllers/legacycontrollersettingslayout.h" + +#include +#include +#include +#include +#include +#include +#include + +void LegacyControllerSettingsLayoutContainer::addItem( + std::shared_ptr setting) { + m_elements.push_back(std::make_unique(setting)); +} + +QBoxLayout* LegacyControllerSettingsLayoutContainer::buildLayout(QWidget* pParent) const { + QBoxLayout* pLayout = nullptr; + + /// Find the active screen. If its size is too small, we force vertical orientation + QScreen* pActive = nullptr; + QWidget* pWidget = pParent; + + while (pWidget) { + auto* pW = pWidget->windowHandle(); + if (pW != nullptr) { + pActive = pW->screen(); + break; + } else { + pWidget = pWidget->parentWidget(); + } + } + + if (m_disposition == VERTICAL || + (pActive != nullptr && + pActive->availableSize().width() < + MIN_SCREEN_SIZE_FOR_CONTROLLER_SETTING_ROW)) { + pLayout = new QVBoxLayout(); + } else { + pLayout = new QHBoxLayout(); + pLayout->setSpacing(16); + } + + pParent->setLayout(pLayout); + + return pLayout; +} + +QWidget* LegacyControllerSettingsLayoutContainer::build(QWidget* pParent) { + QWidget* pContainer = new QWidget(pParent); + QBoxLayout* pLayout = buildLayout(pContainer); + + pLayout->setContentsMargins(0, 0, 0, 0); + + for (auto& element : m_elements) { + auto* pWidget = element->build(pContainer); + if (pLayout->direction() == QBoxLayout::LeftToRight) { + auto* pLayout = dynamic_cast(pWidget->layout()); + if (pLayout != nullptr) { + pLayout->setDirection(QBoxLayout::TopToBottom); + } + } + pLayout->addWidget(pWidget); + } + + return pContainer; +} + +QWidget* LegacyControllerSettingsGroup::build(QWidget* pParent) { + QWidget* pContainer = new QGroupBox(m_label, pParent); + QBoxLayout* pLayout = buildLayout(pContainer); + + for (auto& element : m_elements) { + pLayout->addWidget(element->build(pContainer)); + } + + return pContainer; +} diff --git a/src/controllers/legacycontrollersettingslayout.h b/src/controllers/legacycontrollersettingslayout.h new file mode 100644 index 00000000000..3ce83cb596c --- /dev/null +++ b/src/controllers/legacycontrollersettingslayout.h @@ -0,0 +1,108 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "controllers/legacycontrollersettings.h" +#include "defs_urls.h" +#include "preferences/usersettings.h" + +#define MIN_SCREEN_SIZE_FOR_CONTROLLER_SETTING_ROW 1280 + +class LegacyControllerSettingsLayoutElement { + public: + LegacyControllerSettingsLayoutElement() { + } + virtual ~LegacyControllerSettingsLayoutElement() = default; + + virtual std::unique_ptr clone() const = 0; + + virtual QWidget* build(QWidget* parent) = 0; +}; + +class LegacyControllerSettingsLayoutContainer : public LegacyControllerSettingsLayoutElement { + public: + enum Disposition { + HORIZONTAL = 0, + VERTICAL, + }; + + LegacyControllerSettingsLayoutContainer(Disposition disposition = HORIZONTAL) + : LegacyControllerSettingsLayoutElement(), m_disposition(disposition) { + } + LegacyControllerSettingsLayoutContainer(const LegacyControllerSettingsLayoutContainer& other) { + m_elements.reserve(other.m_elements.size()); + for (const auto& e : other.m_elements) + m_elements.push_back(e->clone()); + } + virtual ~LegacyControllerSettingsLayoutContainer() = default; + + virtual std::unique_ptr clone() const override { + return std::make_unique(*this); + } + + void addItem(std::shared_ptr setting); + void addItem(std::unique_ptr&& container) { + m_elements.push_back(std::move(container)); + } + + virtual QWidget* build(QWidget* parent) override; + + protected: + QBoxLayout* buildLayout(QWidget* parent) const; + + Disposition m_disposition; + std::vector> m_elements; +}; + +class LegacyControllerSettingsGroup : public LegacyControllerSettingsLayoutContainer { + public: + LegacyControllerSettingsGroup(const QString& label, + LegacyControllerSettingsLayoutContainer::Disposition disposition = + VERTICAL) + : LegacyControllerSettingsLayoutContainer(disposition), + m_label(label) { + } + virtual ~LegacyControllerSettingsGroup() = default; + + std::unique_ptr clone() const override { + return std::make_unique(*this); + } + + QWidget* build(QWidget* parent) override; + + private: + QString m_label; +}; + +class LegacyControllerSettingsLayoutItem : public LegacyControllerSettingsLayoutElement { + public: + LegacyControllerSettingsLayoutItem(std::shared_ptr setting) + : LegacyControllerSettingsLayoutElement(), m_setting(setting) { + } + virtual ~LegacyControllerSettingsLayoutItem() = default; + + std::unique_ptr clone() const override { + return std::make_unique(*this); + } + + QWidget* build(QWidget* parent) override { + VERIFY_OR_DEBUG_ASSERT(m_setting.get() != nullptr) { + return nullptr; + } + return m_setting->buildWidget(parent); + } + + private: + std::shared_ptr m_setting; +}; diff --git a/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp b/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp index e0fcea473cf..48d97d2ea83 100644 --- a/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp +++ b/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp @@ -11,6 +11,7 @@ std::shared_ptr LegacyMidiControllerMappingFileHandler::load(const QDomElement& root, const QString& filePath, const QDir& systemMappingsPath) { + // TODO (acolombier): support for controller settings if (root.isNull()) { return nullptr; } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index f760bbeabe3..6f089cb767f 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -99,6 +99,15 @@ void ControllerScriptEngineLegacy::setScriptFiles( m_scriptFiles = scripts; } +void ControllerScriptEngineLegacy::setSettings( + const QList>& settings) { + m_settings.clear(); + for (const auto& pSetting : qAsConst(settings)) { + m_settings.append(std::tuple( + pSetting->variableName(), pSetting->value())); + } +} + bool ControllerScriptEngineLegacy::initialize() { if (!ControllerScriptEngineBase::initialize()) { return false; @@ -125,7 +134,7 @@ bool ControllerScriptEngineLegacy::initialize() { new ControllerScriptInterfaceLegacy(this, m_logger); QJSValue settingsObject = m_pJSEngine->newObject(); - for (auto setting : m_settings) { + for (const auto& setting : qAsConst(m_settings)) { settingsObject.setProperty(std::get<0>(setting), std::get<1>(setting)); } engineGlobalObject.setProperty( diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index e0392a819bf..7cc2d94bf26 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -28,14 +28,15 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { public slots: void setScriptFiles(const QList& scripts); - inline void setSettings( - const QList>& settings) { - m_settings.clear(); - for (auto pSetting : settings) { - m_settings.append(std::tuple( - pSetting->variableName(), pSetting->value())); - } - } + + /// @brief Set the list of customizable settings and their currently set + /// value, ready to be used. This method will generate a JSValue from their + /// current state, meaning that any later mutation won't be used, and this + /// method should be called again + /// @param settings The list of settings in a valid state (initialized and + /// restored) + void setSettings( + const QList>& settings); private: bool evaluateScriptFile(const QFileInfo& scriptFile); diff --git a/src/test/controller_mapping_settings_test.cpp b/src/test/controller_mapping_settings_test.cpp new file mode 100644 index 00000000000..48214c51ec0 --- /dev/null +++ b/src/test/controller_mapping_settings_test.cpp @@ -0,0 +1,214 @@ + +#include + +#include + +// FIXME (acolombier): here we need the CPP file so the templated methods gets +// built. AFAIK, there is an alternate solution which is to include the cpp file +// in the CMake build list +#include "controllers/legacycontrollersettings.cpp" +#include "test/mixxxtest.h" + +class LegacyControllerMappingSettingsTest : public MixxxTest { +}; + +const char* const kValidBoolean = + ""; + +const char* const kValidInteger = + ""; + +// This setting has purposfully no custom "label" and description +const char* const kValidDouble = + "
    @@ -206,6 +206,9 @@ Qt::Vertical + + QSizePolicy::Expanding + 20 @@ -430,6 +433,12 @@ + + + 0 + 0 + + Mapping settings diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index a491149ae46..b476fcda2bb 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -136,7 +136,7 @@ void LegacyControllerMappingFileHandler::parseMappingSettingsElement( for (QDomElement element = current.firstChildElement(); !element.isNull(); element = element.nextSiblingElement()) { - const QString& tagName = element.tagName(); + const QString& tagName = element.tagName().toLower(); if (tagName == "option") { std::shared_ptr pSetting( LegacyControllerSettingBuilder::build(element)); @@ -151,8 +151,15 @@ void LegacyControllerMappingFileHandler::parseMappingSettingsElement( layout->addItem(pSetting); mapping->addSetting(pSetting); } else if (tagName == "row") { + LegacyControllerSettingsLayoutContainer::Disposition orientation = + element.attribute("orientation").trimmed().toLower() == + "vertical" + ? LegacyControllerSettingsLayoutContainer::VERTICAL + : LegacyControllerSettingsLayoutContainer::HORIZONTAL; std::unique_ptr row = - std::make_unique(); + std::make_unique( + LegacyControllerSettingsLayoutContainer::HORIZONTAL, + orientation); parseMappingSettingsElement(element, mapping, row); layout->addItem(std::move(row)); } else if (tagName == "group") { diff --git a/src/controllers/legacycontrollersettings.cpp b/src/controllers/legacycontrollersettings.cpp index 1772e2538a8..f92e57951e4 100644 --- a/src/controllers/legacycontrollersettings.cpp +++ b/src/controllers/legacycontrollersettings.cpp @@ -28,11 +28,31 @@ AbstractLegacyControllerSetting::AbstractLegacyControllerSetting(const QDomEleme } } -QWidget* AbstractLegacyControllerSetting::buildWidget(QWidget* parent) { - QWidget* pRoot = new QWidget(parent); +QWidget* AbstractLegacyControllerSetting::buildWidget(QWidget* pParent, + LegacyControllerSettingsLayoutContainer::Disposition orientation) { + QWidget* pRoot = new QWidget(pParent); QBoxLayout* pLayout = new QBoxLayout(QBoxLayout::LeftToRight); pLayout->setContentsMargins(0, 0, 0, 0); + if (orientation == LegacyControllerSettingsLayoutContainer::VERTICAL) { + auto* pSettingsContainer = dynamic_cast(pParent); + if (pSettingsContainer) { + connect(pSettingsContainer, + &WLegacyControllerSettingsContainer::orientationChanged, + this, + [pLayout, pParent]( + LegacyControllerSettingsLayoutContainer::Disposition + disposition) { + pLayout->setDirection(disposition == + LegacyControllerSettingsLayoutContainer:: + HORIZONTAL + ? QBoxLayout::TopToBottom + : QBoxLayout::LeftToRight); + pParent->layout()->invalidate(); + }); + } + } + QLabel* pLabelWidget = new QLabel(pRoot); pLabelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); pLabelWidget->setText(label()); @@ -60,9 +80,10 @@ LegacyControllerBooleanSetting::LegacyControllerBooleanSetting( m_dirtyValue = m_defaultValue; } -QWidget* LegacyControllerBooleanSetting::buildWidget(QWidget* parent) { +QWidget* LegacyControllerBooleanSetting::buildWidget( + QWidget* parent, LegacyControllerSettingsLayoutContainer::Disposition) { QWidget* pRoot = new QWidget(parent); - QBoxLayout* pLayout = new QBoxLayout(QBoxLayout::LeftToRight); + QBoxLayout* pLayout = new QHBoxLayout(); pLayout->setContentsMargins(0, 0, 0, 0); QLabel* pLabelWidget = new QLabel(pRoot); @@ -143,7 +164,7 @@ QWidget* LegacyControllerNumberSetting::buildInputWidget(QWidget* parent) { InputWidget* spinBox = new InputWidget(parent); - spinBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + spinBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); spinBox->setRange(this->m_minValue, this->m_maxValue); spinBox->setSingleStep(this->m_stepValue); diff --git a/src/controllers/legacycontrollersettings.h b/src/controllers/legacycontrollersettings.h index 01c7bebe296..1c0971a8ba3 100644 --- a/src/controllers/legacycontrollersettings.h +++ b/src/controllers/legacycontrollersettings.h @@ -16,6 +16,7 @@ #include #include "controllers/legacycontrollersettingsfactory.h" +#include "controllers/legacycontrollersettingslayout.h" /// @brief The abstract controller setting. Any type of setting will have to /// implement this base class @@ -29,7 +30,9 @@ class AbstractLegacyControllerSetting : public QObject { /// @param parent The parent widget for which this widget is being created. /// The parent widget will own the newly created widget /// @return a new widget - virtual QWidget* buildWidget(QWidget* parent); + virtual QWidget* buildWidget(QWidget* parent, + LegacyControllerSettingsLayoutContainer::Disposition orientation = + LegacyControllerSettingsLayoutContainer::HORIZONTAL); /// @brief Build a JSValue with the current setting value. The JSValue /// variant will use the appropriate type @@ -121,7 +124,10 @@ class LegacyControllerBooleanSetting virtual ~LegacyControllerBooleanSetting() = default; - QWidget* buildWidget(QWidget* parent) override; + QWidget* buildWidget(QWidget* parent, + LegacyControllerSettingsLayoutContainer::Disposition orientation = + LegacyControllerSettingsLayoutContainer::HORIZONTAL) + override; QJSValue value() const override { return QJSValue(m_currentValue); diff --git a/src/controllers/legacycontrollersettingslayout.cpp b/src/controllers/legacycontrollersettingslayout.cpp index 01cfb6f2617..52a78079422 100644 --- a/src/controllers/legacycontrollersettingslayout.cpp +++ b/src/controllers/legacycontrollersettingslayout.cpp @@ -4,42 +4,22 @@ #include #include #include +#include #include #include #include #include +#include "controllers/legacycontrollersettings.h" + void LegacyControllerSettingsLayoutContainer::addItem( std::shared_ptr setting) { - m_elements.push_back(std::make_unique(setting)); + m_elements.push_back(std::make_unique( + setting, m_widgetOrientation)); } QBoxLayout* LegacyControllerSettingsLayoutContainer::buildLayout(QWidget* pParent) const { - QBoxLayout* pLayout = nullptr; - - /// Find the active screen. If its size is too small, we force vertical orientation - QScreen* pActive = nullptr; - QWidget* pWidget = pParent; - - while (pWidget) { - auto* pW = pWidget->windowHandle(); - if (pW != nullptr) { - pActive = pW->screen(); - break; - } else { - pWidget = pWidget->parentWidget(); - } - } - - if (m_disposition == VERTICAL || - (pActive != nullptr && - pActive->availableSize().width() < - MIN_SCREEN_SIZE_FOR_CONTROLLER_SETTING_ROW)) { - pLayout = new QVBoxLayout(); - } else { - pLayout = new QHBoxLayout(); - pLayout->setSpacing(16); - } + QBoxLayout* pLayout = new QBoxLayout(QBoxLayout::TopToBottom); pParent->setLayout(pLayout); @@ -47,20 +27,13 @@ QBoxLayout* LegacyControllerSettingsLayoutContainer::buildLayout(QWidget* pParen } QWidget* LegacyControllerSettingsLayoutContainer::build(QWidget* pParent) { - QWidget* pContainer = new QWidget(pParent); + QWidget* pContainer = new WLegacyControllerSettingsContainer(m_disposition, pParent); QBoxLayout* pLayout = buildLayout(pContainer); pLayout->setContentsMargins(0, 0, 0, 0); for (auto& element : m_elements) { - auto* pWidget = element->build(pContainer); - if (pLayout->direction() == QBoxLayout::LeftToRight) { - auto* pLayout = dynamic_cast(pWidget->layout()); - if (pLayout != nullptr) { - pLayout->setDirection(QBoxLayout::TopToBottom); - } - } - pLayout->addWidget(pWidget); + pLayout->addWidget(element->build(pContainer)); } return pContainer; @@ -76,3 +49,34 @@ QWidget* LegacyControllerSettingsGroup::build(QWidget* pParent) { return pContainer; } + +QWidget* LegacyControllerSettingsLayoutItem::build(QWidget* parent) { + VERIFY_OR_DEBUG_ASSERT(m_setting.get() != nullptr) { + return nullptr; + } + return m_setting->buildWidget(parent, m_prefferedOrientation); +} + +void WLegacyControllerSettingsContainer::resizeEvent(QResizeEvent* event) { + if (m_prefferedOrientation == LegacyControllerSettingsLayoutContainer::VERTICAL) { + return; + } + + auto* pLayout = dynamic_cast(layout()); + if (pLayout == nullptr) { + return; + } + + if (event->size().width() < MIN_SCREEN_SIZE_FOR_CONTROLLER_SETTING_ROW && + pLayout->direction() == QBoxLayout::LeftToRight) { + pLayout->setDirection(QBoxLayout::TopToBottom); + pLayout->setSpacing(6); + emit orientationChanged(LegacyControllerSettingsLayoutContainer::VERTICAL); + } else if (event->size().width() >= + MIN_SCREEN_SIZE_FOR_CONTROLLER_SETTING_ROW && + pLayout->direction() == QBoxLayout::TopToBottom) { + pLayout->setDirection(QBoxLayout::LeftToRight); + pLayout->setSpacing(16); + emit orientationChanged(LegacyControllerSettingsLayoutContainer::HORIZONTAL); + } +} diff --git a/src/controllers/legacycontrollersettingslayout.h b/src/controllers/legacycontrollersettingslayout.h index 3ce83cb596c..d1b6387a0fa 100644 --- a/src/controllers/legacycontrollersettingslayout.h +++ b/src/controllers/legacycontrollersettingslayout.h @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -13,11 +14,12 @@ #include #include -#include "controllers/legacycontrollersettings.h" #include "defs_urls.h" #include "preferences/usersettings.h" -#define MIN_SCREEN_SIZE_FOR_CONTROLLER_SETTING_ROW 1280 +#define MIN_SCREEN_SIZE_FOR_CONTROLLER_SETTING_ROW 960 + +class AbstractLegacyControllerSetting; class LegacyControllerSettingsLayoutElement { public: @@ -37,8 +39,12 @@ class LegacyControllerSettingsLayoutContainer : public LegacyControllerSettingsL VERTICAL, }; - LegacyControllerSettingsLayoutContainer(Disposition disposition = HORIZONTAL) - : LegacyControllerSettingsLayoutElement(), m_disposition(disposition) { + LegacyControllerSettingsLayoutContainer( + Disposition disposition = HORIZONTAL, + Disposition widgetOrientation = HORIZONTAL) + : LegacyControllerSettingsLayoutElement(), + m_disposition(disposition), + m_widgetOrientation(widgetOrientation) { } LegacyControllerSettingsLayoutContainer(const LegacyControllerSettingsLayoutContainer& other) { m_elements.reserve(other.m_elements.size()); @@ -62,6 +68,7 @@ class LegacyControllerSettingsLayoutContainer : public LegacyControllerSettingsL QBoxLayout* buildLayout(QWidget* parent) const; Disposition m_disposition; + Disposition m_widgetOrientation; std::vector> m_elements; }; @@ -80,15 +87,20 @@ class LegacyControllerSettingsGroup : public LegacyControllerSettingsLayoutConta } QWidget* build(QWidget* parent) override; - + private: QString m_label; }; class LegacyControllerSettingsLayoutItem : public LegacyControllerSettingsLayoutElement { public: - LegacyControllerSettingsLayoutItem(std::shared_ptr setting) - : LegacyControllerSettingsLayoutElement(), m_setting(setting) { + LegacyControllerSettingsLayoutItem( + std::shared_ptr setting, + LegacyControllerSettingsLayoutContainer::Disposition orientation = + LegacyControllerSettingsGroup::HORIZONTAL) + : LegacyControllerSettingsLayoutElement(), + m_setting(setting), + m_prefferedOrientation(orientation) { } virtual ~LegacyControllerSettingsLayoutItem() = default; @@ -96,13 +108,29 @@ class LegacyControllerSettingsLayoutItem : public LegacyControllerSettingsLayout return std::make_unique(*this); } - QWidget* build(QWidget* parent) override { - VERIFY_OR_DEBUG_ASSERT(m_setting.get() != nullptr) { - return nullptr; - } - return m_setting->buildWidget(parent); - } + QWidget* build(QWidget* parent) override; private: std::shared_ptr m_setting; + LegacyControllerSettingsLayoutContainer::Disposition m_prefferedOrientation; +}; + +class WLegacyControllerSettingsContainer : public QWidget { + Q_OBJECT + public: + WLegacyControllerSettingsContainer( + LegacyControllerSettingsLayoutContainer::Disposition + prefferedOrientation, + QWidget* parent) + : QWidget(parent), m_prefferedOrientation(prefferedOrientation) { + } + + protected: + void resizeEvent(QResizeEvent* event); + + signals: + void orientationChanged(LegacyControllerSettingsLayoutContainer::Disposition); + + private: + LegacyControllerSettingsLayoutContainer::Disposition m_prefferedOrientation; }; From d8a925fd61a41184a6ebae59327212d5cf788a70 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Tue, 28 Feb 2023 23:38:01 +0000 Subject: [PATCH 071/124] feat(Controller): adding tests for controller settings --- src/controllers/dlgprefcontrollerdlg.ui | 2 +- src/controllers/legacycontrollermapping.cpp | 2 +- src/controllers/legacycontrollermapping.h | 21 +- .../legacycontrollermappingfilehandler.cpp | 36 ++- .../legacycontrollermappingfilehandler.h | 24 +- src/controllers/legacycontrollersettings.cpp | 60 ++-- src/controllers/legacycontrollersettings.h | 11 +- .../legacycontrollersettingslayout.cpp | 16 +- .../legacycontrollersettingslayout.h | 14 +- ...legacymidicontrollermappingfilehandler.cpp | 2 +- src/test/controller_mapping_settings_test.cpp | 298 +++++++++++++++++- 11 files changed, 409 insertions(+), 77 deletions(-) diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index ba82eb68649..9b296011d89 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -7,7 +7,7 @@ 0 0 507 - 497 + 437 diff --git a/src/controllers/legacycontrollermapping.cpp b/src/controllers/legacycontrollermapping.cpp index 1c6863e040e..cd501f6ba38 100644 --- a/src/controllers/legacycontrollermapping.cpp +++ b/src/controllers/legacycontrollermapping.cpp @@ -23,7 +23,7 @@ void LegacyControllerMapping::restoreSettings(const QFileInfo& mappingFile, pConfig->remove(key); continue; } - auto& pSetting = availableSettings.at(availableSettingKeys.indexOf(key.item)); + const auto& pSetting = availableSettings.at(availableSettingKeys.indexOf(key.item)); QString value = pConfig->getValueString(key); if (!pSetting->valid()) { qWarning() << "The setting" << pSetting->variableName() diff --git a/src/controllers/legacycontrollermapping.h b/src/controllers/legacycontrollermapping.h index a0934281e7b..a989fe5b8a8 100644 --- a/src/controllers/legacycontrollermapping.h +++ b/src/controllers/legacycontrollermapping.h @@ -18,7 +18,6 @@ #include "defs_urls.h" #include "preferences/usersettings.h" -// TODO (acolombier) is it okay to keep it as it? Or shall we generate a UUID from that pair? #define CONTROLLER_SETTINGS_PREFERENCE_GROUP_KEY "[ControllerSettings_%1_%2]" /// This class represents a controller mapping, containing the data elements that @@ -83,16 +82,22 @@ class LegacyControllerMapping { /// Adds a setting option to the list of setting option for this mapping. /// The option added must be a valid option. /// @param option The option to add - void addSetting(std::shared_ptr option) { - // if (m_settings.contains(option->variableName())){ - // qWarning() << QString("Mapping setting duplication detected. - // Keeping the first version of '%1'.").arg(option->variableName()); - // return; - // } + /// @return whether or not the setting was added successfully. + bool addSetting(std::shared_ptr option) { VERIFY_OR_DEBUG_ASSERT(option->valid()) { - return; + return false; + } + for (const auto& setting : qAsConst(m_settings)) { + if (*setting == *option) { + qWarning() << "Mapping setting duplication detected for " + "setting with name" + << option->variableName() + << ". Keeping the first occurrence."; + return false; + } } m_settings.append(option); + return true; } /// @brief Set a setting layout as they should be perceived when edited in diff --git a/src/controllers/legacycontrollermappingfilehandler.cpp b/src/controllers/legacycontrollermappingfilehandler.cpp index b476fcda2bb..34b66017cc4 100644 --- a/src/controllers/legacycontrollermappingfilehandler.cpp +++ b/src/controllers/legacycontrollermappingfilehandler.cpp @@ -66,6 +66,9 @@ std::shared_ptr LegacyControllerMappingFileHandler::loa std::shared_ptr pMapping = pHandler->load( mappingFile.absoluteFilePath(), systemMappingsPath); + + // FIXME(acolombier): is `delete pHandler;` not missing? + if (pMapping) { pMapping->setDirty(false); } @@ -129,10 +132,9 @@ void LegacyControllerMappingFileHandler::parseMappingSettings( void LegacyControllerMappingFileHandler::parseMappingSettingsElement( const QDomElement& current, - std::shared_ptr mapping, + std::shared_ptr pMapping, const std::unique_ptr& layout) const { - // TODO (acolombier) Add test for the parser for (QDomElement element = current.firstChildElement(); !element.isNull(); element = element.nextSiblingElement()) { @@ -141,15 +143,25 @@ void LegacyControllerMappingFileHandler::parseMappingSettingsElement( std::shared_ptr pSetting( LegacyControllerSettingBuilder::build(element)); if (pSetting.get() == nullptr) { - qDebug() << "Could not parse the unknown controller setting. Ignoring it."; + qDebug() << "Ignoring unsupported controller setting in file" + << pMapping->filePath() << "at line" + << element.lineNumber() << "."; continue; } if (!pSetting->valid()) { - qDebug() << "The parsed setting appears to be invalid. Discarding it."; + qDebug() << "The parsed setting in file" << pMapping->filePath() + << "at line" << element.lineNumber() + << "appears to be invalid. It will be ignored."; + continue; + } + if (pMapping->addSetting(pSetting)) { + layout->addItem(pSetting); + } else { + qDebug() << "The parsed setting in file" << pMapping->filePath() + << "at line" << element.lineNumber() + << "couldn't be added. Its layout information will also be ignored."; continue; } - layout->addItem(pSetting); - mapping->addSetting(pSetting); } else if (tagName == "row") { LegacyControllerSettingsLayoutContainer::Disposition orientation = element.attribute("orientation").trimmed().toLower() == @@ -160,17 +172,19 @@ void LegacyControllerMappingFileHandler::parseMappingSettingsElement( std::make_unique( LegacyControllerSettingsLayoutContainer::HORIZONTAL, orientation); - parseMappingSettingsElement(element, mapping, row); + parseMappingSettingsElement(element, pMapping, row); layout->addItem(std::move(row)); } else if (tagName == "group") { std::unique_ptr group = std::make_unique( element.attribute("label")); - parseMappingSettingsElement(element, mapping, group); + parseMappingSettingsElement(element, pMapping, group); layout->addItem(std::move(group)); } else { - qDebug() << "Unsupported tag" << tagName - << "for controller layout settings. Discarding it."; + qDebug() << "Ignoring unsupported tag" << tagName + << "in file" << pMapping->filePath() + << "on line" << element.lineNumber() + << "for controller layout settings. Check the documentation supported tags."; continue; } } @@ -261,7 +275,7 @@ QDomDocument LegacyControllerMappingFileHandler::buildRootWithScripts( QString blank = "\n" "\n" - "\n"; + "\n"; doc.setContent(blank); QDomElement rootNode = doc.documentElement(); diff --git a/src/controllers/legacycontrollermappingfilehandler.h b/src/controllers/legacycontrollermappingfilehandler.h index 8daf2f21602..de7186532ca 100644 --- a/src/controllers/legacycontrollermappingfilehandler.h +++ b/src/controllers/legacycontrollermappingfilehandler.h @@ -47,17 +47,6 @@ class LegacyControllerMappingFileHandler { void parseMappingSettings(const QDomElement& root, std::shared_ptr mapping) const; - /// @brief Recursively parse setting definition and layout information - /// within a setting node - /// @param current The setting node (MixxxControllerPreset.settings) or any - /// children nodes - /// @param mapping The mapping object to populate with the gathered data - /// @param layout The currently active layout, on which new setting item - /// (leaf) should be attached - void parseMappingSettingsElement(const QDomElement& current, - std::shared_ptr mapping, - const std::unique_ptr& layout) const; - /// Adds script files from XML to the LegacyControllerMapping. /// /// This function parses the supplied QDomElement structure, finds the @@ -78,8 +67,21 @@ class LegacyControllerMappingFileHandler { bool writeDocument(const QDomDocument& root, const QString& fileName) const; private: + /// @brief Recursively parse setting definition and layout information + /// within a setting node + /// @param current The setting node (MixxxControllerPreset.settings) or any + /// children nodes + /// @param mapping The mapping object to populate with the gathered data + /// @param layout The currently active layout, on which new setting item + /// (leaf) should be attached + void parseMappingSettingsElement(const QDomElement& current, + std::shared_ptr mapping, + const std::unique_ptr& layout) const; + // Sub-classes implement this. virtual std::shared_ptr load(const QDomElement& root, const QString& filePath, const QDir& systemMappingPath) = 0; + + friend class LegacyControllerMappingSettingsTest_parseSettingBlock_Test; }; diff --git a/src/controllers/legacycontrollersettings.cpp b/src/controllers/legacycontrollersettings.cpp index f92e57951e4..c6eb2fda2f4 100644 --- a/src/controllers/legacycontrollersettings.cpp +++ b/src/controllers/legacycontrollersettings.cpp @@ -6,7 +6,7 @@ #include #include -// TODO (acolombier): remove "new" where possible and use parented_ptr +#include "util/parented_ptr.h" LegacyControllerSettingBuilder* LegacyControllerSettingBuilder::__self = nullptr; @@ -30,8 +30,9 @@ AbstractLegacyControllerSetting::AbstractLegacyControllerSetting(const QDomEleme QWidget* AbstractLegacyControllerSetting::buildWidget(QWidget* pParent, LegacyControllerSettingsLayoutContainer::Disposition orientation) { - QWidget* pRoot = new QWidget(pParent); + auto pRoot = make_parented(pParent); QBoxLayout* pLayout = new QBoxLayout(QBoxLayout::LeftToRight); + pLayout->setContentsMargins(0, 0, 0, 0); if (orientation == LegacyControllerSettingsLayoutContainer::VERTICAL) { @@ -53,7 +54,7 @@ QWidget* AbstractLegacyControllerSetting::buildWidget(QWidget* pParent, } } - QLabel* pLabelWidget = new QLabel(pRoot); + auto pLabelWidget = make_parented(pRoot); pLabelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); pLabelWidget->setText(label()); @@ -81,40 +82,27 @@ LegacyControllerBooleanSetting::LegacyControllerBooleanSetting( } QWidget* LegacyControllerBooleanSetting::buildWidget( - QWidget* parent, LegacyControllerSettingsLayoutContainer::Disposition) { - QWidget* pRoot = new QWidget(parent); - QBoxLayout* pLayout = new QHBoxLayout(); - pLayout->setContentsMargins(0, 0, 0, 0); - - QLabel* pLabelWidget = new QLabel(pRoot); - pLabelWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - pLabelWidget->setText(label()); - - if (!description().isEmpty()) { - pRoot->setToolTip(QString("

    %1

    ").arg(description())); - } - - pLayout->addWidget(buildInputWidget(pRoot)); - pLayout->addWidget(pLabelWidget); - - pRoot->setLayout(pLayout); - - return pRoot; + QWidget* pParent, LegacyControllerSettingsLayoutContainer::Disposition) { + return buildInputWidget(pParent); } -QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* parent) { - QCheckBox* checkBox = new QCheckBox("", parent); - checkBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); +QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* pParent) { + auto pCheckBox = make_parented(label(), pParent); + pCheckBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); if (m_currentValue) { - checkBox->setCheckState(Qt::Checked); + pCheckBox->setCheckState(Qt::Checked); + } + + if (!description().isEmpty()) { + pCheckBox->setToolTip(QString("

    %1

    ").arg(description())); } - connect(checkBox, &QCheckBox::stateChanged, this, [this](int state) { + connect(pCheckBox, &QCheckBox::stateChanged, this, [this](int state) { m_dirtyValue = state == Qt::Checked; emit changed(); }); - return checkBox; + return pCheckBox; } bool LegacyControllerBooleanSetting::match(const QDomElement& element) { @@ -162,8 +150,8 @@ template::buildInputWidget(QWidget* parent) { - InputWidget* spinBox = new InputWidget(parent); + InputWidget>::buildInputWidget(QWidget* pParent) { + auto spinBox = make_parented(pParent); spinBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); spinBox->setRange(this->m_minValue, this->m_maxValue); @@ -211,12 +199,12 @@ LegacyControllerRealSetting::LegacyControllerRealSetting(const QDomElement& elem } } -QWidget* LegacyControllerRealSetting::buildInputWidget(QWidget* parent) { +QWidget* LegacyControllerRealSetting::buildInputWidget(QWidget* pParent) { QDoubleSpinBox* spinBox = dynamic_cast( - LegacyControllerNumberSetting::buildInputWidget(parent)); + LegacyControllerNumberSetting::buildInputWidget(pParent)); VERIFY_OR_DEBUG_ASSERT(spinBox != nullptr) { - qWarning() << "Unable to set precision on the controller setting " - "input: tt does not appear to be a valid QDoubleSpinBox"; + qWarning() << "Unable to set precision on the input widget " + "input: it does not appear to be a valid QDoubleSpinBox"; return spinBox; } spinBox->setDecimals(m_precisionValue); @@ -271,8 +259,8 @@ void LegacyControllerEnumSetting::parse(const QString& in, bool* ok) { } } -QWidget* LegacyControllerEnumSetting::buildInputWidget(QWidget* parent) { - QComboBox* comboBox = new QComboBox(parent); +QWidget* LegacyControllerEnumSetting::buildInputWidget(QWidget* pParent) { + auto comboBox = make_parented(pParent); for (const auto& value : qAsConst(m_options)) { comboBox->addItem(std::get<1>(value)); diff --git a/src/controllers/legacycontrollersettings.h b/src/controllers/legacycontrollersettings.h index 1c0971a8ba3..bf3ab316945 100644 --- a/src/controllers/legacycontrollersettings.h +++ b/src/controllers/legacycontrollersettings.h @@ -93,6 +93,10 @@ class AbstractLegacyControllerSetting : public QObject { return m_description; } + bool operator==(const AbstractLegacyControllerSetting& other) const noexcept { + return variableName() == other.variableName(); + } + protected: AbstractLegacyControllerSetting(const QString& variableName, const QString& label, @@ -278,6 +282,9 @@ class LegacyControllerNumberSetting SettingType m_stepValue; SettingType m_dirtyValue; + + friend class LegacyControllerMappingSettingsTest_integerSettingEditing_Test; + friend class LegacyControllerMappingSettingsTest_doubleSettingEditing_Test; }; template @@ -332,7 +339,7 @@ class LegacyControllerEnumSetting } QString stringify() const override { - return std::get<0>(m_options.value(m_currentValue)); + return std::get<0>(m_options.value((int)m_currentValue)); } void parse(const QString& in, bool* ok) override; bool isDefault() const override { @@ -386,4 +393,6 @@ class LegacyControllerEnumSetting size_t m_defaultValue; size_t m_dirtyValue; + + friend class LegacyControllerMappingSettingsTest_enumSettingEditing_Test; }; diff --git a/src/controllers/legacycontrollersettingslayout.cpp b/src/controllers/legacycontrollersettingslayout.cpp index 52a78079422..8b2ff08e4d5 100644 --- a/src/controllers/legacycontrollersettingslayout.cpp +++ b/src/controllers/legacycontrollersettingslayout.cpp @@ -11,6 +11,7 @@ #include #include "controllers/legacycontrollersettings.h" +#include "util/parented_ptr.h" void LegacyControllerSettingsLayoutContainer::addItem( std::shared_ptr setting) { @@ -19,7 +20,7 @@ void LegacyControllerSettingsLayoutContainer::addItem( } QBoxLayout* LegacyControllerSettingsLayoutContainer::buildLayout(QWidget* pParent) const { - QBoxLayout* pLayout = new QBoxLayout(QBoxLayout::TopToBottom); + auto pLayout = make_parented(QBoxLayout::TopToBottom); pParent->setLayout(pLayout); @@ -27,20 +28,27 @@ QBoxLayout* LegacyControllerSettingsLayoutContainer::buildLayout(QWidget* pParen } QWidget* LegacyControllerSettingsLayoutContainer::build(QWidget* pParent) { - QWidget* pContainer = new WLegacyControllerSettingsContainer(m_disposition, pParent); + auto pContainer = make_parented(m_disposition, pParent); QBoxLayout* pLayout = buildLayout(pContainer); pLayout->setContentsMargins(0, 0, 0, 0); + auto& lastElement = m_elements.back(); for (auto& element : m_elements) { - pLayout->addWidget(element->build(pContainer)); + auto* pWidget = element->build(pContainer); + pWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + pLayout->addWidget(pWidget); + if (element != lastElement) { + pLayout->addItem(new QSpacerItem( + 10, 10, QSizePolicy::Expanding, QSizePolicy::Fixed)); + } } return pContainer; } QWidget* LegacyControllerSettingsGroup::build(QWidget* pParent) { - QWidget* pContainer = new QGroupBox(m_label, pParent); + auto pContainer = make_parented(m_label, pParent); QBoxLayout* pLayout = buildLayout(pContainer); for (auto& element : m_elements) { diff --git a/src/controllers/legacycontrollersettingslayout.h b/src/controllers/legacycontrollersettingslayout.h index d1b6387a0fa..db9cb63eca6 100644 --- a/src/controllers/legacycontrollersettingslayout.h +++ b/src/controllers/legacycontrollersettingslayout.h @@ -21,6 +21,7 @@ class AbstractLegacyControllerSetting; +/// @brief Layout information used for controller setting when rendered in the Preference Dialog class LegacyControllerSettingsLayoutElement { public: LegacyControllerSettingsLayoutElement() { @@ -32,8 +33,14 @@ class LegacyControllerSettingsLayoutElement { virtual QWidget* build(QWidget* parent) = 0; }; +/// @brief This layout element can hold others element. It is also the one used +/// to represent a `row` in the settings class LegacyControllerSettingsLayoutContainer : public LegacyControllerSettingsLayoutElement { public: + /// @brief This is a simplified representation of disposition orientation. This used to + /// define how a container orients its children. This is also used by layout + /// items to decide how the label should be rendered alongside the input + /// widget enum Disposition { HORIZONTAL = 0, VERTICAL, @@ -57,6 +64,11 @@ class LegacyControllerSettingsLayoutContainer : public LegacyControllerSettingsL return std::make_unique(*this); } + /// @brief This helper method allows to add a LegacyControllerSetting + /// directly, without to have it to wrap within an item object. This is + /// helpful as the item that will be create to wrap will be initialised with + /// the right parameters + /// @param setting The controller setting to add to the layout container void addItem(std::shared_ptr setting); void addItem(std::unique_ptr&& container) { m_elements.push_back(std::move(container)); @@ -87,7 +99,7 @@ class LegacyControllerSettingsGroup : public LegacyControllerSettingsLayoutConta } QWidget* build(QWidget* parent) override; - + private: QString m_label; }; diff --git a/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp b/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp index 48d97d2ea83..4735a6a3255 100644 --- a/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp +++ b/src/controllers/midi/legacymidicontrollermappingfilehandler.cpp @@ -11,7 +11,7 @@ std::shared_ptr LegacyMidiControllerMappingFileHandler::load(const QDomElement& root, const QString& filePath, const QDir& systemMappingsPath) { - // TODO (acolombier): support for controller settings + // TODO (XXX): support for controller settings if (root.isNull()) { return nullptr; } diff --git a/src/test/controller_mapping_settings_test.cpp b/src/test/controller_mapping_settings_test.cpp index 48214c51ec0..3d36d136d79 100644 --- a/src/test/controller_mapping_settings_test.cpp +++ b/src/test/controller_mapping_settings_test.cpp @@ -6,6 +6,8 @@ // FIXME (acolombier): here we need the CPP file so the templated methods gets // built. AFAIK, there is an alternate solution which is to include the cpp file // in the CMake build list +#include "controllers/legacycontrollermapping.h" +#include "controllers/legacycontrollermappingfilehandler.h" #include "controllers/legacycontrollersettings.cpp" #include "test/mixxxtest.h" @@ -27,6 +29,11 @@ const char* const kValidDouble = ""; +const char* const kValidEnumOption = "%2"; + TEST_F(LegacyControllerMappingSettingsTest, booleanSettingParsing) { QDomDocument doc; doc.setContent(QString(kValidBoolean).arg("false").toLatin1()); @@ -79,6 +86,8 @@ TEST_F(LegacyControllerMappingSettingsTest, booleanSettingEditing) { EXPECT_TRUE(ok); EXPECT_FALSE(setting->isDirty()); EXPECT_EQ(setting->stringify(), "true"); + EXPECT_TRUE(setting->value().isBool()); + EXPECT_TRUE(setting->value().toBool()); EXPECT_FALSE(setting->isDefault()); setting->parse("1", &ok); EXPECT_TRUE(ok); @@ -89,9 +98,14 @@ TEST_F(LegacyControllerMappingSettingsTest, booleanSettingEditing) { setting->parse("TRUE", &ok); EXPECT_TRUE(ok); EXPECT_EQ(setting->stringify(), "true"); + EXPECT_TRUE(setting->value().isBool()); + EXPECT_TRUE(setting->value().toBool()); setting->reset(); EXPECT_EQ(setting->stringify(), "false"); EXPECT_TRUE(setting->isDefault()); + + EXPECT_TRUE(setting->value().isBool()); + EXPECT_FALSE(setting->value().toBool()); } TEST_F(LegacyControllerMappingSettingsTest, integerSettingParsing) { @@ -151,7 +165,46 @@ TEST_F(LegacyControllerMappingSettingsTest, integerSettingParsing) { } TEST_F(LegacyControllerMappingSettingsTest, integerSettingEditing) { - // TODO (acolombier) Add test for setting edition + QDomDocument doc; + doc.setContent( + QByteArray(" From 31b56bc4269fca0ca0495051c89446d7c0e618fc Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Fri, 5 Apr 2024 11:34:09 +0200 Subject: [PATCH 114/124] fix(util): fix ambigous overload error due to native qDebug impl for std::optional --- src/util/optional.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/util/optional.h b/src/util/optional.h index 195bf17bad0..47cf0b7a0c3 100644 --- a/src/util/optional.h +++ b/src/util/optional.h @@ -1,6 +1,10 @@ #pragma once #include +// support for QDebugging std::optional has been added natively in Qt 6.7 +// this definition clashes with Qt's so disable it. +#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) + #include template @@ -11,3 +15,5 @@ inline QDebug operator<<(QDebug dbg, std::optional arg) { return dbg << "nullopt"; } } + +#endif From 492ce2a2cb9a48efb9e3abdae63936618f9ceb97 Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 6 Apr 2024 12:37:31 +0100 Subject: [PATCH 115/124] fix: segfault when restoring default controller settings --- src/controllers/legacycontrollersettings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/legacycontrollersettings.cpp b/src/controllers/legacycontrollersettings.cpp index 4b4a26a58dd..f97d2b9d628 100644 --- a/src/controllers/legacycontrollersettings.cpp +++ b/src/controllers/legacycontrollersettings.cpp @@ -137,7 +137,7 @@ QWidget* LegacyControllerBooleanSetting::buildInputWidget(QWidget* pParent) { pCheckBox->setToolTip(QString("

    %1

    ").arg(description())); } - connect(this, &AbstractLegacyControllerSetting::valueReset, pCheckBox, [this, &pCheckBox]() { + connect(this, &AbstractLegacyControllerSetting::valueReset, pCheckBox, [this, pCheckBox]() { pCheckBox->setCheckState(m_editedValue ? Qt::Checked : Qt::Unchecked); }); @@ -260,7 +260,7 @@ QWidget* LegacyControllerEnumSetting::buildInputWidget(QWidget* pParent) { } pComboBox->setCurrentIndex(static_cast(m_editedValue)); - connect(this, &AbstractLegacyControllerSetting::valueReset, pComboBox, [this, &pComboBox]() { + connect(this, &AbstractLegacyControllerSetting::valueReset, pComboBox, [this, pComboBox]() { pComboBox->setCurrentIndex(static_cast(m_editedValue)); }); From 6494c6f159608a15238ac0149e7cb67171cc4eed Mon Sep 17 00:00:00 2001 From: Antoine C Date: Sat, 6 Apr 2024 13:14:50 +0100 Subject: [PATCH 116/124] fix: incorrect reboot required notification on preference updates --- src/preferences/dialog/dlgprefinterface.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/preferences/dialog/dlgprefinterface.cpp b/src/preferences/dialog/dlgprefinterface.cpp index 5bf5e07c436..93519c1baaa 100644 --- a/src/preferences/dialog/dlgprefinterface.cpp +++ b/src/preferences/dialog/dlgprefinterface.cpp @@ -202,6 +202,9 @@ DlgPrefInterface::DlgPrefInterface( } else #endif { +#ifdef MIXXX_USE_QML + m_multiSampling = 0; +#endif multiSamplingLabel->hide(); multiSamplingComboBox->hide(); } From cbd3c9f86b5623392909f372550982f11bc6cae7 Mon Sep 17 00:00:00 2001 From: m0dB Date: Sat, 6 Apr 2024 15:00:02 +0200 Subject: [PATCH 117/124] fix rectangle of loop gradient (it erroenously used the position of the hotcue marker texture) and rounding of play position marker --- .../allshader/waveformrendermark.cpp | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/waveform/renderers/allshader/waveformrendermark.cpp b/src/waveform/renderers/allshader/waveformrendermark.cpp index eac87b8e4c5..7887ceb5332 100644 --- a/src/waveform/renderers/allshader/waveformrendermark.cpp +++ b/src/waveform/renderers/allshader/waveformrendermark.cpp @@ -169,9 +169,14 @@ void allshader::WaveformRenderMark::paintGL() { const double samplePosition = pMark->getSamplePosition(); if (samplePosition != Cue::kNoPosition) { - float currentMarkPoint = static_cast( - m_waveformRenderer->transformSamplePositionInRendererWorld( - samplePosition)); + const float currentMarkPoint = + std::round( + static_cast( + m_waveformRenderer + ->transformSamplePositionInRendererWorld( + samplePosition)) * + devicePixelRatio) / + devicePixelRatio; const double sampleEndPosition = pMark->getSampleEndPosition(); // Pixmaps are expected to have the mark stroke at the center, @@ -179,12 +184,11 @@ void allshader::WaveformRenderMark::paintGL() { // exactly at the sample position. const float markHalfWidth = pTexture->width() / devicePixelRatio / 2.f; const float drawOffset = currentMarkPoint - markHalfWidth; - currentMarkPoint = qRound(drawOffset * devicePixelRatio) / devicePixelRatio; bool visible = false; // Check if the current point needs to be displayed. - if (currentMarkPoint > -markHalfWidth && - currentMarkPoint < m_waveformRenderer->getLength() + + if (drawOffset > -markHalfWidth && + drawOffset < m_waveformRenderer->getLength() + markHalfWidth) { drawTexture(drawOffset, 0, pTexture); visible = true; @@ -222,9 +226,12 @@ void allshader::WaveformRenderMark::paintGL() { } m_waveformRenderer->setMarkPositions(marksOnScreen); - const float currentMarkPoint = std::floor( - static_cast(m_waveformRenderer->getPlayMarkerPosition() * - m_waveformRenderer->getLength())); + const float currentMarkPoint = + std::round(static_cast( + m_waveformRenderer->getPlayMarkerPosition() * + m_waveformRenderer->getLength()) * + devicePixelRatio) / + devicePixelRatio; const float markHalfWidth = m_pPlayPosMarkTexture->width() / devicePixelRatio / 2.f; const float drawOffset = currentMarkPoint - markHalfWidth; From 1f7c5f9e0b166a8740013aff8437b3973781bb3e Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 7 Apr 2024 18:03:57 +0200 Subject: [PATCH 118/124] Tools: Add rpm_buildenv.sh --- tools/rpm_buildenv.sh | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100755 tools/rpm_buildenv.sh diff --git a/tools/rpm_buildenv.sh b/tools/rpm_buildenv.sh new file mode 100755 index 00000000000..f21eb62b500 --- /dev/null +++ b/tools/rpm_buildenv.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# This script works with Fedora. While not tested, it may also work with its +# derivatives (CentOS, RHEL) or even other RPM-based distros, contributions +# fixing any deviations in package naming are welcome here. +# shellcheck disable=SC1091 +set -o pipefail + +case "$1" in + name) + echo "No build environment name required for RPM-based distros." >&2 + echo "This script installs the build dependencies via dnf using the \"setup\" option." >&2 + ;; + + setup) + sudo dnf install -y \ + appstream \ + ccache \ + chrpath \ + cmake \ + desktop-file-utils \ + faad2 \ + ffmpeg-devel \ + fftw-devel \ + flac-devel \ + gcc-c++ \ + gmock-devel \ + google-benchmark-devel \ + gtest-devel \ + guidelines-support-library-devel \ + hidapi-devel \ + lame-devel \ + libchromaprint-devel \ + libebur128-devel \ + libid3tag-devel \ + libmad-devel \ + libmodplug-devel \ + libmp4v2-devel \ + libsndfile-devel \ + libusb1-devel \ + libvorbis-devel \ + lilv-devel \ + mesa-libGL-devel \ + mesa-libGLU-devel \ + ninja-build \ + opus-devel \ + opusfile-devel \ + portaudio-devel \ + portmidi-devel \ + protobuf-compiler \ + protobuf-lite-devel \ + qt6-qt{5compat,base,base-private,declarative,shadertools,svg}-devel \ + qtkeychain-qt6-devel \ + rubberband-devel \ + soundtouch-devel \ + sqlite-devel \ + taglib-devel \ + upower-devel \ + wavpack-devel \ + zlib-devel + ;; + *) + echo "Usage: $0 [options]" + echo "" + echo "options:" + echo " help Displays this help." + echo " name Displays the name of the required build environment." + echo " setup Installs the build environment." + ;; +esac From 89b732a8d5096b1bb1ecd3305f45f5c7997fba13 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 7 Apr 2024 18:13:12 +0200 Subject: [PATCH 119/124] README: Update buildenv instructions --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fba0236afa8..5e6a3a20dc6 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,18 @@ source code and navigate to it: $ git clone https://github.com/mixxxdj/mixxx.git $ cd mixxx -Fetch the required dependencies and set up the build environment (on Windows, -macOS and Debian/Ubuntu, you can do that by running -`tools\windows_buildenv.bat`, `source tools/macos_buildenv.sh setup` or `source -tools/debian_buildenv.sh setup` respectively), then run: +Fetch the required dependencies and set up the build environment by running the +corresponding command for your operating system: + +| OS | Command | +| -- | ------- | +| Windows | `tools\windows_buildenv.bat` | +| macOS | `source tools/macos_buildenv.sh setup` | +| Debian/Ubuntu | `source tools/debian_buildenv.sh setup` | +| Fedora | `source tools/rpm_buildenv.sh setup` | +| Other Linux distros | See the [wiki article](https://github.com/mixxxdj/mixxx/wiki/Compiling%20on%20Linux) | + +To build Mixxx, run $ mkdir build $ cd build From 0d527209759b3a363eeaa993c12bf0fbdb55b4b8 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 7 Apr 2024 18:37:50 +0200 Subject: [PATCH 120/124] RPM buildenv: Install RPM Fusion repo --- tools/rpm_buildenv.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/rpm_buildenv.sh b/tools/rpm_buildenv.sh index f21eb62b500..7d4c3713533 100755 --- a/tools/rpm_buildenv.sh +++ b/tools/rpm_buildenv.sh @@ -12,6 +12,13 @@ case "$1" in ;; setup) + if grep -qP '^NAME=.*Fedora.*' /etc/os-release; then + # Add RPM Fusion repository (required for faad2 and ffmpeg-devel) + sudo dnf install -y "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm" + else + echo "Warning: You are not running Fedora and may have to set up RPM Fusion manually if there are missing packages!" + fi + sudo dnf install -y \ appstream \ ccache \ From 95bf9841d00a2b24cb8c1bca56bff0695c767974 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 7 Apr 2024 18:45:58 +0200 Subject: [PATCH 121/124] RPM buildenv: Add a note on the builddep command --- tools/rpm_buildenv.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/rpm_buildenv.sh b/tools/rpm_buildenv.sh index 7d4c3713533..58e6f7e9e42 100755 --- a/tools/rpm_buildenv.sh +++ b/tools/rpm_buildenv.sh @@ -19,6 +19,11 @@ case "$1" in echo "Warning: You are not running Fedora and may have to set up RPM Fusion manually if there are missing packages!" fi + # Install the build dependencies. + # This list is largely identical to what `dnf builddep mixxx` would + # install, with the exception of replacing Qt 5 with Qt 6 (once 2.5 is + # released, we could install `dnf-command(builddep)` and then use `dnf + # builddep mixxx`). sudo dnf install -y \ appstream \ ccache \ From 8c625647eeec6a20bde4cfc7079c1c78ae1e9c72 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 7 Apr 2024 21:14:03 +0200 Subject: [PATCH 122/124] SoundDevice: Clarify that DAZ/FTZ are unsupported on Wasm --- src/soundio/sounddevicenetwork.cpp | 8 +++++--- src/soundio/sounddeviceportaudio.cpp | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/soundio/sounddevicenetwork.cpp b/src/soundio/sounddevicenetwork.cpp index 8cb975fa68e..0c74aaf126f 100644 --- a/src/soundio/sounddevicenetwork.cpp +++ b/src/soundio/sounddevicenetwork.cpp @@ -447,9 +447,11 @@ void SoundDeviceNetwork::callbackProcessClkRef() { // performance penalty of ~20 // https://github.com/mixxxdj/mixxx/issues/7747 - // On Emscripten (WebAssembly) denormals-as-zero/flush-as-zero is not - // configurable, for discussion and links see - // https://github.com/mixxxdj/mixxx/pull/12917 + // On Emscripten (WebAssembly) denormals-as-zero/flush-as-zero are + // neither supported nor configurable. This may lead to degraded + // performance compared to other platforms and may be addressed in the + // future if/when WebAssembly adds support for DAZ/FTZ. For further + // discussion and links see https://github.com/mixxxdj/mixxx/pull/12917 #if defined(__SSE__) && !defined(__EMSCRIPTEN__) if (!_MM_GET_DENORMALS_ZERO_MODE()) { diff --git a/src/soundio/sounddeviceportaudio.cpp b/src/soundio/sounddeviceportaudio.cpp index c23c54b916e..5b413ac6012 100644 --- a/src/soundio/sounddeviceportaudio.cpp +++ b/src/soundio/sounddeviceportaudio.cpp @@ -910,9 +910,11 @@ int SoundDevicePortAudio::callbackProcessClkRef( // performance penalty of ~20 // https://github.com/mixxxdj/mixxx/issues/7747 - // On Emscripten (WebAssembly) denormals-as-zero/flush-as-zero is not - // configurable, for discussion and links see - // https://github.com/mixxxdj/mixxx/pull/12917 + // On Emscripten (WebAssembly) denormals-as-zero/flush-as-zero are + // neither supported nor configurable. This may lead to degraded + // performance compared to other platforms and may be addressed in the + // future if/when WebAssembly adds support for DAZ/FTZ. For further + // discussion and links see https://github.com/mixxxdj/mixxx/pull/12917 #if defined(__SSE__) && !defined(__EMSCRIPTEN__) if (!_MM_GET_DENORMALS_ZERO_MODE()) { From 70ccbb071c3db4758bce9b0fed4622bc343f2ef6 Mon Sep 17 00:00:00 2001 From: m0dB Date: Sat, 6 Apr 2024 15:00:02 +0200 Subject: [PATCH 123/124] fix rectangle of loop gradient (it erroenously used the position of the hotcue marker texture) and rounding of play position marker --- .../allshader/waveformrendermark.cpp | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/waveform/renderers/allshader/waveformrendermark.cpp b/src/waveform/renderers/allshader/waveformrendermark.cpp index eac87b8e4c5..7887ceb5332 100644 --- a/src/waveform/renderers/allshader/waveformrendermark.cpp +++ b/src/waveform/renderers/allshader/waveformrendermark.cpp @@ -169,9 +169,14 @@ void allshader::WaveformRenderMark::paintGL() { const double samplePosition = pMark->getSamplePosition(); if (samplePosition != Cue::kNoPosition) { - float currentMarkPoint = static_cast( - m_waveformRenderer->transformSamplePositionInRendererWorld( - samplePosition)); + const float currentMarkPoint = + std::round( + static_cast( + m_waveformRenderer + ->transformSamplePositionInRendererWorld( + samplePosition)) * + devicePixelRatio) / + devicePixelRatio; const double sampleEndPosition = pMark->getSampleEndPosition(); // Pixmaps are expected to have the mark stroke at the center, @@ -179,12 +184,11 @@ void allshader::WaveformRenderMark::paintGL() { // exactly at the sample position. const float markHalfWidth = pTexture->width() / devicePixelRatio / 2.f; const float drawOffset = currentMarkPoint - markHalfWidth; - currentMarkPoint = qRound(drawOffset * devicePixelRatio) / devicePixelRatio; bool visible = false; // Check if the current point needs to be displayed. - if (currentMarkPoint > -markHalfWidth && - currentMarkPoint < m_waveformRenderer->getLength() + + if (drawOffset > -markHalfWidth && + drawOffset < m_waveformRenderer->getLength() + markHalfWidth) { drawTexture(drawOffset, 0, pTexture); visible = true; @@ -222,9 +226,12 @@ void allshader::WaveformRenderMark::paintGL() { } m_waveformRenderer->setMarkPositions(marksOnScreen); - const float currentMarkPoint = std::floor( - static_cast(m_waveformRenderer->getPlayMarkerPosition() * - m_waveformRenderer->getLength())); + const float currentMarkPoint = + std::round(static_cast( + m_waveformRenderer->getPlayMarkerPosition() * + m_waveformRenderer->getLength()) * + devicePixelRatio) / + devicePixelRatio; const float markHalfWidth = m_pPlayPosMarkTexture->width() / devicePixelRatio / 2.f; const float drawOffset = currentMarkPoint - markHalfWidth; From bd9793cf80d00f1e36b64391143707af9e465d68 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 7 Apr 2024 21:24:00 +0200 Subject: [PATCH 124/124] README: Recommend running buildenvs over sourcing on Linux --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e6a3a20dc6..39f2f6e8dad 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ corresponding command for your operating system: | -- | ------- | | Windows | `tools\windows_buildenv.bat` | | macOS | `source tools/macos_buildenv.sh setup` | -| Debian/Ubuntu | `source tools/debian_buildenv.sh setup` | -| Fedora | `source tools/rpm_buildenv.sh setup` | +| Debian/Ubuntu | `tools/debian_buildenv.sh setup` | +| Fedora | `tools/rpm_buildenv.sh setup` | | Other Linux distros | See the [wiki article](https://github.com/mixxxdj/mixxx/wiki/Compiling%20on%20Linux) | To build Mixxx, run