diff --git a/.cmakelintrc b/.cmakelintrc new file mode 100644 index 00000000000..405ac61e566 --- /dev/null +++ b/.cmakelintrc @@ -0,0 +1 @@ +filter=-linelength,-convention/filename,-package/stdargs diff --git a/.codespellignorelines b/.codespellignorelines index 9a080734c98..9b5a5f02075 100644 --- a/.codespellignorelines +++ b/.codespellignorelines @@ -4,24 +4,24 @@ pInOut, pInOut, CSAMPLE* pInOut, CSAMPLE* pInOut, -void EnginePregain::process(CSAMPLE* pInOut, const int iBufferSize) { -void EngineDelay::process(CSAMPLE* pInOut, const int iBufferSize) { +void EnginePregain::process(CSAMPLE* pInOut, const std::size_t bufferSize) { +void EngineDelay::process(CSAMPLE* pInOut, const std::size_t bufferSize) { pInOut[i] = (CSAMPLE) processSample(fbuf1, (double) pInOut[i]); pInOut[i + 1] = (CSAMPLE) processSample(fbuf2, (double) pInOut[i + 1]); m_pDelayBuffer[m_iDelayPos] = pInOut[i]; pInOut[i] = m_pDelayBuffer[iDelaySourcePos]; - SampleUtil::applyRampingGain(&pInOut[0], m_fPrevGain, 0, iBufferSize / 2); - SampleUtil::applyRampingGain(&pInOut[iBufferSize / 2], 0, totalGain, iBufferSize / 2); - SampleUtil::applyRampingGain(pInOut, m_fPrevGain, totalGain, iBufferSize); - SampleUtil::applyGain(pInOut, totalGain, iBufferSize); - void process(CSAMPLE* pInOut, const int iBufferSize) override; - void process(CSAMPLE* pInOut, const int iBufferSize); - virtual void process(CSAMPLE* pInOut, const int iBufferSize); + SampleUtil::applyRampingGain(&pInOut[0], m_fPrevGain, 0, bufferSize / 2); + SampleUtil::applyRampingGain(&pInOut[bufferSize / 2], 0, totalGain, bufferSize / 2); + SampleUtil::applyRampingGain(pInOut, m_fPrevGain, totalGain, bufferSize); + SampleUtil::applyGain(pInOut, totalGain, bufferSize); + void process(CSAMPLE* pInOut, const std::size_t bufferSize) override; + void process(CSAMPLE* pInOut, const std::size_t bufferSize); + virtual void process(CSAMPLE* pInOut, const std::size_t bufferSize); "CSAMPLE_GAIN gain%(i)din, CSAMPLE_GAIN gain%(i)dout" "gain%(i)dout == CSAMPLE_GAIN_ZERO) {" "pSrc%(i)d, gain%(i)din, gain%(i)dout" % {"i": j} "(gain%(i)dout - gain%(i)din) / (iNumSamples / 2);" - MOCK_METHOD(void, process, (CSAMPLE * pInOut, const int iBufferSize), (override)); + MOCK_METHOD(void, process, (CSAMPLE * pInOut, const std::size_t bufferSize), (override)); const QString kName3 = QStringLiteral("I'm blue, da ba dee"); float m_k2vg; // IIF factor float m_k2vgNew; // IIF factor @@ -71,6 +71,6 @@ void EngineEffectsDelay::process(CSAMPLE* pInOut, // Source: FIPS 180-4 Secure Hash Standard (SHS) // ALAC/CAF has been added in version 1.0.26 QStringLiteral("caf"), -void EngineFilter::process(CSAMPLE* pInOut, const int iBufferSize) +void EngineFilter::process(CSAMPLE* pInOut, const size_t bufferSize) // Note(RRyan/Max Linke): // https://github.com/codders/libshout/blob/a17fb84671d3732317b0353d7281cc47e2df6cf6/src/timing/timing.c diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 16e926154f8..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -res/controllers/lodash.mixxx.js -res/controllers/Novation-Launchpad MK2-scripts.js -res/controllers/Novation-Launchpad Mini MK3-scripts.js -res/controllers/Novation-Launchpad-scripts.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 5b871fcb9b9..00000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "extends": [ "eslint:recommended", - "plugin:jsdoc/recommended", - "plugin:diff/diff"], - - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 7, - "sourceType": "script" - }, - "env": { - "es6": true - }, - "plugins": [ "jsdoc", "@typescript-eslint" ], - "settings": { - "jsdoc": { - "preferredTypes": { - "object": "Object" - } - } - }, - "rules": { - "array-bracket-spacing": "warn", - "block-spacing": "warn", - "brace-style": [ - "warn", - "1tbs", - { - "allowSingleLine": true - } - ], - "curly": "warn", - "camelcase": "warn", - "comma-spacing": "warn", - "computed-property-spacing": [ - "warn", - "never", - { - "enforceForClassMembers": true - } - ], - "dot-location": [ "warn", "property" ], - "dot-notation": "warn", - "eqeqeq": [ "error", "always" ], - "func-call-spacing": "warn", - "func-style": [ - "error", - "expression", - { - "allowArrowFunctions": true - } - ], - "indent": [ "warn", 4 ], - "key-spacing": "warn", - "keyword-spacing": "warn", - "linebreak-style": [ "warn", "unix" ], - "newline-per-chained-call": "warn", - "no-constructor-return": "warn", - "no-extra-bind": "warn", - "no-sequences": "warn", - "no-useless-call": "warn", - "no-useless-return": "warn", - "no-trailing-spaces": "warn", - "no-unneeded-ternary": [ - "warn", - { - "defaultAssignment": false - } - ], - "no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_" - } - ], - "no-var": "warn", - "object-curly-newline": [ - "warn", - { - "consistent": true, - "multiline": true - } - ], - "object-curly-spacing": "warn", - "prefer-const": "warn", - "prefer-regex-literals": "warn", - "prefer-template": "warn", - "quotes": [ "warn", "double" ], - "require-atomic-updates": "error", - "semi": "warn", - "semi-spacing": "warn", - "space-before-blocks": [ "warn", "always" ], - "space-before-function-paren": [ "warn", "never" ], - "space-in-parens": "warn", - "yoda": "warn" - }, - "globals": { - "console": "readonly" - }, - "overrides": [ - { - "files": [ "res/controllers/*.d.ts" ], - "extends": [ "plugin:@typescript-eslint/recommended" ], - "rules": { - "no-unused-vars": "off" - } - }, - { - "files": [ "**/*.mjs" ], - "parserOptions": { - "sourceType": "module" - } - }, - { - "files": [ "res/controllers/common-hid-packet-parser.js" ], - "globals": { - "console": "readonly" - }, - "rules": { - "camelcase": "off" - } - }, - { - "files": [ "res/controllers/*.js" ], - "excludedFiles": "res/controllers/common-hid-packet-parser.js", - "globals": { - "console": "readonly", - "svg": "writable", - "HIDController": "writable", - "HIDDebug": "writable", - "HIDPacket": "writable", - "controller": "writable" - } - } - ] -} diff --git a/.gersemirc b/.gersemirc new file mode 100644 index 00000000000..bfa2d92bb6f --- /dev/null +++ b/.gersemirc @@ -0,0 +1 @@ +indent: 2 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5ace4600a1f..07a87bb17cf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,4 @@ updates: directory: "/" schedule: interval: "weekly" + target-branch: "2.5" diff --git a/.github/workflows/build-checks.yml b/.github/workflows/build-checks.yml index 4eeebcc4f3f..3ad418c4b7f 100644 --- a/.github/workflows/build-checks.yml +++ b/.github/workflows/build-checks.yml @@ -21,11 +21,17 @@ jobs: - name: clazy - name: clang-tidy - name: coverage - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 name: ${{ matrix.name }} steps: - name: Check out repository - uses: actions/checkout@v4.2.1 + uses: actions/checkout@v4.2.2 + - name: Add clazy PPA + if: matrix.name == 'clazy' + # Ubuntu2404 comes with v1.11, which is too old for the Qt6.4 shipped with Ubuntu 24.04 + run: | + sudo add-apt-repository ppa:daschuer/clazy + sudo apt update - name: Install build dependencies run: tools/debian_buildenv.sh setup - name: Create build directory @@ -34,6 +40,7 @@ jobs: if: matrix.name == 'clazy' # Disable optimizations as workaround for Clang 9 bug: https://bugs.llvm.org/show_bug.cgi?id=45034 run: | + clazy --version cmake \ -DCMAKE_BUILD_TYPE=Debug \ -DWARNINGS_FATAL=ON \ @@ -61,6 +68,8 @@ jobs: CXX: clazy - name: Configure (clang-tidy) if: matrix.name == 'clang-tidy' + # Our code contains the use of infinity(), which Clang >= 18 reports + # as error, if you compile with -fast-math run: | cmake \ -DCMAKE_BUILD_TYPE=Debug \ @@ -81,6 +90,7 @@ jobs: -DMAD=ON \ -DMODPLUG=ON \ -DWAVPACK=ON \ + -DCMAKE_CXX_FLAGS="-Wno-nan-infinity-disabled" \ .. working-directory: build env: @@ -116,13 +126,6 @@ jobs: working-directory: build - name: Set up problem matcher uses: ammaraskar/gcc-problem-matcher@0.3.0 - # Work around https://github.com/actions/runner-images/issues/8659 - - name: "Remove GCC 13 from runner image (workaround)" - shell: bash - run: | - sudo rm -f /etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list - sudo apt-get update - sudo apt-get install -y --allow-downgrades libc6=2.35* libc6-dev=2.35* libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04 - name: Build # Do not abort on errors and build/check the whole project run: cmake --build . -j $(nproc) -- --keep-going @@ -157,8 +160,8 @@ jobs: - name: "Upload Coverage Report to coveralls.io" if: matrix.name == 'coverage' continue-on-error: true - uses: coverallsapp/github-action@v2.3.0 + uses: coverallsapp/github-action@v2.3.6 with: - flag-name: ubuntu-22.04 + flag-name: ubuntu-24.04 path-to-lcov: build/lcov.info github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f90645b83fa..8e69cd1360b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,8 +15,8 @@ jobs: fail-fast: false matrix: include: - - name: Ubuntu 22.04 - os: ubuntu-22.04 + - name: Ubuntu 24.04 + os: ubuntu-24.04 cmake_args: >- -DQT6=ON -DQML=ON @@ -34,12 +34,12 @@ jobs: cpack_generator: DEB buildenv_basepath: /home/runner/buildenv buildenv_script: tools/debian_buildenv.sh - artifacts_name: Ubuntu 22.04 Qt6 DEB + artifacts_name: Ubuntu 24.04 Qt6 DEB artifacts_path: build/*.deb artifacts_slug: ubuntu-jammy qt_qpa_platform: offscreen - - name: macOS 12 x64 - os: macos-12 + - name: macOS 13 x64 + os: macos-13 cmake_args: >- -DBULK=ON -DCOREAUDIO=ON @@ -63,8 +63,8 @@ jobs: artifacts_path: build/*.dmg artifacts_slug: macos-macosintel qt_qpa_platform: offscreen - - name: macOS 12 arm64 - os: macos-12 + - name: macOS 13 arm64 + os: macos-13 cmake_args: >- -DBULK=ON -DCOREAUDIO=ON @@ -121,7 +121,6 @@ jobs: env: # macOS codesigning - APPLE_CODESIGN_IDENTITY: EF241CF990A9BE5477438AEE1F308F76F33FD100 MACOS_CODESIGN_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_CODESIGN_CERTIFICATE_P12_BASE64 }} MACOS_CODESIGN_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CODESIGN_CERTIFICATE_PASSWORD }} @@ -134,7 +133,7 @@ jobs: artifact-windows-win64: ${{ steps.prepare_deploy.outputs.artifact-windows-win64 }} steps: - name: "Check out repository" - uses: actions/checkout@v4.2.1 + uses: actions/checkout@v4.2.2 with: # This is necessary for making `git describe` work. fetch-depth: 0 @@ -152,7 +151,7 @@ jobs: - name: "[macOS] Set up cmake" uses: jwlawson/actions-setup-cmake@v2.0 - # Ubuntu 22.04 should use the CMake version from the repos. + # Ubuntu 24.04 should use the CMake version from the repos. if: runner.os == 'macOS' with: # This should always match the minimum required version in @@ -161,7 +160,7 @@ jobs: - name: "[Windows] Set up cmake" uses: jwlawson/actions-setup-cmake@v2.0 - # Ubuntu 22.04 should use the CMake version from the repos. + # Ubuntu 24.04 should use the CMake version from the repos. if: runner.os == 'Windows' with: # This is a workaround for a SSL false positive in cmake 3.26.4 @@ -201,12 +200,15 @@ jobs: security unlock-keychain -p mixxx Mixxx.keychain security import ~/certificate.p12 -k Mixxx.keychain \ -P "${MACOS_CODESIGN_CERTIFICATE_PASSWORD}" -A + security find-certificate -a -Z Mixxx.keychain + APPLE_CODESIGN_IDENTITY="$(security find-certificate -a -Z Mixxx.keychain | grep ^SHA-1 | cut -d " " -f3 | uniq)" security set-key-partition-list -S "apple-tool:,apple:" -k mixxx Mixxx.keychain # Add keychain to search list security list-keychains -s Mixxx.keychain # Prevent keychain access from timing out security set-keychain-settings Mixxx.keychain echo "CMAKE_ARGS_EXTRA=${CMAKE_ARGS_EXTRA} -DAPPLE_CODESIGN_IDENTITY=${APPLE_CODESIGN_IDENTITY}" >> "${GITHUB_ENV}" + echo "APPLE_CODESIGN_IDENTITY=${APPLE_CODESIGN_IDENTITY}" >> $GITHUB_ENV - name: "[macOS/Linux] Set up build environment" if: matrix.buildenv_script != null && runner.os != 'Windows' @@ -316,7 +318,7 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/trusted-signing-action@v0.4.0 + uses: azure/trusted-signing-action@v0.5.1 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} @@ -333,8 +335,17 @@ jobs: - name: "Package" if: matrix.cpack_generator != null - run: cpack -G ${{ matrix.cpack_generator }} -V - working-directory: build + # Use retry loop to work around a race condition on macOS causing + # 'Resource busy' errors with 'hdiutil'. See + # https://github.com/actions/runner-images/issues/7522 + uses: nick-fields/retry@v3 + with: + timeout_minutes: 30 + max_attempts: 12 + retry_wait_seconds: 1 + command: | + cd build + cpack -G ${{ matrix.cpack_generator }} -V - name: "[Ubuntu] Import PPA GPG key" if: startsWith(matrix.os, 'ubuntu') && env.RRYAN_AT_MIXXX_DOT_ORG_GPG_PRIVATE_KEY != null @@ -344,7 +355,7 @@ jobs: - name: "Package for PPA" # No need to do the PPA build for both Ubuntu versions - if: matrix.name == 'Ubuntu 22.04' + if: matrix.name == 'Ubuntu 24.04' run: | if [[ "${{ github.ref }}" == "refs/heads/main" ]] && [[ "${{ github.repository }}" == "mixxxdj/mixxx" ]]; then CPACK_ARGS="-D DEB_UPLOAD_PPA=ppa:mixxx/nightlies" @@ -371,7 +382,7 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/trusted-signing-action@v0.4.0 + uses: azure/trusted-signing-action@v0.5.1 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} @@ -473,7 +484,7 @@ jobs: - name: "Upload GitHub Actions artifacts" if: matrix.artifacts_path != null - uses: actions/upload-artifact@v4.4.1 + uses: actions/upload-artifact@v4.6.0 with: name: ${{ matrix.artifacts_name }} path: ${{ matrix.artifacts_path }} @@ -489,7 +500,7 @@ jobs: if: always() && github.repository == 'mixxxdj/mixxx' steps: - name: "Check out repository" - uses: actions/checkout@v4.2.1 + uses: actions/checkout@v4.2.2 with: fetch-depth: 0 diff --git a/.github/workflows/git.yaml b/.github/workflows/git.yaml index 7362cbb5fa0..a2b94fa7106 100644 --- a/.github/workflows/git.yaml +++ b/.github/workflows/git.yaml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4.2.1 + - uses: actions/checkout@v4.2.2 - name: Block Fixup Commit Merge uses: 13rac1/block-fixup-merge-action@v2.0.0 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 3613e207f5f..c953c6d1b79 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -18,7 +18,7 @@ jobs: container: holzhaus/mixxx-ci:20220930 steps: - name: "Check out repository" - uses: actions/checkout@v4.2.1 + uses: actions/checkout@v4.2.2 with: # Unfortunately we need the whole history and can't use a shallow clone # because the Appstream Metadata hook parses the history to find the @@ -51,6 +51,8 @@ jobs: if: github.event_name == 'pull_request' env: SKIP: no-commit-to-branch + # https://github.com/paleite/eslint-plugin-diff?tab=readme-ov-file#ci-setup + ESLINT_PLUGIN_DIFF_COMMIT: ${{ github.event.pull_request.base.ref }} with: # HEAD is the not yet integrated PR merge commit +refs/pull/xxxx/merge # HEAD^1 is the PR target branch and HEAD^2 is the HEAD of the source branch @@ -67,14 +69,14 @@ jobs: - name: "Upload patch artifact" if: failure() && env.UPLOAD_PATCH_FILE != null - uses: actions/upload-artifact@v4.4.1 + uses: actions/upload-artifact@v4.6.0 with: name: ${{ env.UPLOAD_PATCH_FILE }} path: ${{ env.UPLOAD_PATCH_FILE }} - name: "Upload pre-commit.log" if: failure() && env.UPLOAD_PATCH_FILE == null - uses: actions/upload-artifact@v4.4.1 + uses: actions/upload-artifact@v4.6.0 with: name: pre-commit.log path: /github/home/.cache/pre-commit/pre-commit.log diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39f2b52fcd7..cbc896f2c40 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,14 +25,14 @@ # because it won't prevent catching other, unrelated issues. # _anlz.h/_pdb.h: Header files generated by Kaitai Struct -exclude: ^(lib/|src/test/.*data/).*|res/controllers/lodash\.mixxx\.js|res/translations/.*\.ts|src/.*_(anlz|pdb)\.h$ +exclude: ^(lib/|src/test/.*data/).*|res/controllers/lodash\.mixxx\.js|src/.*_(anlz|pdb)\.h$ minimum_pre_commit_version: 2.21.0 default_language_version: python: python3 rust: 1.64.0 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: fix-byte-order-marker exclude: ^.*(\.cbproj|\.groupproj|\.props|\.sln|\.vcxproj|\.vcxproj.filters|UTF-8-BOM.txt)$ @@ -43,10 +43,10 @@ repos: - id: check-yaml exclude: ^\.clang-format$ - id: end-of-file-fixer - exclude: ^.*UTF-8-BOM.txt$ + exclude: ^(res/translations/.*\.ts|.*UTF-8-BOM\.txt)$ - id: mixed-line-ending - id: trailing-whitespace - exclude: \.(c|cc|cxx|cpp|d.ts|frag|glsl|h|hpp|hxx|ih|ispc|ipp|java|js|m|mm|proto|vert)$ + exclude: \.(c|cc|cxx|cpp|ts|frag|glsl|h|hpp|hxx|ih|ispc|ipp|java|js|m|mm|proto|tsv|vert)$ - id: no-commit-to-branch # protect main and any branch that has a semver-like name args: [-b, main, -p, '^\d+\.\d+(?:\.\d+)?$'] @@ -64,23 +64,25 @@ repos: "\\W(?:m_p*(?=[A-Z])|m_(?=\\w)|pp*(?=[A-Z])|k(?=[A-Z])|s_(?=\\w))", --write-changes, ] - exclude: ^(packaging/wix/LICENSE.rtf.in|src/dialog/dlgabout\.cpp|.*\.(?:pot?|(?` include in `defs.h` [#11348](https://github.com/mixxxdj/mixxx/pull/11348) -* Engine: Minor refactor to prefer simplified ranged-for-loop [#11234](https://github.com/mixxxdj/mixxx/pull/11234) -* Delete unused EngineFilter [#11559](https://github.com/mixxxdj/mixxx/pull/11559) -* AnalyzerWaveform: Fix commented out code [#11561](https://github.com/mixxxdj/mixxx/pull/11561) -* Remove usage of ControlObject::getControl [#11643](https://github.com/mixxxdj/mixxx/pull/11643) -* Fix unnecessary transfer of the ownership before release which returns the pointer itself [#11726](https://github.com/mixxxdj/mixxx/pull/11726) -* Add `ConfigObject::get-/setValue` [#11883](https://github.com/mixxxdj/mixxx/pull/11883) -* Github CI: Enable `WARNINGS_FATAL` on macOS, too [#11905](https://github.com/mixxxdj/mixxx/pull/11905) -* Refactor timers [#11807](https://github.com/mixxxdj/mixxx/pull/11807) [#11850](https://github.com/mixxxdj/mixxx/pull/11850) -* Use `mixxx::audio::ChannelCount` type instead of `int`/`unsigned char`/etc. [#11941](https://github.com/mixxxdj/mixxx/pull/11941) -* Refactor util/timer: cleanup includes [#11937](https://github.com/mixxxdj/mixxx/pull/11937) -* Use `SampleRate` type consistently [#11904](https://github.com/mixxxdj/mixxx/pull/11904) -* CMakeLists: Match arbitrary `arm64-osx` triplets [#11933](https://github.com/mixxxdj/mixxx/pull/11933) -* Reduce sample buffer memory usage [#11988](https://github.com/mixxxdj/mixxx/pull/11988) -* Fix clazy issues on `main` [#12028](https://github.com/mixxxdj/mixxx/pull/12028) -* Tidy and modernize SampleBuffer [#11987](https://github.com/mixxxdj/mixxx/pull/11987) -* Refactor parented_ptr: make trivially destructible in release mode, delete move operations [#11981](https://github.com/mixxxdj/mixxx/pull/11981) -* Labeler: Add more labels to the auto-labeler [#12106](https://github.com/mixxxdj/mixxx/pull/12106) * FindPortMidi: Link ALSA in static builds on Linux [#12292](https://github.com/mixxxdj/mixxx/pull/12292) [#12291](https://github.com/mixxxdj/mixxx/pull/12291) -* privat generated ui headers [#12060](https://github.com/mixxxdj/mixxx/pull/12060) [#11407](https://github.com/mixxxdj/mixxx/pull/11407) -* Github CI: workaround runner-image issue [#12233](https://github.com/mixxxdj/mixxx/pull/12233) * FindLibudev: Link hidapi and libusb with libudev in static builds on Linux [#12294](https://github.com/mixxxdj/mixxx/pull/12294) * FindVorbis: Link ogg in static builds [#12297](https://github.com/mixxxdj/mixxx/pull/12297) -* MixxxApplication: Support linking Qt statically on Linux [#12284](https://github.com/mixxxdj/mixxx/pull/12284) * FindSleef: Use OpenMP in static builds [#12295](https://github.com/mixxxdj/mixxx/pull/12295) -* Happy New Year 2024! [#12486](https://github.com/mixxxdj/mixxx/pull/12486) -* fix/History: remove obsolete placeholder playlists [#12494](https://github.com/mixxxdj/mixxx/pull/12494) -* Add missing Taglib dependency [#12830](https://github.com/mixxxdj/mixxx/pull/12830) -* fix: typo ;) [#12726](https://github.com/mixxxdj/mixxx/pull/12726) -* refactor: Avoid temporary qlist allocation on midi sysex receive [#12843](https://github.com/mixxxdj/mixxx/pull/12843) -* Labeler: Add `qml` to labeler config [#12911](https://github.com/mixxxdj/mixxx/pull/12911) -* WTrackMenu: Add missing wcoverartlabel.h include [#12924](https://github.com/mixxxdj/mixxx/pull/12924) -* Fix clazy complaints and naming [#12935](https://github.com/mixxxdj/mixxx/pull/12935) -* src/library: Sort files into sub-directories [#12956](https://github.com/mixxxdj/mixxx/pull/12956) -* CMakeLists: Fix deduplication trap with `--preload-file` [#12944](https://github.com/mixxxdj/mixxx/pull/12944) -* GitHub CI: Add runner that allows cleaning up the download server [#12957](https://github.com/mixxxdj/mixxx/pull/12957) -* GitHub CI: Skip the manifest update job on forks [#13278](https://github.com/mixxxdj/mixxx/pull/13278) -* Refactor FFmpeg soundsource to allow other soundsource to inherit it [#13042](https://github.com/mixxxdj/mixxx/pull/13042) -* Code Style: Add branches around single line blocks. [#13097](https://github.com/mixxxdj/mixxx/pull/13097) -* Add missing member in copy ctor [#13229](https://github.com/mixxxdj/mixxx/pull/13229) -* Refactor/preferences enums [#12798](https://github.com/mixxxdj/mixxx/pull/12798) -* localDateTimeFromUtc: Make argument a const reference and initialize QDateTime at construction [#13359](https://github.com/mixxxdj/mixxx/pull/13359) -* use enum class for waveform overview type [#13370](https://github.com/mixxxdj/mixxx/pull/13370) -* Update to latest vcpkg dependencies - [#11649](https://github.com/mixxxdj/mixxx/pull/11649) - [#12512](https://github.com/mixxxdj/mixxx/pull/12512) - [#12067](https://github.com/mixxxdj/mixxx/pull/12067) - [#12898](https://github.com/mixxxdj/mixxx/pull/12898) - [#13155](https://github.com/mixxxdj/mixxx/pull/13155) -* GitHub actions updates - [#11544](https://github.com/mixxxdj/mixxx/pull/11544) - [#11508](https://github.com/mixxxdj/mixxx/pull/11508) - [#11487](https://github.com/mixxxdj/mixxx/pull/11487) - [#11438](https://github.com/mixxxdj/mixxx/pull/11438) - [#11410](https://github.com/mixxxdj/mixxx/pull/11410) - [#11560](https://github.com/mixxxdj/mixxx/pull/11560) - [#11578](https://github.com/mixxxdj/mixxx/pull/11578) - [#11610](https://github.com/mixxxdj/mixxx/pull/11610) - [#11631](https://github.com/mixxxdj/mixxx/pull/11631) - [#11710](https://github.com/mixxxdj/mixxx/pull/11710) - [#11736](https://github.com/mixxxdj/mixxx/pull/11736) - [#11920](https://github.com/mixxxdj/mixxx/pull/11920) - [#11961](https://github.com/mixxxdj/mixxx/pull/11961) - [#12241](https://github.com/mixxxdj/mixxx/pull/12241) - [#12394](https://github.com/mixxxdj/mixxx/pull/12394) - [#12447](https://github.com/mixxxdj/mixxx/pull/12447) - [#12425](https://github.com/mixxxdj/mixxx/pull/12425) - [#12421](https://github.com/mixxxdj/mixxx/pull/12421) - [#12799](https://github.com/mixxxdj/mixxx/pull/12799) - [#12801](https://github.com/mixxxdj/mixxx/pull/12801) - [#12800](https://github.com/mixxxdj/mixxx/pull/12800) - [#12736](https://github.com/mixxxdj/mixxx/pull/12736) - [#12692](https://github.com/mixxxdj/mixxx/pull/12692) - [#12694](https://github.com/mixxxdj/mixxx/pull/12694) - [#12695](https://github.com/mixxxdj/mixxx/pull/12695) - [#12691](https://github.com/mixxxdj/mixxx/pull/12691) - [#12693](https://github.com/mixxxdj/mixxx/pull/12693) - [#12625](https://github.com/mixxxdj/mixxx/pull/12625) - [#12627](https://github.com/mixxxdj/mixxx/pull/12627) - [#12626](https://github.com/mixxxdj/mixxx/pull/12626) - [#12577](https://github.com/mixxxdj/mixxx/pull/12577) - [#13162](https://github.com/mixxxdj/mixxx/pull/13162) - [#13163](https://github.com/mixxxdj/mixxx/pull/13163) - [#13187](https://github.com/mixxxdj/mixxx/pull/13187) - [#13217](https://github.com/mixxxdj/mixxx/pull/13217) - [#13246](https://github.com/mixxxdj/mixxx/pull/13246) - [#13232](https://github.com/mixxxdj/mixxx/pull/13232) - -## [2.4.2](https://github.com/mixxxdj/mixxx/milestone/43?closed=1) (unreleased) +* macOS packaging: Enable app sandbox in ad-hoc-packaged (i.e. non-notarized) bundles too [#12101](https://github.com/mixxxdj/mixxx/pull/12101) +* CMakeLists: Match arbitrary `arm64-osx` triplets [#11933](https://github.com/mixxxdj/mixxx/pull/11933) +* Disable warning in lib/apple code [#13522](https://github.com/mixxxdj/mixxx/pull/13522) +* GitHub CI: Use retry loop for CPack to work around macOS issue [#13991](https://github.com/mixxxdj/mixxx/pull/13991) +* Github CI: Enable `WARNINGS_FATAL` on macOS, too [#11905](https://github.com/mixxxdj/mixxx/pull/11905) + +## [2.4.2](https://github.com/mixxxdj/mixxx/milestone/43?closed=1) (2024-11-26) ### Controller Mappings +* Denon MC7000: Fix star up/down logic by only handling button down events [#13588](https://github.com/mixxxdj/mixxx/pull/13588) +* Intech TEK2: Add initial mapping [#13521](https://github.com/mixxxdj/mixxx/pull/13521) * Korg Kaoss DJ: Update script [#12683](https://github.com/mixxxdj/mixxx/pull/12683) +* MIDI for light: Fix unsound timer handling [#13117](https://github.com/mixxxdj/mixxx/pull/13117) * Novation Dicer: Remove flanger mapping with quickeffect toggle [#13196](https://github.com/mixxxdj/mixxx/pull/13196) [#13134](https://github.com/mixxxdj/mixxx/issues/13134) +* Novation Launchpad X: Fix detection on macOS + [#13691](https://github.com/mixxxdj/mixxx/pull/13691) + [#13633](https://github.com/mixxxdj/mixxx/issues/13633) * Numark PartyMix: Fix EQ (script binding) display name [#13255](https://github.com/mixxxdj/mixxx/pull/13255) * Numark Scratch: Add initial mapping [#4834](https://github.com/mixxxdj/mixxx/pull/4834) [#13375](https://github.com/mixxxdj/mixxx/pull/13375) +* Pioneer DDJ-400 and DDJ-FLX4: Remove tap beat mapping to resolve conflict with toggle quantize and fix shift + play + [#13815](https://github.com/mixxxdj/mixxx/pull/13815) + [#13813](https://github.com/mixxxdj/mixxx/issues/13813) + [#13857](https://github.com/mixxxdj/mixxx/pull/13857) +* Reloop Beatmix 2/4: Fix eject button and jog LED being lit on track unload + [#13601](https://github.com/mixxxdj/mixxx/pull/13601) + [#13605](https://github.com/mixxxdj/mixxx/pull/13605) +* Reloop Mixage MK1, MK2, Controller Edition: Add initial mapping [#12296](https://github.com/mixxxdj/mixxx/pull/12296) * Sony SIXAXIS: Fix mapping [#13319](https://github.com/mixxxdj/mixxx/pull/13319) ### Fixes @@ -442,9 +625,6 @@ [#13248](https://github.com/mixxxdj/mixxx/issues/13248) * Recording: with empty config, save default split size immediately [#13304](https://github.com/mixxxdj/mixxx/pull/13304) -* Allow to drop files with supported MIME type regardless off the file extensions - [#13209](https://github.com/mixxxdj/mixxx/pull/13209) - [#13204](https://github.com/mixxxdj/mixxx/issues/13204) * Add support for Ubuntu Oracular Oriole and remove Lunar Lobster [#13348](https://github.com/mixxxdj/mixxx/pull/13348) * Recordbox: Fix string decoding issues @@ -458,6 +638,73 @@ * Fix wrong pitch value on startup, caused by `components.Pot` [#11814](https://github.com/mixxxdj/mixxx/issues/11814) [#13463](https://github.com/mixxxdj/mixxx/pull/13463) +* Engine Prime: Fix build-failure [#13397](https://github.com/mixxxdj/mixxx/pull/13397) +* Engine Prime: Friendlier error message if export fails [#13524](https://github.com/mixxxdj/mixxx/pull/13524) +* macOs: Fix Keyboard shortcuts by not catching num key modifier + [#13481](https://github.com/mixxxdj/mixxx/pull/13481) + [#13305](https://github.com/mixxxdj/mixxx/issues/13305) +* Skins: fix time display to allow AM/PM + [#13430](https://github.com/mixxxdj/mixxx/pull/13430) + [#13421](https://github.com/mixxxdj/mixxx/issues/13421) +* Fix detection last sound if track does not end with silence. + [#13545](https://github.com/mixxxdj/mixxx/pull/13545) + [#13449](https://github.com/mixxxdj/mixxx/issues/13449) +* Remove false positive critical warning related to library columns + [#13165](https://github.com/mixxxdj/mixxx/pull/13165) + [#13164](https://github.com/mixxxdj/mixxx/issues/13164) +* Fix reading metadata for files with wrong extensions + [#13218](https://github.com/mixxxdj/mixxx/pull/13218) + [#13205](https://github.com/mixxxdj/mixxx/issues/13205) +* History: remove purged tracks, auto-remove empty playlists + [#13579](https://github.com/mixxxdj/mixxx/pull/13579) + [#13578](https://github.com/mixxxdj/mixxx/issues/13578) +* Synchronize AutoDJ next deck with top track in queue + [#12909](https://github.com/mixxxdj/mixxx/pull/12909) + [#8956](https://github.com/mixxxdj/mixxx/issues/8956) +* Playlists: Update play duration and bold state in sidebar when dragging tracks into the playlist table + [#13591](https://github.com/mixxxdj/mixxx/pull/13591) + [#13590](https://github.com/mixxxdj/mixxx/issues/13590) + [#13575](https://github.com/mixxxdj/mixxx/pull/13575) +* Playlists: Keep correct track selection (# position) when sorting + [#13103](https://github.com/mixxxdj/mixxx/pull/13103) +* Track file export: Various fixes + [#13610](https://github.com/mixxxdj/mixxx/pull/13610) +* Controller engine: Unify/improve logging, expand error dialog's Details box + [#13626](https://github.com/mixxxdj/mixxx/pull/13626) +* Fix quantization in the effect engine (metronome effect) + [#13636](https://github.com/mixxxdj/mixxx/pull/13636) + [#13733](https://github.com/mixxxdj/mixxx/pull/13733) +* Musicbrainz: Improved messages + [#13672](https://github.com/mixxxdj/mixxx/pull/13672) + [#13673](https://github.com/mixxxdj/mixxx/pull/13673) +* Fix ReplayGain detection in case of short tracks + [#13680](https://github.com/mixxxdj/mixxx/pull/13680) + [#13676](https://github.com/mixxxdj/mixxx/issues/13676) + [#13702](https://github.com/mixxxdj/mixxx/issues/13702) + [#13703](https://github.com/mixxxdj/mixxx/pull/13703) +* Track menu: Avoid crash and UX issues with track nullptr + [#13685](https://github.com/mixxxdj/mixxx/pull/13685) +* Disable Properties shortcut in Computer feature views + [#13698](https://github.com/mixxxdj/mixxx/pull/13698) +* Overview waveform: Add tooltip info about left-click dragging + [#13739](https://github.com/mixxxdj/mixxx/pull/13739) +* Make `hotcue_focus_color_next`/`_prev` COs `ControlPushButton`s to allow direct mappings + [#13764](https://github.com/mixxxdj/mixxx/pull/13764) +* Scaled svg cache to speed up drawing in hidpi mode [#13679](https://github.com/mixxxdj/mixxx/pull/13679) +* Update to libdjinterop 0.22.1 for Enigine Prime 4.0.1 support [#13790](https://github.com/mixxxdj/mixxx/pull/13790) +* HID: Avoid repeated error messages from hid_write()/hid_read() in case of errors + [#13692](https://github.com/mixxxdj/mixxx/pull/13692) + [#13660](https://github.com/mixxxdj/mixxx/issues/13660) +* Fix unnecessary painting with covers in library [#13715](https://github.com/mixxxdj/mixxx/pull/13715) +* Fix check for unrelated decks playing when starting Auto DJ + [#13762](https://github.com/mixxxdj/mixxx/pull/13762) + [#13734](https://github.com/mixxxdj/mixxx/issues/13734) +* Fix read before m_bufferInt during scratching + [#13917](https://github.com/mixxxdj/mixxx/pull/13917) + [#13916](https://github.com/mixxxdj/mixxx/issues/13916) +* Fix waveform EQ High&Mid visualization + [#13923](https://github.com/mixxxdj/mixxx/pull/13923) + [#13922](https://github.com/mixxxdj/mixxx/issues/13922) ## [2.4.1](https://github.com/mixxxdj/mixxx/milestone/41?closed=1) (2024-05-08) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ecd7a092af..a9a01b25497 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.21) +# lint_cmake: -readability/wonkycase message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}") @@ -47,25 +48,48 @@ if(POLICY CMP0135) cmake_policy(SET CMP0135 NEW) endif() -function(FATAL_ERROR_MISSING_ENV) +# Use this function to throw an error because the build environment is not set +# up correctly. +function(fatal_error_missing_env) if(WIN32) if(CMAKE_BUILD_TYPE MATCHES "Debug") - message(FATAL_ERROR "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/windows_buildenv.bat`?") + message( + FATAL_ERROR + "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/windows_buildenv.bat`?" + ) else() - message(FATAL_ERROR "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/windows_release_buildenv.bat` or `${CMAKE_SOURCE_DIR}/tools/windows_buildenv.bat`(includes Debug)?") + message( + FATAL_ERROR + "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/windows_release_buildenv.bat` or `${CMAKE_SOURCE_DIR}/tools/windows_buildenv.bat`(includes Debug)?" + ) endif() elseif(APPLE AND NOT IOS) if(CMAKE_BUILD_TYPE MATCHES "Debug") - message(FATAL_ERROR "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/macos_buildenv.sh`") + message( + FATAL_ERROR + "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/macos_buildenv.sh`" + ) else() - message(FATAL_ERROR "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/macos_release_buildenv.sh` or `${CMAKE_SOURCE_DIR}/tools/macos_buildenv.sh`(includes Debug)?") + message( + FATAL_ERROR + "Did you download the Mixxx build environment using `${CMAKE_SOURCE_DIR}/tools/macos_release_buildenv.sh` or `${CMAKE_SOURCE_DIR}/tools/macos_buildenv.sh`(includes Debug)?" + ) endif() elseif(LINUX) - message(FATAL_ERROR "Did you install the Debian dev packages via `${CMAKE_SOURCE_DIR}/tools/debian_buildenv.sh` or the equivalent packages using your package manager?") + message( + FATAL_ERROR + "Did you install the Debian dev packages via `${CMAKE_SOURCE_DIR}/tools/debian_buildenv.sh` or the equivalent packages using your package manager?" + ) elseif(DEFINED VCPKG_TARGET_TRIPLET) - message(FATAL_ERROR "You are targeting ${VCPKG_TARGET_TRIPLET}, which does not have a prebuilt environment. Please make sure that -DMIXXX_VCPKG_ROOT points to a vcpkg environment containing installed dependencies for ${VCPKG_TARGET_TRIPLET}!") + message( + FATAL_ERROR + "You are targeting ${VCPKG_TARGET_TRIPLET}, which does not have a prebuilt environment. Please make sure that -DMIXXX_VCPKG_ROOT points to a vcpkg environment containing installed dependencies for ${VCPKG_TARGET_TRIPLET}!" + ) else() - message(FATAL_ERROR "You are building for an unknown platform and are missing a build environment. Please set -DVCPKG_TARGET_TRIPLET and make sure that -DMIXXX_VCPKG_ROOT points to a vcpkg environment containing installed dependencies for your target platform!") + message( + FATAL_ERROR + "You are building for an unknown platform and are missing a build environment. Please set -DVCPKG_TARGET_TRIPLET and make sure that -DMIXXX_VCPKG_ROOT points to a vcpkg environment containing installed dependencies for your target platform!" + ) endif() endfunction() @@ -74,26 +98,32 @@ endfunction() # Note: VCPKG_ROOT, the default location for the vcpkg cli tool is later # adjusted by CMAKE_TOOLCHAIN_FILE. if(DEFINED ENV{MIXXX_VCPKG_ROOT} AND NOT DEFINED MIXXX_VCPKG_ROOT) - set(MIXXX_VCPKG_ROOT "$ENV{MIXXX_VCPKG_ROOT}") + set(MIXXX_VCPKG_ROOT "$ENV{MIXXX_VCPKG_ROOT}") endif() if(DEFINED MIXXX_VCPKG_ROOT) - if(EXISTS "$ENV{MIXXX_VCPKG_ROOT}/overlay/ports" OR NOT EXISTS "$ENV{MIXXX_VCPKG_ROOT}/ports") + if( + EXISTS "${MIXXX_VCPKG_ROOT}/overlay/ports" + OR NOT EXISTS "${MIXXX_VCPKG_ROOT}/ports" + ) # MIXXX_VCPKG_ROOT points to our vcpkg environment # and we configure the CMAKE_TOOLCHAIN_FILE and overlays accordingly - message(STATUS "Using MIXXX_VCPKG_ROOT: $ENV{MIXXX_VCPKG_ROOT}") + message(STATUS "Using MIXXX_VCPKG_ROOT: ${MIXXX_VCPKG_ROOT}") else() - message(STATUS "MIXXX_VCPKG_ROOT not correct (missing $ENV{MIXXX_VCPKG_ROOT}/overlay/ports)") - FATAL_ERROR_MISSING_ENV() + message( + STATUS + "MIXXX_VCPKG_ROOT not correct (missing ${MIXXX_VCPKG_ROOT}/overlay/ports)" + ) + fatal_error_missing_env() endif() if(NOT DEFINED VCPKG_OVERLAY_PORTS) # required for manifest mode set(VCPKG_OVERLAY_PORTS "${MIXXX_VCPKG_ROOT}/overlay/ports") if(APPLE) - list(APPEND VCPKG_OVERLAY_PORTS "${MIXXX_VCPKG_ROOT}/overlay/osx") + list(APPEND VCPKG_OVERLAY_PORTS "${MIXXX_VCPKG_ROOT}/overlay/osx") elseif(WIN32) - list(APPEND VCPKG_OVERLAY_PORTS "${MIXXX_VCPKG_ROOT}/overlay/windows") + list(APPEND VCPKG_OVERLAY_PORTS "${MIXXX_VCPKG_ROOT}/overlay/windows") endif() endif() @@ -103,7 +133,12 @@ if(DEFINED MIXXX_VCPKG_ROOT) endif() if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) - set(CMAKE_TOOLCHAIN_FILE "${MIXXX_VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") + set( + CMAKE_TOOLCHAIN_FILE + "${MIXXX_VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + CACHE STRING + "" + ) endif() endif() @@ -114,8 +149,13 @@ if(NOT DEFINED VCPKG_TARGET_TRIPLET) set(VCPKG_TARGET_TRIPLET "$ENV{VCPKG_DEFAULT_TRIPLET}") endif() endif() -set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON CACHE BOOL "Automatically copy dependencies into the install target directory for executables." FORCE) - +set( + X_VCPKG_APPLOCAL_DEPS_INSTALL + ON + CACHE BOOL + "Automatically copy dependencies into the install target directory for executables." + FORCE +) # Set a default build type if none was specified # See https://blog.kitware.com/cmake-and-the-default-build-type/ for details. @@ -128,19 +168,40 @@ endif() if(NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE) - message(STATUS "Setting CMAKE_BUILD_TYPE to '${default_build_type}' as none was specified.") - set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) + message( + STATUS + "Setting CMAKE_BUILD_TYPE to '${default_build_type}' as none was specified." + ) + set( + CMAKE_BUILD_TYPE + "${default_build_type}" + CACHE STRING + "Choose the type of build." + FORCE + ) # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo") + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" + ) elseif(NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release|RelWithDebInfo)$") - message(FATAL_ERROR "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} is not supported, use one of Debug, Release or RelWithDebInfo.") + message( + FATAL_ERROR + "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} is not supported, use one of Debug, Release or RelWithDebInfo." + ) endif() endif() include(CMakeDependentOption) option(QT6 "Build with Qt6" ON) -cmake_dependent_option(QML "Build with QML" ON "QT6" OFF) +cmake_dependent_option( + QML + "Build with QML" + ON + "QT6" + OFF +) option(QOPENGL "Use QOpenGLWindow based widget instead of QGLWidget" ON) if(QOPENGL) @@ -156,10 +217,18 @@ if(VCPKG_TARGET_TRIPLET MATCHES "^wasm(32|64)-emscripten") if(DEFINED ENV{EMSDK}) message(STATUS "Found EMSDK at $ENV{EMSDK}") else() - message(FATAL_ERROR "Please make sure emsdk is installed and the environment variable EMSDK is set (see https://emscripten.org/docs/getting_started/downloads.html)") + message( + FATAL_ERROR + "Please make sure emsdk is installed and the environment variable EMSDK is set (see https://emscripten.org/docs/getting_started/downloads.html)" + ) endif() if(NOT DEFINED VCPKG_CHAINLOAD_TOOLCHAIN_FILE) - set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" CACHE STRING "") + set( + VCPKG_CHAINLOAD_TOOLCHAIN_FILE + "$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" + CACHE STRING + "" + ) endif() # Enabling this causes Qt's FindWrapRt C++ compile check to fail as it tries # to run `clang-scan-deps` (because we set the C++ standard to 20). Emscripten @@ -168,13 +237,17 @@ if(VCPKG_TARGET_TRIPLET MATCHES "^wasm(32|64)-emscripten") set(CMAKE_CXX_SCAN_FOR_MODULES OFF) elseif(APPLE) # Check if xcode-select is installed - execute_process(COMMAND xcode-select -v + execute_process( + COMMAND xcode-select -v RESULT_VARIABLE XCODE_SELECT_RESULT OUTPUT_QUIET ) if(XCODE_SELECT_RESULT) # xcode-select command failed, meaning it is not installed or not configured properly - message(FATAL_ERROR "'xcode-select -v' failed with '${XCODE_SELECT_RESULT}'. You may need to install Xcode and run 'sudo xcode-select --install'.") + message( + FATAL_ERROR + "'xcode-select -v' failed with '${XCODE_SELECT_RESULT}'. You may need to install Xcode and run 'sudo xcode-select --install'." + ) endif() if(VCPKG_TARGET_TRIPLET MATCHES "^[a-zA-Z0-9]+-osx") @@ -182,16 +255,36 @@ elseif(APPLE) set(CMAKE_SYSTEM_NAME Darwin CACHE STRING "Target macOS") if(VCPKG_TARGET_TRIPLET MATCHES "^arm64-") # Minimum macOS version for arm64 Support - set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0 CACHE STRING "Minimum macOS version the build will be able to run on") + set( + CMAKE_OSX_DEPLOYMENT_TARGET + 11.0 + CACHE STRING + "Minimum macOS version the build will be able to run on" + ) set(CMAKE_OSX_ARCHITECTURES arm64 CACHE STRING "The target architecture") - set(CMAKE_SYSTEM_PROCESSOR arm64 CACHE STRING "The target system processor") + set( + CMAKE_SYSTEM_PROCESSOR + arm64 + CACHE STRING + "The target system processor" + ) else() if(QT6) # Minimum macOS version supported by Qt 6 - set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "Minimum macOS version the build will be able to run on") + set( + CMAKE_OSX_DEPLOYMENT_TARGET + 10.15 + CACHE STRING + "Minimum macOS version the build will be able to run on" + ) else() # Minimum macOS version supported by Qt 5.12 - set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "Minimum macOS version the build will be able to run on") + set( + CMAKE_OSX_DEPLOYMENT_TARGET + 10.12 + CACHE STRING + "Minimum macOS version the build will be able to run on" + ) # Needed for deployment target < 10.14 add_compile_options(-fno-aligned-allocation) endif() @@ -199,9 +292,17 @@ elseif(APPLE) elseif(VCPKG_TARGET_TRIPLET MATCHES "^[a-zA-Z0-9]+-ios") message(STATUS "Targeting iOS (${VCPKG_TARGET_TRIPLET})") set(CMAKE_SYSTEM_NAME iOS CACHE STRING "Target iOS") - set(CMAKE_OSX_DEPLOYMENT_TARGET 14.0 CACHE STRING "Minimum iOS version to target") + set( + CMAKE_OSX_DEPLOYMENT_TARGET + 14.0 + CACHE STRING + "Minimum iOS version to target" + ) else() - message(WARNING "Targeting an Apple platform, but VCPKG_TARGET_TRIPLET is not set. This is not a supported scenario!") + message( + WARNING + "Targeting an Apple platform, but VCPKG_TARGET_TRIPLET is not set. This is not a supported scenario!" + ) endif() endif() @@ -211,7 +312,10 @@ enable_language(C CXX) set(MIXXX_VERSION_PRERELEASE "alpha") # set to "alpha" "beta" or "" set(CMAKE_PROJECT_HOMEPAGE_URL "https://www.mixxx.org") -set(CMAKE_PROJECT_DESCRIPTION "Mixxx is Free DJ software that gives you everything you need to perform live mixes.") +set( + CMAKE_PROJECT_DESCRIPTION + "Mixxx is Free DJ software that gives you everything you need to perform live mixes." +) # Used for force control of color output set(BUILD_COLORS "auto" CACHE STRING "Try to use colors auto/always/no") @@ -229,9 +333,12 @@ if(DEFINED _VCPKG_INSTALLED_DIR) if(NOT EXISTS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}") # Fail early if this part of CMAKE_PREFIX_PATH does not exist # else the library lookups below will fail with misleading error messages - message(STATUS "VCPKG_TARGET_TRIPLET dir not found: ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} " - "Make sure the VCPKG build environment is installed and contains the build for the selected triplet.") - FATAL_ERROR_MISSING_ENV() + message( + STATUS + "VCPKG_TARGET_TRIPLET dir not found: ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET} " + "Make sure the VCPKG build environment is installed and contains the build for the selected triplet." + ) + fatal_error_missing_env() else() message(STATUS "Using VCPKG_TARGET_TRIPLET: ${VCPKG_TARGET_TRIPLET}") endif() @@ -248,7 +355,7 @@ else() endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - if (CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") + if(CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") set(LLVM_CLANG false) set(MSVC true) else() @@ -278,7 +385,13 @@ endif() # # This is only applies to gcc/clang, therefore this option is forcibly set to # ON on all other compilers. -cmake_dependent_option(BUILD_LOW_MEMORY "Store temporary build files on disk by disabling the build option -pipe" OFF "GNU_GCC OR LLVM_CLANG" ON) +cmake_dependent_option( + BUILD_LOW_MEMORY + "Store temporary build files on disk by disabling the build option -pipe" + OFF + "GNU_GCC OR LLVM_CLANG" + ON +) if(NOT BUILD_LOW_MEMORY) add_compile_options(-pipe) endif() @@ -287,7 +400,13 @@ endif() # # This is only available with GCC, therefore this option is forcibly set to OFF # for all other compilers. -cmake_dependent_option(COVERAGE "Coverage (i.e. gcov) support" OFF "GNU_GCC" OFF) +cmake_dependent_option( + COVERAGE + "Coverage (i.e. gcov) support" + OFF + "GNU_GCC" + OFF +) if(COVERAGE) add_compile_options(--coverage -fprofile-arcs -ftest-coverage) add_link_options(--coverage -fprofile-arcs -ftest-coverage) @@ -297,7 +416,13 @@ endif() # # This is only available on Linux, therefore this option is forcibly set to OFF # on all other platforms. -cmake_dependent_option(PROFILING "Profiling (e.g. gprof) support" OFF "UNIX;NOT APPLE" OFF) +cmake_dependent_option( + PROFILING + "Profiling (e.g. gprof) support" + OFF + "UNIX;NOT APPLE" + OFF +) if(PROFILING) add_compile_options(-pg) add_link_options(-pg) @@ -307,8 +432,16 @@ endif() # Optimizations # -set(OPTIMIZE "portable" CACHE STRING "Optimization and Tuning (set to off, portable, native, legacy)") -set_property(CACHE OPTIMIZE PROPERTY STRINGS "off" "portable" "native" "legacy") +set( + OPTIMIZE + "portable" + CACHE STRING + "Optimization and Tuning (set to off, portable, native, legacy)" +) +set_property( + CACHE OPTIMIZE + PROPERTY STRINGS "off" "portable" "native" "legacy" +) string(TOLOWER "${OPTIMIZE}" OPTIMIZE) message(STATUS "Optimization level: ${OPTIMIZE}") @@ -341,11 +474,29 @@ if(MSVC) string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + string( + REPLACE + "/Zi" + "/Z7" + CMAKE_CXX_FLAGS_RELEASE + "${CMAKE_CXX_FLAGS_RELEASE}" + ) string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string( + REPLACE + "/Zi" + "/Z7" + CMAKE_CXX_FLAGS_RELWITHDEBINFO + "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" + ) + string( + REPLACE + "/Zi" + "/Z7" + CMAKE_C_FLAGS_RELWITHDEBINFO + "${CMAKE_C_FLAGS_RELWITHDEBINFO}" + ) endif() if(NOT OPTIMIZE STREQUAL "off") @@ -365,20 +516,38 @@ if(MSVC) if(CMAKE_BUILD_TYPE STREQUAL "Debug") #optimize Debug Builds as well, to have "normal" behaviour of mixxx during development - string(REPLACE "/Od" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - string(REPLACE "/Od" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") - string(REPLACE "/Ob0" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - string(REPLACE "/Ob0" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") + string(REPLACE "/Od" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + string(REPLACE "/Od" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") + string(REPLACE "/Ob0" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + string(REPLACE "/Ob0" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS}") add_compile_options(/O2) # this implies /Od2 # Remove /RTC1 flag set by CMAKE by default (conflicts with /O2) - string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - string(REPLACE "/RTC1" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + string( + REPLACE + "/RTC1" + "" + CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG}" + ) + string(REPLACE "/RTC1" "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") # For some reasons cmake uses /Ob1 in RelWithDebInfo https://gitlab.kitware.com/cmake/cmake/-/issues/20812 # /O2 is applied by CMake and this implies /Od2 - string(REPLACE "/Ob1" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - string(REPLACE "/Ob1" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string( + REPLACE + "/Ob1" + "" + CMAKE_CXX_FLAGS_RELWITHDEBINFO + "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" + ) + string( + REPLACE + "/Ob1" + "" + CMAKE_C_FLAGS_RELWITHDEBINFO + "${CMAKE_C_FLAGS_RELWITHDEBINFO}" + ) # Reduce the size of the binary in RelWithDebInfo builds # Do not use /OPT:ICF because it has no effect. @@ -388,10 +557,24 @@ if(MSVC) # /INCREMENTAL is incompatible with /OPT:REF, but it's the CMake default for RelWithDebInfo # The CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO can be defined by the user in the GUI or in CMakeSettings.json, # therefore we can't rely on the default. - string(FIND CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/INCREMENTAL:NO" INCREMENTAL_NO_POSITION) + string( + FIND + CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO + "/INCREMENTAL:NO" + INCREMENTAL_NO_POSITION + ) if(INCREMENTAL_NO_POSITION EQUAL -1) - message(STATUS "Overwriting /INCREMENTAL by /INCREMENTAL:NO to allow link time code optimization") - string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}") + message( + STATUS + "Overwriting /INCREMENTAL by /INCREMENTAL:NO to allow link time code optimization" + ) + string( + REPLACE + "/INCREMENTAL" + "/INCREMENTAL:NO" + CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO + "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}" + ) endif() # Note: CMAKE_INTERPROCEDURAL_OPTIMIZATION sets the /GL and /LTCG flags for us elseif(CMAKE_BUILD_TYPE STREQUAL "Release") @@ -421,7 +604,10 @@ if(MSVC) # Define the target processor instruction and other compiler optimization flags here: # https://docs.microsoft.com/en-us/cpp/build/reference/arch-x64?view=msvc-160 # add_compile_options(/arch:AVX512) - message(FATAL_ERROR "User need to set the MSVC compiler flags for the native processor here!") + message( + FATAL_ERROR + "User need to set the MSVC compiler flags for the native processor here!" + ) add_compile_options("/favor:${CMAKE_SYSTEM_PROCESSOR}") elseif(OPTIMIZE STREQUAL "legacy") if(CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -430,25 +616,64 @@ if(MSVC) message("Enabling pure i386 instruction set (without SSE/SSE2 etc.)") endif() else() - message(FATAL_ERROR "Invalid value passed to OPTIMIZE option: ${OPTIMIZE}") + message( + FATAL_ERROR + "Invalid value passed to OPTIMIZE option: ${OPTIMIZE}" + ) endif() else() # OPTIMIZE=off if(CMAKE_BUILD_TYPE STREQUAL "Release") #Remove optimize flags set by cmake defaults - string(REPLACE "/O2" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") - string(REPLACE "/O2" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") - string(REPLACE "/Ob2" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") - string(REPLACE "/Ob2" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string( + REPLACE + "/O2" + "" + CMAKE_CXX_FLAGS_RELEASE + "${CMAKE_CXX_FLAGS_RELEASE}" + ) + string(REPLACE "/O2" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + string( + REPLACE + "/Ob2" + "" + CMAKE_CXX_FLAGS_RELEASE + "${CMAKE_CXX_FLAGS_RELEASE}" + ) + string(REPLACE "/Ob2" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") add_compile_options(/Od) # this implies /Ob0 add_compile_options(/RTC1) elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") #Remove optimize flags set by cmake defaults - string(REPLACE "/O2" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - string(REPLACE "/O2" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string( + REPLACE + "/O2" + "" + CMAKE_CXX_FLAGS_RELWITHDEBINFO + "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" + ) + string( + REPLACE + "/O2" + "" + CMAKE_C_FLAGS_RELWITHDEBINFO + "${CMAKE_C_FLAGS_RELWITHDEBINFO}" + ) # For some reasons cmake uses /Ob1 in RelWithDebInfo https://gitlab.kitware.com/cmake/cmake/-/issues/20812 - string(REPLACE "/Ob1" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - string(REPLACE "/Ob1" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + string( + REPLACE + "/Ob1" + "" + CMAKE_CXX_FLAGS_RELWITHDEBINFO + "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" + ) + string( + REPLACE + "/Ob1" + "" + CMAKE_C_FLAGS_RELWITHDEBINFO + "${CMAKE_C_FLAGS_RELWITHDEBINFO}" + ) add_compile_options(/Od) # this implies /Ob0 add_compile_options(/RTC1) endif() @@ -461,10 +686,7 @@ elseif(GNU_GCC OR LLVM_CLANG) # unfortunately that work only on 64 bit CPUs or with sse2 enabled # The following optimisation flags makes the engine code ~3 times # faster, measured on a Atom CPU. - add_compile_options( - -ffast-math - -funroll-loops - ) + add_compile_options(-ffast-math -funroll-loops) if(EMSCRIPTEN) # Optimize for size + speed when targeting Emscripten/WebAssembly # This is recommended as we use asyncify: @@ -504,10 +726,7 @@ elseif(GNU_GCC OR LLVM_CLANG) # we require macOS 10.12. # https://stackoverflow.com/questions/45917280/mac-osx-minumum-support-sse-version elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|armv7.*)$") # but not armv8 - add_compile_options( - -mfloat-abi=hard - -mfpu=neon - ) + add_compile_options(-mfloat-abi=hard -mfpu=neon) endif() # this sets macros __SSE2_MATH__ __SSE_MATH__ __SSE2__ __SSE__ # This should be our default build for distribution @@ -527,10 +746,7 @@ elseif(GNU_GCC OR LLVM_CLANG) # macros like __SSE2_MATH__ __SSE_MATH__ __SSE2__ __SSE__ # are set automatically if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm|armv7.*)$") # but not armv8 - add_compile_options( - -mfloat-abi=hard - -mfpu=neon - ) + add_compile_options(-mfloat-abi=hard -mfpu=neon) endif() elseif(OPTIMIZE STREQUAL "legacy") if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i[3456]86|x86|x64|x86_64|AMD64)$") @@ -540,7 +756,10 @@ elseif(GNU_GCC OR LLVM_CLANG) # on arm platforms equivalent to -march=arch endif() else() - message(FATAL_ERROR "Invalid value passed to OPTIMIZE option: ${OPTIMIZE}") + message( + FATAL_ERROR + "Invalid value passed to OPTIMIZE option: ${OPTIMIZE}" + ) endif() endif() endif() @@ -559,7 +778,12 @@ if(MSVC) # With MSVC, PCH is faster than caching set(CMAKE_DISABLE_PRECOMPILE_HEADERS OFF) endif() - set(CMAKE_DISABLE_PRECOMPILE_HEADERS ${CMAKE_DISABLE_PRECOMPILE_HEADERS} CACHE BOOL "Disable precompiled headers") + set( + CMAKE_DISABLE_PRECOMPILE_HEADERS + ${CMAKE_DISABLE_PRECOMPILE_HEADERS} + CACHE BOOL + "Disable precompiled headers" + ) # sccache support find_program(SCCACHE_EXECUTABLE "sccache") @@ -572,11 +796,13 @@ if(MSVC) message(STATUS "Support for sccache: ${SCCACHE_SUPPORT}") if(SCCACHE_SUPPORT) if(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS) - message(WARNING - "sccache: Does not work with precompiled headers. Set CMAKE_DISABLE_PRECOMPILE_HEADERS=ON") + message( + WARNING + "sccache: Does not work with precompiled headers. Set CMAKE_DISABLE_PRECOMPILE_HEADERS=ON" + ) endif() - set( CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_EXECUTABLE}" ) - set( CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_EXECUTABLE}" ) + set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_EXECUTABLE}") + set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_EXECUTABLE}") endif() else() # ccache support @@ -584,37 +810,51 @@ else() if(CCACHE_EXECUTABLE) message(STATUS "Found ccache: ${CCACHE_EXECUTABLE}") else() - message(STATUS "Could NOT find ccache (missing executable)") + message(STATUS "Could NOT find ccache (missing executable)") endif() default_option(CCACHE_SUPPORT "Enable ccache support" "CCACHE_EXECUTABLE") if(NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS) set(CMAKE_DISABLE_PRECOMPILE_HEADERS ${CCACHE_SUPPORT}) endif() - set(CMAKE_DISABLE_PRECOMPILE_HEADERS ${CMAKE_DISABLE_PRECOMPILE_HEADERS} CACHE BOOL "Disable precompiled headers") + set( + CMAKE_DISABLE_PRECOMPILE_HEADERS + ${CMAKE_DISABLE_PRECOMPILE_HEADERS} + CACHE BOOL + "Disable precompiled headers" + ) if(CCACHE_SUPPORT) if(GNU_GCC OR LLVM_CLANG) # without this compiler messages in `make` backend would be uncolored - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=${BUILD_COLORS}") + set( + CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -fdiagnostics-color=${BUILD_COLORS}" + ) endif() if(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS) execute_process( - COMMAND "${CCACHE_EXECUTABLE}" "--get-config=sloppiness" - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - OUTPUT_VARIABLE CCACHE_CONFIGURED_SLOPPINESS OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET) - if (NOT CCACHE_CONFIGURED_SLOPPINESS MATCHES "pch_defines" OR - NOT CCACHE_CONFIGURED_SLOPPINESS MATCHES "time_macros") - message(WARNING - "ccache: For use with precompiled headers, the setting \"sloppiness\" needs to " - "be set to \"pch_defines,time_macros\". This can be done via the environment variable " - "\"CCACHE_SLOPPINESS=pch_defines,time_macros\" or permanent via " - "\"ccache --set-config=sloppiness=pch_defines,time_macros\".") + COMMAND "${CCACHE_EXECUTABLE}" "--get-config=sloppiness" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + OUTPUT_VARIABLE CCACHE_CONFIGURED_SLOPPINESS + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if( + NOT CCACHE_CONFIGURED_SLOPPINESS MATCHES "pch_defines" + OR NOT CCACHE_CONFIGURED_SLOPPINESS MATCHES "time_macros" + ) + message( + WARNING + "ccache: For use with precompiled headers, the setting \"sloppiness\" needs to " + "be set to \"pch_defines,time_macros\". This can be done via the environment variable " + "\"CCACHE_SLOPPINESS=pch_defines,time_macros\" or permanent via " + "\"ccache --set-config=sloppiness=pch_defines,time_macros\"." + ) endif() endif() - set( CMAKE_C_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}" ) - set( CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}" ) + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_EXECUTABLE}") endif() message(STATUS "Support for ccache: ${CCACHE_SUPPORT}") endif() @@ -628,7 +868,7 @@ if(NOT MSVC) OUTPUT_STRIP_TRAILING_WHITESPACE ) if(MOLD_FUSE_VERSION_STRING) - set(MOLD_FUSE_FOUND TRUE) + set(MOLD_FUSE_FOUND TRUE) endif() if(NOT MOLD_FUSE_FOUND) # check if the symlink ld is in the mold folder for older compiler @@ -636,7 +876,7 @@ if(NOT MSVC) get_filename_component(MOLD_SYMLINK_DIRECTORY ${MOLD_SYMLINK} DIRECTORY) endif() if(MOLD_SYMLINK) - set(MOLD_SYMLINK_FOUND TRUE) + set(MOLD_SYMLINK_FOUND TRUE) endif() default_option(MOLD_SUPPORT "Use 'mold' for linking" "MOLD_FUSE_FOUND OR MOLD_SYMLINK_FOUND") if(MOLD_SUPPORT) @@ -644,7 +884,10 @@ if(NOT MSVC) message(STATUS "Selecting mold as linker") add_link_options("-fuse-ld=mold") elseif(MOLD_SYMLINK_FOUND) - message(STATUS "Selecting mold as linker via ld symlink in ${MOLD_SYMLINK_DIRECTORY}") + message( + STATUS + "Selecting mold as linker via ld symlink in ${MOLD_SYMLINK_DIRECTORY}" + ) add_link_options("-B${MOLD_SYMLINK_DIRECTORY}") else() message(FATAL_ERROR "Could NOT find mold (missing executable)") @@ -658,12 +901,20 @@ if(NOT MSVC) OUTPUT_STRIP_TRAILING_WHITESPACE ) if(LLD_VERSION_STRING) - string(REGEX MATCH "LLD ([0-9]+\\.[0-9]+\\.[0-9]+)" LLD_VERSION_MATCH "${LLD_VERSION_STRING}") + string( + REGEX MATCH + "LLD ([0-9]+\\.[0-9]+\\.[0-9]+)" + LLD_VERSION_MATCH + "${LLD_VERSION_STRING}" + ) if(LLD_VERSION_MATCH) set(LLD_VERSION ${CMAKE_MATCH_1}) message(STATUS "Found ld.lld with version: ${LLD_VERSION}") else() - message(WARNING "Failed to parse ld.lld version from: ${LLD_VERSION_STRING}") + message( + WARNING + "Failed to parse ld.lld version from: ${LLD_VERSION_STRING}" + ) endif() endif() # LLD 10.0.0 does not work because of https://bugs.llvm.org/show_bug.cgi?id=45769 @@ -687,10 +938,18 @@ if(CMAKE_VERSION VERSION_LESS "3.7.0") set(CMAKE_INCLUDE_CURRENT_DIR ON) endif() -set(CLANG_TIDY "" CACHE STRING "CMAKE_CXX_CLANG_TIDY equivalent that only applies to mixxx sources, not bundled dependencies") +set( + CLANG_TIDY + "" + CACHE STRING + "CMAKE_CXX_CLANG_TIDY equivalent that only applies to mixxx sources, not bundled dependencies" +) # Mixxx itself -add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL +add_library( + mixxx-lib + STATIC + EXCLUDE_FROM_ALL src/analyzer/analyzerbeats.cpp src/analyzer/analyzerebur128.cpp src/analyzer/analyzergain.cpp @@ -1175,6 +1434,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/util/fileaccess.cpp src/util/fileinfo.cpp src/util/filename.cpp + src/util/font.cpp src/util/imagefiledata.cpp src/util/imagefiledata.cpp src/util/imageutils.cpp @@ -1201,7 +1461,6 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/util/tapfilter.cpp src/util/task.cpp src/util/taskmonitor.cpp - src/util/threadcputimer.cpp src/util/time.cpp src/util/timer.cpp src/util/valuetransformer.cpp @@ -1324,10 +1583,9 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/widget/wwidgetgroup.cpp src/widget/wwidgetstack.cpp ) -set(MIXXX_COMMON_PRECOMPILED_HEADER - src/util/assert.h -) -set(MIXXX_LIB_PRECOMPILED_HEADER +set(MIXXX_COMMON_PRECOMPILED_HEADER src/util/assert.h) +set( + MIXXX_LIB_PRECOMPILED_HEADER src/audio/frame.h src/audio/signalinfo.h src/audio/streaminfo.h @@ -1452,7 +1710,6 @@ set(MIXXX_LIB_PRECOMPILED_HEADER src/util/runtimeloggingcategory.h src/util/safelywritablefile.h src/util/sample.h - src/util/sample_autogen.h src/util/samplebuffer.h src/util/sandbox.h src/util/scopedoverridecursor.h @@ -1471,7 +1728,6 @@ set(MIXXX_LIB_PRECOMPILED_HEADER src/util/taskmonitor.h src/util/thread_affinity.h src/util/thread_annotations.h - src/util/threadcputimer.h src/util/time.h src/util/timer.h src/util/trace.h @@ -1485,71 +1741,88 @@ set(MIXXX_LIB_PRECOMPILED_HEADER src/util/workerthreadscheduler.h src/util/xml.h ) -if (NOT QML) - target_sources(mixxx-lib PRIVATE - # The following sources need to be in the QML target in order to get QML_ELEMENT properly interpreted. - # However, if we build Mixxx without QML support, these are still required, so it gets appended to the - # main target - src/control/controlmodel.cpp - src/control/controlsortfiltermodel.cpp +if(NOT QML) + target_sources( + mixxx-lib + PRIVATE + # The following sources need to be in the QML target in order to get QML_ELEMENT properly interpreted. + # However, if we build Mixxx without QML support, these are still required, so it gets appended to the + # main target + src/control/controlmodel.cpp + src/control/controlsortfiltermodel.cpp ) else() - target_sources(mixxx-lib PRIVATE - # The following source depends of QML being available but aren't part of the new QML UI - src/controllers/rendering/controllerrenderingengine.cpp - src/controllers/controllerenginethreadcontrol.cpp - src/controllers/controllerscreenpreview.cpp + target_sources( + mixxx-lib + PRIVATE + # The following source depends of QML being available but aren't part of the new QML UI + src/controllers/rendering/controllerrenderingengine.cpp + src/controllers/controllerenginethreadcontrol.cpp + src/controllers/controllerscreenpreview.cpp ) endif() if(QOPENGL) - target_sources(mixxx-lib PRIVATE - src/shaders/endoftrackshader.cpp - src/shaders/slipmodeshader.cpp - src/shaders/patternshader.cpp - src/shaders/rgbashader.cpp - src/shaders/rgbshader.cpp - src/shaders/shader.cpp - src/shaders/textureshader.cpp - src/shaders/unicolorshader.cpp - src/shaders/vinylqualityshader.cpp - src/util/opengltexture2d.cpp - src/waveform/renderers/allshader/digitsrenderer.cpp - src/waveform/renderers/allshader/matrixforwidgetgeometry.cpp - src/waveform/renderers/allshader/waveformrenderbackground.cpp - src/waveform/renderers/allshader/waveformrenderbeat.cpp - src/waveform/renderers/allshader/waveformrenderer.cpp - src/waveform/renderers/allshader/waveformrendererendoftrack.cpp - src/waveform/renderers/allshader/waveformrendererslipmode.cpp - src/waveform/renderers/allshader/waveformrendererfiltered.cpp - src/waveform/renderers/allshader/waveformrendererhsv.cpp - src/waveform/renderers/allshader/waveformrendererpreroll.cpp - src/waveform/renderers/allshader/waveformrendererrgb.cpp - src/waveform/renderers/allshader/waveformrenderertextured.cpp - src/waveform/renderers/allshader/waveformrenderersignalbase.cpp - src/waveform/renderers/allshader/waveformrenderersimple.cpp - src/waveform/renderers/allshader/waveformrendermark.cpp - src/waveform/renderers/allshader/waveformrendermarkrange.cpp - src/waveform/widgets/allshader/waveformwidget.cpp - src/widget/openglwindow.cpp - src/widget/tooltipqopengl.cpp - src/widget/wglwidgetqopengl.cpp - src/widget/winitialglwidget.cpp - src/widget/wspinnyglsl.cpp - src/widget/wvumeterglsl.cpp + target_sources( + mixxx-lib + PRIVATE + src/shaders/endoftrackshader.cpp + src/shaders/slipmodeshader.cpp + src/shaders/patternshader.cpp + src/shaders/rgbashader.cpp + src/shaders/rgbshader.cpp + src/shaders/shader.cpp + src/shaders/textureshader.cpp + src/shaders/unicolorshader.cpp + src/shaders/vinylqualityshader.cpp + src/util/opengltexture2d.cpp + src/waveform/renderers/allshader/digitsrenderer.cpp + src/waveform/renderers/allshader/matrixforwidgetgeometry.cpp + src/waveform/renderers/allshader/waveformrenderbackground.cpp + src/waveform/renderers/allshader/waveformrenderbeat.cpp + src/waveform/renderers/allshader/waveformrenderer.cpp + src/waveform/renderers/allshader/waveformrendererendoftrack.cpp + src/waveform/renderers/allshader/waveformrendererslipmode.cpp + src/waveform/renderers/allshader/waveformrendererfiltered.cpp + src/waveform/renderers/allshader/waveformrendererhsv.cpp + src/waveform/renderers/allshader/waveformrendererpreroll.cpp + src/waveform/renderers/allshader/waveformrendererrgb.cpp + src/waveform/renderers/allshader/waveformrenderertextured.cpp + src/waveform/renderers/allshader/waveformrenderersignalbase.cpp + src/waveform/renderers/allshader/waveformrenderersimple.cpp + src/waveform/renderers/allshader/waveformrendermark.cpp + src/waveform/renderers/allshader/waveformrendermarkrange.cpp + src/waveform/widgets/allshader/waveformwidget.cpp + src/widget/openglwindow.cpp + src/widget/tooltipqopengl.cpp + src/widget/wglwidgetqopengl.cpp + src/widget/winitialglwidget.cpp + src/widget/wspinnyglsl.cpp + src/widget/wvumeterglsl.cpp ) else() - target_sources(mixxx-lib PRIVATE - src/waveform/renderers/qtvsynctestrenderer.cpp - src/waveform/renderers/qtwaveformrendererfilteredsignal.cpp - src/waveform/renderers/qtwaveformrenderersimplesignal.cpp - src/widget/wglwidgetqglwidget.cpp + target_sources( + mixxx-lib + PRIVATE + src/waveform/renderers/qtvsynctestrenderer.cpp + src/waveform/renderers/qtwaveformrendererfilteredsignal.cpp + src/waveform/renderers/qtwaveformrenderersimplesignal.cpp + src/widget/wglwidgetqglwidget.cpp ) endif() -set_source_files_properties(src/util/moc_included_test.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON) +set_source_files_properties( + src/util/moc_included_test.cpp + PROPERTIES SKIP_PRECOMPILE_HEADERS ON +) -set_target_properties(mixxx-lib PROPERTIES AUTOMOC ON AUTOUIC ON CXX_CLANG_TIDY "${CLANG_TIDY}") -target_include_directories(mixxx-lib PUBLIC src "${CMAKE_CURRENT_BINARY_DIR}/src") +set_target_properties( + mixxx-lib + PROPERTIES AUTOMOC ON AUTOUIC ON CXX_CLANG_TIDY "${CLANG_TIDY}" +) +target_include_directories( + mixxx-lib + PUBLIC src "${CMAKE_CURRENT_BINARY_DIR}/src" +) if(UNIX AND NOT APPLE) target_sources(mixxx-lib PRIVATE src/util/rlimit.cpp) set(MIXXX_SETTINGS_PATH ".mixxx/") @@ -1568,24 +1841,25 @@ if(APPLE) # Apple's Metal API in the foreseeable future. target_compile_definitions(mixxx-lib PUBLIC GL_SILENCE_DEPRECATION) - target_sources(mixxx-lib PRIVATE - src/util/appleosversion.mm - ) + target_sources(mixxx-lib PRIVATE src/util/appleosversion.mm) if(IOS) - target_sources(mixxx-lib PRIVATE - src/soundio/soundmanagerios.mm - src/util/screensaverios.mm + target_sources( + mixxx-lib + PRIVATE src/soundio/soundmanagerios.mm src/util/screensaverios.mm ) else() - target_sources(mixxx-lib PRIVATE - src/util/darkappearance.mm - ) + target_sources(mixxx-lib PRIVATE src/util/darkappearance.mm) - option(MACOS_ITUNES_LIBRARY "Native macOS iTunes/Music.app library integration" ON) + option( + MACOS_ITUNES_LIBRARY + "Native macOS iTunes/Music.app library integration" + ON + ) if(MACOS_ITUNES_LIBRARY) - target_sources(mixxx-lib PRIVATE - src/library/itunes/itunesmacosimporter.mm + target_sources( + mixxx-lib + PRIVATE src/library/itunes/itunesmacosimporter.mm ) target_link_libraries(mixxx-lib PRIVATE "-weak_framework iTunesLibrary") target_compile_definitions(mixxx-lib PUBLIC __MACOS_ITUNES_LIBRARY__) @@ -1594,15 +1868,17 @@ if(APPLE) option(AU_EFFECTS "Audio Unit (AU) effects integration" ON) if(AU_EFFECTS) - target_sources(mixxx-lib PRIVATE - 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_sources( + mixxx-lib + PRIVATE + 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" - "-weak_framework AVFAudio" + target_link_libraries( + mixxx-lib + PRIVATE "-weak_framework AudioToolbox" "-weak_framework AVFAudio" ) target_compile_definitions(mixxx-lib PUBLIC __AU_EFFECTS__) endif() @@ -1618,7 +1894,10 @@ endif() # QML Debugging if(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_definitions(mixxx-lib PUBLIC QT_QML_DEBUG) - message(STATUS "Enabling QML Debugging! This poses a security risk as Mixxx will open a TCP port for debugging") + message( + STATUS + "Enabling QML Debugging! This poses a security risk as Mixxx will open a TCP port for debugging" + ) endif() option(WARNINGS_PEDANTIC "Let the compiler show even more warnings" OFF) @@ -1626,38 +1905,60 @@ if(MSVC) if(WARNINGS_PEDANTIC) target_compile_options(mixxx-lib PUBLIC /W4) else() - target_compile_options(mixxx-lib PUBLIC - /W3 # Warning Level 3 (production quality) + target_compile_options( + mixxx-lib + PUBLIC + /W3 # Warning Level 3 (production quality) /wd4200 # C4200: nonstandard extension used: zero-sized array in struct/union - # Note: Even with CMAKE_C_STANDARD = 99 MSVC does not complain about C99 flexible array members + # Note: Even with CMAKE_C_STANDARD = 99 MSVC does not complain about C99 flexible array members + ) + target_compile_definitions( + mixxx-lib + PUBLIC + _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + _CRT_SECURE_NO_WARNINGS ) - target_compile_definitions(mixxx-lib PUBLIC _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING _CRT_SECURE_NO_WARNINGS) endif() else() # TODO: Add -Wtrampolines, not yet supported by clazy - target_compile_options(mixxx-lib PUBLIC -Wall -Wextra $<$:-Woverloaded-virtual> -Wfloat-conversion -Werror=return-type -Wformat=2 -Wformat-security -Wvla -Wundef) + target_compile_options( + mixxx-lib + PUBLIC + -Wall + -Wextra + $<$:-Woverloaded-virtual> + -Wfloat-conversion + -Werror=return-type + -Wformat=2 + -Wformat-security + -Wvla + -Wundef + ) if(WARNINGS_PEDANTIC) target_compile_options(mixxx-lib PUBLIC -pedantic) endif() endif() option(INFO_VECTORIZE "Let the compiler show vectorized loops" OFF) -if (INFO_VECTORIZE) - if(MSVC) - target_compile_options(mixxx-lib PUBLIC /Qvec-report:1) - elseif(GNU_GCC) - target_compile_options(mixxx-lib PUBLIC -fopt-info-vec-optimized) - elseif(LLVM_CLANG) - target_compile_options(mixxx-lib PUBLIC -Rpass=loop-vectorize) - else() - message(STATUS "INFO_VECTORIZE not implemented for this compiler.") - endif() +if(INFO_VECTORIZE) + if(MSVC) + target_compile_options(mixxx-lib PUBLIC /Qvec-report:1) + elseif(GNU_GCC) + target_compile_options(mixxx-lib PUBLIC -fopt-info-vec-optimized) + elseif(LLVM_CLANG) + target_compile_options(mixxx-lib PUBLIC -Rpass=loop-vectorize) + else() + message(STATUS "INFO_VECTORIZE not implemented for this compiler.") + endif() endif() option(RELATIVE_MACRO_PATHS "Relativize __FILE__ paths" ON) if(RELATIVE_MACRO_PATHS) if(NOT MSVC) - target_compile_options(mixxx-lib PUBLIC "-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=.") + target_compile_options( + mixxx-lib + PUBLIC "-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=." + ) endif() endif() @@ -1670,19 +1971,33 @@ if(WARNINGS_FATAL) endif() endif() -target_compile_definitions(mixxx-lib PUBLIC - "${CMAKE_SYSTEM_PROCESSOR}" - $<$:MIXXX_BUILD_DEBUG> - $<$:MIXXX_DEBUG_ASSERTIONS_ENABLED> - $<$>:MIXXX_BUILD_RELEASE> +target_compile_definitions( + mixxx-lib + PUBLIC + "${CMAKE_SYSTEM_PROCESSOR}" + $<$:MIXXX_BUILD_DEBUG> + $<$:MIXXX_DEBUG_ASSERTIONS_ENABLED> + $<$>:MIXXX_BUILD_RELEASE> ) # Mac-specific options # # These options are OFF by default, and since they are only available on macOS, # they are forcibly set to OFF on all other platforms. -cmake_dependent_option(MACOS_BUNDLE "Install files to proper locations to make an .app bundle" OFF "APPLE" OFF) -cmake_dependent_option(MACAPPSTORE "Build for Mac App Store" OFF "APPLE" OFF) +cmake_dependent_option( + MACOS_BUNDLE + "Install files to proper locations to make an .app bundle" + OFF + "APPLE" + OFF +) +cmake_dependent_option( + MACAPPSTORE + "Build for Mac App Store" + OFF + "APPLE" + OFF +) if(MACAPPSTORE) target_compile_definitions(mixxx-lib PUBLIC __MACAPPSTORE__) endif() @@ -1705,26 +2020,45 @@ set(MIXXX_INSTALL_BINDIR ".") set(MIXXX_INSTALL_DATADIR ".") set(MIXXX_INSTALL_DOCDIR "./doc") set(MIXXX_INSTALL_LICENSEDIR "./doc") -if (APPLE AND MACOS_BUNDLE) +if(APPLE AND MACOS_BUNDLE) set(MIXXX_INSTALL_BINDIR "${CMAKE_INSTALL_BINDIR}") - set(MACOS_BUNDLE_NAME Mixxx CACHE STRING "The macOS app bundle and executable name") - set(MACOS_BUNDLE_IDENTIFIER org.mixxx.mixxx CACHE STRING "The macOS app bundle identifier") + set( + MACOS_BUNDLE_NAME + Mixxx + CACHE STRING + "The macOS app bundle and executable name" + ) + set( + MACOS_BUNDLE_IDENTIFIER + org.mixxx.mixxx + CACHE STRING + "The macOS app bundle identifier" + ) set(MIXXX_INSTALL_PREFIX "${MACOS_BUNDLE_NAME}.app") set(MIXXX_INSTALL_DATADIR "${MIXXX_INSTALL_PREFIX}/Contents/Resources") set(MIXXX_INSTALL_DOCDIR "${MIXXX_INSTALL_DATADIR}") set(MIXXX_INSTALL_LICENSEDIR "${MIXXX_INSTALL_DATADIR}/licenses") -elseif (APPLE AND IOS) +elseif(APPLE AND IOS) set(MIXXX_INSTALL_BINDIR "${CMAKE_INSTALL_BINDIR}") - set(IOS_BUNDLE_NAME Mixxx CACHE STRING "The iOS app bundle and executable name") - set(IOS_BUNDLE_IDENTIFIER org.mixxx.mixxx CACHE STRING "The iOS app bundle identifier") -elseif (UNIX) + set( + IOS_BUNDLE_NAME + Mixxx + CACHE STRING + "The iOS app bundle and executable name" + ) + set( + IOS_BUNDLE_IDENTIFIER + org.mixxx.mixxx + CACHE STRING + "The iOS app bundle identifier" + ) +elseif(UNIX) set(MIXXX_INSTALL_BINDIR "${CMAKE_INSTALL_BINDIR}") set(MIXXX_INSTALL_DATADIR "${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}") set(MIXXX_INSTALL_DOCDIR "${CMAKE_INSTALL_DOCDIR}") set(MIXXX_INSTALL_LICENSEDIR "${CMAKE_INSTALL_DOCDIR}") endif() - if(WIN32) target_compile_definitions(mixxx-lib PUBLIC __WINDOWS__) @@ -1765,7 +2099,7 @@ if(QT6) find_package(Qt6 COMPONENTS Core) # For Qt Core cmake functions # This is the first package form the environment, if this fails give hints how to install the environment if(NOT Qt6_FOUND) - FATAL_ERROR_MISSING_ENV() + fatal_error_missing_env() endif() # qt_add_executable() is the recommended initial call for qt_finalize_target() # below that takes care of the correct object order in the resulting binary @@ -1776,7 +2110,7 @@ else() find_package(Qt5 COMPONENTS Core) # For Qt Core cmake functions # This is the first package form the environment, if this fails give hints how to install the environment if(NOT Qt5_FOUND) - FATAL_ERROR_MISSING_ENV() + fatal_error_missing_env() endif() add_executable(mixxx WIN32 src/main.cpp) endif() @@ -1787,8 +2121,8 @@ target_link_libraries(mixxx PRIVATE mixxx-lib mixxx-gitinfostore) # # Installation and Packaging # -if (APPLE) - if (IOS) +if(APPLE) + if(IOS) set(IOS_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION}") set(IOS_BUNDLE_SHORTVERSION "${CMAKE_PROJECT_VERSION}") @@ -1797,29 +2131,44 @@ if (APPLE) file(GLOB IOS_RESOURCES res/**) list(APPEND IOS_RESOURCES packaging/ios/Assets.xcassets) target_sources(mixxx PUBLIC ${IOS_RESOURCES}) - set_source_files_properties(${IOS_RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + set_source_files_properties( + ${IOS_RESOURCES} + PROPERTIES MACOSX_PACKAGE_LOCATION Resources + ) source_group(Resources FILES ${IOS_RESOURCES}) - set(QT_IOS_LAUNCH_SCREEN "${CMAKE_CURRENT_SOURCE_DIR}/packaging/ios/LaunchScreen.storyboard") + set( + QT_IOS_LAUNCH_SCREEN + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/ios/LaunchScreen.storyboard" + ) - set_target_properties(mixxx PROPERTIES - MACOSX_BUNDLE true - OUTPUT_NAME "${IOS_BUNDLE_NAME}" - MACOSX_BUNDLE_BUNDLE_NAME "${IOS_BUNDLE_NAME}" - MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/packaging/ios/Info.plist.in" - MACOSX_BUNDLE_GUI_IDENTIFIER "${IOS_BUNDLE_IDENTIFIER}" - XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" + set_target_properties( + mixxx + PROPERTIES + MACOSX_BUNDLE true + OUTPUT_NAME "${IOS_BUNDLE_NAME}" + MACOSX_BUNDLE_BUNDLE_NAME "${IOS_BUNDLE_NAME}" + MACOSX_BUNDLE_INFO_PLIST + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/ios/Info.plist.in" + MACOSX_BUNDLE_GUI_IDENTIFIER "${IOS_BUNDLE_IDENTIFIER}" + XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" + ) + elseif(MACOS_BUNDLE) + install( + FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/osx/application.icns" + DESTINATION ${MIXXX_INSTALL_DATADIR} ) - elseif (MACOS_BUNDLE) - install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/osx/application.icns" DESTINATION ${MIXXX_INSTALL_DATADIR}) set(MACOS_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION}") set(MACOS_BUNDLE_SHORTVERSION "${CMAKE_PROJECT_VERSION}") - set_target_properties(mixxx PROPERTIES + set_target_properties( + mixxx + PROPERTIES MACOSX_BUNDLE true OUTPUT_NAME "${MACOS_BUNDLE_NAME}" - MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/Info.plist.in" + MACOSX_BUNDLE_INFO_PLIST + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/Info.plist.in" ) endif() endif() @@ -1829,7 +2178,10 @@ if(EMSCRIPTEN) # This will generate a mixxx.data file containing all the resources. # See https://emscripten.org/docs/porting/files/packaging_files.html # TODO: Strip this down by only including what we need (i.e. no macOS/Linux packaging, ...) - target_link_options(mixxx-lib PUBLIC "--preload-file=${CMAKE_CURRENT_SOURCE_DIR}/res@/res") + target_link_options( + mixxx-lib + PUBLIC "--preload-file=${CMAKE_CURRENT_SOURCE_DIR}/res@/res" + ) endif() if(WIN32) @@ -1841,49 +2193,37 @@ if(WIN32) endif() install( - TARGETS - mixxx - RUNTIME DESTINATION - "${MIXXX_INSTALL_BINDIR}" - BUNDLE DESTINATION - . + TARGETS mixxx + RUNTIME DESTINATION "${MIXXX_INSTALL_BINDIR}" + BUNDLE DESTINATION . ) # Skins install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/skins" - DESTINATION - "${MIXXX_INSTALL_DATADIR}" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/skins" + DESTINATION "${MIXXX_INSTALL_DATADIR}" ) # Controller mappings install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/controllers" - DESTINATION - "${MIXXX_INSTALL_DATADIR}" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/controllers" + DESTINATION "${MIXXX_INSTALL_DATADIR}" ) # Effect presets install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/effects" - DESTINATION - "${MIXXX_INSTALL_DATADIR}" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/effects" + DESTINATION "${MIXXX_INSTALL_DATADIR}" ) # Translation files install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/translations" - DESTINATION - "${MIXXX_INSTALL_DATADIR}" - FILES_MATCHING PATTERN - "*.qm" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/translations" + DESTINATION "${MIXXX_INSTALL_DATADIR}" + FILES_MATCHING + PATTERN "*.qm" ) - # Font files # # Font installation is only enabled on Windows and macOS, because on Linux/BSD @@ -1894,19 +2234,15 @@ install( # fonts here. if(APPLE OR WIN32) install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts" - DESTINATION - "${MIXXX_INSTALL_DATADIR}" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts" + DESTINATION "${MIXXX_INSTALL_DATADIR}" ) endif() # Keyboard mapping(s) install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/keyboard" - DESTINATION - "${MIXXX_INSTALL_DATADIR}" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/keyboard" + DESTINATION "${MIXXX_INSTALL_DATADIR}" ) # Licenses @@ -1914,8 +2250,7 @@ install( FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_CURRENT_SOURCE_DIR}/COPYING" - DESTINATION - "${MIXXX_INSTALL_LICENSEDIR}" + DESTINATION "${MIXXX_INSTALL_LICENSEDIR}" ) # Documentation @@ -1923,15 +2258,12 @@ install( FILES "${CMAKE_CURRENT_SOURCE_DIR}/README.md" "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Keyboard-Shortcuts.pdf" - DESTINATION - "${MIXXX_INSTALL_DOCDIR}" + DESTINATION "${MIXXX_INSTALL_DOCDIR}" ) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf") install( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf" - DESTINATION - "${MIXXX_INSTALL_DOCDIR}" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf" + DESTINATION "${MIXXX_INSTALL_DOCDIR}" ) endif() @@ -1939,61 +2271,62 @@ endif() if(UNIX AND NOT APPLE) # .desktop file for KDE/GNOME menu install( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/org.mixxx.Mixxx.desktop" - DESTINATION - "${CMAKE_INSTALL_DATADIR}/applications" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/org.mixxx.Mixxx.desktop" + DESTINATION "${CMAKE_INSTALL_DATADIR}/applications" ) # Icon files for menu entry install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/images/icons/" - DESTINATION - "${CMAKE_INSTALL_DATADIR}/icons/hicolor" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/images/icons/" + DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor" # This file is for Windows. PATTERN ic_mixxx.ico EXCLUDE ) # .metainfo.xml file for KDE/GNOME AppStream initiative install( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/org.mixxx.Mixxx.metainfo.xml" - DESTINATION - "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/org.mixxx.Mixxx.metainfo.xml" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo" ) - option(INSTALL_USER_UDEV_RULES "Install user udev rule file for USB HID and Bulk controllers" ON) + option( + INSTALL_USER_UDEV_RULES + "Install user udev rule file for USB HID and Bulk controllers" + ON + ) if(INSTALL_USER_UDEV_RULES) set(MIXXX_UDEVDIR "${MIXXX_INSTALL_DATADIR}/udev") - if (CMAKE_INSTALL_PREFIX STREQUAL "/usr" OR CMAKE_INSTALL_PREFIX STREQUAL "/" ) + if( + CMAKE_INSTALL_PREFIX STREQUAL "/usr" + OR CMAKE_INSTALL_PREFIX STREQUAL "/" + ) # /usr and / install prefixes at treated by cmake GNUInstallDirs as # synonym for "system location". In this case we can look up the correct udevdir # using pkg-config. # See: https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html#special-cases find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_check_modules( PKGCONFIG_UDEV udev) - if (PKGCONFIG_UDEV_FOUND) - execute_process( - COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=udevdir udev + if(PKG_CONFIG_FOUND) + pkg_check_modules(PKGCONFIG_UDEV udev) + if(PKGCONFIG_UDEV_FOUND) + execute_process( + COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=udevdir udev OUTPUT_VARIABLE PKGCONFIG_UDEVDIR OUTPUT_STRIP_TRAILING_WHITESPACE ) if(PKGCONFIG_UDEVDIR) - file(TO_CMAKE_PATH "${PKGCONFIG_UDEVDIR}" MIXXX_UDEVDIR) + file(TO_CMAKE_PATH "${PKGCONFIG_UDEVDIR}" MIXXX_UDEVDIR) endif() endif() endif() endif() - if (MIXXX_UDEVDIR STREQUAL "${MIXXX_INSTALL_DATADIR}/udev") + if(MIXXX_UDEVDIR STREQUAL "${MIXXX_INSTALL_DATADIR}/udev") install( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/mixxx-usb-uaccess.rules" - DESTINATION - "${MIXXX_UDEVDIR}/rules.d" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/mixxx-usb-uaccess.rules" + DESTINATION "${MIXXX_UDEVDIR}/rules.d" ) - install(CODE " + install( + CODE + " message(STATUS \"Important Note: Installation of udev rules\n\" \"The udev rule file for USB HID and Bulk controller permissions have been\n\" \"installed to:\n\" @@ -2006,15 +2339,13 @@ if(UNIX AND NOT APPLE) \"system rules is either /lib/udev/rules.d (e.g. Debian, Fedora) or\n\" \"/usr/lib/udev/rules.d (e.g. Arch Linux) with an appropriate priority prefix.\n\" \"Adjust your package script accordingly and set -DINSTALL_USER_UDEV_RULES=OFF\") - ") + " + ) else() install( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/mixxx-usb-uaccess.rules" - DESTINATION - "${MIXXX_UDEVDIR}/rules.d" - RENAME - "69-mixxx-usb-uaccess.rules" + FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/linux/mixxx-usb-uaccess.rules" + DESTINATION "${MIXXX_UDEVDIR}/rules.d" + RENAME "69-mixxx-usb-uaccess.rules" ) endif() endif() @@ -2026,7 +2357,8 @@ if(MSVC) FILES $ CONFIGURATIONS Debug RelWithDebInfo DESTINATION "${MIXXX_INSTALL_BINDIR}" - COMPONENT PDB # No spaces allowed + COMPONENT + PDB # No spaces allowed ) endif() @@ -2034,12 +2366,18 @@ if(WIN32 AND NOT QT6) # Qt 5 loads these ANGLE DLLs at runtime if the graphics driver is on the ignore list. # It does not work with Debug, because the debug version is compiled without the a d suffix find_package(unofficial-angle CONFIG REQUIRED) - install(IMPORTED_RUNTIME_ARTIFACTS - unofficial::angle::libEGL - unofficial::angle::libGLESv2 - CONFIGURATIONS RelWithDebInfo Release - DESTINATION "${MIXXX_INSTALL_BINDIR}" - COMPONENT applocal) + install( + IMPORTED_RUNTIME_ARTIFACTS + unofficial::angle::libEGL + unofficial::angle::libGLESv2 + CONFIGURATIONS + RelWithDebInfo + Release + DESTINATION + "${MIXXX_INSTALL_BINDIR}" + COMPONENT + applocal + ) set(APPLOCAL_COMPONENT_DEFINED true) endif() @@ -2047,137 +2385,212 @@ endif() # Tests # -add_executable(mixxx-test - src/test/analyserwaveformtest.cpp - src/test/analyzersilence_test.cpp - src/test/audiotaperpot_test.cpp - src/test/autodjprocessor_test.cpp - src/test/beatgridtest.cpp - src/test/beatmaptest.cpp - src/test/beatstest.cpp - src/test/beatstranslatetest.cpp - src/test/bpmtest.cpp - src/test/bpmcontrol_test.cpp - src/test/broadcastprofile_test.cpp - src/test/broadcastsettings_test.cpp - src/test/cache_test.cpp - src/test/channelhandle_test.cpp - src/test/chrono_clock_resolution_test.cpp - src/test/colorconfig_test.cpp - src/test/colormapperjsproxy_test.cpp - 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/controllers/controller_columnid_regression_test.cpp - src/test/controllerscriptenginelegacy_test.cpp - src/test/controlobjecttest.cpp - src/test/controlobjectaliastest.cpp - src/test/controlobjectscripttest.cpp - src/test/controlpotmetertest.cpp - src/test/coreservicestest.cpp - src/test/coverartcache_test.cpp - src/test/coverartutils_test.cpp - src/test/cratestorage_test.cpp - src/test/cue_test.cpp - src/test/cuecontrol_test.cpp - src/test/dbconnectionpool_test.cpp - src/test/dbidtest.cpp - src/test/directorydaotest.cpp - src/test/duration_test.cpp - src/test/durationutiltest.cpp - #TODO: write useful tests for refactored effects system - #src/test/effectchainslottest.cpp - src/test/enginebufferscalelineartest.cpp - src/test/enginebuffertest.cpp - src/test/engineeffectsdelay_test.cpp - src/test/enginefilterbiquadtest.cpp - src/test/enginemixertest.cpp - src/test/enginemicrophonetest.cpp - src/test/enginesynctest.cpp - src/test/fileinfo_test.cpp - src/test/frametest.cpp - src/test/globaltrackcache_test.cpp - src/test/hotcuecontrol_test.cpp - src/test/imageutils_test.cpp - src/test/indexrange_test.cpp - src/test/itunesxmlimportertest.cpp - src/test/keyfactorytest.cpp - src/test/keyutilstest.cpp - src/test/lcstest.cpp - src/test/learningutilstest.cpp - src/test/libraryscannertest.cpp - src/test/librarytest.cpp - src/test/looping_control_test.cpp - src/test/main.cpp - src/test/mathutiltest.cpp - src/test/metadatatest.cpp - #TODO: make this build again - #src/test/metaknob_link_test.cpp - src/test/midicontrollertest.cpp - src/test/mixxxtest.cpp - src/test/mock_networkaccessmanager.cpp - src/test/movinginterquartilemean_test.cpp - src/test/musicbrainzrecordingstasktest.cpp - src/test/nativeeffects_test.cpp - src/test/performancetimer_test.cpp - src/test/playcountertest.cpp - src/test/playermanagertest.cpp - src/test/playlisttest.cpp - src/test/portmidicontroller_test.cpp - src/test/portmidienumeratortest.cpp - src/test/queryutiltest.cpp - src/test/rangelist_test.cpp - src/test/readaheadmanager_test.cpp - src/test/replaygaintest.cpp - src/test/rescalertest.cpp - src/test/rgbcolor_test.cpp - src/test/ringdelaybuffer_test.cpp - src/test/samplebuffertest.cpp - src/test/sampleutiltest.cpp - src/test/schemamanager_test.cpp - src/test/searchqueryparsertest.cpp - src/test/seratobeatgridtest.cpp - src/test/seratomarkerstest.cpp - src/test/seratomarkers2test.cpp - src/test/seratotagstest.cpp - src/test/signalpathtest.cpp - src/test/skincontext_test.cpp - src/test/softtakeover_test.cpp - src/test/soundproxy_test.cpp - src/test/soundsourceproviderregistrytest.cpp - src/test/sqliteliketest.cpp - src/test/synccontroltest.cpp - src/test/synctrackmetadatatest.cpp - src/test/tableview_test.cpp - src/test/taglibtest.cpp - src/test/trackdao_test.cpp - src/test/trackexport_test.cpp - src/test/trackmetadata_test.cpp - src/test/trackmetadataexport_test.cpp - src/test/tracknumberstest.cpp - src/test/trackreftest.cpp - src/test/trackupdate_test.cpp - src/test/uuid_test.cpp - src/test/wbatterytest.cpp - src/test/wpushbutton_test.cpp - src/test/wwidgetstack_test.cpp - src/test/waveform_upgrade_test.cpp - src/util/moc_included_test.cpp - src/test/helpers/log_test.cpp -) -if (QML) - target_sources(mixxx-test PRIVATE - src/test/controller_mapping_file_handler_test.cpp - src/test/controllerrenderingengine_test.cpp - ) +find_package(GTest CONFIG) +default_option(BUILD_TESTING "Build with Unittests" "GTest_FOUND") +if(BUILD_TESTING) + if(GTest_FOUND) + message(STATUS "Found GTest: Unittests enabled") + else() + message(FATAL_ERROR "GTest: not found") + endif() endif() -find_package(GTest CONFIG REQUIRED) -set_target_properties(mixxx-test PROPERTIES AUTOMOC ON) -target_link_libraries(mixxx-test PRIVATE mixxx-lib mixxx-gitinfostore GTest::gtest GTest::gmock) find_package(benchmark) -target_link_libraries(mixxx-test PRIVATE benchmark::benchmark) +default_option(BUILD_BENCH "Build mixxx-benchmark" "benchmark_FOUND") +if(BUILD_BENCH AND BUILD_TESTING) + if(benchmark_FOUND) + message(STATUS "Found google-benchmark: mixxx-benchmark enabled") + else() + message(FATAL_ERROR "google-benchmark: not found") + endif() +elseif(BUILD_BENCH AND NOT BUILD_TESTING) + message(FATAL_ERROR "Benchmark needs Unittests (-DBUILD_TESTING=ON)") +endif() + +if(BUILD_TESTING) + set( + src-mixxx-test + src/test/analyserwaveformtest.cpp + src/test/analyzersilence_test.cpp + src/test/audiotaperpot_test.cpp + src/test/autodjprocessor_test.cpp + src/test/beatgridtest.cpp + src/test/beatmaptest.cpp + src/test/beatstest.cpp + src/test/beatstranslatetest.cpp + src/test/bpmtest.cpp + src/test/bpmcontrol_test.cpp + src/test/broadcastprofile_test.cpp + src/test/broadcastsettings_test.cpp + src/test/cache_test.cpp + src/test/channelhandle_test.cpp + src/test/chrono_clock_resolution_test.cpp + src/test/colorconfig_test.cpp + src/test/colormapperjsproxy_test.cpp + 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/controllers/controller_columnid_regression_test.cpp + src/test/controllerscriptenginelegacy_test.cpp + src/test/controlobjecttest.cpp + src/test/controlobjectaliastest.cpp + src/test/controlobjectscripttest.cpp + src/test/controlpotmetertest.cpp + src/test/coreservicestest.cpp + src/test/coverartcache_test.cpp + src/test/coverartutils_test.cpp + src/test/cratestorage_test.cpp + src/test/cue_test.cpp + src/test/cuecontrol_test.cpp + src/test/dbconnectionpool_test.cpp + src/test/dbidtest.cpp + src/test/directorydaotest.cpp + src/test/duration_test.cpp + src/test/durationutiltest.cpp + #TODO: write useful tests for refactored effects system + #src/test/effectchainslottest.cpp + src/test/enginebufferscalelineartest.cpp + src/test/enginebuffertest.cpp + src/test/enginefilterbiquadtest.cpp + src/test/enginemixertest.cpp + src/test/enginemicrophonetest.cpp + src/test/enginesynctest.cpp + src/test/fileinfo_test.cpp + src/test/frametest.cpp + src/test/globaltrackcache_test.cpp + src/test/hotcuecontrol_test.cpp + src/test/imageutils_test.cpp + src/test/indexrange_test.cpp + src/test/itunesxmlimportertest.cpp + src/test/keyfactorytest.cpp + src/test/keyutilstest.cpp + src/test/lcstest.cpp + src/test/learningutilstest.cpp + src/test/libraryscannertest.cpp + src/test/librarytest.cpp + src/test/looping_control_test.cpp + src/test/main.cpp + src/test/mathutiltest.cpp + src/test/metadatatest.cpp + #TODO: make this build again + #src/test/metaknob_link_test.cpp + src/test/midicontrollertest.cpp + src/test/mixxxtest.cpp + src/test/mock_networkaccessmanager.cpp + src/test/musicbrainzrecordingstasktest.cpp + src/test/performancetimer_test.cpp + src/test/playcountertest.cpp + src/test/playermanagertest.cpp + src/test/playlisttest.cpp + src/test/portmidicontroller_test.cpp + src/test/portmidienumeratortest.cpp + src/test/queryutiltest.cpp + src/test/rangelist_test.cpp + src/test/readaheadmanager_test.cpp + src/test/replaygaintest.cpp + src/test/rescalertest.cpp + src/test/rgbcolor_test.cpp + src/test/rotary_test.cpp + src/test/samplebuffertest.cpp + src/test/schemamanager_test.cpp + src/test/searchqueryparsertest.cpp + src/test/seratobeatgridtest.cpp + src/test/seratomarkerstest.cpp + src/test/seratomarkers2test.cpp + src/test/seratotagstest.cpp + src/test/signalpathtest.cpp + src/test/skincontext_test.cpp + src/test/softtakeover_test.cpp + src/test/soundproxy_test.cpp + src/test/soundsourceproviderregistrytest.cpp + src/test/sqliteliketest.cpp + src/test/synccontroltest.cpp + src/test/synctrackmetadatatest.cpp + src/test/tableview_test.cpp + src/test/taglibtest.cpp + src/test/trackdao_test.cpp + src/test/trackexport_test.cpp + src/test/trackmetadata_test.cpp + src/test/trackmetadataexport_test.cpp + src/test/tracknumberstest.cpp + src/test/trackreftest.cpp + src/test/trackupdate_test.cpp + src/test/uuid_test.cpp + src/test/wbatterytest.cpp + src/test/wpushbutton_test.cpp + src/test/wwidgetstack_test.cpp + src/util/moc_included_test.cpp + src/test/helpers/log_test.cpp + ) + if(BUILD_BENCH) + set( + src-mixxx-test + ${src-mixxx-test} + src/test/engineeffectsdelay_test.cpp + src/test/movinginterquartilemean_test.cpp + src/test/nativeeffects_test.cpp + src/test/ringdelaybuffer_test.cpp + src/test/sampleutiltest.cpp + src/test/waveform_upgrade_test.cpp + ) + endif() + + add_executable(mixxx-test ${src-mixxx-test}) + + if(QML) + target_sources( + mixxx-test + PRIVATE + src/test/controller_mapping_file_handler_test.cpp + src/test/controllerrenderingengine_test.cpp + ) + endif() + + set_target_properties(mixxx-test PROPERTIES AUTOMOC ON) + target_link_libraries( + mixxx-test + PRIVATE mixxx-lib mixxx-gitinfostore GTest::gtest GTest::gmock + ) + + if(BUILD_BENCH) + add_compile_definitions(USE_BENCH) + target_link_libraries(mixxx-test PRIVATE benchmark::benchmark) + endif() + + # Test Suite + include(CTest) + include(GoogleTest) + enable_testing() + gtest_add_tests( + TARGET mixxx-test + EXTRA_ARGS --logLevel info + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + TEST_LIST testsuite + ) + + if(NOT WIN32) + # Default to offscreen rendering during tests. + # This is required if the build system like Fedora koji/mock does not + # allow to pass environment variables into the ctest macro expansion. + set_tests_properties( + ${testsuite} + PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" + ) + endif() + + if(BUILD_BENCH) + # Benchmarking + add_custom_target( + mixxx-benchmark + COMMAND $ --benchmark + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Mixxx Benchmarks" + VERBATIM + ) + add_dependencies(mixxx-benchmark mixxx-test) + endif() +endif() # BUILD_TESTING # # Resources @@ -2187,12 +2600,17 @@ target_link_libraries(mixxx-test PRIVATE benchmark::benchmark) # calls that are not present at the moment. Further information can be found # at: https://doc.qt.io/qt5/resources.html#using-resources-in-a-library option(DOWNLOAD_MANUAL "Download Manual PDF from Mixxx website" OFF) -if(DOWNLOAD_MANUAL AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf") - set(MANUAL_URL "https://downloads.mixxx.org/manual/${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}/mixxx-manual-${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}-en.pdf") +if( + DOWNLOAD_MANUAL + AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf" +) + set( + MANUAL_URL + "https://downloads.mixxx.org/manual/${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}/mixxx-manual-${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}-en.pdf" + ) message(STATUS "Downloading manual from ${MANUAL_URL}...") - file(DOWNLOAD - "${MANUAL_URL}" - "${CMAKE_CURRENT_BINARY_DIR}/res/Mixxx-Manual.pdf" + file( + DOWNLOAD "${MANUAL_URL}" "${CMAKE_CURRENT_BINARY_DIR}/res/Mixxx-Manual.pdf" SHOW_PROGRESS STATUS MANUAL_PDF_DOWNLOAD TLS_VERIFY ON @@ -2200,25 +2618,34 @@ if(DOWNLOAD_MANUAL AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual. list(GET MANUAL_PDF_DOWNLOAD 0 MANUAL_PDF_DOWNLOAD_ERROR) if(NOT MANUAL_PDF_DOWNLOAD_ERROR EQUAL 0) list(GET MANUAL_PDF_DOWNLOAD 1 MANUAL_PDF_DOWNLOAD_MESSGAE) - message(FATAL_ERROR "Manual PDF download failed with: " - "${MANUAL_PDF_DOWNLOAD_MESSGAE} Code: ${MANUAL_PDF_DOWNLOAD_ERROR}. " - "Either download it yourself and move it to " - "'${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf' or " - "reconfigure with -DDOWNLOAD_MANUAL=OFF to build without included " - "manual.") + message( + FATAL_ERROR + "Manual PDF download failed with: " + "${MANUAL_PDF_DOWNLOAD_MESSGAE} Code: ${MANUAL_PDF_DOWNLOAD_ERROR}. " + "Either download it yourself and move it to " + "'${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf' or " + "reconfigure with -DDOWNLOAD_MANUAL=OFF to build without included " + "manual." + ) endif() - file(RENAME "${CMAKE_CURRENT_BINARY_DIR}/res/Mixxx-Manual.pdf" "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf") + file( + RENAME + "${CMAKE_CURRENT_BINARY_DIR}/res/Mixxx-Manual.pdf" + "${CMAKE_CURRENT_SOURCE_DIR}/res/Mixxx-Manual.pdf" + ) endif() target_sources(mixxx PRIVATE res/mixxx.qrc) set_target_properties(mixxx PROPERTIES AUTORCC ON) -target_sources(mixxx-test PRIVATE res/mixxx.qrc) -set_target_properties(mixxx-test PROPERTIES AUTORCC ON) +if(BUILD_TESTING) + target_sources(mixxx-test PRIVATE res/mixxx.qrc) + set_target_properties(mixxx-test PROPERTIES AUTORCC ON) +endif() -if (MIXXX_VERSION_PRERELEASE STREQUAL "") - set(MIXXX_VERSION "${CMAKE_PROJECT_VERSION}") +if(MIXXX_VERSION_PRERELEASE STREQUAL "") + set(MIXXX_VERSION "${CMAKE_PROJECT_VERSION}") else() - set(MIXXX_VERSION "${CMAKE_PROJECT_VERSION}-${MIXXX_VERSION_PRERELEASE}") + set(MIXXX_VERSION "${CMAKE_PROJECT_VERSION}-${MIXXX_VERSION_PRERELEASE}") endif() get_target_property(MIXXX_BUILD_FLAGS mixxx-lib COMPILE_OPTIONS) @@ -2226,36 +2653,44 @@ get_target_property(MIXXX_BUILD_FLAGS mixxx-lib COMPILE_OPTIONS) # uses CMAKE_PROJECT_VERSION MIXXX_VERSION_PRERELEASE MIXXX_BUILD_FLAGS configure_file(src/version.h.in src/version.h @ONLY) -if(GIT_COMMIT_DATE AND NOT GIT_COMMIT_DATE MATCHES "^[0-9]*-[0-9]*-[0-9]*T[0-9]*\\:[0-9]*\\:[0-9]*[+-][0-9]*\\:[0-9]*$") - message(FATAL_ERROR "GIT_COMMIT_DATE requires strict ISO 8601 format %Y-%m-%dT%H:%M:%SZ") +if( + GIT_COMMIT_DATE + AND + NOT + GIT_COMMIT_DATE + MATCHES + "^[0-9]*-[0-9]*-[0-9]*T[0-9]*\\:[0-9]*\\:[0-9]*[+-][0-9]*\\:[0-9]*$" +) + message( + FATAL_ERROR + "GIT_COMMIT_DATE requires strict ISO 8601 format %Y-%m-%dT%H:%M:%SZ" + ) endif() -add_custom_target(mixxx-gitinfo +add_custom_target( + mixxx-gitinfo # Note: We don't quote the paths in the command since CMake already inserts # escapes (which, if quoted, lead to paths wrongly containing backslashes). # See https://stackoverflow.com/questions/8925396/why-does-cmake-prefixes-spaces-with-backslashes-when-executing-a-command - COMMAND ${CMAKE_COMMAND} - -DGIT_DESCRIBE=${GIT_DESCRIBE} + COMMAND + ${CMAKE_COMMAND} -DGIT_DESCRIBE=${GIT_DESCRIBE} -DGIT_COMMIT_DATE=${GIT_COMMIT_DATE} -DINPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/src/gitinfo.h.in - -DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/src/gitinfo.h - -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/gitinfo.cmake + -DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/src/gitinfo.h -P + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/gitinfo.cmake COMMENT "Update git version information in gitinfo.h" BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/src/gitinfo.h" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" ) -add_library(mixxx-gitinfostore STATIC EXCLUDE_FROM_ALL - src/util/gitinfostore.cpp -) -target_include_directories(mixxx-gitinfostore PUBLIC src ${CMAKE_BINARY_DIR}/src) -add_dependencies(mixxx-gitinfostore mixxx-gitinfo) - # Windows-only resource file if(WIN32) string(TIMESTAMP MIXXX_YEAR "%Y") - set(MIXXX_FILEVERSION "${CMAKE_PROJECT_VERSION_MAJOR},${CMAKE_PROJECT_VERSION_MINOR},${CMAKE_PROJECT_VERSION_PATCH}") + set( + MIXXX_FILEVERSION + "${CMAKE_PROJECT_VERSION_MAJOR},${CMAKE_PROJECT_VERSION_MINOR},${CMAKE_PROJECT_VERSION_PATCH}" + ) set(MIXXX_PRODUCTVERSION "${MIXXX_FILEVERSION}") if(CMAKE_BUILD_TYPE STREQUAL "Debug") @@ -2264,24 +2699,22 @@ if(WIN32) set(MIXXX_DEBUG 0) endif() - if (MIXXX_VERSION_PRERELEASE STREQUAL "") + if(MIXXX_VERSION_PRERELEASE STREQUAL "") set(MIXXX_PRERELEASE 0) else() set(MIXXX_PRERELEASE 1) endif() # uses MIXXX_YEAR MIXXX_FILEVERSION MIXXX_PRODUCTVERSION MIXXX_VERSION MIXXX_DEBUG MIXXX_PRERELEASE - configure_file( - "src/mixxx.rc.include.in" - "src/mixxx.rc.include" - @ONLY - ) + configure_file("src/mixxx.rc.include.in" "src/mixxx.rc.include" @ONLY) add_dependencies(mixxx mixxx-gitinfo) - target_sources(mixxx PRIVATE - src/mixxx.rc - "${CMAKE_CURRENT_BINARY_DIR}/src/mixxx.rc.include" - "${CMAKE_CURRENT_BINARY_DIR}/src/gitinfo.h" + target_sources( + mixxx + PRIVATE + src/mixxx.rc + "${CMAKE_CURRENT_BINARY_DIR}/src/mixxx.rc.include" + "${CMAKE_CURRENT_BINARY_DIR}/src/gitinfo.h" ) target_include_directories(mixxx PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") endif() @@ -2308,10 +2741,19 @@ else() set(LOCALECOMPARE_DEFAULT OFF) endif() endif() -cmake_dependent_option(LOCALECOMPARE "Locale Aware Compare support for SQLite" ON "LOCALECOMPARE_DEFAULT" OFF) +cmake_dependent_option( + LOCALECOMPARE + "Locale Aware Compare support for SQLite" + ON + "LOCALECOMPARE_DEFAULT" + OFF +) if(LOCALECOMPARE) if(NOT SQLite3_FOUND) - message(FATAL_ERROR "Locale Aware Compare for SQLite requires libsqlite and its development headers.") + message( + FATAL_ERROR + "Locale Aware Compare for SQLite requires libsqlite and its development headers." + ) endif() target_compile_definitions(mixxx-lib PUBLIC __SQLITE3__) target_link_libraries(mixxx-lib PRIVATE SQLite::SQLite3) @@ -2325,11 +2767,11 @@ option(ENGINEPRIME "Support for library export to Denon Engine Prime" ON) if(ENGINEPRIME) # libdjinterop does not currently have a stable ABI, so we fetch sources for a specific tag, build here, and link # statically. This situation should be reviewed once libdjinterop hits version 1.x. - set(LIBDJINTEROP_VERSION 0.21.0) + set(LIBDJINTEROP_VERSION 0.22.1) # Look whether an existing installation of libdjinterop matches the required version. - find_package(DjInterop ${LIBDJINTEROP_VERSION} EXACT CONFIG) + find_package(DjInterop ${LIBDJINTEROP_VERSION} EXACT CONFIG) if(NOT DjInterop_FOUND) - find_package(DjInterop ${LIBDJINTEROP_VERSION} EXACT MODULE) + find_package(DjInterop ${LIBDJINTEROP_VERSION} EXACT MODULE) endif() if(DjInterop_FOUND) @@ -2339,36 +2781,62 @@ if(ENGINEPRIME) else() # On MacOS, Mixxx does not use system SQLite, so we will use libdjinterop's # embedded SQLite in such a case. - if (APPLE AND NOT SQLite3_IS_STATIC) - message(STATUS "Building libdjinterop sources (with embedded SQLite) fetched from GitHub") + if(APPLE AND NOT SQLite3_IS_STATIC) + message( + STATUS + "Building libdjinterop sources (with embedded SQLite) fetched from GitHub" + ) set(DJINTEROP_SYSTEM_SQLITE OFF) else() - message(STATUS "Building libdjinterop sources (with system SQLite) fetched from GitHub") + message( + STATUS + "Building libdjinterop sources (with system SQLite) fetched from GitHub" + ) set(DJINTEROP_SYSTEM_SQLITE ON) endif() - set(DJINTEROP_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/lib/libdjinterop-install") - set(DJINTEROP_LIBRARY "lib/${CMAKE_STATIC_LIBRARY_PREFIX}djinterop${CMAKE_STATIC_LIBRARY_SUFFIX}") + set( + DJINTEROP_INSTALL_DIR + "${CMAKE_CURRENT_BINARY_DIR}/lib/libdjinterop-install" + ) + set( + DJINTEROP_LIBRARY + "lib/${CMAKE_STATIC_LIBRARY_PREFIX}djinterop${CMAKE_STATIC_LIBRARY_SUFFIX}" + ) # CMake does not pass lists of paths properly to external projects. # This is worked around by changing the list separator. - string(REPLACE ";" "|" PIPE_DELIMITED_CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}") + string( + REPLACE + ";" + "|" + PIPE_DELIMITED_CMAKE_PREFIX_PATH + "${CMAKE_PREFIX_PATH}" + ) # For offline builds download the archive file from the URL and # copy it into DOWNLOAD_DIR under DOWNLOAD_NAME prior to starting # the configuration. - ExternalProject_Add(libdjinterop + # + # If you want to test (locally) an experimental fork/branch of libdjinterop, + # you can comment out URL and URL_HASH and use GIT_REPOSITORY instead: + # + # GIT_REPOSITORY "https://github.com/abcd/your-fork-of-libdjinterop" + # GIT_TAG "origin/name-of-your-branch" + # + ExternalProject_Add( + libdjinterop URL "https://github.com/xsco/libdjinterop/archive/refs/tags/${LIBDJINTEROP_VERSION}.tar.gz" "https://launchpad.net/~xsco/+archive/ubuntu/djinterop/+sourcefiles/libdjinterop/${LIBDJINTEROP_VERSION}-0ubuntu1/libdjinterop_${LIBDJINTEROP_VERSION}.orig.tar.gz" - URL_HASH SHA256=160d4e09b25e859816a6b664058e7c6bc5cd889adeb188a9721c2b65d2133641 + URL_HASH + SHA256=e811158d42c3864f5b682bcf76e0af78278050439d82d14d592dd0a391da6b20 DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/downloads" DOWNLOAD_NAME "libdjinterop-${LIBDJINTEROP_VERSION}.tar.gz" INSTALL_DIR ${DJINTEROP_INSTALL_DIR} LIST_SEPARATOR "|" CMAKE_ARGS - -DBUILD_SHARED_LIBS=OFF - -DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON + -DBUILD_SHARED_LIBS=OFF -DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${PIPE_DELIMITED_CMAKE_PREFIX_PATH} @@ -2402,25 +2870,32 @@ if(ENGINEPRIME) OUTPUT "${DJINTEROP_INSTALL_DIR}/${DJINTEROP_LIBRARY}" DEPENDS libdjinterop COMMAND echo libdjinterop installed + COMMENT + "Tell 'make' that libdjinterop is required for the djinterop target" ) endif() # Assemble a library based on the external project. add_library(mixxx-libdjinterop STATIC IMPORTED) - set_target_properties(mixxx-libdjinterop PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${DJINTEROP_INSTALL_DIR}/include" - INTERFACE_LINK_LIBRARIES ZLIB::ZLIB - IMPORTED_LOCATION "${DJINTEROP_INSTALL_DIR}/${DJINTEROP_LIBRARY}" + set_target_properties( + mixxx-libdjinterop + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${DJINTEROP_INSTALL_DIR}/include" + INTERFACE_LINK_LIBRARIES ZLIB::ZLIB + IMPORTED_LOCATION "${DJINTEROP_INSTALL_DIR}/${DJINTEROP_LIBRARY}" ) add_dependencies(mixxx-libdjinterop libdjinterop) target_link_libraries(mixxx-lib PRIVATE mixxx-libdjinterop) endif() # Include conditional sources only required with Engine Prime export support. - target_sources(mixxx-lib PRIVATE - src/library/export/dlglibraryexport.cpp - src/library/export/engineprimeexportjob.cpp - src/library/export/libraryexporter.cpp) + target_sources( + mixxx-lib + PRIVATE + src/library/export/dlglibraryexport.cpp + src/library/export/engineprimeexportjob.cpp + src/library/export/libraryexporter.cpp + ) target_compile_definitions(mixxx-lib PUBLIC __ENGINEPRIME__) endif() @@ -2436,10 +2911,26 @@ if(MSVC) target_compile_options(fidlib PRIVATE /W3) elseif(MINGW) target_compile_definitions(fidlib PRIVATE T_MINGW) - target_compile_options(fidlib PRIVATE -fno-finite-math-only -Wall -Wextra -Wfloat-conversion -Werror=return-type) + target_compile_options( + fidlib + PRIVATE + -fno-finite-math-only + -Wall + -Wextra + -Wfloat-conversion + -Werror=return-type + ) else() target_compile_definitions(fidlib PRIVATE T_LINUX) - target_compile_options(fidlib PRIVATE -fno-finite-math-only -Wall -Wextra -Wfloat-conversion -Werror=return-type) + target_compile_options( + fidlib + PRIVATE + -fno-finite-math-only + -Wall + -Wextra + -Wfloat-conversion + -Werror=return-type + ) endif() target_include_directories(mixxx-lib SYSTEM PUBLIC lib/fidlib) target_link_libraries(mixxx-lib PRIVATE fidlib) @@ -2455,31 +2946,45 @@ if(KEYFINDER) set(MIN_LIBKEYFINDER_VERSION 2.2.4) set(FETCH_LIBKEYFINDER_VERSION 2.2.8) find_package(KeyFinder ${MIN_LIBKEYFINDER_VERSION}) - if (KeyFinder_FOUND) + if(KeyFinder_FOUND) target_link_libraries(mixxx-lib PRIVATE KeyFinder::KeyFinder) else() # If KeyFinder is built statically, we need FFTW - find_package(FFTW REQUIRED) - set(KeyFinder_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/lib/keyfinder-install") - set(KeyFinder_LIBRARY "${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}keyfinder${CMAKE_STATIC_LIBRARY_SUFFIX}") + find_package(FFTW3 REQUIRED) + set( + KeyFinder_INSTALL_DIR + "${CMAKE_CURRENT_BINARY_DIR}/lib/keyfinder-install" + ) + set( + KeyFinder_LIBRARY + "${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}keyfinder${CMAKE_STATIC_LIBRARY_SUFFIX}" + ) # CMake does not pass lists of paths properly to external projects. # This is worked around by changing the list separator. - string(REPLACE ";" "|" PIPE_DELIMITED_CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}") + string( + REPLACE + ";" + "|" + PIPE_DELIMITED_CMAKE_PREFIX_PATH + "${CMAKE_PREFIX_PATH}" + ) # For offline builds download the archive file from the URL and # copy it into DOWNLOAD_DIR under DOWNLOAD_NAME prior to starting # the configuration. - ExternalProject_Add(libkeyfinder - URL "https://github.com/mixxxdj/libkeyfinder/archive/refs/tags/${FETCH_LIBKEYFINDER_VERSION}.zip" - URL_HASH SHA256=4f10e9e5673d948776e47e78273fa4d61408155cb0e210af1538c83222f285d4 + ExternalProject_Add( + libkeyfinder + URL + "https://github.com/mixxxdj/libkeyfinder/archive/refs/tags/${FETCH_LIBKEYFINDER_VERSION}.zip" + URL_HASH + SHA256=4f10e9e5673d948776e47e78273fa4d61408155cb0e210af1538c83222f285d4 DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/downloads" DOWNLOAD_NAME "libkeyfinder-${FETCH_LIBKEYFINDER_VERSION}.zip" INSTALL_DIR "${KeyFinder_INSTALL_DIR}" LIST_SEPARATOR "|" CMAKE_ARGS - -DBUILD_SHARED_LIBS=OFF - -DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON + -DBUILD_SHARED_LIBS=OFF -DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=ON -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${PIPE_DELIMITED_CMAKE_PREFIX_PATH} @@ -2488,12 +2993,10 @@ if(KEYFINDER) -$,D,U>CMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMEN_TARGET} -$,D,U>CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} -DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR} - -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} - -DBUILD_TESTING=OFF + -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME} -DBUILD_TESTING=OFF BUILD_COMMAND ${CMAKE_COMMAND} --build . BUILD_BYPRODUCTS /${KeyFinder_LIBRARY} EXCLUDE_FROM_ALL TRUE - LIST_SEPARATOR | ) # This is a bit of a hack to make sure that the include directory actually @@ -2514,14 +3017,23 @@ if(KEYFINDER) OUTPUT "${KeyFinder_INSTALL_DIR}/${KeyFinder_LIBRARY}" DEPENDS libkeyfinder COMMAND echo libkeyfinder installed + COMMENT + "Tell 'make' that libkeyfinder is required for the mixxx-keyfinder target" ) endif() add_library(mixxx-keyfinder STATIC IMPORTED) add_dependencies(mixxx-keyfinder libkeyfinder) - set_target_properties(mixxx-keyfinder PROPERTIES IMPORTED_LOCATION "${KeyFinder_INSTALL_DIR}/${KeyFinder_LIBRARY}") - target_link_libraries(mixxx-keyfinder INTERFACE FFTW::FFTW) - target_include_directories(mixxx-keyfinder INTERFACE "${KeyFinder_INSTALL_DIR}/include") + set_target_properties( + mixxx-keyfinder + PROPERTIES + IMPORTED_LOCATION "${KeyFinder_INSTALL_DIR}/${KeyFinder_LIBRARY}" + ) + target_link_libraries(mixxx-keyfinder INTERFACE FFTW3::fftw3) + target_include_directories( + mixxx-keyfinder + INTERFACE "${KeyFinder_INSTALL_DIR}/include" + ) target_link_libraries(mixxx-lib PRIVATE mixxx-keyfinder) endif() @@ -2538,7 +3050,10 @@ target_link_libraries(mixxx-lib PRIVATE FLAC::FLAC) # from -ffast-math optimized objects. The MSVC option /fp:fast does not suffer this issue add_library(FpClassify STATIC EXCLUDE_FROM_ALL src/util/fpclassify.cpp) -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") +if( + CMAKE_CXX_COMPILER_ID MATCHES "Clang" + AND CMAKE_CXX_SIMULATE_ID MATCHES "MSVC" +) target_compile_options(FpClassify PRIVATE /fp:precise) elseif(GNU_GCC OR LLVM_CLANG) # The option `-ffp-contract=on` must precede `-fno-fast-math` @@ -2551,38 +3066,49 @@ target_link_libraries(mixxx-lib PRIVATE FpClassify) find_package(mp3lame REQUIRED) target_link_libraries(mixxx-lib PRIVATE mp3lame::mp3lame) -add_library(rekordbox_metadata STATIC EXCLUDE_FROM_ALL +add_library( + rekordbox_metadata + STATIC + EXCLUDE_FROM_ALL lib/rekordbox-metadata/rekordbox_pdb.cpp lib/rekordbox-metadata/rekordbox_anlz.cpp ) -target_include_directories(rekordbox_metadata SYSTEM PUBLIC lib/rekordbox-metadata) +target_include_directories( + rekordbox_metadata + SYSTEM + PUBLIC lib/rekordbox-metadata +) target_link_libraries(mixxx-lib PRIVATE rekordbox_metadata) #silence "enumeration values not handled in switch" in generated code if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(rekordbox_metadata PRIVATE -Wno-switch) + target_compile_options(rekordbox_metadata PRIVATE -Wno-switch) elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - target_compile_options(rekordbox_metadata PRIVATE /w44063) + target_compile_options(rekordbox_metadata PRIVATE /w44063) endif() # Kaitai for reading Rekordbox libraries -add_library(Kaitai STATIC EXCLUDE_FROM_ALL - lib/kaitai/kaitai/kaitaistream.cpp -) +add_library(Kaitai STATIC EXCLUDE_FROM_ALL lib/kaitai/kaitai/kaitaistream.cpp) target_include_directories(Kaitai SYSTEM PUBLIC lib/kaitai) target_compile_definitions(Kaitai PRIVATE KS_STR_ENCODING_NONE) target_link_libraries(rekordbox_metadata PRIVATE Kaitai) target_link_libraries(mixxx-lib PRIVATE Kaitai) # For determining MP3 timing offset cases in Rekordbox library feature -add_library(MP3GuessEnc STATIC EXCLUDE_FROM_ALL +add_library( + MP3GuessEnc + STATIC + EXCLUDE_FROM_ALL lib/mp3guessenc-0.27.4/mp3guessenc.c lib/mp3guessenc-0.27.4/tags.c lib/mp3guessenc-0.27.4/decode.c lib/mp3guessenc-0.27.4/bit_utils.c ) if(WIN32) - target_compile_definitions(MP3GuessEnc PRIVATE __WINDOWS__ _CRT_SECURE_NO_WARNINGS) + target_compile_definitions( + MP3GuessEnc + PRIVATE __WINDOWS__ _CRT_SECURE_NO_WARNINGS + ) endif() target_include_directories(MP3GuessEnc SYSTEM PUBLIC lib/mp3guessenc-0.27.4) target_link_libraries(mixxx-lib PRIVATE MP3GuessEnc) @@ -2609,7 +3135,10 @@ else() # Require WebGL 2.0 (for a WebGL-friendly subset of OpenGL ES 3.0) and # enable full OpenGL ES 2.0 emulation as per # https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html - target_link_options(mixxx-lib PUBLIC -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 -sFULL_ES2=1) + target_link_options( + mixxx-lib + PUBLIC -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 -sFULL_ES2=1 + ) else() target_link_libraries(mixxx-lib PRIVATE OpenGL::GL) endif() @@ -2625,14 +3154,20 @@ target_link_libraries(mixxx-lib PRIVATE Ogg::ogg) # Vorbis find_package(Vorbis REQUIRED) -target_link_libraries(mixxx-lib PRIVATE Vorbis::vorbis Vorbis::vorbisenc Vorbis::vorbisfile) +target_link_libraries( + mixxx-lib + PRIVATE Vorbis::vorbis Vorbis::vorbisenc Vorbis::vorbisfile +) # PortAudio find_package(PortAudio REQUIRED) target_link_libraries(mixxx-lib PUBLIC PortAudio::PortAudio) # PortAudio Ring Buffer -add_library(PortAudioRingBuffer STATIC EXCLUDE_FROM_ALL +add_library( + PortAudioRingBuffer + STATIC + EXCLUDE_FROM_ALL lib/portaudio/pa_ringbuffer.c ) target_include_directories(mixxx-lib SYSTEM PUBLIC lib/portaudio) @@ -2645,9 +3180,11 @@ if(PORTMIDI) find_package(PortMidi REQUIRED) target_include_directories(mixxx-lib SYSTEM PUBLIC ${PortMidi_INCLUDE_DIRS}) target_link_libraries(mixxx-lib PRIVATE ${PortMidi_LIBRARIES}) - target_sources(mixxx-lib PRIVATE - src/controllers/midi/portmidicontroller.cpp - src/controllers/midi/portmidienumerator.cpp + target_sources( + mixxx-lib + PRIVATE + src/controllers/midi/portmidicontroller.cpp + src/controllers/midi/portmidienumerator.cpp ) endif() @@ -2657,23 +3194,27 @@ target_link_libraries(mixxx-lib PUBLIC mixxx-proto) # Rigtorp SPSC Queue # https://github.com/rigtorp/SPSCQueue -target_include_directories(mixxx-lib SYSTEM PUBLIC lib/rigtorp/SPSCQueue/include) +target_include_directories( + mixxx-lib + SYSTEM + PUBLIC lib/rigtorp/SPSCQueue/include +) # Qt set( QT_COMPONENTS - Concurrent - Core - Gui - Network - OpenGL - PrintSupport - Qml # for QJSEngine - Sql - Svg - Test - Widgets - Xml + Concurrent + Core + Gui + Network + OpenGL + PrintSupport + Qml # for QJSEngine + Sql + Svg + Test + Widgets + Xml ) set(QT_EXTRA_COMPONENTS "") if(QT6) @@ -2696,19 +3237,18 @@ if(QML) list(APPEND QT_EXTRA_COMPONENTS "QmlWorkerScript") list(APPEND QT_EXTRA_COMPONENTS "ShaderTools") endif() -find_package(Qt${QT_VERSION_MAJOR} - COMPONENTS - ${QT_COMPONENTS} - ${QT_EXTRA_COMPONENTS} +find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS ${QT_COMPONENTS} ${QT_EXTRA_COMPONENTS} REQUIRED ) # PUBLIC is required below to find included headers -foreach(COMPONENT ${QT_COMPONENTS}) - target_link_libraries(mixxx-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) +foreach(component ${QT_COMPONENTS}) + target_link_libraries(mixxx-lib PUBLIC Qt${QT_VERSION_MAJOR}::${component}) endforeach() if(QT_EXTRA_COMPONENTS) - foreach(COMPONENT ${QT_EXTRA_COMPONENTS}) - target_link_libraries(mixxx-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) + foreach(component ${QT_EXTRA_COMPONENTS}) + target_link_libraries(mixxx-lib PUBLIC Qt${QT_VERSION_MAJOR}::${component}) endforeach() endif() @@ -2717,12 +3257,18 @@ if(QML) set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) qt_add_library(mixxx-qml-lib STATIC) - foreach(COMPONENT ${QT_COMPONENTS}) - target_link_libraries(mixxx-qml-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) + foreach(component ${QT_COMPONENTS}) + target_link_libraries( + mixxx-qml-lib + PUBLIC Qt${QT_VERSION_MAJOR}::${component} + ) endforeach() if(QT_EXTRA_COMPONENTS) - foreach(COMPONENT ${QT_EXTRA_COMPONENTS}) - target_link_libraries(mixxx-qml-lib PUBLIC Qt${QT_VERSION_MAJOR}::${COMPONENT}) + foreach(component ${QT_EXTRA_COMPONENTS}) + target_link_libraries( + mixxx-qml-lib + PUBLIC Qt${QT_VERSION_MAJOR}::${component} + ) endforeach() endif() set_target_properties(mixxx-qml-lib PROPERTIES AUTOMOC ON) @@ -2742,13 +3288,18 @@ if(QML) # See: https://bugreports.qt.io/browse/QTBUG-87221 target_include_directories(mixxx-qml-lib PRIVATE src/control src/qml) target_include_directories(mixxx-qml-lib PUBLIC src/ ${CMAKE_BINARY_DIR}/src) - target_include_directories(mixxx-qml-lib SYSTEM PUBLIC lib/rigtorp/SPSCQueue/include lib/portaudio) + target_include_directories( + mixxx-qml-lib + SYSTEM + PUBLIC lib/rigtorp/SPSCQueue/include lib/portaudio + ) target_link_libraries(mixxx-qml-lib PUBLIC mixxx-proto) target_link_libraries(mixxx-qml-libplugin PUBLIC mixxx-proto) - target_precompile_headers(mixxx-qml-lib PUBLIC - ${MIXXX_COMMON_PRECOMPILED_HEADER} + target_precompile_headers( + mixxx-qml-lib + PUBLIC ${MIXXX_COMMON_PRECOMPILED_HEADER} ) target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-libplugin) @@ -2770,28 +3321,33 @@ if(QML) ) target_link_libraries(mixxx-qml-lib PRIVATE mixxx-qml-mixxxcontrolsplugin) - target_sources(mixxx-qml-lib PRIVATE - src/qml/asyncimageprovider.cpp - src/qml/qmlapplication.cpp - src/qml/qmlautoreload.cpp - src/qml/qmlbeatsmodel.cpp - src/qml/qmlcuesmodel.cpp - src/qml/qmlcontrolproxy.cpp - src/qml/qmlconfigproxy.cpp - src/qml/qmldlgpreferencesproxy.cpp - src/qml/qmleffectmanifestparametersmodel.cpp - src/qml/qmleffectsmanagerproxy.cpp - src/qml/qmleffectslotproxy.cpp - src/qml/qmllibraryproxy.cpp - src/qml/qmllibrarytracklistmodel.cpp - src/qml/qmlplayermanagerproxy.cpp - src/qml/qmlplayerproxy.cpp - src/qml/qmlvisibleeffectsmodel.cpp - src/qml/qmlchainpresetmodel.cpp - src/qml/qmlwaveformoverview.cpp - # The following sources need to be in this target to get QML_ELEMENT properly interpreted - src/control/controlmodel.cpp - src/control/controlsortfiltermodel.cpp + target_sources( + mixxx-qml-lib + PRIVATE + src/qml/asyncimageprovider.cpp + src/qml/qmlapplication.cpp + src/qml/qmlautoreload.cpp + src/qml/qmlbeatsmodel.cpp + src/qml/qmlcuesmodel.cpp + src/qml/qmlcontrolproxy.cpp + src/qml/qmlconfigproxy.cpp + src/qml/qmldlgpreferencesproxy.cpp + src/qml/qmleffectmanifestparametersmodel.cpp + src/qml/qmleffectsmanagerproxy.cpp + src/qml/qmleffectslotproxy.cpp + src/qml/qmllibraryproxy.cpp + src/qml/qmllibrarytracklistmodel.cpp + src/qml/qmlplayermanagerproxy.cpp + src/qml/qmlplayerproxy.cpp + src/qml/qmlvisibleeffectsmodel.cpp + src/qml/qmlchainpresetmodel.cpp + src/qml/qmlwaveformoverview.cpp + src/qml/qmlmixxxcontrollerscreen.cpp + # The following sources need to be in this target to get QML_ELEMENT properly interpreted + src/control/controlmodel.cpp + src/control/controlsortfiltermodel.cpp + # needed for qml/qmlautoreload.cpp + src/util/autofilereloader.cpp ) # qt_finalize_target takes care that the resources :/mixxx.org/imports/Mixxx/ @@ -2799,33 +3355,46 @@ if(QML) qt_finalize_target(mixxx) install( - DIRECTORY - "${CMAKE_CURRENT_SOURCE_DIR}/res/qml" - DESTINATION - "${MIXXX_INSTALL_DATADIR}" + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/res/qml" + DESTINATION "${MIXXX_INSTALL_DATADIR}" ) - endif() option(DEBUG_ASSERTIONS_FATAL "Fail if debug become true assertions" OFF) if(DEBUG_ASSERTIONS_FATAL) - target_compile_definitions(mixxx-lib PUBLIC MIXXX_DEBUG_ASSERTIONS_FATAL MIXXX_DEBUG_ASSERTIONS_ENABLED) + target_compile_definitions( + mixxx-lib + PUBLIC MIXXX_DEBUG_ASSERTIONS_FATAL MIXXX_DEBUG_ASSERTIONS_ENABLED + ) if(QML) - target_compile_definitions(mixxx-qml-lib PUBLIC MIXXX_DEBUG_ASSERTIONS_FATAL MIXXX_DEBUG_ASSERTIONS_ENABLED) + target_compile_definitions( + mixxx-qml-lib + PUBLIC MIXXX_DEBUG_ASSERTIONS_FATAL MIXXX_DEBUG_ASSERTIONS_ENABLED + ) endif() - if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - message(STATUS "DEBUG_ASSERT statements have been enabled because DEBUG_ASSERTIONS_FATAL is ON.") + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message( + STATUS + "DEBUG_ASSERT statements have been enabled because DEBUG_ASSERTIONS_FATAL is ON." + ) endif() endif() if(EMSCRIPTEN) - option(WASM_ASSERTIONS "Enable additional checks when targeting Emscripten/WebAssembly" OFF) + option( + WASM_ASSERTIONS + "Enable additional checks when targeting Emscripten/WebAssembly" + OFF + ) if(WASM_ASSERTIONS) target_link_options(mixxx-lib PUBLIC -sASSERTIONS) endif() endif() -target_compile_definitions(mixxx-lib PUBLIC QT_TABLET_SUPPORT QT_USE_QSTRINGBUILDER) +target_compile_definitions( + mixxx-lib + PUBLIC QT_TABLET_SUPPORT QT_USE_QSTRINGBUILDER +) is_static_library(Qt_IS_STATIC Qt${QT_VERSION_MAJOR}::Core) if(Qt_IS_STATIC) # NOTE(rryan): If you are adding a plugin here, you must also @@ -2835,45 +3404,65 @@ if(Qt_IS_STATIC) # to see exactly what's available as a standalone .lib vs linked # into Qt .libs by default. - target_link_libraries(mixxx-lib PRIVATE - # imageformats plugins - Qt${QT_VERSION_MAJOR}::QGifPlugin - Qt${QT_VERSION_MAJOR}::QICOPlugin - Qt${QT_VERSION_MAJOR}::QJpegPlugin - Qt${QT_VERSION_MAJOR}::QSvgPlugin - - # sqldrivers - Qt${QT_VERSION_MAJOR}::QSQLiteDriverPlugin + target_link_libraries( + mixxx-lib + PRIVATE + # imageformats plugins + Qt${QT_VERSION_MAJOR}::QGifPlugin + Qt${QT_VERSION_MAJOR}::QICOPlugin + Qt${QT_VERSION_MAJOR}::QJpegPlugin + Qt${QT_VERSION_MAJOR}::QSvgPlugin + # sqldrivers + Qt${QT_VERSION_MAJOR}::QSQLiteDriverPlugin + # network plugins + Qt${QT_VERSION_MAJOR}::QTlsBackendOpenSSLPlugin ) if(EMSCRIPTEN) - target_link_libraries(mixxx-lib PRIVATE - Qt${QT_VERSION_MAJOR}::QWasmIntegrationPlugin + target_link_libraries( + mixxx-lib + PRIVATE Qt${QT_VERSION_MAJOR}::QWasmIntegrationPlugin ) else() - target_link_libraries(mixxx-lib PRIVATE - # platform plugins - Qt${QT_VERSION_MAJOR}::QOffscreenIntegrationPlugin - Qt${QT_VERSION_MAJOR}::QMinimalIntegrationPlugin + target_link_libraries( + mixxx-lib + PRIVATE + # platform plugins + Qt${QT_VERSION_MAJOR}::QOffscreenIntegrationPlugin + Qt${QT_VERSION_MAJOR}::QMinimalIntegrationPlugin ) endif() if(WIN32) - target_link_libraries(mixxx-lib PRIVATE - Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin - Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin + target_link_libraries( + mixxx-lib + PRIVATE Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin ) + if(QT_VERSION VERSION_LESS 6.7) + target_link_libraries( + mixxx-lib + PRIVATE Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin + ) + else() + target_link_libraries( + mixxx-lib + PRIVATE Qt${QT_VERSION_MAJOR}::QModernWindowsStylePlugin + ) + endif() endif() if(APPLE) if(IOS) - target_link_libraries(mixxx-lib PRIVATE - Qt${QT_VERSION_MAJOR}::QIOSIntegrationPlugin + target_link_libraries( + mixxx-lib + PRIVATE Qt${QT_VERSION_MAJOR}::QIOSIntegrationPlugin ) else() - target_link_libraries(mixxx-lib PRIVATE - Qt${QT_VERSION_MAJOR}::QCocoaIntegrationPlugin - Qt${QT_VERSION_MAJOR}::QMacStylePlugin + target_link_libraries( + mixxx-lib + PRIVATE + Qt${QT_VERSION_MAJOR}::QCocoaIntegrationPlugin + Qt${QT_VERSION_MAJOR}::QMacStylePlugin ) endif() endif() @@ -2882,144 +3471,249 @@ else() # Qt6 does not automatically install plugins like in Qt 5 find_package(libjpeg-turbo CONFIG REQUIRED) - install(IMPORTED_RUNTIME_ARTIFACTS - # QJpegPlugin dependency - libjpeg-turbo::jpeg - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - # platform plugins - Qt${QT_VERSION_MAJOR}::QOffscreenIntegrationPlugin - Qt${QT_VERSION_MAJOR}::QMinimalIntegrationPlugin - DESTINATION "${MIXXX_INSTALL_DATADIR}/platforms" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QGifPlugin - Qt${QT_VERSION_MAJOR}::QICOPlugin - Qt${QT_VERSION_MAJOR}::QJpegPlugin - Qt${QT_VERSION_MAJOR}::QSvgPlugin - DESTINATION "${MIXXX_INSTALL_DATADIR}/imageformats" - COMPONENT applocal) + install( + IMPORTED_RUNTIME_ARTIFACTS + # QJpegPlugin dependency + libjpeg-turbo::jpeg + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QSQLiteDriverPlugin - DESTINATION "${MIXXX_INSTALL_DATADIR}/sqldrivers" - COMPONENT applocal) + install( + IMPORTED_RUNTIME_ARTIFACTS + # platform plugins + Qt${QT_VERSION_MAJOR}::QOffscreenIntegrationPlugin + Qt${QT_VERSION_MAJOR}::QMinimalIntegrationPlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/platforms" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QGifPlugin + Qt${QT_VERSION_MAJOR}::QICOPlugin + Qt${QT_VERSION_MAJOR}::QJpegPlugin + Qt${QT_VERSION_MAJOR}::QSvgPlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/imageformats" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QSQLiteDriverPlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/sqldrivers" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QTlsBackendOpenSSLPlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/tls" + COMPONENT + applocal + ) if(QML) - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::LabsQmlModels - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QuickControls2 - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QuickControls2Impl - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QuickLayouts - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QuickShapesPrivate - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QuickTemplates2 - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::QmlWorkerScript - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) - - install(IMPORTED_RUNTIME_ARTIFACTS - Qt${QT_VERSION_MAJOR}::ShaderTools - DESTINATION "${MIXXX_INSTALL_DATADIR}" - COMPONENT applocal) + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::LabsQmlModels + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QuickControls2 + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QuickControls2Impl + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QuickLayouts + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QuickShapesPrivate + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QuickTemplates2 + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QmlWorkerScript + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) + + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::ShaderTools + DESTINATION + "${MIXXX_INSTALL_DATADIR}" + COMPONENT + applocal + ) #install qml6-module-qt5compat-graphicaleffects install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/Qt5Compat/GraphicalEffects" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/Qt5Compat/GraphicalEffects" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/Qt5Compat" - COMPONENT applocal) + COMPONENT applocal + ) # install qml6-module-qtqml-workerscript install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQml/WorkerScript" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQml/WorkerScript" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/QtQml" - COMPONENT applocal) + COMPONENT applocal + ) # install qml6-module-qtquick-controls install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Controls" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Controls" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/QtQuick" - COMPONENT applocal) + COMPONENT applocal + ) # install qml6-module-qtquick-layouts install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Layouts" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Layouts" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/QtQuick" - COMPONENT applocal) + COMPONENT applocal + ) # install qml6-module-qtquick-nativestyle install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/NativeStyle" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/NativeStyle" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/QtQuick" - COMPONENT applocal) + COMPONENT applocal + ) # install qml6-module-qtquick-shapes install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Shapes" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Shapes" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/QtQuick" - COMPONENT applocal) + COMPONENT applocal + ) # install qml6-module-qtquick-templates install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Templates" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Templates" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/QtQuick" - COMPONENT applocal) + COMPONENT applocal + ) # qml6-module-qtquick-window install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Window" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/QtQuick/Window" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/QtQuick" - COMPONENT applocal) + COMPONENT applocal + ) # install qml6-module-qt-labs-qmlmodels install( - DIRECTORY "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/Qt/labs/qmlmodels" + DIRECTORY + "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/Qt6/qml/Qt/labs/qmlmodels" DESTINATION "${MIXXX_INSTALL_DATADIR}/Qt6/qml/Qt/labs" - COMPONENT applocal) - + COMPONENT applocal + ) endif() if(WIN32) - install(IMPORTED_RUNTIME_ARTIFACTS Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin - DESTINATION "${MIXXX_INSTALL_DATADIR}/platforms" - COMPONENT applocal) - install(IMPORTED_RUNTIME_ARTIFACTS Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin - DESTINATION "${MIXXX_INSTALL_DATADIR}/styles" - COMPONENT applocal) + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QWindowsIntegrationPlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/platforms" + COMPONENT + applocal + ) + if(QT_VERSION VERSION_LESS 6.7) + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/styles" + COMPONENT + applocal + ) + else() + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QModernWindowsStylePlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/styles" + COMPONENT + applocal + ) + endif() endif() if(APPLE) - install(IMPORTED_RUNTIME_ARTIFACTS Qt${QT_VERSION_MAJOR}::QCocoaIntegrationPlugin - DESTINATION "${MIXXX_INSTALL_DATADIR}/platforms" - COMPONENT applocal) - install(IMPORTED_RUNTIME_ARTIFACTS Qt${QT_VERSION_MAJOR}::QMacStylePlugin - DESTINATION "${MIXXX_INSTALL_DATADIR}/styles" - COMPONENT applocal) + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QCocoaIntegrationPlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/platforms" + COMPONENT + applocal + ) + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QMacStylePlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/styles" + COMPONENT + applocal + ) endif() set(APPLOCAL_COMPONENT_DEFINED true) @@ -3028,7 +3722,9 @@ endif() if(APPLE) if(Qt_IS_STATIC OR QT6) - target_link_libraries(mixxx-lib PRIVATE + target_link_libraries( + mixxx-lib + PRIVATE "-weak_framework Accelerate" "-weak_framework AudioToolbox" "-weak_framework AVFoundation" @@ -3043,20 +3739,16 @@ if(APPLE) "-weak_framework VideoToolbox" ) if(IOS) - target_link_libraries(mixxx-lib PRIVATE - "-weak_framework UIKit" - ) + target_link_libraries(mixxx-lib PRIVATE "-weak_framework UIKit") elseif() - target_link_libraries(mixxx-lib PRIVATE - "-weak_framework AppKit" - "-weak_framework AudioUnit" + target_link_libraries( + mixxx-lib + PRIVATE "-weak_framework AppKit" "-weak_framework AudioUnit" ) endif() else() # Used for battery measurements and controlling the screensaver on macOS. - target_link_libraries(mixxx-lib PRIVATE - "-weak_framework IOKit" - ) + target_link_libraries(mixxx-lib PRIVATE "-weak_framework IOKit") endif() elseif(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) if(QT6) @@ -3071,51 +3763,64 @@ elseif(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) target_link_libraries(mixxx-lib PRIVATE "${X11_LIBRARIES}") endif() find_package(Qt${QT_VERSION_MAJOR} COMPONENTS DBus REQUIRED) - target_link_libraries(mixxx-lib PUBLIC - Qt${QT_VERSION_MAJOR}::DBus - ) + target_link_libraries(mixxx-lib PUBLIC Qt${QT_VERSION_MAJOR}::DBus) elseif(WIN32) if(Qt_IS_STATIC) - target_link_libraries(mixxx-lib PRIVATE - # Pulled from qt-4.8.2-source\mkspecs\win32-msvc2010\qmake.conf - # QtCore - kernel32 - user32 # QtGui, QtOpenGL, libHSS1394 - shell32 - uuid - ole32 # QtGui, - advapi32 # QtGui, portaudio, portmidi - ws2_32 # QtGui, QtNetwork, libshout - # QtGui - gdi32 # QtOpenGL, libshout - comdlg32 - oleaut32 - imm32 - winmm - winspool - # QtOpenGL - glu32 - opengl32 - - # QtNetwork openssl-linked - crypt32 - - dwmapi # qtwindows - iphlpapi # qt5network - mpr # qt5core - netapi32 # qt5core - userenv # qt5core - uxtheme # ? - version # ? - wtsapi32 # ? - ) - - find_library(QTFONTDATABASESUPPORT_LIBRARY Qt${QT_VERSION_MAJOR}FontDatabaseSupport) + target_link_libraries( + mixxx-lib + PRIVATE + # Pulled from qt-4.8.2-source\mkspecs\win32-msvc2010\qmake.conf + # QtCore + kernel32 + user32 # QtGui, QtOpenGL, libHSS1394 + shell32 + uuid + ole32 # QtGui, + advapi32 # QtGui, portaudio, portmidi + ws2_32 # QtGui, QtNetwork, libshout + # QtGui + gdi32 # QtOpenGL, libshout + comdlg32 + oleaut32 + imm32 + winmm + winspool + # QtOpenGL + glu32 + opengl32 + # QtNetwork openssl-linked + crypt32 + dwmapi # qtwindows + iphlpapi # qt5network + mpr # qt5core + netapi32 # qt5core + userenv # qt5core + uxtheme # ? + version # ? + wtsapi32 # ? + ) + + find_library( + QTFONTDATABASESUPPORT_LIBRARY + Qt${QT_VERSION_MAJOR}FontDatabaseSupport + ) target_link_libraries(mixxx-lib PRIVATE "${QTFONTDATABASESUPPORT_LIBRARY}") - find_library(QTWINDOWSUIAUTOMATIONSUPPORT_LIBRARY Qt${QT_VERSION_MAJOR}WindowsUIAutomationSupport) - target_link_libraries(mixxx-lib PRIVATE "${QTWINDOWSUIAUTOMATIONSUPPORT_LIBRARY}") - find_library(QTEVENTDISPATCHERSUPPORT_LIBRARY Qt${QT_VERSION_MAJOR}EventDispatcherSupport) - target_link_libraries(mixxx-lib PRIVATE "${QTEVENTDISPATCHERSUPPORT_LIBRARY}") + find_library( + QTWINDOWSUIAUTOMATIONSUPPORT_LIBRARY + Qt${QT_VERSION_MAJOR}WindowsUIAutomationSupport + ) + target_link_libraries( + mixxx-lib + PRIVATE "${QTWINDOWSUIAUTOMATIONSUPPORT_LIBRARY}" + ) + find_library( + QTEVENTDISPATCHERSUPPORT_LIBRARY + Qt${QT_VERSION_MAJOR}EventDispatcherSupport + ) + target_link_libraries( + mixxx-lib + PRIVATE "${QTEVENTDISPATCHERSUPPORT_LIBRARY}" + ) find_library(QTTHEMESUPPORT_LIBRARY Qt${QT_VERSION_MAJOR}ThemeSupport) target_link_libraries(mixxx-lib PRIVATE "${QTTHEMESUPPORT_LIBRARY}") @@ -3129,9 +3834,11 @@ elseif(WIN32) target_link_libraries(mixxx-lib PRIVATE "${QTPCRE2_LIBRARY}") else() #libshout is always built statically - target_link_libraries(mixxx-lib PRIVATE - ws2_32 # libshout - gdi32 # libshout + target_link_libraries( + mixxx-lib + PRIVATE + ws2_32 # libshout + gdi32 # libshout ) endif() endif() @@ -3140,9 +3847,23 @@ if(APPLE OR WIN32) # qt_de.qm is just one arbitrary file in the directory that needs to be located; # there is no particular reason to look for this file versus any other one in the directory. if(QT6) - find_file(QT_TRANSLATION_FILE qt_de.qm PATHS "${Qt6_DIR}/../../translations/Qt6" REQUIRED NO_DEFAULT_PATH) + find_file( + QT_TRANSLATION_FILE + qt_de.qm + PATHS "${Qt6_DIR}/../../translations/Qt6" + REQUIRED + NO_DEFAULT_PATH + ) else() - find_file(QT_TRANSLATION_FILE qt_de.qm PATHS "${Qt5_DIR}/../../../translations" "${Qt5_DIR}/../../qt5/translations" REQUIRED NO_DEFAULT_PATH) + find_file( + QT_TRANSLATION_FILE + qt_de.qm + PATHS + "${Qt5_DIR}/../../../translations" + "${Qt5_DIR}/../../qt5/translations" + REQUIRED + NO_DEFAULT_PATH + ) endif() get_filename_component(QT_TRANSLATIONS ${QT_TRANSLATION_FILE} DIRECTORY) install( @@ -3150,13 +3871,31 @@ if(APPLE OR WIN32) DESTINATION "${MIXXX_INSTALL_DATADIR}/translations" # QT 5 translations have been separated into several files, and most of the qt_xx.qm files # contain just shortcuts to load the qtbase, qtmultimedia etc files. - FILES_MATCHING REGEX - "qt_.+\.qm|qtbase_.*\.qm|qtmultimedia_.*\.qm|qtscript_.*\.qm|qtxmlpatterns_.*\.qm" + FILES_MATCHING + REGEX + "qt_.+\.qm|qtbase_.*\.qm|qtmultimedia_.*\.qm|qtscript_.*\.qm|qtxmlpatterns_.*\.qm" ) endif() +add_library( + mixxx-gitinfostore + STATIC + EXCLUDE_FROM_ALL + src/util/gitinfostore.cpp +) +# QtCore for QString +target_link_libraries(mixxx-gitinfostore PUBLIC Qt${QT_VERSION_MAJOR}::Core) +target_include_directories( + mixxx-gitinfostore + PUBLIC src ${CMAKE_BINARY_DIR}/src +) +add_dependencies(mixxx-gitinfostore mixxx-gitinfo) + # Queen Mary DSP -add_library(QueenMaryDsp STATIC EXCLUDE_FROM_ALL +add_library( + QueenMaryDsp + STATIC + EXCLUDE_FROM_ALL # lib/qm-dsp/base/KaiserWindow.cpp lib/qm-dsp/base/Pitch.cpp # lib/qm-dsp/base/SincWindow.cpp @@ -3192,7 +3931,8 @@ add_library(QueenMaryDsp STATIC EXCLUDE_FROM_ALL # lib/qm-dsp/hmm/hmm.c lib/qm-dsp/maths/Correlation.cpp # lib/qm-dsp/maths/CosineDistance.cpp - lib/qm-dsp/maths/KLDivergence.cpp lib/qm-dsp/maths/MathUtilities.cpp + lib/qm-dsp/maths/KLDivergence.cpp + lib/qm-dsp/maths/MathUtilities.cpp # lib/qm-dsp/maths/pca/pca.c # lib/qm-dsp/thread/Thread.cpp ) @@ -3207,13 +3947,15 @@ elseif(MSVC) # may include cmath first. target_compile_definitions(QueenMaryDsp PRIVATE _USE_MATH_DEFINES) endif() -target_include_directories(QueenMaryDsp SYSTEM PUBLIC lib/qm-dsp lib/qm-dsp/include) +target_include_directories( + QueenMaryDsp + SYSTEM + PUBLIC lib/qm-dsp lib/qm-dsp/include +) target_link_libraries(mixxx-lib PRIVATE QueenMaryDsp) # ReplayGain -add_library(ReplayGain STATIC EXCLUDE_FROM_ALL - lib/replaygain/replaygain.cpp -) +add_library(ReplayGain STATIC EXCLUDE_FROM_ALL lib/replaygain/replaygain.cpp) target_include_directories(mixxx-lib SYSTEM PRIVATE lib/replaygain) target_link_libraries(mixxx-lib PRIVATE ReplayGain) @@ -3233,12 +3975,14 @@ if(RUBBERBAND) find_package(rubberband REQUIRED) target_link_libraries(mixxx-lib PRIVATE rubberband::rubberband) target_compile_definitions(mixxx-lib PUBLIC __RUBBERBAND__) - target_sources(mixxx-lib PRIVATE - src/effects/backends/builtin/pitchshifteffect.cpp - src/engine/bufferscalers/enginebufferscalerubberband.cpp - src/engine/bufferscalers/rubberbandwrapper.cpp - src/engine/bufferscalers/rubberbandtask.cpp - src/engine/bufferscalers/rubberbandworkerpool.cpp + target_sources( + mixxx-lib + PRIVATE + src/effects/backends/builtin/pitchshifteffect.cpp + src/engine/bufferscalers/enginebufferscalerubberband.cpp + src/engine/bufferscalers/rubberbandwrapper.cpp + src/engine/bufferscalers/rubberbandtask.cpp + src/engine/bufferscalers/rubberbandworkerpool.cpp ) endif() @@ -3247,7 +3991,10 @@ find_package(SndFile REQUIRED) target_link_libraries(mixxx-lib PRIVATE SndFile::sndfile) target_compile_definitions(mixxx-lib PUBLIC __SNDFILE__) if(SndFile_SUPPORTS_SET_COMPRESSION_LEVEL) - target_compile_definitions(mixxx-lib PUBLIC SFC_SUPPORTS_SET_COMPRESSION_LEVEL) + target_compile_definitions( + mixxx-lib + PUBLIC SFC_SUPPORTS_SET_COMPRESSION_LEVEL + ) endif() # SoundTouch @@ -3256,11 +4003,14 @@ target_link_libraries(mixxx-lib PRIVATE SoundTouch::SoundTouch) # TagLib find_package(TagLib 1.11 REQUIRED) -if (NOT TagLib_VERSION VERSION_LESS 2.0.0) - 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.") +if(NOT TagLib_VERSION VERSION_LESS 2.0.0) + 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() target_link_libraries(mixxx-lib PUBLIC TagLib::TagLib) -if (QML) +if(QML) target_link_libraries(mixxx-qml-lib PUBLIC TagLib::TagLib) endif() @@ -3277,7 +4027,13 @@ target_link_libraries(mixxx-lib PRIVATE Threads::Threads) # # The battery meter is only available on Linux, macOS and Windows, therefore # this option is forcibly set to OFF on all other platforms. -cmake_dependent_option(BATTERY "Battery meter support" ON "WIN32 OR UNIX" OFF) +cmake_dependent_option( + BATTERY + "Battery meter support" + ON + "WIN32 OR UNIX" + OFF +) if(BATTERY) if(WIN32) target_sources(mixxx-lib PRIVATE src/util/battery/batterywindows.cpp) @@ -3289,20 +4045,28 @@ if(BATTERY) endif() elseif(UNIX) if(EMSCRIPTEN) - message(FATAL_ERROR "Battery support is not implemented for Emscripten (WebAssembly)") + message( + FATAL_ERROR + "Battery support is not implemented for Emscripten (WebAssembly)" + ) endif() find_package(Upower REQUIRED) find_package(GLIB COMPONENTS gobject REQUIRED) target_include_directories(mixxx-lib SYSTEM PUBLIC ${GLIB_INCLUDE_DIRS}) - target_link_libraries(mixxx-lib PRIVATE Upower::Upower ${GLIB_LIBRARIES} ${GLIB_GOBJECT_LIBRARIES}) + target_link_libraries( + mixxx-lib + PRIVATE Upower::Upower ${GLIB_LIBRARIES} ${GLIB_GOBJECT_LIBRARIES} + ) target_sources(mixxx-lib PRIVATE src/util/battery/batterylinux.cpp) else() - message(FATAL_ERROR "Battery support is not implemented for the target platform.") + message( + FATAL_ERROR + "Battery support is not implemented for the target platform." + ) endif() target_compile_definitions(mixxx-lib PUBLIC __BATTERY__) endif() - # Build Time option(BUILDTIME "Use __DATE__ and __TIME__" ON) if(NOT BUILDTIME) @@ -3319,7 +4083,10 @@ endif() option(CLANG_COLORDIAG "Clang color diagnostics" OFF) if(CLANG_COLORDIAG) if(NOT LLVM_CLANG) - message(FATAL_ERROR "Color Diagnostics are only available when using Clang.") + message( + FATAL_ERROR + "Color Diagnostics are only available when using Clang." + ) endif() target_compile_options(mixxx-lib PUBLIC -fcolor-diagnostics) endif() @@ -3351,12 +4118,20 @@ endif() # # The CoreAudio API is only available on macOS, therefore this option is # forcibly set to OFF on all other platforms. -cmake_dependent_option(COREAUDIO "CoreAudio MP3/AAC Decoder" ON "APPLE" OFF) +cmake_dependent_option( + COREAUDIO + "CoreAudio MP3/AAC Decoder" + ON + "APPLE" + OFF +) if(COREAUDIO) - target_sources(mixxx-lib PRIVATE - src/sources/soundsourcecoreaudio.cpp - src/sources/v1/legacyaudiosourceadapter.cpp - lib/apple/CAStreamBasicDescription.cpp + target_sources( + mixxx-lib + PRIVATE + src/sources/soundsourcecoreaudio.cpp + src/sources/v1/legacyaudiosourceadapter.cpp + lib/apple/CAStreamBasicDescription.cpp ) set_property( SOURCE lib/apple/CAStreamBasicDescription.cpp @@ -3367,7 +4142,6 @@ if(COREAUDIO) target_include_directories(mixxx-lib SYSTEM PUBLIC lib/apple) endif() - # FAAD AAC audio file decoder plugin find_package(MP4) find_package(MP4v2) @@ -3376,11 +4150,14 @@ find_package(MP4v2) default_option(FAAD "FAAD AAC audio file decoder support" "UNIX;NOT APPLE;MP4_FOUND OR MP4v2_FOUND") if(FAAD) if(NOT MP4_FOUND AND NOT MP4v2_FOUND) - message(FATAL_ERROR "FAAD AAC audio support requires libmp4 or libmp4v2 with development headers.") + message( + FATAL_ERROR + "FAAD AAC audio support requires libmp4 or libmp4v2 with development headers." + ) endif() - target_sources(mixxx-lib PRIVATE - src/sources/soundsourcem4a.cpp - src/sources/libfaadloader.cpp + target_sources( + mixxx-lib + PRIVATE src/sources/soundsourcem4a.cpp src/sources/libfaadloader.cpp ) target_compile_definitions(mixxx-lib PUBLIC __FAAD__) if(MP4v2_FOUND) @@ -3397,8 +4174,15 @@ if(APPLE AND MACOS_BUNDLE) find_library(FDK_AAC_LIBRARY fdk-aac) if(FDK_AAC_LIBRARY) message(STATUS "Found fdk-aac: ${FDK_AAC_LIBRARY}") - file(COPY ${FDK_AAC_LIBRARY} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/lib/fdk-aac-install" FOLLOW_SYMLINK_CHAIN) - install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib/fdk-aac-install/" DESTINATION "${MIXXX_INSTALL_PREFIX}/Contents/Frameworks") + file( + COPY ${FDK_AAC_LIBRARY} + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/lib/fdk-aac-install" + FOLLOW_SYMLINK_CHAIN + ) + install( + DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib/fdk-aac-install/" + DESTINATION "${MIXXX_INSTALL_PREFIX}/Contents/Frameworks" + ) else() message(STATUS "Could NOT find libfdk-aac.dylib") endif() @@ -3426,26 +4210,52 @@ if(FFMPEG) # Minimum library versions according to # Windows: Version numbers are not available!? # macOS: Untested - if(FFMPEG_libavcodec_VERSION AND FFMPEG_libavcodec_VERSION VERSION_LESS 58.35.100) - message(FATAL_ERROR "FFmpeg support requires at least version 58.35.100 of libavcodec (found: ${FFMPEG_libavcodec_VERSION}).") + if( + FFMPEG_libavcodec_VERSION + AND FFMPEG_libavcodec_VERSION VERSION_LESS 58.35.100 + ) + message( + FATAL_ERROR + "FFmpeg support requires at least version 58.35.100 of libavcodec (found: ${FFMPEG_libavcodec_VERSION})." + ) endif() - if(FFMPEG_libavformat_VERSION AND FFMPEG_libavformat_VERSION VERSION_LESS 58.20.100) - message(FATAL_ERROR "FFmpeg support requires at least version 58.20.100 of libavformat (found: ${FFMPEG_libavformat_VERSION}).") + if( + FFMPEG_libavformat_VERSION + AND FFMPEG_libavformat_VERSION VERSION_LESS 58.20.100 + ) + message( + FATAL_ERROR + "FFmpeg support requires at least version 58.20.100 of libavformat (found: ${FFMPEG_libavformat_VERSION})." + ) endif() - if(FFMPEG_libavutil_VERSION AND FFMPEG_libavutil_VERSION VERSION_LESS 56.22.100) - message(FATAL_ERROR "FFmpeg support requires at least version 56.22.100 of libavutil (found: ${FFMPEG_libavutil_VERSION}).") + if( + FFMPEG_libavutil_VERSION + AND FFMPEG_libavutil_VERSION VERSION_LESS 56.22.100 + ) + message( + FATAL_ERROR + "FFmpeg support requires at least version 56.22.100 of libavutil (found: ${FFMPEG_libavutil_VERSION})." + ) endif() - if(FFMPEG_libswresample_VERSION AND FFMPEG_libswresample_VERSION VERSION_LESS 3.3.100) - message(FATAL_ERROR "FFmpeg support requires at least version 3.3.100 of libswresample (found: ${FFMPEG_libswresample_VERSION}).") + if( + FFMPEG_libswresample_VERSION + AND FFMPEG_libswresample_VERSION VERSION_LESS 3.3.100 + ) + message( + FATAL_ERROR + "FFmpeg support requires at least version 3.3.100 of libswresample (found: ${FFMPEG_libswresample_VERSION})." + ) endif() target_sources(mixxx-lib PRIVATE src/sources/soundsourceffmpeg.cpp) - target_compile_definitions(mixxx-lib PUBLIC - __FFMPEG__ - # Needed to build new FFmpeg - __STDC_CONSTANT_MACROS - __STDC_LIMIT_MACROS - __STDC_FORMAT_MACROS + target_compile_definitions( + mixxx-lib + PUBLIC + __FFMPEG__ + # Needed to build new FFmpeg + __STDC_CONSTANT_MACROS + __STDC_LIMIT_MACROS + __STDC_FORMAT_MACROS ) target_link_libraries(mixxx-lib PRIVATE "${FFMPEG_LIBRARIES}") target_include_directories(mixxx-lib PUBLIC "${FFMPEG_INCLUDE_DIRS}") @@ -3453,67 +4263,47 @@ endif() # STEM file support default_option(STEM "STEM file support" "FFMPEG_FOUND;FFMPEG") -if (STEM) +if(STEM) if(NOT FFMPEG) message(FATAL_ERROR "STEM requires that also FFMPEG is enabled") endif() target_compile_definitions(mixxx-lib PUBLIC __STEM__) - target_compile_definitions(mixxx-test PUBLIC __STEM__) - target_sources(mixxx-test PUBLIC - src/test/stemtest.cpp - src/test/steminfotest.cpp - src/test/stemcontrolobjecttest.cpp - ) + if(BUILD_TESTING) + target_compile_definitions(mixxx-test PUBLIC __STEM__) + target_sources( + mixxx-test + PUBLIC + src/test/stemtest.cpp + src/test/steminfotest.cpp + src/test/stemcontrolobjecttest.cpp + ) + endif() list(APPEND MIXXX_LIB_PRECOMPILED_HEADER src/track/steminfo.h) - target_sources(mixxx-lib PRIVATE - src/sources/soundsourcestem.cpp - src/track/steminfoimporter.cpp - src/track/steminfo.cpp - src/widget/wstemlabel.cpp + target_sources( + mixxx-lib + PRIVATE + src/sources/soundsourcestem.cpp + src/track/steminfoimporter.cpp + src/track/steminfo.cpp + src/widget/wtrackstemmenu.cpp + src/widget/wstemlabel.cpp ) if(QOPENGL) - target_sources(mixxx-lib PRIVATE - src/waveform/renderers/allshader/waveformrendererstem.cpp + target_sources( + mixxx-lib + PRIVATE src/waveform/renderers/allshader/waveformrendererstem.cpp ) endif() if(QML) target_compile_definitions(mixxx-qml-lib PUBLIC __STEM__) - target_sources(mixxx-qml-lib PRIVATE - src/qml/qmlstemsmodel.cpp - ) + target_sources(mixxx-qml-lib PRIVATE src/qml/qmlstemsmodel.cpp) endif() endif() -# Test Suite -include(CTest) -include(GoogleTest) -enable_testing() -gtest_add_tests( - TARGET mixxx-test - EXTRA_ARGS --logLevel info - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - TEST_LIST testsuite -) -if (NOT WIN32) - # Default to offscreen rendering during tests. - # This is required if the build system like Fedora koji/mock does not - # allow to pass environment variables into the ctest macro expansion. - set_tests_properties(${testsuite} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen") -endif() - -# Benchmarking -add_custom_target(mixxx-benchmark - COMMAND $ --benchmark - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Mixxx Benchmarks" - VERBATIM -) -add_dependencies(mixxx-benchmark mixxx-test) - # Google PerfTools option(GPERFTOOLS "Google PerfTools libtcmalloc linkage" OFF) option(GPERFTOOLSPROFILER "Google PerfTools libprofiler linkage" OFF) -if(GPERFTOOLS OR GPERFTOOLSPROFILER) +if((BUILD_BENCH) AND (GPERFTOOLS OR GPERFTOOLSPROFILER)) find_package(GPerfTools REQUIRED) if(GPERFTOOLS) target_link_libraries(mixxx-lib PRIVATE GPerfTools::tcmalloc) @@ -3532,15 +4322,26 @@ if(WIN32 OR APPLE) else() set(HSS1394 OFF) endif() -cmake_dependent_option(HSS1394 "HSS1394 MIDI device support" "${HSS1394_FOUND}" "WIN32 OR APPLE" OFF) +cmake_dependent_option( + HSS1394 + "HSS1394 MIDI device support" + "${HSS1394_FOUND}" + "WIN32 OR APPLE" + OFF +) if(HSS1394) - target_sources(mixxx-lib PRIVATE - src/controllers/midi/hss1394controller.cpp - src/controllers/midi/hss1394enumerator.cpp + target_sources( + mixxx-lib + PRIVATE + src/controllers/midi/hss1394controller.cpp + src/controllers/midi/hss1394enumerator.cpp ) target_compile_definitions(mixxx-lib PUBLIC __HSS1394__) if(NOT HSS1394_FOUND) - message(FATAL_ERROR "HSS1394 MIDI device support requires the libhss1394 and its development headers.") + message( + FATAL_ERROR + "HSS1394 MIDI device support requires the libhss1394 and its development headers." + ) endif() target_link_libraries(mixxx-lib PRIVATE HSS1394::HSS1394) endif() @@ -3550,55 +4351,83 @@ find_package(lilv) default_option(LILV "Lilv (LV2) support" "lilv_FOUND") if(LILV) if(NOT lilv_FOUND) - message(FATAL_ERROR "Lilv (LV2) support requires the liblilv-0 and LV2 libraries and development headers.") + message( + FATAL_ERROR + "Lilv (LV2) support requires the liblilv-0 and LV2 libraries and development headers." + ) endif() - target_sources(mixxx-lib PRIVATE - src/effects/backends/lv2/lv2backend.cpp - src/effects/backends/lv2/lv2effectprocessor.cpp - src/effects/backends/lv2/lv2manifest.cpp + target_sources( + mixxx-lib + PRIVATE + src/effects/backends/lv2/lv2backend.cpp + src/effects/backends/lv2/lv2effectprocessor.cpp + src/effects/backends/lv2/lv2manifest.cpp ) target_compile_definitions(mixxx-lib PUBLIC __LILV__) target_link_libraries(mixxx-lib PRIVATE lilv::lilv) - target_link_libraries(mixxx-test PRIVATE lilv::lilv) + if(BUILD_TESTING) + target_link_libraries(mixxx-test PRIVATE lilv::lilv) + endif() endif() # Live Broadcasting (Shoutcast) -cmake_dependent_option(BROADCAST "Live Broadcasting (Shoutcast) support" ON "NOT IOS" OFF) +cmake_dependent_option( + BROADCAST + "Live Broadcasting (Shoutcast) support" + ON + "NOT IOS" + OFF +) if(BROADCAST) find_package(Shoutidjc) # Check if system lib is at least 2.4.6 and not suffering bugs # https://github.com/mixxxdj/mixxx/issues/9681 # https://github.com/mixxxdj/mixxx/issues/10305 if(Shoutidjc_FOUND AND Shoutidjc_VERSION VERSION_LESS 2.4.4) - message(STATUS "Installed libshout-idjc version: ${Shoutidjc_VERSION} is suffering from issue #9681") + message( + STATUS + "Installed libshout-idjc version: ${Shoutidjc_VERSION} is suffering from issue #9681" + ) elseif(Shoutidjc_FOUND AND Shoutidjc_VERSION VERSION_LESS 2.4.6) - message(STATUS "Installed libshout version: ${Shout_VERSION} is suffering from issue #10305") + message( + STATUS + "Installed libshout version: ${Shout_VERSION} is suffering from issue #10305" + ) endif() if(NOT Shoutidjc_FOUND OR Shoutidjc_VERSION VERSION_LESS 2.4.6) # Fall back to internal library in the lib tree message(STATUS "Using internal libshout-idjc") add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/lib/libshout-idjc") - target_include_directories(mixxx-lib SYSTEM PUBLIC lib/libshout-idjc/include) + target_include_directories( + mixxx-lib + SYSTEM + PUBLIC lib/libshout-idjc/include + ) if(WIN32) - target_compile_definitions(shout_mixxx PRIVATE __WINDOWS__ _CRT_NONSTDC_NO_WARNINGS) + target_compile_definitions( + shout_mixxx + PRIVATE __WINDOWS__ _CRT_NONSTDC_NO_WARNINGS + ) endif() target_link_libraries(mixxx-lib PRIVATE shout_mixxx) else() target_link_libraries(mixxx-lib PRIVATE Shoutidjc::Shoutidjc) endif() - target_sources(mixxx-lib PRIVATE - src/preferences/dialog/dlgprefbroadcastdlg.ui - src/preferences/dialog/dlgprefbroadcast.cpp - src/broadcast/broadcastmanager.cpp - src/engine/sidechain/shoutconnection.cpp - src/preferences/broadcastprofile.cpp - src/preferences/broadcastsettings.cpp - src/preferences/broadcastsettings_legacy.cpp - src/preferences/broadcastsettingsmodel.cpp - src/encoder/encoderbroadcastsettings.cpp + target_sources( + mixxx-lib + PRIVATE + src/preferences/dialog/dlgprefbroadcastdlg.ui + src/preferences/dialog/dlgprefbroadcast.cpp + src/broadcast/broadcastmanager.cpp + src/engine/sidechain/shoutconnection.cpp + src/preferences/broadcastprofile.cpp + src/preferences/broadcastsettings.cpp + src/preferences/broadcastsettings_legacy.cpp + src/preferences/broadcastsettingsmodel.cpp + src/encoder/encoderbroadcastsettings.cpp ) target_compile_definitions(mixxx-lib PUBLIC __BROADCAST__) - if (QML) + if(QML) target_compile_definitions(mixxx-qml-lib PUBLIC __BROADCAST__) endif() endif() @@ -3609,16 +4438,23 @@ find_package(Opus) default_option(OPUS "Opus (RFC 6716) support" "OpusFile_FOUND") if(OPUS) if(NOT OpusFile_FOUND OR NOT Opus_FOUND) - message(FATAL_ERROR "Opus support requires libopus and libopusfile with development headers.") + message( + FATAL_ERROR + "Opus support requires libopus and libopusfile with development headers." + ) endif() - target_sources(mixxx-lib PRIVATE - src/sources/soundsourceopus.cpp - src/encoder/encoderopus.cpp - src/encoder/encoderopussettings.cpp + target_sources( + mixxx-lib + PRIVATE + src/sources/soundsourceopus.cpp + src/encoder/encoderopus.cpp + src/encoder/encoderopussettings.cpp ) target_compile_definitions(mixxx-lib PUBLIC __OPUS__) target_link_libraries(mixxx-lib PRIVATE OpusFile::OpusFile Opus::Opus) - target_link_libraries(mixxx-test PRIVATE OpusFile::OpusFile Opus::Opus) + if(BUILD_TESTING) + target_link_libraries(mixxx-test PRIVATE OpusFile::OpusFile Opus::Opus) + endif() endif() # MAD MP3 Decoder @@ -3627,10 +4463,16 @@ find_package(ID3Tag) default_option(MAD "MAD MP3 Decoder" "MAD_FOUND;ID3Tag_FOUND") if(MAD) if(NOT MAD_FOUND) - message(FATAL_ERROR "MAD support requires libmad and its development headers.") + message( + FATAL_ERROR + "MAD support requires libmad and its development headers." + ) endif() if(NOT ID3Tag_FOUND) - message(FATAL_ERROR "ID3Tag support requires libid3tag and its development headers.") + message( + FATAL_ERROR + "ID3Tag support requires libid3tag and its development headers." + ) endif() target_sources(mixxx-lib PRIVATE src/sources/soundsourcemp3.cpp) target_compile_definitions(mixxx-lib PUBLIC __MAD__) @@ -3641,19 +4483,25 @@ endif() # # The Media Foundtation API is only available on Windows, therefore this option # is forcibly set to OFF on all other platforms. -cmake_dependent_option(MEDIAFOUNDATION "Media Foundation AAC decoder plugin" ON "WIN32" OFF) +cmake_dependent_option( + MEDIAFOUNDATION + "Media Foundation AAC decoder plugin" + ON + "WIN32" + OFF +) if(MEDIAFOUNDATION) find_package(MediaFoundation REQUIRED) - target_sources(mixxx-lib PRIVATE - src/sources/soundsourcemediafoundation.cpp - ) + target_sources(mixxx-lib PRIVATE src/sources/soundsourcemediafoundation.cpp) target_compile_definitions(mixxx-lib PUBLIC __MEDIAFOUNDATION__) - target_include_directories(mixxx-lib SYSTEM PRIVATE - ${MediaFoundation_INCLUDE_DIRS} + target_include_directories( + mixxx-lib + SYSTEM + PRIVATE ${MediaFoundation_INCLUDE_DIRS} ) - target_link_libraries(mixxx-lib PRIVATE - ${MediaFoundation_LIBRARIES} - Version.lib + target_link_libraries( + mixxx-lib + PRIVATE ${MediaFoundation_LIBRARIES} Version.lib ) endif() @@ -3662,12 +4510,17 @@ find_package(Modplug) default_option(MODPLUG "Modplug module decoder support" "Modplug_FOUND") if(MODPLUG) if(NOT Modplug_FOUND) - message(FATAL_ERROR "Modplug module decoder support requires libmodplug and its development headers.") + message( + FATAL_ERROR + "Modplug module decoder support requires libmodplug and its development headers." + ) endif() - target_sources(mixxx-lib PRIVATE - src/preferences/dialog/dlgprefmodplugdlg.ui - src/sources/soundsourcemodplug.cpp - src/preferences/dialog/dlgprefmodplug.cpp + target_sources( + mixxx-lib + PRIVATE + src/preferences/dialog/dlgprefmodplugdlg.ui + src/sources/soundsourcemodplug.cpp + src/preferences/dialog/dlgprefmodplug.cpp ) target_compile_definitions(mixxx-lib PUBLIC __MODPLUG__) target_link_libraries(mixxx-lib PRIVATE Modplug::Modplug) @@ -3676,7 +4529,7 @@ endif() find_package(Microsoft.GSL CONFIG) if(Microsoft.GSL_FOUND) target_link_libraries(mixxx-lib PRIVATE Microsoft.GSL::GSL) - if (QML) + if(QML) target_link_libraries(mixxx-qml-lib PRIVATE Microsoft.GSL::GSL) target_link_libraries(mixxx-qml-libplugin PRIVATE Microsoft.GSL::GSL) endif() @@ -3689,9 +4542,12 @@ else() endif() endif() - # QtKeychain -option(QTKEYCHAIN "Secure credentials storage support for Live Broadcasting profiles" ON) +option( + QTKEYCHAIN + "Secure credentials storage support for Live Broadcasting profiles" + ON +) if(QTKEYCHAIN) find_package(Qt${QT_VERSION_MAJOR}Keychain REQUIRED) target_compile_definitions(mixxx-lib PUBLIC __QTKEYCHAIN__) @@ -3705,40 +4561,10 @@ find_package(LibUSB) # USB HID controller support option(HID "USB HID controller support" ON) if(HID) - # hidapi 0.11.2 is the first release, that implements hid_get_input_report - # for the Linux hidraw backend. - find_package(hidapi 0.11.2) + # hidapi 0.14.0 is the first release, that contains bus type information + find_package(hidapi 0.14.0) if(NOT hidapi_FOUND) - message(STATUS "Linking internal libhidapi statically") - add_library(mixxx-hidapi STATIC EXCLUDE_FROM_ALL) - target_include_directories(mixxx-hidapi SYSTEM PUBLIC lib/hidapi/hidapi) - if(WIN32) - target_sources(mixxx-hidapi PRIVATE lib/hidapi/windows/hid.c) - find_library(Setupapi_LIBRARY Setupapi REQUIRED) - target_link_libraries(mixxx-hidapi PUBLIC ${Setupapi_LIBRARY}) - elseif(APPLE) - if(IOS) - message(FATAL_ERROR "USB HID controllers are not supported on iOS") - endif() - target_sources(mixxx-hidapi PRIVATE lib/hidapi/mac/hid.c) - find_library(AppKit_LIBRARY AppKit REQUIRED) - target_link_libraries(mixxx-hidapi PUBLIC ${AppKit_LIBRARY}) - elseif(UNIX) - if(CMAKE_SYSTEM_NAME STREQUAL Linux) - find_library(libudev_LIBRARY udev REQUIRED) - target_sources(mixxx-hidapi PRIVATE lib/hidapi/linux/hid.c) - target_link_libraries(mixxx-hidapi PRIVATE ${libudev_LIBRARY}) - else() - if(NOT LibUSB_FOUND) - message(FATAL_ERROR "USB HID controller support on Unix with statically linked libhidapi-libusb requires libusb 1.0 and its development headers.") - endif() - target_sources(mixxx-hidapi PRIVATE lib/hidapi/libusb/hid.c) - target_link_libraries(mixxx-hidapi PRIVATE LibUSB::LibUSB) - endif() - else() - message(FATAL_ERROR "USB HID controller support only possible on Windows/Mac OS/Linux/BSD.") - endif() - target_link_libraries(mixxx-lib PRIVATE mixxx-hidapi) + message(FATAL_ERROR "hidapi >= 0.14.0 not found!") else() # hidapi has two backends on Linux, one using the kernel's hidraw API and one using libusb. # libusb obviously does not support Bluetooth HID devices, so use the hidraw backend. The @@ -3749,15 +4575,18 @@ if(HID) target_link_libraries(mixxx-lib PRIVATE hidapi::hidapi) endif() endif() - target_sources(mixxx-lib PRIVATE - src/controllers/hid/hidcontroller.cpp - src/controllers/hid/hidiothread.cpp - src/controllers/hid/hidioglobaloutputreportfifo.cpp - src/controllers/hid/hidiooutputreport.cpp - src/controllers/hid/hiddevice.cpp - src/controllers/hid/hidenumerator.cpp - src/controllers/hid/legacyhidcontrollermapping.cpp - src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp + target_sources( + mixxx-lib + PRIVATE + src/controllers/hid/hidcontroller.cpp + src/controllers/hid/hidiothread.cpp + src/controllers/hid/hidioglobaloutputreportfifo.cpp + src/controllers/hid/hidiooutputreport.cpp + src/controllers/hid/hiddevice.cpp + src/controllers/hid/hidenumerator.cpp + src/controllers/hid/hidusagetables.cpp + src/controllers/hid/legacyhidcontrollermapping.cpp + src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp ) target_compile_definitions(mixxx-lib PUBLIC __HID__) endif() @@ -3766,16 +4595,23 @@ endif() default_option(BULK "USB Bulk controller support" "LibUSB_FOUND;NOT WIN32") if(BULK) if(NOT LibUSB_FOUND) - message(FATAL_ERROR "USB Bulk controller support requires libusb 1.0 and its development headers.") + message( + FATAL_ERROR + "USB Bulk controller support requires libusb 1.0 and its development headers." + ) endif() - target_sources(mixxx-lib PRIVATE - src/controllers/bulk/bulkcontroller.cpp - src/controllers/bulk/bulkenumerator.cpp + target_sources( + mixxx-lib + PRIVATE + src/controllers/bulk/bulkcontroller.cpp + src/controllers/bulk/bulkenumerator.cpp ) if(NOT HID) - target_sources(mixxx-lib PRIVATE - src/controllers/hid/legacyhidcontrollermapping.cpp - src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp + target_sources( + mixxx-lib + PRIVATE + src/controllers/hid/legacyhidcontrollermapping.cpp + src/controllers/hid/legacyhidcontrollermappingfilehandler.cpp ) endif() target_compile_definitions(mixxx-lib PUBLIC __BULK__) @@ -3786,18 +4622,23 @@ endif() default_option(VINYLCONTROL "Vinyl Control support" "NOT MACAPPSTORE") if(VINYLCONTROL) if(MACAPPSTORE) - message(FATAL_ERROR "Mac App Store and Vinyl Control support are mutually exclusive due to licensing issues.") + message( + FATAL_ERROR + "Mac App Store and Vinyl Control support are mutually exclusive due to licensing issues." + ) endif() - target_sources(mixxx-lib PRIVATE - src/vinylcontrol/vinylcontrol.cpp - src/vinylcontrol/vinylcontrolxwax.cpp - src/preferences/dialog/dlgprefvinyl.cpp - src/vinylcontrol/vinylcontrolsignalwidget.cpp - src/vinylcontrol/vinylcontrolmanager.cpp - src/vinylcontrol/vinylcontrolprocessor.cpp - src/vinylcontrol/steadypitch.cpp - src/engine/controls/vinylcontrolcontrol.cpp + target_sources( + mixxx-lib + PRIVATE + src/vinylcontrol/vinylcontrol.cpp + src/vinylcontrol/vinylcontrolxwax.cpp + src/preferences/dialog/dlgprefvinyl.cpp + src/vinylcontrol/vinylcontrolsignalwidget.cpp + src/vinylcontrol/vinylcontrolmanager.cpp + src/vinylcontrol/vinylcontrolprocessor.cpp + src/vinylcontrol/steadypitch.cpp + src/engine/controls/vinylcontrolcontrol.cpp ) target_compile_definitions(mixxx-lib PUBLIC __VINYLCONTROL__) @@ -3808,34 +4649,56 @@ if(VINYLCONTROL) target_link_libraries(mixxx-lib PRIVATE mixxx-xwax) endif() +# rendergraph +add_subdirectory(src/rendergraph) +target_link_libraries(mixxx-lib PUBLIC rendergraph_gl) +target_compile_definitions(mixxx-lib PUBLIC rendergraph=rendergraph_gl) + # WavPack audio file support find_package(wavpack) default_option(WAVPACK "WavPack audio file support" "wavpack_FOUND") if(WAVPACK) if(NOT wavpack_FOUND) - message(FATAL_ERROR "WavPack audio file support requires libwv and its development headers.") + message( + FATAL_ERROR + "WavPack audio file support requires libwv and its development headers." + ) endif() target_sources(mixxx-lib PRIVATE src/sources/soundsourcewv.cpp) target_compile_definitions(mixxx-lib PUBLIC __WV__) target_link_libraries(mixxx-lib PRIVATE WavPack::wavpack) endif() -target_precompile_headers(mixxx-lib PUBLIC - ${MIXXX_LIB_PRECOMPILED_HEADER} - ${MIXXX_COMMON_PRECOMPILED_HEADER} +target_precompile_headers( + mixxx-lib + PUBLIC ${MIXXX_LIB_PRECOMPILED_HEADER} ${MIXXX_COMMON_PRECOMPILED_HEADER} ) -target_precompile_headers(mixxx-test REUSE_FROM mixxx-lib) +if(BUILD_TESTING) + target_precompile_headers(mixxx-test REUSE_FROM mixxx-lib) +endif() # Configure file with build options -file(RELATIVE_PATH MIXXX_INSTALL_DOCDIR_RELATIVE_TO_DATADIR "${CMAKE_INSTALL_PREFIX}/${MIXXX_INSTALL_DATADIR}" "${CMAKE_INSTALL_PREFIX}/${MIXXX_INSTALL_DOCDIR}") -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" @ONLY) +file( + RELATIVE_PATH + MIXXX_INSTALL_DOCDIR_RELATIVE_TO_DATADIR + "${CMAKE_INSTALL_PREFIX}/${MIXXX_INSTALL_DATADIR}" + "${CMAKE_INSTALL_PREFIX}/${MIXXX_INSTALL_DOCDIR}" +) +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/src/config.h" + @ONLY +) # Packaging set(CPACK_PACKAGE_NAME "Mixxx") set(CPACK_PACKAGE_VENDOR "Mixxx Project") set(CPACK_PACKAGE_CONTACT "RJ Skerry-Ryan ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Digital DJ Application") -set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/packaging/CPackPackageDescription.txt") +set( + CPACK_PACKAGE_DESCRIPTION_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/CPackPackageDescription.txt" +) set(CPACK_PACKAGE_INSTALL_DIRECTORY "Mixxx") set(CPACK_PACKAGE_EXECUTABLES "mixxx;Mixxx") set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/res/images/mixxx_install_logo.bmp") @@ -3852,7 +4715,7 @@ set(CPACK_GIT_COMMIT_DATE ${GIT_COMMIT_DATE}) # Detailed version information, git info and package file name are set from # CPackConfig.cmake, not here. -set(CPACK_SOURCE_IGNORE_FILES "\\\\.#;/#;.*~;\\\\.o$") +set(CPACK_SOURCE_IGNORE_FILES "\\\\.#;/#;.*~;\\\\.o$") list(APPEND CPACK_SOURCE_IGNORE_FILES "/\\\\.git/") list(APPEND CPACK_SOURCE_IGNORE_FILES "/\\\\.github/") list(APPEND CPACK_SOURCE_IGNORE_FILES "/build/") @@ -3861,86 +4724,147 @@ set(CPACK_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(CPACK_DEBIAN_PACKAGE_SECTION "sound") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") +if(QT6) + set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "qt6-translations-l10n") +else() + set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "qttranslations5-l10n") +endif() set(CPACK_DEBIAN_PACKAGE_SUGGESTS "pdf-viewer, pulseaudio-utils") set(CPACK_DEBIAN_PACKAGE_REPLACES "mixxx-data") if(QT6) if(QML) - set(CPACK_DEBIAN_PACKAGE_DEPENDS - "libqt6sql6-sqlite, fonts-open-sans, fonts-ubuntu, qt6-qpa-plugins,\ - qml6-module-qt5compat-graphicaleffects, qml6-module-qtquick-controls, qml6-module-qtquick-layouts, qml6-module-qtquick-nativestyle, qml6-module-qtquick-templates, qml6-module-qtquick-window, qml6-module-qt-labs-qmlmodels, qml6-module-qtquick-shapes, qml6-module-qtqml-workerscript") + set( + CPACK_DEBIAN_PACKAGE_DEPENDS + "libqt6sql6-sqlite, fonts-open-sans, fonts-ubuntu, qt6-qpa-plugins, qml6-module-qt5compat-graphicaleffects, qml6-module-qtquick-controls, qml6-module-qtquick-layouts, qml6-module-qtquick-nativestyle, qml6-module-qtquick-templates, qml6-module-qtquick-window, qml6-module-qt-labs-qmlmodels, qml6-module-qtquick-shapes, qml6-module-qtqml-workerscript" + ) else() - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6sql6-sqlite, fonts-open-sans, fonts-ubuntu, qt6-qpa-plugins") + set( + CPACK_DEBIAN_PACKAGE_DEPENDS + "libqt6sql6-sqlite, fonts-open-sans, fonts-ubuntu, qt6-qpa-plugins" + ) endif() else() if(QML) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5sql5-sqlite, fonts-open-sans, fonts-ubuntu,\ - qml-module-qtquick-controls, qml-module-qtquick-controls2, qml-module-qt-labs-qmlmodels, qml-module-qtquick-shapes") + set( + CPACK_DEBIAN_PACKAGE_DEPENDS + "libqt5sql5-sqlite, fonts-open-sans, fonts-ubuntu, qml-module-qtquick-controls, qml-module-qtquick-controls2, qml-module-qt-labs-qmlmodels, qml-module-qtquick-shapes" + ) else() - set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5sql5-sqlite, fonts-open-sans, fonts-ubuntu") + set( + CPACK_DEBIAN_PACKAGE_DEPENDS + "libqt5sql5-sqlite, fonts-open-sans, fonts-ubuntu" + ) endif() endif() set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "${CPACK_PACKAGE_HOMEPAGE_URL}") set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE) file(READ ${CPACK_PACKAGE_DESCRIPTION_FILE} CPACK_DEBIAN_PACKAGE_DESCRIPTION) -set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED "${CPACK_DEBIAN_PACKAGE_DESCRIPTION}") -string(PREPEND CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}" "\n") -string(REPLACE "\n\n" "\n.\n" CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED "${CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED}") -string(REPLACE "\n" "\n " CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED "${CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED}") +set( + CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED + "${CPACK_DEBIAN_PACKAGE_DESCRIPTION}" +) +string( + PREPEND + CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED + "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}" + "\n" +) +string( + REPLACE + "\n\n" + "\n.\n" + CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED + "${CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED}" +) +string( + REPLACE + "\n" + "\n " + CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED + "${CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED}" +) # This is the version of the package itself and can be advanced or set to # something like 0ubuntu1 when building a new package from the same version -if (NOT CPACK_DEBIAN_PACKAGE_RELEASE) - set(CPACK_DEBIAN_PACKAGE_RELEASE 1) +if(NOT CPACK_DEBIAN_PACKAGE_RELEASE) + set(CPACK_DEBIAN_PACKAGE_RELEASE 1) endif() -set(CPACK_DEBIAN_DISTRIBUTION_RELEASES jammy mantic noble oracular) +set(CPACK_DEBIAN_DISTRIBUTION_RELEASES jammy noble oracular plucky) set(CPACK_DEBIAN_SOURCE_DIR ${CMAKE_SOURCE_DIR}) -set(CPACK_DEBIAN_UPLOAD_PPA_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/packaging/CPackDebUploadPPA.cmake") -set(CPACK_DEBIAN_INSTALL_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/packaging/CPackDebInstall.cmake") +set( + CPACK_DEBIAN_UPLOAD_PPA_SCRIPT + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/CPackDebUploadPPA.cmake" +) +set( + CPACK_DEBIAN_INSTALL_SCRIPT + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/CPackDebInstall.cmake" +) set(CPACK_WIX_UPGRADE_GUID "921DC99C-4DCF-478D-B950-50685CB9E6BE") -set(CPACK_WIX_LICENSE_RTF "${CMAKE_CURRENT_BINARY_DIR}/packaging/wix/LICENSE.rtf") +set( + CPACK_WIX_LICENSE_RTF + "${CMAKE_CURRENT_BINARY_DIR}/packaging/wix/LICENSE.rtf" +) set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/res/images/icons/ic_mixxx.ico") set(CPACK_WIX_PROPERTY_ARPHELPLINK "${CPACK_PACKAGE_HOMEPAGE_URL}") -set(CPACK_WIX_UI_BANNER "${CMAKE_CURRENT_SOURCE_DIR}/packaging/wix/images/banner.bmp") -set(CPACK_WIX_UI_DIALOG "${CMAKE_CURRENT_SOURCE_DIR}/packaging/wix/images/dialog.bmp") +set( + CPACK_WIX_UI_BANNER + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/wix/images/banner.bmp" +) +set( + CPACK_WIX_UI_DIALOG + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/wix/images/dialog.bmp" +) -set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_SOURCE_DIR}/packaging/CPackConfig.cmake" ) +set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_SOURCE_DIR}/packaging/CPackConfig.cmake") if(WIN32) - # override not working default NSIS - set(CPACK_GENERATOR WIX) - # uses CMAKE_PROJECT_VERSION - configure_file(packaging/wix/LICENSE.rtf.in packaging/wix/LICENSE.rtf @ONLY) + # override not working default NSIS + set(CPACK_GENERATOR WIX) + # uses CMAKE_PROJECT_VERSION + configure_file(packaging/wix/LICENSE.rtf.in packaging/wix/LICENSE.rtf @ONLY) endif() include(CPack) -if (APPLOCAL_COMPONENT_DEFINED) - cpack_add_component(applocal - HIDDEN - REQUIRED) - - # In order to run Mixx from the build directory install applocal components - add_custom_command( - TARGET mixxx POST_BUILD - COMMAND "${CMAKE_COMMAND}" -DCOMPONENT=applocal -DCMAKE_INSTALL_PREFIX="${CMAKE_CURRENT_BINARY_DIR}" -P cmake_install.cmake) +if(APPLOCAL_COMPONENT_DEFINED) + cpack_add_component(applocal HIDDEN REQUIRED) + + # In order to run Mixx from the build directory install applocal components + add_custom_command( + TARGET mixxx + POST_BUILD + COMMAND + "${CMAKE_COMMAND}" -DCOMPONENT=applocal + -DCMAKE_INSTALL_PREFIX="${CMAKE_CURRENT_BINARY_DIR}" -P + cmake_install.cmake + COMMENT + "Install applocal components to allow Mixxx to be run from the build directory." + ) endif() if(APPLE AND MACOS_BUNDLE) - set(BUNDLE_NAME "${MIXXX_INSTALL_PREFIX}") - set(BUNDLE_DIRS "${CMAKE_PREFIX_PATH}/lib") - set(APPLE_CODESIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/Mixxx.entitlements") - - # Starting with arm64 macOS Apple will require ad-hoc code signatures, - # which can be generated by setting the identity to a single dash (-). - # These only include a checksum for verifying integrity, not an actual - # signature. - if (NOT APPLE_CODESIGN_IDENTITY) - set(APPLE_CODESIGN_IDENTITY -) - endif() + set(BUNDLE_NAME "${MIXXX_INSTALL_PREFIX}") + set(BUNDLE_DIRS "${CMAKE_PREFIX_PATH}/lib") + set( + APPLE_CODESIGN_ENTITLEMENTS + "${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/Mixxx.entitlements" + ) + + # Starting with arm64 macOS Apple will require ad-hoc code signatures, + # which can be generated by setting the identity to a single dash (-). + # These only include a checksum for verifying integrity, not an actual + # signature. + if(NOT APPLE_CODESIGN_IDENTITY) + set(APPLE_CODESIGN_IDENTITY -) + endif() - configure_file(cmake/modules/BundleInstall.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" @ONLY) - install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake") + configure_file( + cmake/modules/BundleInstall.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake" + @ONLY + ) + install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/BundleInstall.cmake") endif() diff --git a/COPYING b/COPYING index b84655a62c0..a41e16d1f58 100644 --- a/COPYING +++ b/COPYING @@ -1,3 +1,3 @@ -Mixxx is Copyright (C) 2000-2024 by its respective authors. This version +Mixxx is Copyright (C) 2000-2025 by its respective authors. This version of the program is distributed under the General Public License version 2, as described in the file LICENSE distributed with the program. diff --git a/LICENSE b/LICENSE index 92d4c0aff40..a3ff5f714ce 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Mixxx 2.6-alpha, Digital DJ'ing software. -Copyright (C) 2001-2024 Mixxx Development Team +Copyright (C) 2001-2025 Mixxx Development Team Mixxx is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/cmake/modules/DefaultOption.cmake b/cmake/modules/DefaultOption.cmake index 160cafc2420..c2e7f563690 100644 --- a/cmake/modules/DefaultOption.cmake +++ b/cmake/modules/DefaultOption.cmake @@ -32,9 +32,9 @@ set a default and the value may be overridden by the user. macro(DEFAULT_OPTION option doc depends) set(${option}_DEFAULT_ON 1) - foreach(d ${depends}) + foreach(dependency ${depends}) # if() takes the condition as a list of arguments. Parentheses need to be separated as well. - string(REPLACE "(" " ( " DEFAULT_OPTION_DEP "${d}") + string(REPLACE "(" " ( " DEFAULT_OPTION_DEP "${dependency}") string(REPLACE ")" " ) " DEFAULT_OPTION_DEP "${DEFAULT_OPTION_DEP}") string(REGEX REPLACE " +" ";" DEFAULT_OPTION_DEP "${DEFAULT_OPTION_DEP}") if(${DEFAULT_OPTION_DEP}) diff --git a/cmake/modules/FindChromaprint.cmake b/cmake/modules/FindChromaprint.cmake index d7b136318de..3852d6751db 100644 --- a/cmake/modules/FindChromaprint.cmake +++ b/cmake/modules/FindChromaprint.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,14 +50,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Chromaprint QUIET libchromaprint) endif() -find_path(Chromaprint_INCLUDE_DIR +find_path( + Chromaprint_INCLUDE_DIR NAMES chromaprint.h HINTS ${PC_Chromaprint_INCLUDE_DIRS} PATH_SUFFIXES chromaprint - DOC "Chromaprint include directory") + DOC "Chromaprint include directory" +) mark_as_advanced(Chromaprint_INCLUDE_DIR) -find_library(Chromaprint_LIBRARY +find_library( + Chromaprint_LIBRARY NAMES chromaprint chromaprint_p HINTS ${PC_Chromaprint_LIBRARY_DIRS} DOC "Chromaprint library" @@ -82,7 +85,8 @@ if(Chromaprint_FOUND) if(NOT TARGET Chromaprint::Chromaprint) add_library(Chromaprint::Chromaprint UNKNOWN IMPORTED) - set_target_properties(Chromaprint::Chromaprint + set_target_properties( + Chromaprint::Chromaprint PROPERTIES IMPORTED_LOCATION "${Chromaprint_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Chromaprint_CFLAGS_OTHER}" @@ -92,13 +96,17 @@ if(Chromaprint_FOUND) if(Chromaprint_IS_STATIC) if(WIN32) # used in chomaprint.h to set dllexport for Windows - set_property(TARGET Chromaprint::Chromaprint APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS - CHROMAPRINT_NODLL + set_property( + TARGET Chromaprint::Chromaprint + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS CHROMAPRINT_NODLL ) endif() - find_package(FFTW REQUIRED) - set_property(TARGET Chromaprint::Chromaprint APPEND PROPERTY INTERFACE_LINK_LIBRARIES - FFTW::FFTW + find_package(FFTW3 REQUIRED) + set_property( + TARGET Chromaprint::Chromaprint + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES FFTW3::fftw3 ) endif() endif() diff --git a/cmake/modules/FindDjInterop.cmake b/cmake/modules/FindDjInterop.cmake index ad1a2b0f392..5eec0a11c8d 100644 --- a/cmake/modules/FindDjInterop.cmake +++ b/cmake/modules/FindDjInterop.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,13 +48,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_DjInterop QUIET libdjinterop) endif() -find_path(DjInterop_INCLUDE_DIR +find_path( + DjInterop_INCLUDE_DIR NAMES djinterop/djinterop.hpp HINTS ${PC_DjInterop_INCLUDE_DIRS} - DOC "DjInterop include directory") + DOC "DjInterop include directory" +) mark_as_advanced(DjInterop_INCLUDE_DIR) -find_library(DjInterop_LIBRARY +find_library( + DjInterop_LIBRARY NAMES djinterop HINTS ${PC_DjInterop_LIBRARY_DIRS} DOC "DjInterop library" @@ -79,7 +82,8 @@ if(DjInterop_FOUND) if(NOT TARGET DjInterop::DjInterop) add_library(DjInterop::DjInterop UNKNOWN IMPORTED) - set_target_properties(DjInterop::DjInterop + set_target_properties( + DjInterop::DjInterop PROPERTIES IMPORTED_LOCATION "${DjInterop_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_DjInterop_CFLAGS_OTHER}" diff --git a/cmake/modules/FindEbur128.cmake b/cmake/modules/FindEbur128.cmake index 6fd837ba010..09934058377 100644 --- a/cmake/modules/FindEbur128.cmake +++ b/cmake/modules/FindEbur128.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,14 +48,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Ebur128 QUIET libebur128>=1.2.4) endif() -find_path(Ebur128_INCLUDE_DIR +find_path( + Ebur128_INCLUDE_DIR NAMES ebur128.h HINTS ${PC_Ebur128_INCLUDE_DIRS} PATH_SUFFIXES ebur128 - DOC "Ebur128 include directory") + DOC "Ebur128 include directory" +) mark_as_advanced(Ebur128_INCLUDE_DIR) -find_library(Ebur128_LIBRARY +find_library( + Ebur128_LIBRARY NAMES ebur128 HINTS ${PC_Ebur128_LIBRARY_DIRS} DOC "Ebur128 library" @@ -80,7 +83,8 @@ if(Ebur128_FOUND) if(NOT TARGET Ebur128::Ebur128) add_library(Ebur128::Ebur128 UNKNOWN IMPORTED) - set_target_properties(Ebur128::Ebur128 + set_target_properties( + Ebur128::Ebur128 PROPERTIES IMPORTED_LOCATION "${Ebur128_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Ebur128_CFLAGS_OTHER}" diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 6924d07cae6..55255cc30e2 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -63,9 +63,9 @@ include(FindPackageHandleStandardArgs) # The default components were taken from a survey over other FindFFMPEG.cmake files -if (NOT FFMPEG_FIND_COMPONENTS) +if(NOT FFMPEG_FIND_COMPONENTS) set(FFMPEG_FIND_COMPONENTS libavcodec libavformat libavutil) -endif () +endif() # ### Macro: find_component @@ -73,50 +73,62 @@ endif () # Checks for the given component by invoking pkgconfig and then looking up the libraries and # include directories. # -macro(find_component _component _pkgconfig _library _header) - - # use pkg-config to get the directories and then use these values - # in the FIND_PATH() and FIND_LIBRARY() calls - find_package(PkgConfig QUIET) - if (PkgConfig_FOUND) - pkg_check_modules(PC_FFMPEG_${_component} QUIET ${_pkgconfig}) - endif () +macro(find_component component pkgconfig library header) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(PC_FFMPEG_${component} QUIET ${pkgconfig}) + endif() - find_path(FFMPEG_${_component}_INCLUDE_DIRS ${_header} + find_path( + FFMPEG_${component}_INCLUDE_DIRS + ${header} HINTS - ${PC_FFMPEG_${_component}_INCLUDEDIR} - ${PC_FFMPEG_${_component}_INCLUDE_DIRS} + ${PC_FFMPEG_${component}_INCLUDEDIR} + ${PC_FFMPEG_${component}_INCLUDE_DIRS} ${PC_FFMPEG_INCLUDE_DIRS} - PATH_SUFFIXES - ffmpeg + PATH_SUFFIXES ffmpeg ) - find_library(FFMPEG_${_component}_LIBRARIES NAMES ${PC_FFMPEG_${_component}_LIBRARIES} ${_library} - HINTS - ${PC_FFMPEG_${_component}_LIBDIR} - ${PC_FFMPEG_${_component}_LIBRARY_DIRS} + find_library( + FFMPEG_${component}_LIBRARIES + NAMES ${PC_FFMPEG_${component}_LIBRARIES} ${library} + HINTS + ${PC_FFMPEG_${component}_LIBDIR} + ${PC_FFMPEG_${component}_LIBRARY_DIRS} ${PC_FFMPEG_LIBRARY_DIRS} ) - #message(STATUS ${FFMPEG_${_component}_LIBRARIES}) - #message(STATUS ${PC_FFMPEG_${_component}_LIBRARIES}) + #message(STATUS ${FFMPEG_${component}_LIBRARIES}) + #message(STATUS ${PC_FFMPEG_${component}_LIBRARIES}) - set(FFMPEG_${_component}_DEFINITIONS ${PC_FFMPEG_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") - set(FFMPEG_${_component}_VERSION ${PC_FFMPEG_${_component}_VERSION} CACHE STRING "The ${_component} version number.") + set( + FFMPEG_${component}_DEFINITIONS + ${PC_FFMPEG_${component}_CFLAGS_OTHER} + CACHE STRING + "The ${component} CFLAGS." + ) + set( + FFMPEG_${component}_VERSION + ${PC_FFMPEG_${component}_VERSION} + CACHE STRING + "The ${component} version number." + ) - if (FFMPEG_${_component}_LIBRARIES AND FFMPEG_${_component}_INCLUDE_DIRS) - message(STATUS " - ${_component} ${FFMPEG_${_component}_VERSION} found.") - set(FFMPEG_${_component}_FOUND TRUE) - else () - message(STATUS " - ${_component} not found.") - endif () + if(FFMPEG_${component}_LIBRARIES AND FFMPEG_${component}_INCLUDE_DIRS) + message(STATUS " - ${component} ${FFMPEG_${component}_VERSION} found.") + set(FFMPEG_${component}_FOUND TRUE) + else() + message(STATUS " - ${component} not found.") + endif() mark_as_advanced( - FFMPEG_${_component}_INCLUDE_DIRS - FFMPEG_${_component}_LIBRARIES - FFMPEG_${_component}_DEFINITIONS - FFMPEG_${_component}_VERSION) - + FFMPEG_${component}_INCLUDE_DIRS + FFMPEG_${component}_LIBRARIES + FFMPEG_${component}_DEFINITIONS + FFMPEG_${component}_VERSION + ) endmacro() message(STATUS "Searching for FFMPEG components") @@ -132,34 +144,59 @@ find_component(libswresample libswresample swresample libswresample/swresample.h set(FFMPEG_LIBRARIES "") set(FFMPEG_DEFINITIONS "") # Check if the required components were found and add their stuff to the FFMPEG_* vars. -foreach (_component ${FFMPEG_FIND_COMPONENTS}) - if (FFMPEG_${_component}_FOUND) - #message(STATUS "Required component ${_component} present.") - set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${FFMPEG_${_component}_LIBRARIES}) - set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${FFMPEG_${_component}_DEFINITIONS}) - list(APPEND FFMPEG_INCLUDE_DIRS ${FFMPEG_${_component}_INCLUDE_DIRS}) +foreach(component ${FFMPEG_FIND_COMPONENTS}) + if(FFMPEG_${component}_FOUND) + #message(STATUS "Required component ${component} present.") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${FFMPEG_${component}_LIBRARIES}) + set( + FFMPEG_DEFINITIONS + ${FFMPEG_DEFINITIONS} + ${FFMPEG_${component}_DEFINITIONS} + ) + list(APPEND FFMPEG_INCLUDE_DIRS ${FFMPEG_${component}_INCLUDE_DIRS}) endif() -endforeach () +endforeach() # Build the include path with duplicates removed. -if (FFMPEG_INCLUDE_DIRS) +if(FFMPEG_INCLUDE_DIRS) list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) -endif () +endif() # cache the vars. -set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFMPEG include directories." FORCE) -set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFMPEG libraries." FORCE) -set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFMPEG cflags." FORCE) - -mark_as_advanced(FFMPEG_INCLUDE_DIRS - FFMPEG_LIBRARIES - FFMPEG_DEFINITIONS) +set( + FFMPEG_INCLUDE_DIRS + ${FFMPEG_INCLUDE_DIRS} + CACHE STRING + "The FFMPEG include directories." + FORCE +) +set( + FFMPEG_LIBRARIES + ${FFMPEG_LIBRARIES} + CACHE STRING + "The FFMPEG libraries." + FORCE +) +set( + FFMPEG_DEFINITIONS + ${FFMPEG_DEFINITIONS} + CACHE STRING + "The FFMPEG cflags." + FORCE +) + +mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES FFMPEG_DEFINITIONS) # Compile the list of required vars -set(_FFMPEG_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) -foreach (_component ${FFMPEG_FIND_COMPONENTS}) - list(APPEND _FFMPEG_REQUIRED_VARS FFMPEG_${_component}_LIBRARIES FFMPEG_${_component}_INCLUDE_DIRS) -endforeach () +set(FFMPEG_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) +foreach(component ${FFMPEG_FIND_COMPONENTS}) + list( + APPEND + FFMPEG_REQUIRED_VARS + FFMPEG_${component}_LIBRARIES + FFMPEG_${component}_INCLUDE_DIRS + ) +endforeach() # Give a nice error message if some of the required vars are missing. -find_package_handle_standard_args(FFMPEG DEFAULT_MSG ${_FFMPEG_REQUIRED_VARS}) +find_package_handle_standard_args(FFMPEG DEFAULT_MSG ${FFMPEG_REQUIRED_VARS}) diff --git a/cmake/modules/FindFFTW.cmake b/cmake/modules/FindFFTW.cmake deleted file mode 100644 index a3fb8265c6b..00000000000 --- a/cmake/modules/FindFFTW.cmake +++ /dev/null @@ -1,75 +0,0 @@ -# This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team -# Distributed under the GNU General Public Licence (GPL) version 2 or any later -# later version. See the LICENSE file for details. - -#[=======================================================================[.rst: -FindFFTW --------- - -Finds the FFTW library. - -Imported Targets -^^^^^^^^^^^^^^^^ - -This module provides the following imported targets, if found: - -``FFTW::FFTW`` - The FFTW library - -Result Variables -^^^^^^^^^^^^^^^^ - -This will define the following variables: - -``FFTW_FOUND`` - True if the system has the FFTW library. -``FFTW_INCLUDE_DIRS`` - Include directories needed to use FFTW. -``FFTW_LIBRARIES`` - Libraries needed to link to FFTW. - -Cache Variables -^^^^^^^^^^^^^^^ - -The following cache variables may also be set: - -``FFTW_INCLUDE_DIR`` - The directory containing ``fftw3.h``. -``FFTW_LIBRARY`` - The path to the FFTW library. - -#]=======================================================================] - -find_path(FFTW_INCLUDE_DIR - NAMES fftw3.h - DOC "FFTW include directory") -mark_as_advanced(FFTW_INCLUDE_DIR) - -find_library(FFTW_LIBRARY - NAMES fftw fftw3 fftw-3.3 - DOC "FFTW library" -) -mark_as_advanced(FFTW_LIBRARY) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - FFTW - DEFAULT_MSG - FFTW_LIBRARY - FFTW_INCLUDE_DIR -) - -if(FFTW_FOUND) - set(FFTW_LIBRARIES "${FFTW_LIBRARY}") - set(FFTW_INCLUDE_DIRS "${FFTW_INCLUDE_DIR}") - - if(NOT TARGET FFTW::FFTW) - add_library(FFTW::FFTW UNKNOWN IMPORTED) - set_target_properties(FFTW::FFTW - PROPERTIES - IMPORTED_LOCATION "${FFTW_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${FFTW_INCLUDE_DIR}" - ) - endif() -endif() diff --git a/cmake/modules/FindFFTW3.cmake b/cmake/modules/FindFFTW3.cmake new file mode 100644 index 00000000000..ecbcb05dfe7 --- /dev/null +++ b/cmake/modules/FindFFTW3.cmake @@ -0,0 +1,86 @@ +#[=======================================================================[.rst: +FindFFTW3 +-------- + +Finds the FFTW3 library. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``FFTW3::fftw3`` + The FFTW3 library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``FFTW3_FOUND`` + True if the system has the FFTW3 library. +``FFTW3_INCLUDE_DIRS`` + Include directories needed to use FFTW3. +``FFTW3_LIBRARIES`` + Libraries needed to link to FFTW3. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``FFTW3_INCLUDE_DIR`` + The directory containing ``fftw3.h``. +``FFTW3_LIBRARY`` + The path to the FFTW3 library. + +#]=======================================================================] + +find_package(PkgConfig QUIET) +if(PkgConfig_FOUND) + pkg_check_modules(PC_Fftw3 QUIET fftw3) +endif() + +find_path( + FFTW3_INCLUDE_DIR + NAMES fftw3.h + HINTS ${PC_Fftw3_INCLUDE_DIRS} + DOC "FFTW3 include directory" +) +mark_as_advanced(FFTW3_INCLUDE_DIR) + +find_library( + FFTW3_LIBRARY + NAMES fftw3 fftw-3.3 + HINTS ${PC_Fftw3_LIBRARY_DIRS} + DOC "FFTW3 library" +) +mark_as_advanced(FFTW3_LIBRARY) + +if(DEFINED PC_Fftw3_VERSION AND NOT PC_Fftw3_VERSION STREQUAL "") + set(FFTW3_VERSION "${PC_Fftw3_VERSION}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + FFTW3 + REQUIRED_VARS FFTW3_LIBRARY FFTW3_INCLUDE_DIR + VERSION_VAR FFTW3_VERSION +) + +if(FFTW3_FOUND) + set(FFTW3_LIBRARIES "${FFTW3_LIBRARY}") + set(FFTW3_INCLUDE_DIRS "${FFTW3_INCLUDE_DIR}") + set(FFTW3_DEFINITIONS ${PC_Fftw3_CFLAGS_OTHER}) + + if(NOT TARGET FFTW3::fftw3) + add_library(FFTW3::fftw3 UNKNOWN IMPORTED) + set_target_properties( + FFTW3::fftw3 + PROPERTIES + IMPORTED_LOCATION "${FFTW3_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PC_Fftw3_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${FFTW3_INCLUDE_DIR}" + ) + endif() +endif() diff --git a/cmake/modules/FindFLAC.cmake b/cmake/modules/FindFLAC.cmake index 9441a5963db..74c6f266cce 100644 --- a/cmake/modules/FindFLAC.cmake +++ b/cmake/modules/FindFLAC.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,13 +50,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_FLAC QUIET flac) endif() -find_path(FLAC_INCLUDE_DIR +find_path( + FLAC_INCLUDE_DIR NAMES FLAC/all.h HINTS ${PC_FLAC_INCLUDE_DIRS} - DOC "FLAC include directory") + DOC "FLAC include directory" +) mark_as_advanced(FLAC_INCLUDE_DIR) -find_library(FLAC_LIBRARY +find_library( + FLAC_LIBRARY NAMES FLAC HINTS ${PC_FLAC_LIBRARY_DIRS} DOC "FLAC library" @@ -81,7 +84,8 @@ if(FLAC_FOUND) if(NOT TARGET FLAC::FLAC) add_library(FLAC::FLAC UNKNOWN IMPORTED) - set_target_properties(FLAC::FLAC + set_target_properties( + FLAC::FLAC PROPERTIES IMPORTED_LOCATION "${FLAC_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_FLAC_CFLAGS_OTHER}" @@ -90,8 +94,10 @@ if(FLAC_FOUND) is_static_library(FLAC_IS_STATIC FLAC::FLAC) if(FLAC_IS_STATIC) if(WIN32) - set_property(TARGET FLAC::FLAC APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS - FLAC__NO_DLL + set_property( + TARGET FLAC::FLAC + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS FLAC__NO_DLL ) endif() endif() diff --git a/cmake/modules/FindG72X.cmake b/cmake/modules/FindG72X.cmake index ad22da2b6f1..117e4f95d5f 100644 --- a/cmake/modules/FindG72X.cmake +++ b/cmake/modules/FindG72X.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -37,27 +37,20 @@ The following cache variables may also be set: #]=======================================================================] -find_library(G72X_LIBRARY - NAMES g72x - DOC "G72X library" -) +find_library(G72X_LIBRARY NAMES g72x DOC "G72X library") mark_as_advanced(G72X_LIBRARY) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - G72X - DEFAULT_MSG - G72X_LIBRARY -) +find_package_handle_standard_args(G72X DEFAULT_MSG G72X_LIBRARY) if(G72X_FOUND) set(G72X_LIBRARIES "${G72X_LIBRARY}") if(NOT TARGET G72X::G72X) add_library(G72X::G72X UNKNOWN IMPORTED) - set_target_properties(G72X::G72X - PROPERTIES - IMPORTED_LOCATION "${G72X_LIBRARY}" + set_target_properties( + G72X::G72X + PROPERTIES IMPORTED_LOCATION "${G72X_LIBRARY}" ) endif() endif() diff --git a/cmake/modules/FindGLIB.cmake b/cmake/modules/FindGLIB.cmake index e7e3dac05ee..e500aae309f 100644 --- a/cmake/modules/FindGLIB.cmake +++ b/cmake/modules/FindGLIB.cmake @@ -46,79 +46,129 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_GLIB QUIET glib-2.0) endif() -find_library(GLIB_LIBRARIES - NAMES glib-2.0 - HINTS ${PC_GLIB_LIBDIR} - ${PC_GLIB_LIBRARY_DIRS} +find_library( + GLIB_LIBRARIES + NAMES glib-2.0 + HINTS ${PC_GLIB_LIBDIR} ${PC_GLIB_LIBRARY_DIRS} ) # Files in glib's main include path may include glibconfig.h, which, # for some odd reason, is normally in $LIBDIR/glib-2.0/include. get_filename_component(_GLIB_LIBRARY_DIR ${GLIB_LIBRARIES} PATH) -find_path(GLIBCONFIG_INCLUDE_DIR - NAMES glibconfig.h - HINTS ${PC_LIBDIR} ${PC_LIBRARY_DIRS} ${_GLIB_LIBRARY_DIR} - ${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS} - PATH_SUFFIXES glib-2.0/include +find_path( + GLIBCONFIG_INCLUDE_DIR + NAMES glibconfig.h + HINTS + ${PC_LIBDIR} + ${PC_LIBRARY_DIRS} + ${_GLIB_LIBRARY_DIR} + ${PC_GLIB_INCLUDEDIR} + ${PC_GLIB_INCLUDE_DIRS} + PATH_SUFFIXES glib-2.0/include ) -find_path(GLIB_INCLUDE_DIR - NAMES glib.h - HINTS ${PC_GLIB_INCLUDEDIR} - ${PC_GLIB_INCLUDE_DIRS} - PATH_SUFFIXES glib-2.0 +find_path( + GLIB_INCLUDE_DIR + NAMES glib.h + HINTS ${PC_GLIB_INCLUDEDIR} ${PC_GLIB_INCLUDE_DIRS} + PATH_SUFFIXES glib-2.0 ) set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR} ${GLIBCONFIG_INCLUDE_DIR}) # Version detection -if (EXISTS "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h") - file(READ "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h" GLIBCONFIG_H_CONTENTS) - string(REGEX MATCH "#define GLIB_MAJOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}") - set(GLIB_VERSION_MAJOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define GLIB_MINOR_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}") - set(GLIB_VERSION_MINOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define GLIB_MICRO_VERSION ([0-9]+)" _dummy "${GLIBCONFIG_H_CONTENTS}") - set(GLIB_VERSION_MICRO "${CMAKE_MATCH_1}") - set(GLIB_VERSION "${GLIB_VERSION_MAJOR}.${GLIB_VERSION_MINOR}.${GLIB_VERSION_MICRO}") -endif () +if(EXISTS "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h") + file(READ "${GLIBCONFIG_INCLUDE_DIR}/glibconfig.h" GLIBCONFIG_H_CONTENTS) + string( + REGEX MATCH + "#define GLIB_MAJOR_VERSION ([0-9]+)" + _dummy + "${GLIBCONFIG_H_CONTENTS}" + ) + set(GLIB_VERSION_MAJOR "${CMAKE_MATCH_1}") + string( + REGEX MATCH + "#define GLIB_MINOR_VERSION ([0-9]+)" + _dummy + "${GLIBCONFIG_H_CONTENTS}" + ) + set(GLIB_VERSION_MINOR "${CMAKE_MATCH_1}") + string( + REGEX MATCH + "#define GLIB_MICRO_VERSION ([0-9]+)" + _dummy + "${GLIBCONFIG_H_CONTENTS}" + ) + set(GLIB_VERSION_MICRO "${CMAKE_MATCH_1}") + set( + GLIB_VERSION + "${GLIB_VERSION_MAJOR}.${GLIB_VERSION_MINOR}.${GLIB_VERSION_MICRO}" + ) +endif() # Additional Glib components. We only look for libraries, as not all of them # have corresponding headers and all headers are installed alongside the main # glib ones. -foreach (_component ${GLIB_FIND_COMPONENTS}) - if (${_component} STREQUAL "gio") - find_library(GLIB_GIO_LIBRARIES NAMES gio-2.0 HINTS ${_GLIB_LIBRARY_DIR}) - set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GIO_LIBRARIES) - elseif (${_component} STREQUAL "gobject") - find_library(GLIB_GOBJECT_LIBRARIES NAMES gobject-2.0 HINTS ${_GLIB_LIBRARY_DIR}) - set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GOBJECT_LIBRARIES) - elseif (${_component} STREQUAL "gmodule") - find_library(GLIB_GMODULE_LIBRARIES NAMES gmodule-2.0 HINTS ${_GLIB_LIBRARY_DIR}) - set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GMODULE_LIBRARIES) - elseif (${_component} STREQUAL "gthread") - find_library(GLIB_GTHREAD_LIBRARIES NAMES gthread-2.0 HINTS ${_GLIB_LIBRARY_DIR}) - set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GTHREAD_LIBRARIES) - elseif (${_component} STREQUAL "gio-unix") - # gio-unix is compiled as part of the gio library, but the include paths - # are separate from the shared glib ones. Since this is currently only used - # by WebKitGTK we don't go to extraordinary measures beyond pkg-config. - pkg_check_modules(GIO_UNIX QUIET gio-unix-2.0) - endif () -endforeach () +foreach(component ${GLIB_FIND_COMPONENTS}) + if(${component} STREQUAL "gio") + find_library(GLIB_GIO_LIBRARIES NAMES gio-2.0 HINTS ${_GLIB_LIBRARY_DIR}) + set(ADDITIONAL_REQUIRED_VARS ${ADDITIONAL_REQUIRED_VARS} GLIB_GIO_LIBRARIES) + elseif(${component} STREQUAL "gobject") + find_library( + GLIB_GOBJECT_LIBRARIES + NAMES gobject-2.0 + HINTS ${_GLIB_LIBRARY_DIR} + ) + set( + ADDITIONAL_REQUIRED_VARS + ${ADDITIONAL_REQUIRED_VARS} + GLIB_GOBJECT_LIBRARIES + ) + elseif(${component} STREQUAL "gmodule") + find_library( + GLIB_GMODULE_LIBRARIES + NAMES gmodule-2.0 + HINTS ${_GLIB_LIBRARY_DIR} + ) + set( + ADDITIONAL_REQUIRED_VARS + ${ADDITIONAL_REQUIRED_VARS} + GLIB_GMODULE_LIBRARIES + ) + elseif(${component} STREQUAL "gthread") + find_library( + GLIB_GTHREAD_LIBRARIES + NAMES gthread-2.0 + HINTS ${_GLIB_LIBRARY_DIR} + ) + set( + ADDITIONAL_REQUIRED_VARS + ${ADDITIONAL_REQUIRED_VARS} + GLIB_GTHREAD_LIBRARIES + ) + elseif(${component} STREQUAL "gio-unix") + # gio-unix is compiled as part of the gio library, but the include paths + # are separate from the shared glib ones. Since this is currently only used + # by WebKitGTK we don't go to extraordinary measures beyond pkg-config. + pkg_check_modules(GIO_UNIX QUIET gio-unix-2.0) + endif() +endforeach() include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLIB REQUIRED_VARS GLIB_INCLUDE_DIRS GLIB_LIBRARIES ${ADDITIONAL_REQUIRED_VARS} - VERSION_VAR GLIB_VERSION) +find_package_handle_standard_args( + GLIB + REQUIRED_VARS GLIB_INCLUDE_DIRS GLIB_LIBRARIES ${ADDITIONAL_REQUIRED_VARS} + VERSION_VAR GLIB_VERSION +) mark_as_advanced( - GLIBCONFIG_INCLUDE_DIR - GLIB_GIO_LIBRARIES - GLIB_GIO_UNIX_LIBRARIES - GLIB_GMODULE_LIBRARIES - GLIB_GOBJECT_LIBRARIES - GLIB_GTHREAD_LIBRARIES - GLIB_INCLUDE_DIR - GLIB_INCLUDE_DIRS - GLIB_LIBRARIES + GLIBCONFIG_INCLUDE_DIR + GLIB_GIO_LIBRARIES + GLIB_GIO_UNIX_LIBRARIES + GLIB_GMODULE_LIBRARIES + GLIB_GOBJECT_LIBRARIES + GLIB_GTHREAD_LIBRARIES + GLIB_INCLUDE_DIR + GLIB_INCLUDE_DIRS + GLIB_LIBRARIES ) diff --git a/cmake/modules/FindGPerfTools.cmake b/cmake/modules/FindGPerfTools.cmake index d2b681f54f1..e201a8b2137 100644 --- a/cmake/modules/FindGPerfTools.cmake +++ b/cmake/modules/FindGPerfTools.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -55,26 +55,32 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_GPerfTools_PROFILER QUIET libprofiler) endif() -find_path(GPerfTools_TCMALLOC_INCLUDE_DIR +find_path( + GPerfTools_TCMALLOC_INCLUDE_DIR NAMES gperftools/tcmalloc.h HINTS ${PC_GPerfTools_TCMALLOC_INCLUDE_DIRS} - DOC "tcmalloc include directory") + DOC "tcmalloc include directory" +) mark_as_advanced(GPerfTools_TCMALLOC_INCLUDE_DIR) -find_library(GPerfTools_TCMALLOC_LIBRARY +find_library( + GPerfTools_TCMALLOC_LIBRARY NAMES tcmalloc HINTS ${PC_GPerfTools_TCMALLOC_LIBRARY_DIRS} DOC "tcmalloc library" ) mark_as_advanced(GPerfTools_TCMALLOC_LIBRARY) -find_path(GPerfTools_PROFILER_INCLUDE_DIR +find_path( + GPerfTools_PROFILER_INCLUDE_DIR NAMES gperftools/profiler.h HINTS ${PC_GPerfTools_PROFILER_INCLUDE_DIRS} - DOC "profiler include directory") + DOC "profiler include directory" +) mark_as_advanced(GPerfTools_PROFILER_INCLUDE_DIR) -find_library(GPerfTools_PROFILER_LIBRARY +find_library( + GPerfTools_PROFILER_LIBRARY NAMES profiler HINTS ${PC_GPerfTools_PROFILER_LIBRARY_DIRS} DOC "profiler library" @@ -92,31 +98,40 @@ find_package_handle_standard_args( ) if(GPerfTools_FOUND) - set(GPerfTools_LIBRARIES + set( + GPerfTools_LIBRARIES ${GPerfTools_TCMALLOC_LIBRARY} ${GPerfTools_PROFILER_LIBRARY} ) - set(GPerfTools_INCLUDE_DIRS + set( + GPerfTools_INCLUDE_DIRS ${GPerfTools_TCMALLOC_INCLUDE_DIR} ${GPerfTools_PROFILER_INCLUDE_DIR} ) - set(GPerfTools_DEFINITIONS + set( + GPerfTools_DEFINITIONS ${PC_GPerfTools_TCMALLOC_CFLAGS_OTHER} ${PC_GPerfTools_PROFILER_CFLAGS_OTHER} ) - if (NOT TARGET GPerfTools::tcmalloc) + if(NOT TARGET GPerfTools::tcmalloc) add_library(GPerfTools::tcmalloc UNKNOWN IMPORTED) - set_target_properties(GPerfTools::tcmalloc PROPERTIES - IMPORTED_LOCATION ${GPerfTools_TCMALLOC_LIBRARY} - INTERFACE_COMPILE_OPTIONS "${PC_GPerfTools_TCMALLOC_CFLAGS_OTHER}" - INTERFACE_INCLUDE_DIRECTORIES "${GPerfTools_TCMALLOC_INCLUDE_DIR}") + set_target_properties( + GPerfTools::tcmalloc + PROPERTIES + IMPORTED_LOCATION ${GPerfTools_TCMALLOC_LIBRARY} + INTERFACE_COMPILE_OPTIONS "${PC_GPerfTools_TCMALLOC_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${GPerfTools_TCMALLOC_INCLUDE_DIR}" + ) endif() - if (NOT TARGET GPerfTools::profiler) + if(NOT TARGET GPerfTools::profiler) add_library(GPerfTools::profiler UNKNOWN IMPORTED) - set_target_properties(GPerfTools::profiler PROPERTIES - IMPORTED_LOCATION "${GPerfTools_PROFILER_LIBRARY}" - INTERFACE_COMPILE_OPTIONS "${PC_GPerfTools_PROFILER_CFLAGS_OTHER}" - INTERFACE_INCLUDE_DIRECTORIES "${GPerfTools_PROFILER_INCLUDE_DIR}") + set_target_properties( + GPerfTools::profiler + PROPERTIES + IMPORTED_LOCATION "${GPerfTools_PROFILER_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PC_GPerfTools_PROFILER_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${GPerfTools_PROFILER_INCLUDE_DIR}" + ) endif() endif() diff --git a/cmake/modules/FindHSS1394.cmake b/cmake/modules/FindHSS1394.cmake index 42965f3c808..b349d996b28 100644 --- a/cmake/modules/FindHSS1394.cmake +++ b/cmake/modules/FindHSS1394.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -41,15 +41,14 @@ The following cache variables may also be set: #]=======================================================================] -find_path(HSS1394_INCLUDE_DIR +find_path( + HSS1394_INCLUDE_DIR NAMES HSS1394/HSS1394.h - DOC "HSS1394 include directory") + DOC "HSS1394 include directory" +) mark_as_advanced(HSS1394_INCLUDE_DIR) -find_library(HSS1394_LIBRARY - NAMES hss1394 - DOC "HSS1394 library" -) +find_library(HSS1394_LIBRARY NAMES hss1394 DOC "HSS1394 library") mark_as_advanced(HSS1394_LIBRARY) include(FindPackageHandleStandardArgs) @@ -66,7 +65,8 @@ if(HSS1394_FOUND) if(NOT TARGET HSS1394::HSS1394) add_library(HSS1394::HSS1394 UNKNOWN IMPORTED) - set_target_properties(HSS1394::HSS1394 + set_target_properties( + HSS1394::HSS1394 PROPERTIES IMPORTED_LOCATION "${HSS1394_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${HSS1394_INCLUDE_DIR}" diff --git a/cmake/modules/FindID3Tag.cmake b/cmake/modules/FindID3Tag.cmake index 35c0842b5a4..566c40e953a 100644 --- a/cmake/modules/FindID3Tag.cmake +++ b/cmake/modules/FindID3Tag.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,14 +48,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_ID3Tag QUIET id3tag) endif() -find_path(ID3Tag_INCLUDE_DIR +find_path( + ID3Tag_INCLUDE_DIR NAMES id3tag.h HINTS ${PC_ID3Tag_INCLUDE_DIRS} PATH_SUFFIXES id3tag - DOC "ID3Tag include directory") + DOC "ID3Tag include directory" +) mark_as_advanced(ID3Tag_INCLUDE_DIR) -find_library(ID3Tag_LIBRARY +find_library( + ID3Tag_LIBRARY NAMES id3tag HINTS ${PC_ID3Tag_LIBRARY_DIRS} DOC "ID3Tag library" @@ -80,7 +83,8 @@ if(ID3Tag_FOUND) if(NOT TARGET ID3Tag::ID3Tag) add_library(ID3Tag::ID3Tag UNKNOWN IMPORTED) - set_target_properties(ID3Tag::ID3Tag + set_target_properties( + ID3Tag::ID3Tag PROPERTIES IMPORTED_LOCATION "${ID3Tag_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_ID3Tag_CFLAGS_OTHER}" diff --git a/cmake/modules/FindJACK.cmake b/cmake/modules/FindJACK.cmake index 1bb3cd6ad7e..3699517eea3 100644 --- a/cmake/modules/FindJACK.cmake +++ b/cmake/modules/FindJACK.cmake @@ -26,13 +26,16 @@ if(PkgConfig_FOUND) pkg_check_modules(JACK jack) endif() -find_path(JACK_INCLUDE_DIR +find_path( + JACK_INCLUDE_DIR NAMES jack/jack.h HINTS ${PC_JACK_INCLUDE_DIRS} - DOC "JACK include directory") + DOC "JACK include directory" +) mark_as_advanced(JACK_INCLUDE_DIR) -find_library(JACK_LIBRARY +find_library( + JACK_LIBRARY NAMES jack HINTS ${PC_JACK_LIBRARY_DIRS} DOC "JACK library" @@ -40,10 +43,10 @@ find_library(JACK_LIBRARY mark_as_advanced(JACK_LIBRARY) if(WIN32) - # vcpkg provides CMake targets for pthreads4w - # This won't work if pthreads4w was built without vcpkg. - find_package(pthreads REQUIRED) - list(APPEND JACK_LINK_LIBRARIES PThreads4W::PThreads4W) + # vcpkg provides CMake targets for pthreads4w + # This won't work if pthreads4w was built without vcpkg. + find_package(pthreads REQUIRED) + list(APPEND JACK_LINK_LIBRARIES PThreads4W::PThreads4W) endif() if(DEFINED PC_JACK_VERSION AND NOT PC_JACK_VERSION STREQUAL "") @@ -55,7 +58,6 @@ find_package_handle_standard_args( JACK REQUIRED_VARS JACK_LIBRARY JACK_INCLUDE_DIR VERSION_VAR JACK_VERSION - ) if(JACK_FOUND) @@ -64,8 +66,9 @@ if(JACK_FOUND) set(JACK_DEFINITIONS ${PC_JACK_CFLAGS_OTHER}) if(NOT TARGET JACK::jack) - add_library(JACK::jack UNKNOWN IMPORTED) - set_target_properties(JACK::jack + add_library(JACK::jack UNKNOWN IMPORTED) + set_target_properties( + JACK::jack PROPERTIES IMPORTED_LOCATION "${JACK_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_JACK_CFLAGS_OTHER}" diff --git a/cmake/modules/FindKeyFinder.cmake b/cmake/modules/FindKeyFinder.cmake index d6c9fc230ee..717a414fef7 100644 --- a/cmake/modules/FindKeyFinder.cmake +++ b/cmake/modules/FindKeyFinder.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,13 +50,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_KeyFinder QUIET libKeyFinder>=2.0) endif() -find_path(KeyFinder_INCLUDE_DIR +find_path( + KeyFinder_INCLUDE_DIR NAMES keyfinder/keyfinder.h HINTS ${PC_KeyFinder_INCLUDE_DIRS} - DOC "KeyFinder include directory") + DOC "KeyFinder include directory" +) mark_as_advanced(KeyFinder_INCLUDE_DIR) -find_library(KeyFinder_LIBRARY +find_library( + KeyFinder_LIBRARY NAMES keyfinder HINTS ${PC_KeyFinder_LIBRARY_DIRS} DOC "KeyFinder library" @@ -81,7 +84,8 @@ if(KeyFinder_FOUND) if(NOT TARGET KeyFinder::KeyFinder) add_library(KeyFinder::KeyFinder UNKNOWN IMPORTED) - set_target_properties(KeyFinder::KeyFinder + set_target_properties( + KeyFinder::KeyFinder PROPERTIES IMPORTED_LOCATION "${KeyFinder_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_KeyFinder_CFLAGS_OTHER}" @@ -89,9 +93,11 @@ if(KeyFinder_FOUND) ) is_static_library(KeyFinder_IS_STATIC KeyFinder::KeyFinder) if(KeyFinder_IS_STATIC) - find_package(FFTW REQUIRED) - set_property(TARGET KeyFinder::KeyFinder APPEND PROPERTY INTERFACE_LINK_LIBRARIES - FFTW::FFTW + find_package(FFTW3 REQUIRED) + set_property( + TARGET KeyFinder::KeyFinder + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES FFTW3::fftw3 ) endif() endif() diff --git a/cmake/modules/FindLibUSB.cmake b/cmake/modules/FindLibUSB.cmake index 48aaea30f4d..c0e8ad30777 100644 --- a/cmake/modules/FindLibUSB.cmake +++ b/cmake/modules/FindLibUSB.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,7 +50,8 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_LibUSB QUIET libusb-1.0) endif() -find_path(LibUSB_INCLUDE_DIR +find_path( + LibUSB_INCLUDE_DIR NAMES libusb.h PATH_SUFFIXES libusb libusb-1.0 HINTS ${PC_LibUSB_INCLUDE_DIRS} @@ -58,7 +59,8 @@ find_path(LibUSB_INCLUDE_DIR ) mark_as_advanced(LibUSB_INCLUDE_DIR) -find_library(LibUSB_LIBRARY +find_library( + LibUSB_LIBRARY NAMES usb-1.0 usb HINTS ${PC_LibUSB_LIBRARY_DIRS} DOC "LibUSB library" @@ -83,7 +85,8 @@ if(LibUSB_FOUND) if(NOT TARGET LibUSB::LibUSB) add_library(LibUSB::LibUSB UNKNOWN IMPORTED) - set_target_properties(LibUSB::LibUSB + set_target_properties( + LibUSB::LibUSB PROPERTIES IMPORTED_LOCATION "${LibUSB_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_LibUSB_CFLAGS_OTHER}" @@ -94,8 +97,10 @@ if(LibUSB_FOUND) if(LibUSB_IS_STATIC) find_package(Libudev) if(Libudev_FOUND) - set_property(TARGET LibUSB::LibUSB APPEND PROPERTY INTERFACE_LINK_LIBRARIES - Libudev::Libudev + set_property( + TARGET LibUSB::LibUSB + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES Libudev::Libudev ) endif() endif() diff --git a/cmake/modules/FindLibudev.cmake b/cmake/modules/FindLibudev.cmake index 696d55e28f0..f87ccb32204 100644 --- a/cmake/modules/FindLibudev.cmake +++ b/cmake/modules/FindLibudev.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -24,15 +24,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Libudev QUIET libudev) endif() -find_path(Libudev_INCLUDE_DIR +find_path( + Libudev_INCLUDE_DIR NAMES libudev.h PATHS ${PC_Libudev_INCLUDE_DIRS} - DOC "The libudev include directory") + DOC "The libudev include directory" +) mark_as_advanced(Libudev_INCLUDE_DIR) -find_library(Libudev_LIBRARY - NAMES udev - PATH ${PC_Libudev_LIBRARY_DIRS} +find_library( + Libudev_LIBRARY + NAMES udev PATH ${PC_Libudev_LIBRARY_DIRS} DOC "The libudev library" ) mark_as_advanced(Libudev_LIBRARY) @@ -51,7 +53,8 @@ if(Libudev_FOUND) if(NOT TARGET Libudev::Libudev) add_library(Libudev::Libudev UNKNOWN IMPORTED) - set_target_properties(Libudev::Libudev + set_target_properties( + Libudev::Libudev PROPERTIES IMPORTED_LOCATION "${Libudev_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Libudev_CFLAGS_OTHER}" diff --git a/cmake/modules/FindMAD.cmake b/cmake/modules/FindMAD.cmake index a9d722c45df..2cfa029825d 100644 --- a/cmake/modules/FindMAD.cmake +++ b/cmake/modules/FindMAD.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,14 +48,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_MAD QUIET mad) endif() -find_path(MAD_INCLUDE_DIR +find_path( + MAD_INCLUDE_DIR NAMES mad.h HINTS ${PC_MAD_INCLUDE_DIRS} PATH_SUFFIXES mad - DOC "MAD include directory") + DOC "MAD include directory" +) mark_as_advanced(MAD_INCLUDE_DIR) -find_library(MAD_LIBRARY +find_library( + MAD_LIBRARY NAMES mad HINTS ${PC_MAD_LIBRARY_DIRS} DOC "MAD library" @@ -80,7 +83,8 @@ if(MAD_FOUND) if(NOT TARGET MAD::MAD) add_library(MAD::MAD UNKNOWN IMPORTED) - set_target_properties(MAD::MAD + set_target_properties( + MAD::MAD PROPERTIES IMPORTED_LOCATION "${MAD_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_MAD_CFLAGS_OTHER}" diff --git a/cmake/modules/FindMP4.cmake b/cmake/modules/FindMP4.cmake index d166ef99fda..7f813009e8e 100644 --- a/cmake/modules/FindMP4.cmake +++ b/cmake/modules/FindMP4.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,13 +48,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_MP4 QUIET mp4) endif() -find_path(MP4_INCLUDE_DIR +find_path( + MP4_INCLUDE_DIR NAMES mp4/mp4.h HINTS ${PC_MP4_INCLUDE_DIRS} - DOC "MP4 include directory") + DOC "MP4 include directory" +) mark_as_advanced(MP4_INCLUDE_DIR) -find_library(MP4_LIBRARY +find_library( + MP4_LIBRARY NAMES mp4 HINTS ${PC_MP4_LIBRARY_DIRS} DOC "MP4 library" @@ -79,7 +82,8 @@ if(MP4_FOUND) if(NOT TARGET MP4::MP4) add_library(MP4::MP4 UNKNOWN IMPORTED) - set_target_properties(MP4::MP4 + set_target_properties( + MP4::MP4 PROPERTIES IMPORTED_LOCATION "${MP4_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_MP4_CFLAGS_OTHER}" diff --git a/cmake/modules/FindMP4v2.cmake b/cmake/modules/FindMP4v2.cmake index c60bf765017..041fcc4ad0a 100644 --- a/cmake/modules/FindMP4v2.cmake +++ b/cmake/modules/FindMP4v2.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,13 +48,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_MP4v2 QUIET mp4v2) endif() -find_path(MP4v2_INCLUDE_DIR +find_path( + MP4v2_INCLUDE_DIR NAMES mp4v2/mp4v2.h HINTS ${PC_MP4v2_INCLUDE_DIRS} - DOC "MP4v2 include directory") + DOC "MP4v2 include directory" +) mark_as_advanced(MP4v2_INCLUDE_DIR) -find_library(MP4v2_LIBRARY +find_library( + MP4v2_LIBRARY NAMES mp4v2 HINTS ${PC_MP4v2_LIBRARY_DIRS} DOC "MP4v2 library" @@ -79,7 +82,8 @@ if(MP4v2_FOUND) if(NOT TARGET MP4v2::MP4v2) add_library(MP4v2::MP4v2 UNKNOWN IMPORTED) - set_target_properties(MP4v2::MP4v2 + set_target_properties( + MP4v2::MP4v2 PROPERTIES IMPORTED_LOCATION "${MP4v2_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_MP4v2_CFLAGS_OTHER}" diff --git a/cmake/modules/FindMediaFoundation.cmake b/cmake/modules/FindMediaFoundation.cmake index 0d8f47a8219..47b4a392c13 100644 --- a/cmake/modules/FindMediaFoundation.cmake +++ b/cmake/modules/FindMediaFoundation.cmake @@ -3,18 +3,27 @@ # # MediaFoundation_LIBRARIES - List of libraries when using MediaFoundation # MediaFoundation_FOUND - True if MediaFoundation found +# +# lint_cmake: -package/consistency -IF (MSVC) - SET( MediaFoundation_LIBRARIES mf.lib mfplat.lib mfreadwrite.lib mfuuid.lib strmiids.lib ) - SET( MediaFoundation_FOUND true ) -ENDIF (MSVC) +if(MSVC) + set( + MediaFoundation_LIBRARIES + mf.lib + mfplat.lib + mfreadwrite.lib + mfuuid.lib + strmiids.lib + ) + set(MediaFoundation_FOUND true) +endif() -IF (MediaFoundation_FOUND) - IF (NOT MediaFoundation_FIND_QUIETLY) - MESSAGE(STATUS "Found MediaFoundation: ${MediaFoundation_LIBRARIES}") - ENDIF (NOT MediaFoundation_FIND_QUIETLY) -ELSE (MediaFoundation_FOUND) - IF (MediaFoundation_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find MediaFoundation") - ENDIF (MediaFoundation_FIND_REQUIRED) -ENDIF (MediaFoundation_FOUND) +if(MediaFoundation_FOUND) + if(NOT MediaFoundation_FIND_QUIETLY) + message(STATUS "Found MediaFoundation: ${MediaFoundation_LIBRARIES}") + endif() +else() + if(MediaFoundation_FIND_REQUIRED) + message(FATAL_ERROR "Could not find MediaFoundation") + endif() +endif() diff --git a/cmake/modules/FindModplug.cmake b/cmake/modules/FindModplug.cmake index d6230e840f6..5e7ad5ab0bf 100644 --- a/cmake/modules/FindModplug.cmake +++ b/cmake/modules/FindModplug.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,13 +48,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Modplug QUIET libmodplug) endif() -find_path(Modplug_INCLUDE_DIR +find_path( + Modplug_INCLUDE_DIR NAMES libmodplug/modplug.h HINTS ${PC_Modplug_INCLUDE_DIRS} - DOC "Modplug include directory") + DOC "Modplug include directory" +) mark_as_advanced(Modplug_INCLUDE_DIR) -find_library(Modplug_LIBRARY +find_library( + Modplug_LIBRARY NAMES modplug HINTS ${PC_Modplug_LIBRARY_DIRS} DOC "Modplug library" @@ -79,7 +82,8 @@ if(Modplug_FOUND) if(NOT TARGET Modplug::Modplug) add_library(Modplug::Modplug UNKNOWN IMPORTED) - set_target_properties(Modplug::Modplug + set_target_properties( + Modplug::Modplug PROPERTIES IMPORTED_LOCATION "${Modplug_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Modplug_CFLAGS_OTHER}" diff --git a/cmake/modules/FindOgg.cmake b/cmake/modules/FindOgg.cmake index a32d7042e44..ccce92a4010 100644 --- a/cmake/modules/FindOgg.cmake +++ b/cmake/modules/FindOgg.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -39,14 +39,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Ogg QUIET ogg) endif() -find_path(Ogg_INCLUDE_DIR +find_path( + Ogg_INCLUDE_DIR NAMES ogg/ogg.h HINTS ${PC_Ogg_INCLUDE_DIRS} DOC "Ogg include directory" ) mark_as_advanced(Ogg_INCLUDE_DIR) -find_library(Ogg_LIBRARY +find_library( + Ogg_LIBRARY NAMES ogg HINTS ${PC_Ogg_LIBRARY_DIRS} DOC "Ogg library" @@ -71,7 +73,8 @@ if(Ogg_FOUND) if(NOT TARGET Ogg::ogg) add_library(Ogg::ogg UNKNOWN IMPORTED) - set_target_properties(Ogg::ogg + set_target_properties( + Ogg::ogg PROPERTIES IMPORTED_LOCATION "${Ogg_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Ogg_CFLAGS_OTHER}" diff --git a/cmake/modules/FindOpus.cmake b/cmake/modules/FindOpus.cmake index f3ed9397081..c56ac38a719 100644 --- a/cmake/modules/FindOpus.cmake +++ b/cmake/modules/FindOpus.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -40,13 +40,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Opus QUIET opus) endif() -find_path(Opus_INCLUDE_DIR +find_path( + Opus_INCLUDE_DIR NAMES opus/opus.h HINTS ${PC_Opus_INCLUDE_DIRS} - DOC "Opus include directory") + DOC "Opus include directory" +) mark_as_advanced(Opus_INCLUDE_DIR) -find_library(Opus_LIBRARY +find_library( + Opus_LIBRARY NAMES opus HINTS ${PC_Opus_LIBRARY_DIRS} DOC "Opus library" @@ -71,7 +74,8 @@ if(Opus_FOUND) if(NOT TARGET Opus::Opus) add_library(Opus::Opus UNKNOWN IMPORTED) - set_target_properties(Opus::Opus + set_target_properties( + Opus::Opus PROPERTIES IMPORTED_LOCATION "${Opus_LIBRARIES}" INTERFACE_COMPILE_OPTIONS "${Opus_DEFINITIONS}" diff --git a/cmake/modules/FindOpusFile.cmake b/cmake/modules/FindOpusFile.cmake index 226a4711167..1abe9df8cb8 100644 --- a/cmake/modules/FindOpusFile.cmake +++ b/cmake/modules/FindOpusFile.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -46,14 +46,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_OpusFile QUIET opusfile) endif() -find_path(OpusFile_INCLUDE_DIR +find_path( + OpusFile_INCLUDE_DIR NAMES opusfile.h PATH_SUFFIXES opus HINTS ${PC_OpusFile_INCLUDE_DIRS} - DOC "Opusfile include directory") + DOC "Opusfile include directory" +) mark_as_advanced(OpusFile_INCLUDE_DIR) -find_library(OpusFile_LIBRARY +find_library( + OpusFile_LIBRARY NAMES opusfile HINTS ${PC_OpusFile_LIBRARY_DIRS} DOC "Opusfile library" @@ -78,7 +81,8 @@ if(OpusFile_FOUND) if(NOT TARGET OpusFile::OpusFile) add_library(OpusFile::OpusFile UNKNOWN IMPORTED) - set_target_properties(OpusFile::OpusFile + set_target_properties( + OpusFile::OpusFile PROPERTIES IMPORTED_LOCATION "${OpusFile_LIBRARIES}" INTERFACE_COMPILE_OPTIONS "${OpusFile_DEFINITIONS}" diff --git a/cmake/modules/FindPortAudio.cmake b/cmake/modules/FindPortAudio.cmake index 2c107b8b375..bc4508352a1 100644 --- a/cmake/modules/FindPortAudio.cmake +++ b/cmake/modules/FindPortAudio.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,19 +50,24 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_PortAudio QUIET portaudio-2.0) endif() -find_path(PortAudio_INCLUDE_DIR +find_path( + PortAudio_INCLUDE_DIR NAMES portaudio.h HINTS ${PC_PortAudio_INCLUDE_DIRS} - DOC "PortAudio include directory") + DOC "PortAudio include directory" +) mark_as_advanced(PortAudio_INCLUDE_DIR) # Temporary hack until https://github.com/PortAudio/portaudio/pull/635 is released. -find_path(PortAudio_ALSA_H +find_path( + PortAudio_ALSA_H NAMES pa_linux_alsa.h - HINTS ${PC_PortAudio_INCLUDE_DIRS}) + HINTS ${PC_PortAudio_INCLUDE_DIRS} +) mark_as_advanced(PortAudio_ALSA_H) -find_library(PortAudio_LIBRARY +find_library( + PortAudio_LIBRARY NAMES portaudio HINTS ${PC_PortAudio_LIBRARY_DIRS} DOC "PortAudio library" @@ -83,7 +88,8 @@ find_package_handle_standard_args( if(PortAudio_FOUND) if(NOT TARGET PortAudio::PortAudio) add_library(PortAudio::PortAudio UNKNOWN IMPORTED) - set_target_properties(PortAudio::PortAudio + set_target_properties( + PortAudio::PortAudio PROPERTIES IMPORTED_LOCATION "${PortAudio_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_PortAudio_CFLAGS_OTHER}" @@ -94,15 +100,19 @@ if(PortAudio_FOUND) if(PortAudio_ALSA_H) find_package(ALSA) if(ALSA_FOUND) - set_property(TARGET PortAudio::PortAudio APPEND PROPERTY INTERFACE_LINK_LIBRARIES - ALSA::ALSA + set_property( + TARGET PortAudio::PortAudio + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ALSA::ALSA ) endif() endif() find_package(JACK) if(JACK_FOUND) - set_property(TARGET PortAudio::PortAudio APPEND PROPERTY INTERFACE_LINK_LIBRARIES - JACK::jack + set_property( + TARGET PortAudio::PortAudio + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES JACK::jack ) endif() endif() diff --git a/cmake/modules/FindPortMidi.cmake b/cmake/modules/FindPortMidi.cmake index 84e9342f85e..aa2392c07e6 100644 --- a/cmake/modules/FindPortMidi.cmake +++ b/cmake/modules/FindPortMidi.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -39,28 +39,26 @@ The following cache variables may also be set: include(IsStaticLibrary) -find_path(PortMidi_INCLUDE_DIR +find_path( + PortMidi_INCLUDE_DIR NAMES portmidi.h PATH_SUFFIXES portmidi - DOC "PortMidi include directory") + DOC "PortMidi include directory" +) mark_as_advanced(PortMidi_INCLUDE_DIR) -find_path(PortTime_INCLUDE_DIR +find_path( + PortTime_INCLUDE_DIR NAMES porttime.h PATH_SUFFIXES portmidi porttime - DOC "PortTime include directory") + DOC "PortTime include directory" +) mark_as_advanced(PortTime_INCLUDE_DIR) -find_library(PortMidi_LIBRARY - NAMES portmidi portmidi_s - DOC "PortMidi library" -) +find_library(PortMidi_LIBRARY NAMES portmidi portmidi_s DOC "PortMidi library") mark_as_advanced(PortMidi_LIBRARY) -find_library(PortTime_LIBRARY - NAMES porttime - DOC "PortTime library" -) +find_library(PortTime_LIBRARY NAMES porttime DOC "PortTime library") mark_as_advanced(PortTime_LIBRARY) if(DEFINED PC_PortMidi_VERSION AND NOT PC_PortMidi_VERSION STREQUAL "") @@ -76,7 +74,8 @@ find_package_handle_standard_args( if(PortMidi_FOUND AND NOT TARGET PortMidi::portmidi) add_library(PortMidi::portmidi UNKNOWN IMPORTED) - set_target_properties(PortMidi::portmidi + set_target_properties( + PortMidi::portmidi PROPERTIES IMPORTED_LOCATION "${PortMidi_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_PortMidi_CFLAGS_OTHER}" @@ -89,7 +88,8 @@ if(PortMidi_FOUND AND NOT TARGET PortMidi::portmidi) if(PortTime_LIBRARY) if(NOT TARGET PortTime::porttime) add_library(PortTime::porttime UNKNOWN IMPORTED) - set_target_properties(PortTime::porttime + set_target_properties( + PortTime::porttime PROPERTIES IMPORTED_LOCATION "${PortTime_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_PortTime_CFLAGS_OTHER}" @@ -104,8 +104,10 @@ if(PortMidi_FOUND AND NOT TARGET PortMidi::portmidi) if(PortMidi_IS_STATIC) find_package(ALSA) if(ALSA_FOUND) - set_property(TARGET PortMidi::portmidi APPEND PROPERTY INTERFACE_LINK_LIBRARIES - ALSA::ALSA + set_property( + TARGET PortMidi::portmidi + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ALSA::ALSA ) endif() endif() diff --git a/cmake/modules/FindShoutidjc.cmake b/cmake/modules/FindShoutidjc.cmake index e38ce17cf6d..3109a56c1b5 100644 --- a/cmake/modules/FindShoutidjc.cmake +++ b/cmake/modules/FindShoutidjc.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,13 +50,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Shoutidjc QUIET shout-idjc) endif() -find_path(Shoutidjc_INCLUDE_DIR +find_path( + Shoutidjc_INCLUDE_DIR NAMES shoutidjc/shout.h HINTS ${PC_Shout_INCLUDE_DIRS} - DOC "Shout include directory") + DOC "Shout include directory" +) mark_as_advanced(Shoutidjc_INCLUDE_DIR) -find_library(Shoutidjc_LIBRARY +find_library( + Shoutidjc_LIBRARY NAMES shout-idjc HINTS ${PC_Shoutidjc_LIBRARY_DIRS} DOC "Shoutidjc library" @@ -82,7 +85,8 @@ if(Shoutidjc_FOUND) if(NOT TARGET Shoutidjc::Shoutidjc) add_library(Shoutidjc::Shoutidjc UNKNOWN IMPORTED) - set_target_properties(Shoutidjc::Shoutidjc + set_target_properties( + Shoutidjc::Shoutidjc PROPERTIES IMPORTED_LOCATION "${Shoutidjc_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Shoutidjc_CFLAGS_OTHER}" diff --git a/cmake/modules/FindSleef.cmake b/cmake/modules/FindSleef.cmake index ea412ae9b5d..6b573d81a5b 100644 --- a/cmake/modules/FindSleef.cmake +++ b/cmake/modules/FindSleef.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,49 +50,80 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Sleef QUIET sleef) endif() -find_path(Sleef_INCLUDE_DIR +find_path( + Sleef_INCLUDE_DIR NAMES sleef.h PATHS ${PC_sleef_INCLUDE_DIRS} - DOC "Sleef include directory") + DOC "Sleef include directory" +) mark_as_advanced(Sleef_INCLUDE_DIR) -find_library(Sleef_LIBRARY +find_library( + Sleef_LIBRARY NAMES ${PC_Sleef_LIBRARIES} - PATHS ${PC_Sleef_LIBRARY_DIRS}) + PATHS ${PC_Sleef_LIBRARY_DIRS} +) mark_as_advanced(Sleef_LIBRARY) -find_library(SleefDFT_LIBRARY - NAMES sleefdft - PATHS ${PC_Sleef_LIBRARY_DIRS}) +find_library(SleefDFT_LIBRARY NAMES sleefdft PATHS ${PC_Sleef_LIBRARY_DIRS}) mark_as_advanced(SleefDFT_LIBRARY) if(DEFINED PC_Sleef_VERSION AND NOT PC_Sleef_VERSION STREQUAL "") set(Sleef_VERSION "${PC_Sleef_VERSION}") else() - if (EXISTS "${sleef_INCLUDE_DIR}/sleef.h") + if(EXISTS "${sleef_INCLUDE_DIR}/sleef.h") file(READ "$sleef{SLEEF_INCLUDE_DIR}/sleef.h" SLEEF_FIND_HEADER_CONTENTS) set(SLEEF_MAJOR_PREFIX "#define SLEEF_VERSION_MAJOR ") set(SLEEF_MINOR_PREFIX "#define SLEEF_VERSION_MINOR ") set(SLEEF_PATCH_PREFIX "#define SLEEF_VERSION_PATCHLEVEL ") - string(REGEX MATCH "${SLEEF_MAJOR_PREFIX}[0-9]+" - SLEEF_MAJOR_VERSION "${SLEEF_FIND_HEADER_CONTENTS}") - string(REPLACE "${SLEEF_MAJOR_PREFIX}" "" SLEEF_MAJOR_VERSION - "${SLEEF_MAJOR_VERSION}") + string( + REGEX MATCH + "${SLEEF_MAJOR_PREFIX}[0-9]+" + SLEEF_MAJOR_VERSION + "${SLEEF_FIND_HEADER_CONTENTS}" + ) + string( + REPLACE + "${SLEEF_MAJOR_PREFIX}" + "" + SLEEF_MAJOR_VERSION + "${SLEEF_MAJOR_VERSION}" + ) - string(REGEX MATCH "${SLEEF_MINOR_PREFIX}[0-9]+" - SLEEF_MINOR_VERSION "${SLEEF_FIND_HEADER_CONTENTS}") - string(REPLACE "${SLEEF_MINOR_PREFIX}" "" SLEEF_MINOR_VERSION - "${SLEEF_MINOR_VERSION}") + string( + REGEX MATCH + "${SLEEF_MINOR_PREFIX}[0-9]+" + SLEEF_MINOR_VERSION + "${SLEEF_FIND_HEADER_CONTENTS}" + ) + string( + REPLACE + "${SLEEF_MINOR_PREFIX}" + "" + SLEEF_MINOR_VERSION + "${SLEEF_MINOR_VERSION}" + ) - string(REGEX MATCH "${SLEEF_PATCH_PREFIX}[0-9]+" - SLEEF_SUBMINOR_VERSION "${SLEEF_FIND_HEADER_CONTENTS}") - string(REPLACE "${SLEEF_PATCH_PREFIX}" "" SLEEF_SUBMINOR_VERSION - "${SLEEF_SUBMINOR_VERSION}") + string( + REGEX MATCH + "${SLEEF_PATCH_PREFIX}[0-9]+" + SLEEF_SUBMINOR_VERSION + "${SLEEF_FIND_HEADER_CONTENTS}" + ) + string( + REPLACE + "${SLEEF_PATCH_PREFIX}" + "" + SLEEF_SUBMINOR_VERSION + "${SLEEF_SUBMINOR_VERSION}" + ) - set(Sleef_VERSION - "${SLEEF_MAJOR_VERSION}.${SLEEF_MINOR_VERSION}.${SLEEF_SUBMINOR_VERSION}") + set( + Sleef_VERSION + "${SLEEF_MAJOR_VERSION}.${SLEEF_MINOR_VERSION}.${SLEEF_SUBMINOR_VERSION}" + ) endif() endif() @@ -100,7 +131,8 @@ include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Sleef REQUIRED_VARS Sleef_INCLUDE_DIR Sleef_LIBRARY - VERSION_VAR Sleef_VERSION) + VERSION_VAR Sleef_VERSION +) if(Sleef_FOUND) set(Sleef_LIBRARIES "${Sleef_LIBRARY}") @@ -109,7 +141,8 @@ if(Sleef_FOUND) if(SleefDFT_LIBRARY AND NOT TARGET Sleef::sleefdft) add_library(Sleef::sleefdft UNKNOWN IMPORTED) - set_target_properties(Sleef::sleefdft + set_target_properties( + Sleef::sleefdft PROPERTIES IMPORTED_LOCATION "${SleefDFT_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Sleef_CFLAGS_OTHER}" @@ -118,14 +151,18 @@ if(Sleef_FOUND) is_static_library(SleefDFT_IS_STATIC Sleef::sleefdft) if(SleefDFT_IS_STATIC) - set_property(TARGET Sleef::sleefdft APPEND PROPERTY INTERFACE_LINK_LIBRARIES - Sleef::sleef + set_property( + TARGET Sleef::sleefdft + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES Sleef::sleef ) find_package(OpenMP) if(OpenMP_CXX_FOUND) - set_property(TARGET Sleef::sleefdft APPEND PROPERTY INTERFACE_LINK_LIBRARIES - OpenMP::OpenMP_CXX + set_property( + TARGET Sleef::sleefdft + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES OpenMP::OpenMP_CXX ) endif() endif() @@ -133,7 +170,8 @@ if(Sleef_FOUND) if(NOT TARGET Sleef::sleef) add_library(Sleef::sleef UNKNOWN IMPORTED) - set_target_properties(Sleef::sleef + set_target_properties( + Sleef::sleef PROPERTIES IMPORTED_LOCATION "${Sleef_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Sleef_CFLAGS_OTHER}" diff --git a/cmake/modules/FindSndFile.cmake b/cmake/modules/FindSndFile.cmake index 4bf847b8c65..712da34c77f 100644 --- a/cmake/modules/FindSndFile.cmake +++ b/cmake/modules/FindSndFile.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,14 +50,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_SndFile QUIET sndfile) endif() -find_path(SndFile_INCLUDE_DIR +find_path( + SndFile_INCLUDE_DIR NAMES sndfile.h HINTS ${PC_SndFile_INCLUDE_DIRS} PATH_SUFFIXES sndfile - DOC "SndFile include directory") + DOC "SndFile include directory" +) mark_as_advanced(SndFile_INCLUDE_DIR) -find_library(SndFile_LIBRARY +find_library( + SndFile_LIBRARY NAMES sndfile sndfile-1 HINTS ${PC_SndFile_LIBRARY_DIRS} DOC "SndFile library" @@ -75,7 +78,12 @@ find_package_handle_standard_args( VERSION_VAR SndFile_VERSION ) -file(STRINGS "${SndFile_INCLUDE_DIR}/sndfile.h" SndFile_SUPPORTS_SET_COMPRESSION_LEVEL REGEX ".*SFC_SET_COMPRESSION_LEVEL.*") +file( + STRINGS + "${SndFile_INCLUDE_DIR}/sndfile.h" + SndFile_SUPPORTS_SET_COMPRESSION_LEVEL + REGEX ".*SFC_SET_COMPRESSION_LEVEL.*" +) if(SndFile_SUPPORTS_SET_COMPRESSION_LEVEL) set(SndFile_SUPPORTS_SET_COMPRESSION_LEVEL ON) else() @@ -90,7 +98,8 @@ if(SndFile_FOUND) if(NOT TARGET SndFile::sndfile) add_library(SndFile::sndfile UNKNOWN IMPORTED) - set_target_properties(SndFile::sndfile + set_target_properties( + SndFile::sndfile PROPERTIES IMPORTED_LOCATION "${SndFile_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_SndFile_CFLAGS_OTHER}" @@ -100,8 +109,10 @@ if(SndFile_FOUND) if(SndFile_IS_STATIC) find_package(FLAC) if(FLAC_FOUND) - set_property(TARGET SndFile::sndfile APPEND PROPERTY INTERFACE_LINK_LIBRARIES - FLAC::FLAC + set_property( + TARGET SndFile::sndfile + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES FLAC::FLAC ) endif() @@ -109,8 +120,10 @@ if(SndFile_FOUND) if(SndFile_VERSION VERSION_GREATER_EQUAL "1.1.0") find_package(mpg123 CONFIG) if(mpg123_FOUND) - set_property(TARGET SndFile::sndfile APPEND PROPERTY INTERFACE_LINK_LIBRARIES - MPG123::libmpg123 + set_property( + TARGET SndFile::sndfile + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES MPG123::libmpg123 ) endif() endif() diff --git a/cmake/modules/FindSoundTouch.cmake b/cmake/modules/FindSoundTouch.cmake index ce8aca9f490..e25e76c7310 100644 --- a/cmake/modules/FindSoundTouch.cmake +++ b/cmake/modules/FindSoundTouch.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,13 +48,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_SoundTouch QUIET soundtouch) endif() -find_path(SoundTouch_INCLUDE_DIR +find_path( + SoundTouch_INCLUDE_DIR NAMES soundtouch/SoundTouch.h HINTS ${PC_SoundTouch_INCLUDE_DIRS} - DOC "SoundTouch include directory") + DOC "SoundTouch include directory" +) mark_as_advanced(SoundTouch_INCLUDE_DIR) -find_library(SoundTouch_LIBRARY +find_library( + SoundTouch_LIBRARY NAMES SoundTouch HINTS ${PC_SoundTouch_LIBRARY_DIRS} DOC "SoundTouch library" @@ -66,8 +69,17 @@ if(DEFINED PC_SoundTouch_VERSION AND NOT PC_SoundTouch_VERSION STREQUAL "") set(SoundTouch_VERSION "${PC_SoundTouch_VERSION}") else() if(EXISTS "${SoundTouch_INCLUDE_DIR}/soundtouch/SoundTouch.h") - file(READ "${SoundTouch_INCLUDE_DIR}/soundtouch/SoundTouch.h" SoundTouch_H_CONTENTS) - string(REGEX MATCH "#define SOUNDTOUCH_VERSION[ \t]+\"([0-9]+\.[0-9]+\.[0-9]+)\"" _dummy "${SoundTouch_H_CONTENTS}") + file( + READ + "${SoundTouch_INCLUDE_DIR}/soundtouch/SoundTouch.h" + SoundTouch_H_CONTENTS + ) + string( + REGEX MATCH + "#define SOUNDTOUCH_VERSION[ \t]+\"([0-9]+\.[0-9]+\.[0-9]+)\"" + _dummy + "${SoundTouch_H_CONTENTS}" + ) set(SoundTouch_VERSION "${CMAKE_MATCH_1}") endif() endif() @@ -86,7 +98,8 @@ if(SoundTouch_FOUND) if(NOT TARGET SoundTouch::SoundTouch) add_library(SoundTouch::SoundTouch UNKNOWN IMPORTED) - set_target_properties(SoundTouch::SoundTouch + set_target_properties( + SoundTouch::SoundTouch PROPERTIES IMPORTED_LOCATION "${SoundTouch_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_SoundTouch_CFLAGS_OTHER}" diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index 42515ac569b..4314fcaef42 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -49,19 +49,25 @@ find_package(PkgConfig QUIET) if(PkgConfig_FOUND) if(UNIX AND NOT APPLE) # prioritize the taglib1 package introduced in https://www.archlinux.de/packages/extra/x86_64/taglib1 - set(ENV{PKG_CONFIG_PATH} "/usr/lib/taglib1/pkgconfig/:$ENV{PKG_CONFIG_PATH}") + set( + ENV{PKG_CONFIG_PATH} + "/usr/lib/taglib1/pkgconfig/:$ENV{PKG_CONFIG_PATH}" + ) endif() pkg_check_modules(PC_TagLib QUIET taglib) endif() -find_path(TagLib_INCLUDE_DIR +find_path( + TagLib_INCLUDE_DIR NAMES tag.h HINTS ${PC_TagLib_INCLUDE_DIRS} PATH_SUFFIXES taglib - DOC "TagLib include directory") + DOC "TagLib include directory" +) mark_as_advanced(TagLib_INCLUDE_DIR) -find_library(TagLib_LIBRARY +find_library( + TagLib_LIBRARY NAMES tag HINTS ${PC_TagLib_LIBRARY_DIRS} DOC "TagLib library" @@ -83,19 +89,39 @@ find_package_handle_standard_args( if(DEFINED PC_TagLib_VERSION AND NOT PC_TagLib_VERSION STREQUAL "") set(TagLib_VERSION "${PC_TagLib_VERSION}") else() - if (EXISTS "${TagLib_INCLUDE_DIR}/taglib.h") + if(EXISTS "${TagLib_INCLUDE_DIR}/taglib.h") file(READ "${TagLib_INCLUDE_DIR}/taglib.h" TagLib_H_CONTENTS) - string(REGEX MATCH "#define TAGLIB_MAJOR_VERSION ([0-9]+)" _dummy "${TagLib_H_CONTENTS}") + string( + REGEX MATCH + "#define TAGLIB_MAJOR_VERSION ([0-9]+)" + _dummy + "${TagLib_H_CONTENTS}" + ) set(TagLib_VERSION_MAJOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define TAGLIB_MINOR_VERSION ([0-9]+)" _dummy "${TagLib_H_CONTENTS}") + string( + REGEX MATCH + "#define TAGLIB_MINOR_VERSION ([0-9]+)" + _dummy + "${TagLib_H_CONTENTS}" + ) set(TagLib_VERSION_MINOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define TAGLIB_PATCH_VERSION ([0-9]+)" _dummy "${TagLib_H_CONTENTS}") + string( + REGEX MATCH + "#define TAGLIB_PATCH_VERSION ([0-9]+)" + _dummy + "${TagLib_H_CONTENTS}" + ) set(TagLib_VERSION_PATCH "${CMAKE_MATCH_1}") # Simply using if(NOT) does not work because 0 is a valid value, so compare to empty string. - if (NOT TagLib_VERSION_MAJOR STREQUAL "" AND - NOT TagLib_VERSION_MINOR STREQUAL "" AND - NOT TagLib_VERSION_PATCH STREQUAL "") - set(TagLib_VERSION "${TagLib_VERSION_MAJOR}.${TagLib_VERSION_MINOR}.${TagLib_VERSION_PATCH}") + if( + NOT TagLib_VERSION_MAJOR STREQUAL "" + AND NOT TagLib_VERSION_MINOR STREQUAL "" + AND NOT TagLib_VERSION_PATCH STREQUAL "" + ) + set( + TagLib_VERSION + "${TagLib_VERSION_MAJOR}.${TagLib_VERSION_MINOR}.${TagLib_VERSION_PATCH}" + ) endif() endif() endif() @@ -114,7 +140,8 @@ if(TagLib_FOUND) if(NOT TARGET TagLib::TagLib) add_library(TagLib::TagLib UNKNOWN IMPORTED) - set_target_properties(TagLib::TagLib + set_target_properties( + TagLib::TagLib PROPERTIES IMPORTED_LOCATION "${TagLib_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_TagLib_CFLAGS_OTHER}" @@ -123,8 +150,10 @@ if(TagLib_FOUND) is_static_library(Taglib_IS_STATIC TagLib::TagLib) if(Taglib_IS_STATIC) if(WIN32) - set_property(TARGET TagLib::TagLib APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS - TAGLIB_STATIC + set_property( + TARGET TagLib::TagLib + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS TAGLIB_STATIC ) endif() endif() diff --git a/cmake/modules/FindUpower.cmake b/cmake/modules/FindUpower.cmake index 909fc452b0d..d6b3134a782 100644 --- a/cmake/modules/FindUpower.cmake +++ b/cmake/modules/FindUpower.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,14 +48,17 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_Upower QUIET upower-glib) endif() -find_path(Upower_INCLUDE_DIR +find_path( + Upower_INCLUDE_DIR NAMES upower.h PATH_SUFFIXES upower-glib libupower-glib HINTS ${PC_Upower_INCLUDE_DIRS} - DOC "Upower include directory") + DOC "Upower include directory" +) mark_as_advanced(Upower_INCLUDE_DIR) -find_library(Upower_LIBRARY +find_library( + Upower_LIBRARY NAMES upower-glib HINTS ${PC_Upower_LIBRARY_DIRS} DOC "Upower library" @@ -80,7 +83,8 @@ if(Upower_FOUND) if(NOT TARGET Upower::Upower) add_library(Upower::Upower UNKNOWN IMPORTED) - set_target_properties(Upower::Upower + set_target_properties( + Upower::Upower PROPERTIES IMPORTED_LOCATION "${Upower_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_Upower_CFLAGS_OTHER}" diff --git a/cmake/modules/FindVorbis.cmake b/cmake/modules/FindVorbis.cmake index 59912054e7c..6380db1bb8c 100644 --- a/cmake/modules/FindVorbis.cmake +++ b/cmake/modules/FindVorbis.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -45,37 +45,38 @@ The following cache variables may also be set: include(IsStaticLibrary) -find_path(Vorbis_vorbis_INCLUDE_DIR +find_path( + Vorbis_vorbis_INCLUDE_DIR NAMES vorbis/codec.h DOC "Vorbis include directory" ) mark_as_advanced(Vorbis_vorbis_INCLUDE_DIR) -find_path(Vorbis_vorbisenc_INCLUDE_DIR +find_path( + Vorbis_vorbisenc_INCLUDE_DIR NAMES vorbis/vorbisenc.h DOC "Vorbisenc include directory" ) mark_as_advanced(Vorbis_vorbisenc_INCLUDE_DIR) -find_path(Vorbis_vorbisfile_INCLUDE_DIR +find_path( + Vorbis_vorbisfile_INCLUDE_DIR NAMES vorbis/vorbisfile.h DOC "Vorbisfile include directory" ) mark_as_advanced(Vorbis_vorbisfile_INCLUDE_DIR) -find_library(Vorbis_vorbis_LIBRARY - NAMES vorbis - DOC "Vorbis library") +find_library(Vorbis_vorbis_LIBRARY NAMES vorbis DOC "Vorbis library") mark_as_advanced(Vorbis_vorbis_LIBRARY) -find_library(Vorbis_vorbisenc_LIBRARY - NAMES vorbisenc - DOC "Vorbisenc library") +find_library(Vorbis_vorbisenc_LIBRARY NAMES vorbisenc DOC "Vorbisenc library") mark_as_advanced(Vorbis_vorbisenc_LIBRARY) -find_library(Vorbis_vorbisfile_LIBRARY +find_library( + Vorbis_vorbisfile_LIBRARY NAMES vorbisfile - DOC "Vorbisfile library") + DOC "Vorbisfile library" +) mark_as_advanced(Vorbis_vorbisfile_LIBRARY) if(NOT Vorbis_FIND_COMPONENTS) @@ -87,7 +88,8 @@ foreach(component ${Vorbis_FIND_COMPONENTS}) set(Vorbis_${component}_FOUND TRUE) if(NOT TARGET Vorbis::${component}) add_library(Vorbis::${component} UNKNOWN IMPORTED) - set_target_properties(Vorbis::${component} + set_target_properties( + Vorbis::${component} PROPERTIES IMPORTED_LOCATION "${Vorbis_${component}_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${Vorbis_${component}_INCLUDE_DIR}" @@ -99,9 +101,10 @@ foreach(component ${Vorbis_FIND_COMPONENTS}) endforeach() include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Vorbis - REQUIRED_VARS Vorbis_vorbis_INCLUDE_DIR Vorbis_vorbis_LIBRARY - HANDLE_COMPONENTS +find_package_handle_standard_args( + Vorbis + REQUIRED_VARS Vorbis_vorbis_INCLUDE_DIR Vorbis_vorbis_LIBRARY + HANDLE_COMPONENTS ) if(Vorbis_vorbis_FOUND) @@ -109,8 +112,10 @@ if(Vorbis_vorbis_FOUND) if(Vorbis_vorbis_IS_STATIC) find_package(Ogg) if(Ogg_FOUND) - set_property(TARGET Vorbis::vorbis APPEND PROPERTY INTERFACE_LINK_LIBRARIES - Ogg::ogg + set_property( + TARGET Vorbis::vorbis + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES Ogg::ogg ) endif() endif() diff --git a/cmake/modules/Findhidapi.cmake b/cmake/modules/Findhidapi.cmake index 2b178c38c1b..f934684a8a9 100644 --- a/cmake/modules/Findhidapi.cmake +++ b/cmake/modules/Findhidapi.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,14 +50,17 @@ if(PkgConfig_FOUND) pkg_search_module(PC_hidapi QUIET hidapi-libusb hidapi) endif() -find_path(hidapi_INCLUDE_DIR +find_path( + hidapi_INCLUDE_DIR NAMES hidapi.h HINTS ${PC_hidapi_INCLUDE_DIRS} PATH_SUFFIXES hidapi - DOC "hidapi include directory") + DOC "hidapi include directory" +) mark_as_advanced(hidapi_INCLUDE_DIR) -find_library(hidapi_LIBRARY +find_library( + hidapi_LIBRARY NAMES hidapi-libusb hidapi HINTS ${PC_hidapi_LIBRARY_DIRS} DOC "hidapi library" @@ -65,7 +68,8 @@ find_library(hidapi_LIBRARY mark_as_advanced(hidapi_LIBRARY) if(CMAKE_SYSTEM_NAME STREQUAL Linux) - find_library(hidapi-hidraw_LIBRARY + find_library( + hidapi-hidraw_LIBRARY NAMES hidapi-hidraw HINTS ${PC_hidapi_LIBRARY_DIRS} DOC "hidap-hidraw library" @@ -77,21 +81,41 @@ endif() if(DEFINED PC_hidapi_VERSION AND NOT PC_hidapi_VERSION STREQUAL "") set(hidapi_VERSION "${PC_hidapi_VERSION}") else() - if (EXISTS "${hidapi_LIBRARY}" AND EXISTS "${hidapi_INCLUDE_DIR}/hidapi.h") - file(READ "${hidapi_INCLUDE_DIR}/hidapi.h" hidapi_H_CONTENTS) - string(REGEX MATCH "#define HID_API_VERSION_MAJOR ([0-9]+)" _dummy "${hidapi_H_CONTENTS}") - set(hidapi_VERSION_MAJOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define HID_API_VERSION_MINOR ([0-9]+)" _dummy "${hidapi_H_CONTENTS}") - set(hidapi_VERSION_MINOR "${CMAKE_MATCH_1}") - string(REGEX MATCH "#define HID_API_VERSION_PATCH ([0-9]+)" _dummy "${hidapi_H_CONTENTS}") - set(hidapi_VERSION_PATCH "${CMAKE_MATCH_1}") - # hidapi_VERSION is only available starting with 0.10.0 - # Simply using if(NOT) does not work because 0 is a valid value, so compare to empty string. - if (NOT hidapi_VERSION_MAJOR STREQUAL "" AND - NOT hidapi_VERSION_MINOR STREQUAL "" AND - NOT hidapi_VERSION_PATCH STREQUAL "") - set(hidapi_VERSION "${hidapi_VERSION_MAJOR}.${hidapi_VERSION_MINOR}.${hidapi_VERSION_PATCH}") - endif() + if(EXISTS "${hidapi_LIBRARY}" AND EXISTS "${hidapi_INCLUDE_DIR}/hidapi.h") + file(READ "${hidapi_INCLUDE_DIR}/hidapi.h" hidapi_H_CONTENTS) + string( + REGEX MATCH + "#define HID_API_VERSION_MAJOR ([0-9]+)" + _dummy + "${hidapi_H_CONTENTS}" + ) + set(hidapi_VERSION_MAJOR "${CMAKE_MATCH_1}") + string( + REGEX MATCH + "#define HID_API_VERSION_MINOR ([0-9]+)" + _dummy + "${hidapi_H_CONTENTS}" + ) + set(hidapi_VERSION_MINOR "${CMAKE_MATCH_1}") + string( + REGEX MATCH + "#define HID_API_VERSION_PATCH ([0-9]+)" + _dummy + "${hidapi_H_CONTENTS}" + ) + set(hidapi_VERSION_PATCH "${CMAKE_MATCH_1}") + # hidapi_VERSION is only available starting with 0.10.0 + # Simply using if(NOT) does not work because 0 is a valid value, so compare to empty string. + if( + NOT hidapi_VERSION_MAJOR STREQUAL "" + AND NOT hidapi_VERSION_MINOR STREQUAL "" + AND NOT hidapi_VERSION_PATCH STREQUAL "" + ) + set( + hidapi_VERSION + "${hidapi_VERSION_MAJOR}.${hidapi_VERSION_MINOR}.${hidapi_VERSION_PATCH}" + ) + endif() endif() endif() @@ -109,34 +133,40 @@ if(hidapi_FOUND) if(NOT TARGET hidapi::hidapi) add_library(hidapi::hidapi UNKNOWN IMPORTED) - set_target_properties(hidapi::hidapi + set_target_properties( + hidapi::hidapi PROPERTIES IMPORTED_LOCATION "${hidapi_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_hidapi_CFLAGS_OTHER}" INTERFACE_INCLUDE_DIRECTORIES "${hidapi_INCLUDE_DIR}" ) - if(CMAKE_SYSTEM_NAME STREQUAL Linux) - add_library(hidapi::hidraw UNKNOWN IMPORTED) - set_target_properties(hidapi::hidraw - PROPERTIES - IMPORTED_LOCATION "${hidapi-hidraw_LIBRARY}" - INTERFACE_COMPILE_OPTIONS "${PC_hidapi_CFLAGS_OTHER}" - INTERFACE_INCLUDE_DIRECTORIES "${hidapi_INCLUDE_DIR}" + if(CMAKE_SYSTEM_NAME STREQUAL Linux) + add_library(hidapi::hidraw UNKNOWN IMPORTED) + set_target_properties( + hidapi::hidraw + PROPERTIES + IMPORTED_LOCATION "${hidapi-hidraw_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PC_hidapi_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${hidapi_INCLUDE_DIR}" ) find_package(Libudev) if(Libudev_FOUND) is_static_library(hidapi_IS_STATIC hidapi::hidapi) if(hidapi_IS_STATIC) - set_property(TARGET hidapi::hidapi APPEND PROPERTY INTERFACE_LINK_LIBRARIES - Libudev::Libudev + set_property( + TARGET hidapi::hidapi + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES Libudev::Libudev ) endif() is_static_library(hidapi-hidraw_IS_STATIC hidapi::hidraw) if(hidapi-hidraw_IS_STATIC) - set_property(TARGET hidapi::hidraw APPEND PROPERTY INTERFACE_LINK_LIBRARIES - Libudev::Libudev + set_property( + TARGET hidapi::hidraw + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES Libudev::Libudev ) endif() endif() diff --git a/cmake/modules/Findlilv.cmake b/cmake/modules/Findlilv.cmake index 422e5ae0235..4365fd5df27 100644 --- a/cmake/modules/Findlilv.cmake +++ b/cmake/modules/Findlilv.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -50,7 +50,8 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_lilv QUIET lilv-0 lv2) endif() -find_path(lilv_INCLUDE_DIR +find_path( + lilv_INCLUDE_DIR NAMES lilv/lilv.h PATH_SUFFIXES lilv-0 HINTS ${PC_lilv_INCLUDE_DIRS} @@ -58,7 +59,8 @@ find_path(lilv_INCLUDE_DIR ) mark_as_advanced(lilv_INCLUDE_DIR) -find_library(lilv_LIBRARY +find_library( + lilv_LIBRARY NAMES lilv-0 lilv HINTS ${PC_lilv_LIBRARY_DIRS} DOC "lilv library" @@ -83,7 +85,8 @@ if(lilv_FOUND) if(NOT TARGET lilv::lilv) add_library(lilv::lilv UNKNOWN IMPORTED) - set_target_properties(lilv::lilv + set_target_properties( + lilv::lilv PROPERTIES IMPORTED_LOCATION "${lilv_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_lilv_CFLAGS_OTHER}" @@ -92,8 +95,10 @@ if(lilv_FOUND) is_static_library(lilv_IS_STATIC lilv::lilv) if(lilv_IS_STATIC) find_package(sord CONFIG REQUIRED) - set_property(TARGET lilv::lilv APPEND PROPERTY INTERFACE_LINK_LIBRARIES - sord::sord + set_property( + TARGET lilv::lilv + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES sord::sord ) endif() endif() diff --git a/cmake/modules/Findmp3lame.cmake b/cmake/modules/Findmp3lame.cmake index c204fe21d49..e6e2467bd35 100644 --- a/cmake/modules/Findmp3lame.cmake +++ b/cmake/modules/Findmp3lame.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -41,15 +41,10 @@ The following cache variables may also be set: #]=======================================================================] -find_path(mp3lame_INCLUDE_DIR - NAMES lame/lame.h - DOC "LAME include directory") +find_path(mp3lame_INCLUDE_DIR NAMES lame/lame.h DOC "LAME include directory") mark_as_advanced(mp3lame_INCLUDE_DIR) -find_library(mp3lame_LIBRARY - NAMES mp3lame mp3lame-static - DOC "LAME library" -) +find_library(mp3lame_LIBRARY NAMES mp3lame mp3lame-static DOC "LAME library") mark_as_advanced(mp3lame_LIBRARY) include(FindPackageHandleStandardArgs) @@ -66,7 +61,8 @@ if(mp3lame_FOUND) if(NOT TARGET mp3lame::mp3lame) add_library(mp3lame::mp3lame UNKNOWN IMPORTED) - set_target_properties(mp3lame::mp3lame + set_target_properties( + mp3lame::mp3lame PROPERTIES IMPORTED_LOCATION "${mp3lame_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${mp3lame_INCLUDE_DIR}" diff --git a/cmake/modules/Findrubberband.cmake b/cmake/modules/Findrubberband.cmake index a0c0da2786b..101ab00bcb4 100644 --- a/cmake/modules/Findrubberband.cmake +++ b/cmake/modules/Findrubberband.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,13 +48,16 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_rubberband QUIET rubberband) endif() -find_path(rubberband_INCLUDE_DIR +find_path( + rubberband_INCLUDE_DIR NAMES rubberband/RubberBandStretcher.h HINTS ${PC_rubberband_INCLUDE_DIRS} - DOC "rubberband include directory") + DOC "rubberband include directory" +) mark_as_advanced(rubberband_INCLUDE_DIR) -find_library(rubberband_LIBRARY +find_library( + rubberband_LIBRARY NAMES rubberband rubberband-library rubberband-dll HINTS ${PC_rubberband_LIBRARY_DIRS} DOC "rubberband library" @@ -79,7 +82,8 @@ if(rubberband_FOUND) if(NOT TARGET rubberband::rubberband) add_library(rubberband::rubberband UNKNOWN IMPORTED) - set_target_properties(rubberband::rubberband + set_target_properties( + rubberband::rubberband PROPERTIES IMPORTED_LOCATION "${rubberband_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_rubberband_CFLAGS_OTHER}" @@ -88,20 +92,25 @@ if(rubberband_FOUND) is_static_library(rubberband_IS_STATIC rubberband::rubberband) if(rubberband_IS_STATIC) find_library(SAMPLERATE_LIBRARY samplerate REQUIRED) - set_property(TARGET rubberband::rubberband APPEND PROPERTY INTERFACE_LINK_LIBRARIES - ${SAMPLERATE_LIBRARY} + set_property( + TARGET rubberband::rubberband + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ${SAMPLERATE_LIBRARY} ) - find_package(FFTW) - if (FFTW_FOUND) - set_property(TARGET rubberband::rubberband APPEND PROPERTY INTERFACE_LINK_LIBRARIES - FFTW::FFTW + find_package(FFTW3) + if(FFTW_FOUND) + set_property( + TARGET rubberband::rubberband + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES FFTW3::fftw3 ) endif() find_package(Sleef) - if (Sleef_FOUND) - set_property(TARGET rubberband::rubberband APPEND PROPERTY INTERFACE_LINK_LIBRARIES - Sleef::sleef - Sleef::sleefdft + if(Sleef_FOUND) + set_property( + TARGET rubberband::rubberband + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES Sleef::sleef Sleef::sleefdft ) endif() endif() diff --git a/cmake/modules/Findwavpack.cmake b/cmake/modules/Findwavpack.cmake index 1292ef2551f..187d2717803 100644 --- a/cmake/modules/Findwavpack.cmake +++ b/cmake/modules/Findwavpack.cmake @@ -1,5 +1,5 @@ # This file is part of Mixxx, Digital DJ'ing software. -# Copyright (C) 2001-2024 Mixxx Development Team +# Copyright (C) 2001-2025 Mixxx Development Team # Distributed under the GNU General Public Licence (GPL) version 2 or any later # later version. See the LICENSE file for details. @@ -48,14 +48,18 @@ if(PkgConfig_FOUND) pkg_check_modules(PC_wavpack QUIET wavpack) endif() -find_path(wavpack_INCLUDE_DIR +find_path( + wavpack_INCLUDE_DIR NAMES wavpack.h PATH_SUFFIXES wavpack HINTS ${PC_wavpack_INCLUDE_DIRS} - DOC "wavpack include directory") + DOC "wavpack include directory" +) mark_as_advanced(wavpack_INCLUDE_DIR) -find_library(wavpack_LIBRARY NAMES wavpack wv wavpackdll +find_library( + wavpack_LIBRARY + NAMES wavpack wv wavpackdll HINTS ${PC_wavpack_LIBRARY_DIRS} DOC "wavpack library" ) @@ -79,7 +83,8 @@ if(wavpack_FOUND) if(NOT TARGET WavPack::wavpack) add_library(WavPack::wavpack UNKNOWN IMPORTED) - set_target_properties(WavPack::wavpack + set_target_properties( + WavPack::wavpack PROPERTIES IMPORTED_LOCATION "${wavpack_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_wavpack_CFLAGS_OTHER}" diff --git a/cmake/modules/GitInfo.cmake b/cmake/modules/GitInfo.cmake index 0d3729fd052..6c881ff4db8 100644 --- a/cmake/modules/GitInfo.cmake +++ b/cmake/modules/GitInfo.cmake @@ -12,7 +12,7 @@ if(NOT GIT_DESCRIBE) else() message(NOTICE "Git describe: ${GIT_DESCRIBE}") string(REGEX MATCH "-modified$" GIT_DIRTY "${GIT_DESCRIBE}") - if (GIT_DIRTY) + if(GIT_DIRTY) message("Git worktree modified: yes") set(GIT_DIRTY 1) else() @@ -34,8 +34,13 @@ if(NOT GIT_DESCRIBE) message(NOTICE "Git branch: ${GIT_BRANCH}") endif() # Get the number of commits since the version tag - string(REGEX MATCH ".*-([0-9]*)-.*" GIT_COMMIT_COUNT_MATCH "${GIT_DESCRIBE}") - if (GIT_COMMIT_COUNT_MATCH) + string( + REGEX MATCH + ".*-([0-9]*)-.*" + GIT_COMMIT_COUNT_MATCH + "${GIT_DESCRIBE}" + ) + if(GIT_COMMIT_COUNT_MATCH) set(GIT_COMMIT_COUNT "${CMAKE_MATCH_1}") message(NOTICE "Git commit count: ${GIT_COMMIT_COUNT}") else() @@ -45,7 +50,10 @@ if(NOT GIT_DESCRIBE) else() message(NOTICE "Git describe: ${GIT_DESCRIBE}") if(NOT GIT_COMMIT_DATE) - message(NOTICE "Git commit date unknown, using current time for GIT_COMMIT_DATE") + message( + NOTICE + "Git commit date unknown, using current time for GIT_COMMIT_DATE" + ) # use current date in case of tar ball builds string(TIMESTAMP GIT_COMMIT_DATE "%Y-%m-%dT%H:%M:%SZ" UTC) endif() @@ -54,7 +62,7 @@ endif() # Get the current commit date if(NOT GIT_COMMIT_DATE) execute_process( - COMMAND git show --quiet --format=%cI --date=short + COMMAND git show --quiet --format=%cI --date=short --no-show-signature WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT_DATE OUTPUT_STRIP_TRAILING_WHITESPACE @@ -63,7 +71,10 @@ if(NOT GIT_COMMIT_DATE) endif() if(NOT GIT_COMMIT_DATE) - message(NOTICE "Git commit date unknown, using current time for GIT_COMMIT_DATE") + message( + NOTICE + "Git commit date unknown, using current time for GIT_COMMIT_DATE" + ) # use current date in case of tar ball builds string(TIMESTAMP GIT_COMMIT_DATE "%Y-%m-%dT%H:%M:%SZ" UTC) else() diff --git a/eslint.config.cjs b/eslint.config.cjs new file mode 100644 index 00000000000..a9f3a3f107a --- /dev/null +++ b/eslint.config.cjs @@ -0,0 +1,160 @@ + +const eslint = require("@eslint/js"); +const tseslint = require("typescript-eslint"); +const jsdoc = require("eslint-plugin-jsdoc"); +const diff = require("eslint-plugin-diff"); + +// https://github.com/paleite/eslint-plugin-diff/issues/47#issuecomment-2480733106 +const diffConfig = { + plugins: { + diff, + }, + processor: diff.processors.diff, +} + +module.exports = tseslint.config( + { + // these are autogenerated and thus should not be linted at all. + ignores: [ + "res/controllers/lodash.mixxx.js", + "res/controllers/Novation-Launchpad MK2-scripts.js", + "res/controllers/Novation-Launchpad Mini MK3-scripts.js", + "res/controllers/Novation-Launchpad-scripts.js", + ], + }, + { + languageOptions: { + ecmaVersion: 7, + sourceType: "script", + }, + }, + { + plugins: { + jsdoc, + } + }, + eslint.configs.recommended, + tseslint.configs.recommended, + jsdoc.configs['flat/recommended'], + diffConfig, + { + files: ["res/controllers/**/*"], + languageOptions: { + globals: { + // QJSEngine::ConsoleExtension, https://doc.qt.io/qt-6/qtquick-debugging.html#console-api + "console": "readonly", + // lodash, deprecated + "_": "readonly", + // Mixxx custom + "ColorMapper": "readonly", + "components": "readonly", + "engine": "readonly", + "midi": "readonly", + // common-controller-scripts globals + "print": "readonly", + "printObject": "readonly", + "stringifyObject": "readonly", + "arrayContains": "readonly", + "secondstominutes": "readonly", + "msecondstominutes": "readonly", + "colorCodeToObject": "readonly", + "colorCodeFromObject": "readonly", + "script": "readonly", + "bpm": "readonly", + "ButtonState": "readonly", + "LedState": "readonly", + "Controller": "readonly", + "Button": "readonly", + "Control": "readonly", + "Deck": "readonly" + } + } + }, + { + // vanilla rule config from previous `.eslintrc` + rules: { + "array-bracket-spacing": "warn", + "block-spacing": "warn", + + "brace-style": ["warn", "1tbs", { + allowSingleLine: true, + }], + + curly: "warn", + camelcase: "warn", + "comma-spacing": "warn", + + "computed-property-spacing": ["warn", "never", { + enforceForClassMembers: true, + }], + + "dot-location": ["warn", "property"], + "dot-notation": "warn", + eqeqeq: ["error", "always"], + "func-call-spacing": "warn", + + "func-style": ["error", "expression", { + allowArrowFunctions: true, + }], + + indent: ["warn", 4], + "key-spacing": "warn", + "keyword-spacing": "warn", + "linebreak-style": ["warn", "unix"], + "newline-per-chained-call": "warn", + "no-constructor-return": "warn", + "no-extra-bind": "warn", + "no-sequences": "warn", + "no-useless-call": "warn", + "no-useless-return": "warn", + "no-trailing-spaces": "warn", + + "no-unneeded-ternary": ["warn", { + defaultAssignment: false, + }], + + "no-var": "warn", + + "object-curly-newline": ["warn", { + consistent: true, + multiline: true, + }], + + "object-curly-spacing": "warn", + "prefer-const": "warn", + "prefer-regex-literals": "warn", + "prefer-template": "warn", + quotes: ["warn", "double"], + "require-atomic-updates": "error", + semi: "warn", + "semi-spacing": "warn", + "space-before-blocks": ["warn", "always"], + "space-before-function-paren": ["warn", "never"], + "space-in-parens": "warn", + yoda: "warn", + }, + }, + { + // special rules required for the typescript plugin + rules: { + // Note: you must disable the base rule as it can report incorrect errors + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { + argsIgnorePattern: "^_", + }], + // no need for jsdoc type annotation, the typescript syntax already requires it. + "jsdoc/require-param-type": "off", + "jsdoc/require-returns-type": "off", + // this is commonly needed in ComponentsJS + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-explicit-any": "off", + } + }, + { + // JSDoc specific stuff + rules: { + // the style here is too mixed currently, disable for now + "jsdoc/tag-lines": "off" + } + }, +); diff --git a/lib/hidapi/AUTHORS.txt b/lib/hidapi/AUTHORS.txt deleted file mode 100644 index 7193d71e0b0..00000000000 --- a/lib/hidapi/AUTHORS.txt +++ /dev/null @@ -1,18 +0,0 @@ - -HIDAPI Authors: - -Alan Ott : - Original Author and Maintainer - Linux, Windows, and Mac implementations - -Ludovic Rousseau : - Formatting for Doxygen documentation - Bug fixes - Correctness fixes - -libusb/hidapi Team: - Development/maintainance since June 4th 2019 - -For a comprehensive list of contributions, see the commit list at github: - https://github.com/libusb/hidapi/graphs/contributors - diff --git a/lib/hidapi/LICENSE-bsd.txt b/lib/hidapi/LICENSE-bsd.txt deleted file mode 100644 index 538cdf95cf6..00000000000 --- a/lib/hidapi/LICENSE-bsd.txt +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2010, Alan Ott, Signal 11 Software -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Signal 11 Software nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/hidapi/LICENSE-gpl3.txt b/lib/hidapi/LICENSE-gpl3.txt deleted file mode 100644 index 94a9ed024d3..00000000000 --- a/lib/hidapi/LICENSE-gpl3.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/lib/hidapi/LICENSE-orig.txt b/lib/hidapi/LICENSE-orig.txt deleted file mode 100644 index e3f33808299..00000000000 --- a/lib/hidapi/LICENSE-orig.txt +++ /dev/null @@ -1,9 +0,0 @@ - HIDAPI - Multi-Platform library for - communication with HID devices. - - Copyright 2009, Alan Ott, Signal 11 Software. - All Rights Reserved. - - This software may be used by anyone for any reason so - long as the copyright notice in the source files - remains intact. diff --git a/lib/hidapi/LICENSE.txt b/lib/hidapi/LICENSE.txt deleted file mode 100644 index e1676d4c42d..00000000000 --- a/lib/hidapi/LICENSE.txt +++ /dev/null @@ -1,13 +0,0 @@ -HIDAPI can be used under one of three licenses. - -1. The GNU General Public License, version 3.0, in LICENSE-gpl3.txt -2. A BSD-Style License, in LICENSE-bsd.txt. -3. The more liberal original HIDAPI license. LICENSE-orig.txt - -The license chosen is at the discretion of the user of HIDAPI. For example: -1. An author of GPL software would likely use HIDAPI under the terms of the -GPL. - -2. An author of commercial closed-source software would likely use HIDAPI -under the terms of the BSD-style license or the original HIDAPI license. - diff --git a/lib/hidapi/README.md b/lib/hidapi/README.md deleted file mode 100644 index c2f378f89a7..00000000000 --- a/lib/hidapi/README.md +++ /dev/null @@ -1,189 +0,0 @@ -## HIDAPI library for Windows, Linux, FreeBSD and macOS - -| CI instance | Status | -|----------------------|--------| -| `Linux/macOS/Windows master` | [![GitHub Builds](https://github.com/libusb/hidapi/workflows/GitHub%20Builds/badge.svg?branch=master)](https://github.com/libusb/hidapi/actions/workflows/builds.yml?query=branch%3Amaster) | -| `Windows master` | [![Build status](https://ci.appveyor.com/api/projects/status/xfmr5fo8w0re8ded/branch/master?svg=true)](https://ci.appveyor.com/project/libusb/hidapi/branch/master) | -| `Linux/BSD, last build (branch/PR)` | [![builds.sr.ht status](https://builds.sr.ht/~z3ntu/hidapi.svg)](https://builds.sr.ht/~z3ntu/hidapi) | - -HIDAPI is a multi-platform library which allows an application to interface -with USB and Bluetooth HID-Class devices on Windows, Linux, FreeBSD, and macOS. -HIDAPI can be either built as a shared library (`.so`, `.dll` or `.dylib`) or -can be embedded directly into a target application by adding a single source -file (per platform) and a single header. - -HIDAPI library was originally developed by Alan Ott ([signal11](https://github.com/signal11)). - -It was moved to [libusb/hidapi](https://github.com/libusb/hidapi) on June 4th, 2019, in order to merge important bugfixes and continue development of the library. - -## Table of Contents - -* [About](#about) - * [Test GUI](#test-gui) - * [Console Test App](#console-test-app) -* [What Does the API Look Like?](#what-does-the-api-look-like) -* [License](#license) -* [Installing HIDAPI](#installing-hidapi) -* [Build from Source](#build-from-source) - - -## About - -### HIDAPI has four back-ends: -* Windows (using `hid.dll`) -* Linux/hidraw (using the Kernel's hidraw driver) -* libusb (using libusb-1.0 - Linux/BSD/other UNIX-like systems) -* macOS (using IOHidManager) - -On Linux, either the hidraw or the libusb back-end can be used. There are -tradeoffs, and the functionality supported is slightly different. Both are -built by default. It is up to the application linking to hidapi to choose -the backend at link time by linking to either `libhidapi-libusb` or -`libhidapi-hidraw`. - -Note that you will need to install an udev rule file with your application -for unprivileged users to be able to access HID devices with hidapi. Refer -to the [69-hid.rules](udev/69-hid.rules) file in the `udev` directory -for an example. - -#### __Linux/hidraw__ (`linux/hid.c`): - -This back-end uses the hidraw interface in the Linux kernel, and supports -both USB and Bluetooth HID devices. It requires kernel version at least 2.6.39 -to build. In addition, it will only communicate with devices which have hidraw -nodes associated with them. -Keyboards, mice, and some other devices which are blacklisted from having -hidraw nodes will not work. Fortunately, for nearly all the uses of hidraw, -this is not a problem. - -#### __Linux/FreeBSD/libusb__ (`libusb/hid.c`): - -This back-end uses libusb-1.0 to communicate directly to a USB device. This -back-end will of course not work with Bluetooth devices. - -### Test GUI - -HIDAPI also comes with a Test GUI. The Test GUI is cross-platform and uses -Fox Toolkit . It will build on every platform -which HIDAPI supports. Since it relies on a 3rd party library, building it -is optional but it is useful when debugging hardware. - -NOTE: Test GUI based on Fox Toolkit is not actively developed nor supported -by HIDAPI team. It is kept as a historical artifact. It may even work sometime -or on some platforms, but it is not going to get any new features or bugfixes. - -Instructions for installing Fox-Toolkit on each platform is not provided. -Make sure to use Fox-Toolkit v1.6 if you choose to use it. - -### Console Test App - -If you want to play around with your HID device before starting -any development with HIDAPI and using a GUI app is not an option for you, you may try [`hidapitester`](https://github.com/todbot/hidapitester). - -This app has a console interface for most of the features supported -by HIDAPI library. - -## What Does the API Look Like? - -The API provides the most commonly used HID functions including sending -and receiving of input, output, and feature reports. The sample program, -which communicates with a heavily hacked up version of the Microchip USB -Generic HID sample looks like this (with error checking removed for -simplicity): - -**Warning: Only run the code you understand, and only when it conforms to the -device spec. Writing data (`hid_write`) at random to your HID devices can break them.** - -```c -#include // printf -#include // wprintf - -#include - -#define MAX_STR 255 - -int main(int argc, char* argv[]) -{ - int res; - unsigned char buf[65]; - wchar_t wstr[MAX_STR]; - hid_device *handle; - int i; - - // Initialize the hidapi library - res = hid_init(); - - // Open the device using the VID, PID, - // and optionally the Serial number. - handle = hid_open(0x4d8, 0x3f, NULL); - - // Read the Manufacturer String - res = hid_get_manufacturer_string(handle, wstr, MAX_STR); - wprintf(L"Manufacturer String: %s\n", wstr); - - // Read the Product String - res = hid_get_product_string(handle, wstr, MAX_STR); - wprintf(L"Product String: %s\n", wstr); - - // Read the Serial Number String - res = hid_get_serial_number_string(handle, wstr, MAX_STR); - wprintf(L"Serial Number String: (%d) %s\n", wstr[0], wstr); - - // Read Indexed String 1 - res = hid_get_indexed_string(handle, 1, wstr, MAX_STR); - wprintf(L"Indexed String 1: %s\n", wstr); - - // Toggle LED (cmd 0x80). The first byte is the report number (0x0). - buf[0] = 0x0; - buf[1] = 0x80; - res = hid_write(handle, buf, 65); - - // Request state (cmd 0x81). The first byte is the report number (0x0). - buf[0] = 0x0; - buf[1] = 0x81; - res = hid_write(handle, buf, 65); - - // Read requested state - res = hid_read(handle, buf, 65); - - // Print out the returned buffer. - for (i = 0; i < 4; i++) - printf("buf[%d]: %d\n", i, buf[i]); - - // Close the device - hid_close(handle); - - // Finalize the hidapi library - res = hid_exit(); - - return 0; -} -``` - -You can also use [hidtest/test.c](hidtest/test.c) -as a starting point for your applications. - - -## License - -HIDAPI may be used by one of three licenses as outlined in [LICENSE.txt](LICENSE.txt). - -## Installing HIDAPI - -If you want to build your own application that uses HID devices with HIDAPI, -you need to get HIDAPI development package. - -Depending on what your development environment is, HIDAPI likely to be provided -by your package manager. - -For instance on Ubuntu, HIDAPI is available via APT: -```sh -sudo apt install libhidapi-dev -``` - -HIDAPI package name for other systems/package managers may differ. -Check the documentation/package list of your package manager. - -## Build from Source - -Check [BUILD.md](BUILD.md) for details. diff --git a/lib/hidapi/VERSION b/lib/hidapi/VERSION deleted file mode 100644 index a8839f70de0..00000000000 --- a/lib/hidapi/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.11.2 \ No newline at end of file diff --git a/lib/hidapi/hidapi/hidapi.h b/lib/hidapi/hidapi/hidapi.h deleted file mode 100644 index 3a2dab46e25..00000000000 --- a/lib/hidapi/hidapi/hidapi.h +++ /dev/null @@ -1,498 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - Alan Ott - Signal 11 Software - - 8/22/2009 - - Copyright 2009, All Rights Reserved. - - At the discretion of the user of this library, - this software may be licensed under the terms of the - GNU General Public License v3, a BSD-Style license, or the - original HIDAPI license as outlined in the LICENSE.txt, - LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt - files located at the root of the source distribution. - These files may also be found in the public source - code repository located at: - https://github.com/libusb/hidapi . -********************************************************/ - -/** @file - * @defgroup API hidapi API - */ - -#ifndef HIDAPI_H__ -#define HIDAPI_H__ - -#include - -#ifdef _WIN32 - #define HID_API_EXPORT __declspec(dllexport) - #define HID_API_CALL -#else - #define HID_API_EXPORT /**< API export macro */ - #define HID_API_CALL /**< API call macro */ -#endif - -#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ - -/** @brief Static/compile-time major version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_MAJOR 0 -/** @brief Static/compile-time minor version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_MINOR 11 -/** @brief Static/compile-time patch version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_PATCH 2 - -/* Helper macros */ -#define HID_API_AS_STR_IMPL(x) #x -#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) -#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) - -/** @brief Static/compile-time string version of the library. - - @ingroup API -*/ -#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) - -#ifdef __cplusplus -extern "C" { -#endif - struct hid_api_version { - int major; - int minor; - int patch; - }; - - struct hid_device_; - typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ - - /** hidapi info structure */ - struct hid_device_info { - /** Platform-specific device path */ - char *path; - /** Device Vendor ID */ - unsigned short vendor_id; - /** Device Product ID */ - unsigned short product_id; - /** Serial Number */ - wchar_t *serial_number; - /** Device Release Number in binary-coded decimal, - also known as Device Version Number */ - unsigned short release_number; - /** Manufacturer String */ - wchar_t *manufacturer_string; - /** Product string */ - wchar_t *product_string; - /** Usage Page for this Device/Interface - (Windows/Mac/hidraw only) */ - unsigned short usage_page; - /** Usage for this Device/Interface - (Windows/Mac/hidraw only) */ - unsigned short usage; - /** The USB interface which this logical device - represents. - - * Valid on both Linux implementations in all cases. - * Valid on the Windows implementation only if the device - contains more than one interface. - * Valid on the Mac implementation if and only if the device - is a USB HID device. */ - int interface_number; - - /** Pointer to the next device */ - struct hid_device_info *next; - }; - - - /** @brief Initialize the HIDAPI library. - - This function initializes the HIDAPI library. Calling it is not - strictly necessary, as it will be called automatically by - hid_enumerate() and any of the hid_open_*() functions if it is - needed. This function should be called at the beginning of - execution however, if there is a chance of HIDAPI handles - being opened by different threads simultaneously. - - @ingroup API - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_init(void); - - /** @brief Finalize the HIDAPI library. - - This function frees all of the static data associated with - HIDAPI. It should be called at the end of execution to avoid - memory leaks. - - @ingroup API - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_exit(void); - - /** @brief Enumerate the HID Devices. - - This function returns a linked list of all the HID devices - attached to the system which match vendor_id and product_id. - If @p vendor_id is set to 0 then any vendor matches. - If @p product_id is set to 0 then any product matches. - If @p vendor_id and @p product_id are both set to 0, then - all HID devices will be returned. - - @ingroup API - @param vendor_id The Vendor ID (VID) of the types of device - to open. - @param product_id The Product ID (PID) of the types of - device to open. - - @returns - This function returns a pointer to a linked list of type - struct #hid_device_info, containing information about the HID devices - attached to the system, or NULL in the case of failure. Free - this linked list by calling hid_free_enumeration(). - */ - struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); - - /** @brief Free an enumeration Linked List - - This function frees a linked list created by hid_enumerate(). - - @ingroup API - @param devs Pointer to a list of struct_device returned from - hid_enumerate(). - */ - void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); - - /** @brief Open a HID device using a Vendor ID (VID), Product ID - (PID) and optionally a serial number. - - If @p serial_number is NULL, the first device with the - specified VID and PID is opened. - - This function sets the return value of hid_error(). - - @ingroup API - @param vendor_id The Vendor ID (VID) of the device to open. - @param product_id The Product ID (PID) of the device to open. - @param serial_number The Serial Number of the device to open - (Optionally NULL). - - @returns - This function returns a pointer to a #hid_device object on - success or NULL on failure. - */ - HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); - - /** @brief Open a HID device by its path name. - - The path name be determined by calling hid_enumerate(), or a - platform-specific path name can be used (eg: /dev/hidraw0 on - Linux). - - This function sets the return value of hid_error(). - - @ingroup API - @param path The path name of the device to open - - @returns - This function returns a pointer to a #hid_device object on - success or NULL on failure. - */ - HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); - - /** @brief Write an Output report to a HID device. - - The first byte of @p data[] must contain the Report ID. For - devices which only support a single report, this must be set - to 0x0. The remaining bytes contain the report data. Since - the Report ID is mandatory, calls to hid_write() will always - contain one more byte than the report contains. For example, - if a hid report is 16 bytes long, 17 bytes must be passed to - hid_write(), the Report ID (or 0x0, for devices with a - single report), followed by the report data (16 bytes). In - this example, the length passed in would be 17. - - hid_write() will send the data on the first OUT endpoint, if - one exists. If it does not, it will send the data through - the Control Endpoint (Endpoint 0). - - This function sets the return value of hid_error(). - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data The data to send, including the report number as - the first byte. - @param length The length in bytes of the data to send. - - @returns - This function returns the actual number of bytes written and - -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length); - - /** @brief Read an Input report from a HID device with timeout. - - Input reports are returned - to the host through the INTERRUPT IN endpoint. The first byte will - contain the Report number if the device uses numbered reports. - - This function sets the return value of hid_error(). - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data A buffer to put the read data into. - @param length The number of bytes to read. For devices with - multiple reports, make sure to read an extra byte for - the report number. - @param milliseconds timeout in milliseconds or -1 for blocking wait. - - @returns - This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read within - the timeout period, this function returns 0. - */ - int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); - - /** @brief Read an Input report from a HID device. - - Input reports are returned - to the host through the INTERRUPT IN endpoint. The first byte will - contain the Report number if the device uses numbered reports. - - This function sets the return value of hid_error(). - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data A buffer to put the read data into. - @param length The number of bytes to read. For devices with - multiple reports, make sure to read an extra byte for - the report number. - - @returns - This function returns the actual number of bytes read and - -1 on error. If no packet was available to be read and - the handle is in non-blocking mode, this function returns 0. - */ - int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length); - - /** @brief Set the device handle to be non-blocking. - - In non-blocking mode calls to hid_read() will return - immediately with a value of 0 if there is no data to be - read. In blocking mode, hid_read() will wait (block) until - there is data to read before returning. - - Nonblocking can be turned on and off at any time. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param nonblock enable or not the nonblocking reads - - 1 to enable nonblocking - - 0 to disable nonblocking. - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock); - - /** @brief Send a Feature report to the device. - - Feature reports are sent over the Control endpoint as a - Set_Report transfer. The first byte of @p data[] must - contain the Report ID. For devices which only support a - single report, this must be set to 0x0. The remaining bytes - contain the report data. Since the Report ID is mandatory, - calls to hid_send_feature_report() will always contain one - more byte than the report contains. For example, if a hid - report is 16 bytes long, 17 bytes must be passed to - hid_send_feature_report(): the Report ID (or 0x0, for - devices which do not use numbered reports), followed by the - report data (16 bytes). In this example, the length passed - in would be 17. - - This function sets the return value of hid_error(). - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data The data to send, including the report number as - the first byte. - @param length The length in bytes of the data to send, including - the report number. - - @returns - This function returns the actual number of bytes written and - -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length); - - /** @brief Get a feature report from a HID device. - - Set the first byte of @p data[] to the Report ID of the - report to be read. Make sure to allow space for this - extra byte in @p data[]. Upon return, the first byte will - still contain the Report ID, and the report data will - start in data[1]. - - This function sets the return value of hid_error(). - - @ingroup API - @param dev A device handle returned from hid_open(). - @param data A buffer to put the read data into, including - the Report ID. Set the first byte of @p data[] to the - Report ID of the report to be read, or set it to zero - if your device does not use numbered reports. - @param length The number of bytes to read, including an - extra byte for the report ID. The buffer can be longer - than the actual report. - - @returns - This function returns the number of bytes read plus - one for the report ID (which is still in the first - byte), or -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length); - - /** @brief Get a input report from a HID device. - - Set the first byte of @p data[] to the Report ID of the - report to be read. Make sure to allow space for this - extra byte in @p data[]. Upon return, the first byte will - still contain the Report ID, and the report data will - start in data[1]. - - @ingroup API - @param device A device handle returned from hid_open(). - @param data A buffer to put the read data into, including - the Report ID. Set the first byte of @p data[] to the - Report ID of the report to be read, or set it to zero - if your device does not use numbered reports. - @param length The number of bytes to read, including an - extra byte for the report ID. The buffer can be longer - than the actual report. - - @returns - This function returns the number of bytes read plus - one for the report ID (which is still in the first - byte), or -1 on error. - */ - int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length); - - /** @brief Close a HID device. - - This function sets the return value of hid_error(). - - @ingroup API - @param dev A device handle returned from hid_open(). - */ - void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev); - - /** @brief Get The Manufacturer String from a HID device. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen); - - /** @brief Get The Product String from a HID device. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen); - - /** @brief Get The Serial Number String from a HID device. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen); - - /** @brief Get a string from a HID device, based on its string index. - - @ingroup API - @param dev A device handle returned from hid_open(). - @param string_index The index of the string to get. - @param string A wide string buffer to put the data into. - @param maxlen The length of the buffer in multiples of wchar_t. - - @returns - This function returns 0 on success and -1 on error. - */ - int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen); - - /** @brief Get a string describing the last error which occurred. - - Whether a function sets the last error is noted in its - documentation. These functions will reset the last error - to NULL before their execution. - - Strings returned from hid_error() must not be freed by the user! - - This function is thread-safe, and error messages are thread-local. - - @ingroup API - @param dev A device handle returned from hid_open(), - or NULL to get the last non-device-specific error - (e.g. for errors in hid_open() itself). - - @returns - This function returns a string containing the last error - which occurred or NULL if none has occurred. - */ - HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev); - - /** @brief Get a runtime version of the library. - - @ingroup API - - @returns - Pointer to statically allocated struct, that contains version. - */ - HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void); - - - /** @brief Get a runtime version string of the library. - - @ingroup API - - @returns - Pointer to statically allocated string, that contains version string. - */ - HID_API_EXPORT const char* HID_API_CALL hid_version_str(void); - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/lib/hidapi/libusb/hid.c b/lib/hidapi/libusb/hid.c deleted file mode 100644 index c9e86c07ad8..00000000000 --- a/lib/hidapi/libusb/hid.c +++ /dev/null @@ -1,1689 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - Alan Ott - Signal 11 Software - - 8/22/2009 - Linux Version - 6/2/2010 - Libusb Version - 8/13/2010 - FreeBSD Version - 11/1/2011 - - Copyright 2009, All Rights Reserved. - - At the discretion of the user of this library, - this software may be licensed under the terms of the - GNU General Public License v3, a BSD-Style license, or the - original HIDAPI license as outlined in the LICENSE.txt, - LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt - files located at the root of the source distribution. - These files may also be found in the public source - code repository located at: - https://github.com/libusb/hidapi . -********************************************************/ - -#define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */ - -/* C */ -#include -#include -#include -#include -#include -#include - -/* Unix */ -#include -#include -#include -#include -#include -#include -#include -#include - -/* GNU / LibUSB */ -#include -#if !defined(__ANDROID__) && !defined(NO_ICONV) -#include -#endif - -#include "hidapi_libusb.h" - -#if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__ - -/* Barrier implementation because Android/Bionic don't have pthread_barrier. - This implementation came from Brent Priddy and was posted on - StackOverflow. It is used with his permission. */ -typedef int pthread_barrierattr_t; -typedef struct pthread_barrier { - pthread_mutex_t mutex; - pthread_cond_t cond; - int count; - int trip_count; -} pthread_barrier_t; - -static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) -{ - if(count == 0) { - errno = EINVAL; - return -1; - } - - if(pthread_mutex_init(&barrier->mutex, 0) < 0) { - return -1; - } - if(pthread_cond_init(&barrier->cond, 0) < 0) { - pthread_mutex_destroy(&barrier->mutex); - return -1; - } - barrier->trip_count = count; - barrier->count = 0; - - return 0; -} - -static int pthread_barrier_destroy(pthread_barrier_t *barrier) -{ - pthread_cond_destroy(&barrier->cond); - pthread_mutex_destroy(&barrier->mutex); - return 0; -} - -static int pthread_barrier_wait(pthread_barrier_t *barrier) -{ - pthread_mutex_lock(&barrier->mutex); - ++(barrier->count); - if(barrier->count >= barrier->trip_count) - { - barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); - pthread_mutex_unlock(&barrier->mutex); - return 1; - } - else - { - pthread_cond_wait(&barrier->cond, &(barrier->mutex)); - pthread_mutex_unlock(&barrier->mutex); - return 0; - } -} - -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef DEBUG_PRINTF -#define LOG(...) fprintf(stderr, __VA_ARGS__) -#else -#define LOG(...) do {} while (0) -#endif - -#ifndef __FreeBSD__ -#define DETACH_KERNEL_DRIVER -#endif - -/* Uncomment to enable the retrieval of Usage and Usage Page in -hid_enumerate(). Warning, on platforms different from FreeBSD -this is very invasive as it requires the detach -and re-attach of the kernel driver. See comments inside hid_enumerate(). -libusb HIDAPI programs are encouraged to use the interface number -instead to differentiate between interfaces on a composite HID device. */ -/*#define INVASIVE_GET_USAGE*/ - -/* Linked List of input reports received from the device. */ -struct input_report { - uint8_t *data; - size_t len; - struct input_report *next; -}; - - -struct hid_device_ { - /* Handle to the actual device. */ - libusb_device_handle *device_handle; - - /* Endpoint information */ - int input_endpoint; - int output_endpoint; - int input_ep_max_packet_size; - - /* The interface number of the HID */ - int interface; - - /* Indexes of Strings */ - int manufacturer_index; - int product_index; - int serial_index; - - /* Whether blocking reads are used */ - int blocking; /* boolean */ - - /* Read thread objects */ - pthread_t thread; - pthread_mutex_t mutex; /* Protects input_reports */ - pthread_cond_t condition; - pthread_barrier_t barrier; /* Ensures correct startup sequence */ - int shutdown_thread; - int transfer_loop_finished; - struct libusb_transfer *transfer; - - /* List of received input reports. */ - struct input_report *input_reports; - - /* Was kernel driver detached by libusb */ -#ifdef DETACH_KERNEL_DRIVER - int is_driver_detached; -#endif -}; - -static struct hid_api_version api_version = { - .major = HID_API_VERSION_MAJOR, - .minor = HID_API_VERSION_MINOR, - .patch = HID_API_VERSION_PATCH -}; - -static libusb_context *usb_context = NULL; - -uint16_t get_usb_code_for_current_locale(void); -static int return_data(hid_device *dev, unsigned char *data, size_t length); - -static hid_device *new_hid_device(void) -{ - hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); - dev->blocking = 1; - - pthread_mutex_init(&dev->mutex, NULL); - pthread_cond_init(&dev->condition, NULL); - pthread_barrier_init(&dev->barrier, NULL, 2); - - return dev; -} - -static void free_hid_device(hid_device *dev) -{ - /* Clean up the thread objects */ - pthread_barrier_destroy(&dev->barrier); - pthread_cond_destroy(&dev->condition); - pthread_mutex_destroy(&dev->mutex); - - /* Free the device itself */ - free(dev); -} - -#if 0 -/*TODO: Implement this function on hidapi/libusb.. */ -static void register_error(hid_device *dev, const char *op) -{ - -} -#endif - -#ifdef INVASIVE_GET_USAGE -/* Get bytes from a HID Report Descriptor. - Only call with a num_bytes of 0, 1, 2, or 4. */ -static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur) -{ - /* Return if there aren't enough bytes. */ - if (cur + num_bytes >= len) - return 0; - - if (num_bytes == 0) - return 0; - else if (num_bytes == 1) { - return rpt[cur+1]; - } - else if (num_bytes == 2) { - return (rpt[cur+2] * 256 + rpt[cur+1]); - } - else if (num_bytes == 4) { - return (rpt[cur+4] * 0x01000000 + - rpt[cur+3] * 0x00010000 + - rpt[cur+2] * 0x00000100 + - rpt[cur+1] * 0x00000001); - } - else - return 0; -} - -/* Retrieves the device's Usage Page and Usage from the report - descriptor. The algorithm is simple, as it just returns the first - Usage and Usage Page that it finds in the descriptor. - The return value is 0 on success and -1 on failure. */ -static int get_usage(uint8_t *report_descriptor, size_t size, - unsigned short *usage_page, unsigned short *usage) -{ - unsigned int i = 0; - int size_code; - int data_len, key_size; - int usage_found = 0, usage_page_found = 0; - - while (i < size) { - int key = report_descriptor[i]; - int key_cmd = key & 0xfc; - - //printf("key: %02hhx\n", key); - - if ((key & 0xf0) == 0xf0) { - /* This is a Long Item. The next byte contains the - length of the data section (value) for this key. - See the HID specification, version 1.11, section - 6.2.2.3, titled "Long Items." */ - if (i+1 < size) - data_len = report_descriptor[i+1]; - else - data_len = 0; /* malformed report */ - key_size = 3; - } - else { - /* This is a Short Item. The bottom two bits of the - key contain the size code for the data section - (value) for this key. Refer to the HID - specification, version 1.11, section 6.2.2.2, - titled "Short Items." */ - size_code = key & 0x3; - switch (size_code) { - case 0: - case 1: - case 2: - data_len = size_code; - break; - case 3: - data_len = 4; - break; - default: - /* Can't ever happen since size_code is & 0x3 */ - data_len = 0; - break; - }; - key_size = 1; - } - - if (key_cmd == 0x4) { - *usage_page = get_bytes(report_descriptor, size, data_len, i); - usage_page_found = 1; - //printf("Usage Page: %x\n", (uint32_t)*usage_page); - } - if (key_cmd == 0x8) { - *usage = get_bytes(report_descriptor, size, data_len, i); - usage_found = 1; - //printf("Usage: %x\n", (uint32_t)*usage); - } - - if (usage_page_found && usage_found) - return 0; /* success */ - - /* Skip over this key and it's associated data */ - i += data_len + key_size; - } - - return -1; /* failure */ -} -#endif /* INVASIVE_GET_USAGE */ - -#if defined(__FreeBSD__) && __FreeBSD__ < 10 -/* The libusb version included in FreeBSD < 10 doesn't have this function. In - mainline libusb, it's inlined in libusb.h. This function will bear a striking - resemblance to that one, because there's about one way to code it. - - Note that the data parameter is Unicode in UTF-16LE encoding. - Return value is the number of bytes in data, or LIBUSB_ERROR_*. - */ -static inline int libusb_get_string_descriptor(libusb_device_handle *dev, - uint8_t descriptor_index, uint16_t lang_id, - unsigned char *data, int length) -{ - return libusb_control_transfer(dev, - LIBUSB_ENDPOINT_IN | 0x0, /* Endpoint 0 IN */ - LIBUSB_REQUEST_GET_DESCRIPTOR, - (LIBUSB_DT_STRING << 8) | descriptor_index, - lang_id, data, (uint16_t) length, 1000); -} - -#endif - - -/* Get the first language the device says it reports. This comes from - USB string #0. */ -static uint16_t get_first_language(libusb_device_handle *dev) -{ - uint16_t buf[32]; - int len; - - /* Get the string from libusb. */ - len = libusb_get_string_descriptor(dev, - 0x0, /* String ID */ - 0x0, /* Language */ - (unsigned char*)buf, - sizeof(buf)); - if (len < 4) - return 0x0; - - return buf[1]; /* First two bytes are len and descriptor type. */ -} - -static int is_language_supported(libusb_device_handle *dev, uint16_t lang) -{ - uint16_t buf[32]; - int len; - int i; - - /* Get the string from libusb. */ - len = libusb_get_string_descriptor(dev, - 0x0, /* String ID */ - 0x0, /* Language */ - (unsigned char*)buf, - sizeof(buf)); - if (len < 4) - return 0x0; - - - len /= 2; /* language IDs are two-bytes each. */ - /* Start at index 1 because there are two bytes of protocol data. */ - for (i = 1; i < len; i++) { - if (buf[i] == lang) - return 1; - } - - return 0; -} - - -/* This function returns a newly allocated wide string containing the USB - device string numbered by the index. The returned string must be freed - by using free(). */ -static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) -{ - char buf[512]; - int len; - wchar_t *str = NULL; - -#if !defined(__ANDROID__) && !defined(NO_ICONV) /* we don't use iconv on Android, or when it is explicitly disabled */ - wchar_t wbuf[256]; - /* iconv variables */ - iconv_t ic; - size_t inbytes; - size_t outbytes; - size_t res; - char *inptr; - char *outptr; -#endif - - /* Determine which language to use. */ - uint16_t lang; - lang = get_usb_code_for_current_locale(); - if (!is_language_supported(dev, lang)) - lang = get_first_language(dev); - - /* Get the string from libusb. */ - len = libusb_get_string_descriptor(dev, - idx, - lang, - (unsigned char*)buf, - sizeof(buf)); - if (len < 0) - return NULL; - -#if defined(__ANDROID__) || defined(NO_ICONV) - - /* Bionic does not have iconv support nor wcsdup() function, so it - has to be done manually. The following code will only work for - code points that can be represented as a single UTF-16 character, - and will incorrectly convert any code points which require more - than one UTF-16 character. - - Skip over the first character (2-bytes). */ - len -= 2; - str = (wchar_t*) malloc((len / 2 + 1) * sizeof(wchar_t)); - int i; - for (i = 0; i < len / 2; i++) { - str[i] = buf[i * 2 + 2] | (buf[i * 2 + 3] << 8); - } - str[len / 2] = 0x00000000; - -#else - - /* buf does not need to be explicitly NULL-terminated because - it is only passed into iconv() which does not need it. */ - - /* Initialize iconv. */ - ic = iconv_open("WCHAR_T", "UTF-16LE"); - if (ic == (iconv_t)-1) { - LOG("iconv_open() failed\n"); - return NULL; - } - - /* Convert to native wchar_t (UTF-32 on glibc/BSD systems). - Skip the first character (2-bytes). */ - inptr = buf+2; - inbytes = len-2; - outptr = (char*) wbuf; - outbytes = sizeof(wbuf); - res = iconv(ic, &inptr, &inbytes, &outptr, &outbytes); - if (res == (size_t)-1) { - LOG("iconv() failed\n"); - goto err; - } - - /* Write the terminating NULL. */ - wbuf[sizeof(wbuf)/sizeof(wbuf[0])-1] = 0x00000000; - if (outbytes >= sizeof(wbuf[0])) - *((wchar_t*)outptr) = 0x00000000; - - /* Allocate and copy the string. */ - str = wcsdup(wbuf); - -err: - iconv_close(ic); - -#endif - - return str; -} - -static char *make_path(libusb_device *dev, int interface_number, int config_number) -{ - char str[64]; /* max length "000-000.000.000.000.000.000.000:000.000" */ - /* Note that USB3 port count limit is 7; use 8 here for alignment */ - uint8_t port_numbers[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - int num_ports = libusb_get_port_numbers(dev, port_numbers, 8); - - if (num_ports > 0) { - int n = snprintf(str, sizeof("000-000"), "%u-%u", libusb_get_bus_number(dev), port_numbers[0]); - for (uint8_t i = 1; i < num_ports; i++) { - n += snprintf(&str[n], sizeof(".000"), ".%u", port_numbers[i]); - } - n += snprintf(&str[n], sizeof(":000.000"), ":%u.%u", (uint8_t)config_number, (uint8_t)interface_number); - str[n] = '\0'; - } else { - /* USB3.0 specs limit number of ports to 7 and buffer size here is 8 */ - if (num_ports == LIBUSB_ERROR_OVERFLOW) { - LOG("make_path() failed. buffer overflow error\n"); - } else { - LOG("make_path() failed. unknown error\n"); - } - str[0] = '\0'; - } - return strdup(str); -} - -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() -{ - return &api_version; -} - -HID_API_EXPORT const char* HID_API_CALL hid_version_str() -{ - return HID_API_VERSION_STR; -} - -int HID_API_EXPORT hid_init(void) -{ - if (!usb_context) { - const char *locale; - - /* Init Libusb */ - if (libusb_init(&usb_context)) - return -1; - - /* Set the locale if it's not set. */ - locale = setlocale(LC_CTYPE, NULL); - if (!locale) - setlocale(LC_CTYPE, ""); - } - - return 0; -} - -int HID_API_EXPORT hid_exit(void) -{ - if (usb_context) { - libusb_exit(usb_context); - usb_context = NULL; - } - - return 0; -} - -struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) -{ - libusb_device **devs; - libusb_device *dev; - libusb_device_handle *handle; - ssize_t num_devs; - int i = 0; - - struct hid_device_info *root = NULL; /* return object */ - struct hid_device_info *cur_dev = NULL; - - if(hid_init() < 0) - return NULL; - - num_devs = libusb_get_device_list(usb_context, &devs); - if (num_devs < 0) - return NULL; - while ((dev = devs[i++]) != NULL) { - struct libusb_device_descriptor desc; - struct libusb_config_descriptor *conf_desc = NULL; - int j, k; - - int res = libusb_get_device_descriptor(dev, &desc); - unsigned short dev_vid = desc.idVendor; - unsigned short dev_pid = desc.idProduct; - - if ((vendor_id != 0x0 && vendor_id != dev_vid) || - (product_id != 0x0 && product_id != dev_pid)) { - continue; - } - - res = libusb_get_active_config_descriptor(dev, &conf_desc); - if (res < 0) - libusb_get_config_descriptor(dev, 0, &conf_desc); - if (conf_desc) { - for (j = 0; j < conf_desc->bNumInterfaces; j++) { - const struct libusb_interface *intf = &conf_desc->interface[j]; - for (k = 0; k < intf->num_altsetting; k++) { - const struct libusb_interface_descriptor *intf_desc; - intf_desc = &intf->altsetting[k]; - if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - int interface_num = intf_desc->bInterfaceNumber; - struct hid_device_info *tmp; - - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = make_path(dev, interface_num, conf_desc->bConfigurationValue); - - res = libusb_open(dev, &handle); - - if (res >= 0) { -#ifdef __ANDROID__ - /* There is (a potential) libusb Android backend, in which - device descriptor is not accurate up until the device is opened. - https://github.com/libusb/libusb/pull/874#discussion_r632801373 - A workaround is to re-read the descriptor again. - Even if it is not going to be accepted into libusb master, - having it here won't do any harm, since reading the device descriptor - is as cheap as copy 18 bytes of data. */ - libusb_get_device_descriptor(dev, &desc); -#endif - - /* Serial Number */ - if (desc.iSerialNumber > 0) - cur_dev->serial_number = - get_usb_string(handle, desc.iSerialNumber); - - /* Manufacturer and Product strings */ - if (desc.iManufacturer > 0) - cur_dev->manufacturer_string = - get_usb_string(handle, desc.iManufacturer); - if (desc.iProduct > 0) - cur_dev->product_string = - get_usb_string(handle, desc.iProduct); - -#ifdef INVASIVE_GET_USAGE -{ - /* - This section is removed because it is too - invasive on the system. Getting a Usage Page - and Usage requires parsing the HID Report - descriptor. Getting a HID Report descriptor - involves claiming the interface. Claiming the - interface involves detaching the kernel driver. - Detaching the kernel driver is hard on the system - because it will unclaim interfaces (if another - app has them claimed) and the re-attachment of - the driver will sometimes change /dev entry names. - It is for these reasons that this section is - #if 0. For composite devices, use the interface - field in the hid_device_info struct to distinguish - between interfaces. */ - unsigned char data[256]; -#ifdef DETACH_KERNEL_DRIVER - int detached = 0; - /* Usage Page and Usage */ - res = libusb_kernel_driver_active(handle, interface_num); - if (res == 1) { - res = libusb_detach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't detach kernel driver, even though a kernel driver was attached.\n"); - else - detached = 1; - } -#endif - res = libusb_claim_interface(handle, interface_num); - if (res >= 0) { - /* Get the HID Report Descriptor. */ - res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000); - if (res >= 0) { - unsigned short page=0, usage=0; - /* Parse the usage and usage page - out of the report descriptor. */ - get_usage(data, res, &page, &usage); - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - else - LOG("libusb_control_transfer() for getting the HID report failed with %d\n", res); - - /* Release the interface */ - res = libusb_release_interface(handle, interface_num); - if (res < 0) - LOG("Can't release the interface.\n"); - } - else - LOG("Can't claim interface %d\n", res); -#ifdef DETACH_KERNEL_DRIVER - /* Re-attach kernel driver if necessary. */ - if (detached) { - res = libusb_attach_kernel_driver(handle, interface_num); - if (res < 0) - LOG("Couldn't re-attach kernel driver.\n"); - } -#endif -} -#endif /* INVASIVE_GET_USAGE */ - - libusb_close(handle); - } - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Release Number */ - cur_dev->release_number = desc.bcdDevice; - - /* Interface Number */ - cur_dev->interface_number = interface_num; - } - } /* altsettings */ - } /* interfaces */ - libusb_free_config_descriptor(conf_desc); - } - } - - libusb_free_device_list(devs, 1); - - return root; -} - -void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) -{ - struct hid_device_info *d = devs; - while (d) { - struct hid_device_info *next = d->next; - free(d->path); - free(d->serial_number); - free(d->manufacturer_string); - free(d->product_string); - free(d); - d = next; - } -} - -hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) -{ - struct hid_device_info *devs, *cur_dev; - const char *path_to_open = NULL; - hid_device *handle = NULL; - - devs = hid_enumerate(vendor_id, product_id); - cur_dev = devs; - while (cur_dev) { - if (cur_dev->vendor_id == vendor_id && - cur_dev->product_id == product_id) { - if (serial_number) { - if (cur_dev->serial_number && - wcscmp(serial_number, cur_dev->serial_number) == 0) { - path_to_open = cur_dev->path; - break; - } - } - else { - path_to_open = cur_dev->path; - break; - } - } - cur_dev = cur_dev->next; - } - - if (path_to_open) { - /* Open the device */ - handle = hid_open_path(path_to_open); - } - - hid_free_enumeration(devs); - - return handle; -} - -static void read_callback(struct libusb_transfer *transfer) -{ - hid_device *dev = transfer->user_data; - int res; - - if (transfer->status == LIBUSB_TRANSFER_COMPLETED) { - - struct input_report *rpt = (struct input_report*) malloc(sizeof(*rpt)); - rpt->data = (uint8_t*) malloc(transfer->actual_length); - memcpy(rpt->data, transfer->buffer, transfer->actual_length); - rpt->len = transfer->actual_length; - rpt->next = NULL; - - pthread_mutex_lock(&dev->mutex); - - /* Attach the new report object to the end of the list. */ - if (dev->input_reports == NULL) { - /* The list is empty. Put it at the root. */ - dev->input_reports = rpt; - pthread_cond_signal(&dev->condition); - } - else { - /* Find the end of the list and attach. */ - struct input_report *cur = dev->input_reports; - int num_queued = 0; - while (cur->next != NULL) { - cur = cur->next; - num_queued++; - } - cur->next = rpt; - - /* Pop one off if we've reached 30 in the queue. This - way we don't grow forever if the user never reads - anything from the device. */ - if (num_queued > 30) { - return_data(dev, NULL, 0); - } - } - pthread_mutex_unlock(&dev->mutex); - } - else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { - dev->shutdown_thread = 1; - } - else if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) { - dev->shutdown_thread = 1; - } - else if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { - //LOG("Timeout (normal)\n"); - } - else { - LOG("Unknown transfer code: %d\n", transfer->status); - } - - if (dev->shutdown_thread) { - dev->transfer_loop_finished = 1; - return; - } - - /* Re-submit the transfer object. */ - res = libusb_submit_transfer(transfer); - if (res != 0) { - LOG("Unable to submit URB. libusb error code: %d\n", res); - dev->shutdown_thread = 1; - dev->transfer_loop_finished = 1; - } -} - - -static void *read_thread(void *param) -{ - hid_device *dev = param; - uint8_t *buf; - const size_t length = dev->input_ep_max_packet_size; - - /* Set up the transfer object. */ - buf = (uint8_t*) malloc(length); - dev->transfer = libusb_alloc_transfer(0); - libusb_fill_interrupt_transfer(dev->transfer, - dev->device_handle, - dev->input_endpoint, - buf, - length, - read_callback, - dev, - 5000/*timeout*/); - - /* Make the first submission. Further submissions are made - from inside read_callback() */ - libusb_submit_transfer(dev->transfer); - - /* Notify the main thread that the read thread is up and running. */ - pthread_barrier_wait(&dev->barrier); - - /* Handle all the events. */ - while (!dev->shutdown_thread) { - int res; - res = libusb_handle_events(usb_context); - if (res < 0) { - /* There was an error. */ - LOG("read_thread(): libusb reports error # %d\n", res); - - /* Break out of this loop only on fatal error.*/ - if (res != LIBUSB_ERROR_BUSY && - res != LIBUSB_ERROR_TIMEOUT && - res != LIBUSB_ERROR_OVERFLOW && - res != LIBUSB_ERROR_INTERRUPTED) { - dev->shutdown_thread = 1; - break; - } - } - } - - /* Cancel any transfer that may be pending. This call will fail - if no transfers are pending, but that's OK. */ - libusb_cancel_transfer(dev->transfer); - - while (!dev->transfer_loop_finished) - libusb_handle_events_completed(usb_context, &dev->transfer_loop_finished); - - /* Now that the read thread is stopping, Wake any threads which are - waiting on data (in hid_read_timeout()). Do this under a mutex to - make sure that a thread which is about to go to sleep waiting on - the condition actually will go to sleep before the condition is - signaled. */ - pthread_mutex_lock(&dev->mutex); - pthread_cond_broadcast(&dev->condition); - pthread_mutex_unlock(&dev->mutex); - - /* The dev->transfer->buffer and dev->transfer objects are cleaned up - in hid_close(). They are not cleaned up here because this thread - could end either due to a disconnect or due to a user - call to hid_close(). In both cases the objects can be safely - cleaned up after the call to pthread_join() (in hid_close()), but - since hid_close() calls libusb_cancel_transfer(), on these objects, - they can not be cleaned up here. */ - - return NULL; -} - - -static int hidapi_initialize_device(hid_device *dev, const struct libusb_interface_descriptor *intf_desc) -{ - int i =0; - int res = 0; - struct libusb_device_descriptor desc; - libusb_get_device_descriptor(libusb_get_device(dev->device_handle), &desc); - -#ifdef DETACH_KERNEL_DRIVER - /* Detach the kernel driver, but only if the - device is managed by the kernel */ - dev->is_driver_detached = 0; - if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) { - res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); - if (res < 0) { - LOG("Unable to detach Kernel Driver\n"); - return 0; - } - else { - dev->is_driver_detached = 1; - LOG("Driver successfully detached from kernel.\n"); - } - } -#endif - res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); - if (res < 0) { - LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); - return 0; - } - - /* Store off the string descriptor indexes */ - dev->manufacturer_index = desc.iManufacturer; - dev->product_index = desc.iProduct; - dev->serial_index = desc.iSerialNumber; - - /* Store off the interface number */ - dev->interface = intf_desc->bInterfaceNumber; - - dev->input_endpoint = 0; - dev->input_ep_max_packet_size = 0; - dev->output_endpoint = 0; - - /* Find the INPUT and OUTPUT endpoints. An - OUTPUT endpoint is not required. */ - for (i = 0; i < intf_desc->bNumEndpoints; i++) { - const struct libusb_endpoint_descriptor *ep - = &intf_desc->endpoint[i]; - - /* Determine the type and direction of this - endpoint. */ - int is_interrupt = - (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) - == LIBUSB_TRANSFER_TYPE_INTERRUPT; - int is_output = - (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_OUT; - int is_input = - (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) - == LIBUSB_ENDPOINT_IN; - - /* Decide whether to use it for input or output. */ - if (dev->input_endpoint == 0 && - is_interrupt && is_input) { - /* Use this endpoint for INPUT */ - dev->input_endpoint = ep->bEndpointAddress; - dev->input_ep_max_packet_size = ep->wMaxPacketSize; - } - if (dev->output_endpoint == 0 && - is_interrupt && is_output) { - /* Use this endpoint for OUTPUT */ - dev->output_endpoint = ep->bEndpointAddress; - } - } - - pthread_create(&dev->thread, NULL, read_thread, dev); - - /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); - return 1; -} - - -hid_device * HID_API_EXPORT hid_open_path(const char *path) -{ - hid_device *dev = NULL; - - libusb_device **devs = NULL; - libusb_device *usb_dev = NULL; - int res = 0; - int d = 0; - int good_open = 0; - - if(hid_init() < 0) - return NULL; - - dev = new_hid_device(); - - libusb_get_device_list(usb_context, &devs); - while ((usb_dev = devs[d++]) != NULL && !good_open) { - struct libusb_config_descriptor *conf_desc = NULL; - int j,k; - - if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0) - continue; - for (j = 0; j < conf_desc->bNumInterfaces && !good_open; j++) { - const struct libusb_interface *intf = &conf_desc->interface[j]; - for (k = 0; k < intf->num_altsetting && !good_open; k++) { - const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k]; - if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber, conf_desc->bConfigurationValue); - if (!strcmp(dev_path, path)) { - /* Matched Paths. Open this device */ - - /* OPEN HERE */ - res = libusb_open(usb_dev, &dev->device_handle); - if (res < 0) { - LOG("can't open device\n"); - free(dev_path); - break; - } - good_open = hidapi_initialize_device(dev, intf_desc); - if (!good_open) - libusb_close(dev->device_handle); - } - free(dev_path); - } - } - } - libusb_free_config_descriptor(conf_desc); - } - - libusb_free_device_list(devs, 1); - - /* If we have a good handle, return it. */ - if (good_open) { - return dev; - } - else { - /* Unable to open any devices. */ - free_hid_device(dev); - return NULL; - } -} - - -HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num) -{ -/* 0x01000107 is a LIBUSB_API_VERSION for 1.0.23 - version when libusb_wrap_sys_device was introduced */ -#if (!defined(HIDAPI_TARGET_LIBUSB_API_VERSION) || HIDAPI_TARGET_LIBUSB_API_VERSION >= 0x01000107) && (LIBUSB_API_VERSION >= 0x01000107) - hid_device *dev = NULL; - struct libusb_config_descriptor *conf_desc = NULL; - const struct libusb_interface_descriptor *selected_intf_desc = NULL; - int res = 0; - int j = 0, k = 0; - - if(hid_init() < 0) - return NULL; - - dev = new_hid_device(); - - res = libusb_wrap_sys_device(usb_context, sys_dev, &dev->device_handle); - if (res < 0) { - LOG("libusb_wrap_sys_device failed: %d %s\n", res, libusb_error_name(res)); - goto err; - } - - res = libusb_get_active_config_descriptor(libusb_get_device(dev->device_handle), &conf_desc); - if (res < 0) - libusb_get_config_descriptor(libusb_get_device(dev->device_handle), 0, &conf_desc); - - if (!conf_desc) { - LOG("Failed to get configuration descriptor: %d %s\n", res, libusb_error_name(res)); - goto err; - } - - /* find matching HID interface */ - for (j = 0; j < conf_desc->bNumInterfaces && !selected_intf_desc; j++) { - const struct libusb_interface *intf = &conf_desc->interface[j]; - for (k = 0; k < intf->num_altsetting; k++) { - const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k]; - if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { - if (interface_num < 0 || interface_num == intf_desc->bInterfaceNumber) { - selected_intf_desc = intf_desc; - break; - } - } - } - } - - if (!selected_intf_desc) { - if (interface_num < 0) { - LOG("Sys USB device doesn't contain a HID interface\n"); - } - else { - LOG("Sys USB device doesn't contain a HID interface with number %d\n", interface_num); - } - goto err; - } - - if (!hidapi_initialize_device(dev, selected_intf_desc)) - goto err; - - return dev; - -err: - if (conf_desc) - libusb_free_config_descriptor(conf_desc); - if (dev->device_handle) - libusb_close(dev->device_handle); - free_hid_device(dev); -#else - (void)sys_dev; - (void)interface_num; - LOG("libusb_wrap_sys_device is not available\n"); -#endif - return NULL; -} - - -int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) -{ - int res; - int report_number; - int skipped_report_id = 0; - - if (!data || (length ==0)) { - return -1; - } - - report_number = data[0]; - - if (report_number == 0x0) { - data++; - length--; - skipped_report_id = 1; - } - - - if (dev->output_endpoint <= 0) { - /* No interrupt out endpoint. Use the Control Endpoint */ - res = libusb_control_transfer(dev->device_handle, - LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, - 0x09/*HID Set_Report*/, - (2/*HID output*/ << 8) | report_number, - dev->interface, - (unsigned char *)data, length, - 1000/*timeout millis*/); - - if (res < 0) - return -1; - - if (skipped_report_id) - length++; - - return length; - } - else { - /* Use the interrupt out endpoint */ - int actual_length; - res = libusb_interrupt_transfer(dev->device_handle, - dev->output_endpoint, - (unsigned char*)data, - length, - &actual_length, 1000); - - if (res < 0) - return -1; - - if (skipped_report_id) - actual_length++; - - return actual_length; - } -} - -/* Helper function, to simplify hid_read(). - This should be called with dev->mutex locked. */ -static int return_data(hid_device *dev, unsigned char *data, size_t length) -{ - /* Copy the data out of the linked list item (rpt) into the - return buffer (data), and delete the liked list item. */ - struct input_report *rpt = dev->input_reports; - size_t len = (length < rpt->len)? length: rpt->len; - if (len > 0) - memcpy(data, rpt->data, len); - dev->input_reports = rpt->next; - free(rpt->data); - free(rpt); - return len; -} - -static void cleanup_mutex(void *param) -{ - hid_device *dev = param; - pthread_mutex_unlock(&dev->mutex); -} - - -int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) -{ -#if 0 - int transferred; - int res = libusb_interrupt_transfer(dev->device_handle, dev->input_endpoint, data, length, &transferred, 5000); - LOG("transferred: %d\n", transferred); - return transferred; -#endif - /* by initialising this variable right here, GCC gives a compilation warning/error: */ - /* error: variable ‘bytes_read’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] */ - int bytes_read; /* = -1; */ - - pthread_mutex_lock(&dev->mutex); - pthread_cleanup_push(&cleanup_mutex, dev); - - bytes_read = -1; - - /* There's an input report queued up. Return it. */ - if (dev->input_reports) { - /* Return the first one */ - bytes_read = return_data(dev, data, length); - goto ret; - } - - if (dev->shutdown_thread) { - /* This means the device has been disconnected. - An error code of -1 should be returned. */ - bytes_read = -1; - goto ret; - } - - if (milliseconds == -1) { - /* Blocking */ - while (!dev->input_reports && !dev->shutdown_thread) { - pthread_cond_wait(&dev->condition, &dev->mutex); - } - if (dev->input_reports) { - bytes_read = return_data(dev, data, length); - } - } - else if (milliseconds > 0) { - /* Non-blocking, but called with timeout. */ - int res; - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += milliseconds / 1000; - ts.tv_nsec += (milliseconds % 1000) * 1000000; - if (ts.tv_nsec >= 1000000000L) { - ts.tv_sec++; - ts.tv_nsec -= 1000000000L; - } - - while (!dev->input_reports && !dev->shutdown_thread) { - res = pthread_cond_timedwait(&dev->condition, &dev->mutex, &ts); - if (res == 0) { - if (dev->input_reports) { - bytes_read = return_data(dev, data, length); - break; - } - - /* If we're here, there was a spurious wake up - or the read thread was shutdown. Run the - loop again (ie: don't break). */ - } - else if (res == ETIMEDOUT) { - /* Timed out. */ - bytes_read = 0; - break; - } - else { - /* Error. */ - bytes_read = -1; - break; - } - } - } - else { - /* Purely non-blocking */ - bytes_read = 0; - } - -ret: - pthread_mutex_unlock(&dev->mutex); - pthread_cleanup_pop(0); - - return bytes_read; -} - -int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) -{ - return hid_read_timeout(dev, data, length, dev->blocking ? -1 : 0); -} - -int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) -{ - dev->blocking = !nonblock; - - return 0; -} - - -int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) -{ - int res = -1; - int skipped_report_id = 0; - int report_number = data[0]; - - if (report_number == 0x0) { - data++; - length--; - skipped_report_id = 1; - } - - res = libusb_control_transfer(dev->device_handle, - LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, - 0x09/*HID set_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, - (unsigned char *)data, length, - 1000/*timeout millis*/); - - if (res < 0) - return -1; - - /* Account for the report ID */ - if (skipped_report_id) - length++; - - return length; -} - -int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) -{ - int res = -1; - int skipped_report_id = 0; - int report_number = data[0]; - - if (report_number == 0x0) { - /* Offset the return buffer by 1, so that the report ID - will remain in byte 0. */ - data++; - length--; - skipped_report_id = 1; - } - res = libusb_control_transfer(dev->device_handle, - LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, - 0x01/*HID get_report*/, - (3/*HID feature*/ << 8) | report_number, - dev->interface, - (unsigned char *)data, length, - 1000/*timeout millis*/); - - if (res < 0) - return -1; - - if (skipped_report_id) - res++; - - return res; -} - -int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) -{ - int res = -1; - int skipped_report_id = 0; - int report_number = data[0]; - - if (report_number == 0x0) { - /* Offset the return buffer by 1, so that the report ID - will remain in byte 0. */ - data++; - length--; - skipped_report_id = 1; - } - res = libusb_control_transfer(dev->device_handle, - LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, - 0x01/*HID get_report*/, - (1/*HID Input*/ << 8) | report_number, - dev->interface, - (unsigned char *)data, length, - 1000/*timeout millis*/); - - if (res < 0) - return -1; - - if (skipped_report_id) - res++; - - return res; -} - -void HID_API_EXPORT hid_close(hid_device *dev) -{ - if (!dev) - return; - - /* Cause read_thread() to stop. */ - dev->shutdown_thread = 1; - libusb_cancel_transfer(dev->transfer); - - /* Wait for read_thread() to end. */ - pthread_join(dev->thread, NULL); - - /* Clean up the Transfer objects allocated in read_thread(). */ - free(dev->transfer->buffer); - libusb_free_transfer(dev->transfer); - - /* release the interface */ - libusb_release_interface(dev->device_handle, dev->interface); - - /* reattach the kernel driver if it was detached */ -#ifdef DETACH_KERNEL_DRIVER - if (dev->is_driver_detached) { - int res = libusb_attach_kernel_driver(dev->device_handle, dev->interface); - if (res < 0) - LOG("Failed to reattach the driver to kernel.\n"); - } -#endif - - /* Close the handle */ - libusb_close(dev->device_handle); - - /* Clear out the queue of received reports. */ - pthread_mutex_lock(&dev->mutex); - while (dev->input_reports) { - return_data(dev, NULL, 0); - } - pthread_mutex_unlock(&dev->mutex); - - free_hid_device(dev); -} - - -int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return hid_get_indexed_string(dev, dev->manufacturer_index, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return hid_get_indexed_string(dev, dev->product_index, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return hid_get_indexed_string(dev, dev->serial_index, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) -{ - wchar_t *str; - - str = get_usb_string(dev->device_handle, string_index); - if (str) { - wcsncpy(string, str, maxlen); - string[maxlen-1] = L'\0'; - free(str); - return 0; - } - else - return -1; -} - - -HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) -{ - (void)dev; - return L"hid_error is not implemented yet"; -} - - -struct lang_map_entry { - const char *name; - const char *string_code; - uint16_t usb_code; -}; - -#define LANG(name,code,usb_code) { name, code, usb_code } -static struct lang_map_entry lang_map[] = { - LANG("Afrikaans", "af", 0x0436), - LANG("Albanian", "sq", 0x041C), - LANG("Arabic - United Arab Emirates", "ar_ae", 0x3801), - LANG("Arabic - Bahrain", "ar_bh", 0x3C01), - LANG("Arabic - Algeria", "ar_dz", 0x1401), - LANG("Arabic - Egypt", "ar_eg", 0x0C01), - LANG("Arabic - Iraq", "ar_iq", 0x0801), - LANG("Arabic - Jordan", "ar_jo", 0x2C01), - LANG("Arabic - Kuwait", "ar_kw", 0x3401), - LANG("Arabic - Lebanon", "ar_lb", 0x3001), - LANG("Arabic - Libya", "ar_ly", 0x1001), - LANG("Arabic - Morocco", "ar_ma", 0x1801), - LANG("Arabic - Oman", "ar_om", 0x2001), - LANG("Arabic - Qatar", "ar_qa", 0x4001), - LANG("Arabic - Saudi Arabia", "ar_sa", 0x0401), - LANG("Arabic - Syria", "ar_sy", 0x2801), - LANG("Arabic - Tunisia", "ar_tn", 0x1C01), - LANG("Arabic - Yemen", "ar_ye", 0x2401), - LANG("Armenian", "hy", 0x042B), - LANG("Azeri - Latin", "az_az", 0x042C), - LANG("Azeri - Cyrillic", "az_az", 0x082C), - LANG("Basque", "eu", 0x042D), - LANG("Belarusian", "be", 0x0423), - LANG("Bulgarian", "bg", 0x0402), - LANG("Catalan", "ca", 0x0403), - LANG("Chinese - China", "zh_cn", 0x0804), - LANG("Chinese - Hong Kong SAR", "zh_hk", 0x0C04), - LANG("Chinese - Macau SAR", "zh_mo", 0x1404), - LANG("Chinese - Singapore", "zh_sg", 0x1004), - LANG("Chinese - Taiwan", "zh_tw", 0x0404), - LANG("Croatian", "hr", 0x041A), - LANG("Czech", "cs", 0x0405), - LANG("Danish", "da", 0x0406), - LANG("Dutch - Netherlands", "nl_nl", 0x0413), - LANG("Dutch - Belgium", "nl_be", 0x0813), - LANG("English - Australia", "en_au", 0x0C09), - LANG("English - Belize", "en_bz", 0x2809), - LANG("English - Canada", "en_ca", 0x1009), - LANG("English - Caribbean", "en_cb", 0x2409), - LANG("English - Ireland", "en_ie", 0x1809), - LANG("English - Jamaica", "en_jm", 0x2009), - LANG("English - New Zealand", "en_nz", 0x1409), - LANG("English - Philippines", "en_ph", 0x3409), - LANG("English - Southern Africa", "en_za", 0x1C09), - LANG("English - Trinidad", "en_tt", 0x2C09), - LANG("English - Great Britain", "en_gb", 0x0809), - LANG("English - United States", "en_us", 0x0409), - LANG("Estonian", "et", 0x0425), - LANG("Farsi", "fa", 0x0429), - LANG("Finnish", "fi", 0x040B), - LANG("Faroese", "fo", 0x0438), - LANG("French - France", "fr_fr", 0x040C), - LANG("French - Belgium", "fr_be", 0x080C), - LANG("French - Canada", "fr_ca", 0x0C0C), - LANG("French - Luxembourg", "fr_lu", 0x140C), - LANG("French - Switzerland", "fr_ch", 0x100C), - LANG("Gaelic - Ireland", "gd_ie", 0x083C), - LANG("Gaelic - Scotland", "gd", 0x043C), - LANG("German - Germany", "de_de", 0x0407), - LANG("German - Austria", "de_at", 0x0C07), - LANG("German - Liechtenstein", "de_li", 0x1407), - LANG("German - Luxembourg", "de_lu", 0x1007), - LANG("German - Switzerland", "de_ch", 0x0807), - LANG("Greek", "el", 0x0408), - LANG("Hebrew", "he", 0x040D), - LANG("Hindi", "hi", 0x0439), - LANG("Hungarian", "hu", 0x040E), - LANG("Icelandic", "is", 0x040F), - LANG("Indonesian", "id", 0x0421), - LANG("Italian - Italy", "it_it", 0x0410), - LANG("Italian - Switzerland", "it_ch", 0x0810), - LANG("Japanese", "ja", 0x0411), - LANG("Korean", "ko", 0x0412), - LANG("Latvian", "lv", 0x0426), - LANG("Lithuanian", "lt", 0x0427), - LANG("F.Y.R.O. Macedonia", "mk", 0x042F), - LANG("Malay - Malaysia", "ms_my", 0x043E), - LANG("Malay – Brunei", "ms_bn", 0x083E), - LANG("Maltese", "mt", 0x043A), - LANG("Marathi", "mr", 0x044E), - LANG("Norwegian - Bokml", "no_no", 0x0414), - LANG("Norwegian - Nynorsk", "no_no", 0x0814), - LANG("Polish", "pl", 0x0415), - LANG("Portuguese - Portugal", "pt_pt", 0x0816), - LANG("Portuguese - Brazil", "pt_br", 0x0416), - LANG("Raeto-Romance", "rm", 0x0417), - LANG("Romanian - Romania", "ro", 0x0418), - LANG("Romanian - Republic of Moldova", "ro_mo", 0x0818), - LANG("Russian", "ru", 0x0419), - LANG("Russian - Republic of Moldova", "ru_mo", 0x0819), - LANG("Sanskrit", "sa", 0x044F), - LANG("Serbian - Cyrillic", "sr_sp", 0x0C1A), - LANG("Serbian - Latin", "sr_sp", 0x081A), - LANG("Setsuana", "tn", 0x0432), - LANG("Slovenian", "sl", 0x0424), - LANG("Slovak", "sk", 0x041B), - LANG("Sorbian", "sb", 0x042E), - LANG("Spanish - Spain (Traditional)", "es_es", 0x040A), - LANG("Spanish - Argentina", "es_ar", 0x2C0A), - LANG("Spanish - Bolivia", "es_bo", 0x400A), - LANG("Spanish - Chile", "es_cl", 0x340A), - LANG("Spanish - Colombia", "es_co", 0x240A), - LANG("Spanish - Costa Rica", "es_cr", 0x140A), - LANG("Spanish - Dominican Republic", "es_do", 0x1C0A), - LANG("Spanish - Ecuador", "es_ec", 0x300A), - LANG("Spanish - Guatemala", "es_gt", 0x100A), - LANG("Spanish - Honduras", "es_hn", 0x480A), - LANG("Spanish - Mexico", "es_mx", 0x080A), - LANG("Spanish - Nicaragua", "es_ni", 0x4C0A), - LANG("Spanish - Panama", "es_pa", 0x180A), - LANG("Spanish - Peru", "es_pe", 0x280A), - LANG("Spanish - Puerto Rico", "es_pr", 0x500A), - LANG("Spanish - Paraguay", "es_py", 0x3C0A), - LANG("Spanish - El Salvador", "es_sv", 0x440A), - LANG("Spanish - Uruguay", "es_uy", 0x380A), - LANG("Spanish - Venezuela", "es_ve", 0x200A), - LANG("Southern Sotho", "st", 0x0430), - LANG("Swahili", "sw", 0x0441), - LANG("Swedish - Sweden", "sv_se", 0x041D), - LANG("Swedish - Finland", "sv_fi", 0x081D), - LANG("Tamil", "ta", 0x0449), - LANG("Tatar", "tt", 0X0444), - LANG("Thai", "th", 0x041E), - LANG("Turkish", "tr", 0x041F), - LANG("Tsonga", "ts", 0x0431), - LANG("Ukrainian", "uk", 0x0422), - LANG("Urdu", "ur", 0x0420), - LANG("Uzbek - Cyrillic", "uz_uz", 0x0843), - LANG("Uzbek – Latin", "uz_uz", 0x0443), - LANG("Vietnamese", "vi", 0x042A), - LANG("Xhosa", "xh", 0x0434), - LANG("Yiddish", "yi", 0x043D), - LANG("Zulu", "zu", 0x0435), - LANG(NULL, NULL, 0x0), -}; - -uint16_t get_usb_code_for_current_locale(void) -{ - char *locale; - char search_string[64]; - char *ptr; - struct lang_map_entry *lang; - - /* Get the current locale. */ - locale = setlocale(0, NULL); - if (!locale) - return 0x0; - - /* Make a copy of the current locale string. */ - strncpy(search_string, locale, sizeof(search_string)); - search_string[sizeof(search_string)-1] = '\0'; - - /* Chop off the encoding part, and make it lower case. */ - ptr = search_string; - while (*ptr) { - *ptr = tolower(*ptr); - if (*ptr == '.') { - *ptr = '\0'; - break; - } - ptr++; - } - - /* Find the entry which matches the string code of our locale. */ - lang = lang_map; - while (lang->string_code) { - if (!strcmp(lang->string_code, search_string)) { - return lang->usb_code; - } - lang++; - } - - /* There was no match. Find with just the language only. */ - /* Chop off the variant. Chop it off at the '_'. */ - ptr = search_string; - while (*ptr) { - *ptr = tolower(*ptr); - if (*ptr == '_') { - *ptr = '\0'; - break; - } - ptr++; - } - -#if 0 /* TODO: Do we need this? */ - /* Find the entry which matches the string code of our language. */ - lang = lang_map; - while (lang->string_code) { - if (!strcmp(lang->string_code, search_string)) { - return lang->usb_code; - } - lang++; - } -#endif - - /* Found nothing. */ - return 0x0; -} - -#ifdef __cplusplus -} -#endif diff --git a/lib/hidapi/libusb/hidapi_libusb.h b/lib/hidapi/libusb/hidapi_libusb.h deleted file mode 100644 index 1f56c2c51a9..00000000000 --- a/lib/hidapi/libusb/hidapi_libusb.h +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - libusb/hidapi Team - - Copyright 2021, All Rights Reserved. - - At the discretion of the user of this library, - this software may be licensed under the terms of the - GNU General Public License v3, a BSD-Style license, or the - original HIDAPI license as outlined in the LICENSE.txt, - LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt - files located at the root of the source distribution. - These files may also be found in the public source - code repository located at: - https://github.com/libusb/hidapi . -********************************************************/ - -/** @file - * @defgroup API hidapi API - */ - -#ifndef HIDAPI_LIBUSB_H__ -#define HIDAPI_LIBUSB_H__ - -#include - -#include "hidapi.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /** @brief Open a HID device using libusb_wrap_sys_device. - See https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#ga98f783e115ceff4eaf88a60e6439563c, - for details on libusb_wrap_sys_device. - - @ingroup API - @param sys_dev Platform-specific file descriptor that can be recognised by libusb. - @param interface_num USB interface number of the device to be used as HID interface. - Pass -1 to select first HID interface of the device. - - @returns - This function returns a pointer to a #hid_device object on - success or NULL on failure. - */ - HID_API_EXPORT hid_device * HID_API_CALL hid_libusb_wrap_sys_device(intptr_t sys_dev, int interface_num); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/hidapi/linux/hid.c b/lib/hidapi/linux/hid.c deleted file mode 100644 index c211fae20e4..00000000000 --- a/lib/hidapi/linux/hid.c +++ /dev/null @@ -1,1139 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - Alan Ott - Signal 11 Software - - 8/22/2009 - Linux Version - 6/2/2009 - - Copyright 2009, All Rights Reserved. - - At the discretion of the user of this library, - this software may be licensed under the terms of the - GNU General Public License v3, a BSD-Style license, or the - original HIDAPI license as outlined in the LICENSE.txt, - LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt - files located at the root of the source distribution. - These files may also be found in the public source - code repository located at: - https://github.com/libusb/hidapi . -********************************************************/ - -/* C */ -#include -#include -#include -#include -#include - -/* Unix */ -#include -#include -#include -#include -#include -#include -#include - -/* Linux */ -#include -#include -#include -#include - -#include "hidapi.h" - -#ifdef HIDAPI_ALLOW_BUILD_WORKAROUND_KERNEL_2_6_39 -/* This definitions first appeared in Linux Kernel 2.6.39 in linux/hidraw.h. - hidapi doesn't support kernels older than that, - so we don't define macros below explicitly, to fail builds on old kernels. - For those who really need this as a workaround (e.g. to be able to build on old build machines), - can workaround by defining the macro above. -*/ -#ifndef HIDIOCSFEATURE -#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len) -#endif -#ifndef HIDIOCGFEATURE -#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len) -#endif - -#endif - - -// HIDIOCGINPUT is not defined in Linux kernel headers < 5.11. -// This definition is from hidraw.h in Linux >= 5.11. -// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f43d3870cafa2a0f3854c1819c8385733db8f9ae -#ifndef HIDIOCGINPUT -#define HIDIOCGINPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len) -#endif - -/* USB HID device property names */ -const char *device_string_names[] = { - "manufacturer", - "product", - "serial", -}; - -/* Symbolic names for the properties above */ -enum device_string_id { - DEVICE_STRING_MANUFACTURER, - DEVICE_STRING_PRODUCT, - DEVICE_STRING_SERIAL, - - DEVICE_STRING_COUNT, -}; - -struct hid_device_ { - int device_handle; - int blocking; - int uses_numbered_reports; - wchar_t *last_error_str; -}; - -static struct hid_api_version api_version = { - .major = HID_API_VERSION_MAJOR, - .minor = HID_API_VERSION_MINOR, - .patch = HID_API_VERSION_PATCH -}; - -/* Global error message that is not specific to a device, e.g. for - hid_open(). It is thread-local like errno. */ -__thread wchar_t *last_global_error_str = NULL; - -static hid_device *new_hid_device(void) -{ - hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); - dev->device_handle = -1; - dev->blocking = 1; - dev->uses_numbered_reports = 0; - dev->last_error_str = NULL; - - return dev; -} - - -/* The caller must free the returned string with free(). */ -static wchar_t *utf8_to_wchar_t(const char *utf8) -{ - wchar_t *ret = NULL; - - if (utf8) { - size_t wlen = mbstowcs(NULL, utf8, 0); - if ((size_t) -1 == wlen) { - return wcsdup(L""); - } - ret = (wchar_t*) calloc(wlen+1, sizeof(wchar_t)); - mbstowcs(ret, utf8, wlen+1); - ret[wlen] = 0x0000; - } - - return ret; -} - - -/* Set the last global error to be reported by hid_error(NULL). - * The given error message will be copied (and decoded according to the - * currently locale, so do not pass in string constants). - * The last stored global error message is freed. - * Use register_global_error(NULL) to indicate "no error". */ -static void register_global_error(const char *msg) -{ - if (last_global_error_str) - free(last_global_error_str); - - last_global_error_str = utf8_to_wchar_t(msg); -} - -/* See register_global_error, but you can pass a format string into this function. */ -static void register_global_error_format(const char *format, ...) -{ - va_list args; - va_start(args, format); - - char msg[100]; - vsnprintf(msg, sizeof(msg), format, args); - - va_end(args); - - register_global_error(msg); -} - -/* Set the last error for a device to be reported by hid_error(device). - * The given error message will be copied (and decoded according to the - * currently locale, so do not pass in string constants). - * The last stored global error message is freed. - * Use register_device_error(device, NULL) to indicate "no error". */ -static void register_device_error(hid_device *dev, const char *msg) -{ - if (dev->last_error_str) - free(dev->last_error_str); - - dev->last_error_str = utf8_to_wchar_t(msg); -} - -/* See register_device_error, but you can pass a format string into this function. */ -static void register_device_error_format(hid_device *dev, const char *format, ...) -{ - va_list args; - va_start(args, format); - - char msg[100]; - vsnprintf(msg, sizeof(msg), format, args); - - va_end(args); - - register_device_error(dev, msg); -} - -/* Get an attribute value from a udev_device and return it as a whar_t - string. The returned string must be freed with free() when done.*/ -static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) -{ - return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name)); -} - -/* - * Gets the size of the HID item at the given position - * Returns 1 if successful, 0 if an invalid key - * Sets data_len and key_size when successful - */ -static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size) -{ - int key = report_descriptor[pos]; - int size_code; - - /* - * This is a Long Item. The next byte contains the - * length of the data section (value) for this key. - * See the HID specification, version 1.11, section - * 6.2.2.3, titled "Long Items." - */ - if ((key & 0xf0) == 0xf0) { - if (pos + 1 < size) - { - *data_len = report_descriptor[pos + 1]; - *key_size = 3; - return 1; - } - *data_len = 0; /* malformed report */ - *key_size = 0; - } - - /* - * This is a Short Item. The bottom two bits of the - * key contain the size code for the data section - * (value) for this key. Refer to the HID - * specification, version 1.11, section 6.2.2.2, - * titled "Short Items." - */ - size_code = key & 0x3; - switch (size_code) { - case 0: - case 1: - case 2: - *data_len = size_code; - *key_size = 1; - return 1; - case 3: - *data_len = 4; - *key_size = 1; - return 1; - default: - /* Can't ever happen since size_code is & 0x3 */ - *data_len = 0; - *key_size = 0; - break; - }; - - /* malformed report */ - return 0; -} - -/* uses_numbered_reports() returns 1 if report_descriptor describes a device - which contains numbered reports. */ -static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { - unsigned int i = 0; - int data_len, key_size; - - while (i < size) { - int key = report_descriptor[i]; - - /* Check for the Report ID key */ - if (key == 0x85/*Report ID*/) { - /* This device has a Report ID, which means it uses - numbered reports. */ - return 1; - } - - /* Determine data_len and key_size */ - if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) - return 0; /* malformed report */ - - /* Skip over this key and it's associated data */ - i += data_len + key_size; - } - - /* Didn't find a Report ID key. Device doesn't use numbered reports. */ - return 0; -} - -/* - * Get bytes from a HID Report Descriptor. - * Only call with a num_bytes of 0, 1, 2, or 4. - */ -static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur) -{ - /* Return if there aren't enough bytes. */ - if (cur + num_bytes >= len) - return 0; - - if (num_bytes == 0) - return 0; - else if (num_bytes == 1) - return rpt[cur + 1]; - else if (num_bytes == 2) - return (rpt[cur + 2] * 256 + rpt[cur + 1]); - else if (num_bytes == 4) - return ( - rpt[cur + 4] * 0x01000000 + - rpt[cur + 3] * 0x00010000 + - rpt[cur + 2] * 0x00000100 + - rpt[cur + 1] * 0x00000001 - ); - else - return 0; -} - -/* - * Retrieves the device's Usage Page and Usage from the report descriptor. - * The algorithm returns the current Usage Page/Usage pair whenever a new - * Collection is found and a Usage Local Item is currently in scope. - * Usage Local Items are consumed by each Main Item (See. 6.2.2.8). - * The algorithm should give similar results as Apple's: - * https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc - * Physical Collections are also matched (macOS does the same). - * - * This function can be called repeatedly until it returns non-0 - * Usage is found. pos is the starting point (initially 0) and will be updated - * to the next search position. - * - * The return value is 0 when a pair is found. - * 1 when finished processing descriptor. - * -1 on a malformed report. - */ -static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage) -{ - int data_len, key_size; - int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */ - int usage_pair_ready = 0; - - /* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */ - int usage_found = 0; - - while (*pos < size) { - int key = report_descriptor[*pos]; - int key_cmd = key & 0xfc; - - /* Determine data_len and key_size */ - if (!get_hid_item_size(report_descriptor, *pos, size, &data_len, &key_size)) - return -1; /* malformed report */ - - switch (key_cmd) { - case 0x4: /* Usage Page 6.2.2.7 (Global) */ - *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos); - break; - - case 0x8: /* Usage 6.2.2.8 (Local) */ - *usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos); - usage_found = 1; - break; - - case 0xa0: /* Collection 6.2.2.4 (Main) */ - /* A Usage Item (Local) must be found for the pair to be valid */ - if (usage_found) - usage_pair_ready = 1; - - /* Usage is a Local Item, unset it */ - usage_found = 0; - break; - - case 0x80: /* Input 6.2.2.4 (Main) */ - case 0x90: /* Output 6.2.2.4 (Main) */ - case 0xb0: /* Feature 6.2.2.4 (Main) */ - case 0xc0: /* End Collection 6.2.2.4 (Main) */ - /* Usage is a Local Item, unset it */ - usage_found = 0; - break; - } - - /* Skip over this key and it's associated data */ - *pos += data_len + key_size; - - /* Return usage pair */ - if (usage_pair_ready) - return 0; - } - - /* If no top-level application collection is found and usage page/usage pair is found, pair is valid - https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */ - if (initial && usage_found) - return 0; /* success */ - - return 1; /* finished processing */ -} - -/* - * Retrieves the hidraw report descriptor from a file. - * When using this form, /device/report_descriptor, elevated priviledges are not required. - */ -static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc) -{ - int rpt_handle; - ssize_t res; - - rpt_handle = open(rpt_path, O_RDONLY); - if (rpt_handle < 0) { - register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); - return -1; - } - - /* - * Read in the Report Descriptor - * The sysfs file has a maximum size of 4096 (which is the same as HID_MAX_DESCRIPTOR_SIZE) so we should always - * be ok when reading the descriptor. - * In practice if the HID descriptor is any larger I suspect many other things will break. - */ - memset(rpt_desc, 0x0, sizeof(*rpt_desc)); - res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE); - if (res < 0) { - register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno)); - } - rpt_desc->size = (__u32) res; - - close(rpt_handle); - return (int) res; -} - -static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) -{ - int res = -1; - /* Construct /device/report_descriptor */ - size_t rpt_path_len = strlen(sysfs_path) + 25 + 1; - char* rpt_path = (char*) calloc(1, rpt_path_len); - snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); - - res = get_hid_report_descriptor(rpt_path, rpt_desc); - free(rpt_path); - - return res; -} - -/* - * The caller is responsible for free()ing the (newly-allocated) character - * strings pointed to by serial_number_utf8 and product_name_utf8 after use. - */ -static int -parse_uevent_info(const char *uevent, unsigned *bus_type, - unsigned short *vendor_id, unsigned short *product_id, - char **serial_number_utf8, char **product_name_utf8) -{ - char *tmp = strdup(uevent); - char *saveptr = NULL; - char *line; - char *key; - char *value; - - int found_id = 0; - int found_serial = 0; - int found_name = 0; - - line = strtok_r(tmp, "\n", &saveptr); - while (line != NULL) { - /* line: "KEY=value" */ - key = line; - value = strchr(line, '='); - if (!value) { - goto next_line; - } - *value = '\0'; - value++; - - if (strcmp(key, "HID_ID") == 0) { - /** - * type vendor product - * HID_ID=0003:000005AC:00008242 - **/ - int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id); - if (ret == 3) { - found_id = 1; - } - } else if (strcmp(key, "HID_NAME") == 0) { - /* The caller has to free the product name */ - *product_name_utf8 = strdup(value); - found_name = 1; - } else if (strcmp(key, "HID_UNIQ") == 0) { - /* The caller has to free the serial number */ - *serial_number_utf8 = strdup(value); - found_serial = 1; - } - -next_line: - line = strtok_r(NULL, "\n", &saveptr); - } - - free(tmp); - return (found_id && found_name && found_serial); -} - - -static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen) -{ - struct udev *udev; - struct udev_device *udev_dev, *parent, *hid_dev; - struct stat s; - int ret = -1; - char *serial_number_utf8 = NULL; - char *product_name_utf8 = NULL; - - /* Create the udev object */ - udev = udev_new(); - if (!udev) { - register_global_error("Couldn't create udev context"); - return -1; - } - - /* Get the dev_t (major/minor numbers) from the file handle. */ - ret = fstat(dev->device_handle, &s); - if (-1 == ret) - return ret; - /* Open a udev device from the dev_t. 'c' means character device. */ - udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); - if (udev_dev) { - hid_dev = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "hid", - NULL); - if (hid_dev) { - unsigned short dev_vid; - unsigned short dev_pid; - unsigned bus_type; - size_t retm; - - ret = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - /* Standard USB device */ - if (bus_type == BUS_USB) { - /* This is a USB device. Find its parent USB Device node. */ - parent = udev_device_get_parent_with_subsystem_devtype( - udev_dev, - "usb", - "usb_device"); - if (parent) { - const char *str; - const char *key_str = NULL; - - if (key >= 0 && key < DEVICE_STRING_COUNT) { - key_str = device_string_names[key]; - } else { - ret = -1; - goto end; - } - - str = udev_device_get_sysattr_value(parent, key_str); - if (str) { - /* Convert the string from UTF-8 to wchar_t */ - retm = mbstowcs(string, str, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - } - - /* USB information parsed */ - goto end; - } - else { - /* Correctly handled below */ - } - } - - /* USB information not available (uhid) or another type of HID bus */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - switch (key) { - case DEVICE_STRING_MANUFACTURER: - wcsncpy(string, L"", maxlen); - ret = 0; - break; - case DEVICE_STRING_PRODUCT: - retm = mbstowcs(string, product_name_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_SERIAL: - retm = mbstowcs(string, serial_number_utf8, maxlen); - ret = (retm == (size_t)-1)? -1: 0; - break; - case DEVICE_STRING_COUNT: - default: - ret = -1; - break; - } - } - } - } - -end: - free(serial_number_utf8); - free(product_name_utf8); - - udev_device_unref(udev_dev); - /* parent and hid_dev don't need to be (and can't be) unref'd. - I'm not sure why, but they'll throw double-free() errors. */ - udev_unref(udev); - - return ret; -} - -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() -{ - return &api_version; -} - -HID_API_EXPORT const char* HID_API_CALL hid_version_str() -{ - return HID_API_VERSION_STR; -} - -int HID_API_EXPORT hid_init(void) -{ - const char *locale; - - /* Set the locale if it's not set. */ - locale = setlocale(LC_CTYPE, NULL); - if (!locale) - setlocale(LC_CTYPE, ""); - - return 0; -} - -int HID_API_EXPORT hid_exit(void) -{ - /* Free global error message */ - register_global_error(NULL); - - return 0; -} - - -struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) -{ - struct udev *udev; - struct udev_enumerate *enumerate; - struct udev_list_entry *devices, *dev_list_entry; - - struct hid_device_info *root = NULL; /* return object */ - struct hid_device_info *cur_dev = NULL; - struct hid_device_info *prev_dev = NULL; /* previous device */ - - hid_init(); - - /* Create the udev object */ - udev = udev_new(); - if (!udev) { - register_global_error("Couldn't create udev context"); - return NULL; - } - - /* Create a list of the devices in the 'hidraw' subsystem. */ - enumerate = udev_enumerate_new(udev); - udev_enumerate_add_match_subsystem(enumerate, "hidraw"); - udev_enumerate_scan_devices(enumerate); - devices = udev_enumerate_get_list_entry(enumerate); - /* For each item, see if it matches the vid/pid, and if so - create a udev_device record for it */ - udev_list_entry_foreach(dev_list_entry, devices) { - const char *sysfs_path; - const char *dev_path; - const char *str; - struct udev_device *raw_dev; /* The device's hidraw udev node. */ - struct udev_device *hid_dev; /* The device's HID udev node. */ - struct udev_device *usb_dev; /* The device's USB udev node. */ - struct udev_device *intf_dev; /* The device's interface (in the USB sense). */ - unsigned short dev_vid; - unsigned short dev_pid; - char *serial_number_utf8 = NULL; - char *product_name_utf8 = NULL; - unsigned bus_type; - int result; - struct hidraw_report_descriptor report_desc; - - /* Get the filename of the /sys entry for the device - and create a udev_device object (dev) representing it */ - sysfs_path = udev_list_entry_get_name(dev_list_entry); - raw_dev = udev_device_new_from_syspath(udev, sysfs_path); - dev_path = udev_device_get_devnode(raw_dev); - - hid_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "hid", - NULL); - - if (!hid_dev) { - /* Unable to find parent hid device. */ - goto next; - } - - result = parse_uevent_info( - udev_device_get_sysattr_value(hid_dev, "uevent"), - &bus_type, - &dev_vid, - &dev_pid, - &serial_number_utf8, - &product_name_utf8); - - if (!result) { - /* parse_uevent_info() failed for at least one field. */ - goto next; - } - - /* Filter out unhandled devices right away */ - switch (bus_type) { - case BUS_BLUETOOTH: - case BUS_I2C: - case BUS_USB: - break; - - default: - goto next; - } - - /* Check the VID/PID against the arguments */ - if ((vendor_id == 0x0 || vendor_id == dev_vid) && - (product_id == 0x0 || product_id == dev_pid)) { - struct hid_device_info *tmp; - - /* VID/PID match. Create the record. */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - prev_dev = cur_dev; - cur_dev = tmp; - - /* Fill out the record */ - cur_dev->next = NULL; - cur_dev->path = dev_path? strdup(dev_path): NULL; - - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Serial Number */ - cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); - - /* Release Number */ - cur_dev->release_number = 0x0; - - /* Interface Number */ - cur_dev->interface_number = -1; - - switch (bus_type) { - case BUS_USB: - /* The device pointed to by raw_dev contains information about - the hidraw device. In order to get information about the - USB device, get the parent device with the - subsystem/devtype pair of "usb"/"usb_device". This will - be several levels up the tree, but the function will find - it. */ - usb_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_device"); - - /* uhid USB devices - Since this is a virtual hid interface, no USB information will - be available. */ - if (!usb_dev) { - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - break; - } - - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]); - cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]); - - /* Release Number */ - str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); - cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; - - /* Get a handle to the interface's udev node. */ - intf_dev = udev_device_get_parent_with_subsystem_devtype( - raw_dev, - "usb", - "usb_interface"); - if (intf_dev) { - str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber"); - cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1; - } - - break; - - case BUS_BLUETOOTH: - case BUS_I2C: - /* Manufacturer and Product strings */ - cur_dev->manufacturer_string = wcsdup(L""); - cur_dev->product_string = utf8_to_wchar_t(product_name_utf8); - - break; - - default: - /* Unknown device type - this should never happen, as we - * check for USB and Bluetooth devices above */ - break; - } - - /* Usage Page and Usage */ - result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); - if (result >= 0) { - unsigned short page = 0, usage = 0; - unsigned int pos = 0; - /* - * Parse the first usage and usage page - * out of the report descriptor. - */ - if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - - /* - * Parse any additional usage and usage pages - * out of the report descriptor. - */ - while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { - /* Create new record for additional usage pairs */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - cur_dev->next = tmp; - prev_dev = cur_dev; - cur_dev = tmp; - - /* Update fields */ - cur_dev->path = strdup(dev_path); - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; - cur_dev->release_number = prev_dev->release_number; - cur_dev->interface_number = prev_dev->interface_number; - cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; - cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - } - } - - next: - free(serial_number_utf8); - free(product_name_utf8); - udev_device_unref(raw_dev); - /* hid_dev, usb_dev and intf_dev don't need to be (and can't be) - unref()d. It will cause a double-free() error. I'm not - sure why. */ - } - /* Free the enumerator and udev objects. */ - udev_enumerate_unref(enumerate); - udev_unref(udev); - - return root; -} - -void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) -{ - struct hid_device_info *d = devs; - while (d) { - struct hid_device_info *next = d->next; - free(d->path); - free(d->serial_number); - free(d->manufacturer_string); - free(d->product_string); - free(d); - d = next; - } -} - -hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) -{ - /* Set global error to none */ - register_global_error(NULL); - - struct hid_device_info *devs, *cur_dev; - const char *path_to_open = NULL; - hid_device *handle = NULL; - - devs = hid_enumerate(vendor_id, product_id); - cur_dev = devs; - while (cur_dev) { - if (cur_dev->vendor_id == vendor_id && - cur_dev->product_id == product_id) { - if (serial_number) { - if (wcscmp(serial_number, cur_dev->serial_number) == 0) { - path_to_open = cur_dev->path; - break; - } - } - else { - path_to_open = cur_dev->path; - break; - } - } - cur_dev = cur_dev->next; - } - - if (path_to_open) { - /* Open the device */ - handle = hid_open_path(path_to_open); - } else { - register_global_error("No such device"); - } - - hid_free_enumeration(devs); - - return handle; -} - -hid_device * HID_API_EXPORT hid_open_path(const char *path) -{ - /* Set global error to none */ - register_global_error(NULL); - - hid_device *dev = NULL; - - hid_init(); - - dev = new_hid_device(); - - /* OPEN HERE */ - dev->device_handle = open(path, O_RDWR); - - /* If we have a good handle, return it. */ - if (dev->device_handle >= 0) { - /* Set device error to none */ - register_device_error(dev, NULL); - - /* Get the report descriptor */ - int res, desc_size = 0; - struct hidraw_report_descriptor rpt_desc; - - memset(&rpt_desc, 0x0, sizeof(rpt_desc)); - - /* Get Report Descriptor Size */ - res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); - if (res < 0) - register_device_error_format(dev, "ioctl (GRDESCSIZE): %s", strerror(errno)); - - /* Get Report Descriptor */ - rpt_desc.size = desc_size; - res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); - if (res < 0) { - register_device_error_format(dev, "ioctl (GRDESC): %s", strerror(errno)); - } else { - /* Determine if this device uses numbered reports. */ - dev->uses_numbered_reports = - uses_numbered_reports(rpt_desc.value, - rpt_desc.size); - } - - return dev; - } - else { - /* Unable to open any devices. */ - register_global_error(strerror(errno)); - free(dev); - return NULL; - } -} - - -int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) -{ - int bytes_written; - - if (!data || (length == 0)) { - errno = EINVAL; - register_device_error(dev, strerror(errno)); - return -1; - } - - bytes_written = write(dev->device_handle, data, length); - - register_device_error(dev, (bytes_written == -1)? strerror(errno): NULL); - - return bytes_written; -} - - -int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) -{ - /* Set device error to none */ - register_device_error(dev, NULL); - - int bytes_read; - - if (milliseconds >= 0) { - /* Milliseconds is either 0 (non-blocking) or > 0 (contains - a valid timeout). In both cases we want to call poll() - and wait for data to arrive. Don't rely on non-blocking - operation (O_NONBLOCK) since some kernels don't seem to - properly report device disconnection through read() when - in non-blocking mode. */ - int ret; - struct pollfd fds; - - fds.fd = dev->device_handle; - fds.events = POLLIN; - fds.revents = 0; - ret = poll(&fds, 1, milliseconds); - if (ret == 0) { - /* Timeout */ - return ret; - } - if (ret == -1) { - /* Error */ - register_device_error(dev, strerror(errno)); - return ret; - } - else { - /* Check for errors on the file descriptor. This will - indicate a device disconnection. */ - if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) - // We cannot use strerror() here as no -1 was returned from poll(). - return -1; - } - } - - bytes_read = read(dev->device_handle, data, length); - if (bytes_read < 0) { - if (errno == EAGAIN || errno == EINPROGRESS) - bytes_read = 0; - else - register_device_error(dev, strerror(errno)); - } - - return bytes_read; -} - -int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) -{ - return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); -} - -int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) -{ - /* Do all non-blocking in userspace using poll(), since it looks - like there's a bug in the kernel in some versions where - read() will not return -1 on disconnection of the USB device */ - - dev->blocking = !nonblock; - return 0; /* Success */ -} - - -int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) -{ - int res; - - res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); - if (res < 0) - register_device_error_format(dev, "ioctl (SFEATURE): %s", strerror(errno)); - - return res; -} - -int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) -{ - int res; - - res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); - if (res < 0) - register_device_error_format(dev, "ioctl (GFEATURE): %s", strerror(errno)); - - return res; -} - -int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) -{ - int res; - - res = ioctl(dev->device_handle, HIDIOCGINPUT(length), data); - if (res < 0) - register_device_error_format(dev, "ioctl (GINPUT): %s", strerror(errno)); - - return res; -} - -void HID_API_EXPORT hid_close(hid_device *dev) -{ - if (!dev) - return; - - int ret = close(dev->device_handle); - - register_global_error((ret == -1)? strerror(errno): NULL); - - /* Free the device error message */ - register_device_error(dev, NULL); - - free(dev); -} - - -int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) -{ - (void)dev; - (void)string_index; - (void)string; - (void)maxlen; - return -1; -} - - -/* Passing in NULL means asking for the last global error message. */ -HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) -{ - if (dev) { - if (dev->last_error_str == NULL) - return L"Success"; - return dev->last_error_str; - } - - if (last_global_error_str == NULL) - return L"Success"; - return last_global_error_str; -} diff --git a/lib/hidapi/mac/hid.c b/lib/hidapi/mac/hid.c deleted file mode 100644 index 12648d9cfeb..00000000000 --- a/lib/hidapi/mac/hid.c +++ /dev/null @@ -1,1272 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - Alan Ott - Signal 11 Software - - 2010-07-03 - - Copyright 2010, All Rights Reserved. - - At the discretion of the user of this library, - this software may be licensed under the terms of the - GNU General Public License v3, a BSD-Style license, or the - original HIDAPI license as outlined in the LICENSE.txt, - LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt - files located at the root of the source distribution. - These files may also be found in the public source - code repository located at: - https://github.com/libusb/hidapi . -********************************************************/ - -/* See Apple Technical Note TN2187 for details on IOHidManager. */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hidapi.h" - -/* As defined in AppKit.h, but we don't need the entire AppKit for a single constant. */ -extern const double NSAppKitVersionNumber; - -/* Barrier implementation because Mac OSX doesn't have pthread_barrier. - It also doesn't have clock_gettime(). So much for POSIX and SUSv2. - This implementation came from Brent Priddy and was posted on - StackOverflow. It is used with his permission. */ -typedef int pthread_barrierattr_t; -typedef struct pthread_barrier { - pthread_mutex_t mutex; - pthread_cond_t cond; - int count; - int trip_count; -} pthread_barrier_t; - -static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) -{ - (void) attr; - - if(count == 0) { - errno = EINVAL; - return -1; - } - - if(pthread_mutex_init(&barrier->mutex, 0) < 0) { - return -1; - } - if(pthread_cond_init(&barrier->cond, 0) < 0) { - pthread_mutex_destroy(&barrier->mutex); - return -1; - } - barrier->trip_count = count; - barrier->count = 0; - - return 0; -} - -static int pthread_barrier_destroy(pthread_barrier_t *barrier) -{ - pthread_cond_destroy(&barrier->cond); - pthread_mutex_destroy(&barrier->mutex); - return 0; -} - -static int pthread_barrier_wait(pthread_barrier_t *barrier) -{ - pthread_mutex_lock(&barrier->mutex); - ++(barrier->count); - if(barrier->count >= barrier->trip_count) - { - barrier->count = 0; - pthread_cond_broadcast(&barrier->cond); - pthread_mutex_unlock(&barrier->mutex); - return 1; - } - else - { - pthread_cond_wait(&barrier->cond, &(barrier->mutex)); - pthread_mutex_unlock(&barrier->mutex); - return 0; - } -} - -static int return_data(hid_device *dev, unsigned char *data, size_t length); - -/* Linked List of input reports received from the device. */ -struct input_report { - uint8_t *data; - size_t len; - struct input_report *next; -}; - -struct hid_device_ { - IOHIDDeviceRef device_handle; - int blocking; - int uses_numbered_reports; - int disconnected; - CFStringRef run_loop_mode; - CFRunLoopRef run_loop; - CFRunLoopSourceRef source; - uint8_t *input_report_buf; - CFIndex max_input_report_len; - struct input_report *input_reports; - - pthread_t thread; - pthread_mutex_t mutex; /* Protects input_reports */ - pthread_cond_t condition; - pthread_barrier_t barrier; /* Ensures correct startup sequence */ - pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ - int shutdown_thread; -}; - -static hid_device *new_hid_device(void) -{ - hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); - dev->device_handle = NULL; - dev->blocking = 1; - dev->uses_numbered_reports = 0; - dev->disconnected = 0; - dev->run_loop_mode = NULL; - dev->run_loop = NULL; - dev->source = NULL; - dev->input_report_buf = NULL; - dev->input_reports = NULL; - dev->shutdown_thread = 0; - - /* Thread objects */ - pthread_mutex_init(&dev->mutex, NULL); - pthread_cond_init(&dev->condition, NULL); - pthread_barrier_init(&dev->barrier, NULL, 2); - pthread_barrier_init(&dev->shutdown_barrier, NULL, 2); - - return dev; -} - -static void free_hid_device(hid_device *dev) -{ - if (!dev) - return; - - /* Delete any input reports still left over. */ - struct input_report *rpt = dev->input_reports; - while (rpt) { - struct input_report *next = rpt->next; - free(rpt->data); - free(rpt); - rpt = next; - } - - /* Free the string and the report buffer. The check for NULL - is necessary here as CFRelease() doesn't handle NULL like - free() and others do. */ - if (dev->run_loop_mode) - CFRelease(dev->run_loop_mode); - if (dev->source) - CFRelease(dev->source); - free(dev->input_report_buf); - - /* Clean up the thread objects */ - pthread_barrier_destroy(&dev->shutdown_barrier); - pthread_barrier_destroy(&dev->barrier); - pthread_cond_destroy(&dev->condition); - pthread_mutex_destroy(&dev->mutex); - - /* Free the structure itself. */ - free(dev); -} - -static struct hid_api_version api_version = { - .major = HID_API_VERSION_MAJOR, - .minor = HID_API_VERSION_MINOR, - .patch = HID_API_VERSION_PATCH -}; - -static IOHIDManagerRef hid_mgr = 0x0; -static int is_macos_10_10_or_greater = 0; - - -#if 0 -static void register_error(hid_device *dev, const char *op) -{ - -} -#endif - -static CFArrayRef get_array_property(IOHIDDeviceRef device, CFStringRef key) -{ - CFTypeRef ref = IOHIDDeviceGetProperty(device, key); - if (ref != NULL && CFGetTypeID(ref) == CFArrayGetTypeID()) { - return (CFArrayRef)ref; - } else { - return NULL; - } -} - -static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) -{ - CFTypeRef ref; - int32_t value; - - ref = IOHIDDeviceGetProperty(device, key); - if (ref) { - if (CFGetTypeID(ref) == CFNumberGetTypeID()) { - CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); - return value; - } - } - return 0; -} - -static CFArrayRef get_usage_pairs(IOHIDDeviceRef device) -{ - return get_array_property(device, CFSTR(kIOHIDDeviceUsagePairsKey)); -} - -static unsigned short get_vendor_id(IOHIDDeviceRef device) -{ - return get_int_property(device, CFSTR(kIOHIDVendorIDKey)); -} - -static unsigned short get_product_id(IOHIDDeviceRef device) -{ - return get_int_property(device, CFSTR(kIOHIDProductIDKey)); -} - -static int32_t get_max_report_length(IOHIDDeviceRef device) -{ - return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); -} - -static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) -{ - CFStringRef str; - - if (!len) - return 0; - - str = (CFStringRef) IOHIDDeviceGetProperty(device, prop); - - buf[0] = 0; - - if (str) { - CFIndex str_len = CFStringGetLength(str); - CFRange range; - CFIndex used_buf_len; - CFIndex chars_copied; - - len --; - - range.location = 0; - range.length = ((size_t) str_len > len)? len: (size_t) str_len; - chars_copied = CFStringGetBytes(str, - range, - kCFStringEncodingUTF32LE, - (char) '?', - FALSE, - (UInt8*)buf, - len * sizeof(wchar_t), - &used_buf_len); - - if (chars_copied <= 0) - buf[0] = 0; - else - buf[chars_copied] = 0; - - return 0; - } - else - return -1; - -} - -static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) -{ - return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); -} - -static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) -{ - return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); -} - -static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) -{ - return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); -} - - -/* Implementation of wcsdup() for Mac. */ -static wchar_t *dup_wcs(const wchar_t *s) -{ - size_t len = wcslen(s); - wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t)); - wcscpy(ret, s); - - return ret; -} - -/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ -static int init_hid_manager(void) -{ - /* Initialize all the HID Manager Objects */ - hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - if (hid_mgr) { - IOHIDManagerSetDeviceMatching(hid_mgr, NULL); - IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - return 0; - } - - return -1; -} - -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() -{ - return &api_version; -} - -HID_API_EXPORT const char* HID_API_CALL hid_version_str() -{ - return HID_API_VERSION_STR; -} - -/* Initialize the IOHIDManager if necessary. This is the public function, and - it is safe to call this function repeatedly. Return 0 for success and -1 - for failure. */ -int HID_API_EXPORT hid_init(void) -{ - if (!hid_mgr) { - is_macos_10_10_or_greater = (NSAppKitVersionNumber >= 1343); /* NSAppKitVersionNumber10_10 */ - return init_hid_manager(); - } - - /* Already initialized. */ - return 0; -} - -int HID_API_EXPORT hid_exit(void) -{ - if (hid_mgr) { - /* Close the HID manager. */ - IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); - CFRelease(hid_mgr); - hid_mgr = NULL; - } - - return 0; -} - -static void process_pending_events(void) { - SInt32 res; - do { - res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); - } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); -} - -static struct hid_device_info *create_device_info_with_usage(IOHIDDeviceRef dev, int32_t usage_page, int32_t usage) -{ - unsigned short dev_vid; - unsigned short dev_pid; - int BUF_LEN = 256; - wchar_t buf[BUF_LEN]; - - struct hid_device_info *cur_dev; - io_object_t iokit_dev; - kern_return_t res; - uint64_t entry_id; - - if (dev == NULL) { - return NULL; - } - - cur_dev = (struct hid_device_info *)calloc(1, sizeof(struct hid_device_info)); - if (cur_dev == NULL) { - return NULL; - } - - dev_vid = get_vendor_id(dev); - dev_pid = get_product_id(dev); - - cur_dev->usage_page = usage_page; - cur_dev->usage = usage; - - /* Fill out the record */ - cur_dev->next = NULL; - - /* Fill in the path (as a unique ID of the service entry) */ - cur_dev->path = NULL; - iokit_dev = IOHIDDeviceGetService(dev); - if (iokit_dev != MACH_PORT_NULL) { - res = IORegistryEntryGetRegistryEntryID(iokit_dev, &entry_id); - } - else { - res = KERN_INVALID_ARGUMENT; - } - - if (res == KERN_SUCCESS) { - /* max value of entry_id(uint64_t) is 18446744073709551615 which is 20 characters long, - so for (max) "path" string 'DevSrvsID:18446744073709551615' we would need - 9+1+20+1=31 bytes byffer, but allocate 32 for simple alignment */ - cur_dev->path = calloc(1, 32); - if (cur_dev->path != NULL) { - sprintf(cur_dev->path, "DevSrvsID:%llu", entry_id); - } - } - - if (cur_dev->path == NULL) { - /* for whatever reason, trying to keep it a non-NULL string */ - cur_dev->path = strdup(""); - } - - /* Serial Number */ - get_serial_number(dev, buf, BUF_LEN); - cur_dev->serial_number = dup_wcs(buf); - - /* Manufacturer and Product strings */ - get_manufacturer_string(dev, buf, BUF_LEN); - cur_dev->manufacturer_string = dup_wcs(buf); - get_product_string(dev, buf, BUF_LEN); - cur_dev->product_string = dup_wcs(buf); - - /* VID/PID */ - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - - /* Release Number */ - cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); - - /* Interface Number */ - /* We can only retrieve the interface number for USB HID devices. - * IOKit always seems to return 0 when querying a standard USB device - * for its interface. */ - int is_usb_hid = get_int_property(dev, CFSTR(kUSBInterfaceClass)) == kUSBHIDClass; - if (is_usb_hid) { - /* Get the interface number */ - cur_dev->interface_number = get_int_property(dev, CFSTR(kUSBInterfaceNumber)); - } else { - cur_dev->interface_number = -1; - } - - return cur_dev; -} - -static struct hid_device_info *create_device_info(IOHIDDeviceRef device) -{ - const int32_t primary_usage_page = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); - const int32_t primary_usage = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); - - /* Primary should always be first, to match previous behavior. */ - struct hid_device_info *root = create_device_info_with_usage(device, primary_usage_page, primary_usage); - struct hid_device_info *cur = root; - - if (!root) - return NULL; - - CFArrayRef usage_pairs = get_usage_pairs(device); - - if (usage_pairs != NULL) { - struct hid_device_info *next = NULL; - for (CFIndex i = 0; i < CFArrayGetCount(usage_pairs); i++) { - CFTypeRef dict = CFArrayGetValueAtIndex(usage_pairs, i); - if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) { - continue; - } - - CFTypeRef usage_page_ref, usage_ref; - int32_t usage_page, usage; - - if (!CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsagePageKey), &usage_page_ref) || - !CFDictionaryGetValueIfPresent((CFDictionaryRef)dict, CFSTR(kIOHIDDeviceUsageKey), &usage_ref) || - CFGetTypeID(usage_page_ref) != CFNumberGetTypeID() || - CFGetTypeID(usage_ref) != CFNumberGetTypeID() || - !CFNumberGetValue((CFNumberRef)usage_page_ref, kCFNumberSInt32Type, &usage_page) || - !CFNumberGetValue((CFNumberRef)usage_ref, kCFNumberSInt32Type, &usage)) { - continue; - } - if (usage_page == primary_usage_page && usage == primary_usage) - continue; /* Already added. */ - - next = create_device_info_with_usage(device, usage_page, usage); - cur->next = next; - if (next != NULL) { - cur = next; - } - } - } - - return root; -} - -struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) -{ - struct hid_device_info *root = NULL; /* return object */ - struct hid_device_info *cur_dev = NULL; - CFIndex num_devices; - int i; - - /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) - return NULL; - - /* give the IOHIDManager a chance to update itself */ - process_pending_events(); - - /* Get a list of the Devices */ - CFMutableDictionaryRef matching = NULL; - if (vendor_id != 0 || product_id != 0) { - matching = CFDictionaryCreateMutable(kCFAllocatorDefault, kIOHIDOptionsTypeNone, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - - if (matching && vendor_id != 0) { - CFNumberRef v = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &vendor_id); - CFDictionarySetValue(matching, CFSTR(kIOHIDVendorIDKey), v); - CFRelease(v); - } - - if (matching && product_id != 0) { - CFNumberRef p = CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &product_id); - CFDictionarySetValue(matching, CFSTR(kIOHIDProductIDKey), p); - CFRelease(p); - } - } - IOHIDManagerSetDeviceMatching(hid_mgr, matching); - if (matching != NULL) { - CFRelease(matching); - } - - CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); - if (device_set == NULL) { - return NULL; - } - - /* Convert the list into a C array so we can iterate easily. */ - num_devices = CFSetGetCount(device_set); - IOHIDDeviceRef *device_array = (IOHIDDeviceRef*) calloc(num_devices, sizeof(IOHIDDeviceRef)); - CFSetGetValues(device_set, (const void **) device_array); - - /* Iterate over each device, making an entry for it. */ - for (i = 0; i < num_devices; i++) { - - IOHIDDeviceRef dev = device_array[i]; - if (!dev) { - continue; - } - - struct hid_device_info *tmp = create_device_info(dev); - if (tmp == NULL) { - continue; - } - - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - - /* move the pointer to the tail of returnd list */ - while (cur_dev->next != NULL) { - cur_dev = cur_dev->next; - } - } - - free(device_array); - CFRelease(device_set); - - return root; -} - -void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) -{ - /* This function is identical to the Linux version. Platform independent. */ - struct hid_device_info *d = devs; - while (d) { - struct hid_device_info *next = d->next; - free(d->path); - free(d->serial_number); - free(d->manufacturer_string); - free(d->product_string); - free(d); - d = next; - } -} - -hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) -{ - /* This function is identical to the Linux version. Platform independent. */ - struct hid_device_info *devs, *cur_dev; - const char *path_to_open = NULL; - hid_device * handle = NULL; - - devs = hid_enumerate(vendor_id, product_id); - cur_dev = devs; - while (cur_dev) { - if (cur_dev->vendor_id == vendor_id && - cur_dev->product_id == product_id) { - if (serial_number) { - if (wcscmp(serial_number, cur_dev->serial_number) == 0) { - path_to_open = cur_dev->path; - break; - } - } - else { - path_to_open = cur_dev->path; - break; - } - } - cur_dev = cur_dev->next; - } - - if (path_to_open) { - /* Open the device */ - handle = hid_open_path(path_to_open); - } - - hid_free_enumeration(devs); - - return handle; -} - -static void hid_device_removal_callback(void *context, IOReturn result, - void *sender) -{ - (void) result; - (void) sender; - - /* Stop the Run Loop for this device. */ - hid_device *d = (hid_device*) context; - - d->disconnected = 1; - CFRunLoopStop(d->run_loop); -} - -/* The Run Loop calls this function for each input report received. - This function puts the data into a linked list to be picked up by - hid_read(). */ -static void hid_report_callback(void *context, IOReturn result, void *sender, - IOHIDReportType report_type, uint32_t report_id, - uint8_t *report, CFIndex report_length) -{ - (void) result; - (void) sender; - (void) report_type; - (void) report_id; - - struct input_report *rpt; - hid_device *dev = (hid_device*) context; - - /* Make a new Input Report object */ - rpt = (struct input_report*) calloc(1, sizeof(struct input_report)); - rpt->data = (uint8_t*) calloc(1, report_length); - memcpy(rpt->data, report, report_length); - rpt->len = report_length; - rpt->next = NULL; - - /* Lock this section */ - pthread_mutex_lock(&dev->mutex); - - /* Attach the new report object to the end of the list. */ - if (dev->input_reports == NULL) { - /* The list is empty. Put it at the root. */ - dev->input_reports = rpt; - } - else { - /* Find the end of the list and attach. */ - struct input_report *cur = dev->input_reports; - int num_queued = 0; - while (cur->next != NULL) { - cur = cur->next; - num_queued++; - } - cur->next = rpt; - - /* Pop one off if we've reached 30 in the queue. This - way we don't grow forever if the user never reads - anything from the device. */ - if (num_queued > 30) { - return_data(dev, NULL, 0); - } - } - - /* Signal a waiting thread that there is data. */ - pthread_cond_signal(&dev->condition); - - /* Unlock */ - pthread_mutex_unlock(&dev->mutex); - -} - -/* This gets called when the read_thread's run loop gets signaled by - hid_close(), and serves to stop the read_thread's run loop. */ -static void perform_signal_callback(void *context) -{ - hid_device *dev = (hid_device*) context; - CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ -} - -static void *read_thread(void *param) -{ - hid_device *dev = (hid_device*) param; - SInt32 code; - - /* Move the device's run loop to this thread. */ - IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); - - /* Create the RunLoopSource which is used to signal the - event loop to stop when hid_close() is called. */ - CFRunLoopSourceContext ctx; - memset(&ctx, 0, sizeof(ctx)); - ctx.version = 0; - ctx.info = dev; - ctx.perform = &perform_signal_callback; - dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); - CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode); - - /* Store off the Run Loop so it can be stopped from hid_close() - and on device disconnection. */ - dev->run_loop = CFRunLoopGetCurrent(); - - /* Notify the main thread that the read thread is up and running. */ - pthread_barrier_wait(&dev->barrier); - - /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input - reports into the hid_report_callback(). */ - while (!dev->shutdown_thread && !dev->disconnected) { - code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); - /* Return if the device has been disconnected */ - if (code == kCFRunLoopRunFinished) { - dev->disconnected = 1; - break; - } - - - /* Break if The Run Loop returns Finished or Stopped. */ - if (code != kCFRunLoopRunTimedOut && - code != kCFRunLoopRunHandledSource) { - /* There was some kind of error. Setting - shutdown seems to make sense, but - there may be something else more appropriate */ - dev->shutdown_thread = 1; - break; - } - } - - /* Now that the read thread is stopping, Wake any threads which are - waiting on data (in hid_read_timeout()). Do this under a mutex to - make sure that a thread which is about to go to sleep waiting on - the condition actually will go to sleep before the condition is - signaled. */ - pthread_mutex_lock(&dev->mutex); - pthread_cond_broadcast(&dev->condition); - pthread_mutex_unlock(&dev->mutex); - - /* Wait here until hid_close() is called and makes it past - the call to CFRunLoopWakeUp(). This thread still needs to - be valid when that function is called on the other thread. */ - pthread_barrier_wait(&dev->shutdown_barrier); - - return NULL; -} - -/* \p path must be one of: - - in format 'DevSrvsID:' (as returned by hid_enumerate); - - a valid path to an IOHIDDevice in the IOService plane (as returned by IORegistryEntryGetPath, - e.g.: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver"); - Second format is for compatibility with paths accepted by older versions of HIDAPI. -*/ -static io_registry_entry_t hid_open_service_registry_from_path(const char *path) -{ - if (path == NULL) - return MACH_PORT_NULL; - - /* Get the IORegistry entry for the given path */ - if (strncmp("DevSrvsID:", path, 10) == 0) { - char *endptr; - uint64_t entry_id = strtoull(path + 10, &endptr, 10); - if (*endptr == '\0') { - return IOServiceGetMatchingService(kIOMasterPortDefault, IORegistryEntryIDMatching(entry_id)); - } - } - else { - /* Fallback to older format of the path */ - return IORegistryEntryFromPath(kIOMasterPortDefault, path); - } - - return MACH_PORT_NULL; -} - -hid_device * HID_API_EXPORT hid_open_path(const char *path) -{ - hid_device *dev = NULL; - io_registry_entry_t entry = MACH_PORT_NULL; - IOReturn ret = kIOReturnInvalid; - - /* Set up the HID Manager if it hasn't been done */ - if (hid_init() < 0) - goto return_error; - - dev = new_hid_device(); - - /* Get the IORegistry entry for the given path */ - entry = hid_open_service_registry_from_path(path); - if (entry == MACH_PORT_NULL) { - /* Path wasn't valid (maybe device was removed?) */ - goto return_error; - } - - /* Create an IOHIDDevice for the entry */ - dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry); - if (dev->device_handle == NULL) { - /* Error creating the HID device */ - goto return_error; - } - - /* Open the IOHIDDevice */ - ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); - if (ret == kIOReturnSuccess) { - char str[32]; - - /* Create the buffers for receiving data */ - dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); - dev->input_report_buf = (uint8_t*) calloc(dev->max_input_report_len, sizeof(uint8_t)); - - /* Create the Run Loop Mode for this device. - printing the reference seems to work. */ - sprintf(str, "HIDAPI_%p", (void*) dev->device_handle); - dev->run_loop_mode = - CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); - - /* Attach the device to a Run Loop */ - IOHIDDeviceRegisterInputReportCallback( - dev->device_handle, dev->input_report_buf, dev->max_input_report_len, - &hid_report_callback, dev); - IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); - - /* Start the read thread */ - pthread_create(&dev->thread, NULL, read_thread, dev); - - /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); - - IOObjectRelease(entry); - return dev; - } - else { - goto return_error; - } - -return_error: - if (dev->device_handle != NULL) - CFRelease(dev->device_handle); - - if (entry != MACH_PORT_NULL) - IOObjectRelease(entry); - - free_hid_device(dev); - return NULL; -} - -static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) -{ - const unsigned char *data_to_send = data; - CFIndex length_to_send = length; - IOReturn res; - unsigned char report_id; - - if (!data || (length == 0)) { - return -1; - } - - report_id = data[0]; - - if (report_id == 0x0) { - /* Not using numbered Reports. - Don't send the report number. */ - data_to_send = data+1; - length_to_send = length-1; - } - - /* Avoid crash if the device has been unplugged. */ - if (dev->disconnected) { - return -1; - } - - res = IOHIDDeviceSetReport(dev->device_handle, - type, - report_id, - data_to_send, length_to_send); - - if (res == kIOReturnSuccess) { - return length; - } - - return -1; -} - -static int get_report(hid_device *dev, IOHIDReportType type, unsigned char *data, size_t length) -{ - unsigned char *report = data; - CFIndex report_length = length; - IOReturn res = kIOReturnSuccess; - const unsigned char report_id = data[0]; - - if (report_id == 0x0) { - /* Not using numbered Reports. - Don't send the report number. */ - report = data+1; - report_length = length-1; - } - - /* Avoid crash if the device has been unplugged. */ - if (dev->disconnected) { - return -1; - } - - res = IOHIDDeviceGetReport(dev->device_handle, - type, - report_id, - report, &report_length); - - if (res == kIOReturnSuccess) { - if (report_id == 0x0) { /* 0 report number still present at the beginning */ - report_length++; - } - return report_length; - } - - return -1; -} - -int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) -{ - return set_report(dev, kIOHIDReportTypeOutput, data, length); -} - -/* Helper function, so that this isn't duplicated in hid_read(). */ -static int return_data(hid_device *dev, unsigned char *data, size_t length) -{ - /* Copy the data out of the linked list item (rpt) into the - return buffer (data), and delete the liked list item. */ - struct input_report *rpt = dev->input_reports; - size_t len = (length < rpt->len)? length: rpt->len; - memcpy(data, rpt->data, len); - dev->input_reports = rpt->next; - free(rpt->data); - free(rpt); - return len; -} - -static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) -{ - while (!dev->input_reports) { - int res = pthread_cond_wait(cond, mutex); - if (res != 0) - return res; - - /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's actually - data in the queue before returning, and if not, go back - to sleep. See the pthread_cond_timedwait() man page for - details. */ - - if (dev->shutdown_thread || dev->disconnected) - return -1; - } - - return 0; -} - -static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) -{ - while (!dev->input_reports) { - int res = pthread_cond_timedwait(cond, mutex, abstime); - if (res != 0) - return res; - - /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's actually - data in the queue before returning, and if not, go back - to sleep. See the pthread_cond_timedwait() man page for - details. */ - - if (dev->shutdown_thread || dev->disconnected) - return -1; - } - - return 0; - -} - -int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) -{ - int bytes_read = -1; - - /* Lock the access to the report list. */ - pthread_mutex_lock(&dev->mutex); - - /* There's an input report queued up. Return it. */ - if (dev->input_reports) { - /* Return the first one */ - bytes_read = return_data(dev, data, length); - goto ret; - } - - /* Return if the device has been disconnected. */ - if (dev->disconnected) { - bytes_read = -1; - goto ret; - } - - if (dev->shutdown_thread) { - /* This means the device has been closed (or there - has been an error. An error code of -1 should - be returned. */ - bytes_read = -1; - goto ret; - } - - /* There is no data. Go to sleep and wait for data. */ - - if (milliseconds == -1) { - /* Blocking */ - int res; - res = cond_wait(dev, &dev->condition, &dev->mutex); - if (res == 0) - bytes_read = return_data(dev, data, length); - else { - /* There was an error, or a device disconnection. */ - bytes_read = -1; - } - } - else if (milliseconds > 0) { - /* Non-blocking, but called with timeout. */ - int res; - struct timespec ts; - struct timeval tv; - gettimeofday(&tv, NULL); - TIMEVAL_TO_TIMESPEC(&tv, &ts); - ts.tv_sec += milliseconds / 1000; - ts.tv_nsec += (milliseconds % 1000) * 1000000; - if (ts.tv_nsec >= 1000000000L) { - ts.tv_sec++; - ts.tv_nsec -= 1000000000L; - } - - res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); - if (res == 0) - bytes_read = return_data(dev, data, length); - else if (res == ETIMEDOUT) - bytes_read = 0; - else - bytes_read = -1; - } - else { - /* Purely non-blocking */ - bytes_read = 0; - } - -ret: - /* Unlock */ - pthread_mutex_unlock(&dev->mutex); - return bytes_read; -} - -int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) -{ - return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); -} - -int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) -{ - /* All Nonblocking operation is handled by the library. */ - dev->blocking = !nonblock; - - return 0; -} - -int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) -{ - return set_report(dev, kIOHIDReportTypeFeature, data, length); -} - -int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) -{ - return get_report(dev, kIOHIDReportTypeFeature, data, length); -} - -int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) -{ - return get_report(dev, kIOHIDReportTypeInput, data, length); -} - -void HID_API_EXPORT hid_close(hid_device *dev) -{ - if (!dev) - return; - - /* Disconnect the report callback before close. - See comment below. - */ - if (is_macos_10_10_or_greater || !dev->disconnected) { - IOHIDDeviceRegisterInputReportCallback( - dev->device_handle, dev->input_report_buf, dev->max_input_report_len, - NULL, dev); - IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev); - IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode); - IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode); - } - - /* Cause read_thread() to stop. */ - dev->shutdown_thread = 1; - - /* Wake up the run thread's event loop so that the thread can exit. */ - CFRunLoopSourceSignal(dev->source); - CFRunLoopWakeUp(dev->run_loop); - - /* Notify the read thread that it can shut down now. */ - pthread_barrier_wait(&dev->shutdown_barrier); - - /* Wait for read_thread() to end. */ - pthread_join(dev->thread, NULL); - - /* Close the OS handle to the device, but only if it's not - been unplugged. If it's been unplugged, then calling - IOHIDDeviceClose() will crash. - - UPD: The crash part was true in/until some version of macOS. - Starting with macOS 10.15, there is an opposite effect in some environments: - crash happenes if IOHIDDeviceClose() is not called. - Not leaking a resource in all tested environments. - */ - if (is_macos_10_10_or_greater || !dev->disconnected) { - IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); - } - - /* Clear out the queue of received reports. */ - pthread_mutex_lock(&dev->mutex); - while (dev->input_reports) { - return_data(dev, NULL, 0); - } - pthread_mutex_unlock(&dev->mutex); - CFRelease(dev->device_handle); - - free_hid_device(dev); -} - -int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return get_manufacturer_string(dev->device_handle, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return get_product_string(dev->device_handle, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - return get_serial_number(dev->device_handle, string, maxlen); -} - -int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) -{ - (void) dev; - (void) string_index; - (void) string; - (void) maxlen; - - /* TODO: */ - - return 0; -} - - -HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) -{ - (void) dev; - /* TODO: */ - - return L"hid_error is not implemented yet"; -} - - - - - - - -#if 0 -static int32_t get_location_id(IOHIDDeviceRef device) -{ - return get_int_property(device, CFSTR(kIOHIDLocationIDKey)); -} - -static int32_t get_usage(IOHIDDeviceRef device) -{ - int32_t res; - res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); - if (!res) - res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); - return res; -} - -static int32_t get_usage_page(IOHIDDeviceRef device) -{ - int32_t res; - res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); - if (!res) - res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); - return res; -} - -static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len) -{ - return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len); -} - - -int main(void) -{ - IOHIDManagerRef mgr; - int i; - - mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - IOHIDManagerSetDeviceMatching(mgr, NULL); - IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone); - - CFSetRef device_set = IOHIDManagerCopyDevices(mgr); - - CFIndex num_devices = CFSetGetCount(device_set); - IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); - CFSetGetValues(device_set, (const void **) device_array); - - for (i = 0; i < num_devices; i++) { - IOHIDDeviceRef dev = device_array[i]; - printf("Device: %p\n", dev); - printf(" %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev)); - - wchar_t serial[256], buf[256]; - char cbuf[256]; - get_serial_number(dev, serial, 256); - - - printf(" Serial: %ls\n", serial); - printf(" Loc: %ld\n", get_location_id(dev)); - get_transport(dev, buf, 256); - printf(" Trans: %ls\n", buf); - make_path(dev, cbuf, 256); - printf(" Path: %s\n", cbuf); - - } - - return 0; -} -#endif diff --git a/lib/hidapi/udev/69-hid.rules b/lib/hidapi/udev/69-hid.rules deleted file mode 100644 index a27113ca873..00000000000 --- a/lib/hidapi/udev/69-hid.rules +++ /dev/null @@ -1,36 +0,0 @@ -# This is a sample udev file for HIDAPI devices which lets unprivileged -# users who are physically present at the system (not remote users) access -# HID devices. - -# If you are using the libusb implementation of hidapi (libusb/hid.c), then -# use something like the following line, substituting the VID and PID with -# those of your device. - -# HIDAPI/libusb -SUBSYSTEMS=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="003f", TAG+="uaccess" - -# If you are using the hidraw implementation (linux/hid.c), then do something -# like the following, substituting the VID and PID with your device. - -# HIDAPI/hidraw -KERNEL=="hidraw*", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="003f", TAG+="uaccess" - -# Once done, optionally rename this file for your application, and drop it into -# /etc/udev/rules.d/. -# NOTE: these rules must have priorty before 73-seat-late.rules. -# (Small discussion/explanation in systemd repo: -# https://github.com/systemd/systemd/issues/4288#issuecomment-348166161) -# for example, name the file /etc/udev/rules.d/70-my-application-hid.rules. -# Then, replug your device or run: -# sudo udevadm control --reload-rules && sudo udevadm trigger - -# Note that the hexadecimal values for VID and PID are case sensitive and -# must be lower case. - -# TAG+="uaccess" only gives permission to physically present users, which -# is appropriate in most scenarios. If you require access to the device -# from a remote session (e.g. over SSH), add -# GROUP="plugdev", MODE="660" -# to the end of the udev rule lines, add your user to the plugdev group with: -# usermod -aG plugdev USERNAME -# then log out and log back in (or restart the system). diff --git a/lib/hidapi/windows/hid.c b/lib/hidapi/windows/hid.c deleted file mode 100644 index 9ed98703545..00000000000 --- a/lib/hidapi/windows/hid.c +++ /dev/null @@ -1,1247 +0,0 @@ -/******************************************************* - HIDAPI - Multi-Platform library for - communication with HID devices. - - Alan Ott - Signal 11 Software - - 8/22/2009 - - Copyright 2009, All Rights Reserved. - - At the discretion of the user of this library, - this software may be licensed under the terms of the - GNU General Public License v3, a BSD-Style license, or the - original HIDAPI license as outlined in the LICENSE.txt, - LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt - files located at the root of the source distribution. - These files may also be found in the public source - code repository located at: - https://github.com/libusb/hidapi . -********************************************************/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -// Do not warn about mbsrtowcs and wcsncpy usage. -// https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include - -#ifndef _NTDEF_ -typedef LONG NTSTATUS; -#endif - -#ifdef __MINGW32__ -#include -#include -#include -#endif - -#ifdef __CYGWIN__ -#include -#define _wcsdup wcsdup -#endif - -/* The maximum number of characters that can be passed into the - HidD_Get*String() functions without it failing.*/ -#define MAX_STRING_WCHARS 0xFFF - -/*#define HIDAPI_USE_DDK*/ - -#ifdef __cplusplus -extern "C" { -#endif - #include - #include - #ifdef HIDAPI_USE_DDK - #include - #endif - - /* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */ - #define HID_OUT_CTL_CODE(id) \ - CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) - #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) - #define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104) - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#include -#include -#include -#include - -#include "hidapi.h" - -#undef MIN -#define MIN(x,y) ((x) < (y)? (x): (y)) - -#ifdef __cplusplus -extern "C" { -#endif - -static struct hid_api_version api_version = { - .major = HID_API_VERSION_MAJOR, - .minor = HID_API_VERSION_MINOR, - .patch = HID_API_VERSION_PATCH -}; - -#ifndef HIDAPI_USE_DDK - /* Since we're not building with the DDK, and the HID header - files aren't part of the SDK, we have to define all this - stuff here. In lookup_functions(), the function pointers - defined below are set. */ - typedef struct _HIDD_ATTRIBUTES{ - ULONG Size; - USHORT VendorID; - USHORT ProductID; - USHORT VersionNumber; - } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; - - typedef USHORT USAGE; - typedef struct _HIDP_CAPS { - USAGE Usage; - USAGE UsagePage; - USHORT InputReportByteLength; - USHORT OutputReportByteLength; - USHORT FeatureReportByteLength; - USHORT Reserved[17]; - USHORT fields_not_used_by_hidapi[10]; - } HIDP_CAPS, *PHIDP_CAPS; - typedef void* PHIDP_PREPARSED_DATA; - #define HIDP_STATUS_SUCCESS 0x110000 - - typedef void (__stdcall *HidD_GetHidGuid_)(LPGUID hid_guid); - typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); - typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); - typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); - typedef BOOLEAN (__stdcall *HidD_GetInputReport_)(HANDLE handle, PVOID data, ULONG length); - typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); - typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data); - typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data); - typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps); - typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers); - - static HidD_GetHidGuid_ HidD_GetHidGuid; - static HidD_GetAttributes_ HidD_GetAttributes; - static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; - static HidD_GetManufacturerString_ HidD_GetManufacturerString; - static HidD_GetProductString_ HidD_GetProductString; - static HidD_SetFeature_ HidD_SetFeature; - static HidD_GetFeature_ HidD_GetFeature; - static HidD_GetInputReport_ HidD_GetInputReport; - static HidD_GetIndexedString_ HidD_GetIndexedString; - static HidD_GetPreparsedData_ HidD_GetPreparsedData; - static HidD_FreePreparsedData_ HidD_FreePreparsedData; - static HidP_GetCaps_ HidP_GetCaps; - static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers; - - static HMODULE lib_handle = NULL; - static BOOLEAN initialized = FALSE; - - typedef DWORD RETURN_TYPE; - typedef RETURN_TYPE CONFIGRET; - typedef DWORD DEVNODE, DEVINST; - typedef DEVNODE* PDEVNODE, * PDEVINST; - typedef WCHAR* DEVNODEID_W, * DEVINSTID_W; - -#define CR_SUCCESS (0x00000000) -#define CR_BUFFER_SMALL (0x0000001A) - -#define CM_LOCATE_DEVNODE_NORMAL 0x00000000 - -#define DEVPROP_TYPEMOD_LIST 0x00002000 - -#define DEVPROP_TYPE_STRING 0x00000012 -#define DEVPROP_TYPE_STRING_LIST (DEVPROP_TYPE_STRING|DEVPROP_TYPEMOD_LIST) - - typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags); - typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags); - typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); - typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags); - - static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL; - static CM_Get_Parent_ CM_Get_Parent = NULL; - static CM_Get_DevNode_PropertyW_ CM_Get_DevNode_PropertyW = NULL; - static CM_Get_Device_Interface_PropertyW_ CM_Get_Device_Interface_PropertyW = NULL; - - static HMODULE cfgmgr32_lib_handle = NULL; -#endif /* HIDAPI_USE_DDK */ - -struct hid_device_ { - HANDLE device_handle; - BOOL blocking; - USHORT output_report_length; - unsigned char *write_buf; - size_t input_report_length; - USHORT feature_report_length; - unsigned char *feature_buf; - void *last_error_str; - DWORD last_error_num; - BOOL read_pending; - char *read_buf; - OVERLAPPED ol; - OVERLAPPED write_ol; - struct hid_device_info* device_info; -}; - -static hid_device *new_hid_device() -{ - hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); - dev->device_handle = INVALID_HANDLE_VALUE; - dev->blocking = TRUE; - dev->output_report_length = 0; - dev->write_buf = NULL; - dev->input_report_length = 0; - dev->feature_report_length = 0; - dev->feature_buf = NULL; - dev->last_error_str = NULL; - dev->last_error_num = 0; - dev->read_pending = FALSE; - dev->read_buf = NULL; - memset(&dev->ol, 0, sizeof(dev->ol)); - dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL); - memset(&dev->write_ol, 0, sizeof(dev->write_ol)); - dev->write_ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); - dev->device_info = NULL; - - return dev; -} - -static void free_hid_device(hid_device *dev) -{ - CloseHandle(dev->ol.hEvent); - CloseHandle(dev->write_ol.hEvent); - CloseHandle(dev->device_handle); - LocalFree(dev->last_error_str); - free(dev->write_buf); - free(dev->feature_buf); - free(dev->read_buf); - hid_free_enumeration(dev->device_info); - free(dev); -} - -static void register_error(hid_device *dev, const char *op) -{ - WCHAR *ptr, *msg; - (void)op; // unreferenced param - FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&msg, 0/*sz*/, - NULL); - - /* Get rid of the CR and LF that FormatMessage() sticks at the - end of the message. Thanks Microsoft! */ - ptr = msg; - while (*ptr) { - if (*ptr == L'\r') { - *ptr = L'\0'; - break; - } - ptr++; - } - - /* Store the message off in the Device entry so that - the hid_error() function can pick it up. */ - LocalFree(dev->last_error_str); - dev->last_error_str = msg; -} - -#ifndef HIDAPI_USE_DDK -static int lookup_functions() -{ - lib_handle = LoadLibraryA("hid.dll"); - if (lib_handle) { -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-function-type" -#endif -#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1; - RESOLVE(HidD_GetHidGuid); - RESOLVE(HidD_GetAttributes); - RESOLVE(HidD_GetSerialNumberString); - RESOLVE(HidD_GetManufacturerString); - RESOLVE(HidD_GetProductString); - RESOLVE(HidD_SetFeature); - RESOLVE(HidD_GetFeature); - RESOLVE(HidD_GetInputReport); - RESOLVE(HidD_GetIndexedString); - RESOLVE(HidD_GetPreparsedData); - RESOLVE(HidD_FreePreparsedData); - RESOLVE(HidP_GetCaps); - RESOLVE(HidD_SetNumInputBuffers); -#undef RESOLVE -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - } - else - return -1; - - cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll"); - if (cfgmgr32_lib_handle) { -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-function-type" -#endif -#define RESOLVE(x) x = (x##_)GetProcAddress(cfgmgr32_lib_handle, #x); - RESOLVE(CM_Locate_DevNodeW); - RESOLVE(CM_Get_Parent); - RESOLVE(CM_Get_DevNode_PropertyW); - RESOLVE(CM_Get_Device_Interface_PropertyW); -#undef RESOLVE -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - } - else { - CM_Locate_DevNodeW = NULL; - CM_Get_Parent = NULL; - CM_Get_DevNode_PropertyW = NULL; - CM_Get_Device_Interface_PropertyW = NULL; - } - - return 0; -} -#endif - -static HANDLE open_device(const char *path, BOOL open_rw) -{ - HANDLE handle; - DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0; - DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE; - - handle = CreateFileA(path, - desired_access, - share_mode, - NULL, - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/ - 0); - - return handle; -} - -HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version() -{ - return &api_version; -} - -HID_API_EXPORT const char* HID_API_CALL hid_version_str() -{ - return HID_API_VERSION_STR; -} - -int HID_API_EXPORT hid_init(void) -{ -#ifndef HIDAPI_USE_DDK - if (!initialized) { - if (lookup_functions() < 0) { - hid_exit(); - return -1; - } - initialized = TRUE; - } -#endif - return 0; -} - -int HID_API_EXPORT hid_exit(void) -{ -#ifndef HIDAPI_USE_DDK - if (lib_handle) - FreeLibrary(lib_handle); - lib_handle = NULL; - if (cfgmgr32_lib_handle) - FreeLibrary(cfgmgr32_lib_handle); - cfgmgr32_lib_handle = NULL; - initialized = FALSE; -#endif - return 0; -} - -static void hid_internal_get_ble_info(struct hid_device_info* dev, DEVINST dev_node) -{ - ULONG len; - CONFIGRET cr; - DEVPROPTYPE property_type; - - static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac }, 10 }; // DEVPROP_TYPE_STRING - static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 1 }; // DEVPROP_TYPE_STRING - static DEVPROPKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2BD67D8B, 0x8BEB, 0x48D5, 0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A }, 4 }; // DEVPROP_TYPE_STRING - - /* Manufacturer String */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { - free(dev->manufacturer_string); - dev->manufacturer_string = (wchar_t*)calloc(len, sizeof(BYTE)); - CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_Manufacturer, &property_type, (PBYTE)dev->manufacturer_string, &len, 0); - } - - /* Serial Number String (MAC Address) */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { - free(dev->serial_number); - dev->serial_number = (wchar_t*)calloc(len, sizeof(BYTE)); - CM_Get_DevNode_PropertyW(dev_node, &PKEY_DeviceInterface_Bluetooth_DeviceAddress, &property_type, (PBYTE)dev->serial_number, &len, 0); - } - - /* Get devnode grandparent to reach out Bluetooth LE device node */ - cr = CM_Get_Parent(&dev_node, dev_node, 0); - if (cr != CR_SUCCESS) - return; - - /* Product String */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { - free(dev->product_string); - dev->product_string = (wchar_t*)calloc(len, sizeof(BYTE)); - CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_NAME, &property_type, (PBYTE)dev->product_string, &len, 0); - } -} - -/* USB Device Interface Number. - It can be parsed out of the Hardware ID if a USB device is has multiple interfaces (composite device). - See https://docs.microsoft.com/windows-hardware/drivers/hid/hidclass-hardware-ids-for-top-level-collections - and https://docs.microsoft.com/windows-hardware/drivers/install/standard-usb-identifiers - - hardware_id is always expected to be uppercase. -*/ -static int hid_internal_get_interface_number(const wchar_t* hardware_id) -{ - int interface_number; - wchar_t *startptr, *endptr; - const wchar_t *interface_token = L"&MI_"; - - startptr = wcsstr(hardware_id, interface_token); - if (!startptr) - return -1; - - startptr += wcslen(interface_token); - interface_number = wcstol(startptr, &endptr, 16); - if (endptr == startptr) - return -1; - - return interface_number; -} - -static void hid_internal_get_info(struct hid_device_info* dev) -{ - const char *tmp = NULL; - wchar_t *interface_path = NULL, *device_id = NULL, *compatible_ids = NULL, *hardware_ids = NULL; - mbstate_t state; - ULONG len; - CONFIGRET cr; - DEVPROPTYPE property_type; - DEVINST dev_node; - - static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57 }, 256 }; // DEVPROP_TYPE_STRING - static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}, 3 }; // DEVPROP_TYPE_STRING_LIST - static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}, 4 }; // DEVPROP_TYPE_STRING_LIST - - if (!CM_Get_Device_Interface_PropertyW || - !CM_Locate_DevNodeW || - !CM_Get_Parent || - !CM_Get_DevNode_PropertyW) - goto end; - - tmp = dev->path; - - len = (ULONG)strlen(tmp); - interface_path = (wchar_t*)calloc(len + 1, sizeof(wchar_t)); - memset(&state, 0, sizeof(state)); - - if (mbsrtowcs(interface_path, &tmp, len, &state) == (size_t)-1) - goto end; - - /* Get the device id from interface path */ - len = 0; - cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING) { - device_id = (wchar_t*)calloc(len, sizeof(BYTE)); - cr = CM_Get_Device_Interface_PropertyW(interface_path, &DEVPKEY_Device_InstanceId, &property_type, (PBYTE)device_id, &len, 0); - } - if (cr != CR_SUCCESS) - goto end; - - /* Open devnode from device id */ - cr = CM_Locate_DevNodeW(&dev_node, (DEVINSTID_W)device_id, CM_LOCATE_DEVNODE_NORMAL); - if (cr != CR_SUCCESS) - goto end; - - /* Get the hardware ids from devnode */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_HardwareIds, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING_LIST) { - hardware_ids = (wchar_t*)calloc(len, sizeof(BYTE)); - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_HardwareIds, &property_type, (PBYTE)hardware_ids, &len, 0); - } - if (cr != CR_SUCCESS) - goto end; - - // Search for interface number in hardware ids - for (wchar_t* hardware_id = hardware_ids; *hardware_id; hardware_id += wcslen(hardware_id) + 1) { - /* Normalize to upper case */ - for (wchar_t* p = hardware_id; *p; ++p) *p = towupper(*p); - - dev->interface_number = hid_internal_get_interface_number(hardware_id); - - if (dev->interface_number != -1) - break; - } - - /* Get devnode parent */ - cr = CM_Get_Parent(&dev_node, dev_node, 0); - if (cr != CR_SUCCESS) - goto end; - - /* Get the compatible ids from parent devnode */ - len = 0; - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, NULL, &len, 0); - if (cr == CR_BUFFER_SMALL && property_type == DEVPROP_TYPE_STRING_LIST) { - compatible_ids = (wchar_t*)calloc(len, sizeof(BYTE)); - cr = CM_Get_DevNode_PropertyW(dev_node, &DEVPKEY_Device_CompatibleIds, &property_type, (PBYTE)compatible_ids, &len, 0); - } - if (cr != CR_SUCCESS) - goto end; - - /* Now we can parse parent's compatible IDs to find out the device bus type */ - for (wchar_t* compatible_id = compatible_ids; *compatible_id; compatible_id += wcslen(compatible_id) + 1) { - /* Normalize to upper case */ - for (wchar_t* p = compatible_id; *p; ++p) *p = towupper(*p); - - /* Bluetooth LE devices */ - if (wcsstr(compatible_id, L"BTHLEDEVICE") != NULL) { - /* HidD_GetProductString/HidD_GetManufacturerString/HidD_GetSerialNumberString is not working for BLE HID devices - Request this info via dev node properties instead. - https://docs.microsoft.com/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html */ - hid_internal_get_ble_info(dev, dev_node); - break; - } - } -end: - free(interface_path); - free(device_id); - free(hardware_ids); - free(compatible_ids); -} - -static struct hid_device_info *hid_get_device_info(const char *path, HANDLE handle) -{ - struct hid_device_info *dev = NULL; /* return object */ - - BOOL res; - HIDD_ATTRIBUTES attrib; - PHIDP_PREPARSED_DATA pp_data = NULL; - HIDP_CAPS caps; - - #define WSTR_LEN 512 - wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ - - /* Create the record. */ - dev = (struct hid_device_info*)calloc(1, sizeof(struct hid_device_info)); - - /* Fill out the record */ - dev->next = NULL; - - if (path) { - size_t len = strlen(path); - dev->path = (char*)calloc(len + 1, sizeof(char)); - memcpy(dev->path, path, len + 1); - } - else - dev->path = NULL; - - attrib.Size = sizeof(HIDD_ATTRIBUTES); - res = HidD_GetAttributes(handle, &attrib); - if (res) { - /* VID/PID */ - dev->vendor_id = attrib.VendorID; - dev->product_id = attrib.ProductID; - - /* Release Number */ - dev->release_number = attrib.VersionNumber; - } - - /* Get the Usage Page and Usage for this device. */ - res = HidD_GetPreparsedData(handle, &pp_data); - if (res) { - NTSTATUS nt_res = HidP_GetCaps(pp_data, &caps); - if (nt_res == HIDP_STATUS_SUCCESS) { - dev->usage_page = caps.UsagePage; - dev->usage = caps.Usage; - } - - HidD_FreePreparsedData(pp_data); - } - - /* Serial Number */ - wstr[0] = L'\0'; - res = HidD_GetSerialNumberString(handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN - 1] = L'\0'; - dev->serial_number = _wcsdup(wstr); - - /* Manufacturer String */ - wstr[0] = L'\0'; - res = HidD_GetManufacturerString(handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN - 1] = L'\0'; - dev->manufacturer_string = _wcsdup(wstr); - - /* Product String */ - wstr[0] = L'\0'; - res = HidD_GetProductString(handle, wstr, sizeof(wstr)); - wstr[WSTR_LEN - 1] = L'\0'; - dev->product_string = _wcsdup(wstr); - - hid_internal_get_info(dev); - - return dev; -} - -struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) -{ - BOOL res; - struct hid_device_info *root = NULL; /* return object */ - struct hid_device_info *cur_dev = NULL; - GUID interface_class_guid; - - /* Windows objects for interacting with the driver. */ - SP_DEVINFO_DATA devinfo_data; - SP_DEVICE_INTERFACE_DATA device_interface_data; - SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; - HDEVINFO device_info_set = INVALID_HANDLE_VALUE; - char driver_name[256]; - int device_index = 0; - - if (hid_init() < 0) - return NULL; - - /* Retrieve HID Interface Class GUID - https://docs.microsoft.com/windows-hardware/drivers/install/guid-devinterface-hid */ - HidD_GetHidGuid(&interface_class_guid); - - /* Initialize the Windows objects. */ - memset(&devinfo_data, 0x0, sizeof(devinfo_data)); - devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); - device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - - /* Get information for all the devices belonging to the HID class. */ - device_info_set = SetupDiGetClassDevsA(&interface_class_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - - /* Iterate over each device in the HID class, looking for the right one. */ - - for (;;) { - HANDLE read_handle = INVALID_HANDLE_VALUE; - DWORD required_size = 0; - HIDD_ATTRIBUTES attrib; - - res = SetupDiEnumDeviceInterfaces(device_info_set, - NULL, - &interface_class_guid, - device_index, - &device_interface_data); - - if (!res) { - /* A return of FALSE from this function means that - there are no more devices. */ - break; - } - - /* Call with 0-sized detail size, and let the function - tell us how long the detail struct needs to be. The - size is put in &required_size. */ - res = SetupDiGetDeviceInterfaceDetailA(device_info_set, - &device_interface_data, - NULL, - 0, - &required_size, - NULL); - - /* Allocate a long enough structure for device_interface_detail_data. */ - device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size); - device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); - - /* Get the detailed data for this device. The detail data gives us - the device path for this device, which is then passed into - CreateFile() to get a handle to the device. */ - res = SetupDiGetDeviceInterfaceDetailA(device_info_set, - &device_interface_data, - device_interface_detail_data, - required_size, - NULL, - NULL); - - if (!res) { - /* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail"); - Continue to the next device. */ - goto cont; - } - - /* Populate devinfo_data. This function will return failure - when the device with such index doesn't exist. We've already checked it does. */ - res = SetupDiEnumDeviceInfo(device_info_set, device_index, &devinfo_data); - if (!res) - goto cont; - - - /* Make sure this device has a driver bound to it. */ - res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, - SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); - if (!res) - goto cont; - - //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); - - /* Open read-only handle to the device */ - read_handle = open_device(device_interface_detail_data->DevicePath, FALSE); - - /* Check validity of read_handle. */ - if (read_handle == INVALID_HANDLE_VALUE) { - /* Unable to open the device. */ - //register_error(dev, "CreateFile"); - goto cont; - } - - /* Get the Vendor ID and Product ID for this device. */ - attrib.Size = sizeof(HIDD_ATTRIBUTES); - HidD_GetAttributes(read_handle, &attrib); - //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID); - - /* Check the VID/PID to see if we should add this - device to the enumeration list. */ - if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) && - (product_id == 0x0 || attrib.ProductID == product_id)) { - - /* VID/PID match. Create the record. */ - struct hid_device_info *tmp = hid_get_device_info(device_interface_detail_data->DevicePath, read_handle); - - if (tmp == NULL) { - goto cont_close; - } - - if (cur_dev) { - cur_dev->next = tmp; - } - else { - root = tmp; - } - cur_dev = tmp; - } - -cont_close: - CloseHandle(read_handle); -cont: - /* We no longer need the detail data. It can be freed */ - free(device_interface_detail_data); - - device_index++; - - } - - /* Close the device information handle. */ - SetupDiDestroyDeviceInfoList(device_info_set); - - return root; -} - -void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) -{ - /* TODO: Merge this with the Linux version. This function is platform-independent. */ - struct hid_device_info *d = devs; - while (d) { - struct hid_device_info *next = d->next; - free(d->path); - free(d->serial_number); - free(d->manufacturer_string); - free(d->product_string); - free(d); - d = next; - } -} - - -HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) -{ - /* TODO: Merge this functions with the Linux version. This function should be platform independent. */ - struct hid_device_info *devs, *cur_dev; - const char *path_to_open = NULL; - hid_device *handle = NULL; - - devs = hid_enumerate(vendor_id, product_id); - cur_dev = devs; - while (cur_dev) { - if (cur_dev->vendor_id == vendor_id && - cur_dev->product_id == product_id) { - if (serial_number) { - if (cur_dev->serial_number && wcscmp(serial_number, cur_dev->serial_number) == 0) { - path_to_open = cur_dev->path; - break; - } - } - else { - path_to_open = cur_dev->path; - break; - } - } - cur_dev = cur_dev->next; - } - - if (path_to_open) { - /* Open the device */ - handle = hid_open_path(path_to_open); - } - - hid_free_enumeration(devs); - - return handle; -} - -HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) -{ - hid_device *dev = NULL; - HANDLE device_handle = INVALID_HANDLE_VALUE; - PHIDP_PREPARSED_DATA pp_data = NULL; - HIDP_CAPS caps; - - if (hid_init() < 0) - goto end_of_function; - - /* Open a handle to the device */ - device_handle = open_device(path, TRUE); - - /* Check validity of write_handle. */ - if (device_handle == INVALID_HANDLE_VALUE) { - /* System devices, such as keyboards and mice, cannot be opened in - read-write mode, because the system takes exclusive control over - them. This is to prevent keyloggers. However, feature reports - can still be sent and received. Retry opening the device, but - without read/write access. */ - device_handle = open_device(path, FALSE); - - /* Check the validity of the limited device_handle. */ - if (device_handle == INVALID_HANDLE_VALUE) - goto end_of_function; - } - - /* Set the Input Report buffer size to 64 reports. */ - if (!HidD_SetNumInputBuffers(device_handle, 64)) - goto end_of_function; - - /* Get the Input Report length for the device. */ - if (!HidD_GetPreparsedData(device_handle, &pp_data)) - goto end_of_function; - - if (HidP_GetCaps(pp_data, &caps) != HIDP_STATUS_SUCCESS) - goto end_of_function; - - dev = new_hid_device(); - - dev->device_handle = device_handle; - device_handle = INVALID_HANDLE_VALUE; - - dev->output_report_length = caps.OutputReportByteLength; - dev->input_report_length = caps.InputReportByteLength; - dev->feature_report_length = caps.FeatureReportByteLength; - dev->read_buf = (char*) malloc(dev->input_report_length); - dev->device_info = hid_get_device_info(path, dev->device_handle); - -end_of_function: - CloseHandle(device_handle); - HidD_FreePreparsedData(pp_data); - - return dev; -} - -int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) -{ - DWORD bytes_written = 0; - int function_result = -1; - BOOL res; - BOOL overlapped = FALSE; - - unsigned char *buf; - - if (!data || (length==0)) { - register_error(dev, "Zero length buffer"); - return function_result; - } - - /* Make sure the right number of bytes are passed to WriteFile. Windows - expects the number of bytes which are in the _longest_ report (plus - one for the report number) bytes even if the data is a report - which is shorter than that. Windows gives us this value in - caps.OutputReportByteLength. If a user passes in fewer bytes than this, - use cached temporary buffer which is the proper size. */ - if (length >= dev->output_report_length) { - /* The user passed the right number of bytes. Use the buffer as-is. */ - buf = (unsigned char *) data; - } else { - if (dev->write_buf == NULL) - dev->write_buf = (unsigned char *) malloc(dev->output_report_length); - buf = dev->write_buf; - memcpy(buf, data, length); - memset(buf + length, 0, dev->output_report_length - length); - length = dev->output_report_length; - } - - res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol); - - if (!res) { - if (GetLastError() != ERROR_IO_PENDING) { - /* WriteFile() failed. Return error. */ - register_error(dev, "WriteFile"); - goto end_of_function; - } - overlapped = TRUE; - } - - if (overlapped) { - /* Wait for the transaction to complete. This makes - hid_write() synchronous. */ - res = WaitForSingleObject(dev->write_ol.hEvent, 1000); - if (res != WAIT_OBJECT_0) { - /* There was a Timeout. */ - register_error(dev, "WriteFile/WaitForSingleObject Timeout"); - goto end_of_function; - } - - /* Get the result. */ - res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/); - if (res) { - function_result = bytes_written; - } - else { - /* The Write operation failed. */ - register_error(dev, "WriteFile"); - goto end_of_function; - } - } - -end_of_function: - return function_result; -} - - -int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) -{ - DWORD bytes_read = 0; - size_t copy_len = 0; - BOOL res = FALSE; - BOOL overlapped = FALSE; - - /* Copy the handle for convenience. */ - HANDLE ev = dev->ol.hEvent; - - if (!dev->read_pending) { - /* Start an Overlapped I/O read. */ - dev->read_pending = TRUE; - memset(dev->read_buf, 0, dev->input_report_length); - ResetEvent(ev); - res = ReadFile(dev->device_handle, dev->read_buf, (DWORD) dev->input_report_length, &bytes_read, &dev->ol); - - if (!res) { - if (GetLastError() != ERROR_IO_PENDING) { - /* ReadFile() has failed. - Clean up and return error. */ - CancelIo(dev->device_handle); - dev->read_pending = FALSE; - goto end_of_function; - } - overlapped = TRUE; - } - } - else { - overlapped = TRUE; - } - - if (overlapped) { - if (milliseconds >= 0) { - /* See if there is any data yet. */ - res = WaitForSingleObject(ev, milliseconds); - if (res != WAIT_OBJECT_0) { - /* There was no data this time. Return zero bytes available, - but leave the Overlapped I/O running. */ - return 0; - } - } - - /* Either WaitForSingleObject() told us that ReadFile has completed, or - we are in non-blocking mode. Get the number of bytes read. The actual - data has been copied to the data[] array which was passed to ReadFile(). */ - res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); - } - /* Set pending back to false, even if GetOverlappedResult() returned error. */ - dev->read_pending = FALSE; - - if (res && bytes_read > 0) { - if (dev->read_buf[0] == 0x0) { - /* If report numbers aren't being used, but Windows sticks a report - number (0x0) on the beginning of the report anyway. To make this - work like the other platforms, and to make it work more like the - HID spec, we'll skip over this byte. */ - bytes_read--; - copy_len = length > bytes_read ? bytes_read : length; - memcpy(data, dev->read_buf+1, copy_len); - } - else { - /* Copy the whole buffer, report number and all. */ - copy_len = length > bytes_read ? bytes_read : length; - memcpy(data, dev->read_buf, copy_len); - } - } - -end_of_function: - if (!res) { - register_error(dev, "GetOverlappedResult"); - return -1; - } - - return (int) copy_len; -} - -int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) -{ - return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); -} - -int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) -{ - dev->blocking = !nonblock; - return 0; /* Success */ -} - -int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) -{ - BOOL res = FALSE; - unsigned char *buf; - size_t length_to_send; - - /* Windows expects at least caps.FeatureReportByteLength bytes passed - to HidD_SetFeature(), even if the report is shorter. Any less sent and - the function fails with error ERROR_INVALID_PARAMETER set. Any more - and HidD_SetFeature() silently truncates the data sent in the report - to caps.FeatureReportByteLength. */ - if (length >= dev->feature_report_length) { - buf = (unsigned char *) data; - length_to_send = length; - } else { - if (dev->feature_buf == NULL) - dev->feature_buf = (unsigned char *) malloc(dev->feature_report_length); - buf = dev->feature_buf; - memcpy(buf, data, length); - memset(buf + length, 0, dev->feature_report_length - length); - length_to_send = dev->feature_report_length; - } - - res = HidD_SetFeature(dev->device_handle, (PVOID)buf, (DWORD) length_to_send); - - if (!res) { - register_error(dev, "HidD_SetFeature"); - return -1; - } - - return (int) length; -} - -static int hid_get_report(hid_device *dev, DWORD report_type, unsigned char *data, size_t length) -{ - BOOL res; - DWORD bytes_returned = 0; - - OVERLAPPED ol; - memset(&ol, 0, sizeof(ol)); - - res = DeviceIoControl(dev->device_handle, - report_type, - data, (DWORD) length, - data, (DWORD) length, - &bytes_returned, &ol); - - if (!res) { - if (GetLastError() != ERROR_IO_PENDING) { - /* DeviceIoControl() failed. Return error. */ - register_error(dev, "Get Input/Feature Report DeviceIoControl"); - return -1; - } - } - - /* Wait here until the write is done. This makes - hid_get_feature_report() synchronous. */ - res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); - if (!res) { - /* The operation failed. */ - register_error(dev, "Get Input/Feature Report GetOverLappedResult"); - return -1; - } - - /* When numbered reports aren't used, - bytes_returned seem to include only what is actually received from the device - (not including the first byte with 0, as an indication "no numbered reports"). */ - if (data[0] == 0x0) { - bytes_returned++; - } - - return bytes_returned; -} - -int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) -{ - /* We could use HidD_GetFeature() instead, but it doesn't give us an actual length, unfortunately */ - return hid_get_report(dev, IOCTL_HID_GET_FEATURE, data, length); -} - -int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) -{ - /* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */ - return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length); -} - -void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) -{ - if (!dev) - return; - CancelIo(dev->device_handle); - free_hid_device(dev); -} - -int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - if (!dev->device_info || !string || !maxlen) - return -1; - - wcsncpy(string, dev->device_info->manufacturer_string, maxlen); - string[maxlen] = L'\0'; - - return 0; -} - -int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - if (!dev->device_info || !string || !maxlen) - return -1; - - wcsncpy(string, dev->device_info->product_string, maxlen); - string[maxlen] = L'\0'; - - return 0; -} - -int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) -{ - if (!dev->device_info || !string || !maxlen) - return -1; - - wcsncpy(string, dev->device_info->serial_number, maxlen); - string[maxlen] = L'\0'; - - return 0; -} - -int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) -{ - BOOL res; - - res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * (DWORD) MIN(maxlen, MAX_STRING_WCHARS)); - if (!res) { - register_error(dev, "HidD_GetIndexedString"); - return -1; - } - - return 0; -} - - -HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) -{ - if (dev) { - if (dev->last_error_str == NULL) - return L"Success"; - return (wchar_t*)dev->last_error_str; - } - - // Global error messages are not (yet) implemented on Windows. - return L"hid_error for global errors is not implemented yet"; -} - - -/*#define PICPGM*/ -/*#define S11*/ -#define P32 -#ifdef S11 - unsigned short VendorID = 0xa0a0; - unsigned short ProductID = 0x0001; -#endif - -#ifdef P32 - unsigned short VendorID = 0x04d8; - unsigned short ProductID = 0x3f; -#endif - - -#ifdef PICPGM - unsigned short VendorID = 0x04d8; - unsigned short ProductID = 0x0033; -#endif - - -#if 0 -int __cdecl main(int argc, char* argv[]) -{ - int res; - unsigned char buf[65]; - - UNREFERENCED_PARAMETER(argc); - UNREFERENCED_PARAMETER(argv); - - /* Set up the command buffer. */ - memset(buf,0x00,sizeof(buf)); - buf[0] = 0; - buf[1] = 0x81; - - - /* Open the device. */ - int handle = open(VendorID, ProductID, L"12345"); - if (handle < 0) - printf("unable to open device\n"); - - - /* Toggle LED (cmd 0x80) */ - buf[1] = 0x80; - res = write(handle, buf, 65); - if (res < 0) - printf("Unable to write()\n"); - - /* Request state (cmd 0x81) */ - buf[1] = 0x81; - write(handle, buf, 65); - if (res < 0) - printf("Unable to write() (2)\n"); - - /* Read requested state */ - read(handle, buf, 65); - if (res < 0) - printf("Unable to read()\n"); - - /* Print out the returned buffer. */ - for (int i = 0; i < 4; i++) - printf("buf[%d]: %d\n", i, buf[i]); - - return 0; -} -#endif - -#ifdef __cplusplus -} /* extern "C" */ -#endif diff --git a/packaging/CPackConfig.cmake b/packaging/CPackConfig.cmake index 62c70c79fc4..2ccabc533c5 100644 --- a/packaging/CPackConfig.cmake +++ b/packaging/CPackConfig.cmake @@ -19,8 +19,8 @@ set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-source") # dpkg --compare-versions 2.3~alpha~1234~g8163 lt 2.3~beta~1234~g8163 && echo true # dpkg --compare-versions 2.3~beta~1234~g8163 lt 2.3.0 && echo true # dpkg --compare-versions 2.3.0 lt 2.3.0+2345+g163 && echo true -if (PACKAGE_VERSION MATCHES "^[0-9]+\\.[0-9]+[A-Za-z0-9.+~-]*$") - if (PACKAGE_VERSION MATCHES "(alpha|beta)") +if(PACKAGE_VERSION MATCHES "^[0-9]+\\.[0-9]+[A-Za-z0-9.+~-]*$") + if(PACKAGE_VERSION MATCHES "(alpha|beta)") string(REPLACE "-" "~" CPACK_DEBIAN_PACKAGE_VERSION "${PACKAGE_VERSION}") else() string(REPLACE "-" "+" CPACK_DEBIAN_PACKAGE_VERSION "${PACKAGE_VERSION}") @@ -29,16 +29,16 @@ else() string(REPLACE "-" "~" CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_MIXXX_VERSION}") endif() -if (CPACK_GENERATOR STREQUAL "DEB") +if(CPACK_GENERATOR STREQUAL "DEB") set(CPACK_INSTALL_SCRIPT ${CPACK_DEBIAN_INSTALL_SCRIPT}) endif() -if (CPACK_GENERATOR STREQUAL "External") - if (DEB_SOURCEPKG OR DEB_UPLOAD_PPA OR DEB_BUILD) +if(CPACK_GENERATOR STREQUAL "External") + if(DEB_SOURCEPKG OR DEB_UPLOAD_PPA OR DEB_BUILD) set(CPACK_EXTERNAL_ENABLE_STAGING true) set(CPACK_INSTALLED_DIRECTORIES "${CPACK_DEBIAN_SOURCE_DIR};/") set(CPACK_IGNORE_FILES "${CPACK_SOURCE_IGNORE_FILES}") set(CPACK_INSTALL_CMAKE_PROJECTS "") - set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CPACK_DEBIAN_UPLOAD_PPA_SCRIPT}" ) - endif () + set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CPACK_DEBIAN_UPLOAD_PPA_SCRIPT}") + endif() endif() diff --git a/packaging/CPackDebInstall.cmake b/packaging/CPackDebInstall.cmake index a3198e1dd51..f5fc4d3d838 100644 --- a/packaging/CPackDebInstall.cmake +++ b/packaging/CPackDebInstall.cmake @@ -2,53 +2,65 @@ # The command is # cpack -G DEB -find_program( CPACK_DEBIAN_DEBHELPER dh_prep ) -if( NOT CPACK_DEBIAN_DEBHELPER ) - message( FATAL_ERROR "debhelper not found, required for cpack -G DEB" ) +find_program(CPACK_DEBIAN_DEBHELPER dh_prep) +if(NOT CPACK_DEBIAN_DEBHELPER) + message(FATAL_ERROR "debhelper not found, required for cpack -G DEB") endif() -find_program( CPACK_DEBIAN_MARKDOWN markdown ) -if( NOT CPACK_DEBIAN_MARKDOWN ) - message( FATAL_ERROR "markdown not found, required for cpack -G DEB" ) +find_program(CPACK_DEBIAN_MARKDOWN markdown) +if(NOT CPACK_DEBIAN_MARKDOWN) + message(FATAL_ERROR "markdown not found, required for cpack -G DEB") endif() -find_program( CPACK_DEBIAN_DOCBOOK_TO_MAN docbook-to-man ) -if( NOT CPACK_DEBIAN_DOCBOOK_TO_MAN ) - message( FATAL_ERROR "docbook-to-man not found, required for cpack -G DEB" ) +find_program(CPACK_DEBIAN_DOCBOOK_TO_MAN docbook-to-man) +if(NOT CPACK_DEBIAN_DOCBOOK_TO_MAN) + message(FATAL_ERROR "docbook-to-man not found, required for cpack -G DEB") endif() find_program(CPACK_DEBIAN_DEBCHANGE debchange) if(NOT CPACK_DEBIAN_DEBCHANGE) - message(FATAL_ERROR "debchange not found, required for cpack -G DEB" ) + message(FATAL_ERROR "debchange not found, required for cpack -G DEB") endif() # We create a temporary debian folder that the debhelper below run as usual. # The final debian folder is created independently by cpack -message( NOTICE "Creating temporary debian folder for debhelper" ) -file(COPY ${CPACK_DEBIAN_SOURCE_DIR}/packaging/debian - DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}) +message(NOTICE "Creating temporary debian folder for debhelper") +file( + COPY ${CPACK_DEBIAN_SOURCE_DIR}/packaging/debian + DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} +) set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS_EXTRA "libavformat-dev, ") -configure_file(${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control.in - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control - @ONLY) -file(REMOVE ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control.in) +configure_file( + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control.in + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control + @ONLY +) +file( + REMOVE + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control.in +) -file(COPY ${CPACK_DEBIAN_SOURCE_DIR}/res/linux/mixxx-usb-uaccess.rules - DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian) -file(RENAME - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx-usb-uaccess.rules - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx.mixxx-usb-uaccess.udev) +file( + COPY ${CPACK_DEBIAN_SOURCE_DIR}/res/linux/mixxx-usb-uaccess.rules + DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian +) +file( + RENAME + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx-usb-uaccess.rules + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx.mixxx-usb-uaccess.udev +) execute_process( - COMMAND ${CPACK_DEBIAN_DOCBOOK_TO_MAN} debian/mixxx.sgml - OUTPUT_FILE mixxx.1 - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} + COMMAND ${CPACK_DEBIAN_DOCBOOK_TO_MAN} debian/mixxx.sgml + OUTPUT_FILE mixxx.1 + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} ) execute_process( - COMMAND ${CPACK_DEBIAN_MARKDOWN} ${CPACK_DEBIAN_SOURCE_DIR}/CHANGELOG.md - OUTPUT_FILE NEWS.html - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian + COMMAND ${CPACK_DEBIAN_MARKDOWN} ${CPACK_DEBIAN_SOURCE_DIR}/CHANGELOG.md + OUTPUT_FILE NEWS.html + WORKING_DIRECTORY + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian ) if(DEB_BUILD) @@ -59,18 +71,31 @@ if(DEB_BUILD) ) endif() -execute_process(COMMAND ${CPACK_DEBIAN_DEBCHANGE} -v "${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}" -M "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}) -execute_process(COMMAND ${CPACK_DEBIAN_DEBCHANGE} -r -M "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}) +execute_process( + COMMAND + ${CPACK_DEBIAN_DEBCHANGE} -v + "${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}" -M + "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} +) +execute_process( + COMMAND + ${CPACK_DEBIAN_DEBCHANGE} -r -M "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} +) -function(run_dh DH_COMMAND) - execute_process(COMMAND ${DH_COMMAND} ${ARGV1} ${ARGV2} -P. - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} - RESULT_VARIABLE CPACK_DEBIAN_DH_RET) - if(NOT CPACK_DEBIAN_DH_RET EQUAL "0") - message(FATAL_ERROR "${DH_COMMAND} returned exit code ${CPACK_DEBIAN_DH_RET}") - endif() +function(run_dh dh_command) + execute_process( + COMMAND ${dh_command} ${ARGV1} ${ARGV2} -P. + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} + RESULT_VARIABLE CPACK_DEBIAN_DH_RET + ) + if(NOT CPACK_DEBIAN_DH_RET EQUAL "0") + message( + FATAL_ERROR + "${dh_command} returned exit code ${CPACK_DEBIAN_DH_RET}" + ) + endif() endfunction() # We don't need root, normally read as Rules-Requires-Root from debian/control @@ -84,5 +109,8 @@ run_dh(dh_installman) run_dh(dh_installudev --name=mixxx-usb-uaccess --priority=69) # Remove temporary files only needed by debhelpers -file (REMOVE_RECURSE ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian) -file (REMOVE ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/mixxx.1) +file( + REMOVE_RECURSE + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian +) +file(REMOVE ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/mixxx.1) diff --git a/packaging/CPackDebUploadPPA.cmake b/packaging/CPackDebUploadPPA.cmake index 69af8e187a4..3edbc0c29ce 100644 --- a/packaging/CPackDebUploadPPA.cmake +++ b/packaging/CPackDebUploadPPA.cmake @@ -6,64 +6,95 @@ find_program(CPACK_DEBIAN_DEBUILD debuild) if(NOT CPACK_DEBIAN_DEBUILD) - message(FATAL_ERROR "debuild not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" ) + message( + FATAL_ERROR + "debuild not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" + ) endif() if(DEB_UPLOAD_PPA) find_program(CPACK_DEBIAN_DPUT dput) if(NOT CPACK_DEBIAN_DPUT) - message(FATAL_ERROR "dput not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" ) + message( + FATAL_ERROR + "dput not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" + ) endif() endif() find_program(CPACK_DEBIAN_DEBCHANGE debchange) if(NOT CPACK_DEBIAN_DEBCHANGE) - message(FATAL_ERROR "debchange not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" ) + message( + FATAL_ERROR + "debchange not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" + ) endif() find_program(CPACK_DEBIAN_MARKDOWN markdown) if(NOT CPACK_DEBIAN_MARKDOWN) - message(FATAL_ERROR "markdown not found, required for cpack -G External -D DEB_UPLOAD_PPA=true") + message( + FATAL_ERROR + "markdown not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" + ) endif() find_program(CPACK_DEBIAN_DOCBOOK_TO_MAN docbook-to-man) if(NOT CPACK_DEBIAN_DOCBOOK_TO_MAN) - message(FATAL_ERROR "docbook-to-man not found, required for cpack -G External -D DEB_UPLOAD_PPA=true") + message( + FATAL_ERROR + "docbook-to-man not found, required for cpack -G External -D DEB_UPLOAD_PPA=true" + ) endif() # PR branches have no access to the pgp key. Don't sign. find_program(CPACK_DEBIAN_GPG gpg) if(CPACK_DEBIAN_GPG) - execute_process(COMMAND ${CPACK_DEBIAN_GPG} --fingerprint "${CPACK_PACKAGE_CONTACT}" - RESULT_VARIABLE CPACK_DEBIAN_GPG_RET) + execute_process( + COMMAND ${CPACK_DEBIAN_GPG} --fingerprint "${CPACK_PACKAGE_CONTACT}" + RESULT_VARIABLE CPACK_DEBIAN_GPG_RET + ) endif() if(NOT CPACK_DEBIAN_GPG_RET EQUAL "0") - message(WARNING "No secret key found for \"${CPACK_PACKAGE_CONTACT}\", skip signing" ) - SET(CPACK_DEBIAN_DEBUILD_NOSIGN "--no-sign") + message( + WARNING + "No secret key found for \"${CPACK_PACKAGE_CONTACT}\", skip signing" + ) + set(CPACK_DEBIAN_DEBUILD_NOSIGN "--no-sign") endif() message(NOTICE "Creating mixxx_${CPACK_DEBIAN_PACKAGE_VERSION}.orig.tar.gz") execute_process( - COMMAND tar -czf "mixxx_${CPACK_DEBIAN_PACKAGE_VERSION}.orig.tar.gz" ${CPACK_PACKAGE_FILE_NAME} + COMMAND + tar -czf "mixxx_${CPACK_DEBIAN_PACKAGE_VERSION}.orig.tar.gz" + ${CPACK_PACKAGE_FILE_NAME} WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY} ) # Save Git info from original working tree to allow building without git # uses GIT_BRANCH GIT_DESCRIBE GIT_COMMIT_DATE GIT_COMMIT_YEAR GIT_COMMIT_COUNT GIT_DIRTY configure_file( - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/src/gitinfo.h.in - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/src/gitinfo.h.in - @ONLY) + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/src/gitinfo.h.in + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/src/gitinfo.h.in + @ONLY +) -message( NOTICE "Creating debian folder" ) -file(COPY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/packaging/debian - DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}) -file(REMOVE ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control.in) +message(NOTICE "Creating debian folder") +file( + COPY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/packaging/debian + DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} +) +file( + REMOVE + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control.in +) execute_process( - COMMAND ${CPACK_DEBIAN_MARKDOWN} ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/CHANGELOG.md + COMMAND + ${CPACK_DEBIAN_MARKDOWN} + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/CHANGELOG.md OUTPUT_FILE NEWS.html - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian + WORKING_DIRECTORY + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian ) execute_process( @@ -72,11 +103,16 @@ execute_process( WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} ) -file(COPY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/res/linux/mixxx-usb-uaccess.rules - DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian) -file(RENAME - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx-usb-uaccess.rules - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx.mixxx-usb-uaccess.udev) +file( + COPY + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/res/linux/mixxx-usb-uaccess.rules + DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian +) +file( + RENAME + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx-usb-uaccess.rules + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/mixxx.mixxx-usb-uaccess.udev +) if(DEB_BUILD) execute_process( @@ -86,48 +122,72 @@ if(DEB_BUILD) ) endif() -foreach(RELEASE ${CPACK_DEBIAN_DISTRIBUTION_RELEASES}) - - if (RELEASE STREQUAL "bionic") - set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS_EXTRA "libmp4v2-dev,") +foreach(release ${CPACK_DEBIAN_DISTRIBUTION_RELEASES}) + if(release STREQUAL "jammy") + set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS_EXTRA "libqt6shadertools6-dev,") else() - set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS_EXTRA "libavformat-dev,") + set(CPACK_DEBIAN_PACKAGE_BUILD_DEPENDS_EXTRA "qt6-shadertools-dev,") endif() - configure_file(${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/packaging/debian/control.in - ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control - @ONLY) - - file(COPY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/packaging/debian/changelog - DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian) - execute_process(COMMAND ${CPACK_DEBIAN_DEBCHANGE} -v "${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}~${RELEASE}" -M "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}) - execute_process(COMMAND ${CPACK_DEBIAN_DEBCHANGE} -r -D ${RELEASE} -M "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}) - - if (DEB_UPLOAD_PPA OR DEB_SOURCEPKG) - execute_process(COMMAND ${CPACK_DEBIAN_DEBUILD} -S -sa -d ${CPACK_DEBIAN_DEBUILD_NOSIGN} - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} - RESULT_VARIABLE CPACK_DEBIAN_DEBUILD_RET) + configure_file( + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/packaging/debian/control.in + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian/control + @ONLY + ) + + file( + COPY + ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/packaging/debian/changelog + DESTINATION ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}/debian + ) + execute_process( + COMMAND + ${CPACK_DEBIAN_DEBCHANGE} -v + "${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}~${release}" + -M "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} + ) + execute_process( + COMMAND + ${CPACK_DEBIAN_DEBCHANGE} -r -D ${release} -M + "Build of ${CPACK_DEBIAN_PACKAGE_VERSION}" + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} + ) + + if(DEB_UPLOAD_PPA OR DEB_SOURCEPKG) + execute_process( + COMMAND ${CPACK_DEBIAN_DEBUILD} -S -sa -d ${CPACK_DEBIAN_DEBUILD_NOSIGN} + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} + RESULT_VARIABLE CPACK_DEBIAN_DEBUILD_RET + ) if(NOT CPACK_DEBIAN_DEBUILD_RET EQUAL "0") - message(FATAL_ERROR "${CPACK_DEBIAN_DEBUILD} returned exit code ${CPACK_DEBIAN_DEBUILD_RET}") + message( + FATAL_ERROR + "${CPACK_DEBIAN_DEBUILD} returned exit code ${CPACK_DEBIAN_DEBUILD_RET}" + ) endif() endif() - if (BUILD_MACHINE_RELEASE STREQUAL RELEASE AND DEB_BUILD) - execute_process(COMMAND ${CPACK_DEBIAN_DEBUILD} -b ${CPACK_DEBIAN_DEBUILD_NOSIGN} - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME}) + if(BUILD_MACHINE_RELEASE STREQUAL release AND DEB_BUILD) + execute_process( + COMMAND ${CPACK_DEBIAN_DEBUILD} -b ${CPACK_DEBIAN_DEBUILD_NOSIGN} + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE_NAME} + ) endif() if(DEB_UPLOAD_PPA) - execute_process(COMMAND ${CPACK_DEBIAN_DPUT} ${DEB_UPLOAD_PPA} "mixxx_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}~${RELEASE}_source.changes" - WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY}) + execute_process( + COMMAND + ${CPACK_DEBIAN_DPUT} ${DEB_UPLOAD_PPA} + "mixxx_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}~${release}_source.changes" + WORKING_DIRECTORY ${CPACK_TOPLEVEL_DIRECTORY} + ) endif() - -endforeach(RELEASE ${CPACK_DEBIAN_DISTRIBUTION_RELEASES}) +endforeach() if(DEB_SOURCEPKG OR DEB_BUILD) - file(GLOB ARTIFACTS - "${CPACK_TOPLEVEL_DIRECTORY}/mixxx_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}*" - "${CPACK_TOPLEVEL_DIRECTORY}/mixxx-dbgsym_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}*") - file(COPY ${ARTIFACTS} - DESTINATION ${CPACK_PACKAGE_DIRECTORY}) + file( + GLOB ARTIFACTS + "${CPACK_TOPLEVEL_DIRECTORY}/mixxx_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}*" + "${CPACK_TOPLEVEL_DIRECTORY}/mixxx-dbgsym_${CPACK_DEBIAN_PACKAGE_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}*" + ) + file(COPY ${ARTIFACTS} DESTINATION ${CPACK_PACKAGE_DIRECTORY}) endif() diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 246c4e72fe8..48d0c3e2c92 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,3 +1,15 @@ +mixxx (2.4.2-1~focal) focal; urgency=medium + + * Build of 2.4.2 + + -- RJ Skerry-Ryan Tue, 26 Nov 2024 23:29:32 +0000 + +mixxx (2.4.1-1~focal) focal; urgency=medium + + * Build of 2.4.1 + + -- RJ Skerry-Ryan Wed, 08 May 2024 21:39:18 +0000 + mixxx (2.4.0-1~focal) focal; urgency=medium * Build of 2.4.0 diff --git a/packaging/debian/control.in b/packaging/debian/control.in index ce0c2beb213..31dd10c9f32 100644 --- a/packaging/debian/control.in +++ b/packaging/debian/control.in @@ -23,7 +23,6 @@ Build-Depends: debhelper (>= 11), qml6-module-qt-labs-qmlmodels, libqt6core5compat6-dev, libqt6opengl6-dev, - libqt6shadertools6-dev, libqt6sql6-sqlite, libqt6svg6-dev, cmake (>= 3.13), @@ -34,6 +33,7 @@ Build-Depends: debhelper (>= 11), libogg-dev, libsndfile1-dev, libasound2-dev, + libavformat-dev, libvorbis-dev, libfaad-dev, libportmidi-dev, @@ -72,6 +72,7 @@ Package: mixxx Section: @CPACK_DEBIAN_PACKAGE_SECTION@ Architecture: linux-any Depends: ${shlibs:Depends}, ${misc:Depends}, @CPACK_DEBIAN_PACKAGE_DEPENDS@ +Recommends: @CPACK_DEBIAN_PACKAGE_RECOMMENDS@ Suggests: @CPACK_DEBIAN_PACKAGE_SUGGESTS@ Replaces: mixxx-data Description: @CPACK_DEBIAN_PACKAGE_DESCRIPTION_MERGED@ diff --git a/packaging/debian/copyright b/packaging/debian/copyright index 4723547d474..9a8a6dde503 100644 --- a/packaging/debian/copyright +++ b/packaging/debian/copyright @@ -6,7 +6,7 @@ Source: https://downloads.mixxx.org/ Files: * Copyright: - 2001-2024 Mixxx development team + 2001-2025 Mixxx development team License: GPL-2+ License: GPL-2+ diff --git a/packaging/wix/LICENSE.rtf.in b/packaging/wix/LICENSE.rtf.in index e7cbf4b8125..8b2cbf40480 100644 --- a/packaging/wix/LICENSE.rtf.in +++ b/packaging/wix/LICENSE.rtf.in @@ -2,7 +2,7 @@ {\colortbl ;\red0\green0\blue255;} {\*\generator Riched20 10.0.14393}\viewkind4\uc1 \pard\qj\ul\b\f0\fs22\lang1036 Mixxx @CMAKE_PROJECT_VERSION@, Digital DJ'ing software.\ulnone\b0\fs24\par -\fs22 Copyright (C) 2001-2024 Mixxx Development Team\par +\fs22 Copyright (C) 2001-2025 Mixxx Development Team\par \par Promotional tracks are copyright their respective owners and\par distributed with permission.\par diff --git a/res/controllers/.eslintrc.json b/res/controllers/.eslintrc.json deleted file mode 100644 index 2a6722aac48..00000000000 --- a/res/controllers/.eslintrc.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "globals": { - "ColorMapper": "readonly", - "_": "readonly", - "components": "readonly", - "engine": "readonly", - "midi": "readonly", - "printObject": "readonly", - "stringifyObject": "readonly", - "arrayContains": "readonly", - "secondstominutes": "readonly", - "msecondstominutes": "readonly", - "colorCodeToObject": "readonly", - "colorCodeFromObject": "readonly", - "script": "readonly", - "bpm": "readonly", - "ButtonState": "readonly", - "LedState": "readonly", - "Controller": "readonly", - "Button": "readonly", - "Control": "readonly", - "Deck": "readonly" - } -} diff --git a/res/controllers/Behringer BCR2000.midi.xml b/res/controllers/Behringer BCR2000.midi.xml index 9f557abd74e..a03ad4935c3 100644 --- a/res/controllers/Behringer BCR2000.midi.xml +++ b/res/controllers/Behringer BCR2000.midi.xml @@ -1,5 +1,5 @@ - + Behringer BCR2000 Christian @@ -7,7 +7,6 @@ - diff --git a/res/controllers/Behringer DDM4000.midi.xml b/res/controllers/Behringer DDM4000.midi.xml index 64b92d076cd..873d83e6f25 100644 --- a/res/controllers/Behringer DDM4000.midi.xml +++ b/res/controllers/Behringer DDM4000.midi.xml @@ -1,5 +1,5 @@ - + Behringer DDM4000 Christian @@ -7,7 +7,6 @@ - diff --git a/res/controllers/Behringer-BCR2000-preset-scripts.js b/res/controllers/Behringer-BCR2000-preset-scripts.js index 14524c14111..d98e6c6cca3 100644 --- a/res/controllers/Behringer-BCR2000-preset-scripts.js +++ b/res/controllers/Behringer-BCR2000-preset-scripts.js @@ -4,22 +4,22 @@ (function(global) { /* Controller-specific constants */ - var ROW_SIZE = 8; - var PUSHENCODERGROUP_COUNT = 4; - var BUTTONROW_COUNT = 2; - var ENCODERROW_COUNT = 3; - var BUTTONBOX_SIZE = 4; - var PRESET_MIN = 1; - var PRESET_MAX = 32; - var STATUS_CONTROL_CHANGE = 0xB0; - var STATUS_PROGRAM_CHANGE = 0xC0; + const ROW_SIZE = 8; + const PUSHENCODERGROUP_COUNT = 4; + const BUTTONROW_COUNT = 2; + const ENCODERROW_COUNT = 3; + const BUTTONBOX_SIZE = 4; + const PRESET_MIN = 1; + const PRESET_MAX = 32; + const STATUS_CONTROL_CHANGE = 0xB0; + const STATUS_PROGRAM_CHANGE = 0xC0; /* Preset-specific constants */ - var PUSHENCODERGROUP_START = 0x01; - var PUSHENCODERGROUP_BUTTON_OFFSET = 0x20; - var BUTTONROW_START = 0x41; - var ENCODERROW_START = 0x51; - var BUTTONBOX_START = 0x69; + const PUSHENCODERGROUP_START = 0x01; + const PUSHENCODERGROUP_BUTTON_OFFSET = 0x20; + const BUTTONROW_START = 0x41; + const ENCODERROW_START = 0x51; + const BUTTONBOX_START = 0x69; /** * Select a preset in the controller. @@ -27,7 +27,7 @@ * @param {number} A preset number (integer 1..32) * @public */ - var setPreset = function(presetNumber) { + const setPreset = function(presetNumber) { if (presetNumber) { presetNumber = Math.max(PRESET_MIN, presetNumber); presetNumber = Math.min(PRESET_MAX, presetNumber); @@ -43,7 +43,7 @@ * @return {Array} Array containing values * @private */ - var createElements = function(size, elementFactory) { + const createElements = function(size, elementFactory) { return Object.keys(Array.apply(0, Array(size))).map( function(_v, i) { return elementFactory.call(this, i); }); }; @@ -64,10 +64,10 @@ * @return {Array} Array of MIDI addresses for the given range * @private */ - var calculateRange = function(startAddress, rangeNumber, size) { + const calculateRange = function(startAddress, rangeNumber, size) { size = size || ROW_SIZE; - var rangeOffset = rangeNumber * ROW_SIZE; - var rangeStart = startAddress + rangeOffset; + const rangeOffset = rangeNumber * ROW_SIZE; + const rangeStart = startAddress + rangeOffset; return createElements(size, function(i) { return rangeStart + i; }); }; @@ -81,7 +81,7 @@ * @return {Array} Address range for the encoders in the encoder group * @private */ - var createPushEncoderGroup = function(groupNumber) { + const createPushEncoderGroup = function(groupNumber) { return calculateRange(PUSHENCODERGROUP_START, groupNumber).map(function(encoder) { return {"encoder": encoder, "button": encoder + PUSHENCODERGROUP_BUTTON_OFFSET}; }); @@ -95,7 +95,7 @@ * @return {Array} Address range for the encoders in the encoder row * @private */ - var createEncoderRow = function(rowNumber) { + const createEncoderRow = function(rowNumber) { return calculateRange(ENCODERROW_START, rowNumber); }; @@ -107,7 +107,7 @@ * @return {Array} Address range for the buttons in the button row * @private */ - var createButtonRow = function(rowNumber) { + const createButtonRow = function(rowNumber) { return calculateRange(BUTTONROW_START, rowNumber); }; @@ -118,17 +118,17 @@ * @return {Array} Address range for the buttons in the button box * @private */ - var createButtonBox = function() { + const createButtonBox = function() { return calculateRange(BUTTONBOX_START, 0, BUTTONBOX_SIZE); }; /* Definition of MIDI controls */ - var pushEncoderGroups = createElements(PUSHENCODERGROUP_COUNT, createPushEncoderGroup); - var buttonRows = createElements(BUTTONROW_COUNT, createButtonRow); - var encoderRows = createElements(ENCODERROW_COUNT, createEncoderRow); - var buttonBox = createButtonBox(); + const pushEncoderGroups = createElements(PUSHENCODERGROUP_COUNT, createPushEncoderGroup); + const buttonRows = createElements(BUTTONROW_COUNT, createButtonRow); + const encoderRows = createElements(ENCODERROW_COUNT, createEncoderRow); + const buttonBox = createButtonBox(); - var exports = {}; + const exports = {}; exports.STATUS_CONTROL_CHANGE = STATUS_CONTROL_CHANGE; exports.setPreset = setPreset; exports.pushEncoderGroups = pushEncoderGroups; diff --git a/res/controllers/Behringer-BCR2000-scripts.js b/res/controllers/Behringer-BCR2000-scripts.js index 9d151908d35..9369a3f6e2c 100644 --- a/res/controllers/Behringer-BCR2000-scripts.js +++ b/res/controllers/Behringer-BCR2000-scripts.js @@ -9,10 +9,10 @@ var BCR2000 = new behringer.extension.GenericMidiController({ configurationProvider: function() { /* Shortcut variables */ - var c = components; - var e = behringer.extension; - var p = BCR2000Preset; - var cc = p.STATUS_CONTROL_CHANGE; + const c = components; + const e = behringer.extension; + const p = BCR2000Preset; + const cc = p.STATUS_CONTROL_CHANGE; return { init: function() { diff --git a/res/controllers/Behringer-DDM4000-scripts.js b/res/controllers/Behringer-DDM4000-scripts.js index bf98175b9f2..d89e850e724 100644 --- a/res/controllers/Behringer-DDM4000-scripts.js +++ b/res/controllers/Behringer-DDM4000-scripts.js @@ -8,18 +8,18 @@ var behringer = behringer; var DDM4000 = new behringer.extension.GenericMidiController({ configurationProvider: function() { - var DEFAULT_LONGPRESS_DURATION = 500; - var DEFAULT_BLINK_DURATION = 425; - var THROTTLE_DELAY = 40; + const DEFAULT_LONGPRESS_DURATION = 500; + const DEFAULT_BLINK_DURATION = 425; + const THROTTLE_DELAY = 40; /* Shortcut variables */ - var c = components; - var e = behringer.extension; - var cc = 0xB0; - var note = 0x90; - var toggle = c.Button.prototype.types.toggle; + const c = components; + const e = behringer.extension; + const cc = 0xB0; + const note = 0x90; + const toggle = c.Button.prototype.types.toggle; - var CrossfaderAssignLED = function(options) { + const CrossfaderAssignLED = function(options) { options = options || {}; options.outKey = options.outKey || "orientation"; e.CustomButton.call(this, options); @@ -31,16 +31,16 @@ var DDM4000 = new behringer.extension.GenericMidiController({ right: 2 } }); - var left = CrossfaderAssignLED.prototype.position.left; - var center = CrossfaderAssignLED.prototype.position.center; - var right = CrossfaderAssignLED.prototype.position.right; + const left = CrossfaderAssignLED.prototype.position.left; + const center = CrossfaderAssignLED.prototype.position.center; + const right = CrossfaderAssignLED.prototype.position.right; - var CrossfaderUnit = function(options) { - var unitOptions = options || {}; + const CrossfaderUnit = function(options) { + const unitOptions = options || {}; unitOptions.group = unitOptions.group || "[Master]"; c.ComponentContainer.call(this, unitOptions); - var Crossfader = function(options) { + const Crossfader = function(options) { options = options || {}; options.inKey = options.inKey || options.key || "crossfader"; options.group = options.group || unitOptions.group; @@ -56,9 +56,9 @@ var DDM4000 = new behringer.extension.GenericMidiController({ engine.setValue("[Master]", "crossfader_set_default", 1); }, }); - var crossfader = new Crossfader(options.crossfader); + const crossfader = new Crossfader(options.crossfader); - var CrossfaderToggleButton = function(options) { + const CrossfaderToggleButton = function(options) { options = options || {}; if (options.type === undefined) { options.type = c.Button.prototype.types.toggle; @@ -96,7 +96,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ * @param {number} options Options object * @public */ - var CrossfaderReverseTapButton = function(options) { + const CrossfaderReverseTapButton = function(options) { options = options || {}; options.inKey = options.inKey || "xFaderReverse"; c.Button.call(this, options); @@ -108,7 +108,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ }, }); - var Blinker = function(target, blinkDuration, outValueScale) { + const Blinker = function(target, blinkDuration, outValueScale) { this.target = target; this.outValueScale = outValueScale || components.Component.prototype.outValueScale; @@ -125,11 +125,11 @@ var DDM4000 = new behringer.extension.GenericMidiController({ }, }; - var SamplerBank = function(bankOptions) { + const SamplerBank = function(bankOptions) { c.ComponentContainer.call(this); - var bank = this; + const bank = this; - var PlayButton = function(options) { + const PlayButton = function(options) { options = options || {}; options.inKey = options.inKey || "cue_gotoandplay"; options.outKey = options.outKey || "track_loaded"; @@ -151,7 +151,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ group: bankOptions.group, }); - var PlayIndicatorLED = function(options) { + const PlayIndicatorLED = function(options) { options = options || {}; options.outKey = options.outKey || "play_indicator"; this.blinker = new Blinker(this, options.blinkDuration); @@ -171,7 +171,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ blinkDuration: DEFAULT_BLINK_DURATION, }); - var ReverseMode = function(options) { + const ReverseMode = function(options) { options = options || {}; options.key = options.key || "reverse"; c.Button.call(this, options); @@ -179,7 +179,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ ReverseMode.prototype = e.deriveFrom(c.Button); this.reverseMode = new ReverseMode({midi: bankOptions.reverse, group: bankOptions.group}); - var LoopMode = function(options) { + const LoopMode = function(options) { options = options || {}; options.key = options.inKey || "beatloop_activate"; c.Button.call(this, options); @@ -187,11 +187,11 @@ var DDM4000 = new behringer.extension.GenericMidiController({ }; LoopMode.prototype = e.deriveFrom(c.Button, { outValueScale: function(value) { - var button = c.Button.prototype; + const button = c.Button.prototype; bank.playButton.type = value ? button.types.toggle : button.types.push; if (!value) { - var beatloopSize = engine.getValue(this.group, "beatloop_size"); - var key = "beatloop_" + beatloopSize; + const beatloopSize = engine.getValue(this.group, "beatloop_size"); + const key = `beatloop_${beatloopSize}`; engine.setValue(this.group, key, 0); } return button.outValueScale(value); @@ -199,7 +199,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ }); this.loopMode = new LoopMode({midi: bankOptions.loop, group: bankOptions.group}); - var ModeButton = function(options) { + const ModeButton = function(options) { options = options || {}; options.key = options.key || "mode"; options.longPressTimeout = options.longPressTimeout || DEFAULT_LONGPRESS_DURATION; diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index cc912e64d3a..d65070c2891 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -4,10 +4,53 @@ (function(global) { /** @private */ - var components = global.components; + const components = global.components; /** @private */ - var engine = global.engine; + const engine = global.engine; + + /** + * Determines the merge strategy for a value when merging a component container definition. + * + * @param value anything + * @returns {boolean} merge strategy for the value: true for 'assign', false for 'deep merge' + * @private + */ + const doAssign = value => { + return typeof value !== "object" + || value === null + || value instanceof components.ComponentContainer + || value instanceof components.Component; + }; + + /** + * Merges a list of component container definitions into a target object. + * + * This is not a generic deep merge algorithm for JS objects. It does not handle circular references. + * If a definition contains a reference to an object instance of a component or component container + * (e.g. `ShiftButton.target`), the instance is taken over by reference, no deep copy is made. + * + * @param {object} targetObject Target object for the merged definitions + * @param {Array} sources Source definitions + * @returns {object} The target object + * @see `GenericMidiController` on component container definition + * @private + */ + const mergeDefinitions = (targetObject, ...sources) => sources.reduce((target, source) => { + for (const [key, value] of Object.entries(source || {})) { + if (doAssign(value)) { + target[key] = value; + } else if (value !== undefined) { + if (Array.isArray(value) && !Array.isArray(target[key])) { + target[key] = []; + } else if (typeof target[key] !== "object" || target[key] === null) { + target[key] = {}; + } + mergeDefinitions(target[key], value); + } + }; + return target; + }, targetObject); /** * Contains functions to print a message to the log. @@ -16,7 +59,7 @@ * @param {string} message Message * @private */ - var log = { + const log = { debug: function(message) { if (this.debug) { print("[DEBUG] " + message); @@ -34,10 +77,10 @@ * Determine an ID from a component's MIDI address * * @param {Array} midiAddress MIDI address consisting of two integers - * @return {string} ID for the MIDI address; `undefined` on error + * @returns {string} ID for the MIDI address; `undefined` on error * @private */ - var findComponentId = function(midiAddress) { + const findComponentId = function(midiAddress) { if (Array.isArray(midiAddress) && midiAddress.length === 2 && typeof midiAddress[0] === "number" && typeof midiAddress[1] === "number") { return "[" + midiAddress.map(function(x) { @@ -51,17 +94,17 @@ * Create a human-readable string to identify a component. * * @param {components.Component} component A component - * @return {string} A short string that describes the component; `undefined` on error + * @returns {string} A short string that describes the component; `undefined` on error * @private */ - var stringifyComponent = function(component) { + const stringifyComponent = function(component) { if (!component) { return; } - var key = component.inKey || component.outKey; - var value = component.group + "," + key; + const key = component.inKey || component.outKey; + let value = `${component.group},${key}`; if (component.midi) { - var id = findComponentId(component.midi); + const id = findComponentId(component.midi); if (id !== undefined) { value = id + ": " + value; } @@ -75,7 +118,7 @@ * @param {number} value A number between 0 and 1. * @private */ - var convertToMidiValue = function(value) { + const convertToMidiValue = function(value) { /* * Math.round() is important to keep input and output in sync. * Example: @@ -92,11 +135,11 @@ * * @param {object} parent Constructor of parent whose prototype is used as base * @param {object} members Own members that are not inherited - * @return {object} A new prototype based on parent with the given members + * @returns {object} A new prototype based on parent with the given members * @private */ - var deriveFrom = function(parent, members) { - return _.merge(Object.create(parent.prototype), members || {}); + const deriveFrom = function(parent, members) { + return Object.assign(Object.create(parent.prototype), members); }; /** @@ -107,7 +150,7 @@ * @private * @see `Throttler` */ - var throttle = function(action, owner) { + const throttle = function(action, owner) { if (owner.throttler) { owner.throttler.schedule(action, owner); } else { @@ -123,7 +166,7 @@ * @param {object} options Options object * @public */ - var ParameterComponent = function(options) { + const ParameterComponent = function(options) { components.Component.call(this, options); }; ParameterComponent.prototype = deriveFrom(components.Component, { @@ -145,7 +188,7 @@ * @param {components.Component|components.ComponentContainer} options.target Target component * @public */ - var ShiftButton = function(options) { + const ShiftButton = function(options) { components.Button.call(this, options); }; ShiftButton.prototype = deriveFrom(components.Button, { @@ -167,7 +210,7 @@ * @param {object} options Options object * @public */ - var Trigger = function(options) { + const Trigger = function(options) { components.Component.call(this, options); }; Trigger.prototype = deriveFrom(components.Component, { @@ -183,7 +226,7 @@ * @param {number} options.offValue Value for `off`; optional, default: opposite of `onValue` * @public */ - var CustomButton = function(options) { + const CustomButton = function(options) { options = options || {}; if (options.onValue === undefined) { // do not use '||' to allow 0 options.onValue = 1; @@ -230,8 +273,8 @@ * @public * @see https://github.com/mixxxdj/mixxx/wiki/Script-Timers */ - var Timer = function(options) { - _.assign(this, options); + const Timer = function(options) { + Object.assign(this, options); this.disable(); }; Timer.prototype = { @@ -271,10 +314,10 @@ * @param {number} options.delay Minimal delay between two consecutive actions (in ms) * @public */ - var Throttler = function(options) { + const Throttler = function(options) { options = options || {}; options.delay = options.delay || 0; - _.assign(this, options); + Object.assign(this, options); this.locked = false; this.jobs = []; this.unlockTimer = new Timer( @@ -288,14 +331,14 @@ notify: function() { if (this.jobs.length > 0 && this.acquireLock()) { - var job = this.jobs.shift(); + const job = this.jobs.shift(); job.action.call(job.owner); this.unlockTimer.start(); } }, acquireLock: function() { - var unlocked = !this.locked; + const unlocked = !this.locked; if (unlocked) { this.locked = true; } @@ -316,9 +359,9 @@ * @param {object} options Options object * @public */ - var LongPressButton = function(options) { + const LongPressButton = function(options) { components.Button.call(this, options); - var action = function() { + const action = function() { this.isLongPressed = true; this.onLongPress(); }; @@ -356,9 +399,9 @@ * @param {number} options.blinkDuration Blink duration in ms; optional, default: 500 * @public */ - var BlinkingButton = function(options) { + const BlinkingButton = function(options) { options = options || {}; - var blinkAction = function() { + const blinkAction = function() { this.send(components.Button.prototype.outValueScale.call( this, this.flashing = !this.flashing)); }; @@ -393,14 +436,14 @@ * @param {object} options Options object * @public */ - var DirectionEncoder = function(options) { + const DirectionEncoder = function(options) { components.Encoder.call(this, options); this.previousValue = this.inGetValue(); // available only after call of Encoder constructor }; DirectionEncoder.prototype = deriveFrom(components.Encoder, { min: 0, inValueScale: function(value) { - var direction = 0; + let direction = 0; if (!(this.relative && this.isShifted)) { if (value > this.previousValue || value === this.max) { direction = 1; @@ -428,13 +471,13 @@ * @param {number} options.bound A positive integer defining the range bounds * @public */ - var RangeAwareEncoder = function(options) { + const RangeAwareEncoder = function(options) { components.Encoder.call(this, options); }; RangeAwareEncoder.prototype = deriveFrom(components.Encoder, { outValueScale: function(value) { /* -bound..+bound => 0..1 */ - var normalizedValue = (value + this.bound) / (2 * this.bound); + const normalizedValue = (value + this.bound) / (2 * this.bound); /* 0..1 => 0..127 */ return convertToMidiValue.call(this, normalizedValue); }, @@ -449,7 +492,7 @@ * @param {number} options.bound A positive integer defining the range bounds * @public */ - var RangeAwarePot = function(options) { + const RangeAwarePot = function(options) { components.Pot.call(this, options); }; RangeAwarePot.prototype = deriveFrom(components.Pot, { @@ -469,7 +512,7 @@ * @param {number} options.maxValue A positive integer defining the maximum enumeration value * @public */ - var EnumToggleButton = function(options) { + const EnumToggleButton = function(options) { options = options || {}; if (options.maxValue === undefined && options.values === undefined) { log.error("An EnumToggleButton requires either `values` or a `maxValue`."); @@ -483,9 +526,9 @@ EnumToggleButton.prototype = deriveFrom(components.Button, { input: function(channel, control, value, status, _group) { if (this.isPress(channel, control, value, status)) { - var newValue; + let newValue; if (this.values) { - var index = this.values.indexOf(this.inGetValue()); + const index = this.values.indexOf(this.inGetValue()); newValue = this.values[(index + 1) % this.values.length]; } else { newValue = (this.inGetValue() + 1) % (this.maxValue + 1); @@ -506,7 +549,7 @@ * @public * @see https://github.com/mixxxdj/mixxx/wiki/Midi-Scripting#soft-takeover */ - var EnumEncoder = function(options) { + const EnumEncoder = function(options) { options = options || {}; if (options.values === undefined) { log.error("EnumEncoder constructor was called without specifying enum values."); @@ -520,7 +563,7 @@ }; EnumEncoder.prototype = deriveFrom(components.Encoder, { input: function(_channel, _control, value, _status, _group) { - var scaledValue = this.inValueScale(value); + const scaledValue = this.inValueScale(value); if (!this.softTakeover || this.previousValue === undefined || this.previousValue === this.inGetValue()) { @@ -529,14 +572,14 @@ this.previousValue = scaledValue; }, inValueScale: function(value) { - var normalizedValue = value / this.max; - var index = Math.round(normalizedValue * this.maxIndex); + const normalizedValue = value / this.max; + const index = Math.round(normalizedValue * this.maxIndex); return this.values[index]; }, outValueScale: function(value) { - var index = this.values.indexOf(value); + const index = this.values.indexOf(value); if (index !== -1) { - var normalizedValue = index / this.maxIndex; + const normalizedValue = index / this.maxIndex; return convertToMidiValue.call(this, normalizedValue); } else { log.warn("'" + value + "' is not in supported values " + "[" + this.values + "]"); @@ -552,7 +595,7 @@ * @param {object} options Options object * @public */ - var LoopEncoder = function(options) { + const LoopEncoder = function(options) { options = options || {}; if (options.values === undefined) { /* taken from src/engine/controls/loopingcontrol.cpp */ @@ -577,7 +620,7 @@ * @param {string} options.sizeControl (optional) Name of a control that contains `size` * @public */ - var LoopMoveEncoder = function(options) { + const LoopMoveEncoder = function(options) { options = options || {}; options.inKey = options.inKey || "loop_move"; options.size = options.size || 0.5; @@ -585,8 +628,8 @@ }; LoopMoveEncoder.prototype = deriveFrom(DirectionEncoder, { inValueScale: function(value) { - var direction = DirectionEncoder.prototype.inValueScale.call(this, value); - var beats = this.sizeControl + const direction = DirectionEncoder.prototype.inValueScale.call(this, value); + const beats = this.sizeControl ? engine.getValue(this.group, this.sizeControl) : this.size; return direction * beats; @@ -601,18 +644,18 @@ * @param {object} options Options object * @public */ - var BackLoopButton = function(options) { + const BackLoopButton = function(options) { options = options || {}; options.key = options.key || "loop_enabled"; components.Button.call(this, options); }; BackLoopButton.prototype = deriveFrom(components.Button, { inSetValue: function(value) { - var script = global.script; - var group = this.group; + const script = global.script; + const group = this.group; if (value) { - var loopSize = engine.getValue(group, "beatloop_size"); - var beatjumpSize = engine.getValue(group, "beatjump_size"); + const loopSize = engine.getValue(group, "beatloop_size"); + const beatjumpSize = engine.getValue(group, "beatjump_size"); engine.setValue(group, "beatjump_size", loopSize); script.triggerControl(group, "beatjump_backward"); script.triggerControl(group, "beatloop_activate"); @@ -632,7 +675,7 @@ * (`0`: additive, `1`: constant) * @public */ - var CrossfaderCurvePot = function(options) { + const CrossfaderCurvePot = function(options) { options = options || {}; options.group = options.group || "[Mixer Profile]"; if (options.mode) { @@ -674,7 +717,7 @@ * controller * @public */ - var Publisher = function(options) { + const Publisher = function(options) { if (options.source === undefined) { log.error("Missing source component"); return; @@ -701,16 +744,16 @@ }, }); - var EffectUnit = function(rack, deckGroup) { + const EffectUnit = function(rack, deckGroup) { components.ComponentContainer.call(this); - var effectGroup = "[" + rack + "_" + deckGroup + "_Effect1]"; - var channelGroup = "[" + rack + "_" + deckGroup + "]"; + const effectGroup = `[${rack}_${deckGroup}_Effect1]`; + const channelGroup = `[${rack}_${deckGroup}]`; - var ParameterKnob = function(parameterNumber) { + const ParameterKnob = function(parameterNumber) { components.Pot.call(this, {group: effectGroup, key: "parameter" + parameterNumber}); }; ParameterKnob.prototype = deriveFrom(components.Pot); - var ParameterButton = function(parameterNumber) { + const ParameterButton = function(parameterNumber) { components.Button.call(this, {group: effectGroup, key: "button_parameter" + parameterNumber}); }; ParameterButton.prototype = deriveFrom( @@ -723,16 +766,14 @@ this.mix = new components.Pot({group: channelGroup, key: "mix"}); this.parameterKnobs = new components.ComponentContainer(); - var parameterKnobCount = engine.getValue(effectGroup, "num_parameters"); - for (var knobIndex = 1; knobIndex <= parameterKnobCount; knobIndex++) { - this.parameterKnobs[knobIndex] = new ParameterKnob(knobIndex); - } + const parameterKnobCount = engine.getValue(effectGroup, "num_parameters"); + [...Array(parameterKnobCount)].map((x, i) => i+1).forEach(knobIndex => + this.parameterKnobs[knobIndex] = new ParameterKnob(knobIndex)); this.parameterButtons = new components.ComponentContainer(); - var parameterButtonCount = engine.getValue(effectGroup, "num_button_parameters"); - for (var buttonIndex = 1; buttonIndex <= parameterButtonCount; buttonIndex++) { - this.parameterButtons[buttonIndex] = new ParameterButton(buttonIndex); - } + const parameterButtonCount = engine.getValue(effectGroup, "num_button_parameters"); + [...Array(parameterButtonCount)].map((x, i) => i+1).forEach(buttonIndex => + this.parameterButtons[buttonIndex] = new ParameterButton(buttonIndex)); }; EffectUnit.prototype = deriveFrom(components.ComponentContainer); @@ -757,7 +798,7 @@ * @yields {EqualizerUnit} * @public */ - var EqualizerUnit = function(deckGroup) { + const EqualizerUnit = function(deckGroup) { EffectUnit.call(this, "EqualizerRack1", deckGroup); }; EqualizerUnit.prototype = deriveFrom(EffectUnit); @@ -786,7 +827,7 @@ * @yields {QuickEffectUnit} * @public */ - var QuickEffectUnit = function(deckGroup) { + const QuickEffectUnit = function(deckGroup) { EffectUnit.call(this, "QuickEffectRack1", deckGroup); }; QuickEffectUnit.prototype = deriveFrom(EffectUnit); @@ -798,7 +839,7 @@ * @param {Array} initialContainers Initial container names * @public */ - var ComponentRegistry = function(initialContainers) { + const ComponentRegistry = function(initialContainers) { this.containers = new components.ComponentContainer(); if (Array.isArray(initialContainers)) { initialContainers.forEach(function(name) { this.createContainer(name); }, this); @@ -810,7 +851,7 @@ * Create a new ComponentContainer within this registry. * * @param {string} name Name of the ComponentContainer - * @return {components.ComponentContainer} The ComponentContainer; `undefined` on error + * @returns {components.ComponentContainer} The ComponentContainer; `undefined` on error * @public */ createContainer: function(name) { @@ -827,7 +868,7 @@ * Retrieve an existing ComponentContainer. * * @param {string} name Name of an existing ComponentContainer - * @return {components.ComponentContainer} The ComponentContainer; `undefined` on error + * @returns {components.ComponentContainer} The ComponentContainer; `undefined` on error * @public */ getContainer: function(name) { @@ -884,7 +925,7 @@ * * @param {components.Component} component A component * @param {string} containerName Name of a ComponentContainer - * @return {string} ID of the stored component; `undefined` on error + * @returns {string} ID of the stored component; `undefined` on error * @public */ register: function(component, containerName) { @@ -897,14 +938,14 @@ + stringifyComponent(component) + " without MIDI address"); return; } - var id = findComponentId(component.midi); + const id = findComponentId(component.midi); if (!Object.prototype.hasOwnProperty.call(this.containers, containerName)) { this.createContainer(containerName); } - var container = this.getContainer(containerName); - var store = true; + const container = this.getContainer(containerName); + let store = true; if (Object.prototype.hasOwnProperty.call(container, id)) { - var old = container[id]; + const old = container[id]; if (old !== component) { this.unregister(old, containerName); } else { @@ -926,13 +967,13 @@ * * @param {components.Component} component A component * @param {string} containerName Name of an existing ComponentContainer - * @return {string} ID of the removed component; `undefined` on error + * @returns {string} ID of the removed component; `undefined` on error * @public */ unregister: function(component, containerName) { log.debug(containerName + ": unregister " + stringifyComponent(component)); - var container = this.getContainer(containerName); - var id = findComponentId(component.midi); + const container = this.getContainer(containerName); + const id = findComponentId(component.midi); delete container[id]; return id; }, @@ -961,7 +1002,7 @@ * @param {boolean} options.debug Optional flag to emit debug messages to the log * @public */ - var LayerManager = function(options) { + const LayerManager = function(options) { this.componentRegistry = new ComponentRegistry([ LayerManager.prototype.defaultContainerName, LayerManager.prototype.shiftContainerName]); @@ -977,11 +1018,11 @@ /** * Retrieve the Default layer. * - * @return {object} The Default layer + * @returns {object} The Default layer * @private */ defaultLayer: function() { - var defaultContainer = this.componentRegistry.getContainer(this.defaultContainerName); + const defaultContainer = this.componentRegistry.getContainer(this.defaultContainerName); return Object.keys(this.shiftLayer()).reduce( function(shiftCounterparts, name) { shiftCounterparts[name] = defaultContainer[name]; @@ -992,7 +1033,7 @@ /** * Retrieve the Shift layer. * - * @return {object} The Shift layer + * @returns {object} The Shift layer * @private */ shiftLayer: function() { @@ -1004,20 +1045,20 @@ * * @param {number} status First byte of the component's MIDI address * @param {number} control Second byte of the component's MIDI address - * @return {components.Component} Component on the active layer matching the MIDI address; + * @returns {components.Component} Component on the active layer matching the MIDI address; * undefined on error. When the active layer does not contain * a matching component, the Default layer is used as * fallback. * @private */ findComponent: function(status, control) { - var id = findComponentId([status, control]); + const id = findComponentId([status, control]); if (id === undefined) { return; } - var component = this.activeLayer[id]; + let component = this.activeLayer[id]; if (component === undefined) { - var defaultComponents + const defaultComponents = this.componentRegistry.getContainer(this.defaultContainerName); component = defaultComponents[id]; } @@ -1035,11 +1076,11 @@ * @param {LayerManager~registryOperation} The operation to run * @param {components.Component} component A component * @param {boolean} shift Iff true, use Shift Layer, otherwise use Default Layer - * @return Result of the operation + * @returns Result of the operation * @private */ onRegistry: function(operation, component, shift) { - var layerName = shift === true ? this.shiftContainerName : this.defaultContainerName; + const layerName = shift === true ? this.shiftContainerName : this.defaultContainerName; return operation.call(this.componentRegistry, component, layerName); } /** @@ -1070,7 +1111,7 @@ * @public */ unregister: function(component, shift) { - var id = this.onRegistry(this.componentRegistry.unregister, component, shift); + const id = this.onRegistry(this.componentRegistry.unregister, component, shift); delete this.activeLayer[id]; }, @@ -1134,7 +1175,7 @@ * @public */ input: function(channel, control, value, status /* ignored: ,group */) { - var component = this.findComponent(status, control); + const component = this.findComponent(status, control); if (component === undefined) { return; } @@ -1232,7 +1273,7 @@ * @param {function} components.configurationProvider Mapping configuration provider * @public */ - var GenericMidiController = function(options) { + const GenericMidiController = function(options) { if (!options || typeof options.configurationProvider !== "function") { log.error("The required function 'configurationProvider' is missing."); return; @@ -1254,7 +1295,7 @@ this.controllerId = controllerId; this.debug = debug; - var delay = this.config.throttleDelay; + const delay = this.config.throttleDelay; if (delay > 0) { log.debug("Component registration is throttled using a delay of " + delay + "ms"); this.throttler = new Throttler({delay: delay}); @@ -1317,16 +1358,16 @@ * @param {object} deckDefinitions Definition of decks * @param {object} effectUnitDefinitions Definition of effect units * @param {object} containerDefinitions Definition of additional component containers - * @return {object} Layer manager + * @returns {object} Layer manager * @see `LayerManager` * @private */ createLayerManager: function(target, deckDefinitions, effectUnitDefinitions, containerDefinitions) { - var layerManager = new LayerManager({debug: this.debug}); - var controller = this; - var registerComponents = function(definition, implementation) { + const layerManager = new LayerManager({debug: this.debug}); + const controller = this; + const registerComponents = function(definition, implementation) { controller.registerComponents(layerManager, definition, implementation); }; @@ -1362,7 +1403,7 @@ if (Array.isArray(context.definitions)) { context.definitions.forEach(function(definition) { throttle(function() { - var implementation = context.factory.call(this, definition, target); + const implementation = context.factory.call(this, definition, target); target.push(implementation); context.register(definition, implementation); }, this); @@ -1386,10 +1427,10 @@ * @private */ createDeck: function(deckDefinition, componentStorage) { - var deck = new components.Deck(deckDefinition.deckNumbers); + const deck = new components.Deck(deckDefinition.deckNumbers); deckDefinition.components.forEach(function(componentDefinition, index) { - var options = _.merge({group: deck.currentDeck}, componentDefinition.options); - var definition = _.merge(componentDefinition, {options: options}); + const options = Object.assign({group: deck.currentDeck}, componentDefinition.options); + const definition = Object.assign(componentDefinition, {options: options}); deck[index] = this.createComponent(definition); }, this); if (deckDefinition.equalizerUnit) { @@ -1435,7 +1476,7 @@ * @private */ createPublisher: function(source, publisherStorage) { - var publisher = new Publisher({source: source}); + const publisher = new Publisher({source: source}); publisherStorage.push(publisher); return publisher; }, @@ -1450,7 +1491,7 @@ * @param {Array} publisherStorage Storage for publisher components * @param {Array} rebindTriggers Names of functions that trigger rebinding a * publisher to its source component - * @return {components.ComponentContainer} The given component container argument + * @returns {components.ComponentContainer} The given component container argument * @private */ setupMidi: function(definition, implementation, publisherStorage, rebindTriggers) { @@ -1463,14 +1504,14 @@ /* Add publishers for pots */ if (definition.feedback) { - var triggers = rebindTriggers || []; - var createPublisher = this.createPublisher; // `this` is bound to implementation + const triggers = rebindTriggers || []; + const createPublisher = this.createPublisher; // `this` is bound to implementation implementation.forEachComponent(function(effectComponent) { if (effectComponent instanceof components.Pot) { - var publisher = createPublisher(effectComponent, publisherStorage); - var prototype = Object.getPrototypeOf(effectComponent); + const publisher = createPublisher(effectComponent, publisherStorage); + const prototype = Object.getPrototypeOf(effectComponent); triggers.forEach(function(functionName) { - var delegate = prototype[functionName]; + const delegate = prototype[functionName]; if (typeof delegate === "function") { prototype[functionName] = function() { delegate.apply(this, arguments); @@ -1506,12 +1547,12 @@ * @private */ createEffectUnit: function(effectUnitDefinition, componentStorage) { - var effectUnit = this.setupMidi( + const effectUnit = this.setupMidi( effectUnitDefinition, new components.EffectUnit(effectUnitDefinition.unitNumbers, true), componentStorage, ["onFocusChange", "shift", "unshift"]); - var shiftType = effectUnitDefinition.sendShiftedFor; + const shiftType = effectUnitDefinition.sendShiftedFor; /* * `shiftType` is expected to be a JS component (e.g. `c.Button` or `c.Component`) * which in terms of JS means that it is of type `function`. If something else is given @@ -1536,12 +1577,11 @@ * @private */ createComponentContainer: function(containerDefinition) { - var containerType = containerDefinition.type || components.ComponentContainer; - var container = new containerType(containerDefinition.options); + const containerType = containerDefinition.type || components.ComponentContainer; + const container = new containerType(containerDefinition.options); if (containerDefinition.components) { containerDefinition.components.forEach(function(componentDefinition, index) { - var definition = _.merge( - {}, containerDefinition.defaultDefinition || {}, componentDefinition); + const definition = mergeDefinitions({}, containerDefinition.defaultDefinition, componentDefinition); container[index] = this.createComponent(definition); }, this); } @@ -1559,7 +1599,7 @@ * @private */ createComponent: function(definition) { - var component = null; + let component = null; if (definition && definition.type) { if (definition.type.prototype instanceof components.ComponentContainer) { component = this.createComponentContainer(definition); @@ -1586,14 +1626,14 @@ layerManager.register(implementation, definition && definition.shift === true); } else if (implementation instanceof components.ComponentContainer) { Object.keys(implementation).forEach(function(name) { - var definitionName = definition ? definition[name] : null; + const definitionName = definition ? definition[name] : null; this.registerComponents(layerManager, definitionName, implementation[name]); }, this); } }, }); - var exports = {}; + const exports = {}; exports.deriveFrom = deriveFrom; exports.ParameterComponent = ParameterComponent; exports.ShiftButton = ShiftButton; @@ -1614,5 +1654,5 @@ exports.Publisher = Publisher; exports.LayerManager = LayerManager; exports.GenericMidiController = GenericMidiController; - global.behringer = _.assign(global.behringer, {extension: exports}); + global.behringer = Object.assign(global.behringer || {}, {extension: exports}); })(this); diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index e2c38fc5601..1a7f0f26e9b 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -47,7 +47,7 @@ MC7000.needleSearchPlay = false; // select if the previous sampler shall stop before a new sampler starts // true: a running sampler will stop before the new sampler starts // false: all triggered samplers will play simultaneously -MC7000.prevSamplerStop = true; +MC7000.prevSamplerStop = engine.getSetting("prevSamplerStop") ?? true; // Quantity of Samplers used in mixxx possible values 16 and 32 // To use 32 samplers instead of 16 you can set the user variable @@ -55,7 +55,7 @@ MC7000.prevSamplerStop = true; // Deck 2 will trigger sampler 9 to 16, Deck 3 will trigger // sampler 17 to 24 and Deck 4 will trigger sampler 25 to 32. // Please note that your Mixxx skin needs to support more than 16 samplers. -MC7000.SamplerQty = 16; +MC7000.SamplerQty = parseInt(engine.getSetting("samplerQty") ?? "16"); // Set Vinyl Mode on ("true") or off ("false") when MIXXX starts. // This sets the Jog Wheel touch detection / Vinyl Mode diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index 6ce7d82a6a7..b36a5a0683e 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -92,6 +92,33 @@ + + + + diff --git a/res/controllers/Dummy Device Screen.hid.xml b/res/controllers/Dummy Device Screen.hid.xml index 90526cc1a33..1d6526459a8 100644 --- a/res/controllers/Dummy Device Screen.hid.xml +++ b/res/controllers/Dummy Device Screen.hid.xml @@ -8,6 +8,62 @@ + + + + + + + + + + diff --git a/res/controllers/DummyDeviceDefaultScreen.qml b/res/controllers/DummyDeviceDefaultScreen.qml index c216f2259d6..06179054927 100755 --- a/res/controllers/DummyDeviceDefaultScreen.qml +++ b/res/controllers/DummyDeviceDefaultScreen.qml @@ -13,7 +13,7 @@ import Mixxx.Controls 1.0 as MixxxControls import "." as Skin -Item { +Mixxx.ControllerScreen { id: root required property string screenId @@ -23,7 +23,7 @@ Item { property string group: "[Channel1]" property var deckPlayer: Mixxx.PlayerManager.getPlayer(root.group) - function init(controlerName, isDebug) { + init: function(controlerName, isDebug) { console.log(`Screen ${root.screenId} has started`) switch (root.screenId) { case "jog": @@ -34,13 +34,13 @@ Item { } } - function shutdown() { + shutdown: function() { console.log(`Screen ${root.screenId} is stopping`) loader.sourceComponent = splash } // function transformFrame(input: ArrayBuffer, timestamp: date) { - function transformFrame(input, timestamp) { + transformFrame: function(input, timestamp) { return new ArrayBuffer(0); } diff --git a/res/controllers/Hercules-DJControl-Inpulse-300-script.js b/res/controllers/Hercules-DJControl-Inpulse-300-script.js index e2759d619e2..380fcdf5de3 100644 --- a/res/controllers/Hercules-DJControl-Inpulse-300-script.js +++ b/res/controllers/Hercules-DJControl-Inpulse-300-script.js @@ -2,11 +2,20 @@ // // *************************************************************************** // * Mixxx mapping script file for the Hercules DJControl Inpulse 300. -// * Author: DJ Phatso, contributions by Kerrick Staley -// * Version 1.2 (March 2020) +// * Author: DJ Phatso, contributions by Kerrick Staley and BoredGuy1 +// * Version 1.3 (May 2024) // * Forum: https://www.mixxx.org/forums/viewtopic.php?f=7&t=12599 // * Wiki: https://mixxx.org/wiki/doku.php/hercules_djcontrol_inpulse_300 // +// Changes to v1.3 +// - Added ability to stop samplers (shift + button) +// - Added toneplay +// - Added shift + toneplay controls +// - Added slicer/slicer loop +// - Replaced the song end warning with an actual beatmatch guide +// - Changed the way scratching works (wheels have inertia, allowing backspins and other tricks) +// - Updated VU meter syntax (replaced vu_meter with VuMeter, connectControl with makeConnection, etc) +// // Changes to v1.2 // - Code cleanup. // @@ -20,20 +29,34 @@ // // TO DO: Functions that could be implemented to the script: // +// * HOTCUES: Make loop hotcues more intuitive. Currently, the pads are always lit when loop cues are set, +// regardless of whether or not the loop is enabled (maybe make the pad blink when set but inactive?) +// // * ROLL: Keep SLIP active (if already enabled) when exiting from rolls // -// * SLICER/SLICER LOOP +// * FX: See how to preselect effects for a rack // -// * TONEPLAY +// * SLICER: Currently resource intensive because it's connected to beat_distance (which is always updated). +// Is there a better way? +// +// * BEATMATCH: Also resource intensive because it's connected to beat_distance. We could optimize a bit by +// disconnecting functions when the beatmatch guide is disabled (currently the functions are +// always connected, but the LEDs are turned off by hardware controls) // -// * FX: -// - See how to preselect effects for a rack // **************************************************************************** var DJCi300 = {}; /////////////////////////////////////////////////////////////// // USER OPTIONS // /////////////////////////////////////////////////////////////// +// Beatmatch LED guide tolerances +DJCi300.beatmatchTempoTolerance = .1; // Measured in BPM (e.g. LEDS turn off if decks are <0.1 BPM apart) +DJCi300.beatmatchAlignTolerance = .02; // Measured in beats (e.g. LEDS turn off if decks are <0.02 beats apart) + +// Determines how fast the wheel must be moving to be considered "slipping" +// Higher numbers result in longer backspins +DJCi300.slipThreshold = .1; // Must be between 0 and 1, non-inclusive + // How fast scratching is. DJCi300.scratchScale = 1.0; @@ -44,80 +67,197 @@ DJCi300.scratchShiftMultiplier = 4; DJCi300.bendScale = 1.0; // Other scratch related options -DJCi300.kScratchActionNone = 0; +DJCi300.kScratchActionBend = 0; DJCi300.kScratchActionScratch = 1; DJCi300.kScratchActionSeek = 2; -DJCi300.kScratchActionBend = 3; -DJCi300.vuMeterUpdateMaster = function(value, _group, _control) { - value = (value * 122) + 5; +// Pad modes +DJCi300.padModeNone = 0; +// These correspond directly to the MIDI control values +DJCi300.padModeHotcue = 15; +DJCi300.padModeRoll = 16; +DJCi300.padModeSlicer = 17; +DJCi300.padModeSampler = 18; +DJCi300.padModeToneplay = 19; +DJCi300.padModeFX = 20; +DJCi300.padModeSlicerloop = 21; +DJCi300.padModeBeatjump = 22; + +DJCi300.vuMeterUpdateMain = function(value, _group, _control) { + value = (value * 125); midi.sendShortMsg(0xB0, 0x40, value); midi.sendShortMsg(0xB0, 0x41, value); }; DJCi300.vuMeterUpdateDeck = function(value, group, _control, _status) { - value = (value * 122) + 5; - var status = (group === "[Channel1]") ? 0xB1 : 0xB2; + value = (value * 125); + const status = (group === "[Channel1]") ? 0xB1 : 0xB2; midi.sendShortMsg(status, 0x40, value); }; DJCi300.init = function() { - if (engine.getValue("[App]", "num_samplers") < 16) { - engine.setValue("[App]", "num_samplers", 16); - } - // Scratch button state - DJCi300.scratchButtonState = true; + DJCi300.scratchButtonState = { + "[Channel1]": true, + "[Channel2]": true + }; // Scratch Action DJCi300.scratchAction = { - 1: DJCi300.kScratchActionNone, - 2: DJCi300.kScratchActionNone + "[Channel1]": DJCi300.kScratchActionBend, + "[Channel2]": DJCi300.kScratchActionBend + }; + // Platter state (whether the jog wheel is pressed or not) + DJCi300.wheelTouchState = { + "[Channel1]": false, + "[Channel2]": false + }; + + // Pad mode variables + // Initialize to padModeNone + DJCi300.padMode = { + "[Channel1]": DJCi300.padModeNone, + "[Channel2]": DJCi300.padModeNone }; // Turn On Vinyl buttons LED(one for each deck). midi.sendShortMsg(0x91, 0x03, 0x7F); midi.sendShortMsg(0x92, 0x03, 0x7F); - //Turn On Browser button LED + // Turn On Browser button LED midi.sendShortMsg(0x90, 0x04, 0x05); - //Softtakeover for Pitch fader - engine.softTakeover("[Channel1]", "rate", true); - engine.softTakeover("[Channel2]", "rate", true); - engine.softTakeoverIgnoreNextValue("[Channel1]", "rate"); - engine.softTakeoverIgnoreNextValue("[Channel2]", "rate"); - - // Connect the VUMeters - engine.connectControl("[Channel1]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.getValue("[Channel1]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.connectControl("[Channel2]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.getValue("[Channel2]", "vu_meter", "DJCi300.vuMeterUpdateDeck"); - engine.connectControl("[Main]", "vu_meter_left", "DJCi300.vuMeterUpdateMaster"); - engine.connectControl("[Main]", "vu_meter_right", "DJCi300.vuMeterUpdateMaster"); - engine.getValue("[Main]", "vu_meter_left", "DJCi300.vuMeterUpdateMaster"); - engine.getValue("[Main]", "vu_meter_right", "DJCi300.vuMeterUpdateMaster"); + // Turn On Toneplay LED + midi.sendShortMsg(0x96, 0x40, 0x7F); + midi.sendShortMsg(0x96, 0x48, 0x7F); + midi.sendShortMsg(0x97, 0x40, 0x7F); + midi.sendShortMsg(0x97, 0x48, 0x7F); + + // Connect main VUMeters + engine.makeConnection("[Main]", "vu_meter_left", DJCi300.vuMeterUpdateMain); + engine.makeConnection("[Main]", "vu_meter_right", DJCi300.vuMeterUpdateMain); + + for (const group of ["[Channel1]", "[Channel2]"]) { + // Connect left and right VUMeters + engine.makeConnection(group, "vu_meter", DJCi300.vuMeterUpdateDeck); + + //Softtakeover for Pitch fader + engine.softTakeover(group, "rate", true); + engine.softTakeoverIgnoreNextValue(group, "rate"); + + // Connect jogwheel functions + engine.makeConnection(group, "scratch2", DJCi300.updateScratchAction); + + // Connect the toneplay LED updates + engine.makeConnection(group, "pitch", DJCi300.updateToneplayLED); + + // Connect beatmatch LED functions + engine.makeConnection(group, "bpm", DJCi300.updateBeatmatchTempoLED); + // We also want to update all beatmatch LEDs when a song is played/paused + engine.makeConnection(group, "play", DJCi300.updateBeatmatchAlignLED); + engine.makeConnection(group, "play", DJCi300.updateBeatmatchTempoLED); + } + // Only connect one channel to updateBeatmatchAlignLED because beatmatch LEDs are only enabled when both decks are playing + engine.makeConnection("[Channel1]", "beat_distance", DJCi300.updateBeatmatchAlignLED); // Ask the controller to send all current knob/slider values over MIDI, which will update // the corresponding GUI controls in MIXXX. midi.sendShortMsg(0xB0, 0x7F, 0x7F); + + DJCi300.deck = []; + for (let i = 0; i < 2; i++) { + DJCi300.deck[i] = new DJCi300.Deck(i + 1); + DJCi300.deck[i].setCurrentDeck(`[Channel${ i + 1 }]`); + // For some reason, the slicer callback functions start out connected + // This is a dirty hack to ensure they start disconnected + DJCi300.deck[i].slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + } +}; + +// Update beatmatch tempo LEDs +DJCi300.updateBeatmatchTempoLED = function(_value, _group, _control) { + const deck1tempo = engine.getValue("[Channel1]", "bpm"); + const deck2tempo = engine.getValue("[Channel2]", "bpm"); + + // If successfully synced, or if one of the songs are paused, turn all lights off + if ((Math.abs(deck1tempo - deck2tempo) < DJCi300.beatmatchTempoTolerance) || + (engine.getValue("[Channel1]", "play") === 0) || + (engine.getValue("[Channel2]", "play") === 0)) { + midi.sendShortMsg(0x91, 0x1E, 0x00); + midi.sendShortMsg(0x91, 0x1F, 0x00); + midi.sendShortMsg(0x92, 0x1E, 0x00); + midi.sendShortMsg(0x92, 0x1F, 0x00); + // If deck 1 is faster, lights tell user to slow down 1 and speed up 2 + } else if (deck1tempo > deck2tempo) { + midi.sendShortMsg(0x91, 0x1E, 0x7F); + midi.sendShortMsg(0x91, 0x1F, 0x00); + midi.sendShortMsg(0x92, 0x1E, 0x00); + midi.sendShortMsg(0x92, 0x1F, 0x7F); + // If deck 2 is faster, lights tell user to slow down 2 and speed up 1 + } else if (deck1tempo < deck2tempo) { + midi.sendShortMsg(0x91, 0x1E, 0x00); + midi.sendShortMsg(0x91, 0x1F, 0x7F); + midi.sendShortMsg(0x92, 0x1E, 0x7F); + midi.sendShortMsg(0x92, 0x1F, 0x00); + } +}; + +// Update beatmatch align LEDs +DJCi300.updateBeatmatchAlignLED = function(value, _group, _control) { + let deck1Align = value; + let deck2Align = engine.getValue("[Channel2]", "beat_distance"); + + // Because beat_distance resets to 0 every new beat, it's possible for the two decks to have + // very different beat values and still be almost aligned. So we must adjust for this + if (Math.abs(deck1Align - deck2Align) > .5) { + // Add 1 to the smaller number to compensate for roll over + if (deck1Align < deck2Align) { + deck1Align += 1; + } else { + deck2Align += 1; + } + } + + // If successfully synced, or if one of the songs are paused, turn all lights off + if ((Math.abs(deck1Align - deck2Align) < DJCi300.beatmatchAlignTolerance) || + (engine.getValue("[Channel1]", "play") === 0) || + (engine.getValue("[Channel2]", "play") === 0)) { + midi.sendShortMsg(0x91, 0x1C, 0x00); + midi.sendShortMsg(0x91, 0x1D, 0x00); + midi.sendShortMsg(0x92, 0x1C, 0x00); + midi.sendShortMsg(0x92, 0x1D, 0x00); + // If deck 1 is ahead, lights tell user to push 1 back and push 2 ahead + } else if (deck1Align > deck2Align) { + midi.sendShortMsg(0x91, 0x1C, 0x00); + midi.sendShortMsg(0x91, 0x1D, 0x7F); + midi.sendShortMsg(0x92, 0x1C, 0x7F); + midi.sendShortMsg(0x92, 0x1D, 0x00); + // If deck 2 is ahead, lights tell user to push 2 back and push 1 ahead + } else if (deck1Align < deck2Align) { + midi.sendShortMsg(0x91, 0x1C, 0x7F); + midi.sendShortMsg(0x91, 0x1D, 0x00); + midi.sendShortMsg(0x92, 0x1C, 0x00); + midi.sendShortMsg(0x92, 0x1D, 0x7F); + } }; // The Vinyl button, used to enable or disable scratching on the jog wheels (One per deck). -DJCi300.vinylButton = function(_channel, _control, value, status, _group) { +DJCi300.vinylButton = function(_channel, control, value, status, group) { if (value) { - if (DJCi300.scratchButtonState) { - DJCi300.scratchButtonState = false; - midi.sendShortMsg(status, 0x03, 0x00); + if (DJCi300.scratchButtonState[group]) { + DJCi300.scratchButtonState[group] = false; + midi.sendShortMsg(status, control, 0x00); } else { - DJCi300.scratchButtonState = true; - midi.sendShortMsg(status, 0x03, 0x7F); + DJCi300.scratchButtonState[group] = true; + midi.sendShortMsg(status, control, 0x7F); } } }; DJCi300._scratchEnable = function(deck) { - var alpha = 1.0/8; - var beta = alpha/32; + const alpha = 1.0/8; + const beta = alpha/32; engine.scratchEnable(deck, 248, 33 + 1/3, alpha, beta); }; @@ -128,55 +268,61 @@ DJCi300._convertWheelRotation = function(value) { return value < 0x40 ? 1 : -1; }; +// This is called immediately after the wheel is released and we want to switch between scratching and jogging gracefully. +// It is also connected to callbacks and called regularly to see if the wheel has slowed down enough. Once it does, then we switch from scratching to jogging +DJCi300.updateScratchAction = function(value, group, _control) { + const deck = script.deckFromGroup(group); + + // Stop scratching only if the jogwheel is slow enough and the wheels are not being touched + if (((Math.abs(value) < DJCi300.slipThreshold)) && (engine.isScratching(deck)) + && !DJCi300.wheelTouchState[group]) { + engine.scratchDisable(deck); + DJCi300.scratchAction[group] = DJCi300.kScratchActionBend; + } +}; + // The touch action on the jog wheel's top surface -DJCi300.wheelTouch = function(channel, control, value, _status, _group) { - var deck = channel; +DJCi300.wheelTouch = function(_channel, _control, value, _status, group) { + const deck = script.deckFromGroup(group); if (value > 0) { - // Touching the wheel. - if (engine.getValue("[Channel" + deck + "]", "play") !== 1 || DJCi300.scratchButtonState) { + // Enable scratching in vinyl mode OR if the deck is not playing + if ((engine.getValue(group, "play") !== 1) || (DJCi300.scratchButtonState[group])) { DJCi300._scratchEnable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionScratch; + DJCi300.wheelTouchState[group] = true; + DJCi300.scratchAction[group] = DJCi300.kScratchActionScratch; } else { - DJCi300.scratchAction[deck] = DJCi300.kScratchActionBend; + DJCi300.scratchAction[group] = DJCi300.kScratchActionBend; } } else { // Released the wheel. - engine.scratchDisable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionNone; + DJCi300.wheelTouchState[group] = false; + const scratchValue = engine.getValue(group, "scratch2"); + DJCi300.updateScratchAction(scratchValue, group); } }; // The touch action on the jog wheel's top surface while holding shift -DJCi300.wheelTouchShift = function(channel, control, value, _status, _group) { - var deck = channel - 3; +DJCi300.wheelTouchShift = function(_channel, _control, value, _status, group) { + const deck = script.deckFromGroup(group); // We always enable scratching regardless of button state. if (value > 0) { DJCi300._scratchEnable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionSeek; + DJCi300.wheelTouchState[group] = true; + DJCi300.scratchAction[group] = DJCi300.kScratchActionSeek; + // Released the wheel. } else { - // Released the wheel. - engine.scratchDisable(deck); - DJCi300.scratchAction[deck] = DJCi300.kScratchActionNone; + DJCi300.wheelTouchState[group] = false; + const scratchValue = engine.getValue(group, "scratch2"); + DJCi300.updateScratchAction(scratchValue, group); } }; -// Scratching on the jog wheel (rotating it while pressing the top surface) -DJCi300.scratchWheel = function(channel, control, value, status, _group) { - var deck; - switch (status) { - case 0xB1: - case 0xB4: - deck = 1; - break; - case 0xB2: - case 0xB5: - deck = 2; - break; - default: - return; - } +// Using the jog wheel (spinning the jog wheel, regardless of whether surface or shift is held) +DJCi300.jogWheel = function(_channel, _control, value, _status, group) { + const deck = script.deckFromGroup(group); + var interval = DJCi300._convertWheelRotation(value); - var scratchAction = DJCi300.scratchAction[deck]; + const scratchAction = DJCi300.scratchAction[group]; if (scratchAction === DJCi300.kScratchActionScratch) { engine.scratchTick(deck, interval * DJCi300.scratchScale); } else if (scratchAction === DJCi300.kScratchActionSeek) { @@ -185,16 +331,322 @@ DJCi300.scratchWheel = function(channel, control, value, status, _group) { DJCi300.scratchShiftMultiplier); } else { engine.setValue( - "[Channel" + deck + "]", "jog", interval * DJCi300.bendScale); + group, "jog", interval * DJCi300.bendScale); } }; -// Bending on the jog wheel (rotating using the edge) -DJCi300.bendWheel = function(channel, control, value, _status, _group) { - var interval = DJCi300._convertWheelRotation(value); - engine.setValue( - "[Channel" + channel + "]", "jog", interval * DJCi300.bendScale); +// Helper function that calculates samples per beat +DJCi300._samplesPerBeat = function(group) { + const sampleRate = engine.getValue(group, "track_samplerate"); + const bpm = engine.getValue(group, "local_bpm"); + // The sample rate includes both channels (i.e. it is double the framerate) + // Hence, we multiply by 2*60 (120) instead of 60 to get the correct sample rate + const secondsPerBeat = 120/bpm; + const samplesPerBeat = secondsPerBeat * sampleRate; + return samplesPerBeat; +}; + +// Helper function that returns a deck object from a group +DJCi300._deckObjectFromGroup = function(group) { + return DJCi300.deck[script.deckFromGroup(group) - 1]; +}; + +// Mode buttons +DJCi300.changeMode = function(_channel, control, value, _status, group) { + const oldPadMode = DJCi300.padMode[group]; + DJCi300.padMode[group] = control; + const deckObject = DJCi300._deckObjectFromGroup(group); + + if (value) { + // Connect slicer when entering slicer or slicerloop mode + if ((DJCi300.padMode[group] === DJCi300.padModeSlicer) || + (DJCi300.padMode[group] === DJCi300.padModeSlicerloop)) { + + // If slicer connections are not present, connect them. Otherwise, disconnect them + if (deckObject.slicerPad.beatConnection === undefined || + deckObject.slicerPad.beatConnection.isConnected === false) { + + deckObject.slicerPad.forEachComponent(function(component) { + component.connect(engine.getValue(group, "beat_closest")); + }); + } else { + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + }; + + // When switching from slicer/slicer loop mode into the other modes, disconnect slicer functions + } else if ((oldPadMode === DJCi300.padModeSlicer) || + (oldPadMode === DJCi300.padModeSlicerloop)) { + + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + } + } +}; + +// Toneplay +DJCi300.toneplay = function(_channel, control, value, _status, group) { + let button = control - 0x40; + + if (value) { + // Pad buttons (buttons 1-8) will jump to a hotcue and change pitch + // Shift + pad buttons (buttons 9-16) will only change pitch without jumping + if (button < 8) { + // Jump to the most recently used hotcue + const recentHotcue = engine.getValue(group, "hotcue_focus"); + if ((recentHotcue > 0) && (engine.getValue(group, + `hotcue_${ recentHotcue }_status`) > 0)) { + engine.setValue(group, `hotcue_${ recentHotcue }_goto`, 1); + } else { + // If that hotcue doesn't exist or was deleted, jump to cue + engine.setValue(group, + "cue_goto", 1); + } + } + + // Subtract 8 from buttons if they're shifted + button = (button < 8) ? button : button - 8; + // Adjust pitch + if (button < 4) { + // Buttons 1-4 are +0 to +3 semitones + engine.setValue(group, "pitch", button); + // Buttons 5-8 are -4 to -1 semitones + } else { + engine.setValue(group, "pitch", button - 8); + } + } +}; + +// Update toneplay LEDs (LEDS will change depending on pitch, even if not caused by toneplay) +DJCi300.updateToneplayLED = function(value, group, _control) { + const status = (group === "[Channel1]") ? 0x96 : 0x97; + let control = 0x40; + + // Cut off the value at -4 and 3 semitones, then round + value = Math.min(value, 3); + value = Math.max(value, -4); + value = Math.round(value); + + // Buttons 1-4 (ctrl 0x40-0x43) are +0 to +3 semitones + // Buttons 5-8 (ctrl 0x44-0x47) are -4 to -1 semitones + if (value >= 0) { + control = control + value; + } else { + control = control + 8 + value; + } + + // Do the following for normal LEDs and the shifted LEDs + // Turn off all LEDs + for (let i = 0; i < 8; i++) { + midi.sendShortMsg(status, 0x40 + i, 0x00); + midi.sendShortMsg(status, 0x40 + i + 8, 0x00); + } + // Turn on current LED + midi.sendShortMsg(status, control, 0x7F); + midi.sendShortMsg(status, control + 8, 0x7F); +}; + +// Loop in button +DJCi300.loopInButton = function(_channel, _control, value, _status, group) { + const deckObject = DJCi300._deckObjectFromGroup(group); + + if (value) { + // Override the active slicer if it exists + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + + // Create a 4 beat loop + engine.setValue(group, "beatloop_4_activate", 1); + } +}; + +// Loop out button +DJCi300.loopOutButton = function(_channel, _control, value, _status, group) { + const deckObject = DJCi300._deckObjectFromGroup(group); + + if (value) { + // Override the active slicer if it exists + deckObject.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + + // Disable the current loop if it exists + if (engine.getValue(group, "loop_enabled") === 1) { engine.setValue(group, "reloop_toggle", 1); } + } +}; + +DJCi300.Deck = function(deckNumber) { + components.Deck.call(this, deckNumber); + + // Slicer/slicer loop pad buttons + this.slicerPad = new components.ComponentContainer(); + // It's easier to keep track of which buttons are pressed as an array instead of having a property for each button + this.slicerPad.pressed = [false, false, false, false, false, false, false, false]; + // For slicer/slicer loop pads + for (const midiOffset of [0x20, 0x60]) { + for (let i = 0; i < 8; i++) { + this.slicerPad[i] = new components.Button({ + midi: [0x95 + deckNumber, midiOffset + i], + connect: function(startPos) { + const group = this.currentDeck; + const samplesBetweenSlices = DJCi300._samplesPerBeat(group) * engine.getValue(group, "beatloop_size") / 8; + // Calculate the start and end points (in samples) for each slice + this.slicerPad[i].startSample = (i === 0) ? startPos : this.slicerPad[i-1].endSample; + this.slicerPad[i].endSample = this.slicerPad[i].startSample + samplesBetweenSlices; + // Everything in the if-statement only needs to be done once (and not 8 times) + // when connected, which is why it is only executed when i === 7 + if (i === 7) { + // Connect callback functions if they are not connected already + if (this.slicerPad.beatConnection === undefined || this.slicerPad.beatConnection.isConnected === false) { + this.slicerPad.beatConnection = engine.makeConnection(group, "beat_distance", this.slicerCountBeat); + // This connection will reinitialize Slicer when the beatloop size spinbox changes + this.slicerPad.sizeConnection = engine.makeConnection(group, "beatloop_size", function() { + const nextStartPos = this.slicerPad[0].startSample; + this.slicerPad.forEachComponent(function(component) { + component.disconnect(); + component.connect(nextStartPos); + }); + }.bind(this)); + // This connection will remove Slicer when a new track is loaded + this.slicerPad.loadConnection = engine.makeConnection(group, "LoadSelectedTrack", function() { + this.slicerPad.forEachComponent(function(component) { + component.disconnect(); + }); + }.bind(this)); + } + // Set loop position indicators to the start and end of the Slicer section as visual feedback + engine.setValue(group, "loop_start_position", this.slicerPad[0].startSample); + engine.setValue(group, "loop_end_position", this.slicerPad[7].endSample); + if (DJCi300.padMode[group] === DJCi300.padModeSlicer) { + if (engine.getValue(group, "loop_enabled") === 1) { engine.setValue(group, "reloop_toggle", 1); } + } else if (DJCi300.padMode[group] === DJCi300.padModeSlicerloop) { + if (engine.getValue(group, "loop_enabled") === 0) { engine.setValue(group, "reloop_toggle", 1); } + } + }; + }.bind(this), + disconnect: function() { + // Set start and end points of each slice to placeholder value + this.slicerPad[i].startSample = -1; + this.slicerPad[i].endSample = -1; + // Much like before, everything in the if-statement only needs to be done once (not 8 times) + if (i === 0) { + const group = this.currentDeck; + // Disconnect slicer if it is connected + if (this.slicerPad.beatConnection !== undefined && this.slicerPad.beatConnection.isConnected === true) { + this.slicerPad.beatConnection.disconnect(); + this.slicerPad.sizeConnection.disconnect(); + this.slicerPad.loadConnection.disconnect(); + this.slicerPad.beat = -1; + this.slicerUpdateLED(group); + } + // Make loop position indicators disappear as visual feedback + engine.setValue(group, "loop_start_position", -1); + engine.setValue(group, "loop_end_position", -1); + } + }.bind(this), + input: function(_channel, control, value, _status, group) { + const button = control % 0x20; + + // Update array. 1 for on, 0 for off + if (value) { + this.slicerPad.pressed[button] = true; + } else { + this.slicerPad.pressed[button] = false; + } + + const startPad = this.slicerPad.pressed.indexOf(true); + const endPad = this.slicerPad.pressed.lastIndexOf(true); + + // If the slicer points are uninitialized, then do nothing. Otherwise: + if (this.slicerPad[0].startSample !== -1) { + // If at least one button is pressed, create a loop between those points + if (startPad !== -1) { + engine.setValue(group, "loop_start_position", this.slicerPad[startPad].startSample); + engine.setValue(group, "loop_end_position", this.slicerPad[endPad].endSample); + engine.setValue(group, "loop_in_goto", 1); + // Enable a loop if it doesn't already exist + if (engine.getValue(group, "loop_enabled") === 0) { + engine.setValue(group, "reloop_toggle", 1); + } + // If no buttons are pressed, reset the loop + } else { + engine.setValue(group, "loop_start_position", this.slicerPad[0].startSample); + engine.setValue(group, "loop_end_position", this.slicerPad[7].endSample); + + // Disable the loop (if we're not in slicer loop mode) + if (DJCi300.padMode[group] === DJCi300.padModeSlicer) { + if (engine.getValue(group, "loop_enabled") === 1) { engine.setValue(group, "reloop_toggle", 1); } + } else if (DJCi300.padMode[group] === DJCi300.padModeSlicerloop) { + if (engine.getValue(group, "loop_enabled") === 0) { engine.setValue(group, "reloop_toggle", 1); } + } + } + this.slicerUpdateLED(group); + } + }.bind(this), + }); + } + } + // This function will count beats and move the Slicer section forward when needed + // It also lights up LEDs corresponding to the beat + this.slicerCountBeat = function(_value, group, _control) { + // Calculate current position in samples + const currentPos = engine.getValue(group, "track_samples") * engine.getValue(group, "playposition"); + // Calculate beat + let beat = 0; + for (let i = 0; i < 8; i++) { + beat = (currentPos >= this.slicerPad[i].endSample) ? (beat + 1) : beat; + } + + // If the beat count has changed, update the object property's value + if (this.slicerPad.beat !== beat) { + this.slicerPad.beat = beat; + // Only send an LED update if no pads are currently held down (pressed pad LEDs are handled above) + if (!this.slicerPad.pressed.includes(true)) { this.slicerUpdateLED(group); } + }; + + // If in slicer mode (not slicer loop mode), check to see if the slicer section needs to be moved + if (DJCi300.padMode[group] === DJCi300.padModeSlicer) { + + // If slicerBeat is 8, move the slicer section forward + if (beat > 7) { + const nextStartPos = this.slicerPad[7].endSample; + this.slicerPad.forEachComponent(function(component) { + component.disconnect(); + component.connect(nextStartPos); + }); + } + } + }.bind(this); + this.slicerUpdateLED = function(group) { + const offset = (DJCi300.padMode[group] === DJCi300.padModeSlicer) ? 0x20 : 0x60; + const status = (group === "[Channel1]") ? 0x96 : 0x97; + + const startPad = this.slicerPad.pressed.indexOf(true); + const endPad = this.slicerPad.pressed.lastIndexOf(true); + + // Turn off all LEDs + for (let i = 0; i < 8; i++) { + midi.sendShortMsg(status, offset + i, 0x00); + } + // If the slicer points are uninitialized, then do nothing. Otherwise: + if (this.slicerPad[0].startSample !== -1) { + // If at least 1 button is held down, light that up + // Or in the case of 2+ buttons, light up everything between the outer 2 buttons + if (startPad !== -1) { + for (let i = startPad; i <= endPad; i++) { + midi.sendShortMsg(status, offset + i, 0x7F); + } + // Otherwise, light up the LED corresponding to the beat + } else { + midi.sendShortMsg(status, offset + Math.min(this.slicerPad.beat, 7), 0x7F); + } + } + }; }; +DJCi300.Deck.prototype = new components.Deck(); DJCi300.shutdown = function() { midi.sendShortMsg(0xB0, 0x7F, 0x00); diff --git a/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml b/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml index 464a320c8e6..c1c0a58cdd6 100644 --- a/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml +++ b/res/controllers/Hercules_DJControl_Inpulse_300.midi.xml @@ -1,11 +1,11 @@ - + Hercules DJControl Inpulse 300 - DJ Phatso for Hercules Technical Support - MIDI Preset for Hercules DJControl Inpulse 300 - https://www.mixxx.org/wiki/doku.php/hercules_djcontrol_inpulse_300 - https://www.mixxx.org/forums/viewtopic.php?f=7&t=12599 + DJ Phatso for Hercules Technical Support + MIDI Preset for Hercules DJControl Inpulse 300 + https://www.mixxx.org/wiki/doku.php/hercules_djcontrol_inpulse_300 + https://www.mixxx.org/forums/viewtopic.php?f=7&t=12599 @@ -156,22 +156,22 @@ [Channel1] - beatloop_4_activate + DJCi300.loopInButton Loop In button 0x91 0x09 - + [Channel1] - reloop_toggle + DJCi300.loopOutButton Loop Out button 0x91 0x0A - + @@ -187,6 +187,88 @@ + + + [Channel1] + DJCi300.changeMode + Hot cue mode + 0x91 + 0x0F + + + + + + [Channel1] + DJCi300.changeMode + Roll mode + 0x91 + 0x10 + + + + + + [Channel1] + DJCi300.changeMode + Slicer mode + 0x91 + 0x11 + + + + + + [Channel1] + DJCi300.changeMode + Sampler mode + 0x91 + 0x12 + + + + + + [Channel1] + DJCi300.changeMode + Toneplay mode + 0x91 + 0x13 + + + + + + [Channel1] + DJCi300.changeMode + FX mode + 0x91 + 0x14 + + + + + + [Channel1] + DJCi300.changeMode + Slicer loop mode + 0x91 + 0x15 + + + + + + [Channel1] + DJCi300.changeMode + Beatjump mode + 0x91 + 0x16 + + + + + @@ -276,6 +358,7 @@ + [Channel2] @@ -287,6 +370,7 @@ + [Channel2] @@ -298,29 +382,30 @@ + [Channel2] - beatloop_4_activate + DJCi300.loopInButton Loop In button 0x92 0x09 - + [Channel2] - reloop_toggle + DJCi300.loopOutButton Loop Out button 0x92 0x0A - + - + [EffectRack1_EffectUnit2_Effect3] enabled @@ -332,6 +417,88 @@ + + + [Channel2] + DJCi300.changeMode + Hot cue mode + 0x92 + 0x0F + + + + + + [Channel2] + DJCi300.changeMode + Roll mode + 0x92 + 0x10 + + + + + + [Channel2] + DJCi300.changeMode + Slicer mode + 0x92 + 0x11 + + + + + + [Channel2] + DJCi300.changeMode + Sampler mode + 0x92 + 0x12 + + + + + + [Channel2] + DJCi300.changeMode + Toneplay mode + 0x92 + 0x13 + + + + + + [Channel2] + DJCi300.changeMode + FX mode + 0x92 + 0x14 + + + + + + [Channel2] + DJCi300.changeMode + Slicer loop mode + 0x92 + 0x15 + + + + + + [Channel2] + DJCi300.changeMode + Beatjump mode + 0x92 + 0x16 + + + + + @@ -485,7 +652,7 @@ [Channel2] loop_double - SHIFT + Loop Ou: Loop Double + SHIFT + Loop Out: Loop Double 0x95 0x0A @@ -992,589 +1159,1387 @@ - - - - - - - + - [Channel1] - beatjump_1_backward - PAD 1 + [Sampler1] + cue_gotoandstop + SHIFT + PAD 1 0x96 - 0x70 + 0x38 - [Channel1] - beatjump_1_forward - PAD 2 + [Sampler2] + cue_gotoandstop + SHIFT + PAD 2 0x96 - 0x71 + 0x39 - [Channel1] - beatjump_2_backward - PAD 3 + [Sampler3] + cue_gotoandstop + SHIFT + PAD 3 0x96 - 0x72 + 0x3A - [Channel1] - beatjump_2_forward - PAD 4 + [Sampler4] + cue_gotoandstop + SHIFT + PAD 4 0x96 - 0x73 + 0x3B - [Channel1] - beatjump_4_backward - PAD 5 + [Sampler5] + cue_gotoandstop + SHIFT + PAD 5 0x96 - 0x74 + 0x3C - [Channel1] - beatjump_4_forward - PAD 6 + [Sampler6] + cue_gotoandstop + SHIFT + PAD 6 0x96 - 0x75 + 0x3D - [Channel1] - beatjump_8_backward - PAD 7 + [Sampler7] + cue_gotoandstop + SHIFT + PAD 7 0x96 - 0x76 + 0x3E - [Channel1] - beatjump_8_forward - PAD 8 + [Sampler8] + cue_gotoandstop + SHIFT + PAD 8 0x96 - 0x77 + 0x3F - - - - - + - [Channel2] - hotcue_1_activate + [Channel1] + DJCi300.deck[0].slicerPad[0].input PAD 1 - 0x97 - 0x00 + 0x96 + 0x20 - + - [Channel2] - hotcue_2_activate + [Channel1] + DJCi300.deck[0].slicerPad[1].input PAD 2 - 0x97 - 0x01 + 0x96 + 0x21 - + - [Channel2] - hotcue_3_activate + [Channel1] + DJCi300.deck[0].slicerPad[2].input PAD 3 - 0x97 - 0x02 + 0x96 + 0x22 - + - [Channel2] - hotcue_4_activate + [Channel1] + DJCi300.deck[0].slicerPad[3].input PAD 4 - 0x97 - 0x03 + 0x96 + 0x23 - + - [Channel2] - hotcue_5_activate + [Channel1] + DJCi300.deck[0].slicerPad[4].input PAD 5 - 0x97 - 0x04 + 0x96 + 0x24 - + - [Channel2] - hotcue_6_activate + [Channel1] + DJCi300.deck[0].slicerPad[5].input PAD 6 - 0x97 - 0x05 + 0x96 + 0x25 - + - [Channel2] - hotcue_7_activate + [Channel1] + DJCi300.deck[0].slicerPad[6].input PAD 7 - 0x97 - 0x06 + 0x96 + 0x26 - + - [Channel2] - hotcue_8_activate + [Channel1] + DJCi300.deck[0].slicerPad[7].input PAD 8 - 0x97 - 0x07 + 0x96 + 0x27 - + - + - [Channel2] - hotcue_1_clear + [Channel1] + DJCi300.toneplay PAD 1 - 0x97 - 0x08 + 0x96 + 0x40 + + + + + + [Channel1] + DJCi300.toneplay + PAD 2 + 0x96 + 0x41 + + + + + + [Channel1] + DJCi300.toneplay + PAD 3 + 0x96 + 0x42 + + + + + + [Channel1] + DJCi300.toneplay + PAD 4 + 0x96 + 0x43 + + + + + + [Channel1] + DJCi300.toneplay + PAD 5 + 0x96 + 0x44 + + + + + + [Channel1] + DJCi300.toneplay + PAD 6 + 0x96 + 0x45 + + + + + + [Channel1] + DJCi300.toneplay + PAD 7 + 0x96 + 0x46 + + + + + + [Channel1] + DJCi300.toneplay + PAD 8 + 0x96 + 0x47 + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 1 + 0x96 + 0x48 + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 2 + 0x96 + 0x49 + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 3 + 0x96 + 0x4A + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 4 + 0x96 + 0x4B + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 5 + 0x96 + 0x4C + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 6 + 0x96 + 0x4D + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 7 + 0x96 + 0x4E + + + + + + [Channel1] + DJCi300.toneplay + SHIFT + PAD 8 + 0x96 + 0x4F + + + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[0].input + PAD 1 + 0x96 + 0x60 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[1].input + PAD 2 + 0x96 + 0x61 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[2].input + PAD 3 + 0x96 + 0x62 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[3].input + PAD 4 + 0x96 + 0x63 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[4].input + PAD 5 + 0x96 + 0x64 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[5].input + PAD 6 + 0x96 + 0x65 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[6].input + PAD 7 + 0x96 + 0x66 + + + + + + [Channel1] + DJCi300.deck[0].slicerPad[7].input + PAD 8 + 0x96 + 0x67 + + + + + + + [Channel1] + beatjump_1_backward + PAD 1 + 0x96 + 0x70 + + + + + + [Channel1] + beatjump_1_forward + PAD 2 + 0x96 + 0x71 + + + + + + [Channel1] + beatjump_2_backward + PAD 3 + 0x96 + 0x72 + + + + + + [Channel1] + beatjump_2_forward + PAD 4 + 0x96 + 0x73 + + + + + + [Channel1] + beatjump_4_backward + PAD 5 + 0x96 + 0x74 + + + + + + [Channel1] + beatjump_4_forward + PAD 6 + 0x96 + 0x75 + + + + + + [Channel1] + beatjump_8_backward + PAD 7 + 0x96 + 0x76 + + + + + + [Channel1] + beatjump_8_forward + PAD 8 + 0x96 + 0x77 + + + + + + + + + + + [Channel2] + hotcue_1_activate + PAD 1 + 0x97 + 0x00 + + + + + + [Channel2] + hotcue_2_activate + PAD 2 + 0x97 + 0x01 + + + + + + [Channel2] + hotcue_3_activate + PAD 3 + 0x97 + 0x02 + + + + + + [Channel2] + hotcue_4_activate + PAD 4 + 0x97 + 0x03 + + + + + + [Channel2] + hotcue_5_activate + PAD 5 + 0x97 + 0x04 + + + + + + [Channel2] + hotcue_6_activate + PAD 6 + 0x97 + 0x05 + + + + + + [Channel2] + hotcue_7_activate + PAD 7 + 0x97 + 0x06 + + + + + + [Channel2] + hotcue_8_activate + PAD 8 + 0x97 + 0x07 + + + + + + + [Channel2] + hotcue_1_clear + PAD 1 + 0x97 + 0x08 + + + + + + [Channel2] + hotcue_2_clear + PAD 2 + 0x97 + 0x09 + + + + + + [Channel2] + hotcue_3_clear + PAD 3 + 0x97 + 0x0A + + + + + + [Channel2] + hotcue_4_clear + PAD 4 + 0x97 + 0x0B + + + + + + [Channel2] + hotcue_5_clear + PAD 5 + 0x97 + 0x0C + + + + + + [Channel2] + hotcue_6_clear + PAD 6 + 0x97 + 0x0D + + + + + + [Channel2] + hotcue_7_clear + PAD 7 + 0x97 + 0x0E + + + + + + [Channel2] + hotcue_8_clear + PAD 8 + 0x97 + 0x0F + + + + + + + [EffectRack1_EffectUnit2_Effect1] + enabled + FX Unit 2 - Slot 1 On/Off + 0x97 + 0x50 + + + + + + [EffectRack1_EffectUnit2_Effect2] + enabled + FX Unit 2 - Slot 2 On/Off + 0x97 + 0x51 + + + + + + [EffectRack1_EffectUnit2_Effect3] + enabled + FX Unit 2 - Slot 3 On/Off + 0x97 + 0x52 + + + + + + [EffectRack1_EffectUnit1] + group_[Channel2]_enable + FX Unit 1 On/Off - Deck B + 0x97 + 0x53 + + + + + + [EffectRack1_EffectUnit2_Effect1] + next_effect + FX Unit 2 - Slot 1 next effect + 0x97 + 0x54 + + + + + + [EffectRack1_EffectUnit2_Effect2] + next_effect + FX Unit 2 - Slot 2 next effect + 0x97 + 0x55 + + + + + + [EffectRack1_EffectUnit2_Effect3] + next_effect + FX Unit 2 - Slot 3 next effect + 0x97 + 0x56 + + + + + + [EffectRack1_EffectUnit2] + group_[Channel2]_enable + FX Unit 2 On/Off - Deck B + 0x97 + 0x57 + + + + + + + [EffectRack1_EffectUnit4_Effect1] + enabled + FX Unit 4 - Slot 1 On/Off + 0x97 + 0x58 + + + + + + [EffectRack1_EffectUnit4_Effect2] + enabled + FX Unit 4 - Slot 2 On/Off + 0x97 + 0x59 + + + + + + [EffectRack1_EffectUnit4_Effect3] + enabled + FX Unit 4 - Slot 3 On/Off + 0x97 + 0x5A + + + + + + [EffectRack1_EffectUnit3] + group_[Channel2]_enable + FX Unit 3 On/Off - Deck B + 0x97 + 0x5B + + + + + + [EffectRack1_EffectUnit4_Effect1] + next_effect + FX Unit 4 - Slot 1 next effect + 0x97 + 0x5C + + + + + + [EffectRack1_EffectUnit4_Effect2] + next_effect + FX Unit 4 - Slot 2 next effect + 0x97 + 0x5D + + + + + + [EffectRack1_EffectUnit4_Effect3] + next_effect + FX Unit 4 - Slot 3 next effect + 0x97 + 0x5E + + + + + + [EffectRack1_EffectUnit4] + group_[Channel2]_enable + FX Unit 4 On/Off - Deck B + 0x97 + 0x5F + + + + + + + [Channel2] + beatlooproll_0.125_activate + Loop 1/8 Beat (Pad 1) + 0x97 + 0x10 + + + + + + [Channel2] + beatlooproll_0.25_activate + Loop 1/4 Beat (Pad 2) + 0x97 + 0x11 + + + + + + [Channel2] + beatlooproll_0.5_activate + Loop 1/2 Beat (Pad 3) + 0x97 + 0x12 [Channel2] - hotcue_2_clear - PAD 2 + beatlooproll_1_activate + Loop 1 Beat (Pad 4) 0x97 - 0x09 + 0x13 [Channel2] - hotcue_3_clear - PAD 3 + beatlooproll_2_activate + Loop 2 Beat (Pad 5) 0x97 - 0x0A + 0x14 [Channel2] - hotcue_4_clear - PAD 4 + beatlooproll_4_activate + Loop 4 Beat (Pad 6) 0x97 - 0x0B + 0x15 [Channel2] - hotcue_5_clear - PAD 5 + beatlooproll_8_activate + Loop 8 Beat (Pad 7) 0x97 - 0x0C + 0x16 [Channel2] - hotcue_6_clear - PAD 6 + beatlooproll_16_activate + Loop 16 Beat (Pad 8) 0x97 - 0x0D + 0x17 + - [Channel2] - hotcue_7_clear - PAD 7 + [Sampler9] + cue_gotoandplay + PAD 1 0x97 - 0x0E + 0x30 - [Channel2] - hotcue_8_clear - PAD 8 + [Sampler10] + cue_gotoandplay + PAD 2 0x97 - 0x0F + 0x31 - - [EffectRack1_EffectUnit2_Effect1] - enabled - FX Unit 2 - Slot 1 On/Off + [Sampler11] + cue_gotoandplay + PAD 3 0x97 - 0x50 + 0x32 - [EffectRack1_EffectUnit2_Effect2] - enabled - FX Unit 2 - Slot 2 On/Off + [Sampler12] + cue_gotoandplay + PAD 1 0x97 - 0x51 + 0x33 - [EffectRack1_EffectUnit2_Effect3] - enabled - FX Unit 2 - Slot 3 On/Off + [Sampler13] + cue_gotoandplay + PAD 5 0x97 - 0x52 + 0x34 - [EffectRack1_EffectUnit1] - group_[Channel2]_enable - FX Unit 1 On/Off - Deck B + [Sampler14] + cue_gotoandplay + PAD 6 0x97 - 0x53 + 0x35 - [EffectRack1_EffectUnit2_Effect1] - next_effect - FX Unit 2 - Slot 1 next effect + [Sampler15] + cue_gotoandplay + PAD 7 0x97 - 0x54 + 0x36 - [EffectRack1_EffectUnit2_Effect2] - next_effect - FX Unit 2 - Slot 2 next effect + [Sampler16] + cue_gotoandplay + PAD 8 0x97 - 0x55 + 0x37 + - [EffectRack1_EffectUnit2_Effect3] - next_effect - FX Unit 2 - Slot 3 next effect + [Sampler9] + cue_gotoandstop + SHIFT + PAD 1 0x97 - 0x56 + 0x38 - [EffectRack1_EffectUnit2] - group_[Channel2]_enable - FX Unit 2 On/Off - Deck B + [Sampler10] + cue_gotoandstop + SHIFT + PAD 2 0x97 - 0x57 + 0x39 - - [EffectRack1_EffectUnit4_Effect1] - enabled - FX Unit 4 - Slot 1 On/Off + [Sampler11] + cue_gotoandstop + SHIFT + PAD 3 0x97 - 0x58 + 0x3A - [EffectRack1_EffectUnit4_Effect2] - enabled - FX Unit 4 - Slot 2 On/Off + [Sampler12] + cue_gotoandstop + SHIFT + PAD 4 0x97 - 0x59 + 0x3B - [EffectRack1_EffectUnit4_Effect3] - enabled - FX Unit 4 - Slot 3 On/Off + [Sampler13] + cue_gotoandstop + SHIFT + PAD 5 0x97 - 0x5A + 0x3C - [EffectRack1_EffectUnit3] - group_[Channel2]_enable - FX Unit 3 On/Off - Deck B + [Sampler14] + cue_gotoandstop + SHIFT + PAD 6 0x97 - 0x5B + 0x3D - [EffectRack1_EffectUnit4_Effect1] - next_effect - FX Unit 4 - Slot 1 next effect + [Sampler15] + cue_gotoandstop + SHIFT + PAD 7 0x97 - 0x5C + 0x3E - [EffectRack1_EffectUnit4_Effect2] - next_effect - FX Unit 4 - Slot 2 next effect + [Sampler16] + cue_gotoandstop + SHIFT + PAD 8 0x97 - 0x5D + 0x3F + - [EffectRack1_EffectUnit4_Effect3] - next_effect - FX Unit 4 - Slot 3 next effect + [Channel2] + DJCi300.deck[1].slicerPad[0].input + PAD 1 0x97 - 0x5E + 0x20 - + - [EffectRack1_EffectUnit4] - group_[Channel2]_enable - FX Unit 4 On/Off - Deck B + [Channel2] + DJCi300.deck[1].slicerPad[1].input + PAD 2 0x97 - 0x5F + 0x21 - + - [Channel2] - beatlooproll_0.125_activate - Loop 1/8 Beat (Pad 1) + DJCi300.deck[1].slicerPad[2].input + PAD 3 0x97 - 0x10 + 0x22 - + [Channel2] - beatlooproll_0.25_activate - Loop 1/4 Beat (Pad 2) + DJCi300.deck[1].slicerPad[3].input + PAD 4 + 0x97 + 0x23 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[4].input + PAD 5 + 0x97 + 0x24 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[5].input + PAD 6 + 0x97 + 0x25 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[6].input + PAD 7 + 0x97 + 0x26 + + + + + + [Channel2] + DJCi300.deck[1].slicerPad[7].input + PAD 8 + 0x97 + 0x27 + + + + + + + [Channel2] + DJCi300.toneplay + PAD 1 + 0x97 + 0x40 + + + + + + [Channel2] + DJCi300.toneplay + PAD 2 + 0x97 + 0x41 + + + + + + [Channel2] + DJCi300.toneplay + PAD 3 + 0x97 + 0x42 + + + + + + [Channel2] + DJCi300.toneplay + PAD 4 + 0x97 + 0x43 + + + + + + [Channel2] + DJCi300.toneplay + PAD 5 + 0x97 + 0x44 + + + + + + [Channel2] + DJCi300.toneplay + PAD 6 + 0x97 + 0x45 + + + + + + [Channel2] + DJCi300.toneplay + PAD 7 + 0x97 + 0x46 + + + + + + [Channel2] + DJCi300.toneplay + PAD 8 + 0x97 + 0x47 + + + + + + [Channel2] + DJCi300.toneplay + SHIFT + PAD 1 + 0x97 + 0x48 + + + + + + [Channel2] + DJCi300.toneplay + SHIFT + PAD 2 0x97 - 0x11 + 0x49 - + [Channel2] - beatlooproll_0.5_activate - Loop 1/2 Beat (Pad 3) + DJCi300.toneplay + SHIFT + PAD 3 0x97 - 0x12 + 0x4A - + [Channel2] - beatlooproll_1_activate - Loop 1 Beat (Pad 4) + DJCi300.toneplay + SHIFT + PAD 4 0x97 - 0x13 + 0x4B - + [Channel2] - beatlooproll_2_activate - Loop 2 Beat (Pad 5) + DJCi300.toneplay + SHIFT + PAD 5 0x97 - 0x14 + 0x4C - + [Channel2] - beatlooproll_4_activate - Loop 4 Beat (Pad 6) + DJCi300.toneplay + SHIFT + PAD 6 0x97 - 0x15 + 0x4D - + [Channel2] - beatlooproll_8_activate - Loop 8 Beat (Pad 7) + DJCi300.toneplay + SHIFT + PAD 7 0x97 - 0x16 + 0x4E - + [Channel2] - beatlooproll_16_activate - Loop 16 Beat (Pad 8) + DJCi300.toneplay + SHIFT + PAD 8 0x97 - 0x17 + 0x4F - + - + + - [Sampler9] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[0].input PAD 1 0x97 - 0x30 + 0x60 - + - [Sampler10] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[1].input PAD 2 0x97 - 0x31 + 0x61 - + - [Sampler11] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[2].input PAD 3 0x97 - 0x32 + 0x62 - + - [Sampler12] - cue_gotoandplay - PAD 1 + [Channel2] + DJCi300.deck[1].slicerPad[3].input + PAD 4 0x97 - 0x33 + 0x63 - + - [Sampler13] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[4].input PAD 5 0x97 - 0x34 + 0x64 - + - [Sampler14] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[5].input PAD 6 0x97 - 0x35 + 0x65 - + - [Sampler15] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[6].input PAD 7 0x97 - 0x36 + 0x66 - + - [Sampler16] - cue_gotoandplay + [Channel2] + DJCi300.deck[1].slicerPad[7].input PAD 8 0x97 - 0x37 + 0x67 - + - - - - - - [Channel2] @@ -1784,7 +2749,7 @@ [Channel1] - DJCi300.scratchWheel + DJCi300.jogWheel Scratch Deck A (Jog-Wheel) 0xB1 0x0A @@ -1794,7 +2759,7 @@ [Channel1] - DJCi300.bendWheel + DJCi300.jogWheel Pitch Bend Deck A (Jog-Wheel) 0xB1 0x09 @@ -1802,16 +2767,6 @@ - - [Channel1] - DJCi300.scratchPad - Pitch Bend Deck A (FX PAD 7 / 8 ) - 0xB1 - 0x0C - - - - [EffectRack1_EffectUnit1_Effect3] @@ -1836,7 +2791,6 @@ - [Channel2] @@ -1923,8 +2877,8 @@ [Channel2] - DJCi300.scratchWheel - Pitch Bend Deck B (Jog-Wheel) + DJCi300.jogWheel + Scratch Deck B (Jog-Wheel) 0xB2 0x0A @@ -1933,7 +2887,7 @@ [Channel2] - DJCi300.bendWheel + DJCi300.jogWheel Pitch Bend Deck B (Jog-Wheel) 0xB2 0x09 @@ -1941,16 +2895,6 @@ - - [Channel2] - DJCi300.scratchPad - Pitch Bend Deck B (FX PAD 7 / 8 ) - 0xB2 - 0x0C - - - - @@ -1978,7 +2922,17 @@ [Channel1] - DJCi300.scratchWheel + DJCi300.jogWheel + SHIFT + Bend Deck A (Jog-Wheel) + 0xB4 + 0x09 + + + + + + [Channel1] + DJCi300.jogWheel SHIFT + Scratch Deck A (Jog-Wheel) 0xB4 0x0A @@ -1991,7 +2945,17 @@ [Channel2] - DJCi300.scratchWheel + DJCi300.jogWheel + SHIFT + Bend Deck B (Jog-Wheel) + 0xB5 + 0x09 + + + + + + [Channel2] + DJCi300.jogWheel SHIFT + Scratch Deck B (Jog-Wheel) 0xB5 0x0A @@ -2251,340 +3215,327 @@ 0x7f 0x0 - - - [Channel1] - end_of_track - Auto DJ On - 0.5 - 1 - 0x91 - 0x1C - 0x7f - 0x0 - - - [Channel1] - end_of_track - Auto DJ On - 0.5 - 1 - 0x91 - 0x1D - 0x7f - 0x0 - - - [Channel2] - end_of_track - Auto DJ On - 0.5 - 1 - 0x92 - 0x1C - 0x7f - 0x0 - - - [Channel2] - end_of_track - Auto DJ On - 0.5 - 1 - 0x92 - 0x1D - 0x7f - 0x0 - [Channel1] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x96 0x00 0x7E 0.5 + 2 [Channel1] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x96 0x01 0x7E 0.5 + 2 [Channel1] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x96 0x02 0x7E 0.5 + 2 [Channel1] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x96 0x03 0x7E 0.5 + 2 [Channel1] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x96 0x04 0x7E 0.5 + 2 [Channel1] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x96 0x05 0x7E 0.5 + 2 [Channel1] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x96 0x06 0x7E 0.5 + 2 [Channel1] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x96 0x07 0x7E 0.5 + 2 [Channel2] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x97 0x00 0x7E 0.5 + 2 [Channel2] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x97 0x01 0x7E 0.5 + 2 [Channel2] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x97 0x02 0x7E 0.5 + 2 [Channel2] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x97 0x03 0x7E 0.5 + 2 [Channel2] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x97 0x04 0x7E 0.5 + 2 [Channel2] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x97 0x05 0x7E 0.5 + 2 [Channel2] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x97 0x06 0x7E 0.5 + 2 [Channel2] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x97 0x07 0x7E 0.5 + 2 [Channel1] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x96 0x08 0x7E 0.5 + 2 [Channel1] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x96 0x09 0x7E 0.5 + 2 [Channel1] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x96 0x0A 0x7E 0.5 + 2 [Channel1] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x96 0x0B 0x7E 0.5 + 2 [Channel1] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x96 0x0C 0x7E 0.5 + 2 [Channel1] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x96 0x0D 0x7E 0.5 + 2 [Channel1] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x96 0x0E 0x7E 0.5 + 2 [Channel1] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x96 0x0F 0x7E 0.5 + 2 [Channel2] - hotcue_1_enabled + hotcue_1_status Hotcue 1 (Pad 1) 0x97 0x08 0x7E 0.5 + 2 [Channel2] - hotcue_2_enabled + hotcue_2_status Hotcue 2 (Pad 2) 0x97 0x09 0x7E 0.5 + 2 [Channel2] - hotcue_3_enabled + hotcue_3_status Hotcue 3 (Pad 3) 0x97 0x0A 0x7E 0.5 + 2 [Channel2] - hotcue_4_enabled + hotcue_4_status Hotcue 4 (Pad 4) 0x97 0x0B 0x7E 0.5 + 2 [Channel2] - hotcue_5_enabled + hotcue_5_status Hotcue 5 (Pad 5) 0x97 0x0C 0x7E 0.5 + 2 [Channel2] - hotcue_6_enabled + hotcue_6_status Hotcue 6 (Pad 6) 0x97 0x0D 0x7E 0.5 + 2 [Channel2] - hotcue_7_enabled + hotcue_7_status Hotcue 7 (Pad 7) 0x97 0x0E 0x7E 0.5 + 2 [Channel2] - hotcue_8_enabled + hotcue_8_status Hotcue 8 (Pad 8) 0x97 0x0F 0x7E 0.5 + 2 @@ -2803,7 +3754,7 @@ 0x7f - [EffectRack1_EffectUnit1] + [EffectRack1_EffectUnit2] group_[Channel1]_enable FX2 is active on Deck A 0.5 @@ -3197,6 +4148,150 @@ 0x37 0x7F 0.5 + + + [Sampler1] + play_indicator + (SHIFT + Pad 1 DECK A) + 0x96 + 0x38 + 0x7F + 0.5 + + + [Sampler9] + play_indicator + (SHIFT + Pad 1 DECK B) + 0x97 + 0x38 + 0x7F + 0.5 + + + [Sampler2] + play_indicator + (SHIFT + Pad 2 DECK A) + 0x96 + 0x39 + 0x7F + 0.5 + + + [Sampler10] + play_indicator + (SHIFT + Pad 2 DECK B) + 0x97 + 0x39 + 0x7F + 0.5 + + + [Sampler3] + play_indicator + (SHIFT + Pad 3 DECK A) + 0x96 + 0x3A + 0x7F + 0.5 + + + [Sampler11] + play_indicator + (SHIFT + Pad 3 DECK B) + 0x97 + 0x3A + 0x7F + 0.5 + + + [Sampler4] + play_indicator + (SHIFT + Pad 4 DECK A) + 0x96 + 0x3B + 0x7F + 0.5 + + + [Sampler12] + play_indicator + (SHIFT + Pad 4 DECK B) + 0x97 + 0x3B + 0x7F + 0.5 + + + [Sampler5] + play_indicator + (SHIFT + Pad 5 DECK A) + 0x96 + 0x3C + 0x7F + 0.5 + + + [Sampler13] + play_indicator + (SHIFT + Pad 5 DECK B) + 0x97 + 0x3C + 0x7F + 0.5 + + + [Sampler6] + play_indicator + (SHIFT + Pad 6 DECK A) + 0x96 + 0x3D + 0x7F + 0.5 + + + [Sampler14] + play_indicator + (SHIFT + Pad 6 DECK B) + 0x97 + 0x3D + 0x7F + 0.5 + + + [Sampler7] + play_indicator + (SHIFT + Pad 7 DECK A) + 0x96 + 0x3E + 0x7F + 0.5 + + + [Sampler15] + play_indicator + (SHIFT + Pad 7 DECK B) + 0x97 + 0x3E + 0x7F + 0.5 + + + [Sampler8] + play_indicator + (SHIFT + Pad 8 DECK A) + 0x96 + 0x3F + 0x7F + 0.5 + + + [Sampler16] + play_indicator + (SHIFT + Pad 4 DECK B) + 0x97 + 0x3F + 0x7F + 0.5 diff --git a/res/controllers/Intech TEK2.midi.xml b/res/controllers/Intech TEK2.midi.xml new file mode 100644 index 00000000000..98f686db408 --- /dev/null +++ b/res/controllers/Intech TEK2.midi.xml @@ -0,0 +1,90 @@ + + + + Intech TEK2 + + + + + + + + [Channel1] + TEK2.wheelTurn + 0xB0 + 0x08 + + + + + + [Channel2] + TEK2.wheelTurn + 0xB0 + 0x09 + + + + + + [Channel1] + TEK2.wheelTouch + 0x90 + 0x28 + + + + + + [Channel2] + TEK2.wheelTouch + 0x90 + 0x29 + + + + + + [Channel1] + play + MIDI Learned from 2 messages. + 0x90 + 0x20 + + + + + + [Channel1] + cue_default + MIDI Learned from 2 messages. + 0x90 + 0x21 + + + + + + [Channel2] + play + MIDI Learned from 2 messages. + 0x90 + 0x22 + + + + + + [Channel2] + cue_default + MIDI Learned from 2 messages. + 0x90 + 0x23 + + + + + + + + diff --git a/res/controllers/Intech TEK2.scripts.js b/res/controllers/Intech TEK2.scripts.js new file mode 100644 index 00000000000..47071783762 --- /dev/null +++ b/res/controllers/Intech TEK2.scripts.js @@ -0,0 +1,33 @@ +// eslint-disable-next-line no-var +var TEK2 = {}; +TEK2.jogScratchSensitivity = 1; +TEK2.jogPitchSensitivity = 1.6; + +TEK2.init = function() {}; + +TEK2.shutdown = function() {}; + +// The button that enables/disables scratching +TEK2.wheelTouch = function(channel, control, value, status, group) { + const deckNumber = script.deckFromGroup(group); + if (value === 0x7f) { + // If button downs + const alpha = 1.0 / 8; + const beta = alpha / 32; + engine.scratchEnable(deckNumber, 100, 33 + 1 / 3, alpha, beta); + } else { + // If button up + engine.scratchDisable(deckNumber); + } +}; + +TEK2.wheelTurn = function(channel, control, value, status, group) { + const newValue = value - 64; + + const deckNumber = script.deckFromGroup(group); + if (engine.isScratching(deckNumber)) { + engine.scratchTick(deckNumber, newValue / TEK2.jogScratchSensitivity); // Scratch! + } else { + engine.setValue(group, "jog", newValue / TEK2.jogPitchSensitivity); // Pitch bend + } +}; diff --git a/res/controllers/Midi_for_light-scripts.js b/res/controllers/Midi_for_light-scripts.js index 28f170f7e21..a7b1a863143 100644 --- a/res/controllers/Midi_for_light-scripts.js +++ b/res/controllers/Midi_for_light-scripts.js @@ -16,33 +16,33 @@ function midi_for_light() {} /////////////////////////////////////////////////////////////// // USER OPTIONS // /////////////////////////////////////////////////////////////// -var midi_channel = 1; // set midi_channel. Valid range: 1 to 16. -var enable_beat = true; // set to false if you not need beat -var enable_bpm = true; // set to false if you not need BPM -var enable_mtc_timecode = false; // set to false if you not need midi time code -var enable_vu_mono_current = false; // set to false if you not need VU mono current -var enable_vu_mono_average_min = false; // set to false if you not need VU mono average min -var enable_vu_mono_average_mid = false; // set to false if you not need VU mono average mid -var enable_vu_mono_average_max = false; // set to false if you not need VU mono average max -var enable_vu_mono_average_fit = true; // set to false if you not need VU mono average fit -var enable_vu_mono_current_meter = false; // set to false if you not need VU mono current meter -var enable_vu_mono_average_meter = true; // set to false if you not need VU mono average meter -var enable_vu_left_current = false; // set to false if you not need VU left current -var enable_vu_left_average_min = false; // set to false if you not need VU left average min -var enable_vu_left_average_mid = false; // set to false if you not need VU left average mid -var enable_vu_left_average_max = false; // set to false if you not need VU left average max -var enable_vu_left_average_fit = true; // set to false if you not need VU left average fit -var enable_vu_left_current_meter = false; // set to false if you not need VU left current meter -var enable_vu_left_average_meter = false; // set to false if you not need VU left average meter -var enable_vu_right_current = false; // set to false if you not need VU right current -var enable_vu_right_average_min = false; // set to false if you not need VU right average min -var enable_vu_right_average_mid = false; // set to false if you not need VU right average mid -var enable_vu_right_average_max = false; // set to false if you not need VU right average max -var enable_vu_right_average_fit = true; // set to false if you not need VU right average fit -var enable_vu_right_current_meter = false; // set to false if you not need VU right current meter -var enable_vu_right_average_meter = false; // set to false if you not need VU right average meter -var deck_ending_time = 15; // set a time (in seconds) in which the playing track is considered to be ending -var deck_ending_priority_factor = 0.9; // decrease the priority of the ending track by this factor +var midi_channel = engine.getSetting("midi_channel"); // set midi_channel. Valid range: 1 to 16. +var enable_beat = engine.getSetting("enable_beat"); // set to false if you not need beat +var enable_bpm = engine.getSetting("enable_bpm"); // set to false if you not need BPM +var enable_mtc_timecode = engine.getSetting("enable_mtc_timecode"); // set to false if you not need midi time code +var deck_ending_time = engine.getSetting("deck_ending_time"); // set a time (in seconds) in which the playing track is considered to be ending +var deck_ending_priority_factor = engine.getSetting("deck_ending_priority_factor"); // decrease the priority of the ending track by this factor +var enable_vu_mono_current = engine.getSetting("enable_vu_mono_current"); // set to false if you not need VU mono current +var enable_vu_mono_average_min = engine.getSetting("enable_vu_mono_average_min"); // set to false if you not need VU mono average min +var enable_vu_mono_average_mid = engine.getSetting("enable_vu_mono_average_mid"); // set to false if you not need VU mono average mid +var enable_vu_mono_average_max = engine.getSetting("enable_vu_mono_average_max"); // set to false if you not need VU mono average max +var enable_vu_mono_average_fit = engine.getSetting("enable_vu_mono_average_fit"); // set to false if you not need VU mono average fit +var enable_vu_mono_current_meter = engine.getSetting("enable_vu_mono_current_meter"); // set to false if you not need VU mono current meter +var enable_vu_mono_average_meter = engine.getSetting("enable_vu_mono_average_meter"); // set to false if you not need VU mono average meter +var enable_vu_left_current = engine.getSetting("enable_vu_left_current"); // set to false if you not need VU left current +var enable_vu_left_average_min = engine.getSetting("enable_vu_left_average_min"); // set to false if you not need VU left average min +var enable_vu_left_average_mid = engine.getSetting("enable_vu_left_average_mid"); // set to false if you not need VU left average mid +var enable_vu_left_average_max = engine.getSetting("enable_vu_left_average_max"); // set to false if you not need VU left average max +var enable_vu_left_average_fit = engine.getSetting("enable_vu_left_average_fit"); // set to false if you not need VU left average fit +var enable_vu_left_current_meter = engine.getSetting("enable_vu_left_current_meter"); // set to false if you not need VU left current meter +var enable_vu_left_average_meter = engine.getSetting("enable_vu_left_average_meter"); // set to false if you not need VU left average meter +var enable_vu_right_current = engine.getSetting("enable_vu_right_current"); // set to false if you not need VU right current +var enable_vu_right_average_min = engine.getSetting("enable_vu_right_average_min"); // set to false if you not need VU right average min +var enable_vu_right_average_mid = engine.getSetting("enable_vu_right_average_mid"); // set to false if you not need VU right average mid +var enable_vu_right_average_max = engine.getSetting("enable_vu_right_average_max"); // set to false if you not need VU right average max +var enable_vu_right_average_fit = engine.getSetting("enable_vu_right_average_fit"); // set to false if you not need VU right average fit +var enable_vu_right_current_meter = engine.getSetting("enable_vu_right_current_meter"); // set to false if you not need VU right current meter +var enable_vu_right_average_meter = engine.getSetting("enable_vu_right_average_meter"); // set to false if you not need VU right average meter /////////////////////////////////////////////////////////////// // GLOBAL FOR SCRIPT, DON'T TOUCH // @@ -85,7 +85,7 @@ midi_for_light.init = function(id) { // called when the MIDI device is opened & ]; midi_for_light.vu_meter_timer = undefined; - engine.connectControl("[Master]", "crossfader", "midi_for_light.calculateDeckPriority"); + engine.makeConnection("[Master]", "crossfader", midi_for_light.calculateDeckPriority); if (enable_vu_meter_global === true) midi_for_light.vu_meter_timer = engine.beginTimer(40, midi_for_light.vuMeter); @@ -95,10 +95,10 @@ midi_for_light.init = function(id) { // called when the MIDI device is opened & for (var i = 0; i <= 3; i++) { deck_beat_watchdog_timer[i] = engine.beginTimer(beat_watchdog_time, () => { midi_for_light.deckBeatWatchdog(i); }); - engine.connectControl(`[Channel${ i + 1 }]`, "beat_active", "midi_for_light.deckBeatOutputToMidi"); - engine.connectControl(`[Channel${ i + 1 }]`, "volume", "midi_for_light.calculateDeckPriority"); - engine.connectControl(`[Channel${ i + 1 }]`, "play", "midi_for_light.deckButtonPlay"); - if (enable_mtc_timecode === true) { engine.connectControl(`[Channel${ i + 1 }]`, "playposition", "midi_for_light.sendMidiMtcFullFrame"); } + engine.makeConnection(`[Channel${ i + 1 }]`, "beat_active", midi_for_light.deckBeatOutputToMidi); + engine.makeConnection(`[Channel${ i + 1 }]`, "volume", midi_for_light.calculateDeckPriority); + engine.makeConnection(`[Channel${ i + 1 }]`, "play", midi_for_light.deckButtonPlay); + if (enable_mtc_timecode === true) { engine.makeConnection(`[Channel${ i + 1 }]`, "playposition", midi_for_light.sendMidiMtcFullFrame); } } midi_for_light.calculateDeckPriority(); diff --git a/res/controllers/Midi_for_light.midi.xml b/res/controllers/Midi_for_light.midi.xml index 9f9668dca46..69b1629fc5a 100644 --- a/res/controllers/Midi_for_light.midi.xml +++ b/res/controllers/Midi_for_light.midi.xml @@ -7,6 +7,274 @@ https://mixxx.discourse.group/t/midi-for-light/15513 http://mixxx.org/wiki/doku.php/midi_for_light + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/controllers/Numark NS6II.midi.xml b/res/controllers/Numark NS6II.midi.xml new file mode 100755 index 00000000000..ea329ce46d0 --- /dev/null +++ b/res/controllers/Numark NS6II.midi.xml @@ -0,0 +1,4990 @@ + + + + Numark NS6II + Swiftb0y + Mapping for the Numark NS6II controller. It is able to fully communicate with the integrated screens. You can manipulate the Beatgrid of the track via the slicer pad page (since mixxx doesn't have slicer capabilities) + Encoded URL to Mixxx wiki page documenting this controller mapping + + + + + + + + + + + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x80 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x81 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x82 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x83 + 0x00 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x84 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x85 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x86 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x87 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x88 + 0x00 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x89 + 0x00 + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x90 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x91 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x92 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x93 + 0x00 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x94 + 0x00 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x95 + 0x00 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x96 + 0x00 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x97 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x98 + 0x00 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x99 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].knobs[1].input + 0xB8 + 0x00 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].knobs[1].input + 0xB9 + 0x00 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.turn.input + 0xBF + 0x00 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x80 + 0x01 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x81 + 0x01 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x82 + 0x01 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x83 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x88 + 0x01 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x89 + 0x01 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x90 + 0x01 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x91 + 0x01 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x92 + 0x01 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x93 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x98 + 0x01 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x99 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].knobs[2].input + 0xB8 + 0x01 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].knobs[2].input + 0xB9 + 0x01 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.turn.input + 0xBF + 0x01 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x80 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x81 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x82 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x83 + 0x02 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x84 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x85 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x86 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x87 + 0x02 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x88 + 0x02 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x89 + 0x02 + + + + + + [NS6II] + NS6II.mixer.channels[0].loadTrackIntoDeck.input + 0x8F + 0x02 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x90 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x91 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x92 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x93 + 0x02 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.cues.input + 0x94 + 0x02 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.cues.input + 0x95 + 0x02 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.cues.input + 0x96 + 0x02 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.cues.input + 0x97 + 0x02 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x98 + 0x02 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x99 + 0x02 + + + + + + [NS6II] + NS6II.mixer.channels[0].loadTrackIntoDeck.input + 0x9F + 0x02 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].knobs[3].input + 0xB8 + 0x02 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].knobs[3].input + 0xB9 + 0x02 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x80 + 0x03 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x81 + 0x03 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x82 + 0x03 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x83 + 0x03 + + + + + + [NS6II] + NS6II.mixer.channels[1].loadTrackIntoDeck.input + 0x8F + 0x03 + + + + + + [Channel1] + NS6II.decks[0].sync.input + 0x90 + 0x03 + + + + + + [Channel2] + NS6II.decks[1].sync.input + 0x91 + 0x03 + + + + + + [Channel3] + NS6II.decks[2].sync.input + 0x92 + 0x03 + + + + + + [Channel4] + NS6II.decks[3].sync.input + 0x93 + 0x03 + + + + + + [NS6II] + NS6II.mixer.channels[1].loadTrackIntoDeck.input + 0x9F + 0x03 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].dryWetKnob.input + 0xB8 + 0x03 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].dryWetKnob.input + 0xB9 + 0x03 + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x80 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x81 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x82 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x83 + 0x04 + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x84 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x85 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x86 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x87 + 0x04 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].effectFocusButton.input + 0x88 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].effectFocusButton.input + 0x89 + 0x04 + + + + + + [NS6II] + NS6II.mixer.channels[2].loadTrackIntoDeck.input + 0x8F + 0x04 + + + + + + [Channel1] + NS6II.decks[0].play.input + 0x90 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].play.input + 0x91 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].play.input + 0x92 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].play.input + 0x93 + 0x04 + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x94 + 0x04 + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x95 + 0x04 + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x96 + 0x04 + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x97 + 0x04 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].effectFocusButton.input + 0x98 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].effectFocusButton.input + 0x99 + 0x04 + + + + + + [NS6II] + NS6II.mixer.channels[2].loadTrackIntoDeck.input + 0x9F + 0x04 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x80 + 0x05 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x81 + 0x05 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x82 + 0x05 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x83 + 0x05 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel1.input + 0x88 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel1.input + 0x89 + 0x05 + + + + + + [NS6II] + NS6II.mixer.channels[3].loadTrackIntoDeck.input + 0x8F + 0x05 + + + + + + [Channel1] + NS6II.decks[0].cue.input + 0x90 + 0x05 + + + + + + [Channel2] + NS6II.decks[1].cue.input + 0x91 + 0x05 + + + + + + [Channel3] + NS6II.decks[2].cue.input + 0x92 + 0x05 + + + + + + [Channel4] + NS6II.decks[3].cue.input + 0x93 + 0x05 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel1.input + 0x98 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel1.input + 0x99 + 0x05 + + + + + + [NS6II] + NS6II.mixer.channels[3].loadTrackIntoDeck.input + 0x9F + 0x05 + + + + + + [Channel1] + NS6II.decks[0].jog.inputTouch + 0x80 + 0x06 + + + + + + [Channel2] + NS6II.decks[1].jog.inputTouch + 0x81 + 0x06 + + + + + + [Channel3] + NS6II.decks[2].jog.inputTouch + 0x82 + 0x06 + + + + + + [Channel4] + NS6II.decks[3].jog.inputTouch + 0x83 + 0x06 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel2.input + 0x88 + 0x06 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel2.input + 0x89 + 0x06 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.press.input + 0x8F + 0x06 + + + + + + [Channel1] + NS6II.decks[0].jog.inputTouch + 0x90 + 0x06 + + + + + + [Channel2] + NS6II.decks[1].jog.inputTouch + 0x91 + 0x06 + + + + + + [Channel3] + NS6II.decks[2].jog.inputTouch + 0x92 + 0x06 + + + + + + [Channel4] + NS6II.decks[3].jog.inputTouch + 0x93 + 0x06 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel2.input + 0x98 + 0x06 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel2.input + 0x99 + 0x06 + + + + + + [NS6II] + NS6II.mixer.browseSection.libraryNavigation.press.input + 0x9F + 0x06 + + + + + + [Channel1] + NS6II.decks[0].jog.inputWheel + 0xB0 + 0x06 + + + + + + [Channel2] + NS6II.decks[1].jog.inputWheel + 0xB1 + 0x06 + + + + + + [Channel3] + NS6II.decks[2].jog.inputWheel + 0xB2 + 0x06 + + + + + + [Channel4] + NS6II.decks[3].jog.inputWheel + 0xB3 + 0x06 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x80 + 0x07 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x81 + 0x07 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x82 + 0x07 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x83 + 0x07 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel3.input + 0x88 + 0x07 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel3.input + 0x89 + 0x07 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x90 + 0x07 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x91 + 0x07 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x92 + 0x07 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x93 + 0x07 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel3.input + 0x98 + 0x07 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel3.input + 0x99 + 0x07 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel4.input + 0x88 + 0x08 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel4.input + 0x89 + 0x08 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableOnChannelButtons.Channel4.input + 0x98 + 0x08 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableOnChannelButtons.Channel4.input + 0x99 + 0x08 + + + + + + [Master] + NS6II.mixer.crossfader.input + 0xBF + 0x08 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.slider.input + 0x84 + 0x09 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.slider.input + 0x85 + 0x09 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.slider.input + 0x86 + 0x09 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.slider.input + 0x87 + 0x09 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.slider.input + 0x94 + 0x09 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.slider.input + 0x95 + 0x09 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.slider.input + 0x96 + 0x09 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.slider.input + 0x97 + 0x09 + + + + + + [Channel1] + NS6II.decks[0].pitch.inputMSB + 0xB0 + 0x09 + + + + + + [Channel2] + NS6II.decks[1].pitch.inputMSB + 0xB1 + 0x09 + + + + + + [Channel3] + NS6II.decks[2].pitch.inputMSB + 0xB2 + 0x09 + + + + + + [Channel4] + NS6II.decks[3].pitch.inputMSB + 0xB3 + 0x09 + + + + + + [Mixer Profile] + NS6II.mixer.crossfaderContour.input + 0xBF + 0x09 + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x80 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x81 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x82 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x83 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x84 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x85 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x86 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x87 + 0x0B + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x88 + 0x0B + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x89 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x90 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x91 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x92 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x93 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x94 + 0x0B + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x95 + 0x0B + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x96 + 0x0B + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x97 + 0x0B + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[1].input + 0x98 + 0x0B + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[1].input + 0x99 + 0x0B + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x80 + 0x0C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x81 + 0x0C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x82 + 0x0C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x83 + 0x0C + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x88 + 0x0C + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x89 + 0x0C + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x90 + 0x0C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x91 + 0x0C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x92 + 0x0C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x93 + 0x0C + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[2].input + 0x98 + 0x0C + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[2].input + 0x99 + 0x0C + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x80 + 0x0D + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x81 + 0x0D + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x82 + 0x0D + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x83 + 0x0D + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x88 + 0x0D + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x89 + 0x0D + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x90 + 0x0D + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x91 + 0x0D + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x92 + 0x0D + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x93 + 0x0D + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].enableButtons[3].input + 0x98 + 0x0D + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].enableButtons[3].input + 0x99 + 0x0D + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.loop.input + 0x84 + 0x0E + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.loop.input + 0x85 + 0x0E + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.loop.input + 0x86 + 0x0E + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.loop.input + 0x87 + 0x0E + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x8F + 0x0E + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.loop.input + 0x94 + 0x0E + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.loop.input + 0x95 + 0x0E + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.loop.input + 0x96 + 0x0E + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.loop.input + 0x97 + 0x0E + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x9F + 0x0E + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x84 + 0x0F + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x85 + 0x0F + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x86 + 0x0F + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x87 + 0x0F + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x8F + 0x0F + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.sampler.input + 0x94 + 0x0F + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.sampler.input + 0x95 + 0x0F + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.sampler.input + 0x96 + 0x0F + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.sampler.input + 0x97 + 0x0F + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x9F + 0x0F + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x80 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x81 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x82 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x83 + 0x10 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.auto.input + 0x84 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.auto.input + 0x85 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.auto.input + 0x86 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.auto.input + 0x87 + 0x10 + + + + + + [Channel1] + NS6II.decks[0].bleep.input + 0x90 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].bleep.input + 0x91 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].bleep.input + 0x92 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].bleep.input + 0x93 + 0x10 + + + + + + [Channel1] + NS6II.decks[0].padUnit.modeSelectors.auto.input + 0x94 + 0x10 + + + + + + [Channel2] + NS6II.decks[1].padUnit.modeSelectors.auto.input + 0x95 + 0x10 + + + + + + [Channel3] + NS6II.decks[2].padUnit.modeSelectors.auto.input + 0x96 + 0x10 + + + + + + [Channel4] + NS6II.decks[3].padUnit.modeSelectors.auto.input + 0x97 + 0x10 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x8F + 0x11 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x9F + 0x11 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x8F + 0x12 + + + + + + [NS6II] + NS6II.mixer.browseSection.back.input + 0x9F + 0x12 + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x8F + 0x13 + + + + + + [NS6II] + NS6II.mixer.browseSection.view.input + 0x9F + 0x13 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x84 + 0x14 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x85 + 0x14 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x86 + 0x14 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x87 + 0x14 + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x8F + 0x14 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x94 + 0x14 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x95 + 0x14 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x96 + 0x14 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x97 + 0x14 + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x9F + 0x14 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x84 + 0x15 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x85 + 0x15 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x86 + 0x15 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x87 + 0x15 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x94 + 0x15 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x95 + 0x15 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x96 + 0x15 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x97 + 0x15 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x84 + 0x16 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x85 + 0x16 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x86 + 0x16 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x87 + 0x16 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x94 + 0x16 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x95 + 0x16 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x96 + 0x16 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x97 + 0x16 + + + + + + [Channel1] + NS6II.mixer.channels[0].preGain.input + 0xB0 + 0x16 + + + + + + [Channel2] + NS6II.mixer.channels[1].preGain.input + 0xB1 + 0x16 + + + + + + [Channel3] + NS6II.mixer.channels[2].preGain.input + 0xB2 + 0x16 + + + + + + [Channel4] + NS6II.mixer.channels[3].preGain.input + 0xB3 + 0x16 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[0].input + 0x80 + 0x17 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[0].input + 0x81 + 0x17 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[0].input + 0x82 + 0x17 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[0].input + 0x83 + 0x17 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x84 + 0x17 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x85 + 0x17 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x86 + 0x17 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x87 + 0x17 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[0].input + 0x90 + 0x17 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[0].input + 0x91 + 0x17 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[0].input + 0x92 + 0x17 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[0].input + 0x93 + 0x17 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x94 + 0x17 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x95 + 0x17 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x96 + 0x17 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x97 + 0x17 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqKnobs[0].input + 0xB0 + 0x17 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqKnobs[0].input + 0xB1 + 0x17 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqKnobs[0].input + 0xB2 + 0x17 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqKnobs[0].input + 0xB3 + 0x17 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[1].input + 0x80 + 0x18 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[1].input + 0x81 + 0x18 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[1].input + 0x82 + 0x18 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[1].input + 0x83 + 0x18 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x84 + 0x18 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x85 + 0x18 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x86 + 0x18 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x87 + 0x18 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[1].input + 0x90 + 0x18 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[1].input + 0x91 + 0x18 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[1].input + 0x92 + 0x18 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[1].input + 0x93 + 0x18 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x94 + 0x18 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x95 + 0x18 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x96 + 0x18 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x97 + 0x18 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqKnobs[1].input + 0xB0 + 0x18 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqKnobs[1].input + 0xB1 + 0x18 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqKnobs[1].input + 0xB2 + 0x18 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqKnobs[1].input + 0xB3 + 0x18 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[2].input + 0x80 + 0x19 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[2].input + 0x81 + 0x19 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[2].input + 0x82 + 0x19 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[2].input + 0x83 + 0x19 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x84 + 0x19 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x85 + 0x19 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x86 + 0x19 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x87 + 0x19 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqCaps[2].input + 0x90 + 0x19 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqCaps[2].input + 0x91 + 0x19 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqCaps[2].input + 0x92 + 0x19 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqCaps[2].input + 0x93 + 0x19 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x94 + 0x19 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x95 + 0x19 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x96 + 0x19 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x97 + 0x19 + + + + + + [Channel1] + NS6II.mixer.channels[0].eqKnobs[2].input + 0xB0 + 0x19 + + + + + + [Channel2] + NS6II.mixer.channels[1].eqKnobs[2].input + 0xB1 + 0x19 + + + + + + [Channel3] + NS6II.mixer.channels[2].eqKnobs[2].input + 0xB2 + 0x19 + + + + + + [Channel4] + NS6II.mixer.channels[3].eqKnobs[2].input + 0xB3 + 0x19 + + + + + + [Channel1] + NS6II.mixer.channels[0].filterCap.input + 0x80 + 0x1A + + + + + + [Channel2] + NS6II.mixer.channels[1].filterCap.input + 0x81 + 0x1A + + + + + + [Channel3] + NS6II.mixer.channels[2].filterCap.input + 0x82 + 0x1A + + + + + + [Channel4] + NS6II.mixer.channels[3].filterCap.input + 0x83 + 0x1A + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x84 + 0x1A + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x85 + 0x1A + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x86 + 0x1A + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x87 + 0x1A + + + + + + [Channel1] + NS6II.mixer.channels[0].filterCap.input + 0x90 + 0x1A + + + + + + [Channel2] + NS6II.mixer.channels[1].filterCap.input + 0x91 + 0x1A + + + + + + [Channel3] + NS6II.mixer.channels[2].filterCap.input + 0x92 + 0x1A + + + + + + [Channel4] + NS6II.mixer.channels[3].filterCap.input + 0x93 + 0x1A + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x94 + 0x1A + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x95 + 0x1A + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x96 + 0x1A + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x97 + 0x1A + + + + + + [Channel1] + NS6II.mixer.channels[0].filter.input + 0xB0 + 0x1A + + + + + + [Channel2] + NS6II.mixer.channels[1].filter.input + 0xB1 + 0x1A + + + + + + [Channel3] + NS6II.mixer.channels[2].filter.input + 0xB2 + 0x1A + + + + + + [Channel4] + NS6II.mixer.channels[3].filter.input + 0xB3 + 0x1A + + + + + + [Channel1] + NS6II.mixer.channels[0].pfl.input + 0x80 + 0x1B + + + + + + [Channel2] + NS6II.mixer.channels[1].pfl.input + 0x81 + 0x1B + + + + + + [Channel3] + NS6II.mixer.channels[2].pfl.input + 0x82 + 0x1B + + + + + + [Channel4] + NS6II.mixer.channels[3].pfl.input + 0x83 + 0x1B + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x84 + 0x1B + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x85 + 0x1B + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x86 + 0x1B + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x87 + 0x1B + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x8F + 0x1B + + + + + + [Channel1] + NS6II.mixer.channels[0].pfl.input + 0x90 + 0x1B + + + + + + [Channel2] + NS6II.mixer.channels[1].pfl.input + 0x91 + 0x1B + + + + + + [Channel3] + NS6II.mixer.channels[2].pfl.input + 0x92 + 0x1B + + + + + + [Channel4] + NS6II.mixer.channels[3].pfl.input + 0x93 + 0x1B + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x94 + 0x1B + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x95 + 0x1B + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x96 + 0x1B + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x97 + 0x1B + + + + + + [NS6II] + NS6II.mixer.browseSection.lprep.input + 0x9F + 0x1B + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x84 + 0x1C + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x85 + 0x1C + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x86 + 0x1C + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x87 + 0x1C + + + + + + [Master] + NS6II.mixer.splitCue.input + 0x8F + 0x1C + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[0].input + 0x94 + 0x1C + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[0].input + 0x95 + 0x1C + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[0].input + 0x96 + 0x1C + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[0].input + 0x97 + 0x1C + + + + + + [Master] + NS6II.mixer.splitCue.input + 0x9F + 0x1C + + + + + + [Channel1] + NS6II.mixer.channels[0].volume.input + 0xB0 + 0x1C + + + + + + [Channel2] + NS6II.mixer.channels[1].volume.input + 0xB1 + 0x1C + + + + + + [Channel1] + NS6II.mixer.channels[2].volume.input + 0xB2 + 0x1C + + + + + + [Channel4] + NS6II.mixer.channels[3].volume.input + 0xB3 + 0x1C + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x84 + 0x1D + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x85 + 0x1D + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x86 + 0x1D + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x87 + 0x1D + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[1].input + 0x94 + 0x1D + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[1].input + 0x95 + 0x1D + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[1].input + 0x96 + 0x1D + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[1].input + 0x97 + 0x1D + + + + + + [Channel1] + NS6II.mixer.channels[0].crossfaderOrientation.input + 0x80 + 0x1E + + + + + + [Channel2] + NS6II.mixer.channels[1].crossfaderOrientation.input + 0x81 + 0x1E + + + + + + [Channel3] + NS6II.mixer.channels[2].crossfaderOrientation.input + 0x82 + 0x1E + + + + + + [Channel4] + NS6II.mixer.channels[3].crossfaderOrientation.input + 0x83 + 0x1E + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x84 + 0x1E + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x85 + 0x1E + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x86 + 0x1E + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x87 + 0x1E + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x8F + 0x1E + + + + + + [Channel1] + NS6II.mixer.channels[0].crossfaderOrientation.input + 0x90 + 0x1E + + + + + + [Channel2] + NS6II.mixer.channels[1].crossfaderOrientation.input + 0x91 + 0x1E + + + + + + [Channel3] + NS6II.mixer.channels[2].crossfaderOrientation.input + 0x92 + 0x1E + + + + + + [Channel4] + NS6II.mixer.channels[3].crossfaderOrientation.input + 0x93 + 0x1E + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[2].input + 0x94 + 0x1E + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[2].input + 0x95 + 0x1E + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[2].input + 0x96 + 0x1E + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[2].input + 0x97 + 0x1E + + + + + + [NS6II] + NS6II.mixer.browseSection.area.input + 0x9F + 0x1E + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x80 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x81 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x82 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x83 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x84 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x85 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x86 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x87 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].slip.input + 0x90 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].slip.input + 0x91 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].slip.input + 0x92 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].slip.input + 0x93 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[3].input + 0x94 + 0x1F + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[3].input + 0x95 + 0x1F + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[3].input + 0x96 + 0x1F + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[3].input + 0x97 + 0x1F + + + + + + [Channel1] + NS6II.decks[0].shiftButton.input + 0x80 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].shiftButton.input + 0x81 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].shiftButton.input + 0x82 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].shiftButton.input + 0x83 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x84 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x85 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x86 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x87 + 0x20 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[1].input + 0x88 + 0x20 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[1].input + 0x89 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].shiftButton.input + 0x90 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].shiftButton.input + 0x91 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].shiftButton.input + 0x92 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].shiftButton.input + 0x93 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[4].input + 0x94 + 0x20 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[4].input + 0x95 + 0x20 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[4].input + 0x96 + 0x20 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[4].input + 0x97 + 0x20 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[1].input + 0x98 + 0x20 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[1].input + 0x99 + 0x20 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x84 + 0x21 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x85 + 0x21 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x86 + 0x21 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x87 + 0x21 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[2].input + 0x88 + 0x21 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[2].input + 0x89 + 0x21 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[5].input + 0x94 + 0x21 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[5].input + 0x95 + 0x21 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[5].input + 0x96 + 0x21 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[5].input + 0x97 + 0x21 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[2].input + 0x98 + 0x21 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[2].input + 0x99 + 0x21 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x84 + 0x22 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x85 + 0x22 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x86 + 0x22 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x87 + 0x22 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[3].input + 0x88 + 0x22 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[3].input + 0x89 + 0x22 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[6].input + 0x94 + 0x22 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[6].input + 0x95 + 0x22 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[6].input + 0x96 + 0x22 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[6].input + 0x97 + 0x22 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].fxCaps[3].input + 0x98 + 0x22 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].fxCaps[3].input + 0x99 + 0x22 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x84 + 0x23 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x85 + 0x23 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x86 + 0x23 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x87 + 0x23 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.pads[7].input + 0x94 + 0x23 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.pads[7].input + 0x95 + 0x23 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.pads[7].input + 0x96 + 0x23 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.pads[7].input + 0x97 + 0x23 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterLeft.input + 0x84 + 0x28 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterLeft.input + 0x85 + 0x28 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterLeft.input + 0x86 + 0x28 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterLeft.input + 0x87 + 0x28 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterLeft.input + 0x94 + 0x28 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterLeft.input + 0x95 + 0x28 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterLeft.input + 0x96 + 0x28 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterLeft.input + 0x97 + 0x28 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterRight.input + 0x84 + 0x29 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterRight.input + 0x85 + 0x29 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterRight.input + 0x86 + 0x29 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterRight.input + 0x87 + 0x29 + + + + + + [Channel1] + NS6II.decks[0].padUnit.padsContainer.parameterRight.input + 0x94 + 0x29 + + + + + + [Channel2] + NS6II.decks[1].padUnit.padsContainer.parameterRight.input + 0x95 + 0x29 + + + + + + [Channel3] + NS6II.decks[2].padUnit.padsContainer.parameterRight.input + 0x96 + 0x29 + + + + + + [Channel4] + NS6II.decks[3].padUnit.padsContainer.parameterRight.input + 0x97 + 0x29 + + + + + + [Channel1] + NS6II.decks[0].pitch.inputLSB + 0xB0 + 0x29 + + + + + + [Channel2] + NS6II.decks[1].pitch.inputLSB + 0xB1 + 0x29 + + + + + + [Channel3] + NS6II.decks[2].pitch.inputLSB + 0xB2 + 0x29 + + + + + + [Channel4] + NS6II.decks[3].pitch.inputLSB + 0xB3 + 0x29 + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x80 + 0x2B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x81 + 0x2B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x82 + 0x2B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x83 + 0x2B + + + + + + [Channel1] + NS6II.decks[0].pitchBendPlus.input + 0x90 + 0x2B + + + + + + [Channel2] + NS6II.decks[1].pitchBendPlus.input + 0x91 + 0x2B + + + + + + [Channel3] + NS6II.decks[2].pitchBendPlus.input + 0x92 + 0x2B + + + + + + [Channel4] + NS6II.decks[3].pitchBendPlus.input + 0x93 + 0x2B + + + + + + [Channel1] + NS6II.decks[0].stripSearch.inputMSB + 0xB0 + 0x2B + + + + + + [Channel2] + NS6II.decks[1].stripSearch.inputMSB + 0xB1 + 0x2B + + + + + + [Channel3] + NS6II.decks[2].stripSearch.inputMSB + 0xB2 + 0x2B + + + + + + [Channel4] + NS6II.decks[3].stripSearch.inputMSB + 0xB3 + 0x2B + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x80 + 0x2C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x81 + 0x2C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x82 + 0x2C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x83 + 0x2C + + + + + + [Channel1] + NS6II.decks[0].pitchBendMinus.input + 0x90 + 0x2C + + + + + + [Channel2] + NS6II.decks[1].pitchBendMinus.input + 0x91 + 0x2C + + + + + + [Channel3] + NS6II.decks[2].pitchBendMinus.input + 0x92 + 0x2C + + + + + + [Channel4] + NS6II.decks[3].pitchBendMinus.input + 0x93 + 0x2C + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].mixMode.input + 0x88 + 0x41 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].mixMode.input + 0x89 + 0x41 + + + + + + [EffectRack1_EffectUnit1] + NS6II.EffectUnits[1].mixMode.input + 0x98 + 0x41 + + + + + + [EffectRack1_EffectUnit2] + NS6II.EffectUnits[2].mixMode.input + 0x99 + 0x41 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x80 + 0x46 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x81 + 0x46 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x82 + 0x46 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x83 + 0x46 + + + + + + [Channel1] + NS6II.decks[0].scratch.input + 0x90 + 0x46 + + + + + + [Channel2] + NS6II.decks[1].scratch.input + 0x91 + 0x46 + + + + + + [Channel3] + NS6II.decks[2].scratch.input + 0x92 + 0x46 + + + + + + [Channel4] + NS6II.decks[3].scratch.input + 0x93 + 0x46 + + + + + + [Channel1] + NS6II.decks[0].stripSearch.inputLSB + 0xB0 + 0x4B + + + + + + [Channel2] + NS6II.decks[1].stripSearch.inputLSB + 0xB1 + 0x4B + + + + + + [Channel3] + NS6II.decks[2].stripSearch.inputLSB + 0xB2 + 0x4B + + + + + + [Channel4] + NS6II.decks[3].stripSearch.inputLSB + 0xB3 + 0x4B + + + + + + [Channel3] + NS6II.mixer.extInputChannel3.input + 0x9F + 0x57 + + + + + + [NS6II] + NS6II.knobCapBehavior.input + 0x9F + 0x59 + + + + + + [NS6II] + NS6II.filterKnobBehavior.input + 0x9F + 0x5A + + + + + + [Channel4] + NS6II.mixer.extInputChannel4.input + 0x9F + 0x60 + + + + + + [Channel1] + NS6II.deckWatcherInput + 0x90 + 0x08 + + + + + + [Channel2] + NS6II.deckWatcherInput + 0x91 + 0x08 + + + + + + [Channel3] + NS6II.deckWatcherInput + 0x92 + 0x08 + + + + + + [Channel4] + NS6II.deckWatcherInput + 0x93 + 0x08 + + + + + + + NS6II.PCSelectorInput + 0x8F + 0x3C + + + + + + + NS6II.PCSelectorInput + 0x9F + 0x3C + + + + + + + NS6II.PCSelectorInput + 0x8F + 0x3D + + + + + + + NS6II.PCSelectorInput + 0x9F + 0x3D + + + + + + + + diff --git a/res/controllers/Numark-NS6II-scripts.js b/res/controllers/Numark-NS6II-scripts.js new file mode 100755 index 00000000000..35a4bc0f545 --- /dev/null +++ b/res/controllers/Numark-NS6II-scripts.js @@ -0,0 +1,1475 @@ +/* + +TODO: +Maybe indicate current loop-/jumpsize by coloring the pads in a gradient? + +Reverse Engineering notes (likely interesting for other Numark Mixtrack-like controllers): + Platter: 1000 steps/revolution + 14bit-precision elements: search strips, pitch + (CC: 0x06 setup display controls) + CC: 0x0E set fine pitch of display + CC: Ox3F set track duration leds + CC: 0x06 set platter pos led + CC: 0x75 (val: 0 turn off all leds, !0 turn all on) (LEDs surface) + CC: 0x7F (val: 0 turn all of, val: !0 turn all elements on) (Display) + on: 0x09 pitch up led (0: off, 1: dimm, 2: full); + on: 0x0A pitch down led (sam vals as pitch up); + on: 0x51 pitch led 0; + on: 0x0D KeyLock display; + on: 0x0E pitch range value; + CC: 0x1F channel VuMeter: 1: off, 1: 1, 21: 2, 41: 3, 61: 4, 81: 5 (clipping) + CC: 0x7E individual setting of some display elements (solo element)? + ON: channel=0xF control=0x3C left PC1/PC2 val: 0x00 (lost control), 0x7F, (gained control) + ON: channel=0xF control=0x3D right PC1/PC2 val: 0x00 (lost control), 0x7F, (gained control) + + Master VUMeters: is set by controller + PAD_COLORS: color channels (r,g,b) encoded in two bits each to form velocity value (0b0rrbbgg) + BPM Display: + Absolute BPM Syx: 0x00,0x20,0x7f,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + Least-significant-Nibble of Last 5 bytes are responsible for the display value + Pitch_percentage_change syx: 0x00,0x20,0x7f,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + Pitch_change_ratio: 0.1bpm=10 offset 5 => 100*bpm (so it hits right in the middle) + Pitch_percentage_change 0%: 0x00,0x20,0x7f,,0xf,0xf,0xf,0xf,0xd,0x5 + set pitch percentage by + get 2's complement of d: (~d + 1 >>> 0) + + pitch range: + midi: ["note_on", note=0x0E, velocity=|bpm|] (abs(bpm) = 100bpm=100velocity) + Time Display: + Set Current Time Syx: 0x00,0x20,0x7f,0x01,0x04,0x08,0x00,0x00,0x00,0x00,0x00,0x00 + Set Track duration syx: 0x00,0x20,0x7f,0x01,0x03,0x08,0x00,0x00,0x00,0x00,0x00,0x00 + Set Track duration syx: 0x00,0x20,0x7f,0x01,0x03,0x08,0x00,0x04,0x0a,0x07,0x03,0x07 + syx[3] = channel (1-based) + switch time display: ["note_on",control=0x46,velocity] only 0x00 || 0x7F (0x00 display elapsed) + Least-significant-Nibble of Last 5 bytes are responsible for the display value + 6bit value increase in sysex = 1ms timer increase on display +*/ + +// eslint-disable-next-line no-var +var NS6II = {}; + +// UserSettings +// available rateRanges to cycle through using the Pitch Bend +/- Buttons. +NS6II.RATE_RANGES = [0.04, 0.08, 0.10, 0.16, 0.24, 0.50, 0.90, 1.00,]; + +// Globals + +NS6II.SCRATCH_SETTINGS = Object.freeze({ + alpha: 1/8, + beta: 0.125/32, +}); + +NS6II.PAD_COLORS = Object.freeze({ + OFF: 0, + RED: {FULL: 48, DIMM: 32, DIMMER: 16}, + YELLOW: {FULL: 60, DIMM: 40}, + GREEN: {FULL: 12, DIMM: 8}, + CELESTE: {FULL: 15, DIMM: 10}, + BLUE: {FULL: 3, DIMM: 2}, + PURPLE: {FULL: 59, DIMM: 34}, + PINK: {FULL: 58, DIMM: 37}, + ORANGE: {FULL: 56, DIMM: 36}, + WHITE: {FULL: 63, DIMM: 42}, +}); + + +NS6II.SERATO_SYX_PREFIX = [0x00, 0x20, 0x7f]; + +components.Button.prototype.off = engine.getSetting("useButtonBacklight") ? 0x01 : 0x00; + +components.HotcueButton.prototype.off = NS6II.PAD_COLORS.OFF; +components.HotcueButton.prototype.sendShifted = true; +components.HotcueButton.prototype.shiftControl = true; +components.HotcueButton.prototype.shiftOffset = 8; +components.HotcueButton.prototype.outConnect = false; + +components.SamplerButton.prototype.sendShifted = true; +components.SamplerButton.prototype.shiftControl = true; +components.SamplerButton.prototype.shiftOffset = 8; +components.HotcueButton.prototype.outConnect = false; + +NS6II.physicalSliderPositions = { + left: 0.5, + right: 0.5, +}; + +NS6II.mixxxColorToDeviceColorCode = colorObj => { + const red = (colorObj.red & 0xC0) >> 2; + const green = (colorObj.green & 0xC0) >> 4; + const blue = (colorObj.blue & 0xC0) >> 6; + return (red | green | blue); +}; + +NS6II.hardwareColorToHex = colorcode => { + const red = (colorcode & 0x30) << 18; + const green = (colorcode & 0x0C) << 12; + const blue = (colorcode & 0x03) << 6; + return (red | green | blue); +}; + +NS6II.padColorMapper = new ColorMapper(_.keyBy(_.range(0, 64), NS6II.hardwareColorToHex)); + +NS6II.RingBufferView = class { + constructor(indexable, startIndex = 0) { + this.indexable = indexable; + this.index = startIndex; + } + advanceBy(n) { + this.index = script.posMod(this.index + n, this.indexable.length); + return this.current(); + } + next() { + return this.advanceBy(1); + } + previous() { + return this.advanceBy(-1); + } + current() { + return this.indexable[this.index]; + } +}; + +NS6II.createFilteredSend = function(filter, send) { + return function(value) { + if (filter.call(this, value)) { + send.call(this, value); + } + }; +}; + +NS6II.createIdempotentSend = function(send) { + return NS6II.createFilteredSend(function(value) { + const v = this._value !== value; + if (v) { + this._value = value; + } + return v; + }, send); +}; + +/** + * creates an this.isPress guarded input handler + * @param {(value: number) => void} func callback that is called on ButtonDown + * @returns {MidiInputHandler} a MIDI handler suitable to be called via a XML binding + */ +NS6II.makeButtonDownInputHandler = function(func) { + return function(channel, control, value, status, _group) { + const isPress = this.isPress(channel, control, value, status); + this.output(isPress); + if (!isPress) { + return; + } + func.call(this, value); + }; +}; + + +NS6II.Deck = function(channelOffset) { + const theDeck = this; + const deckNumber = channelOffset + 1; + this.group = `[Channel${deckNumber}]`; + + const lr = channelOffset % 2 === 0 ? "left" : "right"; + const sliderPosAccessors = { + set: function(pos) { + NS6II.physicalSliderPositions[lr] = pos; + }, + get: function() { + return NS6II.physicalSliderPositions[lr]; + } + }; + + this.slip = new components.Button({ + midi: [0x90+channelOffset, 0x1F], + // shift: [0x90+channelOffset,0x04], + type: components.Button.prototype.types.toggle, + unshift: function() { + this.inKey = "slip_enabled"; + this.outKey = this.inKey; + }, + shift: function() { + // use repeat instead of quantize since that + // is already handled by the SyncButton + this.inKey = "repeat"; + this.outKey = this.inKey; + }, + }); + + // also known as "censor" + this.bleep = new components.Button({ + midi: [0x90 + channelOffset, 0x10], + // shift: [0x90+channelOffset,0x0D] + unshift: function() { + this.inKey = "reverseroll"; + this.outKey = this.inKey; + this.type = components.Button.prototype.types.push; + }, + shift: function() { + this.inKey = "keylock"; + this.outKey = this.inKey; + this.type = components.Button.prototype.types.toggle; + }, + }); + + const takeoverLEDValues = Object.freeze({ + OFF: 0, + DIMM: 1, + FULL: 2, + }); + const takeoverLEDControls = Object.freeze({ + up: 0x09, + center: 0x51, + down: 0x0A, + }); + + const takeoverDistance2Brightness = distance => { + // src/controllers/softtakeover.cpp + // SoftTakeover::kDefaultTakeoverThreshold = 3.0 / 128; + const takeoverThreshold = 3 / 128; + if (distance > takeoverThreshold && distance < 0.10) { + return takeoverLEDValues.DIMM; + } else if (distance >= 0.10) { + return takeoverLEDValues.FULL; + } else { + return takeoverLEDValues.OFF; + } + }; + + const directionOutValueScale = function(softwareSliderPosition) { + const normalizedPhysicalSliderPosition = sliderPosAccessors.get()*2 - 1; + + if ((this.midi[1] !== takeoverLEDControls.up) !== (normalizedPhysicalSliderPosition > softwareSliderPosition)) { + return takeoverLEDValues.OFF; + } + + const distance = Math.abs(normalizedPhysicalSliderPosition - softwareSliderPosition); + return takeoverDistance2Brightness(distance); + }; + + + this.takeoverLeds = new components.ComponentContainer({ + trigger: function() { + this.up.trigger(); + this.down.trigger(); + }, + center: new components.Component({ + midi: [0x90 + channelOffset, takeoverLEDControls.center], + outKey: "rate", + off: 0x00, + send: NS6II.createIdempotentSend(components.Component.prototype.send), + outValueScale: function(value) { + const distance = Math.abs(value); + if (distance === 0) { + return takeoverLEDValues.FULL; + } else if (distance < 0.10) { + return takeoverLEDValues.DIMM; + } else { + return takeoverLEDValues.OFF; + } + } + }), + up: new components.Component({ + midi: [0x90 + channelOffset, takeoverLEDControls.up], + outKey: "rate", + off: 0x00, + send: NS6II.createIdempotentSend(components.Component.prototype.send), + outValueScale: directionOutValueScale, + }), + down: new components.Component({ + midi: [0x90 + channelOffset, takeoverLEDControls.down], + outKey: "rate", + off: 0x00, + send: NS6II.createIdempotentSend(components.Component.prototype.send), + outValueScale: directionOutValueScale, + }), + }); + + // features 14-bit precision + this.pitch = new components.Pot({ + midi: [0xB0 + channelOffset, 0x9], + // LSB: [0x90+channelOffset,0x29] + group: theDeck.group, + inKey: "rate", + invert: true, + inSetParameter: function(value) { + sliderPosAccessors.set(value); + components.Pot.prototype.inSetParameter.call(this, value); + theDeck.takeoverLeds.trigger(); + }, + }); + const rates = new NS6II.RingBufferView(NS6II.RATE_RANGES); + this.pitchBendPlus = new components.Button({ + midi: [0x90 + channelOffset, 0x0B], + // shift: [0x90+channelOffset,0x2B] + unshift: function() { + this.inKey = "rate_temp_up"; + this.input = components.Button.prototype.input; + }, + shift: function() { + this.inKey = "rateRange"; + this.input = NS6II.makeButtonDownInputHandler(function() { + this.inSetValue(rates.next()); + }); + }, + outConnect: false, + }); + this.pitchBendMinus = new components.Button({ + midi: [0x90 + channelOffset, 0x0C], + // shift: [0x90+channelOffset,0x2C] + unshift: function() { + this.inKey = "rate_temp_down"; + this.input = components.Button.prototype.input; + }, + shift: function() { + this.inKey = "rateRange"; + this.input = NS6II.makeButtonDownInputHandler(function() { + this.inSetValue(rates.previous()); + }); + }, + outConnect: false, + }); + this.shiftButton = new components.Button({ + midi: [0x90 + channelOffset, 0x20], + input: function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + NS6II.mixer.shift(); + NS6II.EffectUnits[channelOffset % 2 + 1].shift(); + theDeck.shift(); + } else { + NS6II.mixer.unshift(); + NS6II.EffectUnits[channelOffset % 2 + 1].unshift(); + theDeck.unshift(); + } + }, + }); + + this.sync = new components.SyncButton({ + midi: [0x90 + channelOffset, 0x02], + // shift: [0x90+channelOffset,0x03] + }); + + this.play = new components.PlayButton({ + midi: [0x90 + channelOffset, 0x00], + // shift: [0x90+channelOffset,0x04] + }); + this.cue = new components.CueButton({ + midi: [0x90 + channelOffset, 0x01], + // shift: [0x90+channelOffset,0x05] + }); + + // midi: [0xB0 + channelOffset, 0x06], + this.jog = new components.JogWheelBasic({ + deck: deckNumber, + wheelResolution: 1000, // measurement (1000) wasn't producing accurate results (alt: 1073) + alpha: NS6II.SCRATCH_SETTINGS.alpha, + beta: NS6II.SCRATCH_SETTINGS.beta, + }); + + this.stripSearch = new components.Pot({ + midi: [0xB0 + channelOffset, 0x4D], // no feedback + // input MSB: [0xB0+deck,0x2F] LSB + group: theDeck.group, + inKey: "playposition", + shift: function() { + this.inSetParameter = components.Pot.prototype.inSetParameter; + }, + unshift: function() { + this.inSetParameter = function(value) { + // only allow searching when deck is not playing. + if (!engine.getParameter(this.group, "play")) { + engine.setParameter(this.group, this.inKey, value); + } + }; + }, + }); + this.scratch = new components.Button({ + midi: [0x90 + channelOffset, 0x07], + // shift: [0x90+channelOffset,0x46] + timerMode: false, + unshift: function() { + this.input = NS6II.makeButtonDownInputHandler; + this.input = function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + theDeck.jog.vinylMode = !theDeck.jog.vinylMode; + this.output(theDeck.jog.vinylMode); + } + }; + this.output(theDeck.jog.vinylMode); + }, + shift: function() { + this.input = function(channelmidi, control, value, status, _group) { + if (this.isPress(channelmidi, control, value, status)) { + // toggle between time_elapsed/_remaining display mode + this.timerMode = !this.timerMode; + midi.sendShortMsg(0x90 + channelOffset, 0x46, this.timerMode ? 0x7F : 0x00); + } + }; + }, + }); + + this.display = new NS6II.Display(channelOffset, this); + + this.padUnit = new NS6II.PadModeContainers.ModeSelector(channelOffset+4, this.group); + + this.reconnectComponents(function(c) { + if (c.group === undefined) { + c.group = theDeck.group; + } + }); +}; + +NS6II.Deck.prototype = new components.Deck(); + +// JS implementation of engine/enginexfader.cpp:getPowerCalibration (8005e8cc81f7da91310bfc9088802bf5228a2d43) +NS6II.getPowerCalibration = function(transform) { + return Math.pow(0.5, 1.0/transform); +}; + +// JS implementation of util/rescaler.h:linearToOneByX (a939d976b12b4261f8ba14f7ba5e1f2ce9664342) +NS6II.linearToOneByX = function(input, inMin, inMax, outMax) { + const outRange = outMax - 1; + const inRange = inMax - inMin; + return outMax / (((inMax - input) / inRange * outRange) + 1); +}; + + +NS6II.MixerContainer = function() { + this.channels = []; + for (let i = 0; i < 4; i++) { + this.channels[i] = new NS6II.Channel(i); + } + this.crossfader = new components.Pot({ + midi: [0xBF, 0x08], + group: "[Master]", + inKey: "crossfader", + }); + this.splitCue = new components.Button({ + // There is a bug in Firmware v1.0.4 which causes the headsplit + // control to be sent inverted when the controller status is sent + // (either on PC1/PC2 switch or when requested via sysex). + // Numark is aware of the issue but they don't seem to be interested + // in fixing it, so this implements a workaround. + // `invertNext` should be called whenever the controller dumps the status + // of its physical controls to mixxx. + invertNext: function() { + this._invertNext = true; + this._timerHandle = engine.beginTimer(200, () => { + this._invertNext = false; + }, true); + }, + _invertNext: false, + midi: [0x9F, 0x1C], + group: "[Master]", + inKey: "headSplit", + isPress: function(channelmidi, control, value, status) { + const pressed = components.Button.prototype.isPress.call(this, channelmidi, control, value, status); + return this._invertNext ? !pressed : pressed; + } + }); + this.crossfaderContour = new components.Pot({ + midi: [0xBF, 0x09], + input: function(_channelMidi, _control, value, _status, _group) { + // mimic preferences/dialog/dlgprefcrossfader.cpp:slotUpdateXFader + const transform = NS6II.linearToOneByX(value, 0, 0x7F, 999.6); + engine.setValue("[Mixer Profile]", "xFaderCurve", transform); + const calibration = NS6II.getPowerCalibration(transform); + engine.setValue("[Mixer Profile]", "xFaderCalibration", calibration); + }, + }); + this.extInputChannel3 = new components.Button({ + midi: [0x9F, 0x57], + group: "[Channel3]", + max: 2, + inKey: "mute" + }); + this.extInputChannel4 = new components.Button({ + midi: [0x9F, 0x60], + group: "[Channel4]", + max: 2, + inKey: "mute" + }); + + this.browseSection = new NS6II.BrowseSection(); +}; + +NS6II.MixerContainer.prototype = new components.ComponentContainer(); + +/** + * Serialize a Number into the controller compatible format used in sysex messages + * @param {number} number input Integer to be converted + * @param {boolean} signed specify if the value can be negative. + * @param {number} precision how many nibbles the resulting buffer should have (depends on the message) + * @returns {Array} array of length that can be used to build sysex payloads + */ +NS6II.numberToSysex = function(number, signed, precision) { + const out = Array(precision).fill(0); + // build 2's complement in case number is negative + if (number < 0) { + number = ((~Math.abs(number|0) + 1) >>> 0); + } + // split nibbles of number into array + for (let i = out.length; i; i--) { + out[i-1] = number & 0xF; + number = number >> 4; + } + // set signed bit in sysex payload + if (signed) { + out[0] = (number < 0) ? 0b0111 : 0b1000; + } + return out; +}; +NS6II.sendSysexMessage = function(channel, location, payload) { + const msg = [0xF0].concat(NS6II.SERATO_SYX_PREFIX, channel, location, payload, 0xF7); + midi.sendSysexMsg(msg, msg.length); +}; + + +NS6II.DisplayElement = function(options) { + components.Component.call(this, options); +}; + +NS6II.DisplayElement.prototype = new components.Component({ + send: function(payload) { + if (this.loc !== undefined && this.loc.deck !== undefined && this.loc.control !== undefined) { + NS6II.sendSysexMessage(this.loc.deck, this.loc.control, payload); + } else { + components.Component.prototype.send.call(this, payload); + } + }, + shutdown: function() { + this.output(this.off); + }, +}); + + +NS6II.Display = function(channelOffset) { + const channel = (channelOffset + 1); + const deck = `[Channel${channel}]`; + + // optimization so frequently updated controls don't have to poll seldom + // updated controls each time. + const deckInfoCache = { + // seconds + duration: 0, + // stored as 1% = 100 + rate: 0, + rateDir: 1, // 1 or -1 (like the CO) + trackLoaded: false, + // stored as rotations per second instead of rpm. + vinylControlSpeedTypeRatio: 0, + }; + + const vinylControlSpeedTypeConnection = engine.makeConnection(deck, "vinylcontrol_speed_type", function(value) { + deckInfoCache.vinylControlSpeedTypeRatio = value/60; + }); + vinylControlSpeedTypeConnection.trigger(); + + const rateDirConnection = engine.makeConnection(deck, "rate_dir", function(value) { + deckInfoCache.rateDir = value; + }); + rateDirConnection.trigger(); + + this.keylockUI = new NS6II.DisplayElement({ + midi: [0x90 + channelOffset, 0x0D], + outKey: "keylock", + off: 0x00, + on: 0x7F, + }); + + this.rateRangeUI = new NS6II.DisplayElement({ + midi: [0x90 + channelOffset, 0x0E], + outKey: "rateRange", + off: 0, + outValueScale: function(value) { + deckInfoCache.rate = value * 10000; + return Math.round(value * 100); + } + }); + + this.bpmUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x01}, + outKey: "bpm", + off: 0, + outValueScale: function(value) { + return NS6II.numberToSysex(value * 100, false, 6); + }, + }); + + this.rateChangePercentageUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x02}, + outKey: "rate", + outValueScale: function(value) { + return NS6II.numberToSysex( + value * deckInfoCache.rate * deckInfoCache.rateDir, + true, + 6 + ); + }, + }); + + this.durationUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x3}, + outKey: "duration", + outValueScale: function(value) { + deckInfoCache.duration = value; + return NS6II.numberToSysex(value*62.5, true, 7); + }, + }); + + this.timeElapsedUI = new NS6II.DisplayElement({ + loc: {deck: channel, control: 0x04}, + outKey: "playposition", + outValueScale: function(playpos) { + const elapsedTime = deckInfoCache.duration * playpos; + return NS6II.numberToSysex( + elapsedTime*62.5, // arbitrary controller specific scaling factor + true, // signed int + 7 + ); + }, + }); + + this.playPositionRingUI = new NS6II.DisplayElement({ + midi: [0xB0 + channelOffset, 0x3F], + outKey: "playposition", + max: 0x7F, + off: 0x00, + outValueScale: function(playpos) { + // check if track is loaded because playpos value is 0.5 when there isn't a track loaded. + return deckInfoCache.trackLoaded ? + Math.round(playpos * this.max) : + this.off; + }, + send: NS6II.createIdempotentSend(NS6II.DisplayElement.prototype.send), + }); + + this.vinylStickerPositionUI = new NS6II.DisplayElement({ + midi: [0xB0 + channelOffset, 0x06], + outKey: "playposition", + max: 0x7F, + off: 0x00, + outValueScale: function(playpos) { + const elapsedTime = deckInfoCache.duration * playpos; + return script.posMod(elapsedTime * deckInfoCache.vinylControlSpeedTypeRatio, 1) * this.max; + }, + send: NS6II.createIdempotentSend(NS6II.DisplayElement.prototype.send), + }); + + this.deckLoadedConnection = engine.makeConnection(deck, "track_loaded", function(value) { + deckInfoCache.trackLoaded = value; + }); + this.deckLoadedConnection.trigger(); + +}; + +NS6II.Display.prototype = new components.ComponentContainer(); + +NS6II.PadMode = function(channelOffset) { + components.ComponentContainer.call(this, {}); + + this.constructPads = constructPad => { + this.pads = this.pads.map((_, padIndex) => constructPad(padIndex)); + }; + const makeParameterPressHandler = (control, onButtonDown) => + new components.Button({ + midi: [0x90 + channelOffset, control], + // never outconnect, as these buttons don't have LEDs + outConnect: false, + input: function(channelmidi, control, value, status, group) { + if (this.isPress(channelmidi, control, value, status)) { + onButtonDown(channelmidi, control, value, status, group); + } + }, + }); + this.assignParameterPressHandlerLeft = onButtonDown => { + this.parameterLeft = makeParameterPressHandler(0x28, onButtonDown); + }; + this.assignParameterPressHandlerRight = onButtonDown => { + this.parameterRight = makeParameterPressHandler(0x29, onButtonDown); + }; + // this is a workaround for components, forEachComponent only iterates + // over ownProperties, so these have to constructed by the constructor here + // instead of being merged by the ComponentContainer constructor + this.pads = Array(8).fill(undefined); + const doNothing = () => {}; + this.assignParameterPressHandlerLeft(doNothing); + this.assignParameterPressHandlerLeft(doNothing); +}; + +NS6II.PadMode.prototype = new components.ComponentContainer(); + +NS6II.Pad = function(options) { + components.Button.call(this, options); +}; +NS6II.Pad.prototype = new components.Button({ + // grey could be an alternative as well as a backlight color. + off: engine.getSetting("useButtonBacklight") ? NS6II.PAD_COLORS.RED.DIMMER : NS6II.PAD_COLORS.OFF, + outConnect: false, + sendShifted: true, + shiftControl: true, + shiftOffset: 8, +}); + +NS6II.PadModeContainers = {}; + +NS6II.PadModeContainers.HotcuesRegular = function(channelOffset, hotCueOffset) { + + NS6II.PadMode.call(this, channelOffset); + + this.constructPads(i => + new components.HotcueButton({ + midi: [0x90 + channelOffset, 0x14 + i], + // shift: [0x94+channelOffset,0x1b+i], + number: i + 1 + hotCueOffset, + colorMapper: NS6II.padColorMapper, + // sendRGB: function(colorObj) { + // this.send(NS6II.mixxxColorToDeviceColorCode(colorObj)); + // }, + off: NS6II.PAD_COLORS.OFF, + }) + ); + this.parameterLeft = new components.Button({ + midi: [0x90, 0x28], + unshift: function() { + // TODO change hotcue page + this.inKey = undefined; + this.input = () => {}; + }, + shift: function() { + this.inKey = "hotcue_focus_color_prev"; + this.input = components.Button.prototype.input; + } + }); + this.parameterRight = new components.Button({ + midi: [0x90, 0x29], + unshift: function() { + // TODO change hotcue page + this.inKey = undefined; + this.input = () => {}; + }, + shift: function() { + this.inKey = "hotcue_focus_color_next"; + this.input = components.Button.prototype.input; + } + }); +}; +NS6II.PadModeContainers.HotcuesRegular.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopAuto = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + const theContainer = this; + this.currentBaseLoopSize = engine.getSetting("defaultLoopRootSize"); + + const changeLoopSize = loopSize => { + theContainer.currentBaseLoopSize = _.clamp(loopSize, -5, 7); + theContainer.pads.forEach((c, i) => { + if (c instanceof components.Component) { + c.disconnect(); + const loopSize = Math.pow(2, theContainer.currentBaseLoopSize + i); + c.inKey = `beatloop_${loopSize}_toggle`; + c.outKey = `beatloop_${loopSize}_enabled`; + c.connect(); + c.trigger(); + } + }); + }; + + this.constructPads(i => + new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + // key is set by changeLoopSize() + }) + ); + + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); +}; + +NS6II.PadModeContainers.LoopAuto.prototype = new NS6II.PadMode(); + + +NS6II.PadModeContainers.BeatJump = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + const theContainer = this; + this.currentBaseJumpExponent = engine.getSetting("defaultLoopRootSize"); + + const changeLoopSize = function(loopSize) { + theContainer.currentBaseJumpExponent = _.clamp(loopSize, -5, 2); + + const applyToComponent = function(component, key) { + if (!(component instanceof components.Component)) { + return; + } + component.disconnect(); + component.inKey = key; + component.outKey = component.inKey; + component.connect(); + component.trigger(); + }; + for (let i = 0; i < 4; i++) { + const size = Math.pow(2, theContainer.currentBaseJumpExponent + i); + applyToComponent(theContainer.pads[i], `beatjump_${size}_forward`); + applyToComponent(theContainer.pads[i+4], `beatjump_${size}_backward`); + } + }; + + this.constructPads(i => + new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + // key is set by changeLoopSize() + }) + ); + + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseJumpExponent - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseJumpExponent + 1)); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); +}; + +NS6II.PadModeContainers.BeatJump.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopRoll = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + const theContainer = this; + this.currentBaseLoopSize = engine.getSetting("defaultLoopRootSize"); + + const changeLoopSize = function(loopSize) { + // clamp loopSize to [-5;7] + theContainer.currentBaseLoopSize = Math.min(Math.max(-5, loopSize), 7); + let i = 0; + theContainer.pads.forEach(c => { + if (c instanceof components.Component) { + c.disconnect(); + c.inKey = `beatlooproll_${Math.pow(2, theContainer.currentBaseLoopSize + (i++))}_activate`; + c.outKey = c.inKey; + c.connect(); + c.trigger(); + } + }); + }; + + this.constructPads(i => + new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + type: components.Button.prototype.types.toggle, + // key is set by changeLoopSize() + }) + ); + + this.assignParameterPressHandlerLeft(() => changeLoopSize(theContainer.currentBaseLoopSize - 1)); + this.assignParameterPressHandlerRight(() => changeLoopSize(theContainer.currentBaseLoopSize + 1)); + changeLoopSize(engine.getSetting("defaultLoopRootSize")); +}; + +NS6II.PadModeContainers.LoopRoll.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.LoopControl = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + this.pads[0] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14], + key: "loop_in", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[1] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x15], + key: "loop_out", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM + }); + this.pads[2] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x16], + key: "beatloop_activate", + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[3] = new components.LoopToggleButton({ + midi: [0x90 + channelOffset, 0x17], + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[4] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x19], + key: "beatjump_backward", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[5] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x18], + key: "beatjump_forward", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[6] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1A], + key: "loop_halve", + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); + this.pads[7] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1B], + key: "loop_double", + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); +}; +NS6II.PadModeContainers.LoopControl.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.KeyControl = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + this.pads[0] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14], + key: "sync_key", + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[1] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x15], + key: "pitch_down", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[2] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x16], + key: "pitch_up", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[3] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x17], + inKey: "reset_key", + outKey: "pitch_adjust", + outValueScale: function(pitchAdjust) { + // reset_key sometimes sets the key to some small non-zero value sometimes (probably floating point rounding errors) + // so we check with tolerance here. + const epsilon = 0.001; + return Math.abs(pitchAdjust) > epsilon ? this.on : this.off; + }, + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); + // TODO lower 4 pads; What should I map them to, maybe going by circle of fifths? + for (let i = 4; i < this.pads.length; i++) { + // Dummy pads for now. + this.pads[i] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + trigger: function() { + this.send(this.off); + }, + }); + } +}; + +NS6II.PadModeContainers.KeyControl.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.SamplerNormal = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + this.constructPads(i => + new components.SamplerButton({ + midi: [0x90 + channelOffset, 0x14 + i], + number: i + 1, + empty: NS6II.PAD_COLORS.OFF, + playing: NS6II.PAD_COLORS.WHITE.FULL, + loaded: NS6II.PAD_COLORS.WHITE.DIMM, + }) + ); +}; +NS6II.PadModeContainers.SamplerNormal.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.SamplerVelocity = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + + this.constructPads(i => + new components.SamplerButton({ + midi: [0x90 + channelOffset, 0x14 + i], + number: i + 1, + empty: NS6II.PAD_COLORS.OFF, + playing: NS6II.PAD_COLORS.PINK.FULL, + loaded: NS6II.PAD_COLORS.PINK.DIMM, + volumeByVelocity: true, + }) + ); +}; + +NS6II.PadModeContainers.SamplerVelocity.prototype = new NS6II.PadMode(); + + +NS6II.PadModeContainers.BeatgridSettings = function(channelOffset) { + + NS6II.PadMode.call(this, channelOffset); + + // Same layout as waveform customization in LateNight + // except pads[4] (bottom left button) + + this.pads[0] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14], + key: "beats_translate_curpos", + on: NS6II.PAD_COLORS.RED.FULL, + off: NS6II.PAD_COLORS.RED.DIMM, + }); + this.pads[1] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x15], + key: "beats_translate_earlier", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[2] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x16], + key: "beats_translate_later", + on: NS6II.PAD_COLORS.ORANGE.FULL, + off: NS6II.PAD_COLORS.ORANGE.DIMM, + }); + this.pads[3] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x17], + key: "shift_cues_later", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + this.pads[4] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x18], + key: "bpm_tap", + on: NS6II.PAD_COLORS.GREEN.FULL, + off: NS6II.PAD_COLORS.GREEN.DIMM, + }); + this.pads[5] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x19], + key: "beats_adjust_faster", + on: NS6II.PAD_COLORS.YELLOW.FULL, + off: NS6II.PAD_COLORS.YELLOW.DIMM, + }); + this.pads[6] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1A], + key: "beats_adjust_slower", + on: NS6II.PAD_COLORS.YELLOW.FULL, + off: NS6II.PAD_COLORS.YELLOW.DIMM, + }); + this.pads[7] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x1B], + key: "shift_cues_earlier", + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); +}; + +NS6II.PadModeContainers.BeatgridSettings.prototype = new NS6II.PadMode(); + + +NS6II.PadModeContainers.IntroOutroMarkers = function(channelOffset) { + NS6II.PadMode.call(this, channelOffset); + const keyPrefix = ["intro_start", "intro_end", "outro_start", "outro_end"]; + for (let i = 0; i < keyPrefix.length; ++i) { + this.pads[i] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + outKey: `${keyPrefix[i]}_enabled`, + unshift: function() { + this.inKey = `${keyPrefix[i]}_activate`; + }, + shift: function() { + this.inKey = `${keyPrefix[i]}_clear`; + }, + on: NS6II.PAD_COLORS.BLUE.FULL, + off: NS6II.PAD_COLORS.BLUE.DIMM, + }); + } + // TODO lower 4 pads; What should I map them to? + for (let i = 4; i < this.pads.length; i++) { + // Dummy pads for now. + this.pads[i] = new NS6II.Pad({ + midi: [0x90 + channelOffset, 0x14 + i], + trigger: function() { + this.send(this.off); + }, + }); + } +}; + +NS6II.PadModeContainers.IntroOutroMarkers.prototype = new NS6II.PadMode(); + +NS6II.PadModeContainers.ModeSelector = function(channelOffset, group) { + const theSelector = this; + + const updateSelectorLeds = () => { + theSelector.forEachComponent(c => c.trigger()); + }; + + const setPads = padInstance => { + if (padInstance === theSelector.padsContainer) { + return; + } + theSelector.padsContainer.forEachComponent(function(component) { + component.disconnect(); + }); + theSelector.padsContainer = padInstance; + updateSelectorLeds(); + theSelector.padsContainer.reconnectComponents(function(c) { + if (c.group === undefined) { + c.group = group; + } + }); + }; + + const makeModeSelectorInputHandler = (control, padInstances) => + new components.Button({ + midi: [0x90 + channelOffset, control], + padInstances: new NS6II.RingBufferView(padInstances), + input: function(channelmidi, control, value, status, _group) { + if (!this.isPress(channelmidi, control, value, status)) { + return; + } + if (this.padInstances.current() === theSelector.padsContainer) { + setPads(this.padInstances.next()); + } else { + this.padInstances.index = 0; + setPads(this.padInstances.current()); + } + }, + outValueScale: function(active) { + if (!active) { + return this.off; + } + switch (this.padInstances.index) { + case 0: return 0x04; // solid on + case 1: return 0x02; // blink on/off + case 2: return 0x03; // blink 3x + default: return this.off; + } + }, + trigger: function() { + this.output(this.padInstances.indexable.indexOf(theSelector.padsContainer) !== -1); + }, + }); + + const startupModeInstance = new NS6II.PadModeContainers.HotcuesRegular(channelOffset, 0); + + this.modeSelectors = new components.ComponentContainer({ + cues: makeModeSelectorInputHandler(0x00 /*shift: 0x02*/, [startupModeInstance, new NS6II.PadModeContainers.HotcuesRegular(channelOffset, 8)]), + auto: makeModeSelectorInputHandler(0x10, [new NS6II.PadModeContainers.LoopAuto(channelOffset), new NS6II.PadModeContainers.LoopRoll(channelOffset)]), + loop: makeModeSelectorInputHandler(0x0E, [new NS6II.PadModeContainers.LoopControl(channelOffset), new NS6II.PadModeContainers.KeyControl(channelOffset)]), + sampler: makeModeSelectorInputHandler(0x0B /*shift: 0x0F*/, [new NS6II.PadModeContainers.SamplerNormal(channelOffset), new NS6II.PadModeContainers.SamplerVelocity(channelOffset)]), + slider: makeModeSelectorInputHandler(0x09, [new NS6II.PadModeContainers.BeatJump(channelOffset), new NS6II.PadModeContainers.IntroOutroMarkers(channelOffset), new NS6II.PadModeContainers.BeatgridSettings(channelOffset)]), + }); + + this.padsContainer = new NS6II.PadMode(channelOffset); + setPads(startupModeInstance); +}; + +NS6II.PadModeContainers.ModeSelector.prototype = new components.ComponentContainer(); + +NS6II.Channel = function(channelOffset) { + const deck = `[Channel${channelOffset+1}]`; + this.loadTrackIntoDeck = new components.Button({ + midi: [0x9F, 0x02 + channelOffset], + // midi: [0x90 + channelOffset, 0x17], + group: deck, + shift: function() { + this.inKey = "eject"; + this.outKey = this.inKey; + }, + unshift: function() { + this.inKey = "LoadSelectedTrack"; + this.outKey = this.inKey; + }, + }); + // used to determine whether vumeter on the controller would change + // so messages get only when that is the case. + let lastVuLevel = 0; + this.vuMeterLevelConnection = engine.makeConnection(deck, "vu_meter", value => { + // check if channel is peaking and increase value so that the peaking led gets lit as well + // (the vumeter and the peak led are driven by the same control) (values > 81 light up the peakLED as well) + + // convert high res value to 5 LED resolution + value = Math.floor(value*4) + engine.getValue(deck, "PeakIndicator"); + if (value === lastVuLevel) { + // return early if vumeter has not changed (on the controller) + return; + } else { + lastVuLevel = value; + } + midi.sendShortMsg(0xB0 + channelOffset, 0x1F, value * 20); + }); + this.preGain = new components.Pot({ + midi: [0xB0 + channelOffset, 0x16], + softTakeover: false, + group: deck, + inKey: "pregain" + }); + const eqIndicies = [0, 1, 2]; + this.eqKnobs = eqIndicies.map(i => + new components.Pot({ + midi: [0xB0 + channelOffset, 0x16 + i], + softTakeover: false, + group: `[EqualizerRack1_${deck}_Effect1]`, + inKey: `parameter${3-i}`, + }) + ); + this.eqCaps = eqIndicies.map(i => + new components.Button({ + midi: [0x90 + channelOffset, 0x16 + i], + group: `[EqualizerRack1_${deck}_Effect1]`, + inKey: `button_parameter${3-i}`, + isPress: function(_midiChannel, _control, value, _status) { + return NS6II.knobCapBehavior.state > 1 && value > 0; + } + }) + ); + this.filter = new components.Pot({ + midi: [0xB0 + channelOffset, 0x1A], + softTakeover: false, + group: `[QuickEffectRack1_${deck}]`, + inKey: "super1", + }); + + // Unused ATM + this.filterCap = new components.Button({ + // midi: [0x90 + channelOffset, 0x1A], + // group: "[QuickEffectRack1_" + deck + "_Effect1]", + // inKey: "enabled", + input: function() {}, + }); + + this.pfl = new components.Button({ + midi: [0x90 + channelOffset, 0x1B], + group: deck, + key: "pfl", + // override off as pfl buttons are always backlit (never completely off) + // and only turn dim with 0x00 + off: 0x00, + }); + this.crossfaderOrientation = new components.Component({ + midi: [0x90 + channelOffset, 0x1E], + group: deck, + inKey: "orientation", + inValueScale: function(value) { + // Controller values to represent the orientation and mixxx + // orientation representation don't match. + switch (value) { + case 1: return 0; + case 0: return 1; + case 2: return 2; + default: throw "unreachable!"; + } + }, + }); + + this.volume = new components.Pot({ + midi: [0xB0 + channelOffset, 0x1C], + softTakeover: false, + group: deck, + inKey: "volume", + }); +}; +NS6II.Channel.prototype = new components.ComponentContainer(); + +NS6II.BrowseSection = function() { + this.libraryNavigation = new components.ComponentContainer({ + turn: new components.Encoder({ + midi: [0xBF, 0x00], // shift: [0xBF,0x01] + group: "[Library]", + inKey: "MoveVertical", + shift: function() { + this.stepsize = engine.getSetting("navEncoderAcceleration"); + }, + unshift: function() { + this.stepsize = 1; + }, + input: function(_midiChannel, _control, value, _status, _group) { + this.inSetValue(value === 0x01 ? this.stepsize : -this.stepsize); + }, + }), + press: new components.Button({ + midi: [0x9F, 0x06], + group: "[Library]", + inKey: "GoToItem", + }), + }); + + /** + * @param {number} columnIdToSort Value from `[Library], sort_column` docs + * @returns {MidiInputHandler} a MIDI handler suitable to be called via a XML binding + */ + const makeSortColumnInputHandler = columnIdToSort => + NS6II.makeButtonDownInputHandler(function() { this.inSetValue(columnIdToSort); }); + + const makeSortColumnShiftHandler = inputFun => + function() { + this.group = "[Library]"; + this.inKey = "sort_column_toggle"; + this.outKey = this.inKey; + this.input = inputFun; + this.type = components.Button.prototype.types.push; + }; + + const sortBy = columnIdToSort => makeSortColumnShiftHandler(makeSortColumnInputHandler(columnIdToSort)); + + this.view = new components.Button({ + midi: [0x9F, 0x0E], // shift: [0x9F,0x13], + unshift: function() { + // TODO 2.5: switch to `[Skin], show_maximize_library`. + this.group = "[Master]"; + this.inKey = "maximize_library"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.toggle; + }, + shift: sortBy(script.LIBRARY_COLUMNS.BPM), + }); + this.back = new components.Button({ + midi: [0x9F, 0x11], // shift: [0x9F,0x12] + unshift: function() { + this.group = "[Library]"; + this.inKey = "MoveFocusBackward"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: sortBy(script.LIBRARY_COLUMNS.TITLE), + }); + this.area = new components.Button({ + midi: [0x9F, 0xF], // shift: [0x9F, 0x1E] + unshift: function() { + this.group = "[Library]"; + this.inKey = "MoveFocusForward"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: sortBy(script.LIBRARY_COLUMNS.KEY), + }); + this.lprep = new components.Button({ + midi: [0x9F, 0x1B], // shift: [0x9F, 0x14] + unshift: function() { + this.group = "[PreviewDeck1]"; + this.inKey = "LoadSelectedTrack"; + this.outKey = this.inKey; + this.input = components.Button.prototype.input; + this.type = components.Button.prototype.types.push; + }, + shift: sortBy(script.LIBRARY_COLUMNS.ARTIST), + }); +}; +NS6II.BrowseSection.prototype = new components.ComponentContainer(); + +// TouchFX / TouchAll +NS6II.knobCapBehavior = new components.Button({ + midi: [0x9F, 0x59], + state: 0, + input: function(_midiChannel, _control, value, _status, _group) { + // map 0, 64, 127 to 0, 1, 2 respectively + this.state = Math.round(value/64); + }, +}); + + +// FilterRoll / FilterFX +// Unused ATM +NS6II.filterKnobBehavior = new components.Button({ + midi: [0x9F, 0x5A], + state: 0, + input: function(_channel, _control, value, _status, _group) { + // map 0, 64, 127 to 0, 1, 2 respectively + this.state = Math.round(value/64); + }, +}); + +NS6II.deckWatcherInput = function(midichannel, _control, _value, _status, _group) { + const deck = midichannel; + const toDeck = NS6II.decks[deck]; + const fromDeck = NS6II.decks[(deck + 2) % 4]; + fromDeck.pitch.disconnect(); + toDeck.pitch.connect(); + toDeck.takeoverLeds.trigger(); +}; + +NS6II.PCSelectorInput = function(_midichannel, _control, value, _status, _group) { + if (value > 0) { + NS6II.mixer.splitCue.invertNext(); + } +}; + +NS6II.createEffectUnits = function() { + NS6II.EffectUnits = []; + for (let i = 1; i <= 2; i++) { + NS6II.EffectUnits[i] = new components.EffectUnit(i); + NS6II.EffectUnits[i].fxCaps = []; + for (let ii = 0; ii < 3; ii++) { + NS6II.EffectUnits[i].enableButtons[ii + 1].midi = [0x97 + i, ii]; // shift: [0x97+i,0x0B+ii] + NS6II.EffectUnits[i].fxCaps[ii + 1] = new components.Button({ + midi: [0x97 + i, 0x21 + ii], + group: `[EffectRack1_EffectUnit${NS6II.EffectUnits[i].currentUnitNumber}_Effect${ii+1}]`, + inKey: "enabled", + shifted: false, // used to disable fx input while selecting + input: function(midichannel, control, value, status, _group) { + if (NS6II.knobCapBehavior.state > 0) { + this.inSetParameter(this.isPress(midichannel, control, value, status) && !this.shifted); + } + }, + unshift: function() { + this.shifted = false; + }, + shift: function() { + this.shifted = true; + }, + }); + NS6II.EffectUnits[i].knobs[ii + 1].midi = [0xB7 + i, ii]; + } + NS6II.EffectUnits[i].effectFocusButton.midi = [0x97 + i, 0x04]; + NS6II.EffectUnits[i].dryWetKnob.midi = [0xB7 + i, 0x03]; + NS6II.EffectUnits[i].dryWetKnob.input = function(_midichannel, _control, value, _status, _group) { + if (value === 1) { + this.inSetParameter(this.inGetParameter() + 0.04); + } else if (value === 127) { + this.inSetParameter(this.inGetParameter() - 0.04); + } + }; + NS6II.EffectUnits[i].mixMode = new components.Button({ + midi: [0xB7 + i, 0x41], + type: components.Button.prototype.types.toggle, + inKey: "mix_mode", + group: NS6II.EffectUnits[i].group, + }); + for (let ii = 0; ii < 4; ii++) { + const channel = `Channel${ii + 1}`; + NS6II.EffectUnits[i].enableOnChannelButtons.addButton(channel); + NS6II.EffectUnits[i].enableOnChannelButtons[channel].midi = [0x97 + i, 0x05 + ii]; + } + NS6II.EffectUnits[i].init(); + } +}; + +NS6II.askControllerStatus = function() { + const controllerStatusSysex = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7]; + NS6II.mixer.splitCue.invertNext(); + midi.sendSysexMsg(controllerStatusSysex, controllerStatusSysex.length); +}; + +NS6II.init = function() { + + // force headMix to 0 because it is managed by the controller hardware mixer. + engine.setParameter("[Master]", "headMix", 0); + + NS6II.decks = new components.ComponentContainer(); + for (let i = 0; i < 4; i++) { + NS6II.decks[i] = new NS6II.Deck(i); + } + NS6II.mixer = new NS6II.MixerContainer(); + + NS6II.createEffectUnits(); + + NS6II.askControllerStatus(); +}; + +NS6II.shutdown = function() { + NS6II.mixer.shutdown(); + NS6II.decks.shutdown(); + NS6II.EffectUnits.forEach(unit => unit.shutdown()); +}; diff --git a/res/controllers/Pioneer-DDJ-400.midi.xml b/res/controllers/Pioneer-DDJ-400.midi.xml index 018d8dc682d..f7f68d11616 100644 --- a/res/controllers/Pioneer-DDJ-400.midi.xml +++ b/res/controllers/Pioneer-DDJ-400.midi.xml @@ -889,27 +889,6 @@ - - CUE Channel +SHIFT - press - Adjust BPM to match tapped BPM - [Channel1] - bpm_tap - 0x90 - 0x68 - - - - - - CUE Channel +SHIFT - press - Adjust BPM to match tapped BPM - [Channel2] - bpm_tap - 0x91 - 0x68 - - - - - HEADPHONES MIXING - rotate - Monitor Balance [Master] diff --git a/res/controllers/Pioneer-DDJ-FLX4.midi.xml b/res/controllers/Pioneer-DDJ-FLX4.midi.xml index 12875bbd5a4..21d5ed042c6 100644 --- a/res/controllers/Pioneer-DDJ-FLX4.midi.xml +++ b/res/controllers/Pioneer-DDJ-FLX4.midi.xml @@ -118,7 +118,7 @@ [Channel1] reverseroll 0x90 - 0x47 + 0x0E @@ -138,7 +138,7 @@ [Channel2] reverseroll 0x91 - 0x47 + 0x0E @@ -899,27 +899,6 @@ - - CUE Channel +SHIFT - press - Adjust BPM to match tapped BPM - [Channel1] - bpm_tap - 0x90 - 0x68 - - - - - - CUE Channel +SHIFT - press - Adjust BPM to match tapped BPM - [Channel2] - bpm_tap - 0x91 - 0x68 - - - - - HEADPHONES MIXING - rotate - Monitor Balance [Master] diff --git a/res/controllers/Reloop-Mixage.midi.xml b/res/controllers/Reloop-Mixage.midi.xml new file mode 100644 index 00000000000..363d98cc690 --- /dev/null +++ b/res/controllers/Reloop-Mixage.midi.xml @@ -0,0 +1,1274 @@ + + + + Reloop Mixage + HorstBaerbel & gqzomer + Mapping for the Reloop Mixage Interface Edition MK1, Interface Edition MK2 and Controller Edition. + https://mixxx.discourse.group/t/reloop-mixage-mapping/14779 + https://github.com/mixxxdj/mixxx/wiki/Reloop%20Mixage + reloop_mixage + + + + + + + + + + + [Channel1] + rate_temp_down + 0x90 + 0x01 + + + + + + + [Channel1] + pitch_down + 0x90 + 0x40 + + + + + + + [Channel1] + rate_temp_up + 0x90 + 0x02 + + + + + + + [Channel1] + pitch_up + 0x90 + 0x41 + + + + + + + [Channel1] + Mixage.handleShift + 0x90 + 0x2A + + + + + + + [Channel1] + Mixage.handleLoop + 0x90 + 0x05 + + + + + + + [Channel1] + Mixage.handleLoopIn + 0x90 + 0x44 + + + + + + + [Channel1] + Mixage.handleLoopLength + 0xB0 + 0x20 + + + + + + + [Channel1] + Mixage.handleBeatMove + 0xB0 + 0x5F + + + + + + + [Channel1] + Mixage.handleLoopLengthPress + 0x90 + 0x20 + + + + + + + [Channel1] + Mixage.handleBeatLoopPress + 0x90 + 0x5F + + + + + + + [Channel1] + pregain + 0xB0 + 0x33 + + + + + + + [Channel1] + pregain + 0xB0 + 0x72 + + + + + + + [Channel1] + rate + 0xE0 + + + + + + + [Channel1] + rate + 0xE2 + + + + + + + [Channel1] + Mixage.handleReloop + 0x90 + 0x06 + + + + + + + [Channel1] + Mixage.handleLoopOut + 0x90 + 0x45 + + + + + + + [Channel1] + Mixage.nextEffect + 0x90 + 0x07 + + + + + + + [Channel1] + sync_leader + 0x90 + 0x46 + + + + + + + [Channel1] + Mixage.handleEffectDryWet + 0xB0 + 0x21 + + + + + + + [QuickEffectRack1_[Channel1]] + chain_preset_selector + 0xB0 + 0x60 + + + + + + + [Channel1] + Mixage.handleDryWetPressed + 0x90 + 0x21 + + + + + + + [QuickEffectRack1_[Channel1]] + enabled + 0x90 + 0x60 + + + + + + + [Channel1] + Mixage.handleFxAmount + 0xB0 + 0x34 + + + + + + + [Channel1] + Mixage.handleFilter + 0xB0 + 0x73 + + + + + + + + + + + [Channel1] + Mixage.handleFxPress + 0x90 + 0x08 + + + + + + + [Channel1] + keylock + 0x90 + 0x47 + + + + + + + [Channel1] + Mixage.scrollToggle + 0x90 + 0x03 + + + + + + + [Channel1] + Mixage.scrollToggle + 0x90 + 0x42 + + + + + + + [Channel1] + Mixage.scratchToggle + 0x90 + 0x04 + + + + + + + [Channel1] + Mixage.scratchToggle + 0x90 + 0x43 + + + + + + + [Channel1] + Mixage.wheelTouch + 0x90 + 0x24 + + + + + + + [Channel1] + Mixage.wheelTouch + 0x90 + 0x63 + + + + + + + [Channel1] + Mixage.wheelTurn + 0xB0 + 0x24 + + + + + + + [Channel1] + Mixage.wheelTurn + 0xB0 + 0x63 + + + + + + + [Channel1] + sync_enabled + 0x90 + 0x09 + + + + + + + [Channel1] + hotcue_1_activate + 0x90 + 0x48 + + + + + + + [Channel1] + cue_play + 0x90 + 0x0A + + + + + + + [Channel1] + hotcue_2_activate + 0x90 + 0x49 + + + + + + + [Channel1] + cue_default + 0x90 + 0x0B + + + + + + + [Channel1] + hotcue_3_activate + 0x90 + 0x4A + + + + + + + [Channel1] + Mixage.handlePlay + 0x90 + 0x0C + + + + + + + [Channel1] + hotcue_4_activate + 0x90 + 0x4B + + + + + + + [Channel1] + Mixage.handleTrackLoading + 0x90 + 0x0D + + + + + + + [Library] + MoveLeft + 0x90 + 0x4C + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter3 + 0xB0 + 0x35 + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter3 + 0xB0 + 0x74 + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter2 + 0xB0 + 0x36 + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter2 + 0xB0 + 0x75 + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter1 + 0xB0 + 0x37 + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter1 + 0xB0 + 0x76 + + + + + + + [Channel1] + pfl + 0x90 + 0x0E + + + + + + + [PreviewDeck1] + LoadSelectedTrackAndPlay + 0x90 + 0x4D + + + + + + + [Channel1] + volume + 0xB0 + 0x38 + + + + + + + [Channel1] + volume + 0xB0 + 0x77 + + + + + + + [Channel2] + rate_temp_down + 0x90 + 0x0F + + + + + + + [Channel2] + pitch_down + 0x90 + 0x4E + + + + + + + [Channel2] + rate_temp_up + 0x90 + 0x10 + + + + + + + [Channel2] + pitch_up + 0x90 + 0x4F + + + + + + + [Channel2] + Mixage.handleShift + 0x90 + 0x2B + + + + + + + [Channel2] + Mixage.handleLoop + 0x90 + 0x13 + + + + + + + [Channel2] + Mixage.handleLoopIn + 0x90 + 0x52 + + + + + + + [Channel2] + Mixage.handleLoopLength + 0xB0 + 0x22 + + + + + + + [Channel2] + Mixage.handleBeatMove + 0xB0 + 0x61 + + + + + + + [Channel2] + Mixage.handleLoopLengthPress + 0x90 + 0x22 + + + + + + + [Channel2] + Mixage.handleBeatLoopPress + 0x90 + 0x61 + + + + + + + [Channel2] + pregain + 0xB0 + 0x39 + + + + + + + [Channel2] + pregain + 0xB0 + 0x78 + + + + + + + [Channel2] + rate + 0xE1 + + + + + + + [Channel2] + rate + 0xE3 + + + + + + + [Channel2] + Mixage.handleReloop + 0x90 + 0x14 + + + + + + + [Channel2] + Mixage.handleLoopOut + 0x90 + 0x53 + + + + + + + [Channel2] + Mixage.nextEffect + 0x90 + 0x15 + + + + + + + [Channel2] + sync_leader + 0x90 + 0x54 + + + + + + + [Channel2] + Mixage.handleEffectDryWet + 0xB0 + 0x23 + + + + + + + [QuickEffectRack1_[Channel2]] + chain_preset_selector + 0xB0 + 0x62 + + + + + + + [Channel2] + Mixage.handleDryWetPressed + 0x90 + 0x23 + + + + + + + [QuickEffectRack1_[Channel2]] + enabled + 0x90 + 0x62 + + + + + + + [Channel2] + Mixage.handleFxAmount + 0xB0 + 0x3A + + + + + + + [Channel2] + Mixage.handleFilter + 0xB0 + 0x79 + + + + + + + + + + + [Channel2] + Mixage.handleFxPress + 0x90 + 0x16 + + + + + + + [Channel2] + keylock + 0x90 + 0x55 + + + + + + + [Channel2] + Mixage.scrollToggle + 0x90 + 0x11 + + + + + + + [Channel2] + Mixage.scrollToggle + 0x90 + 0x31 + + + + + + + [Channel2] + Mixage.scratchToggle + 0x90 + 0x12 + + + + + + + [Channel2] + Mixage.scratchToggle + 0x90 + 0x51 + + + + + + + [Channel2] + Mixage.wheelTouch + 0x90 + 0x25 + + + + + + + [Channel2] + Mixage.wheelTouch + 0x90 + 0x64 + + + + + + + [Channel2] + Mixage.wheelTurn + 0xB0 + 0x25 + + + + + + + [Channel2] + Mixage.wheelTurn + 0xB0 + 0x64 + + + + + + + [Channel2] + sync_enabled + 0x90 + 0x17 + + + + + + + [Channel2] + hotcue_1_activate + 0x90 + 0x56 + + + + + + + [Channel2] + cue_play + 0x90 + 0x18 + + + + + + + [Channel2] + hotcue_2_activate + 0x90 + 0x57 + + + + + + + [Channel2] + cue_default + 0x90 + 0x19 + + + + + + + [Channel2] + hotcue_3_activate + 0x90 + 0x58 + + + + + + + [Channel2] + Mixage.handlePlay + 0x90 + 0x1A + + + + + + + [Channel2] + hotcue_4_activate + 0x90 + 0x59 + + + + + + + [Channel2] + Mixage.handleTrackLoading + 0x90 + 0x1B + + + + + + + [Library] + MoveRight + 0x90 + 0x5A + + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter3 + 0xB0 + 0x3B + + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter3 + 0xB0 + 0x7A + + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter2 + 0xB0 + 0x3C + + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter2 + 0xB0 + 0x7B + + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter1 + 0xB0 + 0x3D + + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter1 + 0xB0 + 0x7C + + + + + + + [Channel2] + pfl + 0x90 + 0x1C + + + + + + + [PreviewDeck1] + stop + 0x90 + 0x5B + + + + + + + [Channel2] + volume + 0xB0 + 0x3E + + + + + + + [Channel2] + volume + 0xB0 + 0x7D + + + + + + + [Master] + Mixage.handleTraxPress + 0x90 + 0x1F + + + + + + + [Master] + Mixage.handleTraxPress + 0x90 + 0x5E + + + + + + + [Playlist] + Mixage.selectTrack + 0xB0 + 0x1F + + + + + + + [Playlist] + Mixage.selectPlaylist + 0xB0 + 0x5E + + + + + + + [Master] + headMix + 0xB0 + 0x32 + + + + + + + [Master] + crossfader + 0xB0 + 0x31 + + + + + + + + [Channel1] + pitch_down + 0x90 + 0x01 + 0.5 + + + [Channel1] + pitch_up + 0x90 + 0x02 + 0.5 + + + [Channel1] + rate_temp_down + 0x90 + 0x01 + 0.5 + + + [Channel1] + rate_temp_up + 0x90 + 0x02 + 0.5 + + + [Channel2] + pitch_down + 0x90 + 0x0F + 0.5 + + + [Channel2] + pitch_up + 0x90 + 0x10 + 0.5 + + + [Channel2] + rate_temp_down + 0x90 + 0x0F + 0.5 + + + [Channel2] + rate_temp_up + 0x90 + 0x10 + 0.5 + + + + diff --git a/res/controllers/Reloop-Mixage.scripts.js b/res/controllers/Reloop-Mixage.scripts.js new file mode 100644 index 00000000000..168a13fd725 --- /dev/null +++ b/res/controllers/Reloop-Mixage.scripts.js @@ -0,0 +1,780 @@ +// Name: Reloop Mixage +// Author: HorstBaerbel / gqzomer +// Version: 1.1.4 requires Mixxx 2.4 or higher + +var Mixage = {}; + +// ----- User-configurable settings ----- +Mixage.scratchByWheelTouch = false; // Set to true to scratch by touching the jog wheel instead of having to toggle the disc button. Default is false +Mixage.scratchTicksPerRevolution = 620; // Number of jog wheel ticks that make a full revolution when scratching. Reduce to "scratch more" of the track, increase to "scratch less". Default is 620 (measured) +Mixage.jogWheelScrollSpeed = 1.0; // Scroll speed when the jog wheel is used to scroll through the track. The higher, the faster. Default is 1.0 +Mixage.shortPressTimeout = 400; // timeout to decide between a short press or a long press for various controls, default 400 +Mixage.doublePressTimeout = 800; // timeout to decide between a single or double press, default 800 (currently only used on the Traxx button) + +// ----- Internal variables (don't touch) ----- + +// engine connections +Mixage.vuMeterConnection = []; +Mixage.trackLoadedConnection = []; +Mixage.fxOnConnection = []; +Mixage.fxSelectConnection = []; + +// timers +Mixage.traxxPressTimer = 0; +Mixage.loopLengthPressTimer = { + "[Channel1]": 0, + "[Channel2]": 0, +}; +Mixage.dryWetPressTimer = { + "[Channel1]": 0, + "[Channel2]": 0, +}; +Mixage.scratchTogglePressTimer = { + "[Channel1]": 0, + "[Channel2]": 0, +}; + +// constants +Mixage.numEffectUnits = 4; +Mixage.numEffectSlots = 3; +const ON = 0x7F, OFF = 0x00, DOWN = 0x7F; +const QUICK_PRESS = 1, DOUBLE_PRESS = 2; + +// these objects store the state of different buttons and modes +Mixage.channels = [ + "[Channel1]", + "[Channel2]", +]; + +Mixage.scratchToggleState = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.scrollToggleState = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.wheelTouched = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.loopLengthPressed = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.dryWetPressed = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.scratchPressed = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.adjustLoop = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.adjustLoopIn = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.adjustLoopOut = { + "[Channel1]": false, + "[Channel2]": false, +}; + +Mixage.effectSlotState = { + "[EffectRack1_EffectUnit1]": new Array(Mixage.numEffectSlots).fill(1), + "[EffectRack1_EffectUnit2]": new Array(Mixage.numEffectSlots).fill(1), +}; + +Mixage.blinkTimer = { + "[Channel1]": {}, + "[Channel2]": {}, +}; + +Mixage.amountValue = { + "[Channel1]": 0, + "[Channel2]": 0, +}; + +// Maps channels and their controls to a MIDI control number to toggle their LEDs +Mixage.ledMap = { + "[Channel1]": { + "cue_indicator": 0x0B, + "cue_play": 0x0A, + "play_indicator": 0x0C, + "load_indicator": 0x0D, + "pfl": 0x0E, + "loop": 0x05, + "reloop": 0x06, + "sync_enabled": 0x09, + "fx_on": 0x08, + "fx_sel": 0x07, + "scratch_active": 0x04, + "scroll_active": 0x03, + "vu_meter": 0x1D, + }, + "[Channel2]": { + "cue_indicator": 0x19, + "cue_play": 0x18, + "play_indicator": 0x1A, + "load_indicator": 0x1B, + "pfl": 0x1C, + "loop": 0x13, + "reloop": 0x14, + "sync_enabled": 0x17, + "fx_on": 0x16, + "fx_sel": 0x15, + "scratch_active": 0x12, + "scroll_active": 0x11, + "vu_meter": 0x1E, + } +}; + +// Maps mixxx controls to a function that toggles their LEDs +Mixage.connectionMap = { + "cue_indicator": {"function": function(v, g, c) { Mixage.toggleLED(v, g, c); }}, + "cue_play": {"function": function(v, g, c) { Mixage.toggleLED(v, g, c); }}, + "play_indicator": {"function": function(v, g, c) { Mixage.toggleLED(v, g, c); Mixage.toggleLED(v, g, "load_indicator"); }}, + "pfl": {"function": function(v, g, c) { Mixage.toggleLED(v, g, c); }}, + "loop_enabled": {"function": function(v, g) { Mixage.toggleLoopLEDS(v, g); }}, + "loop_in": {"function": function(v, g) { if (v === 1) { Mixage.toggleLED(ON, g, "loop"); } }}, + "loop_out": {"function": function(v, g) { if (v === 1) { Mixage.toggleLED(ON, g, "reloop"); } }}, + "sync_enabled": {"function": function(v, g, c) { Mixage.toggleLED(v, g, c); }}, +}; + +// ----- Internal variables functions ----- + +// Set or remove functions to call when the state of a mixxx control changes +Mixage.connectControlsToFunctions = function(group, remove) { + for (const control in Mixage.connectionMap) { + if (remove !== undefined) { + Mixage.connectionMap[control][group].disconnect(); + } else { + Mixage.connectionMap[control][group] = engine.makeConnection(group, control, Mixage.connectionMap[control].function); + } + } +}; + +Mixage.init = function(_id, _debugging) { + + // all button LEDs off + for (let i = 0; i < 255; i++) { + midi.sendShortMsg(0x90, i, 0); + } + + // find controls and make engine connections for each channel in Mixage.channels + // A predefined list with channels is used instead of a for loop to prevent engine connections to be overwritten + Mixage.channels.forEach(function(channel) { + const deck = script.deckFromGroup(channel); + Mixage.connectControlsToFunctions(channel); + const effectUnit = `[EffectRack1_EffectUnit${deck}]`; + + // set soft takeovers for effect slot Meta knobs + for (let effectSlot = 1; effectSlot <= Mixage.numEffectSlots; effectSlot++) { + const groupString = `[EffectRack1_EffectUnit${deck}_Effect${effectSlot}]`; + engine.softTakeover(groupString, "meta", true); + } + + for (let effectUnit = 1; effectUnit <= Mixage.numEffectUnits; effectUnit++) { + // make connections for the fx on LEDs + const fxControl = `group_${channel}_enable`; + const fxGroup = `[EffectRack1_EffectUnit${effectUnit}]`; + Mixage.fxOnConnection.push(engine.makeConnection(fxGroup, fxControl, function() { Mixage.toggleFxLED(channel); })); + + // set soft takeovers for effectunit meta + engine.softTakeover(fxGroup, "super1", true); + engine.setValue(fxGroup, "show_focus", 1); + } + + // set soft takeover for Quick Effect + engine.softTakeover(`[QuickEffectRack1_${channel}]`, "super1", true); + + // make connections for status LEDs + Mixage.vuMeterConnection.push(engine.makeUnbufferedConnection(channel, "vu_meter", function(val) { midi.sendShortMsg(0x90, Mixage.ledMap[channel].vu_meter, val * 7); })); + Mixage.trackLoadedConnection.push(engine.makeConnection(channel, "track_loaded", function() { if (Mixage.adjustLoop[channel]) { Mixage.stopLoopAdjust(channel); } })); + Mixage.fxSelectConnection.push(engine.makeConnection(effectUnit, "focused_effect", function(value) { Mixage.handleFxSelect(value, channel); })); + + // get current status and set LEDs accordingly + Mixage.toggleFxLED(channel); + Mixage.handleFxSelect(engine.getValue(effectUnit, "focused_effect"), channel); + }); +}; + +Mixage.shutdown = function() { + + // Disconnect all engine connections that are present + Mixage.vuMeterConnection.forEach(function(connection) { connection.disconnect(); }); + Mixage.trackLoadedConnection.forEach(function(connection) { connection.disconnect(); }); + Mixage.fxSelectConnection.forEach(function(connection) { connection.disconnect(); }); + Mixage.fxOnConnection.forEach(function(connection) { connection.disconnect(); }); + + // Disconnect all controls from functions + Mixage.channels.forEach(function(channel) { Mixage.connectControlsToFunctions(channel, true); }); + + // all button LEDs off + for (let i = 0; i < 255; i++) { + midi.sendShortMsg(0x90, i, 0); + } +}; + +// Toggle the LED on the MIDI controller by sending a MIDI message +Mixage.toggleLED = function(value, group, control) { + midi.sendShortMsg(0x90, Mixage.ledMap[group][control], value ? 0x7F : 0); +}; + +// Toggles the FX On LED / Off when no effect unit is activated for a channel / On when any effect unit is active for a channel +Mixage.toggleFxLED = function(group) { + const fxChannel = `group_${group}_enable`; + const enabledFxGroups = []; + + for (let i = 1; i <= Mixage.numEffectUnits; i++) { + enabledFxGroups.push(engine.getValue(`[EffectRack1_EffectUnit${i}]`, fxChannel)); + } + + if (enabledFxGroups.indexOf(1) !== -1) { + Mixage.toggleLED(ON, group, "fx_on"); + } else { + Mixage.toggleLED(OFF, group, "fx_on"); + } +}; + +// Turns the loop in and reloop LEDs on or off +Mixage.toggleLoopLEDS = function(value, group) { + Mixage.toggleLED(value, group, "loop"); + Mixage.toggleLED(value, group, "reloop"); +}; + +// Enable the adjustment of the loop end or start position with the jogwheel +Mixage.startLoopAdjust = function(group, adjustpoint) { + + // enable adjustment of the loop in point + if (adjustpoint === "start" || adjustpoint === undefined) { + Mixage.adjustLoopIn[group] = true; + Mixage.blinkLED(Mixage.ledMap[group].loop, group, 250); + + if (Mixage.adjustLoopOut[group] && adjustpoint === "start") { + Mixage.adjustLoopOut[group] = false; + Mixage.blinkLED(Mixage.ledMap[group].reloop, group, 0); + Mixage.toggleLED(ON, group, "reloop"); + } + } + + // enable adjustment of the loop out point + if (adjustpoint === "end" || adjustpoint === undefined) { + Mixage.adjustLoopOut[group] = true; + Mixage.blinkLED(Mixage.ledMap[group].reloop, group, 250); + + if (Mixage.adjustLoopIn[group] && adjustpoint === "end") { + Mixage.adjustLoopIn[group] = false; + Mixage.blinkLED(Mixage.ledMap[group].loop, group, 0); + Mixage.toggleLED(ON, group, "loop"); + } + } + + // disable scratch mode if active + if (Mixage.scratchToggleState[group]) { + Mixage.toggleLED(OFF, group, "scratch_active"); + Mixage.scratchToggleState[group] = false; + } + + // disable scroll mode if active + if (Mixage.scrollToggleState[group]) { + Mixage.toggleLED(OFF, group, "scroll_active"); + Mixage.scrollToggleState[group] = false; + } +}; + +// Disable the adjustment of the loop end or start position with the jogwheel +Mixage.stopLoopAdjust = function(group, adjustpoint) { + if (adjustpoint === "start" | adjustpoint === undefined) { + Mixage.adjustLoopIn[group] = false; + Mixage.blinkLED(Mixage.ledMap[group].loop, group, 0); + } + + if (adjustpoint === "end" | adjustpoint === undefined) { + Mixage.adjustLoopOut[group] = false; + Mixage.blinkLED(Mixage.ledMap[group].reloop, group, 0); + } + + if (adjustpoint === undefined) { + Mixage.adjustLoop[group] = false; + } + + if (engine.getValue(group, "loop_enabled") === 1) { + Mixage.toggleLoopLEDS(ON, group); + } +}; + +// Start blinking the LED for a given control based on the time parameter, stops blinking a control light if time is set to zero +// blinking is synchronized with the "indicator_250millis" control and the time parameter is rounded to the closest division of 250ms +Mixage.blinkLED = function(control, group, time) { + + // remove any connection that might be present + if (Object.prototype.hasOwnProperty.call(Mixage.blinkTimer[group], control)) { + Mixage.blinkTimer[group][control].timer.disconnect(); + delete Mixage.blinkTimer[group][control]; + midi.sendShortMsg(0x90, control, OFF); + } + + if (time > 0) { // if a time is given start blinking the led + const cycles = Math.round(time / 250); //convert time to cycles of 250ms + Mixage.blinkTimer[group][control] = {}; + Mixage.blinkTimer[group][control].toggle = 0; + Mixage.blinkTimer[group][control].counter = 0; + + Mixage.blinkTimer[group][control].timer = engine.makeConnection("[App]", "indicator_250ms", function() { + Mixage.blinkTimer[group][control].counter += 1; + + if (Mixage.blinkTimer[group][control].counter === cycles) { + Mixage.blinkTimer[group][control].toggle = !Mixage.blinkTimer[group][control].toggle; + midi.sendShortMsg(0x90, control, Mixage.blinkTimer[group][control].toggle); + Mixage.blinkTimer[group][control].counter = 0; + } + }); + } +}; + +// Runs every time the focused_effect for a channel is changed either by controller or mixxx +Mixage.handleFxSelect = function(value, group) { + const unitNr = script.deckFromGroup(group); + const effectUnit = `[EffectRack1_EffectUnit${unitNr}]`; + if (value === 0) { + Mixage.toggleLED(OFF, group, "fx_sel"); + if (engine.getValue(effectUnit, "super1") !== Mixage.amountValue[group]) { + engine.softTakeoverIgnoreNextValue(effectUnit, "super1"); + } + } else { + Mixage.toggleLED(ON, group, "fx_sel"); + const effectSlot = `[EffectRack1_EffectUnit${unitNr}_Effect${value}]`; + if (engine.getValue(effectSlot, "meta") !== Mixage.amountValue[group]) { + engine.softTakeoverIgnoreNextValue(effectSlot, "meta"); + } + } +}; + +// Callback function for handleTraxPress +// previews a track on a quick press and maximize/minimize the library on double press +Mixage.TraxPressCallback = function(_channel, _control, _value, _status, group, event) { + if (event === QUICK_PRESS) { + if (engine.getValue("[PreviewDeck1]", "play")) { + engine.setValue("[PreviewDeck1]", "stop", true); + } else { + engine.setValue("[PreviewDeck1]", "LoadSelectedTrackAndPlay", true); + } + } + if (event === DOUBLE_PRESS) { + script.toggleControl(group, "maximize_library"); + } + Mixage.traxxPressTimer = 0; +}; + +// toggles the focussed effect or all effect slots in an effect unit on or off +Mixage.toggleEffect = function(group) { + const unitNr = script.deckFromGroup(group); + const effectUnit = `EffectRack1_EffectUnit${unitNr}`; + const effectUnitGroup = `[${effectUnit}]`; + const focusedEffect = engine.getValue(effectUnitGroup, "focused_effect"); + const enabledFxSlots = []; + + if (focusedEffect === 0) { + for (let effectSlot = 1; effectSlot <= Mixage.numEffectSlots; effectSlot++) { + enabledFxSlots.push(engine.getValue(`[${effectUnit}_Effect${effectSlot}]`, "enabled")); + } + + if (enabledFxSlots.indexOf(1) === -1) { + Mixage.effectSlotState[effectUnitGroup].map(function(state, effect) { + engine.setValue(`[${effectUnit}_Effect${effect+1}]`, "enabled", state); + }); + } else { + Mixage.effectSlotState[effectUnitGroup] = enabledFxSlots; + for (let effectSlot = 1; effectSlot <= Mixage.numEffectSlots; effectSlot++) { + engine.setValue(`[${effectUnit}_Effect${effectSlot}]`, "enabled", 0); + } + } + } else { + script.toggleControl(`[${effectUnit}_Effect${focusedEffect}]`, "enabled"); + } +}; + +// ----- functions mapped to buttons ----- + +// selects the loop in point in loop adjustment mode, otherwise trigger "beatloop_activate" +Mixage.handleLoop = function(_channel, _control, value, _status, group) { + if (Mixage.adjustLoop[group]) { // loop adjustment mode is active + if (Mixage.adjustLoopOut[group] && value === DOWN) { // loop out is currently being adjusted, switch to loop in + Mixage.startLoopAdjust(group, "start"); + } else if (Mixage.adjustLoopIn[group] && value === DOWN) { // loop in is currently being adjusted switch to loop in and out + Mixage.startLoopAdjust(group); + } + } else { + if (value === DOWN) { // loop adjustment mode is not active + engine.setValue(group, "beatloop_activate", 1); + } else { + engine.setValue(group, "beatloop_activate", 0); + } + } +}; + +// selects the loop out point in loop adjustment mode, otherwise trigger reloop +Mixage.handleReloop = function(_channel, _control, value, _status, group) { + if (Mixage.adjustLoop[group]) { // loop adjustment mode is active + if (Mixage.adjustLoopIn[group] && value === DOWN) { // loop in is currently being adjusted, switch to loop out + Mixage.startLoopAdjust(group, "end"); + } else if (Mixage.adjustLoopOut[group] && value === DOWN) { // loop out is currently being adjusted switch to loop in and out + Mixage.startLoopAdjust(group); + } + } else { + if (value === DOWN) { // loop adjustment mode is not active + engine.setValue(group, "reloop_toggle", 1); + } else { + engine.setValue(group, "reloop_toggle", 0); + } + } +}; + +// set a loop in point if none is defined, otherwise enable adjustment of the start position with the jogwheel +Mixage.handleLoopIn = function(_channel, _control, value, _status, group) { + if (Mixage.adjustLoop[group]) { // loop adjustment mode is active + if (Mixage.adjustLoopOut[group] && value === DOWN) { // loop out is currently being adjusted, switch to loop in + Mixage.startLoopAdjust(group, "start"); + } else if (Mixage.adjustLoopIn[group] && value === DOWN) { // loop in is currently being adjusted switch to loop in and out + Mixage.startLoopAdjust(group); + } + } else { // loop adjustment mode is not active + if (value === DOWN) { + engine.setValue(group, "loop_in", 1); + } else { + engine.setValue(group, "loop_in", 0); + } + } +}; + +// set a loop in point if none is defined, otherwise enable adjustment of the start position with the jogwheel +Mixage.handleLoopOut = function(_channel, _control, value, _status, group) { + if (Mixage.adjustLoop[group]) { // loop adjustment mode is active + if (Mixage.adjustLoopIn[group] && value === DOWN) { // loop in is currently being adjusted, switch to loop out + Mixage.startLoopAdjust(group, "end"); + } else if (Mixage.adjustLoopOut[group] && value === DOWN) { // loop out is currently being adjusted switch to loop in and out + Mixage.startLoopAdjust(group); + } + } else { + if (value === DOWN) { // loop adjustment mode is not active + engine.setValue(group, "loop_out", 1); + } else { + engine.setValue(group, "loop_out", 0); + } + } +}; + +// Toggle play and make sure the preview deck stops when starting to play in a deck +// brake or softStart a while the scratch toggle button is held +Mixage.handlePlay = function(_channel, _control, value, _status, group) { + const deck = script.deckFromGroup(group); + if (value === DOWN && Mixage.scratchPressed[group]) { // scratch toggle is pressed + if (engine.getValue(group, "play") === 0) { + engine.softStart(deck, true, 1.5); + } else { + engine.brake(deck, true, 0.75); + } + } else if (value === DOWN) { // scratch toggle is not pressed + script.toggleControl(group, "play"); + } +}; + +// Checks whether the Traxx button is double pressed +Mixage.handleTraxPress = function(channel, control, value, status, group) { + if (value === DOWN) { + if (Mixage.traxxPressTimer === 0) { // first press + Mixage.traxxPressTimer = engine.beginTimer(Mixage.doublePressTimeout, function() { + Mixage.TraxPressCallback(channel, control, value, status, group, QUICK_PRESS); + }, true); + } else { // 2nd press (before timer's out) + engine.stopTimer(Mixage.traxxPressTimer); + Mixage.TraxPressCallback(channel, control, value, status, group, DOUBLE_PRESS); + } + } +}; + +// select track when turning the Traxx button +Mixage.selectTrack = function(_channel, _control, value, _status, _group) { + const diff = value - 64; // 0x40 (64) centered control + engine.setValue("[Playlist]", "SelectTrackKnob", diff); +}; + +// select playlist when turning the Traxx button +Mixage.selectPlaylist = function(_channel, _control, value, _status, _group) { + const diff = value - 64; // 0x40 (64) centered control + engine.setValue("[Playlist]", "SelectPlaylist", diff); +}; + +// Stops a preview that might be playing and loads the selected track regardless +Mixage.handleTrackLoading = function(_channel, _control, value, _status, group) { + if (value === DOWN) { + engine.setValue("[PreviewDeck1]", "stop", true); + engine.setValue(group, "LoadSelectedTrack", true); + } +}; + +// Cycle through the effectslots of the effectunit that corresponds to a channel +Mixage.nextEffect = function(_channel, _control, value, _status, group) { + const unitNr = script.deckFromGroup(group); + const controlString = `[EffectRack1_EffectUnit${unitNr}]`; + if (value === DOWN) { + if (engine.getValue(controlString, "focused_effect") === Mixage.numEffectSlots) { // after cycling through all effectslot go back to the start + engine.setValue(controlString, "focused_effect", 0); + } else { // next effect slot + const currentSelection = engine.getValue(controlString, "focused_effect"); + engine.setValue(controlString, "focused_effect", currentSelection + 1); + } + } +}; + +// Handle turning of the Dry/Wet nob +// control the dry/wet when no effect slot is selected else selects the effect for the currently selected effect slot +Mixage.handleEffectDryWet = function(_channel, _control, value, _status, group) { + const unitNr = script.deckFromGroup(group); + const controlString = `[EffectRack1_EffectUnit${unitNr}]`; + const diff = (value - 64); // 0x40 (64) centered control + if (Mixage.dryWetPressed[group]) { + engine.setValue(controlString, "chain_preset_selector", diff); + } else if (engine.getValue(controlString, "focused_effect") === 0) { // no effect slot is selected + const dryWetValue = engine.getValue(controlString, "mix"); + engine.setValue(controlString, "mix", dryWetValue + (diff / 16.0)); + } else { + const focussedEffect = engine.getValue(controlString, "focused_effect"); + engine.setValue(`[EffectRack1_EffectUnit${unitNr}_Effect${focussedEffect}]`, "effect_selector", diff); + } +}; + +// Turns a currently selected effect slot on, if none are selected all effect slots are turned off +Mixage.handleDryWetPressed = function(_channel, _control, value, _status, group) { + if (value === DOWN) { + Mixage.dryWetPressed[group] = true; + Mixage.dryWetPressTimer[group] = engine.beginTimer(Mixage.shortPressTimeout, function() { + Mixage.dryWetPressTimer[group] = 0; + }, true); + } else { + Mixage.dryWetPressed[group] = false; + if (Mixage.dryWetPressTimer[group] !== 0) { + engine.stopTimer(Mixage.dryWetPressTimer[group]); + Mixage.dryWetPressTimer[group] = 0; + Mixage.toggleEffect(group); + } + } +}; + +// Controls the meta for an effect slot if selected, otherwise controls the meta for an effect unit +Mixage.handleFxAmount = function(_channel, _control, value, _status, group) { + const unitNr = script.deckFromGroup(group); + const controlString = `[EffectRack1_EffectUnit${unitNr}]`; + const focussedEffect = engine.getValue(controlString, "focused_effect"); + const amountValue = value / 127; + Mixage.amountValue[group] = amountValue; + // Mixage.takeOver[group] = true; + if (focussedEffect === 0) { // no effect slot is selected + engine.setValue(controlString, "super1", amountValue); + } else { + engine.setValue(`[EffectRack1_EffectUnit${unitNr}_Effect${focussedEffect}]`, "meta", amountValue); + } +}; + +// Turn off any effect units that are enabled for the channel, if none are enabled enable the corresponding effect unit +Mixage.handleFxPress = function(_channel, _control, value, _status, group) { + if (value === DOWN) { + const fxChannel = `group_${group}_enable`; + const unitNr = script.deckFromGroup(group); + const enabledFxGroups = []; + + for (let i = 1; i <= Mixage.numEffectUnits; i++) { + enabledFxGroups.push(engine.getValue(`[EffectRack1_EffectUnit${i}]`, fxChannel)); + } + + if (enabledFxGroups.indexOf(1) !== -1) { + for (let effectUnit = 1; effectUnit <= Mixage.numEffectUnits; effectUnit++) { + engine.setValue(`[EffectRack1_EffectUnit${effectUnit}]`, fxChannel, false); + } + } else { + engine.setValue(`[EffectRack1_EffectUnit${unitNr}]`, fxChannel, true); + } + } +}; + +// This function is necessary to allow for soft takeover of the filter amount button +// see https://github.com/mixxxdj/mixxx/wiki/Midi-Scripting#soft-takeover +Mixage.handleFilter = function(_channel, _control, value, _status, group) { + const amountValue = value / 127; + Mixage.amountValue[group] = amountValue; + engine.setValue(`[QuickEffectRack1_${group}]`, "super1", value / 127); +}; + +// Handles setting soft takeovers when pressing shift +Mixage.handleShift = function(_channel, _control, value, _status, group) { + const unitNr = script.deckFromGroup(group); + if (value === DOWN) { + if (engine.getValue(`[QuickEffectRack1_${group}]`, "super1") !== Mixage.amountValue[group]) { + engine.softTakeoverIgnoreNextValue(`[QuickEffectRack1_${group}]`, "super1"); + } + } else { + const effectUnit = `[EffectRack1_EffectUnit${unitNr}]`; + const focusedEffect = engine.getValue(effectUnit, "focused_effect"); + if (focusedEffect === 0) { + if (engine.getValue(effectUnit, "super1") !== Mixage.amountValue[group]) { + engine.softTakeoverIgnoreNextValue(effectUnit, "super1"); + } + } else { + const effectSlot = `[EffectRack1_EffectUnit${unitNr}_Effect${focusedEffect}]`; + if (engine.getValue(effectSlot, "meta") !== Mixage.amountValue[group]) { + engine.softTakeoverIgnoreNextValue(effectSlot, "meta"); + } + } + } +}; + +// The "disc" button that enables/disables scratching +Mixage.scratchToggle = function(_channel, _control, value, _status, group) { + if (value === DOWN) { + Mixage.scratchPressed[group] = true; + Mixage.scratchTogglePressTimer[group] = engine.beginTimer(Mixage.shortPressTimeout, function() { + Mixage.scratchTogglePressTimer[group] = 0; + }, true); + } else { + Mixage.scratchPressed[group] = false; + if (Mixage.scratchTogglePressTimer[group] !== 0) { + engine.stopTimer(Mixage.scratchTogglePressTimer[group]); + Mixage.scratchTogglePressTimer[group] = 0; + Mixage.stopLoopAdjust(group); + Mixage.scratchToggleState[group] = !Mixage.scratchToggleState[group]; + Mixage.toggleLED(Mixage.scratchToggleState[group], group, "scratch_active"); + if (Mixage.scrollToggleState[group]) { + Mixage.scrollToggleState[group] = !Mixage.scrollToggleState[group]; + Mixage.toggleLED(Mixage.scrollToggleState[group], group, "scroll_active"); + } + } + } +}; + +// The "loupe" button that enables/disables track scrolling +Mixage.scrollToggle = function(_channel, _control, value, _status, group) { + if (value === DOWN) { + Mixage.stopLoopAdjust(group); + Mixage.scrollToggleState[group] = !Mixage.scrollToggleState[group]; + Mixage.toggleLED(Mixage.scrollToggleState[group], group, "scroll_active"); + if (Mixage.scratchToggleState[group]) { + Mixage.scratchToggleState[group] = !Mixage.scratchToggleState[group]; + Mixage.toggleLED(Mixage.scratchToggleState[group], group, "scratch_active"); + } + } +}; + +// The touch function on the wheels that enables/disables scratching +Mixage.wheelTouch = function(_channel, _control, value, _status, group) { + const unitNr = script.deckFromGroup(group); + + if (value === DOWN) { + Mixage.wheelTouched[group] = true; + } else { + Mixage.wheelTouched[group] = false; + } + + if (Mixage.scratchByWheelTouch || Mixage.scratchToggleState[group]) { + if (value === DOWN) { + const alpha = 1.0 / 8.0; + const beta = alpha / 32.0; + engine.scratchEnable(unitNr, Mixage.scratchTicksPerRevolution, 33.33, alpha, beta); + } else { + engine.scratchDisable(unitNr); + } + } +}; + +// The wheel that controls the scratching / jogging +Mixage.wheelTurn = function(_channel, _control, value, _status, group) { + const diff = value - 64; // 0x40 (64) centered control + if (Mixage.adjustLoop[group]) { // loop adjustment + // triple the adjustment rate if the top of the jogwheel is being touched + const factor = Mixage.wheelTouched[group] ? 100 : 33; + if (Mixage.adjustLoopIn[group]) { + const newStartPosition = engine.getValue(group, "loop_start_position") + (diff * factor); + if (newStartPosition < engine.getValue(group, "loop_end_position")) { + engine.setValue(group, "loop_start_position", newStartPosition); + } + } + if (Mixage.adjustLoopOut[group]) { + const newEndPosition = engine.getValue(group, "loop_end_position") + (diff * factor); + if (newEndPosition > engine.getValue(group, "loop_start_position")) { + engine.setValue(group, "loop_end_position", newEndPosition); + } + } + } else if (Mixage.scratchByWheelTouch || Mixage.scratchToggleState[group] || Mixage.scrollToggleState[group]) { + if (Mixage.scrollToggleState[group]) { // scroll deck + // triple the scroll rate if the top of the jogwheel is being touched + const speedFactor = Mixage.wheelTouched[group] ? 0.00020 : 0.000066; + const currentPosition = engine.getValue(group, "playposition"); + engine.setValue(group, "playposition", currentPosition + speedFactor * diff * Mixage.jogWheelScrollSpeed); + } else if (Mixage.wheelTouched[group]) { + const deckNr = script.deckFromGroup(group); + engine.scratchTick(deckNr, diff); // scratch deck + } else { + engine.setValue(group, "jog", diff); // pitch bend deck + } + } +}; + +// stop or start loop adjustment mode +Mixage.handleBeatLoopPress = function(_channel, _control, value, _status, group) { + if (Mixage.adjustLoop[group] && value === DOWN) { + Mixage.stopLoopAdjust(group); + } else if (value === DOWN && engine.getValue(group, "loop_start_position") !== -1 && engine.getValue(group, "loop_end_position") !== -1) { + Mixage.adjustLoop[group] = true; + Mixage.startLoopAdjust(group); + } +}; + +// move the track or an active loop "beatjump_size" number of beats +Mixage.handleBeatMove = function(_channel, _control, value, _status, group) { + const beatjumpSize = (value - 64) * engine.getValue(group, "beatjump_size"); + engine.setValue(group, "beatjump", beatjumpSize); +}; + +// clears a loop on a short press, set internal variable to true to adjust "beatjump_size" +Mixage.handleLoopLengthPress = function(_channel, _control, value, _status, group) { + if (value === DOWN) { + Mixage.loopLengthPressed[group] = true; + Mixage.loopLengthPressTimer[group] = engine.beginTimer(Mixage.shortPressTimeout, function() { + Mixage.loopLengthPressTimer[group] = 0; + }, true); + } else { + Mixage.loopLengthPressed[group] = false; + if (Mixage.loopLengthPressTimer[group] !== 0) { + engine.stopTimer(Mixage.loopLengthPressTimer[group]); + Mixage.loopLengthPressTimer[group] = 0; + if (Mixage.adjustLoop[group]) { + Mixage.stopLoopAdjust(group); + } + script.triggerControl(group, "loop_remove", 100); + } + } +}; + +// changes the loop length if Mixage.loopLengthPressed[group] is false otherwise adjusts the "beatjump_size" +Mixage.handleLoopLength = function(_channel, _control, value, _status, group) { + const diff = (value - 64); // 0x40 (64) centered control + if (Mixage.loopLengthPressed[group]) { + const beatjumpSize = engine.getParameter(group, "beatjump_size"); + const newBeatJumpSize = diff > 0 ? 2 * beatjumpSize : beatjumpSize / 2; + engine.setParameter(group, "beatjump_size", newBeatJumpSize); + } else { + const loopScale = diff > 0 ? "loop_double" : "loop_halve"; + engine.setValue(group, loopScale, true); + } +}; diff --git a/res/controllers/Traktor-Kontrol-S4-MK3.js b/res/controllers/Traktor-Kontrol-S4-MK3.js index 960c2ac50c1..8b3897e79fe 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK3.js +++ b/res/controllers/Traktor-Kontrol-S4-MK3.js @@ -1262,7 +1262,7 @@ class FXSelect extends Button { if (this.mixer.firstPressedFxSelector !== null) { for (const deck of [1, 2, 3, 4]) { const presetNumber = this.mixer.calculatePresetNumber(); - engine.setValue(`[QuickEffectRack1_[Channel${deck}]]`, "loaded_chain_preset", presetNumber + 1); + engine.setValue(`[QuickEffectRack1_[Channel${deck}]]`, "loaded_chain_preset", presetNumber); } } if (this.mixer.firstPressedFxSelector === this.number) { @@ -1296,7 +1296,7 @@ class QuickEffectButton extends Button { } else { const presetNumber = this.mixer.calculatePresetNumber(); this.color = QuickEffectPresetColors[presetNumber - 1]; - engine.setValue(this.group, "loaded_chain_preset", presetNumber + 1); + engine.setValue(this.group, "loaded_chain_preset", presetNumber); this.mixer.firstPressedFxSelector = null; this.mixer.secondPressedFxSelector = null; this.mixer.resetFxSelectorColors(); @@ -1317,7 +1317,7 @@ class QuickEffectButton extends Button { } } presetLoaded(presetNumber) { - this.color = QuickEffectPresetColors[presetNumber - 2]; + this.color = QuickEffectPresetColors[presetNumber - 1]; this.outConnections[1].trigger(); } outConnect() { @@ -2454,7 +2454,7 @@ class S4Mk3Deck extends Deck { let [oldValue, oldTimestamp, speed] = this.oldValue; if (timestamp < oldTimestamp) { - oldTimestamp -= wheelRelativeMax; + oldTimestamp -= wheelTimerMax; } let diff = value - oldValue; @@ -2877,6 +2877,9 @@ class S4Mk3MixerColumn extends ComponentContainer { class S4MK3 { constructor() { + if (engine.getValue("[App]", "num_decks") < 4) { + engine.setValue("[App]", "num_decks", 4); + } if (engine.getValue("[App]", "num_samplers") < 16) { engine.setValue("[App]", "num_samplers", 16); } diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 93c56803ca0..36d0ff74298 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -140,57 +140,6 @@ var colorCodeToObject = function(colorCode) { var script = function() { }; -/** - * Discriminates whether an object was created using the `{}` synthax. - * - * Returns true when was an object was created using the `{}` synthax. - * False if the object is an instance of a class like Date or Proxy or an Array. - * - * isSimpleObject({}) // true - * isSimpleObject(null) // false - * isSimpleObject(undefined) // false - * isSimpleObject(new Date) // false - * isSimpleObject(new (class {})()) // false - * @param {any} obj Object to test - * @returns {boolean} true if obj was created using the `{}` or `new Object()` synthax, false otherwise - */ -const isSimpleObject = function(obj) { - return obj !== null && typeof obj === "object" && obj.constructor.name === "Object"; -}; - -/** - * Deeply merges 2 objects (Arrays and Objects only, not Map for instance). - * @param target {object | Array} Object to merge source into - * @param source {object | Array} Object to merge into source - * @deprecated Use {@link Object.assign} instead - */ -script.deepMerge = function(target, source) { - console.warn("script.deepMerge is deprecated; use Object.assign instead"); - - if (target === source || target === undefined || target === null || source === undefined || source === null) { - return; - } - - if (Array.isArray(target) && Array.isArray(source)) { - const objTarget = target.reduce((acc, val, idx) => Object.assign(acc, {[idx]: val}), {}); - const objSource = source.reduce((acc, val, idx) => Object.assign(acc, {[idx]: val}), {}); - deepMerge(objTarget, objSource); - target.length = 0; - target.push(...Object.values(objTarget)); - } else if (isSimpleObject(target) && isSimpleObject(source)) { - Object.keys(source).forEach(key => { - if ( - Array.isArray(target[key]) && Array.isArray(source[key]) || - isSimpleObject(target[key]) && isSimpleObject(source[key]) - ) { - deepMerge(target[key], source[key]); - } else if (source[key] !== undefined && source[key] !== null) { - Object.assign(target, {[key]: source[key]}); - } - }); - } -}; - // ----------------- Mapping constants --------------------- // Library column value, which can be used to interact with the CO for "[Library] sort_column" @@ -248,6 +197,9 @@ script.midiDebug = function(channel, control, value, status, group) { // Returns the deck number of a "ChannelN" or "SamplerN" group script.deckFromGroup = function(group) { let deck = 0; + if (group === undefined) { + return undefined; + } if (group.substring(2, 8) === "hannel") { // Extract deck number from the group text deck = group.substring(8, group.length - 1); diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 5547af90050..f651b933a51 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1,4 +1,6 @@ -/* global controller */ +// fixing names to to be camelcase would break the API +// so disable it for the entire file for now. +/* eslint-disable camelcase */ /** * Common HID script debugging function. Just to get logging with 'HID' prefix. diff --git a/res/controllers/console-api.d.ts b/res/controllers/console-api.d.ts index fe221958d7a..4dc5b12ac86 100644 --- a/res/controllers/console-api.d.ts +++ b/res/controllers/console-api.d.ts @@ -108,21 +108,23 @@ declare namespace console { /** * Prints the current number of times a particular piece of code has run, along with a message. * - * @param label + * @param label message to be prepended before the count */ function count(label?: string): void; /** * Turns on the JavaScript profiler. * - * @deprecated Not usable for controller mappings for now [see QTBUG-65419]{@link https://bugreports.qt.io/browse/QTBUG-65419} + * @param label measurement label + * @deprecated Not usable for controller mappings for now [see QTBUG-65419]{@link https://bugreports.qt.io/browse/QTBUG-65419} */ function profile(label?: string): void; /** * Turns off the JavaScript profiler. * - * @deprecated Not usable for controller mappings for now [see QTBUG-65419]{@link https://bugreports.qt.io/browse/QTBUG-65419} + * @param label measurement label + * @deprecated Not usable for controller mappings for now [see QTBUG-65419]{@link https://bugreports.qt.io/browse/QTBUG-65419} */ function profileEnd(label?: string): void; diff --git a/res/controllers/engine-api.d.ts b/res/controllers/engine-api.d.ts index f406a745a01..3a8022ce9db 100644 --- a/res/controllers/engine-api.d.ts +++ b/res/controllers/engine-api.d.ts @@ -159,7 +159,7 @@ declare namespace engine { * @param callback JS function, which will be called every time, the value of the connected control changes. * @param disconnect If "true", all connections to the ControlObject are removed. [default = false] * @returns Returns script connection object on success, otherwise 'undefined' or 'false' depending on the error cause. - * @deprecated Use {@link makeConnection} instead + * @deprecated Use {@link engine.makeConnection} instead */ function connectControl(group: string, name: string, callback: CoCallback, disconnect?: boolean): ScriptConnection | boolean | undefined; @@ -173,7 +173,10 @@ declare namespace engine { */ function trigger(group: string, name: string): void; - /** @deprecated Use {@link console.log} instead */ + /** + * @param message string to be logged + * @deprecated Use {@link console.log} instead + */ function log(message: string): void; type TimerID = number; @@ -301,4 +304,41 @@ declare namespace engine { * SoftStart with low factors would take a while until sound is audible. [default = 1.0] */ function softStart(deck: number, activate: boolean, factor?: number): void; + + enum Charset { + ASCII, // American Standard Code for Information Interchange (7-Bit) + UTF_8, // Unicode Transformation Format (8-Bit) + UTF_16LE, // UTF-16 for Little-Endian devices (ARM, x86) + UTF_16BE, // UTF-16 for Big-Endian devices (MIPS, PPC) + UTF_32LE, // UTF-32 for Little-Endian devices (ARM, x86) + UTF_32BE, // UTF-32 for Big-Endian devices (MIPS, PPC) + CentralEurope, // Windows_1250 which includes all characters of ISO_8859_2 + Cyrillic, // Windows_1251 which includes all characters of ISO_8859_5 + Latin1, // Windows_1252 which includes all characters of ISO_8859_1 + Greek, // Windows_1253 which includes all characters of ISO_8859_7 + Turkish, // Windows_1254 which includes all characters of ISO_8859_9 + Hebrew, // Windows_1255 which includes all characters of ISO_8859_8 + Arabic, // Windows_1256 which includes all characters of ISO_8859_6 + Baltic, // Windows_1257 which includes all characters of ISO_8859_13 + Vietnamese, // Windows_1258 which includes all characters of ISO_8859_14 + Latin9, // ISO_8859_15 + Shift_JIS, // Japanese Industrial Standard (JIS X 0208) + EUC_JP, // Extended Unix Code for Japanese + EUC_KR, // Extended Unix Code for Korean + Big5_HKSCS, // Includes all characters of Big5 and the Hong Kong Supplementary Character Set (HKSCS) + KOI8_U, // Includes all characters of KOI8_R for Russian language and adds Ukrainian language characters + UCS2, // Universal Character Set (2-Byte) ISO_10646 + SCSU, // Standard Compression Scheme for Unicode + BOCU_1, // Binary Ordered Compression for Unicode + CESU_8 // Compatibility Encoding Scheme for UTF-16 (8-Bit) + } + + /** + * Converts a string into another charset. + * + * @param value The string to encode + * @param targetCharset The charset to encode the string into. + * @returns The converted String as an array of bytes. Will return an empty buffer on conversion error or unavailable charset. + */ + function convertCharset(targetCharset: Charset, value: string): ArrayBuffer } diff --git a/res/controllers/midi-components-0.0.js b/res/controllers/midi-components-0.0.js index 7470afd59fc..031f51cb901 100644 --- a/res/controllers/midi-components-0.0.js +++ b/res/controllers/midi-components-0.0.js @@ -653,33 +653,8 @@ /** * @param newLayer Layer to apply to this * @param reconnectComponents Whether components should be reconnected or not - * @deprecated since 2.5.0. Use @{ComponentContainer#setLayer} instead */ applyLayer: function(newLayer, reconnectComponents) { - console.warn("ComponentContainer.applyLayer is deprecated; use ComponentContainer.setLayer instead"); - if (reconnectComponents !== false) { - reconnectComponents = true; - } - if (reconnectComponents === true) { - this.forEachComponent(function(component) { - component.disconnect(); - }); - } - - script.deepMerge(this, newLayer); - - if (reconnectComponents === true) { - this.forEachComponent(function(component) { - component.connect(); - component.trigger(); - }); - } - }, - /** - * @param newLayer Layer to apply to this - * @param reconnectComponents Whether components should be reconnected or not - */ - setLayer(newLayer, reconnectComponents) { if (reconnectComponents !== false) { reconnectComponents = true; } @@ -697,7 +672,6 @@ component.trigger(); }); } - }, shutdown: function() { this.forEachComponent(function(component) { diff --git a/res/controllers/midi-controller-api.d.ts b/res/controllers/midi-controller-api.d.ts index b49ef8dd63f..cf5f08b32a5 100644 --- a/res/controllers/midi-controller-api.d.ts +++ b/res/controllers/midi-controller-api.d.ts @@ -1,7 +1,11 @@ +type MidiInputHandler = (channel: number, control: number, value:number, status:number, group:string) => void; + declare interface MidiInputHandlerController { disconnect(): boolean; } +/** MidiControllerJSProxy */ + declare namespace midi { /** @@ -14,7 +18,7 @@ declare namespace midi { function sendShortMsg(status: number, byte1: number, byte2: number): void; /** - * Alias for {@link sendSysexMsg} + * Alias for {@link midi.sendSysexMsg} * Sends a MIDI system-exclusive message of arbitrary number of bytes * * @param dataList List of bytes to send diff --git a/res/images/mixxx-icon-logo-christmas.svg b/res/images/mixxx-icon-logo-christmas.svg new file mode 100644 index 00000000000..34bfc093452 --- /dev/null +++ b/res/images/mixxx-icon-logo-christmas.svg @@ -0,0 +1,126 @@ + + diff --git a/res/images/preferences/dark/ic_preferences_bulk.svg b/res/images/preferences/dark/ic_preferences_bulk.svg new file mode 100644 index 00000000000..5ed0f314364 --- /dev/null +++ b/res/images/preferences/dark/ic_preferences_bulk.svg @@ -0,0 +1,55 @@ + + + + + + + diff --git a/res/images/preferences/dark/ic_preferences_hid.svg b/res/images/preferences/dark/ic_preferences_hid.svg new file mode 100644 index 00000000000..a4ca67acf4a --- /dev/null +++ b/res/images/preferences/dark/ic_preferences_hid.svg @@ -0,0 +1,49 @@ + + + + + + + diff --git a/res/images/preferences/dark/ic_preferences_midi.svg b/res/images/preferences/dark/ic_preferences_midi.svg new file mode 100644 index 00000000000..0365b45798f --- /dev/null +++ b/res/images/preferences/dark/ic_preferences_midi.svg @@ -0,0 +1,42 @@ + + + + + + + diff --git a/res/images/preferences/light/ic_preferences_bulk.svg b/res/images/preferences/light/ic_preferences_bulk.svg new file mode 100644 index 00000000000..3c427ba6403 --- /dev/null +++ b/res/images/preferences/light/ic_preferences_bulk.svg @@ -0,0 +1,20 @@ + + + + + + + diff --git a/res/images/preferences/light/ic_preferences_hid.svg b/res/images/preferences/light/ic_preferences_hid.svg new file mode 100644 index 00000000000..b4ce5b5ce2d --- /dev/null +++ b/res/images/preferences/light/ic_preferences_hid.svg @@ -0,0 +1,45 @@ + + + + + + + diff --git a/res/images/preferences/light/ic_preferences_midi.svg b/res/images/preferences/light/ic_preferences_midi.svg new file mode 100644 index 00000000000..94be84ee660 --- /dev/null +++ b/res/images/preferences/light/ic_preferences_midi.svg @@ -0,0 +1,67 @@ + + + + + + + diff --git a/res/linux/org.mixxx.Mixxx.desktop b/res/linux/org.mixxx.Mixxx.desktop index 4218ed78544..0d63ef8ff75 100644 --- a/res/linux/org.mixxx.Mixxx.desktop +++ b/res/linux/org.mixxx.Mixxx.desktop @@ -1,14 +1,54 @@ [Desktop Entry] Version=1.0 Name=Mixxx -Name[de]=Mixxx GenericName=Digital DJ interface GenericName[de]=Digitales DJ-System GenericName[fr]=Interface numérique pour DJ +GenericName[nl]=Digitaal DJ-systeem +GenericName[it]=Sistema DJ digitale +GenericName[es]=Sistema DJ digital +GenericName[cz]=Digitální DJ systém +GenericName[ru]=Цифровая диджейская система +GenericName[uk]=Цифрова діджейська система +GenericName[sl]=Digitalni DJ sistem +GenericName[pl]=Cyfrowy system DJ-ski +GenericName[zh]=数字 DJ 系统 +GenericName[sv]=Digitalt DJ-system +GenericName[pt]=Sistema de DJ digital +GenericName[ro]=Sistem DJ digital +GenericName[ja]=デジタルDJシステム +GenericName[hu]=Digitális DJ rendszer +GenericName[tr]=Dijital DJ sistemi +GenericName[gr]=Ψηφιακό σύστημα DJ +GenericName[fi]=Digitaalinen DJ-järjestelmä +GenericName[ko]=디지털 DJ 시스템 +GenericName[nb]=Digitalt DJ-system +GenericName[bg]=Цифрова DJ система Comment=A digital DJ interface Comment[de]=Ein digitales DJ-System Comment[fr]=Une interface numérique pour DJ +Comment[nl]=Een digitaal DJ-systeem +Comment[it]=Un sistema DJ digitale +Comment[es]=Un sistema de DJ digital +Comment[cz]=Digitální DJ systém +Comment[ru]=Цифровая диджейская система +Comment[uk]=Цифрова діджейська система +Comment[sl]=Digitalni DJ sistem +Comment[pl]=Cyfrowy system DJ-ski +Comment[zh]=数字 DJ 系统 +Comment[sv]=Ett digitalt DJ-system +Comment[pt]=Um sistema de DJ digital +Comment[ro]=Un sistem DJ digital +Comment[ja]=デジタルDJシステム +Comment[hu]=Digitális DJ rendszer +Comment[tr]=Dijital bir DJ sistemi +Comment[gr]=Ένα ψηφιακό σύστημα DJ +Comment[fi]=Digitaalinen DJ-järjestelmä +Comment[ko]=디지털 DJ 시스템 +Comment[nb]=Et digitalt DJ-system +Comment[bg]=Цифрова DJ система Exec=sh -c "pasuspender -- mixxx || mixxx" +Keywords=dj;music;alsa;jack:realtime;standalone; Terminal=false Icon=mixxx Type=Application diff --git a/res/linux/org.mixxx.Mixxx.metainfo.xml b/res/linux/org.mixxx.Mixxx.metainfo.xml index 41b9047e8b6..bfb094c13d3 100644 --- a/res/linux/org.mixxx.Mixxx.metainfo.xml +++ b/res/linux/org.mixxx.Mixxx.metainfo.xml @@ -96,1079 +96,1421 @@ Do not edit it manually. --> - +

- Waveforms + Engine

  • - Simplify waveform combobox in preferences - #13220 - #6428 - #13226 + fix: sync rate using the current BPM instead of the file one + #13671 + #12738
  • -
-

- STEM support -

-
  • - Add simple support for STEM files - #13044 + fix: prevent null CO access when cloning sampler or preview + #13740
  • - Multithreaded Rubberband - #13143 + Tooltips: fix cue mode setting location + #14045

- Controller Backend + Preferences

  • - Add screen renderer to support controllers with a screen - #11407 - #13334 + (fix) Sound preferences: don't set m_settingsModified in update slots + #13450
  • -
-

- Auto-DJ -

-
  • - Add AutoDJ xfader recenter option (default off) - #13303 - #11571 + Track Search Preferences: Fix accidental use of wrong preference controls + #13592 +
  • +
  • + (fix) Pref Mixer: fix crossader graph + #13848 +
  • +
  • + Make extended controller information available for device selection + #13896

- Misc Refactorings + Skins

  • - Refactor/shrink modernize scopedtimer - #13258 + LegacySkinParser: Short-circuit if template fails to open + #13488 +
  • +
  • + Update waveforms_container.xml + #13501
-
-
- -

- Waveforms + Library

  • - SlipMode waveform visual for RGB GLSL - #13002 - #13256 + feat: static color coding for key column + #13390
  • - Show beats and time until next marker in the waveform - #12994 - #13311 + fix: Key text is elided from left, should be right + #13475
  • - Waveforms: don't elide hotcue labels - #13219 - #10722 + Add Key Color Palettes + #13497
  • - Waveforms: Allshader RGB, Filtered and Stacked Waveforms using textures for waveform data - #13151 - #12641 + Fix BPM and Bitrate columns were wider than normal + #13571
  • - Allow changing the waveform overview type without reloading the skin - #13273 + Track Info dialogs: move metadata buttons below color picker + #13632 +
  • +
  • + CmdlineArgs: Add + --rescan-library + for rescanning on startup + #13661 +
  • +
  • + Track menu, purge: allow to hide further success popups in the current session + #13807

- Skins / Interface + Effects

  • - Toggle the menubar with single Alt key press (auto hide) - #11526 - #13301 + Compressor effect: Adjust Makeup Time constant calculation + #13261 + #13237
  • - Fullscreen toggle rework - #11566 + fix: prevent quickFX model out of bound + #13668
  • +
+

+ Waveforms +

+
  • - Allow to edit track title and artist directly within the decks via a delayed double-click - #11755 + Simplify waveform combobox in preferences + #13220 + #6428 + #13226
  • - Require a minimum movement before initiating the drag&drop of tracks - #12903 + Disable textured waveforms when using OpenGL ES + #13381 + #13380
  • - Add type toggle to cue popup - #13215 + ControllerRenderingEngine: Patch out unavailable APIs when using GL ES + #13382
  • - add WEffectMetaKnob, draws arc from default meta position - #12638 - #12634 + fix: invalid slip render marker + #13422
  • - Handle not supported files when dragging to waveforms and spinnies - #13206 + Add minute markers on horizontal waveform overview + #13401 + #5843 + #13648
  • - Improve - rate_up/down - tooltips, pitch vs. speed - #12590 + Fix high details waveforms wrapping around after visual index 65K + #13491
  • - Add tooltip for expand/collapse samplers button - #13005 - #12998 + Fix: support for new WaveformData struct in shaders + #13474 + #13472
  • - LateNight: Merge vinyl control toggle and status light - #12947 - #10192 + Overview waveform: draw minute markers on top of played overlay + #13489
  • - Track label widgets: set - show_track_menu - only for main decks - #12978 + fix: remove scaleSignal in waveform analyzer + #13416
  • - MacOS: App proxy icon of the playing track to the window title - #12116 + feat: improve screen rendering framework + #13737
  • - PreviewDeckN,LoadSelectedTrackAndPlay toggles play/pause if the track is already loaded - #12920 - #9819 + fix: prevent double free on DigitsRenderer + #13859
  • - Add command line option - --start-autodj - to start Auto DJ immediately after Mixxx start. - #13017 - #10189 + fix: waveform overview seeking + #13947 + #13946
  • - Logging: Include timestamps in messages by default - #11861 + rendergraph: add rendergraph library + #14007
  • - LateNight, Deere, Tango: Deactivate beatgrid edit controls if BPM is locked - #13320 - #13323 - #13325 + mark rendering improvements + #13969

- Engine + STEM support

  • - Beats: allow undoing the last BPM/beats change - #12954 - #12774 - #10138 + Add simple support for STEM files + #13044
  • - Add beatloop anchor to set and adjust loop from either start or end - #12745 - #13241 + Multithreaded Rubberband + #13143 + #13649
  • - Add Rate Tap button - #12104 + Add support for stem in the engine + #13070
  • - Store/restore regular loop when toggling rolling loops - #12475 - #8947 + Add analyser support for stem + #13106
  • - Add - beats_translate_move - ControlEncoder - #12376 + Add stem controls + #13086
  • - Looping/Beatjump: use seconds if track has no beats - #12961 - #11124 + Add quick effect support on stem + #13123
  • - Add Track colour palette cycling controls - track_color_next - and - track_color_prev - to library, decks and samplers - #13066 - #12905 + Add stem files to the taglib lookup table + #13612
  • - Add Tempo locking controls - #13041 - #13041 - #13038 - #13199 + fix: exclude stem samples for QML waveform + #13655
  • -
-

- Effects -

-
  • - Add Compressor effect - #12523 + Non-floating stem controls for LateNight + #13537
  • - add Glitch effect - #11329 + (fix) make "stem_group,mute" a powerwindow button + #13751 + #13749
  • - Add backend for Audio Unit (AU) plugins on macOS - #12112 + feat: add advanced stem loading COs + #13268
  • - Effect Meta knob: Draw arc from default meta position - #12638 - #12634 + Fix build with -DSTEM=OFF + #13948
  • - Show newly added effects, read/write HiddenEffects - #13326 - #11343 + Stem control test fix + #13960 +
  • +
  • + Solves problem with special characters in path to stems + #13784

- Library + Controller Backend

  • - Shortkeys Cut, Copy, Paste for track list management - #12020 -
  • -
  • - Track menu: Rephrase "Reset" to "Clear" - #12955 -
  • -
  • - Playlists: move tracks with Alt + Up/Down/PageUp/PageDown/Home/End - #13092 - #10826 - #13098 -
  • -
  • - Search: Add special BPM filters - #12072 - #8191 + Add screen renderer to support controllers with a screen + #11407 + #13334
  • - Search: Add "OR" search operator - #12061 - #8881 + Deprecate + lodash.mixxx.js + , and + script.deepMerge + #13460
  • - Search: add 'type' filter - #13338 + Don't return in JogWheelBasic on deck absent in option + #13425
  • - Search related Tracks menu: Allow to use multiple filters at once - #12213 - #12211 + Refactor: modernize softtakeover code + #13553
  • - Add multi-track property editor / batch tag editor - #12548 - #9023 - #13299 + document + ScriptConnection + readonly properties & slight cleanup + #13630
  • - Computer feature: add sidebar action "Refresh directory tree" - #12908 + Modernize Hid/Bulk Lists + #13622
  • - Library: Custom color for missing tracks - #12895 + Prevent deadlock with BULK transfer and reduce log noise + #13735
  • - Library: Add feedback to directory operations (add, remove, relink) - #12436 - #10481 + feat: add file and color controller setting types + #13669
  • - Library: Add support for scaling BPM by different ratios - #12934 - #9133 + Controllers: allow to enable MIDI Through Port in non-developer sessions + #13909
  • - Library: Add ability to import external playlists as crates - #11852 + Expose convertCharset convenience function to controllers + #13935
  • +
+

+ Auto-DJ +

+
  • - Library: Add 'Shuffle playlist' sidebar action - #12498 - #6988 + Add AutoDJ xfader recenter option (default off) + #13303 + #11571
  • - Playlists: Update of playlist labels after adding tracks - #12866 - #12761 + Auto DJ: Add context menu action for enabling/disabling the Auto DJ + #13593
  • - Tracks: Custom text color for played tracks (qss) - #12744 - #5911 - #12912 + Auto DJ Cross fader center + #13628
  • +
+

+ Experimental Features +

+
  • - History: Show track count and duration in sidebar - #12811 - #12788 + SoundManagerIOS: Remove unsupported/redundant options + #13487
  • - Fixes around cratetablemodel, remove tracks + don't allow pasting tracks into locked playlists/crates or History - #12926 + ControllerRenderingEngine: Disable BGRA when targeting Wasm + #13502
  • - Track menu, Remove from disk: stop and eject all affected decks - #13214 + BaseTrackTableModel: Disable inline track editing on iOS + #13494
  • - Track menu: add star rating - #12700 - #10652 + set QQuickStyle to "basic" + #13696 + #13600
  • - Track menu: Show Properties in Missing and Hidden view - #13426 + fix: trigger QML waveform slot at init + #13736
  • +
+

+ Target support +

+
  • - Library control: make use of WLibrary::getCurrentTrackTableView() - #13335 + DlgPrefSound: Add missing ifdefs for building without Rubberband + #13577
  • - Optimize Library scrolling in BPMDelegate::paintItem - #13358 + Update Linux-GitHub runner to Ubuntu 24.04.01 LTS + #13781 + #13880
  • - Library: fix font reset in multiline comment editor - #13448 + Add missing qt6-declarative-private-dev and qt6-base-private-dev package + #13904

- Preferences + Misc Refactorings

  • - Add missing spacer in Interface preferences - #13094 + Refactor/shrink modernize scopedtimer + #13258
  • - Fix fetching of soundcard sample rate - #11951 - 11949 + Improve use of parented_ptr + #13411
  • - Add load point option 'First hotcue' - #12869 - #12740 + Pre-allocate memory in basetrackcache to avoid multiple reallocations + #13368
  • - MIDI Input editor: allow selecting multiple Options - #12348 + Bump actions/checkout from 4.1.6 to 4.1.7 + #13386
  • - Fix incorrect reboot required notification on preference updates - #13058 + Bump actions/checkout from 4.1.7 to 4.2.0 + #13713
  • - Apply changes only after pressing Apply in color preferences - #13302 + Bump actions/checkout from 4.2.0 to 4.2.1 + #13726 +
  • +
  • + Bump actions/checkout from 4.2.1 to 4.2.2 + #13810 +
  • +
  • + Bump azure/trusted-signing-action from 0.3.20 to 0.4.0 + #13500 +
  • +
  • + Bump azure/trusted-signing-action from 0.4.0 to 0.5.0 + #13809 +
  • +
  • + Bump actions/upload-artifact from 4.3.4 to 4.3.5 + #13539 +
  • +
  • + Bump actions/upload-artifact from 4.3.5 to 4.3.6 + #13562 +
  • +
  • + Bump actions/upload-artifact from 4.3.6 to 4.4.0 + #13621 +
  • +
  • + Bump actions/upload-artifact from 4.4.0 to 4.4.1 + #13725 +
  • +
  • + Bump actions/upload-artifact from 4.4.1 to 4.4.3 + #13765 +
  • +
  • + Bump coverallsapp/github-action from 2.3.0 to 2.3.1 + #13766 +
  • +
  • + Bump coverallsapp/github-action from 2.3.1 to 2.3.3 + #13793 +
  • +
  • + Bump coverallsapp/github-action from 2.3.3 to 2.3.4 + #13811 +
  • +
  • + chore: update the donate button label + #13353 +
  • +
  • + WPixmapStore: Change getPixmapNoCache to std::unique_ptr and further optimizations + #13369 +
  • +
  • + Removed unused setSVG and hash functionality from pixmapsource + #13423 +
  • +
  • + remove FAQ from Readme.md + #13453 +
  • +
  • + #13452 +
  • +
  • + Paintable cleanup + #13435 +
  • +
  • + Made Paintable::DrawMode an enum class + #13424 +
  • +
  • + hash clean up + #13458 +
  • +
  • + clang-format: Indent Objective-C blocks with 4 spaces + #13503 +
  • +
  • + fix(basetracktablemodel): Fix + -Wimplicit-fallthrough + warning on GCC 14.1.1 + #13505 +
  • +
  • + Refactor fix trivial cpp coreguideline violations + #13552 +
  • +
  • + Refactor + EngineMixer + #13568 +
  • +
  • + more + ControlDoublePrivate + optimization + #13581 +
  • +
  • + Modernize + ControlValueAtomic + #13574 +
  • +
  • + Optimize control code + #13354 +
  • +
  • + Fix some minor code issue + #13586 +
  • +
  • + Static initialization order fix + #13594 +
  • +
  • + Remove referenceholder + #13240 +
  • +
  • + chore: add note about ConfigKey naming convention + #13658 +
  • +
  • + refactor: split out + AutoFileReloader + from + QmlAutoReload + #13607 + #13756 + #13755 +
  • +
  • + Fix Clazy v1.12 errors in main + #13770 +
  • +
  • + Code cleanup in SidebarModel and WLibrarySidebar + #13816 +
  • +
  • + Refactor: + MovingInterquartileMean + #13730 +
  • +
  • + Improved comments in enginecontrol and use of std::size_t for bufferSize across the codebase + #13819 +
  • +
  • + refactor: use higher-level + std::span + based logic + #13654 +
  • +
  • + tsan fix pll vars data race + #13873 +
  • +
  • + use atomic to fix tsan detected data race condition of blink value in control indicator + #13875 +
  • +
  • + Fix undefined behaviour of infinity() + #13884 +
  • +
  • + use atomic for m_bWakeScheduler, protect m_bQuit with mutex + #13898 +
  • +
  • + Refactor + ValueTransformer + and + WBaseWidget + #13853 +
  • +
  • + avoid data race on m_pStream + #13899 +
  • +
  • + Cleanup and deprecate more + util/ + classes + #13687 + #13968 + #13965 + #14107 + #14095 + #14087 + #14086 +
  • +
  • + ci(pre-commit): Add cmake-lint hook + #13932 +
  • +
  • + refactor: remove samplew_autogen.h + #13988 + #14005 +
  • +
  • + fix clang-tidy complain + #14029 +
  • +
  • + ci(dependabot): Open PRs against 2.5 branch instead of main + #14060 +
  • +
  • + Happy New Year 2025! + #14098
+
+
+ +

Controller Mappings

  • - Numark Scratch: Add controller settings - #13404 + Numark NS6II: Add new controller mapping + #11075
  • - Pioneer DDJ-FLX4: Mapping improvements - #12842 + Hercules Inpulse 300: Updated mapping + #14051
  • +
+

+ Fixes +

+
  • - Traktor S4 MK3: Add setting definition for - #12995 + Deere (64 samplers): Bring back library in regular view + #14101 + #14097
  • - Traktor S4 MK3: Software mixer support and default pad layout customisation - #13059 + Enable R3 time-stretching with Rubberband 4.0.0 API version numbers + #14100
+
+
+ +

- Controller Backend + Modernized Platform: Update to Qt6

  • - Send sysex to all handlers - #12827 -
  • -
  • - Add control for showing a deck's track menu - #10825 + Mixxx is now using Qt6, offering improved performance and enhanced compatibility with modern systems. + #11863 + #11892
  • - Removed old examples HID keyboard and HID trackpad - #12977 + Build system defaults to Qt6. Qt5 build support will be dropped with Mixxx 2.6 + #11934
  • - Reduce log noise with HID device - #13010 - #13125 + Drop support for macOS versions earlier than 11
  • - Allow controller mapping to discard polling - #12558 + Drop support for Windows versions earlier than Windows 10 build 1809
  • - Add support for mapping user settings - #11300 - #13046 - #13057 - #13045 + Drop support for Ubuntu versions earlier than 22.04
  • - Registering MIDI Input Handlers From Javascript - #12781 - #13089 + Require a C++20 compiler
  • - Controller IO table: Fix display text for Action/control delegate - #13188 + Support GCC 14 + #13504 + #13467
  • - Drop lodash dependency in ComponentJS - #12779 + DlgAbout: Add Qt version to the dialog + #11862
  • - Support for bulk devices on Windows and Mac - #13008 + WWidget: Disable touch events on macOS (fixing trackpad issues on Qt 6) + #11870
  • - Registering MIDI Input Handlers From Javascript - #12781 - #13089 + Various Skin adjustments + #11970 + #11957 + #12050 + #12939 + #13242 + #14014 + #13535 + #14013 + #13959 + #14034 + #12972 + #14035
  • - Drop lodash dependency in ComponentJS - #12779 + Various Library adjustments + #12380 + #12478 + #13035 + #13033 + #12488 + #12216 + #13448

- Experimental QML Skin + Engine

  • - Add Experimental QML Skin that can be tested via the --qml command line option - #13152 + Beats: allow undoing the last BPM/beats change + #12954 + #12774 + #10138 + #13339
  • - Fix type error in - Slider.qml - #11423 + Add beatloop anchor to set and adjust loop from either start or end + #12745 + #13241
  • - Allow switching between legacy and new QML UI with command arg - #12139 + Add Rate Tap button + #12104
  • - Add PlayerProxy missing current track when created after loading - #12559 + Store/restore regular loop when toggling rolling loops + #12475 + #8947
  • - Fix: Add - qt6-qpa-plugins - to dependencies - #12549 + Add + beats_translate_move + ControlEncoder + #12376
  • - Fix: Improve knobs by applying selective 4xMSAA on the Arc shape - #12541 + Looping/Beatjump: use seconds if track has no beats + #12961 + #11124
  • - Add QML interceptor to auto reload on file change - #12795 - #12844 + Add Track colour palette cycling controls + track_color_next + and + track_color_prev + to library, decks and samplers + #13066 + #12905
  • - Add multi-sampling settings for QML - #12546 - #12794 - #12536 - #13058 + Add Tempo locking controls + #13041 + #13041 + #13038 + #13199
  • - Install qml module on Windows - #12604 + Recording: Fix bogus timestamp in CUE sheet after restarting a recording + #13966 + #13964
  • - Add scrolling waveforms - #3967 - #13009 + Improve Taglib/SoundSource logging + #13541
  • +
+

+ Skins / Interface +

+
  • - Fix: handle case where Waveform data is missing - #13009 + Toggle the menubar with single Alt key press (auto hide) + #11526 + #13301
  • - Fix: allow missing COs on QML component - #13011 + Fullscreen toggle rework + #11566 + #13189 + #13030
  • - Initialize CmdlineArgs::m_qml - #13152 + Allow to edit track title and artist directly within the decks via a delayed double-click + #11755 + #13930
  • -
-

- Update to Qt6 -

-
  • - Qt6 prepare - #11863 + Require a minimum movement before initiating the drag&drop of tracks + #12903
  • - Qt6 switch - #11892 + Add type toggle to cue popup + #13215
  • - CMakeLists: Default - QT6 - to - ON - #11934 + Handle not supported files when dragging to waveforms and spinnies + #13206
  • - Build with Qt6 and optionally with QML - #11608 + Tooltips: Improve + rate_up/down + description regarding pitch vs. speed + #12590
  • - Use constInsert() template - #11847 + Tooltips: Add description for expand/collapse samplers buttons + #13005 + #12998
  • - DlgAbout: Add Qt version to the dialog - #11862 + Track label widgets: Set + show_track_menu + only for main decks + #12978
  • - CMakeLists: Fix - QT_TRANSLATION_FILE - path for Qt6 - #11880 + MacOS: App proxy icon of the playing track to the window title + #12116
  • - LibraryControl: Enable control inputs for Qt6 - #11877 + Auto DJ: Force-show decks 3/4 if we are going to use them + #13455
  • - Fix wrong Windows buildenv name and missing Qt6 switch for non CI builds - #11895 + Auto DJ: Add new random tracks if one track does not exists + #13551
  • - WWidget: Disable touch events on macOS (fixing trackpad issues on Qt 6) - #11870 + Allow to set LaunchImage style per color scheme + #13731
  • - Install libjpeg-turbo::jpeg to fix cover display with Qt6 - #11922 + Show wait cursor when re/loading a skin (not during startup) + #13747
  • - Skins: Remove - border: 0px - from sidebar item styling - #11970 - #11957 + LateNight: Merge vinyl control toggle and status light + #12947 + #10192
  • - Skins: Fix checkbox styling on Qt 6 - #12050 + LateNight, Deere, Tango: Deactivate beatgrid edit controls if BPM is locked + #13320 + #13323 + #13325
  • - Skins: Fix Tango waveform splitter - #12939 + LateNight: Add/tweak CueDelete icons + #13495 + #13492
  • - Skin: Fix Tango rate range label position - #13242 + LateNight: Use Classic launch image style also for 64 samplers version + #13796
  • - Introduce wrapper for non const iterators for erase and insert - #12201 + Adjust some skin controls, to allow point-and-click mapping + #13906
  • - Fix Qt6/QML build - #12255 + PreviewDeckN,LoadSelectedTrackAndPlay toggles play/pause if the track is already loaded + #12920 + #9819
  • - Fix track color background with Qt6 - #12380 + Command line interface: Determine whether to color output based on + TERM + variable + #13486
  • - multi-line delegate: fix bg color, Qt6 on Linux - #12478 + Command line interface: Add option + --start-autodj + to start Auto DJ immediately after Mixxx start. + #13017 + #10189
  • - Revert "BaseTrackPlayer: Remove references to WaveformWidgetRenderer when using Qt6" - #12342 + Logging: Include timestamps in messages by default + #11861
  • - Fix: Replace deprecated - qAsConst - with - std::as_const - #13028 + Logging: Limit mixxx.log size to 100 MB or via --log-max-file-size + #13684 + #13660
  • - Fix Drag'n'drop: avoid unintended drag on hover (WTrackProperty, WCoverArt etc.) - #13035 - #13033 + Fix skin reload after changing color scheme + #13847
  • +
+

+ Effects +

+
  • - Fix ambiguous overload error due to native qDebug impl for std::optional - #12981 + Add Compressor effect + #12523
  • - Workaround for Qt6 'selected click' bug - #12488 + add Glitch effect + #11329
  • - Fix menu icon position - #12216 + Add backend for Audio Unit (AU) plugins on macOS + #12112 + #13938 + #13887 +
  • +
  • + Effect Meta knob: Draw arc from default meta position + #12638 + #12634 +
  • +
  • + Show newly added effects, read/write HiddenEffects + #13326 + #11343

- Experimental iOs support + Library

  • - CMakeLists: Support building for iOS - #12672 + Shortkeys Cut, Copy, Paste for track list management + #12020 + #13361 + #13364 + #13958 + #13100
  • - DlgPrefInterface: Disable tooltips on iOS by default - #12689 + Playlists: move tracks with Alt + Up/Down/PageUp/PageDown/Home/End + #13092 + #10826 + #13098
  • - SoundManager: Set up - AVAudioSession - on iOS - #12714 + Search: Add special BPM filters + #12072 + #8191
  • - SoundManager: Use correct PortAudio backend on iOS - #12716 + Search: Add "OR" search operator + #12061 + #8881
  • - DesktopHelper: Add openUrl abstraction to support iOS - #12698 + Search: Add 'type' filter + #13338
  • - iOS packaging: Add Info.plist, launch screen and app icon - #12676 + Search: Add 'id' filter + #13694
  • - CmdlineArgs: Move config directory to a user-accessible location on iOS - #12688 + Search related Tracks menu: Allow to use multiple filters at once + #12213 + #12211
  • - CMakeLists: Work around Qt shader bug with Xcode - #13379 - #13378 + Track menu: Rephrase "Reset" to "Clear" + #12955
  • - AudioUnitManager: Disable unavailable in-process instantiation on iOS - #13383 + Track menu: Add support for scaling BPM by different ratios + #12934 + #9133
  • -
-

- Experimental WebAssembly support -

-
  • - CMakeLists: Add support for targeting Emscripten/WebAssembly - #12918 + Track menu: Remove from disk: stop and eject all affected decks + #13214
  • - CMakeLists: Emit better errors for exotic target platforms - #12910 + Track menu: add star rating + #12700 + #10652
  • - Build: Add - PORTMIDI - flag for compiling with(out) PortMidi - #12913 + Track menu: Show Properties in Missing and Hidden view + #13426
  • - DesktopHelper: Compile out process-spawning on WASM too - #12916 + Add multi-track property editor / batch tag editor + #12548 + #9023 + #13299 + #13609 + #13597 + #13631
  • - MixxxApplication: Use - QWasmIntegrationPlugin - when targeting WebAssembly - #12915 + Track property editor: focus the editing field in the track properties that corresponds to the focused column + #13841 + #14036
  • - CMakeLists: Enable asyncify when targeting WASM - #12921 + Computer feature: add sidebar action "Refresh directory tree" + #12908
  • - Resources: Bundle resources for preloading when targeting Emscripten/WASM - #12922 + Add feedback to directory operations (add, remove, relink) + #12436 + #10481
  • - CMakeLists: Add - WASM_ASSERTIONS - option - #12931 + Add ability to import external playlists as crates + #11852
  • - VersionStore: Recognize Emscripten/WebAssembly - #12940 + Add 'Shuffle playlist' sidebar action + #12498 + #6988
  • - OpenGLWindow: Fix sizing on Wasm by setting - Qt::FramelessWindowHint - #12945 + Playlists: Update of playlist labels after adding tracks + #12866 + #12761
  • - CMakeLists: Require WebGL 2.0 when building for Wasm - #12952 + Tracks: Custom color for missing tracks + #12895
  • - ScreenSaverHelper: Add no-op implementation for WASM - #12930 + Tracks: Custom text color for played tracks (qss) + #12744 + #5911 + #12912 + #13538
  • - SSE: Check - !defined(__EMSCRIPTEN__) - where intrinsics are unavailable on WASM - #12917 + History: Show track count and duration in sidebar + #12811 + #12788 +
  • +
  • + Don't allow pasting tracks into locked playlists/crates or History + #12926 +
  • +
  • + Optimize Library scrolling + #13358 +
  • +
  • + Keep the metadata key text unchanged, use it as the origin of information + #11096 + #11095 + #13650 + #14011 + #14008 + #14020 +
  • +
  • + Center date values, right-align Track # + #13674 +
  • +
  • + Analysis: Fix stop button when analyzing crate/playlist + #13902 +
  • +
  • + Add a debug message, which appears when event loop processing in Mixxx application takes very long + #12094 + #13900 + #13889 + #13903 + #14012 +
  • +
+

+ Preferences +

+
    +
  • + Add load point option 'First hotcue' + #12869 + #12740 +
  • +
  • + MIDI Input editor: allow selecting multiple Options + #12348 +
  • +
  • + Apply changes only after pressing Apply in color preferences + #13302 +
  • +
  • + Add/reorder tabstops in Library and Waveform preferences + #13846 +
  • +
  • + Add missing spacer in interface preferences + #13094 +
  • +
  • + Fix fetching of soundcard sample rate + #11951 + 11949

- Target support + Controller Mappings

  • - Lenient taglib 2.0 guard - #12793 -
  • -
  • - Tools: Add - rpm_buildenv.sh - for building on Fedora - #13069 -
  • -
  • - README: Recommend running buildenvs over sourcing them on Linux - #13071 + Denon MC7000: Add optional jog wheel acceleration to the controller mapping + #4684
  • - FindSndFile: Link mpg123 in static builds - #13087 + Denon MC7000: Unify parameter button logic and add customizable modes + #13589
  • - macOS packaging: Enable app sandbox in ad-hoc-packaged (i.e. non-notarized) bundles too - #12101 + Denon MC7000: Add sampler options to mapping settings + #13950
  • - Drop support for macOS versions earlier than 11 + MIDI for light: Implement new Active deck heuristic + #13513
  • - Drop support for Windows versions earlier than Windows 10 build 1809 + MIDI for light: Add settings GUI + #13721
  • - Drop support for Ubuntu versions earlier than 22.04 + Numark Scratch: Add controller settings + #13404
  • - Require a C++20 compiler + Pioneer DDJ-FLX4: Mapping improvements + #12842
  • -
-

- Misc Refactorings -

-
  • - Add missing - <Qt> - include in - defs.h - #11348 + Traktor Kontrol S4 MK3: Add setting definition for + #12995
  • - Engine: Minor refactor to prefer simplified ranged-for-loop - #11234 + Traktor Kontrol S4 MK3: Software mixer support and default pad layout customisation + #13059
  • - Delete unused EngineFilter - #11559 + Traktor Kontrol S4 Mk3: Rework jogwheel speed compute and motorized platter + #13393
  • - AnalyzerWaveform: Fix commented out code - #11561 + Traktor Kontrol S4 Mk3: Revert QuickEffect preset offset + #13997
  • - Remove usage of ControlObject::getControl - #11643 + Traktor Kontrol S4 Mk3: Correct wheel timestamp wrap-around + #14016
  • +
+

+ Controller Backend +

+
  • - Fix unnecessary transfer of the ownership before release which returns the pointer itself - #11726 + Send sysex to all handlers + #12827
  • - Add - ConfigObject::get-/setValue<EnumType> - #11883 + Speed up midi sysex receive + #12843
  • - Github CI: Enable - WARNINGS_FATAL - on macOS, too - #11905 + Add control for showing a deck's track menu + #10825
  • - Refactor timers - #11807 - #11850 + Removed old examples HID keyboard and HID trackpad + #12977
  • - Use - mixxx::audio::ChannelCount - type instead of - int - / - unsigned char - /etc. - #11941 + Reduce log noise with HID device + #13010 + #13125
  • - Refactor util/timer: cleanup includes - #11937 + Allow controller mapping to discard polling + #12558
  • - Use - SampleRate - type consistently - #11904 + Add support for mapping user settings + #11300 + #13046 + #13057 + #13045 + #13656 + #13738 + #13979 + #13990
  • - CMakeLists: Match arbitrary - arm64-osx - triplets - #11933 + Registering MIDI Input Handlers From Javascript + #12781 + #13089
  • - Reduce sample buffer memory usage - #11988 + Controller IO table: Fix display text for Action/control delegate + #13188
  • - Fix clazy issues on - main - #12028 + Drop lodash dependency in ComponentJS + #12779
  • - Tidy and modernize SampleBuffer - #11987 + Support for bulk devices on Windows and Mac + #13008
  • - Refactor parented_ptr: make trivially destructible in release mode, delete move operations - #11981 + Fix pending reference to the old mapping after selecting 'No mapping' + #13907
  • - Labeler: Add more labels to the auto-labeler - #12106 + Fix crash with GoToItem when no app windows has the focus + #13657
  • +
+

+ Waveforms +

+
  • - FindPortMidi: Link ALSA in static builds on Linux - #12292 - #12291 + Visualize slip mode position by splitting waveform (RGB GLSL only) + #13002 + #13256 + #10063
  • - privat generated ui headers - #12060 - #11407 + Show beats and time until next marker in the waveform + #12994 + #13311 + #13953 + #13314
  • - Github CI: workaround runner-image issue - #12233 + Don't elide hotcue labels + #13219 + #10722
  • - FindLibudev: Link hidapi and libusb with libudev in static builds on Linux - #12294 + Allshader RGB, Filtered and Stacked Waveforms using textures for waveform data + #13151 + #12641
  • - FindVorbis: Link ogg in static builds - #12297 + Allow changing the waveform overview type without reloading the skin + #13273
  • - MixxxApplication: Support linking Qt statically on Linux - #12284 + Overview: Update immediately, when the normalize option or global gain changed + #13634
  • - FindSleef: Use OpenMP in static builds - #12295 + Overview: Clear pickup position display when opening cue menu + #13693
  • +
+

+ Experimental Features +

+
  • - Happy New Year 2024! - #12486 + QML Skin: Can be tested via the --qml command line option + #13152 + #12139 + #13152
  • - fix/History: remove obsolete placeholder playlists - #12494 + QML Skin related changes + #11423 + #12559 + #12549 + #12541 + #12795 + #12844 + #12546 + #12794 + #12536 + #13058 + #12604 + #3967 + #13009 + #13009 + #13011 + #13506
  • - Add missing Taglib dependency - #12830 + iOS support: Mixxx can be built for iOS + #12672
  • - fix: typo ;) - #12726 + iOS support related changes + #12689 + #12714 + #12716 + #12698 + #12676 + #12688 + #13379 + #13378 + #13383
  • - refactor: Avoid temporary qlist allocation on midi sysex receive - #12843 + Emscripten/WebAssembly support, to run Mixxx hardware independent in a browser + #12918
  • - Labeler: Add - qml - to labeler config - #12911 + Emscripten/WebAssembly related changes + #12910 + #12913 + #12916 + #12915 + #12921 + #12922 + #12931 + #12940 + #12945 + #12952 + #12930 + #12917
  • +
+

+ Target support +

+
  • - WTrackMenu: Add missing wcoverartlabel.h include - #12924 + Maintain GL ES support + #13485
  • - Fix clazy complaints and naming - #12935 + Tools: Add + rpm_buildenv.sh + for building on Fedora + #13069
  • - src/library: Sort files into sub-directories - #12956 + Lenient taglib 2.0 guard + #12793
  • - CMakeLists: Fix deduplication trap with - --preload-file - #12944 + MixxxApplication: Support linking Qt statically on Linux + #12284
  • - GitHub CI: Add runner that allows cleaning up the download server - #12957 + FindSndFile: Link mpg123 in static builds + #13087
  • - GitHub CI: Skip the manifest update job on forks - #13278 + FindPortMidi: Link ALSA in static builds on Linux + #12292 + #12291
  • - Refactor FFmpeg soundsource to allow other soundsource to inherit it - #13042 + FindLibudev: Link hidapi and libusb with libudev in static builds on Linux + #12294
  • - Code Style: Add branches around single line blocks. - #13097 + FindVorbis: Link ogg in static builds + #12297
  • - Add missing member in copy ctor - #13229 + FindSleef: Use OpenMP in static builds + #12295
  • - Refactor/preferences enums - #12798 + macOS packaging: Enable app sandbox in ad-hoc-packaged (i.e. non-notarized) bundles too + #12101
  • - localDateTimeFromUtc: Make argument a const reference and initialize QDateTime at construction - #13359 + CMakeLists: Match arbitrary + arm64-osx + triplets + #11933
  • - use enum class for waveform overview type - #13370 + Disable warning in lib/apple code + #13522
  • - Update to latest vcpkg dependencies - #11649 - #12512 - #12067 - #12898 - #13155 + GitHub CI: Use retry loop for CPack to work around macOS issue + #13991
  • - GitHub actions updates - #11544 - #11508 - #11487 - #11438 - #11410 - #11560 - #11578 - #11610 - #11631 - #11710 - #11736 - #11920 - #11961 - #12241 - #12394 - #12447 - #12425 - #12421 - #12799 - #12801 - #12800 - #12736 - #12692 - #12694 - #12695 - #12691 - #12693 - #12625 - #12627 - #12626 - #12577 - #13162 - #13163 - #13187 - #13217 - #13246 - #13232 + Github CI: Enable + WARNINGS_FATAL + on macOS, too + #11905
- +

Controller Mappings

    +
  • + Denon MC7000: Fix star up/down logic by only handling button down events + #13588 +
  • +
  • + Intech TEK2: Add initial mapping + #13521 +
  • Korg Kaoss DJ: Update script #12683
  • +
  • + MIDI for light: Fix unsound timer handling + #13117 +
  • Novation Dicer: Remove flanger mapping with quickeffect toggle #13196 #13134
  • +
  • + Novation Launchpad X: Fix detection on macOS + #13691 + #13633 +
  • Numark PartyMix: Fix EQ (script binding) display name #13255 @@ -1178,6 +1520,21 @@ #4834 #13375
  • +
  • + Pioneer DDJ-400 and DDJ-FLX4: Remove tap beat mapping to resolve conflict with toggle quantize and fix shift + play + #13815 + #13813 + #13857 +
  • +
  • + Reloop Beatmix 2/4: Fix eject button and jog LED being lit on track unload + #13601 + #13605 +
  • +
  • + Reloop Mixage MK1, MK2, Controller Edition: Add initial mapping + #12296 +
  • Sony SIXAXIS: Fix mapping #13319 @@ -1220,11 +1577,6 @@ Recording: with empty config, save default split size immediately #13304
  • -
  • - Allow to drop files with supported MIME type regardless off the file extensions - #13209 - #13204 -
  • Add support for Ubuntu Oracular Oriole and remove Lunar Lobster #13348 @@ -1256,6 +1608,138 @@ #11814 #13463
  • +
  • + Engine Prime: Fix build-failure + #13397 +
  • +
  • + Engine Prime: Friendlier error message if export fails + #13524 +
  • +
  • + macOs: Fix Keyboard shortcuts by not catching num key modifier + #13481 + #13305 +
  • +
  • + Skins: fix time display to allow AM/PM + #13430 + #13421 +
  • +
  • + Fix detection last sound if track does not end with silence. + #13545 + #13449 +
  • +
  • + Remove false positive critical warning related to library columns + #13165 + #13164 +
  • +
  • + Fix reading metadata for files with wrong extensions + #13218 + #13205 +
  • +
  • + History: remove purged tracks, auto-remove empty playlists + #13579 + #13578 +
  • +
  • + Synchronize AutoDJ next deck with top track in queue + #12909 + #8956 +
  • +
  • + Playlists: Update play duration and bold state in sidebar when dragging tracks into the playlist table + #13591 + #13590 + #13575 +
  • +
  • + Playlists: Keep correct track selection (# position) when sorting + #13103 +
  • +
  • + Track file export: Various fixes + #13610 +
  • +
  • + Controller engine: Unify/improve logging, expand error dialog's Details box + #13626 +
  • +
  • + Fix quantization in the effect engine (metronome effect) + #13636 + #13733 +
  • +
  • + Musicbrainz: Improved messages + #13672 + #13673 +
  • +
  • + Fix ReplayGain detection in case of short tracks + #13680 + #13676 + #13702 + #13703 +
  • +
  • + Track menu: Avoid crash and UX issues with track nullptr + #13685 +
  • +
  • + Disable Properties shortcut in Computer feature views + #13698 +
  • +
  • + Overview waveform: Add tooltip info about left-click dragging + #13739 +
  • +
  • + Make + hotcue_focus_color_next + / + _prev + COs + ControlPushButton + s to allow direct mappings + #13764 +
  • +
  • + Scaled svg cache to speed up drawing in hidpi mode + #13679 +
  • +
  • + Update to libdjinterop 0.22.1 for Enigine Prime 4.0.1 support + #13790 +
  • +
  • + HID: Avoid repeated error messages from hid_write()/hid_read() in case of errors + #13692 + #13660 +
  • +
  • + Fix unnecessary painting with covers in library + #13715 +
  • +
  • + Fix check for unrelated decks playing when starting Auto DJ + #13762 + #13734 +
  • +
  • + Fix read before m_bufferInt during scratching + #13917 + #13916 +
  • +
  • + Fix waveform EQ High&Mid visualization + #13923 + #13922 +
diff --git a/res/mixxx.qrc b/res/mixxx.qrc index 8011fe39f94..828b89ffdf2 100644 --- a/res/mixxx.qrc +++ b/res/mixxx.qrc @@ -36,6 +36,7 @@ images/heart_icon_dark.svg images/heart_icon_rainbow.svg images/mixxx-icon-logo-symbolic.svg + images/mixxx-icon-logo-christmas.svg images/skin_preview_placeholder.png images/ic_checkmark.svg images/ic_delete.svg @@ -45,16 +46,19 @@ images/preferences/dark/ic_preferences_autodj.svg images/preferences/dark/ic_preferences_bpmdetect.svg images/preferences/dark/ic_preferences_broadcast.svg + images/preferences/dark/ic_preferences_bulk.svg images/preferences/dark/ic_preferences_colors.svg images/preferences/dark/ic_preferences_controllers.svg images/preferences/dark/ic_preferences_crossfader.svg images/preferences/dark/ic_preferences_decks.svg images/preferences/dark/ic_preferences_effects.svg images/preferences/dark/ic_preferences_equalizers.svg + images/preferences/dark/ic_preferences_hid.svg images/preferences/dark/ic_preferences_interface.svg images/preferences/dark/ic_preferences_keydetect.svg images/preferences/dark/ic_preferences_library.svg images/preferences/dark/ic_preferences_lv2.svg + images/preferences/dark/ic_preferences_midi.svg images/preferences/dark/ic_preferences_modplug.svg images/preferences/dark/ic_preferences_recording.svg images/preferences/dark/ic_preferences_replaygain.svg @@ -68,16 +72,19 @@ images/preferences/light/ic_preferences_autodj.svg images/preferences/light/ic_preferences_bpmdetect.svg images/preferences/light/ic_preferences_broadcast.svg + images/preferences/light/ic_preferences_bulk.svg images/preferences/light/ic_preferences_colors.svg images/preferences/light/ic_preferences_controllers.svg images/preferences/light/ic_preferences_crossfader.svg images/preferences/light/ic_preferences_decks.svg images/preferences/light/ic_preferences_effects.svg images/preferences/light/ic_preferences_equalizers.svg + images/preferences/light/ic_preferences_hid.svg images/preferences/light/ic_preferences_interface.svg images/preferences/light/ic_preferences_keydetect.svg images/preferences/light/ic_preferences_library.svg images/preferences/light/ic_preferences_lv2.svg + images/preferences/light/ic_preferences_midi.svg images/preferences/light/ic_preferences_modplug.svg images/preferences/light/ic_preferences_recording.svg images/preferences/light/ic_preferences_replaygain.svg diff --git a/res/qml/EqColumn.qml b/res/qml/EqColumn.qml index cea2c51006c..0404379cbe8 100644 --- a/res/qml/EqColumn.qml +++ b/res/qml/EqColumn.qml @@ -19,7 +19,7 @@ Column { } function stemGroup(group, index) { - return `${group.substr(0, group.length-1)}Stem${index + 1}]` + return `${group.substr(0, group.length-1)}_Stem${index + 1}]` } Row { diff --git a/res/skins/Deere (64 Samplers)/skin.xml b/res/skins/Deere (64 Samplers)/skin.xml index dc80dbaca77..6defc15c8b0 100644 --- a/res/skins/Deere (64 Samplers)/skin.xml +++ b/res/skins/Deere (64 Samplers)/skin.xml @@ -212,7 +212,7 @@ vertical - min,max + min,min