diff --git a/.clang-format b/.clang-format index 3b113a6ad6a..e3a7f137eb0 100644 --- a/.clang-format +++ b/.clang-format @@ -28,6 +28,7 @@ IndentCaseLabels: false DerivePointerAlignment: false SpaceAfterTemplateKeyword: false SpacesBeforeTrailingComments: 1 +PackConstructorInitializers: Never # StatementMacros don't require a trailing semicolon. # Trailing semicolons should be omitted after these macros # when compiling with -Wpedantic to avoid warnings. @@ -41,6 +42,7 @@ StatementMacros: - QT_REQUIRE_VERSION --- Language: ObjC +ObjCBlockIndentWidth: 4 # We exclude Objective-C(++) from the second line-wrapping pass in tools/clang_format.py # since this pass only applies C++ rules and therefore include the ColumnLimit in the # 'main' clang-format config here. 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/.codespellignore b/.codespellignore index 60f9f40700c..dfabf7450fb 100644 --- a/.codespellignore +++ b/.codespellignore @@ -7,3 +7,7 @@ doubleClick sur jus caf +ReflectIn +bufferIn +indexIn +allLocations diff --git a/.codespellignorelines b/.codespellignorelines index 993aef1b457..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_METHOD2(process, void(CSAMPLE* pInOut, const int iBufferSize)); + 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,5 +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/labeler.yml b/.github/labeler.yml index 52e6f3698c5..fed7a060800 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -32,6 +32,17 @@ controller mappings: - any-glob-to-any-file: - res/controllers/** +developer experience: + - changed-files: + - any-glob-to-any-file: + - .github/ISSUE_TEMPLATE/** + - .github/labeler.yml + - tools/** + - .pre-commit-config.yaml + - CODE_OF_CONDUCT.md + - CONTRIBUTING.md + - README.md + analyzer: - changed-files: - any-glob-to-any-file: @@ -128,6 +139,11 @@ soundsource: - any-glob-to-any-file: - src/sources/soundsource* +sync-branches: + - all: + - base-branch: ['\d+\.\d+'] + - head-branch: ["main", '\d+\.\d+'] + ui: - changed-files: - any-glob-to-any-file: diff --git a/.github/workflows/build-checks.yml b/.github/workflows/build-checks.yml index d91ba8863c6..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.1.7 + 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 071ec5a851b..ae0904d8de1 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.1.7 + 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,13 +318,13 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/azure-code-signing-action@v0.3.0 + uses: azure/trusted-signing-action@v0.5.1 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} endpoint: https://weu.codesigning.azure.net/ - code-signing-account-name: mixxx + trusted-signing-account-name: mixxx certificate-profile-name: mixxx files-folder: build files-folder-filter: exe @@ -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,13 +382,13 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/azure-code-signing-action@v0.3.0 + uses: azure/trusted-signing-action@v0.5.1 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} endpoint: https://weu.codesigning.azure.net/ - code-signing-account-name: mixxx + trusted-signing-account-name: mixxx certificate-profile-name: mixxx files-folder: build files-folder-filter: msi @@ -473,7 +484,7 @@ jobs: - name: "Upload GitHub Actions artifacts" if: matrix.artifacts_path != null - uses: actions/upload-artifact@v4.3.3 + uses: actions/upload-artifact@v4.6.1 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.1.7 + uses: actions/checkout@v4.2.2 with: fetch-depth: 0 diff --git a/.github/workflows/git.yaml b/.github/workflows/git.yaml index 7af001d8835..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.1.7 + - 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 2100a816e23..4108268d060 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.1.7 + 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.3.3 + uses: actions/upload-artifact@v4.6.1 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.3.3 + uses: actions/upload-artifact@v4.6.1 with: name: pre-commit.log path: /github/home/.cache/pre-commit/pre-commit.log diff --git a/.github/workflows/softfix.yml b/.github/workflows/softfix.yml new file mode 100644 index 00000000000..2f2dc776831 --- /dev/null +++ b/.github/workflows/softfix.yml @@ -0,0 +1,36 @@ +name: Softfix workflow +on: + issue_comment: + types: [created] + +permissions: + pull-requests: write + contents: write + +jobs: + softfix: + name: Softfix action + if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/softfix') + runs-on: ubuntu-latest + steps: + - name: Check if commenter is maintainer + id: check-maintainer + uses: actions/github-script@v7 + with: + script: | + const response = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: context.payload.comment.user.login + }); + + const isMaintainer = ['admin', 'write'].includes(response.data.permission); + return isMaintainer; + - name: Checkout repository + if: steps.check-maintainer.outputs.result == 'true' + uses: actions/checkout@v4 + - name: Softfix + if: steps.check-maintainer.outputs.result == 'true' + uses: daschuer/softfix@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sync_branches.yml b/.github/workflows/sync_branches.yml new file mode 100644 index 00000000000..6f1137b4ec9 --- /dev/null +++ b/.github/workflows/sync_branches.yml @@ -0,0 +1,78 @@ +name: Sync Branches +on: + push: + branches: + - "2.4" + - "2.5" + workflow_dispatch: + +permissions: {} + +jobs: + sync-branches: + strategy: + matrix: + include: + - from_branch: "2.4" + to_branch: "2.5" + - from_branch: "2.5" + to_branch: "main" + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + SYNC_COMMITTER: github-actions[bot] + steps: + - name: "Check out repository" + uses: actions/checkout@v4.1.7 + with: + persist-credentials: true + + - name: "Configure Git" + run: | + git config --global merge.ours.driver true + git config --global pull.rebase false + + - name: "Check if merge branch already exists" + id: check_sync + run: | + if git fetch origin "${SYNC_BRANCH}"; then + echo "Branch ${SYNC_BRANCH} already exists, checking if the branch was modified..." + COMMITTER="$(git show --pretty=format:"%cn <%ce>" --no-patch "origin/${SYNC_BRANCH}")" + if [ "${COMMITTER}" = "${SYNC_COMMITTER}" ]; then + echo "Branch ${SYNC_BRANCH} was not modified." + else + echo "Branch ${SYNC_BRANCH} was modified, either delete it or update it yourself." + echo "skip_sync=true" >> $GITHUB_OUTPUT + fi + else + echo "Branch ${SYNC_BRANCH} does not exist yet." + fi + env: + SYNC_BRANCH: sync-branch-${{ matrix.from_branch}}-to-${{ matrix.to_branch }} + + - name: "Merge Changes" + if: steps.check_sync.outputs.skip_sync != 'true' + run: | + git checkout -b "${SYNC_BRANCH}" + git fetch origin "${TO_BRANCH}" + git reset --hard "origin/${TO_BRANCH}" + git pull --no-edit origin "${FROM_BRANCH}" + COMMIT_ORIGINAL="$(git show --no-patch --format="%h" "origin/${TO_BRANCH}")" + COMMIT_MERGE="$(git show --no-patch --format="%h" "origin/${TO_BRANCH}")" + if [ "${COMMIT_ORIGINAL}" = "${COMMIT_MERGE}" ]; then + git status + echo "No changes (or merge conflict), skipping push and PR creation." + else + git push --force origin "${SYNC_BRANCH}" + gh pr create -B "${FROM_BRANCH}" -H "${SYNC_BRANCH}" --title "${PULL_REQUEST_TITLE}" --body "${PULL_REQUEST_BODY}" --labels "sync-branches" + fi + env: + FROM_BRANCH: ${{ matrix.from_branch}} + TO_BRANCH: ${{ matrix.to_branch }} + SYNC_BRANCH: sync-branch-${{ matrix.from_branch}}-to-${{ matrix.to_branch }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PULL_REQUEST_TITLE: Merge changes from `${{ matrix.from_branch }}` into `${{ matrix.to_branch }}` + PULL_REQUEST_BODY: | + New content has landed in the `${{ matrix.from_branch }}` branch, so let's merge the changes into `${{ matrix.to_branch }}`. diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml index e42d18e03da..40f100b0b6b 100644 --- a/.markdownlint-cli2.yaml +++ b/.markdownlint-cli2.yaml @@ -8,8 +8,8 @@ config: # The same headline in different nested sections is okay (and necessary for # CHANGELOG.md). - no-duplicate-header: - allow_different_nesting: true + no-duplicate-heading: + siblings_only: true # We use ordered lists to make stuff easier to read in a text editor. ol-prefix: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 852bc3a8b72..3b77b37d609 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.4.0 + rev: v5.0.0 hooks: - id: fix-byte-order-marker exclude: ^.*(\.cbproj|\.groupproj|\.props|\.sln|\.vcxproj|\.vcxproj.filters|UTF-8-BOM.txt)$ @@ -43,15 +43,15 @@ 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+)?$'] - repo: https://github.com/codespell-project/codespell - rev: v2.2.5 + rev: v2.3.0 hooks: - id: codespell args: @@ -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) -* 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) -* Novation Dicer: Remove flanger mapping with quickeffect toggle +* 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) +* 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 @@ -420,9 +754,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 @@ -432,6 +763,77 @@ * Hardware preferences: Fix UX when applying config with missing/busy devices [#13312](https://github.com/mixxxdj/mixxx/pull/13312) * Fix minor 64 bit CPU performance issue [#13355](https://github.com/mixxxdj/mixxx/pull/13355) +* Fix clicks at loop-out when looping into lead-in [#13294](https://github.com/mixxxdj/mixxx/pull/13294) +* 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) @@ -602,7 +1004,7 @@ [#4806](https://github.com/mixxxdj/mixxx/pull/4806) [#11873](https://github.com/mixxxdj/mixxx/pull/11873) [#11872](https://github.com/mixxxdj/mixxx/issues/11872) -* Add support for overriding analyzis settings about variable/constant BPM on a per-track basis [#10931](https://github.com/mixxxdj/mixxx/pull/10931) +* Add support for overriding analysis settings about variable/constant BPM on a per-track basis [#10931](https://github.com/mixxxdj/mixxx/pull/10931) * Add menu for looking up track metadata at Discogs, SoundCloud and LastFM [#4772](https://github.com/mixxxdj/mixxx/pull/4772) [#4836](https://github.com/mixxxdj/mixxx/pull/4836) * Add "Delete Track Files" action, does "Move to Trash" with Qt >= 5.15 [#4560](https://github.com/mixxxdj/mixxx/pull/4560) @@ -1436,7 +1838,7 @@ [#11327](https://github.com/mixxxdj/mixxx/issues/11327) * LateNight: brighter fx parameter buttons [#11397](https://github.com/mixxxdj/mixxx/pull/11397) -* Fix drift in analyzis data after exporting metadata to MP3 files with ID3v1.1 tags +* Fix drift in analysis data after exporting metadata to MP3 files with ID3v1.1 tags [#11168](https://github.com/mixxxdj/mixxx/pull/11168) [#11159](https://github.com/mixxxdj/mixxx/issues/11159) * Fix broadcasting using Opus encoding @@ -1827,7 +2229,7 @@ * Add controller mapping for Denon MC7000 [#2546](https://github.com/mixxxdj/mixxx/pull/2546) * Add controller mapping for Stanton DJC.4 [#2607](https://github.com/mixxxdj/mixxx/pull/2607) * Fix broadcasting via broadcast/recording input [#9959](https://github.com/mixxxdj/mixxx/issues/9959) [#2743](https://github.com/mixxxdj/mixxx/pull/2743) -* Only apply ducking gain in manual ducking mode when talkover is enabed [#7668](https://github.com/mixxxdj/mixxx/issues/7668) [#8995](https://github.com/mixxxdj/mixxx/issues/8995) [#8795](https://github.com/mixxxdj/mixxx/issues/8795) [#2759](https://github.com/mixxxdj/mixxx/pull/2759) +* Only apply ducking gain in manual ducking mode when talkover is enabled [#7668](https://github.com/mixxxdj/mixxx/issues/7668) [#8995](https://github.com/mixxxdj/mixxx/issues/8995) [#8795](https://github.com/mixxxdj/mixxx/issues/8795) [#2759](https://github.com/mixxxdj/mixxx/pull/2759) * Ignore MIDI Clock Messages (0xF8) because they are not usable in Mixxx and inhibited the screensaver [#2786](https://github.com/mixxxdj/mixxx/pull/2786) ## [2.2.3](https://launchpad.net/mixxx/+milestone/2.2.3) (2019-11-24) diff --git a/CHANGELOG.md.backup b/CHANGELOG.md.backup deleted file mode 100644 index 0ed482364ba..00000000000 --- a/CHANGELOG.md.backup +++ /dev/null @@ -1,2069 +0,0 @@ -# Changelog - -## [2.5.0](https://github.com/mixxxdj/mixxx/issues?q=milestone%3A2.5.0) (Unreleased) - -### Features - -* Logging: Include timestamps in messages by default [#11861](https://github.com/mixxxdj/mixxx/pull/11861) -* PreviewDeckN,LoadSelectedTrackAndPlay toggles play/pause if the track is already loaded - [#12920](https://github.com/mixxxdj/mixxx/pull/12920) - [#9819](https://github.com/mixxxdj/mixxx/issues/9819) -* Add command line option `--start-autodj` to start Auto DJ immediately after Mixxx start. - [#13017](https://github.com/mixxxdj/mixxx/pull/13017) - [#10189](https://github.com/mixxxdj/mixxx/issues/10189) - -### Waveforms - -* SlipMode waveform visual for RGB GLSL - [#13002](https://github.com/mixxxdj/mixxx/pull/13002) - [#13256](https://github.com/mixxxdj/mixxx/pull/13256) -* Show beats and time until next marker in the waveform [#12994](https://github.com/mixxxdj/mixxx/pull/12994) -* Waveforms: don't elide hotcue labels - [#13219](https://github.com/mixxxdj/mixxx/pull/13219) - [#10722](https://github.com/mixxxdj/mixxx/issues/10722) -* Waveforms: Allshader RGB, Filtered and Stacked Waveforms using textures for waveform data - [#13151](https://github.com/mixxxdj/mixxx/pull/13151) - [#12641](https://github.com/mixxxdj/mixxx/issues/12641) - -### Skins / Interface - -* Toggle the menubar with single Alt key press (auto hide) - [#11526](https://github.com/mixxxdj/mixxx/pull/11526) - [#13301](https://github.com/mixxxdj/mixxx/pull/13301) -* Fullscreen toggle rework [#11566](https://github.com/mixxxdj/mixxx/pull/11566) -* Allow to edit track title and artist directly within the decks via a delayed double-click - [#11755](https://github.com/mixxxdj/mixxx/pull/11755) -* Require a minimum movement before initiating the drag&drop of tracks [#12903](https://github.com/mixxxdj/mixxx/pull/12903) -* Add type toggle to cue popup [#13215](https://github.com/mixxxdj/mixxx/pull/13215) -* add WEffectMetaKnob, draws arc from default meta position - [#12638](https://github.com/mixxxdj/mixxx/pull/12638) - [#12634](https://github.com/mixxxdj/mixxx/issues/12634) -* Handle not supported files when dragging to waveforms and spinnies - [#13206](https://github.com/mixxxdj/mixxx/issues/13206) -* Improve `rate_up/down` tooltips, pitch vs. speed [#12590](https://github.com/mixxxdj/mixxx/pull/12590) -* Add tooltip for expand/collapse samplers button - [#13005](https://github.com/mixxxdj/mixxx/pull/13005) - [#12998](https://github.com/mixxxdj/mixxx/issues/12998) -* LateNight: Merge vinyl control toggle and status light - [#12947](https://github.com/mixxxdj/mixxx/pull/12947) - [#10192](https://github.com/mixxxdj/mixxx/issues/10192) -* Track label widgets: set `show_track_menu` only for main decks [#12978](https://github.com/mixxxdj/mixxx/pull/12978) -* MacOS: App proxy icon of the playing track to the window title [#12116](https://github.com/mixxxdj/mixxx/pull/12116) - -### Engine - -* Beats: allow undoing the last BPM/beats change [#12954](https://github.com/mixxxdj/mixxx/pull/12954) - [#12774](https://github.com/mixxxdj/mixxx/issues/12774) - [#10138](https://github.com/mixxxdj/mixxx/issues/10138) -* Add beatloop anchor to set and adjust loop from either start or end - [#12745](https://github.com/mixxxdj/mixxx/pull/12745) - [#13241](https://github.com/mixxxdj/mixxx/pull/13241) -* Add Rate Tap button [#12104](https://github.com/mixxxdj/mixxx/pull/12104) -* Store/restore regular loop when toggling rolling loops - [#12475](https://github.com/mixxxdj/mixxx/pull/12475) - [#8947](https://github.com/mixxxdj/mixxx/issues/8947) -* Add `beats_translate_move` ControlEncoder [#12376](https://github.com/mixxxdj/mixxx/pull/12376) -* Looping/Beatjump: use seconds if track has no beats - [#12961](https://github.com/mixxxdj/mixxx/pull/12961) - [#11124](https://github.com/mixxxdj/mixxx/issues/11124) -* Add Track colour palette cycling controls `track_color_next` and `track_color_prev` to library, decks and samplers - [#13066](https://github.com/mixxxdj/mixxx/pull/13066) - [#12905](https://github.com/mixxxdj/mixxx/issues/12905) -* Add Tempo locking controls - [#13041](https://github.com/mixxxdj/mixxx/pull/13041) - [#13041](https://github.com/mixxxdj/mixxx/pull/13041) - [#13038](https://github.com/mixxxdj/mixxx/issues/13038) - [#13199](https://github.com/mixxxdj/mixxx/pull/13199) - -### Effects - -* Add Compressor effect [#12523](https://github.com/mixxxdj/mixxx/pull/12523) -* add Glitch effect [#11329](https://github.com/mixxxdj/mixxx/pull/11329) -* Add backend for Audio Unit (AU) plugins on macOS [#12112](https://github.com/mixxxdj/mixxx/pull/12112) - -### Library - -* Shortkeys Cut, Copy, Paste for track list management [#12020](https://github.com/mixxxdj/mixxx/pull/12020) -* Track menu: Rephrase "Reset" to "Clear" [#12955](https://github.com/mixxxdj/mixxx/pull/12955) -* Playlists: move tracks with Alt + Up/Down/PageUp/PageDown/Home/End - [#13092](https://github.com/mixxxdj/mixxx/pull/13092) - [#10826](https://github.com/mixxxdj/mixxx/issues/10826) -* Search: Add special BPM filters - [#12072](https://github.com/mixxxdj/mixxx/pull/12072) - [#8191](https://github.com/mixxxdj/mixxx/issues/8191) -* Search: Add "OR" search operator - [#12061](https://github.com/mixxxdj/mixxx/pull/12061) - [#8881](https://github.com/mixxxdj/mixxx/issues/8881) -* Search related Tracks menu: Allow to use multiple filters at once - [#12213](https://github.com/mixxxdj/mixxx/pull/12213) - [#12211](https://github.com/mixxxdj/mixxx/issues/12211) - * Add multi-track property editor / batch tag editor - [#12548](https://github.com/mixxxdj/mixxx/pull/12548) - [#9023](https://github.com/mixxxdj/mixxx/issues/9023) - [#13299](https://github.com/mixxxdj/mixxx/pull/13299) -* Computer feature: add sidebar action "Refresh directory tree" [#12908](https://github.com/mixxxdj/mixxx/pull/12908) -* Library: Custom color for missing tracks [#12895](https://github.com/mixxxdj/mixxx/pull/12895) -* Library: Add feedback to directory operations (add, remove, relink) - [#12436](https://github.com/mixxxdj/mixxx/pull/12436) - [#10481](https://github.com/mixxxdj/mixxx/issues/10481) -* Library: Add support for scaling BPM by different ratios - [#12934](https://github.com/mixxxdj/mixxx/pull/12934) - [#9133](https://github.com/mixxxdj/mixxx/issues/9133) -* Library: Add ability to import external playlists as crates [#11852](https://github.com/mixxxdj/mixxx/pull/11852) -* Library: Add 'Shuffle playlist' sidebar action - [#12498](https://github.com/mixxxdj/mixxx/pull/12498) - [#6988](https://github.com/mixxxdj/mixxx/issues/6988) -* Playlists: Update of playlist labels after adding tracks [#12866](https://github.com/mixxxdj/mixxx/pull/12866) [#12761](https://github.com/mixxxdj/mixxx/issues/12761) -* Tracks: Custom text color for played tracks (qss) - [#12744](https://github.com/mixxxdj/mixxx/pull/12744) - [#5911](https://github.com/mixxxdj/mixxx/issues/5911) - [#12912](https://github.com/mixxxdj/mixxx/pull/12912) -* History: Show track count and duration in sidebar [#12811](https://github.com/mixxxdj/mixxx/pull/12811) [#12788](https://github.com/mixxxdj/mixxx/issues/12788) -* Fixes around cratetablemodel, remove tracks + don't allow pasting tracks into locked playlists/crates or History [#12926](https://github.com/mixxxdj/mixxx/pull/12926) -* Track menu, Remove from disk: stop and eject all affected decks [#13214](https://github.com/mixxxdj/mixxx/pull/13214) -* Track menu: add star rating - [#12700](https://github.com/mixxxdj/mixxx/pull/12700) - [#10652](https://github.com/mixxxdj/mixxx/issues/10652) -* Playlists: move tracks with Alt + Up/Down/PageUp/PageDown/Home/End - [#13092](https://github.com/mixxxdj/mixxx/pull/13092) - [#10826](https://github.com/mixxxdj/mixxx/issues/10826) - -### Preferences - -* Add missing spacer in Interface preferences [#13094](https://github.com/mixxxdj/mixxx/pull/13094) -* Fix fetching of soundcard sample rate [#11951](https://github.com/mixxxdj/mixxx/pull/11951) [11949](https://github.com/mixxxdj/mixxx/issues/11949) -* Add load point option 'First hotcue' - [#12869](https://github.com/mixxxdj/mixxx/pull/12869) - [#12740](https://github.com/mixxxdj/mixxx/issues/12740) -* MIDI Input editor: allow selecting multiple Options [#12348](https://github.com/mixxxdj/mixxx/pull/12348) -* Fix incorrect reboot required notification on preference updates [#13058](https://github.com/mixxxdj/mixxx/pull/13058) - -### Controller Mappings - -* Pioneer DDJ-FLX4: Mapping improvements [#12842](https://github.com/mixxxdj/mixxx/pull/12842) -* Traktor S4 MK3: Add setting definition for [#12995](https://github.com/mixxxdj/mixxx/pull/12995) -* Traktor S4 MK3: Software mixer support and default pad layout customisation [#13059](https://github.com/mixxxdj/mixxx/pull/13059) - -### Controller Backend - -* Send sysex to all handlers [#12827](https://github.com/mixxxdj/mixxx/pull/12827) -* Add control for showing a deck's track menu [#10825](https://github.com/mixxxdj/mixxx/pull/10825) -* Removed old examples HID keyboard and HID trackpad [#12977](https://github.com/mixxxdj/mixxx/pull/12977) -* Reduce log noise with HID device - [#13010](https://github.com/mixxxdj/mixxx/pull/13010) - [#13125](https://github.com/mixxxdj/mixxx/pull/13125) -* Allow controller mapping to discard polling [#12558](https://github.com/mixxxdj/mixxx/pull/12558) -* Add support for mapping user settings - [#11300](https://github.com/mixxxdj/mixxx/pull/11300) - [#13046](https://github.com/mixxxdj/mixxx/pull/13046) - [#13057](https://github.com/mixxxdj/mixxx/pull/13057) - [#13045](https://github.com/mixxxdj/mixxx/pull/13045) -* Registering MIDI Input Handlers From Javascript - [#12781](https://github.com/mixxxdj/mixxx/pull/12781) - [#13089](https://github.com/mixxxdj/mixxx/pull/13089) -* Controller IO table: Fix display text for Action/control delegate [#13188](https://github.com/mixxxdj/mixxx/pull/13188) -* Drop lodash dependency in ComponentJS [#12779](https://github.com/mixxxdj/mixxx/pull/12779) -* Support for bulk devices on Windows and Mac [#13008](https://github.com/mixxxdj/mixxx/pull/13008) -* Registering MIDI Input Handlers From Javascript - [#12781](https://github.com/mixxxdj/mixxx/pull/12781) - [#13089](https://github.com/mixxxdj/mixxx/pull/13089) - -### Experimental QML Skin - -* Add Experimental QML Skin that can be tested via the --qml command line option - [#13152](https://github.com/mixxxdj/mixxx/pull/13152) -* Fix type error in `Slider.qml` [#11423](https://github.com/mixxxdj/mixxx/pull/11423) -* Allow switching between legacy and new QML UI with command arg [#12139](https://github.com/mixxxdj/mixxx/pull/12139) -* Add PlayerProxy missing current track when created after loading [#12559](https://github.com/mixxxdj/mixxx/pull/12559) -* Fix: Add `qt6-qpa-plugins` to dependencies [#12549](https://github.com/mixxxdj/mixxx/pull/12549) -* Fix: Improve knobs by applying selective 4xMSAA on the Arc shape [#12541](https://github.com/mixxxdj/mixxx/pull/12541) -* Add QML interceptor to auto reload on file change - [#12795](https://github.com/mixxxdj/mixxx/pull/12795) - [#12844](https://github.com/mixxxdj/mixxx/pull/12844) -* Add multi-sampling settings for QML [#12546](https://github.com/mixxxdj/mixxx/pull/12546) - [#12794](https://github.com/mixxxdj/mixxx/pull/12794) - [#12536](https://github.com/mixxxdj/mixxx/issues/12536) - [#13058](https://github.com/mixxxdj/mixxx/pull/13058) -* Install qml module on Windows [#12604](https://github.com/mixxxdj/mixxx/pull/12604) -* Add scrolling waveforms - [#3967](https://github.com/mixxxdj/mixxx/pull/3967) - [#13009](https://github.com/mixxxdj/mixxx/pull/13009) -* Fix: handle case where Waveform data is missing [#13009](https://github.com/mixxxdj/mixxx/pull/13009) -* Fix: allow missing COs on QML component [#13011](https://github.com/mixxxdj/mixxx/pull/13011) -* Initialize CmdlineArgs::m_qml [#13152](https://github.com/mixxxdj/mixxx/pull/13152) - -### Update to Qt6 - -* Qt6 prepare [#11863](https://github.com/mixxxdj/mixxx/pull/11863) -* Qt6 switch [#11892](https://github.com/mixxxdj/mixxx/pull/11892) -* CMakeLists: Default `QT6` to `ON` [#11934](https://github.com/mixxxdj/mixxx/pull/11934) -* Build with Qt6 and optionally with QML [#11608](https://github.com/mixxxdj/mixxx/pull/11608) -* Use constInsert() template [#11847](https://github.com/mixxxdj/mixxx/pull/11847) -* DlgAbout: Add Qt version to the dialog [#11862](https://github.com/mixxxdj/mixxx/pull/11862) -* CMakeLists: Fix `QT_TRANSLATION_FILE` path for Qt6 [#11880](https://github.com/mixxxdj/mixxx/pull/11880) -* LibraryControl: Enable control inputs for Qt6 [#11877](https://github.com/mixxxdj/mixxx/pull/11877) -* Fix wrong Windows buildenv name and missing Qt6 switch for non CI builds [#11895](https://github.com/mixxxdj/mixxx/pull/11895) -* WWidget: Disable touch events on macOS (fixing trackpad issues on Qt 6) [#11870](https://github.com/mixxxdj/mixxx/pull/11870) -* Install libjpeg-turbo::jpeg to fix cover display with Qt6 [#11922](https://github.com/mixxxdj/mixxx/pull/11922) -* Skins: Remove `border: 0px` from sidebar item styling - [#11970](https://github.com/mixxxdj/mixxx/pull/11970) - [#11957](https://github.com/mixxxdj/mixxx/issues/11957) -* Skins: Fix checkbox styling on Qt 6 [#12050](https://github.com/mixxxdj/mixxx/pull/12050) -* Skins: Fix Tango waveform splitter [#12939](https://github.com/mixxxdj/mixxx/pull/12939) -* Skin: Fix Tango rate range label position [#13242](https://github.com/mixxxdj/mixxx/pull/13242) -* Introduce wrapper for non const iterators for erase and insert [#12201](https://github.com/mixxxdj/mixxx/pull/12201) -* Fix Qt6/QML build [#12255](https://github.com/mixxxdj/mixxx/pull/12255) -* Fix track color background with Qt6 [#12380](https://github.com/mixxxdj/mixxx/pull/12380) -* multi-line delegate: fix bg color, Qt6 on Linux [#12478](https://github.com/mixxxdj/mixxx/pull/12478) -* Revert "BaseTrackPlayer: Remove references to WaveformWidgetRenderer when using Qt6" [#12342](https://github.com/mixxxdj/mixxx/pull/12342) -* Fix: Replace deprecated `qAsConst` with `std::as_const` [#13028](https://github.com/mixxxdj/mixxx/pull/13028) -* Fix Drag'n'drop: avoid unintended drag on hover (WTrackProperty, WCoverArt etc.) - [#13035](https://github.com/mixxxdj/mixxx/pull/13035) - [#13033](https://github.com/mixxxdj/mixxx/issues/13033) -* Fix ambiguous overload error due to native qDebug impl for std::optional - [#12981](https://github.com/mixxxdj/mixxx/issues/12981) -* Fix 'selected click' bug [#12488](https://github.com/mixxxdj/mixxx/pull/12488) - -### Experimental iOs support - -* CMakeLists: Support building for iOS [#12672](https://github.com/mixxxdj/mixxx/pull/12672) -* DlgPrefInterface: Disable tooltips on iOS by default [#12689](https://github.com/mixxxdj/mixxx/pull/12689) -* SoundManager: Set up `AVAudioSession` on iOS [#12714](https://github.com/mixxxdj/mixxx/pull/12714) -* SoundManager: Use correct PortAudio backend on iOS [#12716](https://github.com/mixxxdj/mixxx/pull/12716) -* DesktopHelper: Add openUrl abstraction to support iOS [#12698](https://github.com/mixxxdj/mixxx/pull/12698) -* iOS packaging: Add Info.plist, launch screen and app icon [#12676](https://github.com/mixxxdj/mixxx/pull/12676) -* CmdlineArgs: Move config directory to a user-accessible location on iOS [#12688](https://github.com/mixxxdj/mixxx/pull/12688) - -### Experimental WebAssembly support - -* CMakeLists: Add support for targeting Emscripten/WebAssembly [#12918](https://github.com/mixxxdj/mixxx/pull/12918) -* CMakeLists: Emit better errors for exotic target platforms [#12910](https://github.com/mixxxdj/mixxx/pull/12910) -* Build: Add `PORTMIDI` flag for compiling with(out) PortMidi [#12913](https://github.com/mixxxdj/mixxx/pull/12913) -* DesktopHelper: Compile out process-spawning on WASM too [#12916](https://github.com/mixxxdj/mixxx/pull/12916) -* MixxxApplication: Use `QWasmIntegrationPlugin` when targeting WebAssembly [#12915](https://github.com/mixxxdj/mixxx/pull/12915) -* CMakeLists: Enable asyncify when targeting WASM [#12921](https://github.com/mixxxdj/mixxx/pull/12921) -* Resources: Bundle resources for preloading when targeting Emscripten/WASM [#12922](https://github.com/mixxxdj/mixxx/pull/12922) -* CMakeLists: Add `WASM_ASSERTIONS` option [#12931](https://github.com/mixxxdj/mixxx/pull/12931) -* VersionStore: Recognize Emscripten/WebAssembly [#12940](https://github.com/mixxxdj/mixxx/pull/12940) -* OpenGLWindow: Fix sizing on Wasm by setting `Qt::FramelessWindowHint` [#12945](https://github.com/mixxxdj/mixxx/pull/12945) -* CMakeLists: Require WebGL 2.0 when building for Wasm [#12952](https://github.com/mixxxdj/mixxx/pull/12952) -* ScreenSaverHelper: Add no-op implementation for WASM [#12930](https://github.com/mixxxdj/mixxx/pull/12930) -* SSE: Check `!defined(__EMSCRIPTEN__)` where intrinsics are unavailable on WASM [#12917](https://github.com/mixxxdj/mixxx/pull/12917) - -### Target support - -* Lenient taglib 2.0 guard [#12793](https://github.com/mixxxdj/mixxx/pull/12793) -* Tools: Add `rpm_buildenv.sh` for building on Fedora [#13069](https://github.com/mixxxdj/mixxx/pull/13069) -* README: Recommend running buildenvs over sourcing them on Linux [#13071](https://github.com/mixxxdj/mixxx/pull/13071) -* FindSndFile: Link mpg123 in static builds [#13087](https://github.com/mixxxdj/mixxx/pull/13087) -* macOS packaging: Enable app sandbox in ad-hoc-packaged (i.e. non-notarized) bundles too [#12101](https://github.com/mixxxdj/mixxx/pull/12101) - -### Misc Refactorings - -* Add missing `` 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) -* 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) -* 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) -* 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.1](https://github.com/mixxxdj/mixxx/milestone/41?closed=1) (2024-05-08) - -### Controller Mappings - -* Behringer DDM4000 & BCR2000: Fix exception in JS code [#12969](https://github.com/mixxxdj/mixxx/pull/12969) -* Denon DJ MC6000MK2: Fix mapping of filter knob/button [#13166](https://github.com/mixxxdj/mixxx/pull/13166) -* Denon DJ MC7000: Fix redundant argument and migrate to `hotcue_x_status` - [#13113](https://github.com/mixxxdj/mixxx/pull/13113) - [#13121](https://github.com/mixxxdj/mixxx/pull/13121) -* Hercules Inpulse 200: Configure shift-browser knob to scroll the library (quick) [#12932](https://github.com/mixxxdj/mixxx/pull/12932) -* Nintendo Wii Remote: Fix hid script regarding addOutput [#12973](https://github.com/mixxxdj/mixxx/pull/12973) -* Pioneer CDJ: Fix hid script regarding addOutput [#12973](https://github.com/mixxxdj/mixxx/pull/12973) -* Pioneer DDJ-FLX4: Add waveform zoom and other mapping improvements - [#12896](https://github.com/mixxxdj/mixxx/pull/12896) - [#12842](https://github.com/mixxxdj/mixxx/pull/12842) -* Traktor Kontrol F1: Fixes for hid-parser and related script [#12876](https://github.com/mixxxdj/mixxx/pull/12876) -* Traktor S2 Mk1: fix warnings [#13145](https://github.com/mixxxdj/mixxx/pull/13145) -* Traktor S3: Fix mapping crash on macOS [#12840](https://github.com/mixxxdj/mixxx/pull/12840) -* Controller I/O table: sort action column by display string [#13039](https://github.com/mixxxdj/mixxx/pull/13039) - -### Target Support - -* Fix various minor build issues - [#12853](https://github.com/mixxxdj/mixxx/pull/12853) - [#12847](https://github.com/mixxxdj/mixxx/pull/12847) - [#12822](https://github.com/mixxxdj/mixxx/pull/12822) - [#12892](https://github.com/mixxxdj/mixxx/pull/12892) - [#13079](https://github.com/mixxxdj/mixxx/pull/13079) - [#12989](https://github.com/mixxxdj/mixxx/pull/12989) -* CMakeLists: Always prefer OpenGL framework on macOS - [#13080](https://github.com/mixxxdj/mixxx/pull/13080) -* Use capitalized Mixxx in Windows installer and start menu - [#13178](https://github.com/mixxxdj/mixxx/pull/13178) - -### Skins - -* Deere: make sampler rows persist [#12928](https://github.com/mixxxdj/mixxx/pull/12928) -* Tango: Remove unneeded waveform Singleton [#12938](https://github.com/mixxxdj/mixxx/pull/12938) -* Tango 64: fix Main VU meter -* Prevent possible crash in customs skins using parallel waveforms - [#13043](https://github.com/mixxxdj/mixxx/pull/13043) - [#12580](https://github.com/mixxxdj/mixxx/issues/12580) - [#13136](https://github.com/mixxxdj/mixxx/pull/13136) -* Slider tooltip: consider orientation for up/down shortcut tooltips + add support for WKnobComposed [#13088](https://github.com/mixxxdj/mixxx/pull/13088) -* Tooltips: update 'hotcue' with saved loop features [#12875](https://github.com/mixxxdj/mixxx/pull/12875) -* Animate long press latching of sync button - [#12990](https://github.com/mixxxdj/mixxx/pull/12990) - [#13212](https://github.com/mixxxdj/mixxx/pull/13212) -* Polish fx chain controls [#12805](https://github.com/mixxxdj/mixxx/pull/12805) -* Waveforms: draw loop gradient at the correct position - [#13061](https://github.com/mixxxdj/mixxx/pull/13061) - [#13060](https://github.com/mixxxdj/mixxx/issues/13060) -* Waveform / spinnies: don't take keyboard focus on click - [#13174](https://github.com/mixxxdj/mixxx/pull/13174) - [#13211](https://github.com/mixxxdj/mixxx/pull/13211) - -### Library - -* Sidebar: show track count and duration of History playlists - [#13020](https://github.com/mixxxdj/mixxx/pull/13020) - [#13019](https://github.com/mixxxdj/mixxx/issues/13019) - [#12788](https://github.com/mixxxdj/mixxx/issues/12788) - [#12880](https://github.com/mixxxdj/mixxx/issues/12880) - [#12882](https://github.com/mixxxdj/mixxx/pull/12882) -* Computer feature: update removable devices on Linux [#12893](https://github.com/mixxxdj/mixxx/pull/12893) [#12891](https://github.com/mixxxdj/mixxx/issues/12891) -* Playlists: Prevent removing tracks from locked playlists [#12927](https://github.com/mixxxdj/mixxx/pull/12927) -* History feature: Fix removing deleted tracks after export - [#13016](https://github.com/mixxxdj/mixxx/pull/13016) - [#13000](https://github.com/mixxxdj/mixxx/issues/13000) -* BPM display uses decimal separator of selected locale [#13067](https://github.com/mixxxdj/mixxx/pull/13067) [#13051](https://github.com/mixxxdj/mixxx/issues/13051) -* Fix relink directory when migrate between Linux/macOS and Windows [#12878](https://github.com/mixxxdj/mixxx/pull/12878) -* Allow adding new directories while watched directories are missing - [#12937](https://github.com/mixxxdj/mixxx/pull/12937) - [#10481](https://github.com/mixxxdj/mixxx/issues/10481) -* Require a minimum movement before initiating the drag&drop of tracks - [#13135](https://github.com/mixxxdj/mixxx/pull/13135) - [#12902](https://github.com/mixxxdj/mixxx/issues/12902) - [#12979](https://github.com/mixxxdj/mixxx/pull/12979) -* iTunes/Serato/Traktor/Rhythmbox: Print error if library file could not be opened - [#13012](https://github.com/mixxxdj/mixxx/pull/13012) -* Playlists: improve table update after deleting (purging) track files - [#13127](https://github.com/mixxxdj/mixxx/pull/13127) -* Fix Color column width issue [#12852](https://github.com/mixxxdj/mixxx/pull/12852) -* Tracks: select track row when clicking the preview button (only when starting preview) - [#12791](https://github.com/mixxxdj/mixxx/pull/12791) -* Library track menu: show Hide action also in Playlist & Crates [#11901](https://github.com/mixxxdj/mixxx/pull/11901) -* iTunes: Obtain FileAccess before accessing iTunes XML [#13013](https://github.com/mixxxdj/mixxx/pull/13013) - -### Miscellaneous - -* Remove unnecessary unpolish operation of the style, before polish the new style [#12445](https://github.com/mixxxdj/mixxx/pull/12445) -* Developer Tools: Initially sort controls by group name, ascending [#12884](https://github.com/mixxxdj/mixxx/pull/12884) -* Waveforms: Fix scratching crossing loop boundaries [#13007](https://github.com/mixxxdj/mixxx/pull/13007) -* Prohibit un-replace when deck is playing [#13023](https://github.com/mixxxdj/mixxx/pull/13023) [#12906](https://github.com/mixxxdj/mixxx/issues/12906) -* Track Properties dialog: Prevent wiping metadata when applying twice quickly - [#12965](https://github.com/mixxxdj/mixxx/pull/12965) - [#12963](https://github.com/mixxxdj/mixxx/issues/12963) -* AutoDJ: Fix button state after error message about playing deck 3/4 - [#12976](https://github.com/mixxxdj/mixxx/pull/12976) - [#12975](https://github.com/mixxxdj/mixxx/issues/12975) -* Tagfetcher: Cache fetched covers - [#12301](https://github.com/mixxxdj/mixxx/pull/12301) - [#11084](https://github.com/mixxxdj/mixxx/issues/11084) -* Avoid beats iterator being one off and DEBUG_ASSERT in Beats::iteratorFrom - [#13150](https://github.com/mixxxdj/mixxx/pull/13150) - [#13149](https://github.com/mixxxdj/mixxx/issues/13149) -* Show hint if resource path in CMakeCache.txt does not exist - [#12929](https://github.com/mixxxdj/mixxx/pull/12929) -* Always calculate the auto value for colorful console output [#13153](https://github.com/mixxxdj/mixxx/pull/13153) -* Fix FLAC recording on macOS and Windows - [#10880](https://github.com/mixxxdj/mixxx/issues/10880) - [#13154](https://github.com/mixxxdj/mixxx/pull/13154) -* LV Mix EQ: Fix pops when enabling in effect rack - [#13055](https://github.com/mixxxdj/mixxx/issues/13055) - [#13073](https://github.com/mixxxdj/mixxx/pull/13073) -* Fix hid addOutput - -## [2.4.0](https://github.com/mixxxdj/mixxx/milestone/15?closed=1) (2024-02-16) - -### Music Library: Tracks Table & Track Menu - -* Remember track selection when switching library features, fix initial selection etc. - [#4177](https://github.com/mixxxdj/mixxx/pull/4177) - [#4536](https://github.com/mixxxdj/mixxx/pull/4536) - [#12321](https://github.com/mixxxdj/mixxx/pull/12321) - [#12064](https://github.com/mixxxdj/mixxx/issues/12064) - [#11196](https://github.com/mixxxdj/mixxx/pull/11196) - [#11130](https://github.com/mixxxdj/mixxx/pull/11130) -* Add new library column that shows the last time a track was played - [#3140](https://github.com/mixxxdj/mixxx/pull/3140) - [#3457](https://github.com/mixxxdj/mixxx/pull/3457) - [#3494](https://github.com/mixxxdj/mixxx/pull/3494) - [#3596](https://github.com/mixxxdj/mixxx/pull/3596) - [#3740](https://github.com/mixxxdj/mixxx/pull/3740) -* Add keyboard shortcut Ctrl+Enter to open track properties [#4347](https://github.com/mixxxdj/mixxx/pull/4347) -* Home/End keys jump to first/last row [#4850](https://github.com/mixxxdj/mixxx/pull/4850) -* Wrap selection around at the bottom/top, only if Shift is not pressed - [#11090](https://github.com/mixxxdj/mixxx/pull/11090) - [#11100](https://github.com/mixxxdj/mixxx/pull/11100) - [#12391](https://github.com/mixxxdj/mixxx/pull/12391) -* Allow to hide/remove tracks from the library by pressing the Delete key - [#4330](https://github.com/mixxxdj/mixxx/pull/4330) - [#7176](https://github.com/mixxxdj/mixxx/issues/7176) - [#9793](https://github.com/mixxxdj/mixxx/issues/9793) - [#9837](https://github.com/mixxxdj/mixxx/issues/9837) - [#10537](https://github.com/mixxxdj/mixxx/issues/10537) - [#11239](https://github.com/mixxxdj/mixxx/pull/11239) - [#4577](https://github.com/mixxxdj/mixxx/pull/4577) - [#10577](https://github.com/mixxxdj/mixxx/issues/10577) - [#11171](https://github.com/mixxxdj/mixxx/pull/11171) - [#10761](https://github.com/mixxxdj/mixxx/issues/10761) -* Fix Recording table refresh issues [#4648](https://github.com/mixxxdj/mixxx/pull/4648) -* Show time in addition to the date in the timestamp column - [#4900](https://github.com/mixxxdj/mixxx/pull/4900) - [#10726](https://github.com/mixxxdj/mixxx/issues/10726) - [#11020](https://github.com/mixxxdj/mixxx/pull/11020) -* Show only the date in Date Added / Last Played columns. Move the time of day to tooltips [#3945](https://github.com/mixxxdj/mixxx/pull/3945) -* Right-align BPM, duration & bitrate values [#11634](https://github.com/mixxxdj/mixxx/pull/11634) [#11668](https://github.com/mixxxdj/mixxx/pull/11668) [#11657](https://github.com/mixxxdj/mixxx/issues/11657) -* Remove parenthesis from play counter display [#11357](https://github.com/mixxxdj/mixxx/pull/11357) -* Refocus library, after editing skin controls [#11767](https://github.com/mixxxdj/mixxx/pull/11767) -* Fix performance with large playlists [#11851](https://github.com/mixxxdj/mixxx/pull/11851) [#11724](https://github.com/mixxxdj/mixxx/issues/11724) -* Add multi-line editor delegate for comment column [#11752](https://github.com/mixxxdj/mixxx/pull/11752) -* Keep current item visible when the view shrinks vertically [#11273](https://github.com/mixxxdj/mixxx/pull/11273) -* macOS scrollbar: Make sure last track is shown in library [#11669](https://github.com/mixxxdj/mixxx/pull/11669) [#9495](https://github.com/mixxxdj/mixxx/issues/9495) -* Add action to select loaded track in library [#4740](https://github.com/mixxxdj/mixxx/pull/4740) -* Add menu for Analyze and Reanalyze - [#4806](https://github.com/mixxxdj/mixxx/pull/4806) - [#11873](https://github.com/mixxxdj/mixxx/pull/11873) - [#11872](https://github.com/mixxxdj/mixxx/issues/11872) -* Add support for overriding analyzis settings about variable/constant BPM on a per-track basis [#10931](https://github.com/mixxxdj/mixxx/pull/10931) -* Add menu for looking up track metadata at Discogs, SoundCloud and LastFM [#4772](https://github.com/mixxxdj/mixxx/pull/4772) [#4836](https://github.com/mixxxdj/mixxx/pull/4836) -* Add "Delete Track Files" action, does "Move to Trash" with Qt >= 5.15 - [#4560](https://github.com/mixxxdj/mixxx/pull/4560) - [#4831](https://github.com/mixxxdj/mixxx/pull/4831) - [#10763](https://github.com/mixxxdj/mixxx/issues/10763) - [#11580](https://github.com/mixxxdj/mixxx/pull/11580) - [#11577](https://github.com/mixxxdj/mixxx/issues/11577) - [#11583](https://github.com/mixxxdj/mixxx/pull/11583) - [#3212](https://github.com/mixxxdj/mixxx/pull/3212) - [#11842](https://github.com/mixxxdj/mixxx/pull/11842) -* Allow to clear the comment field - [#4722](https://github.com/mixxxdj/mixxx/pull/4722) - [#10615](https://github.com/mixxxdj/mixxx/issues/10615) -* Allow to reset loops and also via "[ChannelN], loop_remove" control object - [#4802](https://github.com/mixxxdj/mixxx/pull/4802) - [#10748](https://github.com/mixxxdj/mixxx/issues/10748) - [#12392](https://github.com/mixxxdj/mixxx/pull/12392) - [#12521](https://github.com/mixxxdj/mixxx/pull/12521) -* Add 'Update ReplayGain' decks' to track menus [#4031](https://github.com/mixxxdj/mixxx/pull/4031) [#4719](https://github.com/mixxxdj/mixxx/pull/4719) -* Restore "Remove from playlist" in History [#11591](https://github.com/mixxxdj/mixxx/pull/11591) [#10974](https://github.com/mixxxdj/mixxx/issues/10974) -* Enable Lock BPM action if any selected track BPM is unlocked [#12385](https://github.com/mixxxdj/mixxx/pull/12385) -* Order BPM action by factor, show peview (for single track) [#12701](https://github.com/mixxxdj/mixxx/pull/12701) [#10128](https://github.com/mixxxdj/mixxx/issues/10128) -* Provide the same features in all deck track menus [#12214](https://github.com/mixxxdj/mixxx/pull/12214) -* Track table header: Keep menu open after toggling a checkbox [#12218](https://github.com/mixxxdj/mixxx/pull/12218) - -### Music Library: Sidebar & Searchbar - -* Add F2 and Del/Backspace shortcuts for renaming & deleting playlists and crates - [#11172](https://github.com/mixxxdj/mixxx/pull/11172) - [#11235](https://github.com/mixxxdj/mixxx/pull/11235) - [#4697](https://github.com/mixxxdj/mixxx/pull/4697) - [#4700](https://github.com/mixxxdj/mixxx/pull/4700) - [#10294](https://github.com/mixxxdj/mixxx/issues/10294) -* Improve presentation of the History library tree - [#2996](https://github.com/mixxxdj/mixxx/pull/2996) - [#4298](https://github.com/mixxxdj/mixxx/pull/4298) - [#10533](https://github.com/mixxxdj/mixxx/issues/10533) -* History: Fix sidebar context menu actions - [#4384](https://github.com/mixxxdj/mixxx/pull/4384) - [#4297](https://github.com/mixxxdj/mixxx/pull/4297) - [#10529](https://github.com/mixxxdj/mixxx/issues/10529) -* History: Add cleanup options - [#4726](https://github.com/mixxxdj/mixxx/pull/4726) - [#9259](https://github.com/mixxxdj/mixxx/issues/9259) - [#10714](https://github.com/mixxxdj/mixxx/issues/10714) -* History: Fix update of play count after removing tracks - [#12258](https://github.com/mixxxdj/mixxx/pull/12258) - [#12046](https://github.com/mixxxdj/mixxx/issues/12046) - [#12256](https://github.com/mixxxdj/mixxx/issues/12256) -* Improve UX with right-click and selection after add, rename, delete, duplicate etc. - [#11208](https://github.com/mixxxdj/mixxx/pull/11208) - [#4193](https://github.com/mixxxdj/mixxx/pull/4193) - [#10488](https://github.com/mixxxdj/mixxx/issues/10488) - [#11574](https://github.com/mixxxdj/mixxx/pull/11574) - [#11208](https://github.com/mixxxdj/mixxx/pull/11208) - [#11712](https://github.com/mixxxdj/mixxx/pull/11712) -* Map Left Arrow Key to jump to parent node and activates it - [#4253](https://github.com/mixxxdj/mixxx/pull/4253) -* Crates: only store or activate sibling crate if it's valid - [#11770](https://github.com/mixxxdj/mixxx/pull/11770) - [#11769](https://github.com/mixxxdj/mixxx/issues/11769) -* Add recent searches to a drop down menu of the search box - [#3171](https://github.com/mixxxdj/mixxx/pull/3171) - [#3262](https://github.com/mixxxdj/mixxx/pull/3262) - [#4505](https://github.com/mixxxdj/mixxx/pull/4505) -* Save search queries across restarts - [#4458](https://github.com/mixxxdj/mixxx/pull/4458) - [#10517](https://github.com/mixxxdj/mixxx/issues/10517) - [#10561](https://github.com/mixxxdj/mixxx/issues/10561) - [#4571](https://github.com/mixxxdj/mixxx/pull/4571) -* Enable search in Browse & Recording views [#11014](https://github.com/mixxxdj/mixxx/pull/11014) [#11012](https://github.com/mixxxdj/mixxx/issues/11012) [#4382](https://github.com/mixxxdj/mixxx/pull/4382) -* Update Clear button when search is disabled [#4447](https://github.com/mixxxdj/mixxx/pull/4447) -* Fix reset to default of search timeout in preferences [#4504](https://github.com/mixxxdj/mixxx/pull/4504) [#10589](https://github.com/mixxxdj/mixxx/issues/10589) -* Ctrl+F in focused search box selects the entire search string [#4515](https://github.com/mixxxdj/mixxx/pull/4515) -* Improve keypress handling, fix glitch in popup, strip whitespaces [#4658](https://github.com/mixxxdj/mixxx/pull/4658) -* Enter jumps to track table if search query was transmitted [#4844](https://github.com/mixxxdj/mixxx/pull/4844) - Push completion entry to top, to make up/down behave naturally -* Remove ESC shortcut in favour of new `[Library],focused_widget` [#4571](https://github.com/mixxxdj/mixxx/pull/4571) - [#11030](https://github.com/mixxxdj/mixxx/pull/11030) - [#10975](https://github.com/mixxxdj/mixxx/issues/10975) -* Restore previous search term when switching between playlists and crates - [#11129](https://github.com/mixxxdj/mixxx/pull/11129) - [#11015](https://github.com/mixxxdj/mixxx/issues/11015) - [#11477](https://github.com/mixxxdj/mixxx/pull/11477) - [#11476](https://github.com/mixxxdj/mixxx/issues/11476) -* Add options to disable auto-completion and history [#10942](https://github.com/mixxxdj/mixxx/pull/10942) [#10634](https://github.com/mixxxdj/mixxx/issues/10634) -* Require Enter or Right key to search for auto completed strings - [#11207](https://github.com/mixxxdj/mixxx/pull/11207) - [#11289](https://github.com/mixxxdj/mixxx/pull/11289) - [#11287](https://github.com/mixxxdj/mixxx/issues/11287) -* Allow to use := and quotes to find exact matches [#12063](https://github.com/mixxxdj/mixxx/pull/12063) [#10699](https://github.com/mixxxdj/mixxx/issues/10699) - -### Music Library: Backend & Database - -* Add new "[AutoDJ],add_random_track" to make this feature accessible from controllers [#3076](https://github.com/mixxxdj/mixxx/pull/3076) -* Don't store or update metadata of missing tracks in the Mixxx database to prevent inconsistencies with file tags [#3811](https://github.com/mixxxdj/mixxx/pull/3811) -* Update library schema to 37 for synchronizing file modified time with track source on metadata import/export - [#3978](https://github.com/mixxxdj/mixxx/pull/3978) - [#4012](https://github.com/mixxxdj/mixxx/pull/4012) -* Track Metadata: Fix synchronization (import/export) of file tags - [#4628](https://github.com/mixxxdj/mixxx/pull/4628) - [#4631](https://github.com/mixxxdj/mixxx/pull/4631) - [#4847](https://github.com/mixxxdj/mixxx/pull/4847) [#10782](https://bugs.launchpad.net/bugs/1981106) -* Track Metadata: Do not overwrite unchanged multi-valued fields [#12613](https://github.com/mixxxdj/mixxx/pull/12613) [#12587](https://github.com/mixxxdj/mixxx/issues/12587) -* Optionally reset metadata on reimport if file tags are missing, enabled by "[Library] ResetMissingTagMetadataOnImport 1"). [#4873](https://github.com/mixxxdj/mixxx/pull/4873) -* Logging: Suppress expected and harmless schema migration errors [#4248](https://github.com/mixxxdj/mixxx/pull/4248) -* Fix handling of undefined BPM values - [#4062](https://github.com/mixxxdj/mixxx/pull/4062) - [#4063](https://github.com/mixxxdj/mixxx/pull/4063) - [#4100](https://github.com/mixxxdj/mixxx/pull/4100) - [#4154](https://github.com/mixxxdj/mixxx/pull/4154) - [#4165](https://github.com/mixxxdj/mixxx/pull/4165) - [#4168](https://github.com/mixxxdj/mixxx/pull/4168) -* Automatic analyze and optimize database [#4199](https://github.com/mixxxdj/mixxx/pull/4199) -* Re-import and update metadata after files have been modified when loading tracks [#4218](https://github.com/mixxxdj/mixxx/pull/4218) -* Re-enable shortcuts after editing controls - [#4360](https://github.com/mixxxdj/mixxx/pull/4360) - [#10184](https://github.com/mixxxdj/mixxx/issues/10184) - [#10523](https://github.com/mixxxdj/mixxx/issues/10523) -* Allow to remove a track form the disk [#3212](https://github.com/mixxxdj/mixxx/pull/3212) [#4639](https://github.com/mixxxdj/mixxx/pull/4639) -* Fix accasional resetting of played counter in database [#4578](https://github.com/mixxxdj/mixxx/pull/4578) [#10617](https://github.com/mixxxdj/mixxx/issues/10617) -* Experimental: Fix writing of undefined MusicBrainz Recording ID [#4694](https://github.com/mixxxdj/mixxx/pull/4694) -* Traktor library: fix importing track key [#4701](https://github.com/mixxxdj/mixxx/pull/4701) -* Fix exporting m3u files with tracks and special characters by using the URL format [#4752](https://github.com/mixxxdj/mixxx/pull/4752) -* Library Scanner: Sort files before adding them [#10919](https://github.com/mixxxdj/mixxx/pull/10919) -* Library Scanner: Fix track relocation query [#12462](https://github.com/mixxxdj/mixxx/pull/12462) -* MenuBar: Add shortcut for rescanning library [#11136](https://github.com/mixxxdj/mixxx/pull/11136) -* Playlists: simplify import function, add whitespace before the # suffix [#12246](https://github.com/mixxxdj/mixxx/pull/12246) -* Destroy PlayerInfo after EngineRecord is stopped to fix a debug assertion [#12341](https://github.com/mixxxdj/mixxx/pull/12341) [#12242](https://github.com/mixxxdj/mixxx/issues/12242) -* iTunes: Modularize importer and use `iTunesLibrary` on macOS for compatibility with `Music.app` - [#11353](https://github.com/mixxxdj/mixxx/pull/11353) - [#11256](https://github.com/mixxxdj/mixxx/issues/11256) - [#11446](https://github.com/mixxxdj/mixxx/pull/11446) - [#11444](https://github.com/mixxxdj/mixxx/pull/11444) - [#11503](https://github.com/mixxxdj/mixxx/pull/11503) - [#11500](https://github.com/mixxxdj/mixxx/pull/11500) - [#11509](https://github.com/mixxxdj/mixxx/pull/11509) -* iTunes: Fix sporadic crash during unit tests due to a not initialized reference. [#11666](https://github.com/mixxxdj/mixxx/pull/11666) -* iTunes: Permit duplicate playlist names by identifying playlists by id (rather than name) [#11794](https://github.com/mixxxdj/mixxx/pull/11794) -* iTunes: Re-enable test and add `composer`, `playCount`, `lastPlayedAt` and `dateAdded` to model [#11948](https://github.com/mixxxdj/mixxx/pull/11948) -* Fix setting the wrong default cue color [#11554](https://github.com/mixxxdj/mixxx/pull/11554) [#11260](https://github.com/mixxxdj/mixxx/issues/11260) -* Ensure that tracks with an invalid BPM are re-analyzed [#2776](https://github.com/mixxxdj/mixxx/pull/2776) -* Add support for exporting crates, playlists and the library to Engine Prime and Denon standalone controllers - [#2753](https://github.com/mixxxdj/mixxx/pull/2753) - [#2932](https://github.com/mixxxdj/mixxx/pull/2932) - [#3102](https://github.com/mixxxdj/mixxx/pull/3102) - [#3155](https://github.com/mixxxdj/mixxx/pull/3155) - [#3621](https://github.com/mixxxdj/mixxx/pull/3621) - [#3776](https://github.com/mixxxdj/mixxx/pull/3776) - [#3787](https://github.com/mixxxdj/mixxx/pull/3787) - [#3797](https://github.com/mixxxdj/mixxx/pull/3797) - [#3798](https://github.com/mixxxdj/mixxx/pull/3798) - [#4025](https://github.com/mixxxdj/mixxx/pull/4025) - [#4087](https://github.com/mixxxdj/mixxx/pull/4087) - [#4102](https://github.com/mixxxdj/mixxx/pull/4102) - [#4143](https://github.com/mixxxdj/mixxx/pull/4143) - [#4463](https://github.com/mixxxdj/mixxx/pull/4463) - [#11815](https://github.com/mixxxdj/mixxx/pull/11815) - [#12309](https://github.com/mixxxdj/mixxx/pull/12309) - [#12005](https://github.com/mixxxdj/mixxx/pull/12005) - [#11816](https://github.com/mixxxdj/mixxx/pull/11816) - [#11720](https://github.com/mixxxdj/mixxx/pull/11720) - [#11834](https://github.com/mixxxdj/mixxx/pull/11834) - [#12452](https://github.com/mixxxdj/mixxx/pull/12452) - [#11979](https://github.com/mixxxdj/mixxx/pull/11979) -* Rekordbox: Save all loops and correct AAC timing offset for CoreAudio [#2779](https://github.com/mixxxdj/mixxx/pull/2779) -* Rekordbox: Fix missing playlists due to invalid child ID [#10955](https://github.com/mixxxdj/mixxx/pull/10955) -* Rekordbox: Fix unhandled exception when parsing corrupt PDB files - [#10452](https://github.com/mixxxdj/mixxx/issues/10452) - [#4040](https://github.com/mixxxdj/mixxx/pull/4040) -* Improve log messages during schema migration [#2979](https://github.com/mixxxdj/mixxx/pull/2979) -* Search related tracks in collection - [#3181](https://github.com/mixxxdj/mixxx/pull/3181) - [#3213](https://github.com/mixxxdj/mixxx/pull/3213) - [#2796](https://github.com/mixxxdj/mixxx/pull/2796) - [#4207](https://github.com/mixxxdj/mixxx/pull/4207) - -### Sync - -* Add support for setting an explicit leader for sync lock - [#2768](https://github.com/mixxxdj/mixxx/pull/2768) - [#3099](https://github.com/mixxxdj/mixxx/pull/3099) - [#3695](https://github.com/mixxxdj/mixxx/pull/3695) - [#3734](https://github.com/mixxxdj/mixxx/pull/3734) - [#3698](https://github.com/mixxxdj/mixxx/pull/3698) - [#3864](https://github.com/mixxxdj/mixxx/pull/3864) - [#3867](https://github.com/mixxxdj/mixxx/pull/3867) - [#3921](https://github.com/mixxxdj/mixxx/pull/3921) - [#4119](https://github.com/mixxxdj/mixxx/pull/4119) - [#4135](https://github.com/mixxxdj/mixxx/pull/4135) - [#4149](https://github.com/mixxxdj/mixxx/pull/4149) - [#4276](https://github.com/mixxxdj/mixxx/pull/4276) - [#3944](https://github.com/mixxxdj/mixxx/pull/3944) - [#11828](https://github.com/mixxxdj/mixxx/pull/11828) - [#11831](https://github.com/mixxxdj/mixxx/pull/11831) - [#11829](https://github.com/mixxxdj/mixxx/issues/11829) - [#12431](https://github.com/mixxxdj/mixxx/pull/12431) - [#11788](https://github.com/mixxxdj/mixxx/issues/11788) - [#12234](https://github.com/mixxxdj/mixxx/pull/12234) - [#12499](https://github.com/mixxxdj/mixxx/pull/12499) -* Fix pitch issue with dynamic tracks and sync while cloning tracks - [#12515](https://github.com/mixxxdj/mixxx/pull/12515) -* Fix issue with half/double BPM calculation when using sync - [#3899](https://github.com/mixxxdj/mixxx/pull/3899) - [#3706](https://github.com/mixxxdj/mixxx/pull/3706) -* Sync Lock: Don't seek phase when disabling sync [#4169](https://github.com/mixxxdj/mixxx/pull/4169) -* Sync Lock: Fix issues with single-playing syncables - [#4155](https://github.com/mixxxdj/mixxx/pull/4155) - [#4389](https://github.com/mixxxdj/mixxx/pull/4389) -* Re-sync to leader after scratching [#4005](https://github.com/mixxxdj/mixxx/pull/4005) -* Fix audio artifacts when fading from or to zero [#4363](https://github.com/mixxxdj/mixxx/pull/4363) -* EngineBuffer: Fix assert when new track is loaded during playback with sync [#4682](https://github.com/mixxxdj/mixxx/pull/4682) - -### Audio Codecs - -* Add support for m4v files [#4088](https://github.com/mixxxdj/mixxx/pull/4088) -* Fix recovering from FAAD2 decoding issues [#2850](https://github.com/mixxxdj/mixxx/pull/2850) -* MP3: Log recoverable errors as info instead of warning [#4365](https://github.com/mixxxdj/mixxx/pull/4365) -* MP3: Garbage detection fix [#12464](https://github.com/mixxxdj/mixxx/pull/12464) -* MP3: Improve decoding precision on Windows [#11911](https://github.com/mixxxdj/mixxx/pull/11911) [#11888](https://github.com/mixxxdj/mixxx/issues/11888) -* AAC encoder: Fix a memory leak [#4386](https://github.com/mixxxdj/mixxx/pull/4386) [#4408](https://github.com/mixxxdj/mixxx/pull/4408) -* Improve robustness of file type detection by considering the actual MIME type of the content. [#7970](https://github.com/mixxxdj/mixxx/issues/7970) [#4356](https://github.com/mixxxdj/mixxx/pull/4356) [#4357](https://github.com/mixxxdj/mixxx/pull/4357) -* Fix file type detection when file has wrong file extension by determining the MIME type from content - [#4602](https://github.com/mixxxdj/mixxx/pull/4602) - [#4600](https://github.com/mixxxdj/mixxx/pull/4600) - [#4615](https://github.com/mixxxdj/mixxx/pull/4615) - [#7970](https://github.com/mixxxdj/mixxx/issues/7970) - [#10624](https://github.com/mixxxdj/mixxx/issues/10624) - [#4683](https://github.com/mixxxdj/mixxx/pull/4683) - [#10669](https://github.com/mixxxdj/mixxx/issues/10669) -* Fix type detection of AIFF files [#4364](https://github.com/mixxxdj/mixxx/pull/4364) -* Fix synchronization time stamps of ModPlug files [#4826](https://github.com/mixxxdj/mixxx/pull/4826) [#10758](https://github.com/mixxxdj/mixxx/issues/10758) -* ID3v2 parsing: Improve log warnings [#4610](https://github.com/mixxxdj/mixxx/pull/4610) -* ID3v2 parsing: Fix inconsistent import of comment field [#11249](https://github.com/mixxxdj/mixxx/pull/11249) -* Enable Modpug and Wavpack Support on macOS [#11182](https://github.com/mixxxdj/mixxx/pull/11182) [#11119](https://github.com/mixxxdj/mixxx/issues/11119) -* Fix missing file name in file metadata error message [#11965](https://github.com/mixxxdj/mixxx/pull/11965) [#11964](https://github.com/mixxxdj/mixxx/issues/11964) -* Verify the "first sound" of as an analysis sanity check - [#4773](https://github.com/mixxxdj/mixxx/pull/4773) - [#11887](https://github.com/mixxxdj/mixxx/pull/11887) - [#11946](https://github.com/mixxxdj/mixxx/pull/11946) - [#11940](https://github.com/mixxxdj/mixxx/issues/11940) -* Fix zeros in the first m4a chunk on Linux [#11879](https://github.com/mixxxdj/mixxx/pull/11879) -* Fix overlapping buffers when decoding m4a files using ffmpeg [#11760](https://github.com/mixxxdj/mixxx/pull/11760) [#11545](https://github.com/mixxxdj/mixxx/issues/11545) -* Fix possible crash with opus files with embedded cover arts and require TagLib 1.11 or newer - [#4251](https://github.com/mixxxdj/mixxx/pull/4251) - [#4252](https://github.com/mixxxdj/mixxx/pull/4252) [#10500](https://github.com/mixxxdj/mixxx/issues/10500) - -### Audio Engine - -* Add support for Saved loops - [#2194](https://github.com/mixxxdj/mixxx/pull/2194) - [#3267](https://github.com/mixxxdj/mixxx/pull/3267) - [#3202](https://github.com/mixxxdj/mixxx/pull/3202) - [#4265](https://github.com/mixxxdj/mixxx/pull/4265) - [#7574](https://github.com/mixxxdj/mixxx/issues/7574) - [#11006](https://github.com/mixxxdj/mixxx/pull/11006) - [#11003](https://github.com/mixxxdj/mixxx/issues/11003) - [#12637](https://github.com/mixxxdj/mixxx/pull/12637) - [#12632](https://github.com/mixxxdj/mixxx/pull/12632) - [#12623](https://github.com/mixxxdj/mixxx/pull/12623) - [#12618](https://github.com/mixxxdj/mixxx/issues/12618) -* Fix an issue when pressing multiple cue buttons at the same time [#3382](https://github.com/mixxxdj/mixxx/pull/3382) -* Fix synchronization of main cue point/position - [#4137](https://github.com/mixxxdj/mixxx/pull/4137) - [#10478](https://github.com/mixxxdj/mixxx/issues/10478) - [#4153](https://github.com/mixxxdj/mixxx/pull/4153) -* Adjust ReplayGain: Allow user to update the replaygain value based on a deck pregain value [#4031](https://github.com/mixxxdj/mixxx/pull/4031) -* Add halve/double controls for beatjump size [#4269](https://github.com/mixxxdj/mixxx/pull/4269) -* Implement Un-eject by pressing eject again - [#4668](https://github.com/mixxxdj/mixxx/pull/4668) - [#11246](https://github.com/mixxxdj/mixxx/pull/11246) -* Implement Un-replace by double-clicking eject - [#11246](https://github.com/mixxxdj/mixxx/pull/11246) -* Allow to cancel active loops via beatloop_activate [#4328](https://github.com/mixxxdj/mixxx/pull/4328) [#9950](https://github.com/mixxxdj/mixxx/issues/9950) -* Slip Mode: Preserve active (regular) loop when leaving Slip Mode [#11435](https://github.com/mixxxdj/mixxx/pull/11435) [#6993](https://github.com/mixxxdj/mixxx/issues/6993) -* Fix possible segfault when ejecting track [#4362](https://github.com/mixxxdj/mixxx/pull/4362) [#10497](https://github.com/mixxxdj/mixxx/issues/10497) -* Fix possible crash when ejecting track from a controller [#11884](https://github.com/mixxxdj/mixxx/pull/11884) [#11819](https://github.com/mixxxdj/mixxx/issues/11819) -* Fix an assertion when loop is before track start [#4383](https://github.com/mixxxdj/mixxx/pull/4383) [#10556](https://github.com/mixxxdj/mixxx/issues/10556) -* Fix and improve snapping to beats in various situations [#4366](https://github.com/mixxxdj/mixxx/pull/4366) [#10541](https://github.com/mixxxdj/mixxx/issues/10541) -* Don't wipe inapplicable sound config immediately [#4544](https://github.com/mixxxdj/mixxx/pull/4544) -* Rubberband: Support Version 3 "finer" (near-hi-fi quality) setting, on Windows and MacOs and when available on Linux - [#4853](https://github.com/mixxxdj/mixxx/pull/4853) - [#4855](https://github.com/mixxxdj/mixxx/pull/4855) - [#11047](https://github.com/mixxxdj/mixxx/pull/11047) -* Rubberband: Add missing padding, preventing it from eating the initial transient [#11120](https://github.com/mixxxdj/mixxx/pull/11120) -* Rubberband: Improve mono-compatibility for R3 "finer" [#11418](https://github.com/mixxxdj/mixxx/pull/11418) -* Fix a possible crash when ejecting a track [#11334](https://github.com/mixxxdj/mixxx/pull/11334) [#11257](https://github.com/mixxxdj/mixxx/issues/11257) -* Add a range limits for beatjump_size of 512 [#11248](https://github.com/mixxxdj/mixxx/pull/11248) [#11203](https://github.com/mixxxdj/mixxx/issues/11203) -* Auto DJ: Fix sharp cut transition after cueing a track without a defined intro [#11629](https://github.com/mixxxdj/mixxx/pull/11629) [#11621](https://github.com/mixxxdj/mixxx/issues/11621) -* Auto DJ: Don't use removed Intro end and outro start makers, use transition time instead [#11830](https://github.com/mixxxdj/mixxx/pull/11830) -* Auto DJ: Fix GUI freeze when updating duration for many selected tracks - [#12530](https://github.com/mixxxdj/mixxx/pull/12530) - [#12520](https://github.com/mixxxdj/mixxx/issues/12520) - [#12537](https://github.com/mixxxdj/mixxx/pull/12537) -* KeyControl: fix keylock/unlock bugs, reset pitch_adjust [4710](https://github.com/mixxxdj/mixxx/pull/4710) -* Looping: fix asserts for loop move [#11735](https://github.com/mixxxdj/mixxx/pull/11735) -* Looping: reset loop_end_pos on eject [#12224](https://github.com/mixxxdj/mixxx/pull/12224) [#12223](https://github.com/mixxxdj/mixxx/issues/12223) -* Fix Loop_out not seeking back [#12739](https://github.com/mixxxdj/mixxx/pull/12739) [#12742](https://github.com/mixxxdj/mixxx/pull/12742) -* ReadAheadManager: fix loop wraparound reader condition [#11717](https://github.com/mixxxdj/mixxx/pull/11717) -* Slip mode: consider loop for background position only if it was enabled before slip [#11848](https://github.com/mixxxdj/mixxx/pull/11848) [#11844](https://github.com/mixxxdj/mixxx/issues/11844) -* Make decks' xfader assignment persistent [#12074](https://github.com/mixxxdj/mixxx/pull/12074) [#10122](https://github.com/mixxxdj/mixxx/issues/10122) -* Fix gain issue with cloned tracks [#12435](https://github.com/mixxxdj/mixxx/pull/12435) [#10550](https://github.com/mixxxdj/mixxx/issues/10550) - -### Controller Mappings - -* new: Hercules DJControl MIX controller mapping [#11279](https://github.com/mixxxdj/mixxx/pull/11279) -* new: Pioneer DDJ-FLX4 controller mapping based on DDJ-400 [#11245](https://github.com/mixxxdj/mixxx/pull/11245) -* new: Traktor Kontrol S4 Mk3 controller mapping [#11284](https://github.com/mixxxdj/mixxx/pull/11284) -* new: Traktor Kontrol Z1 HID controller mapping [#12366](https://github.com/mixxxdj/mixxx/pull/12366) [#12426](https://github.com/mixxxdj/mixxx/pull/12426) -* new: Yaeltex MiniMixxx controller mapping [#4350](https://github.com/mixxxdj/mixxx/pull/4350) -* Behringer DDM4000 mixer: Update controller mapping [#4262](https://github.com/mixxxdj/mixxx/pull/4262) [#4799](https://github.com/mixxxdj/mixxx/pull/4799) -* Hercules DJ Console RMX: Replace not defined CO name pitch_reset by pitch_set_default [#12441](https://github.com/mixxxdj/mixxx/pull/12441) -* Korg nanoKONTROL2: Don't try to configure more than 4 main decks [#12322](https://github.com/mixxxdj/mixxx/pull/12322) [#12317](https://github.com/mixxxdj/mixxx/issues/12317) -* Korg nanoKONTROL2: Removed along with Mixco scripts [#2682](https://github.com/mixxxdj/mixxx/pull/2682) -* MAudio Xponent: Removed along with Mixco scripts [#2682](https://github.com/mixxxdj/mixxx/pull/2682) -* MIDI4lights: Give beginTimer callbacks the anonymous function expression form [#12048](https://github.com/mixxxdj/mixxx/pull/12048) -* Novation Twitch: Removed along with Mixco scripts [#2682](https://github.com/mixxxdj/mixxx/pull/2682) -* Novation Launchpad: Update controller scripts [#2600](https://github.com/mixxxdj/mixxx/pull/2600) [#11914](https://github.com/mixxxdj/mixxx/pull/11914) -* Numark DJ2GO2 Touch: Fix sampler, hotcue, beatloop buttons [#4287](https://github.com/mixxxdj/mixxx/pull/4287) [#11595](https://github.com/mixxxdj/mixxx/pull/11595) -* Numark MixTrack Pro 3: Fix beginTimer callback syntax [#12401](https://github.com/mixxxdj/mixxx/pull/12401) [#12369](https://github.com/mixxxdj/mixxx/issues/12369) -* Roland DJ-505: Make blinking lights blink in sync and other improvements [#4159](https://github.com/mixxxdj/mixxx/pull/4159) [#4517](https://github.com/mixxxdj/mixxx/pull/4517) -* Traktor Kontrol S2 MK1: Add calibration and refactor [#11237](https://github.com/mixxxdj/mixxx/pull/11237) -* Traktor Kontrol S2 MK2 fix loaded chain preset CO [#11823](https://github.com/mixxxdj/mixxx/pull/11823) [#10667](https://github.com/mixxxdj/mixxx/issues/10667) -* Traktor Kontrol S2 MK3: Use FX select buttons to set quick effect presets - [#11702](https://github.com/mixxxdj/mixxx/pull/11702) -* Traktor Kontrol S3: script improvements, vanilla-like FX behavior, control initialization, better scratching, and more - [#11199](https://github.com/mixxxdj/mixxx/pull/11199) - [#10645](https://github.com/mixxxdj/mixxx/issues/10645) - [#12409](https://github.com/mixxxdj/mixxx/pull/12409) - [#12510](https://github.com/mixxxdj/mixxx/pull/12510) -* Various mappings: Fix `waveform_zoom` ranges [#12393](https://github.com/mixxxdj/mixxx/pull/12393) -* Various mappings: Ensure required samplers are created [#12769](https://github.com/mixxxdj/mixxx/pull/12769) - -### Controller Backend - -* Never raise a fatal error if a controller mapping tries access a non-existent control object [#2947](https://github.com/mixxxdj/mixxx/pull/2947) -* Add support to access HID FeatureReports - [#11326](https://github.com/mixxxdj/mixxx/pull/11326) - [#10828](https://github.com/mixxxdj/mixxx/issues/10828) - [#11664](https://github.com/mixxxdj/mixxx/pull/11664) -* Add function to request HID InputReports, to determine controller state at startup [#3317](https://github.com/mixxxdj/mixxx/pull/3317) -* Exclude HID device: ELAN touch screen [#11324](https://github.com/mixxxdj/mixxx/pull/11324) [#11323](https://github.com/mixxxdj/mixxx/issues/11323) -* Show otherwise hidden HID devices in developer mode [#11317](https://github.com/mixxxdj/mixxx/pull/11317) -* Use hidapi's hidraw backend instead of libusb on Linux [#4054](https://github.com/mixxxdj/mixxx/pull/4054) -* Fix broken HID controller mappings Traktor Kontrol S2 MK3 and others [#11470](https://github.com/mixxxdj/mixxx/pull/11470) [#11461](https://github.com/mixxxdj/mixxx/issues/11461) -* HID mappings: Modernize and document common-hid-packet-parser.js [#4718](https://github.com/mixxxdj/mixxx/pull/4718) [#4894](https://github.com/mixxxdj/mixxx/pull/4894) -* HID mappings: Small fixes for common-hid-packet-parser.js [#11925](https://github.com/mixxxdj/mixxx/pull/11925) -* HID mappings: Add [Main] to the list of valid groups [#12102](https://github.com/mixxxdj/mixxx/pull/12102) [#12406](https://github.com/mixxxdj/mixxx/pull/12406) -* Consistently use "mapping" instead of "preset" to refer to controller mappings [#3472](https://github.com/mixxxdj/mixxx/pull/3472) -* Introduce new control object `[Library],show_track_menu` to open/close the track menu [#4465](https://github.com/mixxxdj/mixxx/pull/4465) -* Introduce new control object `[Library],sort_focused_column` [#4749](https://github.com/mixxxdj/mixxx/pull/4749) [#4763](https://github.com/mixxxdj/mixxx/pull/4763) [#10719](https://github.com/mixxxdj/mixxx/issues/10719) -* Introduce new control objects `[Master],indicator_250millis` and `[Master],indicator_500millis` [#4157](https://github.com/mixxxdj/mixxx/pull/4157) -* Introduce new control object `[Library],clear_search` [#4331](https://github.com/mixxxdj/mixxx/pull/4331) -* Introduce new control object `[Library],focused_widget` to focus library directly [#4369](https://github.com/mixxxdj/mixxx/pull/4369) [#4490](https://github.com/mixxxdj/mixxx/pull/4490) -* Introduce new control object `LoadTrackFromDeck` and `LoadTrackFromSampler` [#11244](https://github.com/mixxxdj/mixxx/pull/11244) -* Don't automatically enable controller if it was disabled before [#4244](https://github.com/mixxxdj/mixxx/pull/4244) [#10503](https://github.com/mixxxdj/mixxx/issues/10503) -* Enable Qt logging categories for controller logging [#4523](https://github.com/mixxxdj/mixxx/pull/4523) -* Fix segfault during Mixxx shutdown due to a stale controller connection [#4476](https://github.com/mixxxdj/mixxx/pull/4476) [#10553](https://github.com/mixxxdj/mixxx/issues/10553) -* Components JS: Fix syncbutton [#4329](https://github.com/mixxxdj/mixxx/pull/4329) -* Components JS: Add script.posMod for euclidean modulo [#11415](https://github.com/mixxxdj/mixxx/pull/11415) -* Components JS: make JogWheelBasic correctly switch which deck it controls [#11913](https://github.com/mixxxdj/mixxx/pull/11913) [#11867](https://github.com/mixxxdj/mixxx/issues/11867) -* Add Trace for the mapping connections, to allow JS profiling [#4766](https://github.com/mixxxdj/mixxx/pull/4766) -* Controller preferences: Allow creating a new mapping with 'No Mapping' selected - [#4905](https://github.com/mixxxdj/mixxx/pull/4905) - [#10540](https://github.com/mixxxdj/mixxx/issues/10540) - [#10539](https://github.com/mixxxdj/mixxx/issues/10539) -* Add TypeScript declarations for engine and controller scripting API to improve IDE code completion during mapping developent [#4759](https://github.com/mixxxdj/mixxx/pull/4759) -* Retire Mixco Scripts [#2682](https://github.com/mixxxdj/mixxx/pull/2682) -* Relax strictness of `ControllerScriptInterfaceLegacy` methods. [#11474](https://github.com/mixxxdj/mixxx/pull/11474) [#11473](https://github.com/mixxxdj/mixxx/issues/11473) -* Do not show ControlObject aliases in developer tools window [#12265](https://github.com/mixxxdj/mixxx/pull/12265) -* Do not use deprecated COs in C++ code/Keyboard Mapping/Skins [#11990](https://github.com/mixxxdj/mixxx/pull/11990) -* Fix creation of Sampler `end_of_track` ControlObjects [#12305](https://github.com/mixxxdj/mixxx/pull/12305) [#12304](https://github.com/mixxxdj/mixxx/issues/12304) -* Add a test SoftTakeoverTest.CatchOutOfBounds [#12114](https://github.com/mixxxdj/mixxx/pull/12114) [#12011](https://github.com/mixxxdj/mixxx/issues/12011) -* Make WHotcueButton learnable with the MIDI Wizard [#12252](https://github.com/mixxxdj/mixxx/pull/12252) -* Control picker menu: add `waveform_zoom_set_default` [#12247](https://github.com/mixxxdj/mixxx/pull/12247) -* CO Renaming - [#12022](https://github.com/mixxxdj/mixxx/pull/12022) - [#12021](https://github.com/mixxxdj/mixxx/pull/12021) - [#11998](https://github.com/mixxxdj/mixxx/pull/11998) - [#11996](https://github.com/mixxxdj/mixxx/pull/11996) - [#11980](https://github.com/mixxxdj/mixxx/pull/11980) - [#12007](https://github.com/mixxxdj/mixxx/pull/12007) -* Remove deprecated ControlObjects from Skins [#12030](https://github.com/mixxxdj/mixxx/pull/12030) -* Log warning if deprecated control is used [#11972](https://github.com/mixxxdj/mixxx/pull/11972) -* ControlObject alias improvements [#11973](https://github.com/mixxxdj/mixxx/pull/11973) -* Keyboard mapping: Repeat certain control actions if key is held [#12474](https://github.com/mixxxdj/mixxx/pull/12474) -* Keyboard mapping: Return triggers double-click, move Preview functions to P / Shift+P [#12639](https://github.com/mixxxdj/mixxx/pull/12639) -* Keyboard mapping: Various fixes [#12730](https://github.com/mixxxdj/mixxx/pull/12730) -* Update keyboard sheet [#12578](https://github.com/mixxxdj/mixxx/pull/12578) -* Logging: Add support for `QT_MESSAGE_PATTERN` environment variable - [#3204](https://github.com/mixxxdj/mixxx/pull/3204) - [#3518](https://github.com/mixxxdj/mixxx/pull/3518) -* Avoid issue with `stars_up/_down` ControlObjects [#12591](https://github.com/mixxxdj/mixxx/pull/12591) -* hotcue_X_color control: Fix color not stored in cue [#12733](https://github.com/mixxxdj/mixxx/pull/12733) - -### Skins - -* Add harmonic keywheel window - [#1695](https://github.com/mixxxdj/mixxx/pull/1695) - [#3622](https://github.com/mixxxdj/mixxx/pull/3622) - [#3624](https://github.com/mixxxdj/mixxx/pull/3624) -* Allow skin scaling from preferences - [#3960](https://github.com/mixxxdj/mixxx/pull/3960) - [#11588](https://github.com/mixxxdj/mixxx/pull/11588) - [#11586](https://github.com/mixxxdj/mixxx/issues/11586) -* Fix icon rendering on HiDPI/Retina screens [#12407](https://github.com/mixxxdj/mixxx/pull/12407) [#12361](https://github.com/mixxxdj/mixxx/issues/12361) -* Increase pixmapCache size limit and made it dependent on devicePixelRatio (for HiDPI/Retina displays) [#12416](https://github.com/mixxxdj/mixxx/pull/12416) -* Make beat indicator control behaviour more natural [#3608](https://github.com/mixxxdj/mixxx/pull/3608) -* Fix crash if no skin is available - [#3918](https://github.com/mixxxdj/mixxx/pull/3918) - [#3939](https://github.com/mixxxdj/mixxx/pull/3939) -* Fix crash when starting without a valid skin directory [#4575](https://github.com/mixxxdj/mixxx/pull/4575) [#10461](https://github.com/mixxxdj/mixxx/issues/10461) -* Fix leaked controls [#4213](https://github.com/mixxxdj/mixxx/pull/4213) [#10293](https://github.com/mixxxdj/mixxx/issues/10293) -* Fix switching from Shade to other skins [#4421](https://github.com/mixxxdj/mixxx/pull/4421) [#10558](https://github.com/mixxxdj/mixxx/issues/10558) -* Use double click to reset knobs and sliders [#4509](https://github.com/mixxxdj/mixxx/pull/4509) [#9947](https://github.com/mixxxdj/mixxx/issues/9947) -* Use info not warning for skin COs [#4525](https://github.com/mixxxdj/mixxx/pull/4525) -* Spinny: Allow to toggle cover art at runtime [#4565](https://github.com/mixxxdj/mixxx/pull/4565) [#10015](https://github.com/mixxxdj/mixxx/issues/10015) -* Passthrough: improve UI / UX [#4794](https://github.com/mixxxdj/mixxx/pull/4794) -* Knob: Hide cursor on wheel event for .8s [#11077](https://github.com/mixxxdj/mixxx/pull/11077) -* Move skin control hack to c++ (spinny/cover controls, mic/ducking controls) [#11183](https://github.com/mixxxdj/mixxx/pull/11183) -* LateNight: Move logo to the right [#4677](https://github.com/mixxxdj/mixxx/pull/4677) -* LateNight: Use correct tooltip for key control toggle [#4696](https://github.com/mixxxdj/mixxx/pull/4696) -* LateNight: Add toggles to show loop and beatjump controls [#4713](https://github.com/mixxxdj/mixxx/pull/4713) -* LateNight: Remove blinking play indicator from mini samplers [#4807](https://github.com/mixxxdj/mixxx/pull/4807) -* LateNight: Add buffer underflow indicator [#4906](https://github.com/mixxxdj/mixxx/pull/4906) [#10978](https://github.com/mixxxdj/mixxx/pull/10978) -* LateNight: Fix xfader icons in samplers and aux units [#12477](https://github.com/mixxxdj/mixxx/pull/12477) -* LateNight: use default RGB waveform colors [#12712](https://github.com/mixxxdj/mixxx/pull/12712) -* Add LateNight (64 Samplers) [#11715](https://github.com/mixxxdj/mixxx/pull/11715) -* Deere: fix skin/library layout (library missing in default view with Qt6) [#11912](https://github.com/mixxxdj/mixxx/pull/11912) -* Deere: use decks' waveform colors for sliders (Vol + pitch) [#12129](https://github.com/mixxxdj/mixxx/pull/12129) [#10240](https://github.com/mixxxdj/mixxx/issues/10240) -* Shade: Remove initial setting of now accessible effect controls [#4398](https://github.com/mixxxdj/mixxx/pull/4398) [#10557](https://github.com/mixxxdj/mixxx/issues/10557) -* Shade: Audio Latency meter fix [#11601](https://github.com/mixxxdj/mixxx/pull/11601) -* Tango: allow to toggle crossfader independently from mixer [#12703](https://github.com/mixxxdj/mixxx/pull/12703) [#12654](https://github.com/mixxxdj/mixxx/issues/12654) -* Fix outdated tooltips - [#11387](https://github.com/mixxxdj/mixxx/pull/11387) - [#11384](https://github.com/mixxxdj/mixxx/issues/11384) - [#11860](https://github.com/mixxxdj/mixxx/pull/11860) -* Add settings directory link to Help menu [#11670](https://github.com/mixxxdj/mixxx/pull/11670) [#11667](https://github.com/mixxxdj/mixxx/issues/11667) -* Fix sidebar item styling - [#11975](https://github.com/mixxxdj/mixxx/pull/11975) - [#11957](https://github.com/mixxxdj/mixxx/issues/11957) -* Fix 500ms blocking of the whole event loop, when holding mouse down on title bar on Windows [#12359](https://github.com/mixxxdj/mixxx/pull/12359) [#12358](https://github.com/mixxxdj/mixxx/issues/12358) [#12433](https://github.com/mixxxdj/mixxx/pull/12433) [#12458](https://github.com/mixxxdj/mixxx/pull/12458) -* Change SKIN_WARNING to show the skin file and line first, then c++ context [#12253](https://github.com/mixxxdj/mixxx/pull/12253) -* Fix style of selected QComboBox items on Windows [#12339](https://github.com/mixxxdj/mixxx/pull/12339) [#12323](https://github.com/mixxxdj/mixxx/issues/12323) -* Fix reading the Spinny cover on Windows [#12103](https://github.com/mixxxdj/mixxx/pull/12103) [#11131](https://github.com/mixxxdj/mixxx/issues/11131) -* Fix inconsistent/wrong musical keys in the UI [#12051](https://github.com/mixxxdj/mixxx/pull/12051) [#12044](https://github.com/mixxxdj/mixxx/issues/12044) -* Add `skins:` path alias [#12463](https://github.com/mixxxdj/mixxx/pull/12463) -* Remove `Text`, use `TrackProperty` or `Label` [#12004](https://github.com/mixxxdj/mixxx/pull/12004) -* Beat spinBox/AutoDJ spinbox: Enter & Esc also move focus to library [#4617](https://github.com/mixxxdj/mixxx/pull/4617) [#4845](https://github.com/mixxxdj/mixxx/pull/4845) -* Add effect chain menu button to Deere, polish in Tango [#12735](https://github.com/mixxxdj/mixxx/pull/12735) -* Skins: reload default.qss when (re)loading a skin [#12219](https://github.com/mixxxdj/mixxx/pull/12219) - -### Waveforms and GL Widgets - -* Waveform overhaul based on QOpenGlWindow and introduce full GLSL shader based waveforms, vumeters and spinnies. This fixes a couple of performance issues mainly on macOS. - [#10989](https://github.com/mixxxdj/mixxx/pull/10989) - [#10416](https://github.com/mixxxdj/mixxx/issues/10416) - [#11460](https://github.com/mixxxdj/mixxx/issues/11460) - [#11556](https://github.com/mixxxdj/mixxx/issues/11556) - [#11450](https://github.com/mixxxdj/mixxx/issues/11450) - [#10416](https://github.com/mixxxdj/mixxx/issues/10416) - [#11734](https://github.com/mixxxdj/mixxx/issues/11734) - [#12466](https://github.com/mixxxdj/mixxx/pull/12466) - [#12678](https://github.com/mixxxdj/mixxx/pull/12678) - [#12731](https://github.com/mixxxdj/mixxx/pull/12731) -* Default to 60 Hz waveform refresh rate [#11918](https://github.com/mixxxdj/mixxx/pull/11918) -* Introduce a VSsync mode driven by a phase locked loop [#12469](https://github.com/mixxxdj/mixxx/pull/12469) -* Make VSync mode 0 refer to the default mode and make ST_PLL the default on macOS, ST_TIMER otherwise [#12489](https://github.com/mixxxdj/mixxx/pull/12489) -* Use WaveformWidgetType::AllShaderRGBWaveform as autoChooseWidgetType [#11822](https://github.com/mixxxdj/mixxx/pull/11822) -* Add new "RGB Stacked" waveform [#3153](https://github.com/mixxxdj/mixxx/pull/3153) -* Fix micro jitter from clamping position offset to vsync interval [#12470](https://github.com/mixxxdj/mixxx/pull/12470) -* Avoid flickering when resizing [#12487](https://github.com/mixxxdj/mixxx/pull/12487) -* Invert scroll wheel waveform zoom direction to mach other applications [#4195](https://github.com/mixxxdj/mixxx/pull/4195) -* Waveform scrolling: Use set interval setting to fix performance degradation for AMD graphics adapters [#11681](https://github.com/mixxxdj/mixxx/pull/11681) [#11617](https://github.com/mixxxdj/mixxx/issues/11617) -* Fix waveform zooming [#11650](https://github.com/mixxxdj/mixxx/pull/11650) [#11626](https://github.com/mixxxdj/mixxx/issues/11626) -* Fix OpenGL version detection [#11673](https://github.com/mixxxdj/mixxx/pull/11673) -* Fix crash when no GL context is available [#11963](https://github.com/mixxxdj/mixxx/pull/11963) [#11929](https://github.com/mixxxdj/mixxx/issues/11929) -* Fix stopped waveform rendering in case of vinyl control [#11977](https://github.com/mixxxdj/mixxx/pull/11977) [#10764](https://github.com/mixxxdj/mixxx/issues/10764) -* Fix visual play position related to looping - [#11840](https://github.com/mixxxdj/mixxx/pull/11840) - [#11836](https://github.com/mixxxdj/mixxx/issues/11836) - [#12538](https://github.com/mixxxdj/mixxx/pull/12538) - [#12506](https://github.com/mixxxdj/mixxx/issues/12506) - [#12513](https://github.com/mixxxdj/mixxx/issues/12513) -* Fix for visual position while scratching outside of an activated loop [#12281](https://github.com/mixxxdj/mixxx/pull/12281) [#12274](https://github.com/mixxxdj/mixxx/issues/12274) -* Spinny: Fix drawing of non-square cover arts [#11971](https://github.com/mixxxdj/mixxx/pull/11971) [#11967](https://github.com/mixxxdj/mixxx/issues/11967) -* Spinny/VU-Meter: Fix drawing [#12010](https://github.com/mixxxdj/mixxx/pull/12010) [#11930](https://github.com/mixxxdj/mixxx/issues/11930) -* VU-Meter: Don't use OpenGL by default [#11722](https://github.com/mixxxdj/mixxx/pull/11722) -* Improve GLSL pre-roll triangles [#12100](https://github.com/mixxxdj/mixxx/pull/12100) [#12015](https://github.com/mixxxdj/mixxx/issues/12015) -* Make scaling of GLSL RGB and RGB L/R waveform amplitudes consistent with simple waveform [#12205](https://github.com/mixxxdj/mixxx/pull/12205) [#12356](https://github.com/mixxxdj/mixxx/pull/12356) -* Improve rendering of waveform marks [#12203](https://github.com/mixxxdj/mixxx/pull/12203) [#12237](https://github.com/mixxxdj/mixxx/pull/12237) -* avoid overlapping marks [#12273](https://github.com/mixxxdj/mixxx/pull/12273) -* gradually "compact" the markers if the waveform height is reduced [#12501](https://github.com/mixxxdj/mixxx/pull/12501) -* Fix clamping of the index for drawing the waveform left of zero position [#12411](https://github.com/mixxxdj/mixxx/pull/12411) -* Fix possible crash when closing Mixxx [#12314](https://github.com/mixxxdj/mixxx/pull/12314) [#11737](https://github.com/mixxxdj/mixxx/issues/11737) -* Fix EGL support - [#11982](https://github.com/mixxxdj/mixxx/pull/11982) - [#11641](https://github.com/mixxxdj/mixxx/issues/11641) - [#11935](https://github.com/mixxxdj/mixxx/pull/11935) - [#11985](https://github.com/mixxxdj/mixxx/pull/11985) - [#11982](https://github.com/mixxxdj/mixxx/pull/11982) - [#11995](https://github.com/mixxxdj/mixxx/pull/11995) - [#11994](https://github.com/mixxxdj/mixxx/pull/11994) - [#12607](https://github.com/mixxxdj/mixxx/pull/12607) -* Preferences: recall correct waveform type when selecting an overview type - [#12231](https://github.com/mixxxdj/mixxx/pull/12231) - [#12226](https://github.com/mixxxdj/mixxx/issues/12226) - -### Cover Art - -* Prevent wrong cover art display due to hash conflicts [#2524](https://github.com/mixxxdj/mixxx/pull/2524) [#4904](https://github.com/mixxxdj/mixxx/pull/4904) -* Add background color for quick cover art preview [#2524](https://github.com/mixxxdj/mixxx/pull/2524) -* Fix coverart tooltip if cover is not cached [#12087](https://github.com/mixxxdj/mixxx/pull/12087) -* Add cover art fetcher to the Musicbrainz dialog - [#10908](https://github.com/mixxxdj/mixxx/pull/10908) - [#4871](https://github.com/mixxxdj/mixxx/pull/4871) - [#10795](https://github.com/mixxxdj/mixxx/issues/10795) - [#10796](https://github.com/mixxxdj/mixxx/issues/10796) - [#10902](https://github.com/mixxxdj/mixxx/pull/10902) - [#4851](https://github.com/mixxxdj/mixxx/pull/4851) - [#11938](https://github.com/mixxxdj/mixxx/pull/11938) - [#11086](https://github.com/mixxxdj/mixxx/issues/11086) - [#12041](https://github.com/mixxxdj/mixxx/pull/12041) - [#12300](https://github.com/mixxxdj/mixxx/pull/12300) - [#12543](https://github.com/mixxxdj/mixxx/pull/12543) - [#12532](https://github.com/mixxxdj/mixxx/issues/12532) -* CoverArtCache refactoring + Fix scrolling lag after updating Mixxx [#12009](https://github.com/mixxxdj/mixxx/pull/12009) - -### Effects - -* Effect refactoring: Effect chain saving/loading, parameter hiding/rearrangement, effect preferences overhaul - [#4467](https://github.com/mixxxdj/mixxx/pull/4467) - [#4431](https://github.com/mixxxdj/mixxx/pull/4431) - [#4426](https://github.com/mixxxdj/mixxx/pull/4426) - [#4457](https://github.com/mixxxdj/mixxx/pull/4457) - [#4456](https://github.com/mixxxdj/mixxx/pull/4456) - [#4459](https://github.com/mixxxdj/mixxx/pull/4459) - [#4462](https://github.com/mixxxdj/mixxx/pull/4462) - [#4466](https://github.com/mixxxdj/mixxx/pull/4466) - [#4468](https://github.com/mixxxdj/mixxx/pull/4468) - [#4472](https://github.com/mixxxdj/mixxx/pull/4472) - [#4470](https://github.com/mixxxdj/mixxx/pull/4470) - [#4471](https://github.com/mixxxdj/mixxx/pull/4471) - [#4483](https://github.com/mixxxdj/mixxx/pull/4483) - [#4482](https://github.com/mixxxdj/mixxx/pull/4482) - [#4484](https://github.com/mixxxdj/mixxx/pull/4484) - [#4486](https://github.com/mixxxdj/mixxx/pull/4486) - [#4502](https://github.com/mixxxdj/mixxx/pull/4502) - [#4501](https://github.com/mixxxdj/mixxx/pull/4501) - [#4518](https://github.com/mixxxdj/mixxx/pull/4518) - [#4532](https://github.com/mixxxdj/mixxx/pull/4532) - [#4461](https://github.com/mixxxdj/mixxx/pull/4461) - [#4548](https://github.com/mixxxdj/mixxx/pull/4548) - [#4503](https://github.com/mixxxdj/mixxx/pull/4503) - [#4686](https://github.com/mixxxdj/mixxx/pull/4686) - [#4691](https://github.com/mixxxdj/mixxx/pull/4691) - [#4704](https://github.com/mixxxdj/mixxx/pull/4704) - [#4748](https://github.com/mixxxdj/mixxx/pull/4748) - [#4833](https://github.com/mixxxdj/mixxx/pull/4833) - [#10762](https://github.com/mixxxdj/mixxx/issues/10762) - [#4884](https://github.com/mixxxdj/mixxx/pull/4884) - [#10802](https://github.com/mixxxdj/mixxx/issues/10802) - [#10801](https://github.com/mixxxdj/mixxx/issues/10801) - [#4899](https://github.com/mixxxdj/mixxx/pull/4899) - [#8817](https://github.com/mixxxdj/mixxx/pull/8817) - [#10868](https://github.com/mixxxdj/mixxx/pull/10868) - [#11055](https://github.com/mixxxdj/mixxx/pull/11055) - [#11135](https://github.com/mixxxdj/mixxx/pull/11135) - [#11185](https://github.com/mixxxdj/mixxx/pull/11185) - [#11242](https://github.com/mixxxdj/mixxx/pull/11242) - [#10837](https://github.com/mixxxdj/mixxx/pull/10837) - [#10834](https://github.com/mixxxdj/mixxx/issues/10834) - [#11424](https://github.com/mixxxdj/mixxx/pull/11424) - [#11376](https://github.com/mixxxdj/mixxx/pull/11376) - [#11456](https://github.com/mixxxdj/mixxx/pull/11456) - [#11454](https://github.com/mixxxdj/mixxx/issues/11454) - [#11695](https://github.com/mixxxdj/mixxx/pull/11695) - [#12633](https://github.com/mixxxdj/mixxx/pull/12633) - [#12561](https://github.com/mixxxdj/mixxx/pull/12561) - [#10859](https://github.com/mixxxdj/mixxx/pull/10859) - [#10777](https://github.com/mixxxdj/mixxx/issues/10777) - [#11886](https://github.com/mixxxdj/mixxx/pull/11886) - [#12282](https://github.com/mixxxdj/mixxx/pull/12282) - [#12277](https://github.com/mixxxdj/mixxx/issues/12277) - [#11705](https://github.com/mixxxdj/mixxx/pull/11705) - [#4469](https://github.com/mixxxdj/mixxx/pull/4469) - [#11902](https://github.com/mixxxdj/mixxx/pull/11902) - [#10605](https://github.com/mixxxdj/mixxx/issues/10605) - [#4702](https://github.com/mixxxdj/mixxx/pull/4702) - [#10579](https://github.com/mixxxdj/mixxx/issues/10579) - [#4501](https://github.com/mixxxdj/mixxx/pull/4501) - [#4502](https://github.com/mixxxdj/mixxx/pull/4502) - [#4503](https://github.com/mixxxdj/mixxx/pull/4503) - [#4590](https://github.com/mixxxdj/mixxx/pull/4590) - [#4593](https://github.com/mixxxdj/mixxx/pull/4593) - [#11062](https://github.com/mixxxdj/mixxx/pull/11062) -* Add Noise effect [#2921](https://github.com/mixxxdj/mixxx/pull/2921) -* Add Pitch Shift effect - [#4775](https://github.com/mixxxdj/mixxx/pull/4775) - [#7389](https://github.com/mixxxdj/mixxx/issues/7389) - [#4810](https://github.com/mixxxdj/mixxx/pull/4810) - [#4901](https://github.com/mixxxdj/mixxx/pull/4901) - [#10858](https://github.com/mixxxdj/mixxx/pull/10858) - [#12481](https://github.com/mixxxdj/mixxx/pull/12481) -* Add Distortion effect [#10932](https://github.com/mixxxdj/mixxx/pull/10932) -* Effect parameter knobs: Briefly show parameter value in parameter name widget - [#11032](https://github.com/mixxxdj/mixxx/pull/11032) - [#9022](https://github.com/mixxxdj/mixxx/issues/9022) - [#11034](https://github.com/mixxxdj/mixxx/pull/11034) -* Effect parameter knobs: Implement ValueScaler::Integral, snap value to int [#11061](https://github.com/mixxxdj/mixxx/pull/11061) -* Show effect parameter units in parameter name label [#11041](https://github.com/mixxxdj/mixxx/pull/11041) [#11194](https://github.com/mixxxdj/mixxx/pull/11194) -* Fix gain compensation for the Moog filter [#11177](https://github.com/mixxxdj/mixxx/pull/11177) -* Fix memory leak in AutoPan [#11346](https://github.com/mixxxdj/mixxx/pull/11346) -* EngineFilterDelay: clamp wrong delay values [#4869](https://github.com/mixxxdj/mixxx/pull/4869) -* Fix crash when changing effect unit routing [#4707](https://github.com/mixxxdj/mixxx/pull/4707) [#9331](https://github.com/mixxxdj/mixxx/issues/9331) -* Clear effect buffer after ejecting a track [#10692](https://github.com/mixxxdj/mixxx/issues/10692) -* Center Super knob when loading empty (QuickEffect) chain preset [#12320](https://github.com/mixxxdj/mixxx/pull/12320) -* Don't reset "super" and "mix" knob on startup [#11781](https://github.com/mixxxdj/mixxx/pull/11781) [#11773](https://github.com/mixxxdj/mixxx/issues/11773) -* Add a missing early return [#11809](https://github.com/mixxxdj/mixxx/pull/11809) [#111808](https://github.com/mixxxdj/mixxx/issues/11808) -* Update EffectSlot meta default value according to loaded effect [#12480](https://github.com/mixxxdj/mixxx/pull/12480) [#12479](https://github.com/mixxxdj/mixxx/issues/12479) - -### Target Support - -* Added support for macOS ARM builds on M1/M2 Apple silicon [#11398](https://github.com/mixxxdj/mixxx/pull/11398) -* Set app_id to fix Mixxx window icon on Wayland [#12635](https://github.com/mixxxdj/mixxx/pull/12635) -* Require C++20 but keep Ubuntu Focal support - [#4889](https://github.com/mixxxdj/mixxx/pull/4889) - [#4895](https://github.com/mixxxdj/mixxx/pull/4895) - [#11204](https://github.com/mixxxdj/mixxx/pull/11204) - [#4832](https://github.com/mixxxdj/mixxx/pull/4832) - [#4803](https://github.com/mixxxdj/mixxx/pull/4803) - [#11551](https://github.com/mixxxdj/mixxx/issues/11551) - [#11573](https://github.com/mixxxdj/mixxx/pull/11573) -* Drop Ubuntu Bionic support, require Qt 5.12 - [#3687](https://github.com/mixxxdj/mixxx/pull/3687) - [#3735](https://github.com/mixxxdj/mixxx/pull/3735) - [#3736](https://github.com/mixxxdj/mixxx/pull/3736) - [#3985](https://github.com/mixxxdj/mixxx/pull/3985) -* Drop Ubuntu Groovy and Impish support because of EOL - [#4283](https://github.com/mixxxdj/mixxx/pull/4283) - [#4849](https://github.com/mixxxdj/mixxx/pull/4849) - [#12353](https://github.com/mixxxdj/mixxx/pull/12353) -* Support Ubuntu Noble and Jammy - [#4780](https://github.com/mixxxdj/mixxx/pull/4780) - [#4857](https://github.com/mixxxdj/mixxx/pull/4857) - [#12353](https://github.com/mixxxdj/mixxx/pull/12353) -* Add NixOS support - [#2820](https://github.com/mixxxdj/mixxx/pull/2820) - [#2828](https://github.com/mixxxdj/mixxx/pull/2828) - [#2836](https://github.com/mixxxdj/mixxx/pull/2836) - [#2827](https://github.com/mixxxdj/mixxx/pull/2827) - [#2827](https://github.com/mixxxdj/mixxx/pull/2827) - [#2828](https://github.com/mixxxdj/mixxx/pull/2828) - [#3113](https://github.com/mixxxdj/mixxx/pull/3113) - [#3089](https://github.com/mixxxdj/mixxx/pull/3089) - [#3545](https://github.com/mixxxdj/mixxx/pull/3545) -* Windows packaging: Use Azure for signing exe, msi and all dlls with timestamp and sha256 - [#12465](https://github.com/mixxxdj/mixxx/pull/12465) - [#4824](https://github.com/mixxxdj/mixxx/pull/4824) - [#4825](https://github.com/mixxxdj/mixxx/pull/4825) -* macOS packaging: Fix signing and migrate script to `notarytool` - [#12123](https://github.com/mixxxdj/mixxx/pull/12123) - [#12089](https://github.com/mixxxdj/mixxx/issues/12089) - [#12095](https://github.com/mixxxdj/mixxx/pull/12095) -* macOS packaging: Enable app sandbox and fix related issues - [#12138](https://github.com/mixxxdj/mixxx/pull/12138) - [#12457](https://github.com/mixxxdj/mixxx/pull/12457) - [#12137](https://github.com/mixxxdj/mixxx/issues/12137) - [#11552](https://github.com/mixxxdj/mixxx/issues/11552) - [#4018](https://github.com/mixxxdj/mixxx/pull/4018) - [#10373](https://github.com/mixxxdj/mixxx/issues/10373) -* macOS: Use rounded Mixxx Icon to follow Apples style guide - [#4545](https://github.com/mixxxdj/mixxx/pull/4545) - [#10958](https://github.com/mixxxdj/mixxx/pull/10958) -* macOS packaging: Capitalize bundle and executable name (Mixxx.app) - [#12656](https://github.com/mixxxdj/mixxx/pull/12656) -* OpenBSD: Allow building Mixxx [#11083](https://github.com/mixxxdj/mixxx/pull/11083) -* Improve Linux launcher - [#11826](https://github.com/mixxxdj/mixxx/pull/11826) - [#11820](https://github.com/mixxxdj/mixxx/issues/11820) - [#11805](https://github.com/mixxxdj/mixxx/pull/11805) - [#12424](https://github.com/mixxxdj/mixxx/pull/12424) -* Experimental iOS support - [#12665](https://github.com/mixxxdj/mixxx/pull/12665) - [#12666](https://github.com/mixxxdj/mixxx/pull/12666) - [#12662](https://github.com/mixxxdj/mixxx/pull/12662) - [#12663](https://github.com/mixxxdj/mixxx/pull/12663) - [#12661](https://github.com/mixxxdj/mixxx/pull/12661) - [#12650](https://github.com/mixxxdj/mixxx/pull/12650) -* Fail early in case Taglib 2.0 is found [#12709](https://github.com/mixxxdj/mixxx/pull/12709) - -### Track properties - -* Fix a SIGSEGV after a debug assertion [#4316](https://github.com/mixxxdj/mixxx/pull/4316) -* Apply pending changes also when saving via hotkey [#4562](https://github.com/mixxxdj/mixxx/pull/4562) [#10612](https://github.com/mixxxdj/mixxx/issues/10612) -* Fix crash when trying to scale 0.0 BPM [#4587](https://github.com/mixxxdj/mixxx/pull/4587) [#1955853](https://github.com/mixxxdj/mixxx/issues/10625) -* Add track color selector [#11436](https://github.com/mixxxdj/mixxx/pull/11436) [#10324](https://github.com/mixxxdj/mixxx/issues/10324) -* Don't clear unsaved properties when updating star rating [#11565](https://github.com/mixxxdj/mixxx/pull/11565) [#11540](https://github.com/mixxxdj/mixxx/issues/11540) -* Fix glitch in Star rating [#12582](https://github.com/mixxxdj/mixxx/pull/12582) [#12576](https://github.com/mixxxdj/mixxx/issues/12576) -* Focus Double-clicked property field for edit - [#11764](https://github.com/mixxxdj/mixxx/pull/11764) - [#11804](https://github.com/mixxxdj/mixxx/pull/11804) - [#11802](https://github.com/mixxxdj/mixxx/issues/11802) -* Display the samplerate [#12418](https://github.com/mixxxdj/mixxx/pull/12418) - -### Preferences - -* Always show tooltips [#4198](https://github.com/mixxxdj/mixxx/pull/4198) [#9716](https://github.com/mixxxdj/mixxx/issues/9716) -* Add option to keep deck playing on track load [#10944](https://github.com/mixxxdj/mixxx/pull/10944) [#10548](https://github.com/mixxxdj/mixxx/issues/10548) -* Always enable Alt shortcut keys [#11145](https://github.com/mixxxdj/mixxx/pull/11145) [#10413](https://github.com/mixxxdj/mixxx/issues/10413) -* Sound Hardware: auto select free device channels [#11859](https://github.com/mixxxdj/mixxx/pull/11859) [#10163](https://github.com/mixxxdj/mixxx/issues/10163) -* Various layout and UX fixes - [#12429](https://github.com/mixxxdj/mixxx/pull/12429) - [#12399](https://github.com/mixxxdj/mixxx/pull/12399) - [#11663](https://github.com/mixxxdj/mixxx/pull/11663) - [#11926](https://github.com/mixxxdj/mixxx/pull/11926) - [#12057](https://github.com/mixxxdj/mixxx/pull/12057) -* macOS: set preferences dialog title to the selected page title [#11696](https://github.com/mixxxdj/mixxx/pull/11696) -* macOS: fix the preferences menu and opening the settings directory [#11679](https://github.com/mixxxdj/mixxx/pull/11679) -* macOS: fix slider styling in preferences dialog [#11647](https://github.com/mixxxdj/mixxx/pull/11647) -* Vinyl control: Improve quality indicator [#3279](https://github.com/mixxxdj/mixxx/pull/3279) -* Mixer: apply & save settings only in slotApply(), fix bugs, improve UX [#11527](https://github.com/mixxxdj/mixxx/pull/11527) -* Mixer: fix reset of EQ auto-reset checkbox [#11818](https://github.com/mixxxdj/mixxx/pull/11818) [#11817](https://github.com/mixxxdj/mixxx/issues/11817) -* Interface: avoid unneeded skin reload, clean up [#11853](https://github.com/mixxxdj/mixxx/pull/11853) -* Library: Add link to settings files info in the manual [#4367](https://github.com/mixxxdj/mixxx/pull/4367) -* Controllers: add search bars to mapping tables [#11165](https://github.com/mixxxdj/mixxx/pull/11165) -* Add 13 new translation languages [#4785](https://github.com/mixxxdj/mixxx/pull/4785) [#9702](https://github.com/mixxxdj/mixxx/issues/9702) -* Join Franch translations to "fr" and remove all untranslated English strings. [#12699](https://github.com/mixxxdj/mixxx/pull/12699) -* Apply changes from all pages when pressing Apply (like when pressing Okay) [#12194](https://github.com/mixxxdj/mixxx/pull/12194) - -### Known issues - -* Volume / Loudness spikes on Windows with M4A/AAC files. - Last known working version is Windows 10 build 17763. - Affected versions are Windows 10 build 19041 and Windows 11 build 22000. - [#12289](https://github.com/mixxxdj/mixxx/issues/12289) - [#11094](https://github.com/mixxxdj/mixxx/issues/11094) -* macOS: Library entries are now sorted using the language depending Unicode Collation Algorithm (UCA). - [#12517](https://github.com/mixxxdj/mixxx/issues/12517) -* macOS: Visual glitches with the main EQ sliders - [#12517](https://github.com/mixxxdj/mixxx/issues/12630) -* Linux: possible crash when enabling a MIDI controller [#12001](https://github.com/mixxxdj/mixxx/issues/12001) - Introduce with Qt 5.15.5, fixed in Qt 5.15.17 and Qt 6.6.3 -* Extra Samplers are created during startup, when found in a saved Sampler Bank [#12657](https://github.com/mixxxdj/mixxx/pull/12657) [#12809](https://github.com/mixxxdj/mixxx/pull/12809) - -## [2.3.6](https://github.com/mixxxdj/mixxx/milestone/40) (2023-08-15) - -* Fixed possible crash when closing Mixxx while browsing the file system - [#11593](https://github.com/mixxxdj/mixxx/pull/11593) - [#11589](https://github.com/mixxxdj/mixxx/issues/11589) -* No longer stop a track with an active loop at the very end - [#11558](https://github.com/mixxxdj/mixxx/pull/11558) - [#11557](https://github.com/mixxxdj/mixxx/issues/11557) -* Fixed resyncing when moving an active loop - [#11152](https://github.com/mixxxdj/mixxx/pull/11152) - [#11381](https://github.com/mixxxdj/mixxx/issues/11381) -* Allow true gapless playback when repeating full tracks - [#11532](https://github.com/mixxxdj/mixxx/pull/11532) - [#9842](https://github.com/mixxxdj/mixxx/issues/9842) - [#11704](https://github.com/mixxxdj/mixxx/pull/11704) -* Rhythmbox: Fixed bulk track imports from playlists - [#11661](https://github.com/mixxxdj/mixxx/pull/11661) -* Console log spam reduced - [#11690](https://github.com/mixxxdj/mixxx/pull/11690) - [#11691](https://github.com/mixxxdj/mixxx/issues/11691) -* Numark DJ2GO2 Touch: Add missing loop_out mapping for the right deck - [#11595](https://github.com/mixxxdj/mixxx/pull/11595) - [#11659](https://github.com/mixxxdj/mixxx/pull/11659) -* Shade: Fixed VU-Meter and other minor issues - [#11598](https://github.com/mixxxdj/mixxx/pull/11598) -* Fixed a rare crash when disabling quantize form a controller - [#11744](https://github.com/mixxxdj/mixxx/pull/11744) - [#11709](https://github.com/mixxxdj/mixxx/issues/11709) -* Controller Preferences: Avoid scrollbars in I/O tabs if Info tab exceeds page height - [#11756](https://github.com/mixxxdj/mixxx/pull/11756) -* Broadcast: Improved error message in case of timeout - [#11775](https://github.com/mixxxdj/mixxx/pull/11775) -* Handle setting `loop_in` and `loop_out` to the same position - [#11771](https://github.com/mixxxdj/mixxx/pull/11771) - [#10600](https://github.com/mixxxdj/mixxx/issues/10600) -* Fix build issues with Protobuf v23.4 and with clang 32 - [#11751](https://github.com/mixxxdj/mixxx/pull/11751) - [#11765](https://github.com/mixxxdj/mixxx/pull/11765) - [#11762](https://github.com/mixxxdj/mixxx/issues/11762) -* Disable GL VU-Meters on Windows by default. They can be re-enabled via the command line option `--enableVuMeterGL`. - [#11787](https://github.com/mixxxdj/mixxx/pull/11787) - [#11785](https://github.com/mixxxdj/mixxx/issues/11785) - [#11789](https://github.com/mixxxdj/mixxx/issues/11789) -* Library preferences: Uncheck Serato metadata export when file metadata export is unchecked - [#11782](https://github.com/mixxxdj/mixxx/pull/11782) - [#11226](https://github.com/mixxxdj/mixxx/issues/11226) -* Denon MC6000MK2: Delete mapping for main gain - [#11792](https://github.com/mixxxdj/mixxx/pull/11792) -* Improve output in case of some failed file system operations - [#11783](https://github.com/mixxxdj/mixxx/pull/11783) -* Fix overlapping buffers when decoding M4A files using FFmpeg before 4.4 - [#11760](https://github.com/mixxxdj/mixxx/pull/11760) - [#11545](https://github.com/mixxxdj/mixxx/issues/11545) -* Don't reject key values from file metadata with non-minor/-major scales. - [#11001](https://github.com/mixxxdj/mixxx/pull/11001) - [#10995](https://github.com/mixxxdj/mixxx/issues/10995) -* Allow playing tracks with durations of more than 6 hours - [#11511](https://github.com/mixxxdj/mixxx/pull/11511) - [#11504](https://github.com/mixxxdj/mixxx/issues/11504) -* Update latency compensation for Soundtouch version 2.1.1 to 2.3 - [#11154](https://github.com/mixxxdj/mixxx/pull/11154) - -## [2.3.5](https://github.com/mixxxdj/mixxx/milestone/39) (2023-05-10) - -* Fix empty waveform overview after loading a track (Mixxx 2.3.4 regression) - Fixed by [#11333](https://github.com/mixxxdj/mixxx/pull/11333) - [#11359](https://github.com/mixxxdj/mixxx/pull/11359) - [#11344](https://github.com/mixxxdj/mixxx/issues/11344) -* Fullscreen: Fix a crash that occurs on Linux after enabling fullsceen and using menu - shortcuts e.g. Alt-F. - [#11328](https://github.com/mixxxdj/mixxx/pull/11328) - [#11320](https://github.com/mixxxdj/mixxx/issues/11320) -* Fullscreen: Rebuild & reconnect menu only on desktops with global menu - [#11350](https://github.com/mixxxdj/mixxx/pull/11350) -* macOS: Request Microphone and line-in access permission. - [#11367](https://github.com/mixxxdj/mixxx/pull/11367) - [#11365](https://github.com/mixxxdj/mixxx/issues/11365) -* JACK API: Allow to explicit select buffers of 2048 and 4096 frames/period. They are not - supported by the automatic buffer setting of the used PortAudio library. - [#11366](https://github.com/mixxxdj/mixxx/pull/11366) - [#11341](https://github.com/mixxxdj/mixxx/issues/11341) -* Pioneer DDJ-400: Make Beat FX section more intuitive - [#10912](https://github.com/mixxxdj/mixxx/pull/10912) -* Playlist export: Adopt new extension after changing the playlist type - [#11332](https://github.com/mixxxdj/mixxx/pull/11332) - [#11327](https://github.com/mixxxdj/mixxx/issues/11327) -* LateNight: brighter fx parameter buttons - [#11397](https://github.com/mixxxdj/mixxx/pull/11397) -* Fix drift in analyzis data after exporting metadata to MP3 files with ID3v1.1 tags - [#11168](https://github.com/mixxxdj/mixxx/pull/11168) - [#11159](https://github.com/mixxxdj/mixxx/issues/11159) -* Fix broadcasting using Opus encoding - [#11349](https://github.com/mixxxdj/mixxx/pull/11349) - [#10666](https://github.com/mixxxdj/mixxx/issues/10666) -* Tango: Remove VU peak indicators from stacked layout. This fixes a visual regression in Mixxx 2.3.4. - [#11430](https://github.com/mixxxdj/mixxx/pull/11430) - [#11362](https://github.com/mixxxdj/mixxx/issues/11362) -* Hercules P32: Allow optional using pregain instead of dry/wet knob - [#3538](https://github.com/mixxxdj/mixxx/pull/3538) -* Improve Color Picker dialog - [#11439](https://github.com/mixxxdj/mixxx/pull/11439) -* Fix blank Waveform overview after changing Skin with a track loaded - [#11453](https://github.com/mixxxdj/mixxx/pull/11453) -* Linux: Log a warning when the audio thread is not scheduled with real-time policy - [#11472](https://github.com/mixxxdj/mixxx/pull/11472) - [#11465](https://github.com/mixxxdj/mixxx/issues/11465) -* Auto DJ: Fixes stop due to tracks with changed length - [#11479](https://github.com/mixxxdj/mixxx/pull/11479) - [#11492](https://github.com/mixxxdj/mixxx/pull/11492) - [#11448](https://github.com/mixxxdj/mixxx/issues/11448) - * Auto DJ: Fix Auto DJ indicator state when controlling it via shortcut (SHIFT+F12) - [#11494](https://github.com/mixxxdj/mixxx/issues/11494) - [#11495](https://github.com/mixxxdj/mixxx/pull/11495) -* Fix building with Clang 15/16 - [#11490](https://github.com/mixxxdj/mixxx/pull/11490) - [#11485](https://github.com/mixxxdj/mixxx/pull/11485) -* Fix EQ and waveforms analysis when compiling with GCC 13 - [#11501](https://github.com/mixxxdj/mixxx/pull/11501) - [#11483](https://github.com/mixxxdj/mixxx/issues/11483) - [#11502](https://github.com/mixxxdj/mixxx/pull/11502) - [#11480](https://github.com/mixxxdj/mixxx/pull/11480) - [#11488](https://github.com/mixxxdj/mixxx/pull/11488) -* Numark Mixtrack Pro FX: Fix sound output via WDM-KS on Windows - [#11393](https://github.com/mixxxdj/mixxx/issues/11393) -* Fix crash on startup caused by faulty ASIO driver like FlexASIO 1.4 or Music Maker - [#11426](https://github.com/mixxxdj/mixxx/issues/11426) - [#10081](https://github.com/mixxxdj/mixxx/issues/10081) -* Windows: Show a loopback device that allows to mix in system sound - [#11427](https://github.com/mixxxdj/mixxx/issues/11427) - [#11451](https://github.com/mixxxdj/mixxx/issues/11451) -* Fix sorting via column header in external library features - [#11491](https://github.com/mixxxdj/mixxx/issues/11491) - [#11499](https://github.com/mixxxdj/mixxx/pull/11499) - [#11498](https://github.com/mixxxdj/mixxx/pull/11498) -* Fix wrong waveform background color on recent Linux distributions like Fedora 37 - [#11164](https://github.com/mixxxdj/mixxx/issues/11164) - [#11523](https://github.com/mixxxdj/mixxx/pull/11523) -* Serato Metadata: Don't import empty (black) cue points - [#11534](https://github.com/mixxxdj/mixxx/pull/11534) - [#11530](https://github.com/mixxxdj/mixxx/issues/11530) - [#11467](https://github.com/mixxxdj/mixxx/pull/11467) - [#11466](https://github.com/mixxxdj/mixxx/pull/11466) - [#11283](https://github.com/mixxxdj/mixxx/issues/11283) -* Track context menu: Immediately adopt new position when resetting cues - [#11482](https://github.com/mixxxdj/mixxx/pull/11482) -* Windows: Fix possible crash with faulty mp3 files - [#11535](https://github.com/mixxxdj/mixxx/pull/11535) - [#11531](https://github.com/mixxxdj/mixxx/issues/11531) - [#11528](https://github.com/mixxxdj/mixxx/pull/11528) - [#11521](https://github.com/mixxxdj/mixxx/issues/11521) - -## [2.3.4](https://launchpad.net/mixxx/+milestone/2.3.4) (2023-03-03) - -* Track Properties: Show 'date added' as local time [#4838](https://github.com/mixxxdj/mixxx/pull/4838) [#10776](https://github.com/mixxxdj/mixxx/issues/10776) -* Shade: Fix library sidebar splitter glitch [#4828](https://github.com/mixxxdj/mixxx/pull/4828) [#10757](https://github.com/mixxxdj/mixxx/issues/10757) -* LateNight: Add a border to the crossfader when Auto DJ is active. [#10913](https://github.com/mixxxdj/mixxx/pull/10913) -* LateNight: Isolate searchbar so maximize button is attached to tracks view. [#11132](https://github.com/mixxxdj/mixxx/pull/11132) -* macOS builds: Perform ad-hoc signing of macOS bundle in Pull request and personal repositories [#4774](https://github.com/mixxxdj/mixxx/pull/4774) -* Waveform: Avoid visual glitch with ranges < 1 px [#4804](https://github.com/mixxxdj/mixxx/pull/4804) -* Build Mixxx on macOS 11, replacing deprecated macOS 10.15 [#4863](https://github.com/mixxxdj/mixxx/pull/4863) -* Add macOS 13.0 (Ventura) support, by using portaudio 19.7.0 [#11046](https://github.com/mixxxdj/mixxx/pull/11046) -* EQ preferences: Properly restore 'One EQ for all decks' setting [#4886](https://github.com/mixxxdj/mixxx/pull/4886) -* Cover Art: Fix picking wrong cover file, when track file name contains extra dots [#4909](https://github.com/mixxxdj/mixxx/pull/4909) -* MusicBrainz: Respect rate limits [#10874](https://github.com/mixxxdj/mixxx/pull/10874) [#10795](https://github.com/mixxxdj/mixxx/issues/10795) -* MusicBrainz: Stop fetching after closing the dialog [#10878](https://github.com/mixxxdj/mixxx/pull/10878) [#10877](https://github.com/mixxxdj/mixxx/issues/10877) -* MusicBrainz: Fixed stalled GUI after client timeout [#10875](https://github.com/mixxxdj/mixxx/pull/10875) [#10883](https://github.com/mixxxdj/mixxx/issues/10883) -* macOs: Fix frozen skin control after Ctrl-Click [#10869](https://github.com/mixxxdj/mixxx/pull/10869) [#10831](https://github.com/mixxxdj/mixxx/issues/10831) -* Avoid redundant messages boxes after track loading fails [#10889](https://github.com/mixxxdj/mixxx/pull/10889) -* Use OpenGL VU meter widgets. This aims to improve performance with macOS. - [#10893](https://github.com/mixxxdj/mixxx/pull/10893) - [#11052](https://github.com/mixxxdj/mixxx/pull/11052) - [#10979](https://github.com/mixxxdj/mixxx/pull/10979) - [#10973](https://github.com/mixxxdj/mixxx/pull/10973) - [#10983](https://github.com/mixxxdj/mixxx/pull/10983) - [#11288](https://github.com/mixxxdj/mixxx/pull/11288) -* Prevent wild numbers from appearing during scratching under vinyl control. [#10916](https://github.com/mixxxdj/mixxx/pull/10916) -* Fixed a possible crash due to a race condition when editing cue points. [#10976](https://github.com/mixxxdj/mixxx/pull/10976) [#10689](https://github.com/mixxxdj/mixxx/issues/10689) -* Fixed a possible crash when overing cue point via mouse in the waveforms. [#10960](https://github.com/mixxxdj/mixxx/pull/10960) [#10956](https://github.com/mixxxdj/mixxx/issues/10956) -* History: Disallow dropping tracks. [#10969](https://github.com/mixxxdj/mixxx/pull/10969) [#10250](https://github.com/mixxxdj/mixxx/issues/10250) -* WTrackMenu: Sort crates and playlists like in sidebar. [#11023](https://github.com/mixxxdj/mixxx/pull/11023) -* WCoverArtLabel: Don't open full-size cover if no cover is loaded, to avoid an issue when closing. [#11022](https://github.com/mixxxdj/mixxx/pull/11022) [#11021](https://github.com/mixxxdj/mixxx/issues/11021) -* Removed integer truncation of the position when reading cue points from the database. [#10998](https://github.com/mixxxdj/mixxx/pull/10998) -* Fix cue points being assigned invalid value of -1.0 [#11000](https://github.com/mixxxdj/mixxx/pull/11000) [#10993](https://github.com/mixxxdj/mixxx/issues/10993) -* Auto DJ: Added a warning in a message box when it is started without decks with left and a right crossfader orientation [#11018](https://github.com/mixxxdj/mixxx/pull/11018) -* Fixed crash with FFmpeg decoder [#11044](https://github.com/mixxxdj/mixxx/pull/11044) -* Fixed issue with finding moved library tracks. [#11051](https://github.com/mixxxdj/mixxx/pull/11051) -* Preserve and synchronize ID3v1 tags (TagLib v1.12) [#11163](https://github.com/mixxxdj/mixxx/pull/11163) [#11123](https://github.com/mixxxdj/mixxx/issues/11123) -* Replay Gain Preferences: Fix the "adjust by" text in case of negative adjustments [#11176](https://github.com/mixxxdj/mixxx/pull/11176) -* macOs: Install Qt translation [#11134](https://github.com/mixxxdj/mixxx/pull/11134) [#11110](https://github.com/mixxxdj/mixxx/issues/11110) -* macOs: Fix assuming wrong system language [#11218](https://github.com/mixxxdj/mixxx/pull/11218) [#11195](https://github.com/mixxxdj/mixxx/issues/11195) -* Fix resetting track colors on metadata reimport (Serato metadata): [#11217](https://github.com/mixxxdj/mixxx/pull/11217) [#11213](https://github.com/mixxxdj/mixxx/issues/11213) -* Preferences: Fix incomplete version check in 2.3 during upgrade [#11229](https://github.com/mixxxdj/mixxx/pull/11229) [#9709](https://github.com/mixxxdj/mixxx/issues/9709) -* Allow search in external libraries [#11221](https://github.com/mixxxdj/mixxx/pull/11221) [#11216](https://github.com/mixxxdj/mixxx/issues/11216) -* JACK buffer size fix [#11121](https://github.com/mixxxdj/mixxx/pull/11121) -* Don't discard file tags with tuning information like "A#m +50" [#10992](https://github.com/mixxxdj/mixxx/pull/10992) -* Year search: Find also full date entries [#11251](https://github.com/mixxxdj/mixxx/pull/11251) [#11113](https://github.com/mixxxdj/mixxx/issues/11113) -* Fix visual alignment of beats and waveform in case of decoding issues [#11162](https://github.com/mixxxdj/mixxx/pull/11162) -* Avoid "active key-value observers" messages during skin parsing on macOS [#11265](https://github.com/mixxxdj/mixxx/pull/11265) -* Fullscreen on Linux: Fix issues caused by Ubuntu Unity workaround [#11295](https://github.com/mixxxdj/mixxx/pull/11295) [#11281](https://github.com/mixxxdj/mixxx/issues/11281) [#11294](https://github.com/mixxxdj/mixxx/issues/11294) - -### New Controller Mappings - -* Traktor Kontrol S2 Mk1: Add controller mapping [#3905](https://github.com/mixxxdj/mixxx/pull/3905) -* Numark Party Mix: Mapping added [#4720](https://github.com/mixxxdj/mixxx/pull/4720) - -### Controller Fixes - -* Traktor S3: Fix issues with sampler and hotcue buttons [#4676](https://github.com/mixxxdj/mixxx/pull/4676) -* Numark DJ2GO2: Fix sliders and knobs [#4835](https://github.com/mixxxdj/mixxx/pull/4835) [#10586](https://github.com/mixxxdj/mixxx/issues/10586) -* Numark DJ2Go2: Support HotCue clear with pad [#10841](https://github.com/mixxxdj/mixxx/pull/10841) -* Numark DJ2Go2: Fix inverted tempo fader [#10852](https://github.com/mixxxdj/mixxx/pull/10852) [#10586](https://github.com/mixxxdj/mixxx/issues/10586) -* Numark N4: Inverted pitch slider, to match the GUI orientation [#11057](https://github.com/mixxxdj/mixxx/pull/11046) -* Ableton Push: Show as one device [#10905](https://github.com/mixxxdj/mixxx/pull/10905) -* Denon DJ MC7000: off-by-one fix, soft-start/break effect, pitch play, 32 velocity samplers - [#4902](https://github.com/mixxxdj/mixxx/pull/4902) - [#4729](https://github.com/mixxxdj/mixxx/pull/4729) -* Potmeters: Add support for arbitrary maximums in 7-/14-bit handlers from controller scripts [#4495](https://github.com/mixxxdj/mixxx/pull/4495) -* Controller Preferences: Fix some usability issues [#10821](https://github.com/mixxxdj/mixxx/pull/10821) -* Controller mapping table: show readable/translated strings for script bindings [#11139](https://github.com/mixxxdj/mixxx/pull/11139) -* Control picker menu: Added loop_in/out_goto to list [#11133](https://github.com/mixxxdj/mixxx/pull/11133) - -### Packaging - -* Fix compatibility with FFmpeg 5.1 and require FFmpeg v4.1.9 [#10862](https://github.com/mixxxdj/mixxx/pull/10862) [#10866](https://github.com/mixxxdj/mixxx/pull/10866) -* Fix GCC 12.2.0 compatibility [#10863](https://github.com/mixxxdj/mixxx/pull/10863) -* Improve CMake 3.24 compatibility [#10864](https://github.com/mixxxdj/mixxx/pull/10864) -* Use MIXXX_VCPKG_ROOT cmake and environment variable to find the vcpkg environment [#10904](https://github.com/mixxxdj/mixxx/pull/10904) -* Fix `-Wswitch` when building with FLAC >= 1.4.0 [#10921](https://github.com/mixxxdj/mixxx/pull/10921) - -## [2.3.3](https://launchpad.net/mixxx/+milestone/2.3.3) (2022-06-21) - -* Pioneer DDJ-SB3: Fix controller breaking when releasing the shift button [#4659](https://github.com/mixxxdj/mixxx/pull/4659) -* Traktor S3: Push two deck switches to explicitly clone decks [#4665](https://github.com/mixxxdj/mixxx/pull/4665) [#4671](https://github.com/mixxxdj/mixxx/pull/4671) [#10660](https://github.com/mixxxdj/mixxx/issues/10660) -* Behringer DDM4000: Improve stability and add soft-takeover for encoder knobs [#4318](https://github.com/mixxxdj/mixxx/pull/4318) [#4799](https://github.com/mixxxdj/mixxx/pull/4799) -* Denon MC7000: Fix 'inverted shift' bug in the controller mapping [#4755](https://github.com/mixxxdj/mixxx/pull/4755) -* Fix spinback and break effect in the controller engine [#4708](https://github.com/mixxxdj/mixxx/pull/4708) -* Fix scratch on first wheel touch [#4761](https://github.com/mixxxdj/mixxx/pull/4761) [#9489](https://github.com/mixxxdj/mixxx/issues/9489) -* Preferences: Prevent controller settings being treated as changed even though they were not [#4721](https://github.com/mixxxdj/mixxx/pull/4721) [#10365](https://github.com/mixxxdj/mixxx/issues/10365) -* Fix rare crash when closing the progress dialog [#4695](https://github.com/mixxxdj/mixxx/pull/4695) -* Prevent preferences dialog from going out of screen [#4613](https://github.com/mixxxdj/mixxx/pull/4613) -* Fix undesired jump-cuts in Auto DJ [#4693](https://github.com/mixxxdj/mixxx/pull/4693) [#10592](https://github.com/mixxxdj/mixxx/issues/10592) [#10093](https://github.com/mixxxdj/mixxx/issues/10093) -* Fix bug that caused Auto DJ to stop playback after some time [#4698](https://github.com/mixxxdj/mixxx/pull/4698) [#10093](https://github.com/mixxxdj/mixxx/issues/10093) [#10670](https://github.com/mixxxdj/mixxx/issues/10670) -* Do not reset crossfader when Auto DJ is deactivated [#4714](https://github.com/mixxxdj/mixxx/pull/4714) [#10683](https://bugs.launchpad.net/bugs/1965298) -* Change the minimum Auto DJ transition time to -99 [#4768](https://github.com/mixxxdj/mixxx/pull/4768) [#10739](https://github.com/mixxxdj/mixxx/issues/10739) -* Samplers, crates, playlists: fix storing import/export paths [#4699](https://github.com/mixxxdj/mixxx/pull/4699) [#10679](https://bugs.launchpad.net/bugs/1964508) -* Library: keep hidden tracks in history [#4725](https://github.com/mixxxdj/mixxx/pull/4725) -* Broadcasting: allow multiple connections to same mount if only one is enabled [#4750](https://github.com/mixxxdj/mixxx/pull/4750) [#10727](https://github.com/mixxxdj/mixxx/issues/10727) -* Fix a rare mouse vanish bug when controlling knobs [#4744](https://github.com/mixxxdj/mixxx/pull/4744) [#6922](https://github.com/mixxxdj/mixxx/issues/6922) [#10715](https://github.com/mixxxdj/mixxx/issues/10715) -* Restore keylock from configuration and fix pitch ratio rounding issue [#4756](https://github.com/mixxxdj/mixxx/pull/4756) [#10518](https://github.com/mixxxdj/mixxx/issues/10518) -* Improve CSV export of playlists and crates and fix empty rating column [#4762](https://github.com/mixxxdj/mixxx/pull/4762) -* Fix passthrough-related crash in waveform code [#4789](https://github.com/mixxxdj/mixxx/pull/4789) [#4791](https://github.com/mixxxdj/mixxx/pull/4791) [#10650](https://github.com/mixxxdj/mixxx/issues/10650) [#10743](https://github.com/mixxxdj/mixxx/issues/10743) -* Passthrough: stop rendering waveforms and disable Cue/Play indicators [4793](https://github.com/mixxxdj/mixxx/pull/4793) - -## [2.3.2](https://launchpad.net/mixxx/+milestone/2.3.2) (2022-01-31) - -* Playlist: Enable sorting by color [#4352](https://github.com/mixxxdj/mixxx/pull/4352) [#10546](https://github.com/mixxxdj/mixxx/issues/10546) -* Fix crash when using Doubling/Halving/etc. BPM from track's Properties window on tracks without BPM [#4587](https://github.com/mixxxdj/mixxx/pull/4587) [#10625](https://github.com/mixxxdj/mixxx/issues/10625) -* Fix writing metadata on Windows for files that have never been played [#4586](https://github.com/mixxxdj/mixxx/pull/4586) [#10620](https://github.com/mixxxdj/mixxx/issues/10620) -* Preserve file creation time when writing metadata on Windows [#4586](https://github.com/mixxxdj/mixxx/pull/4586) [#10619](https://github.com/mixxxdj/mixxx/issues/10619) -* Fix handling of file extension when importing and exporting sampler settings [#4539](https://github.com/mixxxdj/mixxx/pull/4539) -* Fix crash when using an empty directory as resource path using the `--resource-path` command line option [#4575](https://github.com/mixxxdj/mixxx/pull/4575) [#10461](https://github.com/mixxxdj/mixxx/issues/10461) -* Pioneer DDJ-SB3: Add controller mapping [#3821](https://github.com/mixxxdj/mixxx/pull/3821) -* Don't wipe sound config during startup if configured devices are unavailable [#4544](https://github.com/mixxxdj/mixxx/pull/4544) -* Append selected file extension when exporting to playlist files [#4531](https://github.com/mixxxdj/mixxx/pull/4531) [#10066](https://github.com/mixxxdj/mixxx/issues/10066) -* Fix crash when using midi.sendShortMsg and platform vnc [#4635](https://github.com/mixxxdj/mixxx/pull/4635) [#10632](https://github.com/mixxxdj/mixxx/issues/10632) -* Traktor S3: Fix timedelta calculation bugs [#4646](https://github.com/mixxxdj/mixxx/pull/4646) [#10645](https://github.com/mixxxdj/mixxx/issues/10645) - -### Packaging - -* Downloads of external dependencies are placed in build/downloads -* The sources for libkeyfinder are now expected in build/downloads/libkeyfinder-2.2.6.zip instead of build/download/libkeyfinder/v2.2.6.zip -* CMake: Adjust the download directory and name of external dependencies [#4511](https://github.com/mixxxdj/mixxx/pull/4511) -* Fix/Improve Appstream metainfo - [#4344](https://github.com/mixxxdj/mixxx/pull/4344) - [#4346](https://github.com/mixxxdj/mixxx/pull/4346) - [#4349](https://github.com/mixxxdj/mixxx/pull/4349) - -## [2.3.1](https://launchpad.net/mixxx/+milestone/2.3.1) (2021-09-29) - -* Added mapping for the Numark DJ2GO2 Touch controller [#4108](https://github.com/mixxxdj/mixxx/pull/4108) [#4287](https://github.com/mixxxdj/mixxx/pull/4287) -* Added mapping for the Numark Mixtrack Pro FX controller [#4160](https://github.com/mixxxdj/mixxx/pull/4160) -* Updated mapping for Behringer DDM4000 mixer [#4262](https://github.com/mixxxdj/mixxx/pull/4262) -* Updated mapping for Denon MC7000 controller [#4021](https://github.com/mixxxdj/mixxx/pull/4021) -* Hercules Inpulse 300: Add better FX controls and other minor improvements [#4246](https://github.com/mixxxdj/mixxx/pull/4246) -* Denon MC7000: Improve slip mode and jog wheel handling [#4021](https://github.com/mixxxdj/mixxx/pull/4021) [#4324](https://github.com/mixxxdj/mixxx/pull/4324) -* Disabled detection of keyboards and mice as HID controllers [#4243](https://github.com/mixxxdj/mixxx/pull/4243) -* Disabled detection of all HID controllers with Apple's vendor ID. Apple doesn't build actual controllers. [#4260](https://github.com/mixxxdj/mixxx/pull/4260) [#4273](https://github.com/mixxxdj/mixxx/pull/4273) -* Add support for HiDPI scale factors of 125% and 175% (only with Qt 5.14+) [#10485](https://github.com/mixxxdj/mixxx/issues/10485) [#4161](https://github.com/mixxxdj/mixxx/pull/4161) -* Fix Echo effect adding left channel samples to right channel [#4141](https://github.com/mixxxdj/mixxx/pull/4141) -* Fix bad phase seek when starting from preroll [#10423](https://github.com/mixxxdj/mixxx/issues/10423) [#4093](https://github.com/mixxxdj/mixxx/pull/4093) -* Fix bad phase seek when a channel's audible status changes [#4156](https://github.com/mixxxdj/mixxx/pull/4156) -* Tango skin: Show crossfader assign buttons by default [#4046](https://github.com/mixxxdj/mixxx/pull/4046) -* Fix keyfinder library in arm64 builds [#4047](https://github.com/mixxxdj/mixxx/pull/4047) -* Fix wrong track being recorded in History [#10454](https://github.com/mixxxdj/mixxx/issues/10454) [#4041](https://github.com/mixxxdj/mixxx/pull/4041) [#4059](https://github.com/mixxxdj/mixxx/pull/4059) [#4107](https://github.com/mixxxdj/mixxx/pull/4107) [#4296](https://github.com/mixxxdj/mixxx/pull/4296) -* Fix support for relative paths in the skin system which caused missing images in third-party skins [#4151](https://github.com/mixxxdj/mixxx/pull/4151) -* Fix relocation of directories with special/reserved characters in path name [#4146](https://github.com/mixxxdj/mixxx/pull/4146) -* Update keyboard shortcuts sheet [#4042](https://github.com/mixxxdj/mixxx/pull/4042) -* Library: resize the Played checkbox and BPM lock with the library font [#4050](https://github.com/mixxxdj/mixxx/pull/4050) -* Don't allow Input focus on waveforms [#4134](https://github.com/mixxxdj/mixxx/pull/4134) -* Fix performance issue on AArch64 by enabling flush-to-zero for floating-point arithmetic [#4144](https://github.com/mixxxdj/mixxx/pull/4144) -* Fix custom key notation not restored correctly after restart [#4136](https://github.com/mixxxdj/mixxx/pull/4136) -* Traktor S3: Disable scratch when switching decks to prevent locked scratch issue [#4073](https://github.com/mixxxdj/mixxx/pull/4073) -* FFmpeg: Ignore inaudible samples before start of stream [#4245](https://github.com/mixxxdj/mixxx/pull/4245) -* Controller Preferences: Don't automatically enable checkbox if controller is disabled [#4244](https://github.com/mixxxdj/mixxx/pull/4244) [#10503](https://github.com/mixxxdj/mixxx/issues/10503) -* Tooltips: Always show tooltips in preferences [#4198](https://github.com/mixxxdj/mixxx/pull/4198) [#9716](https://github.com/mixxxdj/mixxx/issues/9716) -* Tooltips: Use item label for tooltips in library side bar and show ID when debugging. [#4247](https://github.com/mixxxdj/mixxx/pull/4247) -* Library sidebar: Also activate items on PageUp/Down events. [#4237](https://github.com/mixxxdj/mixxx/pull/4237) -* Fix handling of preview button cell events in developer mode. [#4264](https://github.com/mixxxdj/mixxx/pull/4264) [#10418](https://github.com/mixxxdj/mixxx/issues/10418) -* Auto DJ: Fix bug which could make an empty track stop Auto DJ. [#4267](https://github.com/mixxxdj/mixxx/pull/4267) [#10504](https://github.com/mixxxdj/mixxx/issues/10504) -* Fix Auto DJ skipping tracks randomly [#4319](https://github.com/mixxxdj/mixxx/pull/4319) [#10505](https://github.com/mixxxdj/mixxx/issues/10505) -* Fix high CPU load due to extremely high internal sync clock values [#4312](https://github.com/mixxxdj/mixxx/pull/4312) [#10520](https://github.com/mixxxdj/mixxx/issues/10520) -* Fix preference option for re-analyzing beatgrids imported from other software [#4288](https://github.com/mixxxdj/mixxx/pull/4288) -* Fix wrong base tag used for deployment and displayed in About dialog [#4070](https://github.com/mixxxdj/mixxx/pull/4070) - -### Packaging - -* It is no longer necessary to manually copy the udev rule file in packaging scripts. Now pkg-config is used to determine the udevdir used to install the rules file in the CMake install step when CMAKE_INSTALL_PREFIX is `/` or `/usr`. [#4126](https://github.com/mixxxdj/mixxx/pull/4126) -* Various build issues on FreeBSD are fixed [#4122](https://github.com/mixxxdj/mixxx/pull/4122) [#4123](https://github.com/mixxxdj/mixxx/pull/4123) [#4124](https://github.com/mixxxdj/mixxx/pull/4124) -* .desktop file has be renamed to org.mixxx.Mixxx.desktop according to Freedesktop standards [#4206](https://github.com/mixxxdj/mixxx/pull/4206) -* Uses system provided hidapi library if version >= 0.10.1 [#4215](https://github.com/mixxxdj/mixxx/pull/4215) -* Please update PortAudio to [19.7](https://github.com/PortAudio/portaudio/releases/tag/v19.7.0) if you have not done so already. This is required for Mixxx to work with PipeWire via the JACK API for many devices. -* Install multiple sizes of rasterized icons [#4204](https://github.com/mixxxdj/mixxx/pull/4204) [#4315](https://github.com/mixxxdj/mixxx/pull/4315) [#4254](https://github.com/mixxxdj/mixxx/pull/4254) -* CMake: Fixed detection of SoundTouch pkgconfig file and version [#4209](https://github.com/mixxxdj/mixxx/pull/4209) -* Fix AppStream metainfo [#4205](https://github.com/mixxxdj/mixxx/pull/4205) [#4317](https://github.com/mixxxdj/mixxx/pull/4317) - -## [2.3.0](https://launchpad.net/mixxx/+milestone/2.3.0) (2021-06-28) - -### Hotcues - -* Add hotcue colors and custom labels by right clicking hotcue buttons or right clicking hotcues on overview waveforms [#2016](https://github.com/mixxxdj/mixxx/pull/2016) [#2520](https://github.com/mixxxdj/mixxx/pull/2520) [#2238](https://github.com/mixxxdj/mixxx/pull/2238) [#2560](https://github.com/mixxxdj/mixxx/pull/2560) [#2557](https://github.com/mixxxdj/mixxx/pull/2557) [#2362](https://github.com/mixxxdj/mixxx/pull/2362) -* Mouse hover cues on overview waveform to show time remaining until the cue [#2238](https://github.com/mixxxdj/mixxx/pull/2238) - -### Hotcue & Track Colors - -* Add configurable color per track [#2470](https://github.com/mixxxdj/mixxx/pull/2470) [#2539](https://github.com/mixxxdj/mixxx/pull/2539) [#2545](https://github.com/mixxxdj/mixxx/pull/2545) [#2630](https://github.com/mixxxdj/mixxx/pull/2630) [#6852](https://github.com/mixxxdj/mixxx/issues/6852) -* Add customizable color palettes for hotcue and track colors [#2530](https://github.com/mixxxdj/mixxx/pull/2530) [#2589](https://github.com/mixxxdj/mixxx/pull/2589) [#3749](https://github.com/mixxxdj/mixxx/pull/3749) [#2902](https://github.com/mixxxdj/mixxx/pull/2902) -* Add hotcue color find-and-replace tool [#2547](https://github.com/mixxxdj/mixxx/pull/2547) - -### Importing From Other DJ Software - -* Import cue points, track colors, and playlists from Serato file tags & database [#2480](https://github.com/mixxxdj/mixxx/pull/2480) [#2526](https://github.com/mixxxdj/mixxx/pull/2526) [#2499](https://github.com/mixxxdj/mixxx/pull/2499) [#2495](https://github.com/mixxxdj/mixxx/pull/2495) [#2673](https://github.com/mixxxdj/mixxx/pull/2673) [#3885](https://github.com/mixxxdj/mixxx/pull/3885) - * Note: Mixxx does not yet support multiple loops per track. We are [working on this for Mixxx 2.4](https://github.com/mixxxdj/mixxx/pull/2194). In Mixxx 2.3, if you import a track with multiple loops from Serato, Mixxx will use the first loop cue as the single loop Mixxx currently supports. The imported loops are still stored in Mixxx's database and are treated as hotcues in Mixxx 2.3. If you do not delete these hotcues, they will be usable as loops in Mixxx 2.4. Serato keeps loops and hotcues in separate lists, but Mixxx does not, so loops from Serato are imported starting as hotcue 9. -* Import cue points, track colors, and playlists from Rekordbox USB drives [#2119](https://github.com/mixxxdj/mixxx/pull/2119) [#2555](https://github.com/mixxxdj/mixxx/pull/2555) [#2543](https://github.com/mixxxdj/mixxx/pull/2543) [#2779](https://github.com/mixxxdj/mixxx/pull/2779) - * Note: The first Rekordbox memory cue is imported for the main cue button in Mixxx and the remaining Rekordbox memory cues are imported as Mixxx hotcues, starting with the next hotcue number after the last hotcue from Rekordbox. - * Note: Mixxx does not yet support multiple loops per track. Imported loops from Rekordbox are treated like imported loops from Serato, so refer to the note above for details. - -### Intro & Outro Cues - -* Add intro & outro range cues with automatic silence detection [#1242](https://github.com/mixxxdj/mixxx/pull/1242) -* Show duration of intro & outro ranges on overview waveform [#2089](https://github.com/mixxxdj/mixxx/pull/2089) -* Use intro & outro cues in AutoDJ transitions [#2103](https://github.com/mixxxdj/mixxx/pull/2103) - -### Deck cloning - -* Add deck cloning (also known as "instant doubles" in other DJ software) by dragging and dropping between decks [#1892](https://github.com/mixxxdj/mixxx/pull/1892) and samplers [#3200](https://github.com/mixxxdj/mixxx/pull/3200) -* Clone decks by double pressing the load button on a controller (with option to disable this) [#2024](https://github.com/mixxxdj/mixxx/pull/2024) [#2042](https://github.com/mixxxdj/mixxx/pull/2042) - -### Skins & GUI - -* Aesthetically revamped LateNight skin [#2298](https://github.com/mixxxdj/mixxx/pull/2298) [#2342](https://github.com/mixxxdj/mixxx/pull/2342) -* Right click overview waveform to show time remaining until that point [#2238](https://github.com/mixxxdj/mixxx/pull/2238) -* Show track info dialog when double clicking track labels in decks [#2990](https://github.com/mixxxdj/mixxx/pull/2990) -* Show track context menu when right clicking text in decks [#2612](https://github.com/mixxxdj/mixxx/pull/2612) [#2675](https://github.com/mixxxdj/mixxx/pull/2675) [#2684](https://github.com/mixxxdj/mixxx/pull/2684) [#2696](https://github.com/mixxxdj/mixxx/pull/2696) -* Add laptop battery widget to skins [#2283](https://github.com/mixxxdj/mixxx/pull/2283) [#2277](https://github.com/mixxxdj/mixxx/pull/2277) [#2250](https://github.com/mixxxdj/mixxx/pull/2250) [#2228](https://github.com/mixxxdj/mixxx/pull/2228) [#2221](https://github.com/mixxxdj/mixxx/pull/2221) [#2163](https://github.com/mixxxdj/mixxx/pull/2163) [#2160](https://github.com/mixxxdj/mixxx/pull/2160) [#2147](https://github.com/mixxxdj/mixxx/pull/2147) [#2281](https://github.com/mixxxdj/mixxx/pull/2281) [#2319](https://github.com/mixxxdj/mixxx/pull/2319) [#2287](https://github.com/mixxxdj/mixxx/pull/2287) -* Show when passthrough mode is active on overview waveforms [#2575](https://github.com/mixxxdj/mixxx/pull/2575) [#2616](https://github.com/mixxxdj/mixxx/pull/2616) -* Changed format of currently playing track in window title from "artist, title" to "artist - title" [#2807](https://github.com/mixxxdj/mixxx/pull/2807) -* Workaround Linux skin change crash [#3144](https://github.com/mixxxdj/mixxx/pull/3144) [#10030](https://github.com/mixxxdj/mixxx/issues/10030) -* Fix touch control [#10108](https://github.com/mixxxdj/mixxx/issues/10108) -* Fix broken knob interaction on touchscreens [#3512](https://github.com/mixxxdj/mixxx/pull/3512) -* AutoDJ: Make "enable" shortcut work after startup [#3242](https://github.com/mixxxdj/mixxx/pull/3242) -* Add rate range indicator [#3693](https://github.com/mixxxdj/mixxx/pull/3693) -* Allow menubar to be styled [#3372](https://github.com/mixxxdj/mixxx/pull/3372) [#3788](https://github.com/mixxxdj/mixxx/pull/3788) -* Add Donate button to About dialog [#3838](https://github.com/mixxxdj/mixxx/pull/3838) [#3846](https://github.com/mixxxdj/mixxx/pull/3846) -* Add Scrollable Skin Widget [#3890](https://github.com/mixxxdj/mixxx/pull/3890) -* Fix minor visual issues in Skins [#3958](https://github.com/mixxxdj/mixxx/pull/3958/) [#3954](https://github.com/mixxxdj/mixxx/pull/3954/) [#3941](https://github.com/mixxxdj/mixxx/pull/3941/) [#3938](https://github.com/mixxxdj/mixxx/pull/3938/) [#3936](https://github.com/mixxxdj/mixxx/pull/3936/) [#3886](https://github.com/mixxxdj/mixxx/pull/3886/) [#3927](https://github.com/mixxxdj/mixxx/pull/3927/) [#3844](https://github.com/mixxxdj/mixxx/pull/3844/) [#3933](https://github.com/mixxxdj/mixxx/pull/3933/) [#3835](https://github.com/mixxxdj/mixxx/pull/3835/) [#3902](https://github.com/mixxxdj/mixxx/pull/3902) [#3931](https://github.com/mixxxdj/mixxx/pull/3931) - -### Music Feature Analysis - -* Multithreaded analysis for much faster batch analysis on multicore CPUs [#1624](https://github.com/mixxxdj/mixxx/pull/1624) [#2142](https://github.com/mixxxdj/mixxx/pull/2142) [#8686](https://github.com/mixxxdj/mixxx/issues/8686) -* Fix bugs affecting key detection accuracy [#2137](https://github.com/mixxxdj/mixxx/pull/2137) [#2152](https://github.com/mixxxdj/mixxx/pull/2152) [#2112](https://github.com/mixxxdj/mixxx/pull/2112) [#2136](https://github.com/mixxxdj/mixxx/pull/2136) - * Note: Users who have not manually corrected keys are advised to clear all keys in their library by pressing Ctrl + A in the library, right clicking, going to Reset -> Key, then reanalyzing their library. This will freeze the GUI while Mixxx clears the keys; this is a known problem that we will not be able to fix for 2.3. Wait until it is finished and you will be able to reanalyze tracks for better key detection results. -* Remove VAMP plugin support and use Queen Mary DSP library directly. vamp-plugin-sdk and vamp-hostsdk are no longer required dependencies. [#926](https://github.com/mixxxdj/mixxx/pull/926) -* Improvements BPM detection on non-const beatgrids [#3626](https://github.com/mixxxdj/mixxx/pull/3626) -* Fix const beatgrid placement [#3965](https://github.com/mixxxdj/mixxx/pull/3965) [#3973](https://github.com/mixxxdj/mixxx/pull/3973) - -### Music Library - -* Add support for searching for empty fields (for example `crate:""`) [#9411](https://github.com/mixxxdj/mixxx/issues/9411) -* Improve synchronization of track metadata and file tags [#2406](https://github.com/mixxxdj/mixxx/pull/2406) -* Library Scanner: Improve hashing of directory contents [#2497](https://github.com/mixxxdj/mixxx/pull/2497) -* Rework of Cover Image Hashing [#8618](https://github.com/mixxxdj/mixxx/issues/8618) [#2507](https://github.com/mixxxdj/mixxx/pull/2507) [#2508](https://github.com/mixxxdj/mixxx/pull/2508) -* MusicBrainz: Handle 301 status response [#2510](https://github.com/mixxxdj/mixxx/pull/2510) -* MusicBrainz: Add extended metadata support [#8549](https://github.com/mixxxdj/mixxx/issues/8549) [#2522](https://github.com/mixxxdj/mixxx/pull/2522) -* TagLib: Fix detection of empty or missing file tags [#9891](https://github.com/mixxxdj/mixxx/issues/9891) [#2535](https://github.com/mixxxdj/mixxx/pull/2535) -* Fix caching of duplicate tracks that reference the same file [#3027](https://github.com/mixxxdj/mixxx/pull/3027) -* Use 6 instead of only 4 compatible musical keys (major/minor) [#3205](https://github.com/mixxxdj/mixxx/pull/3205) -* Fix possible crash when trying to refocus the tracks table while another Mixxx window has focus [#3201](https://github.com/mixxxdj/mixxx/pull/3201) -* Don't create new tags in file when exporting metadata to it [#3898](https://github.com/mixxxdj/mixxx/pull/3898) -* Fix playlist files beginning with non-english characters not being loaded [#3916](https://github.com/mixxxdj/mixxx/pull/3916) -* Enable sorting in "Hidden Tracks" and "Missing Tracks" views [#3828](https://github.com/mixxxdj/mixxx/pull/3828) [#9658](https://github.com/mixxxdj/mixxx/issues/9658/) [#10397](https://github.com/mixxxdj/mixxx/issues/10397/) -* Fix track table being empty after start [#3935](https://github.com/mixxxdj/mixxx/pull/3935/) [#10426](https://github.com/mixxxdj/mixxx/issues/10426/) [#10402](https://github.com/mixxxdj/mixxx/issues/10402/) - -### Audio Codecs - -* Add FFmpeg audio decoder, bringing support for ALAC files [#1356](https://github.com/mixxxdj/mixxx/pull/1356) -* Include LAME MP3 encoder with Mixxx now that the MP3 patent has expired [#7341](https://github.com/mixxxdj/mixxx/issues/7341) [buildserver:#37](https://github.com/mixxxdj/buildserver/pull/37) [buildserver:9e8bcee](https://github.com/mixxxdj/buildserver/commit/9e8bcee771731920ae82f3e076d43f0fb51e5027) -* Add Opus streaming and recording support. [#7530](https://github.com/mixxxdj/mixxx/issues/7530) -* Remove support for SoundSource plugins because the code was not well-maintained and could lead to crashes [#9435](https://github.com/mixxxdj/mixxx/issues/9435) -* Add HE-AAC encoding capabilities for recording and broadcasting [#3615](https://github.com/mixxxdj/mixxx/pull/3615) - -### Audio Engine - -* Fix loss of precision when dealing with floating-point sample positions while setting loop out position and seeking using vinyl control [#3126](https://github.com/mixxxdj/mixxx/pull/3126) [#3127](https://github.com/mixxxdj/mixxx/pull/3127) -* Prevent moving a loop beyond track end [#3117](https://github.com/mixxxdj/mixxx/pull/3117) [#9478](https://github.com/mixxxdj/mixxx/issues/9478) -* Fix possible memory corruption using JACK on Linux [#3160](https://github.com/mixxxdj/mixxx/pull/3160) -* Fix changing of vinyl lead-in time [#10319](https://github.com/mixxxdj/mixxx/issues/10319) [#3781](https://github.com/mixxxdj/mixxx/pull/3781) -* Fix tempo change of non-const beatgrid track on audible deck when cueing another track [#3772](https://github.com/mixxxdj/mixxx/pull/3772) -* Fix crash when changing effect unit routing [#3882](https://github.com/mixxxdj/mixxx/pull/3882) [#9331](https://github.com/mixxxdj/mixxx/issues/9331) -* Make microphone ducking use strength knob the same way in automatic & manual mode [#2750](https://github.com/mixxxdj/mixxx/pull/2750) - -### Controllers - -* Improve workflow for configuring controller mappings and editing mappings [#2569](https://github.com/mixxxdj/mixxx/pull/2569) [#3278](https://github.com/mixxxdj/mixxx/pull/3278) [#3667](https://github.com/mixxxdj/mixxx/pull/3667) -* Improve error reporting from controller scripts [#2588](https://github.com/mixxxdj/mixxx/pull/2588) -* Make hotcue and track colors mappable on controllers [#2030](https://github.com/mixxxdj/mixxx/pull/2030) [#2541](https://github.com/mixxxdj/mixxx/pull/2541) [#2665](https://github.com/mixxxdj/mixxx/pull/2665) [#2520](https://github.com/mixxxdj/mixxx/pull/2520) -* Add way to change library table sorting from controllers [#2118](https://github.com/mixxxdj/mixxx/pull/2118) -* Add support for velocity sensitive sampler buttons in Components JS library [#2032](https://github.com/mixxxdj/mixxx/pull/2032) -* Add logging when script ControlObject callback is disconnected successfully [#2054](https://github.com/mixxxdj/mixxx/pull/2054) -* Add controller mapping for Roland DJ-505 [#2111](https://github.com/mixxxdj/mixxx/pull/2111) -* Add controller mapping for Numark iDJ Live II [#2818](https://github.com/mixxxdj/mixxx/pull/2818) -* Add controller mapping for Hercules DJControl Inpulse 200 [#2542](https://github.com/mixxxdj/mixxx/pull/2542) -* Add controller mapping for Hercules DJControl Jogvision [#2370](https://github.com/mixxxdj/mixxx/pull/2370) -* Add controller mapping for Pioneer DDJ-200 [#3185](https://github.com/mixxxdj/mixxx/pull/3185) [#3193](https://github.com/mixxxdj/mixxx/pull/3193) [#3742](https://github.com/mixxxdj/mixxx/pull/3742) [#3793](https://github.com/mixxxdj/mixxx/pull/3793) [#3949](https://github.com/mixxxdj/mixxx/pull/3949) -* Add controller mapping for Pioneer DDJ-400 [#3479](https://github.com/mixxxdj/mixxx/pull/3479) -* Add controller mapping for ION Discover DJ Pro [#2893](https://github.com/mixxxdj/mixxx/pull/2893) -* Add controller mapping for Native Instrument Traktor Kontrol S3 [#3031](https://github.com/mixxxdj/mixxx/pull/3031) -* Add controller mapping for Behringer BCR2000 [#3342](https://github.com/mixxxdj/mixxx/pull/3342) [#3943](https://github.com/mixxxdj/mixxx/pull/3943) -* Add controller mapping for Behringer DDM4000 [#3542](https://github.com/mixxxdj/mixxx/pull/3542) -* Add controller mapping for Native Instruments Traktor Kontrol S4MK3 [#11284](https://github.com/mixxxdj/mixxx/pull/11284) -* Update controller mapping for Allen & Heath Xone K2 to add intro/outro cues [#2236](https://github.com/mixxxdj/mixxx/pull/2236) -* Update controller mapping for Hercules P32 for more accurate headmix control [#3537](https://github.com/mixxxdj/mixxx/pull/3537) -* Update controller mapping for Native Instruments Traktor Kontrol S4MK2 to add auto-slip mode and pitch fader range [#3331](https://github.com/mixxxdj/mixxx/pull/3331) -* Fix Pioneer DDJ-SB2 controller mapping auto tempo going to infinity bug [#2559](https://github.com/mixxxdj/mixxx/pull/2559) [#9838](https://github.com/mixxxdj/mixxx/issues/9838) -* Fix Numark Mixtrack Pro 3 controller mapping inverted FX on/off control [#3758](https://github.com/mixxxdj/mixxx/pull/3758) -* Gracefully handle MIDI overflow [#825](https://github.com/mixxxdj/mixxx/pull/825) - -### Other - -* Add CMake build system with `ccache` and `sccache` support for faster compilation times and remove SCons [#2280](https://github.com/mixxxdj/mixxx/pull/2280) [#3618](https://github.com/mixxxdj/mixxx/pull/3618) -* Make Mixxx compile even though `QT_NO_OPENGL` or `QT_OPENGL_ES_2` is defined (fixes build on Raspberry Pi) [#9887](https://github.com/mixxxdj/mixxx/issues/9887) [#2504](https://github.com/mixxxdj/mixxx/pull/2504) -* Fix ARM build issues [#3602](https://github.com/mixxxdj/mixxx/pull/3602) -* Fix missing manual in DEB package [#10070](https://github.com/mixxxdj/mixxx/issues/10070) [#2985](https://github.com/mixxxdj/mixxx/pull/2985) -* Add macOS codesigning and notarization to fix startup warnings [#3281](https://github.com/mixxxdj/mixxx/pull/3281) -* Don't trash user configuration if an error occurs when writing [#3192](https://github.com/mixxxdj/mixxx/pull/3192) -* Enable CUE sheet recording by default [#3374](https://github.com/mixxxdj/mixxx/pull/3374) -* Fix crash when double clicking GLSL waveforms with right mouse button [#3904](https://github.com/mixxxdj/mixxx/pull/3904) -* Derive Mixxx version from `git describe` [#3824](https://github.com/mixxxdj/mixxx/pull/3824) [#3841](https://github.com/mixxxdj/mixxx/pull/3841) [#3848](https://github.com/mixxxdj/mixxx/pull/3848) -* Improve tapping the BPM of a deck [#3790](https://github.com/mixxxdj/mixxx/pull/3790) [#10010](https://github.com/mixxxdj/mixxx/issues/10010) -* And countless other small fixes and improvements (too many to list them all!) - -## [2.2.4](https://launchpad.net/mixxx/+milestone/2.2.4) (2020-06-27) - -* Store default recording format after "Restore Defaults" [#9853](https://github.com/mixxxdj/mixxx/issues/9853) [#2414](https://github.com/mixxxdj/mixxx/pull/2414) -* Prevent infinite loop when decoding corrupt MP3 files [#2417](https://github.com/mixxxdj/mixxx/pull/2417) -* Add workaround for broken libshout versions [#2040](https://github.com/mixxxdj/mixxx/pull/2040) [#2438](https://github.com/mixxxdj/mixxx/pull/2438) -* Speed up purging of tracks [#9762](https://github.com/mixxxdj/mixxx/issues/9762) [#2393](https://github.com/mixxxdj/mixxx/pull/2393) -* Don't stop playback if vinyl passthrough input is configured and PASS button is pressed [#2474](https://github.com/mixxxdj/mixxx/pull/2474) -* Fix debug assertion for invalid crate names [#9871](https://github.com/mixxxdj/mixxx/issues/9871) [#2477](https://github.com/mixxxdj/mixxx/pull/2477) -* Fix crashes when executing actions on tracks that already disappeared from the DB [#2527](https://github.com/mixxxdj/mixxx/pull/2527) -* AutoDJ: Skip next track when both deck are playing [#7712](https://github.com/mixxxdj/mixxx/issues/7712) [#2531](https://github.com/mixxxdj/mixxx/pull/2531) -* Tweak scratch parameters for Mixtrack Platinum [#2028](https://github.com/mixxxdj/mixxx/pull/2028) -* Fix auto tempo going to infinity on Pioneer DDJ-SB2 [#2559](https://github.com/mixxxdj/mixxx/pull/2559) -* Fix bpm.tapButton logic and reject missed & double taps [#2594](https://github.com/mixxxdj/mixxx/pull/2594) -* Add controller mapping for Native Instruments Traktor Kontrol S2 MK3 [#2348](https://github.com/mixxxdj/mixxx/pull/2348) -* Add controller mapping for Soundless joyMIDI [#2425](https://github.com/mixxxdj/mixxx/pull/2425) -* Add controller mapping for Hercules DJControl Inpulse 300 [#2465](https://github.com/mixxxdj/mixxx/pull/2465) -* Add controller mapping for Denon MC7000 [#2546](https://github.com/mixxxdj/mixxx/pull/2546) -* Add controller mapping for Stanton DJC.4 [#2607](https://github.com/mixxxdj/mixxx/pull/2607) -* Fix broadcasting via broadcast/recording input [#9959](https://github.com/mixxxdj/mixxx/issues/9959) [#2743](https://github.com/mixxxdj/mixxx/pull/2743) -* Only apply ducking gain in manual ducking mode when talkover is enabed [#7668](https://github.com/mixxxdj/mixxx/issues/7668) [#8995](https://github.com/mixxxdj/mixxx/issues/8995) [#8795](https://github.com/mixxxdj/mixxx/issues/8795) [#2759](https://github.com/mixxxdj/mixxx/pull/2759) -* Ignore MIDI Clock Messages (0xF8) because they are not usable in Mixxx and inhibited the screensaver [#2786](https://github.com/mixxxdj/mixxx/pull/2786) - -## [2.2.3](https://launchpad.net/mixxx/+milestone/2.2.3) (2019-11-24) - -* Don't make users reconfigure sound hardware when it has not changed [#2253](https://github.com/mixxxdj/mixxx/pull/2253) -* Fix MusicBrainz metadata lookup [#9780](https://github.com/mixxxdj/mixxx/issues/9780) [#2328](https://github.com/mixxxdj/mixxx/pull/2328) -* Fix high DPI scaling of cover art [#2247](https://github.com/mixxxdj/mixxx/pull/2247) -* Fix high DPI scaling of cue point labels on scrolling waveforms [#2331](https://github.com/mixxxdj/mixxx/pull/2331) -* Fix high DPI scaling of sliders in Tango skin [#2318](https://github.com/mixxxdj/mixxx/pull/2318) -* Fix sound dropping out during recording [#9732](https://github.com/mixxxdj/mixxx/issues/9732) [#2265](https://github.com/mixxxdj/mixxx/pull/2265) [#2305](https://github.com/mixxxdj/mixxx/pull/2305) [#2308](https://github.com/mixxxdj/mixxx/pull/2308) [#2309](https://github.com/mixxxdj/mixxx/pull/2309) -* Fix rare crash on application shutdown [#2293](https://github.com/mixxxdj/mixxx/pull/2293) -* Workaround various rare bugs caused by database inconsistencies [#9773](https://github.com/mixxxdj/mixxx/issues/9773) [#2321](https://github.com/mixxxdj/mixxx/pull/2321) -* Improve handling of corrupt FLAC files [#2315](https://github.com/mixxxdj/mixxx/pull/2315) -* Don't immediately jump to loop start when loop_out is pressed in quantized mode [#9694](https://github.com/mixxxdj/mixxx/issues/9694) [#2269](https://github.com/mixxxdj/mixxx/pull/2269) -* Preserve order of tracks when dragging and dropping from AutoDJ to playlist [#9661](https://github.com/mixxxdj/mixxx/issues/9661) [#2237](https://github.com/mixxxdj/mixxx/pull/2237) -* Explicitly use X11 Qt platform plugin instead of Wayland in .desktop launcher [#9787](https://github.com/mixxxdj/mixxx/issues/9787) [#2340](https://github.com/mixxxdj/mixxx/pull/2340) -* Pioneer DDJ-SX: fix delayed sending of MIDI messages with low audio buffer sizes [#2326](https://github.com/mixxxdj/mixxx/pull/2326) -* Enable modplug support on Linux by default [#9719](https://github.com/mixxxdj/mixxx/issues/9719) [#2244](https://github.com/mixxxdj/mixxx/pull/2244) [#2272](https://github.com/mixxxdj/mixxx/pull/2272) -* Fix keyboard shortcut for View > Skin Preferences [#9796](https://github.com/mixxxdj/mixxx/issues/9796) [#2358](https://github.com/mixxxdj/mixxx/pull/2358) [#2372](https://github.com/mixxxdj/mixxx/pull/2372) -* Reloop Terminal Mix: Fix mapping of sampler buttons 5-8 [#9772](https://github.com/mixxxdj/mixxx/issues/9772) [#2330](https://github.com/mixxxdj/mixxx/pull/2330) - -## [2.2.2](https://launchpad.net/mixxx/+milestone/2.2.2) (2019-08-10) - -* Fix battery widget with upower <= 0.99.7. [#2221](https://github.com/mixxxdj/mixxx/pull/2221) -* Fix BPM adjust in BpmControl. [#9690](https://github.com/mixxxdj/mixxx/issues/9690) -* Disable track metadata export for .ogg files and TagLib 1.11.1. [#9680](https://github.com/mixxxdj/mixxx/issues/9680) -* Fix interaction of hot cue buttons and looping. [#9350](https://github.com/mixxxdj/mixxx/issues/9350) -* Fix detection of moved tracks. [#2197](https://github.com/mixxxdj/mixxx/pull/2197) -* Fix playlist import. [#2200](https://github.com/mixxxdj/mixxx/pull/2200) [#8852](https://github.com/mixxxdj/mixxx/issues/8852) -* Fix updating playlist labels. [#9697](https://github.com/mixxxdj/mixxx/issues/9697) -* Fix potential segfault on exit. [#9656](https://github.com/mixxxdj/mixxx/issues/9656) -* Fix parsing of invalid BPM values in MP3 files. [#9671](https://github.com/mixxxdj/mixxx/issues/9671) -* Fix crash when removing rows from empty model. [#2128](https://github.com/mixxxdj/mixxx/pull/2128) -* Fix high DPI scaling of RGB overview waveforms. [#2090](https://github.com/mixxxdj/mixxx/pull/2090) -* Fix for OpenGL SL detection on macOS. [#9653](https://github.com/mixxxdj/mixxx/issues/9653) -* Fix OpenGL ES detection. [#9636](https://github.com/mixxxdj/mixxx/issues/9636) -* Fix FX1/2 buttons missing Mic unit in Deere (64 samplers). [#9703](https://github.com/mixxxdj/mixxx/issues/9703) -* Tango64: Re-enable 64 samplers. [#2223](https://github.com/mixxxdj/mixxx/pull/2223) -* Numark DJ2Go re-enable note-off for deck A cue button. [#2087](https://github.com/mixxxdj/mixxx/pull/2087) -* Replace Flanger with QuickEffect in keyboard mapping. [#2233](https://github.com/mixxxdj/mixxx/pull/2233) - -## [2.2.1](https://launchpad.net/mixxx/+milestone/2.2.1) (2019-04-22) - -* Include all fixes from Mixxx 2.1.7 and 2.1.8 -* Fix high CPU usage on MAC due to preview column [#9574](https://github.com/mixxxdj/mixxx/issues/9574) -* Fix HID controller output on Windows with common-hid-packet-parser.js -* Fix rendering slow down by not using QStylePainter in WSpinny [#8419](https://github.com/mixxxdj/mixxx/issues/8419) -* Fix broken Mic mute button [#9387](https://github.com/mixxxdj/mixxx/issues/9387) -* added quick effect enable button to the control picker menu -* Fix Cover Window close issue with empty cover arts -* Fix Numark Mixtrack 3 mapping. [#2057](https://github.com/mixxxdj/mixxx/pull/2057) - -## [2.2.0](https://launchpad.net/mixxx/+milestone/2.2.0) (2018-12-17) - -### General - -* Update from Qt4 to Qt5. -* Use Qt5's automatic high DPI scaling (and remove the old - scaling option from the preferences). -* Vectorize remaining raster graphics for better HiDPI support. - -### Effects - -* Add mix mode switch (Dry/Wet vs Dry+Wet) for effect units. -* Add support for LV2 effects plugins (currently no way to show plugin GUIs). -* Add preference option for selecting which effects are shown in the - list of available effects in the main window (all LV2 effects are - hidden by default and must be explicitly enabled by users). - -### Skins - -* Add 8 sampler and small sampler options to LateNight. -* Add key / BPM expansion indicators to Deere decks. -* Add skin settings menu to LateNight. - -### Controllers - -* Add controller mapping for Numark Mixtrack Platinum. -* Update controller mapping for Numark N4. -* Add spinback and break for Vestax VCI-400 mapping. - -### Miscellaneous - -* Add preference option to adjust the play position marker of - scrolling waveforms. -* Add preference option to adjust opacity of beatgrid markers on - scrolling waveforms. -* Support IRC/AIM/ICQ broadcast metadata. - -## [2.1.8](https://launchpad.net/mixxx/+milestone/2.1.8) (2019-04-07) - -* Fix a rare chance for a corrupt track file while writing metadata in out of disk situations. [lp:1815305](https://bugs.launchpad.net/mixxx/+bug/1815305) -* Fix export of BPM track file metadata. [#9593](https://github.com/mixxxdj/mixxx/issues/9593) -* Fix sending of broadcast metadata with TLS enabled libshout 2.4.1. [#9599](https://github.com/mixxxdj/mixxx/issues/9599) -* Fix resdicovering purged tracks in all cases. [#9616](https://github.com/mixxxdj/mixxx/issues/9616) -* Fix dropping track from OSX Finder. [#9620](https://github.com/mixxxdj/mixxx/issues/9620) - -## [2.1.7](https://launchpad.net/mixxx/+milestone/2.1.7) (2019-01-15) - -* Fix syncing to doublespeed [#9549](https://github.com/mixxxdj/mixxx/issues/9549) -* Fix issues when changing beats of a synced track [#9550](https://github.com/mixxxdj/mixxx/issues/9550) -* Fix direction of pitch bend buttons when inverting rate slider [#9284](https://github.com/mixxxdj/mixxx/issues/9284) -* Use first loaded deck if no playing deck is found [#9397](https://github.com/mixxxdj/mixxx/issues/9397) -* Encode file names correctly on macOS [lp:1776949](https://bugs.launchpad.net/mixxx/+bug/1776949) - -## [2.1.6](https://launchpad.net/mixxx/+milestone/2.1.6) (2018-12-23) - -* Fix crash when loading a Qt5 Soundsource / Vamp Plug-In. [#9324](https://github.com/mixxxdj/mixxx/issues/9324) -* Validate effect parameter range. [lp:1795234](https://bugs.launchpad.net/mixxx/+bug/1795234) -* Fix crash using the bpm_tap button without a track loaded. [#9512](https://github.com/mixxxdj/mixxx/issues/9512) -* Fix possible crash after ejecting a track. [#9513](https://github.com/mixxxdj/mixxx/issues/9513) -* Fix wrong bitrate reported for faulty mp3 files. [#9389](https://github.com/mixxxdj/mixxx/issues/9389) -* Fix Echo effect syncing [#9442](https://github.com/mixxxdj/mixxx/issues/9442) -* Fix iTunes context menu [#9484](https://github.com/mixxxdj/mixxx/issues/9484) -* Fix loading the wrong track after delete search and scroll. [#9519](https://github.com/mixxxdj/mixxx/issues/9519) -* Improve search bar timing. [#8665](https://github.com/mixxxdj/mixxx/issues/8665) -* Fix quoted search sentence. [#9396](https://github.com/mixxxdj/mixxx/issues/9396) -* Fix loading a track formerly not existing. [#9492](https://github.com/mixxxdj/mixxx/issues/9492) -* Fix importing m3u files with blank lines. [#9535](https://github.com/mixxxdj/mixxx/issues/9535) -* Fix position in sampler overview waveforms. [#9096](https://github.com/mixxxdj/mixxx/issues/9096) -* Don't reset rate slider, syncing a track without a beatgrid. [#9391](https://github.com/mixxxdj/mixxx/issues/9391) -* Clean up iTunes track context menu. [#9488](https://github.com/mixxxdj/mixxx/issues/9488) -* Collapsed sampler are not analyzed on startup. [#9502](https://github.com/mixxxdj/mixxx/issues/9502) -* search for decoration characters like "˚". [#9517](https://github.com/mixxxdj/mixxx/issues/9517) -* Fix cue button blinking after pressing eject on an empty deck. [#9543](https://github.com/mixxxdj/mixxx/issues/9543) - -## [2.1.5](https://launchpad.net/mixxx/+milestone/2.1.5) (2018-10-28) - -* Code signing for Windows builds. [#8309](https://github.com/mixxxdj/mixxx/issues/8309) -* Fix crash on exit when preferences is open. [#9438](https://github.com/mixxxdj/mixxx/issues/9438) -* Fix crash when analyzing corrupt MP3s. [#9443](https://github.com/mixxxdj/mixxx/issues/9443) -* Fix crash when importing metadata from MusicBrainz. [#9456](https://github.com/mixxxdj/mixxx/issues/9456) -* Library search fixes when single quotes are used. [#9395](https://github.com/mixxxdj/mixxx/issues/9395) [#9419](https://github.com/mixxxdj/mixxx/issues/9419) -* Fix scrolling waveform on Windows with WDM-KS sound API. [lp:1729345](https://bugs.launchpad.net/mixxx/+bug/1729345) -* Fix right clicking on beatgrid alignment button in Tango and LateNight skins. [#9471](https://github.com/mixxxdj/mixxx/issues/9471) -* Improve speed of importing iTunes library. [#9400](https://github.com/mixxxdj/mixxx/issues/9400) -* Add 2 deck mapping for DJTechTools MIDI Fighter Twister. - -## [2.1.4](https://launchpad.net/mixxx/+milestone/2.1.4) (2018-08-29) - -Fix track selection not getting shown in the track -table on Windows. There are no changes to the -source code, but the Jenkins build configuration -was changed to delete the Jenkins workspace before -each build. [lp:1751482](https://bugs.launchpad.net/mixxx/+bug/1751482) - -## [2.1.3](https://launchpad.net/mixxx/+milestone/2.1.3) (2018-08-20) - -Fix a severe performance regression on Windows: -[Mixxx 2.1.2 running much slower than 2.1.1](https://mixxx.org/forums/viewtopic.php?f=3&t=12082) - -## [2.1.2](https://launchpad.net/mixxx/+milestone/2.1.2) (2018-08-10) - -Yet another bugfix release of Mixxx 2.1. -Here is a quick summary of what is new in Mixxx 2.1.2: - -* Allow maximum deck speed of 4x normal. -* Don't always quantize hotcues, a 2.1.1 regression. [#9345](https://github.com/mixxxdj/mixxx/issues/9345) -* Fix artifacts using more than 32 samplers. [#9363](https://github.com/mixxxdj/mixxx/issues/9363) -* store No EQ and Filter persistently. [#9376](https://github.com/mixxxdj/mixxx/issues/9376) -* Pad unreadable samples with silence on cache miss. [#9346](https://github.com/mixxxdj/mixxx/issues/9346) -* Fixing painting of preview column for Qt5 builds. [#9337](https://github.com/mixxxdj/mixxx/issues/9337) -* LateNight: Fix play button right click. [#9384](https://github.com/mixxxdj/mixxx/issues/9384) -* LateNight: Added missing sort up/down buttons. -* Fix sampler play button tooltips. [#9358](https://github.com/mixxxdj/mixxx/issues/9358) -* Shade: remove superfluid margins and padding in sampler.xml. [#9310](https://github.com/mixxxdj/mixxx/issues/9310) -* Deere: Fix background-color code. -* ITunes: Don't stop import in case of duplicated Playlists. [#9394](https://github.com/mixxxdj/mixxx/issues/9394) - -## [2.1.1](https://launchpad.net/mixxx/+milestone/2.1.1) (2018-06-13) - -After two months it is time to do a bugfix release of Mixxx 2.1. -Here is a quick summary of what is new in Mixxx 2.1.1: - -* Require Soundtouch 2.0 to avoid segfault. [#8534](https://github.com/mixxxdj/mixxx/issues/8534) -* Improved skins including library view fix. [#9317](https://github.com/mixxxdj/mixxx/issues/9317) [#9297](https://github.com/mixxxdj/mixxx/issues/9297) [#9239](https://github.com/mixxxdj/mixxx/issues/9239) -* Fix crash when importing ID3v2 APIC frames. [#9325](https://github.com/mixxxdj/mixxx/issues/9325) -* Synchronize execution of Vamp analyzers. [#9085](https://github.com/mixxxdj/mixxx/issues/9085) -* DlgTrackInfo: Mismatching signal/slot connection. -* Detect M4A decoding errors on Windows. [#9266](https://github.com/mixxxdj/mixxx/issues/9266) -* Fix spinback inertia effect. -* Fix decoding fixes and upgrade DB schema. [#9255](https://github.com/mixxxdj/mixxx/issues/9255) [#9275](https://github.com/mixxxdj/mixxx/issues/9275) -* Fix integration of external track libraries. [#9264](https://github.com/mixxxdj/mixxx/issues/9264) -* Fix memory leak when loading cover art. [#9267](https://github.com/mixxxdj/mixxx/issues/9267) -* Fix clearing of ReplayGain gain/ratio in file tags. [#9256](https://github.com/mixxxdj/mixxx/issues/9256) -* Fix crash when removing a quick link. [#8270](https://github.com/mixxxdj/mixxx/issues/8270) -* Fidlib: Thread-safe and reentrant generation of filters. [#9247](https://github.com/mixxxdj/mixxx/issues/9247) -* Fix unresponsive scrolling through crates & playlists using encoder. [#8941](https://github.com/mixxxdj/mixxx/issues/8941) -* Swap default values for temp/perm rate changes. [#9243](https://github.com/mixxxdj/mixxx/issues/9243) - -## [2.1.0](https://launchpad.net/mixxx/+milestone/2.1.0) (2018-04-15) - -After two years of hard work, we are pleased to announce Mixxx 2.1. We -have overhauled the effects system, redesigned the skins, added and improved -lots of controller mappings, rewrote the audio file decoders twice, and of -course fixed a bunch of bugs. Download it! - -Here is a quick summary of what is new in Mixxx 2.1.0: - -* Graphical interface scales for high resolution screens -* Overhauled Deere and LateNight skins -* New Tango skin -* Effects are synchronized to the tempo -* Effects are processed post-fader and post-crossfader and can be previewed in headphones -* One metaknob per effect with customizable parameter control for intuitive use of effect chains -* Nine new effects: Autopan, Biquad Equalizer, Biquad Full Kill Equalizer, Loudness Contour, Metronome, Parametric Equalizer, Phaser, Stereo Balance, Tremolo -* Loaded effects and their parameters are saved and restored when Mixxx restarts -* More transparent sounding equalizers (Biquad Equalizer and Biquad Full Kill Equalizer) -* Improved scratching sounds with jog wheels, vinyl control, and dragging waveforms with the mouse -* Simplified looping and beatjump controls -* Configurable rows of 8 samplers with up to 8 rows available for a total of 64 samplers -* Files loaded to samplers are reloaded when Mixxx restarts -* Improved volume normalization algorithm (EBU-R 128) -* Filter library table by crates -* Sort musical keys in library table by circle of fifths -* Write metadata tags back to audio files -* New JavaScript library for controller mapping -* Configure multiple Internet broadcasting stations and use multiple stations at the same time -* Broadcast and record microphones with direct monitoring and latency compensation -* Broadcast and record from an external mixer -* Booth output with independent gain knob for using sound cards with 6 output channels without an external mixer -* Prevent screensaver from starting while Mixxx is running -* CUP (Cue And Play) cue button mode -* Time remaining and time elapsed now take into account the tempo fader -* Clicking cover art now shows it full size in a separate window -* and of course, lots and lots of bug fixes. - -Here are controllers with mappings that have been added or updated since the 2.0 -release. Mappings marked with an asterisk (*) have been updated for the new -effects interface: - -* American Audio VMS2 -* American Audio VMS4 -* Allen & Heath Xone K2/K1* -* Behringer CMD Micro -* Behringer CMD MM1* -* Behringer CMD Studio 4a -* Denon MC4000* -* Denon MC6000 Mk2* -* FaderFox DJ2 -* Hercules DJ Console 4-Mx* -* Hercules DJ Control MP3 LE / Glow -* Hercules DJ Control Compact -* Hercules P32* -* Ion Discover DJ -* Korg Nanokontrol 2 -* Korg KAOSS DJ -* M-Audio Xponent -* Native Instruments Traktor Kontrol S4 Mk2* -* Novation Launchpad Mk1 & Mk2 -* Novation Twitch -* Numark Mixtrack Pro 3 & Numark Mixtrack 3* -* Pioneer DDJ-SB2* -* Pioneer DDJ-SX* -* Reloop Beatmix 2 -* Reloop Beatmix 4 -* Reloop Digital Jockey 3 ME -* Reloop Terminal Mix 2 -* Reloop Terminal Mix 4 -* Vestax VCI-100 Mk2 -* Vestax Typhoon - -For users upgrading from older versions of Mixxx, we have a few important -announcements. First, if you are using Windows, you will have to uninstall any -old versions of Mixxx before you can install 2.1. How to uninstall Mixxx -varies on different versions of Windows: - -* Windows Vista, 7, and 8: [Start > Control Panel > Programs > Uninstall a - Program](https://support.microsoft.com/en-us/help/2601726) -* Windows 10: [Start > Control Panel > Programs > Programs And Features > - look for Mixxx > Uninstall](https://support.microsoft.com/en-gb/help/4028054/windows-repair-or-remove-programs-in-windows-10) - -If you are upgrading from an older version of Mixxx and have MP3 files in -your library, we have another important announcement. The good news is that we -fixed a bug where the waveforms and audio playback of MP3 files were -misaligned. The bad news is that we have no way of knowing which MP3 files were -affected or how much the offset was. That means that waveforms, beatgrids, -cues, and loops from older versions of Mixxx may be offset by an unknown amount -for any MP3 file. Only MP3 files were affected by this bug; other audio file -types are unaffected. You can either correct your beatgrids and cue points -manually for each track, or you can clear this information for all MP3s and -start fresh. Regardless, we recommend clearing the waveforms for all MP3 -files. To clear these, type "location:mp3" into the library search bar, press -Control + A to select all tracks, right click, and select the information you -want to clear from the menu. - -In the works for Mixxx 2.2, we have a big redesign of the library GUI. Along -with that will come saving & restoring search queries plus nested crates. -We are also planning on adding support for saving and loading custom effect -chain presets with the ability to import and export them to share online. - -Want to help make Mixxx even more awesome? The biggest thing we need is more -people. You do not need to be a programmer to help out. Giving feedback on the -design of new features as they are being made is very valuable. Refer to the -Testing page on the wiki for more information on how to get involved with that. -Reporting bugs and telling us your ideas on the Launchpad bug tracker is a big -help too! We cannot fix problems we do not know about, so please let us know if -you find any issues with Mixxx. If you would like to help translate Mixxx into -another language, refer to the Internationalization wiki page. Of course, more -programmers could always help. Read the Developer Documentation on the wiki for -tips on getting started contributing code to Mixxx. - -We hope you have as much fun with Mixxx as we do! - -For a full list of new features and bugfixes, check out the -[2.1.0 milestone on Launchpad](https://launchpad.net/mixxx/+milestone/2.1.0). - -## [2.0.0](https://launchpad.net/mixxx/+milestone/2.0.0) (2015-12-31) - -* 4 Decks with Master Sync -* New Effects Framework with 4 Effect Units and 5 Built-in Effects: - * Flanger, Bit Crusher, Reverb, Echo, Filter - * More to come! -* Configurable, Resizable User Interface with 3 Brand New Skins -* Cover Art Display -* Music Key Detection and Shifting -* Vinyl Audio Pass-Through -* 4 Microphone inputs and 4 Auxiliary inputs -* MIDI Mapping GUI and Improved Learning Wizard -* MusicBrainz metadata fetching -* RGB Musical Waveforms -* Hundreds of Bug Fixes and Improvements -* New Pitch-Independent Algorithm for Better-Sounding Key-lock - -For a full list of new features and bugfixes, check out the -[2.0.0 milestone on Launchpad](https://launchpad.net/mixxx/+milestone/2.0.0). diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b836179f73..019d5fea65f 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,66 @@ 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`") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") + message( + FATAL_ERROR + "Did you build the Mixxx build environment as described here: https://github.com/mixxxdj/mixxx/wiki/Compiling-dependencies-for-macOS-arm64?" + ) + elseif(CMAKE_BUILD_TYPE MATCHES "Debug") + 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(UNIX AND NOT APPLE) + # Linux, BSD, Solaris, Minix + if(EXISTS "/etc/debian_version") # exists also on Ubuntu and Mint + message( + FATAL_ERROR + "Did you install the required Debian dev packages via `${CMAKE_SOURCE_DIR}/tools/debian_buildenv.sh`?" + ) + elseif(EXISTS "/etc/redhat-release") # exists also on Fedora Mageia Madndriva Alma CentOS + message( + FATAL_ERROR + "Did you install the required RPM dev packages via `${CMAKE_SOURCE_DIR}/tools/rpm_buildenv.sh`?" + ) + else() + message( + FATAL_ERROR + "Did you install the equivalent dev packages listed in `${CMAKE_SOURCE_DIR}/tools/debian_buildenv.sh`?" + ) 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?") 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 +116,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 +151,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 +167,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 +186,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 +235,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 +255,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 +273,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 +310,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 +330,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 +351,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 +373,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 +403,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 +418,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 +434,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 +450,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 +492,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 +534,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 +575,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 +622,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 +634,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 +704,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 +744,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 +764,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 +774,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 +796,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 +814,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 +828,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 +886,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 +894,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 +902,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 +919,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 +956,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 @@ -796,6 +1073,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/effects/backends/builtin/linkwitzriley8eqeffect.cpp src/effects/backends/builtin/loudnesscontoureffect.cpp src/effects/backends/builtin/metronomeeffect.cpp + src/effects/backends/builtin/metronomeclick.cpp src/effects/backends/builtin/moogladder4filtereffect.cpp src/effects/backends/builtin/compressoreffect.cpp src/effects/backends/builtin/parametriceqeffect.cpp @@ -946,6 +1224,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/itunes/itunesfeature.cpp src/library/itunes/itunesimporter.cpp src/library/itunes/itunesplaylistmodel.cpp + src/library/itunes/itunestrackmodel.cpp src/library/itunes/itunesxmlimporter.cpp src/library/library_prefs.cpp src/library/library.cpp @@ -982,11 +1261,12 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/sidebarmodel.cpp src/library/starrating.cpp src/library/tabledelegates/bpmdelegate.cpp + src/library/tabledelegates/checkboxdelegate.cpp src/library/tabledelegates/colordelegate.cpp src/library/tabledelegates/coverartdelegate.cpp + src/library/tabledelegates/keydelegate.cpp src/library/tabledelegates/locationdelegate.cpp src/library/tabledelegates/multilineeditdelegate.cpp - src/library/tabledelegates/playcountdelegate.cpp src/library/tabledelegates/previewbuttondelegate.cpp src/library/tabledelegates/stardelegate.cpp src/library/tabledelegates/stareditor.cpp @@ -1145,6 +1425,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/track/tracknumbers.cpp src/track/trackrecord.cpp src/track/trackref.cpp + src/util/autofilereloader.cpp src/util/battery/battery.cpp src/util/cache.cpp src/util/clipboard.cpp @@ -1172,6 +1453,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 @@ -1198,7 +1480,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 @@ -1248,6 +1529,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/widget/findonwebmenuservices/findonwebmenulastfm.cpp src/widget/findonwebmenuservices/findonwebmenusoundcloud.cpp src/widget/hexspinbox.cpp + src/widget/hotcuedrag.cpp src/widget/paintable.cpp src/widget/wanalysislibrarytableview.cpp src/widget/wbasewidget.cpp @@ -1291,6 +1573,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/widget/wnumberrate.cpp src/widget/woverview.cpp src/widget/wpixmapstore.cpp + src/widget/wplaybutton.cpp src/widget/wpushbutton.cpp src/widget/wraterange.cpp src/widget/wstarratingaction.cpp @@ -1298,6 +1581,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/widget/wscrollable.cpp src/widget/wsearchlineedit.cpp src/widget/wsearchrelatedtracksmenu.cpp + src/widget/wsettingscheckboxlabel.cpp src/widget/wsingletoncontainer.cpp src/widget/wsizeawarestack.cpp src/widget/wskincolor.cpp @@ -1321,10 +1605,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 @@ -1332,6 +1615,7 @@ set(MIXXX_LIB_PRECOMPILED_HEADER src/control/control.h src/control/controlaudiotaperpot.h src/control/controlbehavior.h + src/control/controlbuttonmode.h src/control/controlcompressingproxy.h src/control/controleffectknob.h src/control/controlencoder.h @@ -1441,7 +1725,6 @@ set(MIXXX_LIB_PRECOMPILED_HEADER src/util/rampingvalue.h src/util/rangelist.h src/util/readaheadsamplebuffer.h - src/util/reference.h src/util/regex.h src/util/rescaler.h src/util/ringdelaybuffer.h @@ -1449,7 +1732,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 @@ -1468,7 +1750,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 @@ -1482,71 +1763,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/") @@ -1565,24 +1863,38 @@ 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 ) + + option(IOS_ITUNES_LIBRARY "Native iOS music library integration" ON) + if(IOS_ITUNES_LIBRARY) + target_sources( + mixxx-lib + PRIVATE + src/library/itunes/itunesiosassetexporter.mm + src/library/itunes/itunesiosimporter.mm + src/library/itunes/itunesiostrackresolver.cpp + ) + target_link_libraries(mixxx-lib PRIVATE "-weak_framework MediaPlayer") + target_compile_definitions(mixxx-lib PUBLIC __IOS_ITUNES_LIBRARY__) + endif() 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__) @@ -1591,15 +1903,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() @@ -1615,7 +1929,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) @@ -1623,38 +1940,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() @@ -1667,19 +2006,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() @@ -1702,26 +2055,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__) @@ -1762,7 +2134,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 @@ -1773,7 +2145,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() @@ -1784,8 +2156,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}") @@ -1794,29 +2166,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() @@ -1826,7 +2213,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) @@ -1838,49 +2228,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 @@ -1891,19 +2269,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 @@ -1911,8 +2285,7 @@ install( FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_CURRENT_SOURCE_DIR}/COPYING" - DESTINATION - "${MIXXX_INSTALL_LICENSEDIR}" + DESTINATION "${MIXXX_INSTALL_LICENSEDIR}" ) # Documentation @@ -1920,15 +2293,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() @@ -1936,61 +2306,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\" @@ -2003,15 +2374,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() @@ -2023,7 +2392,8 @@ if(MSVC) FILES $ CONFIGURATIONS Debug RelWithDebInfo DESTINATION "${MIXXX_INSTALL_BINDIR}" - COMPONENT PDB # No spaces allowed + COMPONENT + PDB # No spaces allowed ) endif() @@ -2031,12 +2401,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() @@ -2044,135 +2420,214 @@ 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/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/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/borrowabletest.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/hotcueorderbyposition_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 @@ -2182,12 +2637,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 @@ -2195,25 +2655,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) @@ -2221,36 +2690,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") @@ -2259,24 +2736,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() @@ -2303,10 +2778,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) @@ -2320,11 +2804,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.24.3) # 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) @@ -2334,36 +2818,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=df41fe39bed9d16d27a3649d237b68edd2cdb6fc71a82cae5cd746d4e4ef6578 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} @@ -2397,25 +2907,39 @@ 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) + # Add the local include directory explicitly before linking the library + # to make sure not the system provided headers are used + target_include_directories( + mixxx-lib + BEFORE + PRIVATE "${DJINTEROP_INSTALL_DIR}/include" + ) 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() @@ -2431,10 +2955,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) @@ -2450,31 +2990,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} @@ -2483,12 +3037,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 @@ -2509,14 +3061,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() @@ -2533,7 +3094,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` @@ -2546,31 +3110,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) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + 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) @@ -2597,7 +3179,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() @@ -2613,14 +3198,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) @@ -2633,9 +3224,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() @@ -2645,23 +3238,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) @@ -2684,33 +3281,48 @@ 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() if(QML) + if(QT_KNOWN_POLICY_QTP0004) + # See: https://doc.qt.io/qt-6/qt-cmake-policy-qtp0004.html + # OLD (Qt < 6.8) requires to import qml modules with a folder e.g.: + # fragmentShader: "qrc:/shaders/rgbsignal_qml.frag.qsb" + # For using NEW, we need to back-port: + # https://github.com/qt/qtdeclarative/commit/6314d305ee0d9064ca848980ef2dab1793c191b8 + # until Qt 6.8 lands in all supported distros + qt6_policy(SET QTP0004 OLD) + endif() + add_subdirectory(res/shaders) 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) @@ -2730,13 +3342,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) @@ -2758,27 +3375,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/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/ @@ -2786,33 +3409,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 @@ -2822,45 +3458,72 @@ 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 ) + if(NOT IOS) + target_link_libraries( + mixxx-lib + PRIVATE + # network plugins + Qt${QT_VERSION_MAJOR}::QTlsBackendOpenSSLPlugin + ) + endif() + 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() @@ -2869,144 +3532,251 @@ 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 + ) + + if(NOT IOS) + install( + IMPORTED_RUNTIME_ARTIFACTS + Qt${QT_VERSION_MAJOR}::QTlsBackendOpenSSLPlugin + DESTINATION + "${MIXXX_INSTALL_DATADIR}/tls" + COMPONENT + applocal + ) + endif() 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) @@ -3015,7 +3785,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" @@ -3030,20 +3802,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) @@ -3058,51 +3826,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}") @@ -3116,9 +3897,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() @@ -3127,9 +3910,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( @@ -3137,13 +3934,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 @@ -3179,7 +3994,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 ) @@ -3194,13 +4010,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) @@ -3220,12 +4038,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() @@ -3234,7 +4054,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 @@ -3243,11 +4066,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() @@ -3264,7 +4090,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) @@ -3276,20 +4108,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) @@ -3306,7 +4146,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() @@ -3338,12 +4181,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 @@ -3354,7 +4205,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) @@ -3363,11 +4213,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) @@ -3384,8 +4237,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() @@ -3413,26 +4273,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}") @@ -3440,54 +4326,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 - ) + 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 + 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 + ) + endif() + if(QML) + target_compile_definitions(mixxx-qml-lib PUBLIC __STEM__) + 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) @@ -3506,15 +4385,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() @@ -3524,55 +4414,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() @@ -3583,16 +4501,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 @@ -3601,10 +4526,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__) @@ -3615,19 +4546,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() @@ -3636,12 +4573,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) @@ -3650,7 +4592,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() @@ -3659,13 +4601,16 @@ else() check_include_file_cxx(gsl/gsl HAVE_GSL_GSL) if(NOT HAVE_GSL_GSL) unset(HAVE_GSL_GSL CACHE) # unset cache to re-evaluate this until it succeeds. check_include_file_cxx() has no REQUIRED flag. - message(FATAL_ERROR "ms-gsl deveopment headers (libmsgsl-dev) not found") + message(FATAL_ERROR "ms-gsl development headers (libmsgsl-dev) not found") 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__) @@ -3679,40 +4624,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 @@ -3723,15 +4638,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() @@ -3740,16 +4658,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__) @@ -3760,18 +4685,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__) @@ -3782,34 +4712,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") @@ -3826,7 +4778,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/") @@ -3835,86 +4787,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/CONTRIBUTING.md b/CONTRIBUTING.md index aac82d3bc42..f21860c8018 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,15 @@ Table of Contents ## Orientation -We have lots more helpful information for users and developers on the [Mixxx wiki](https://mixxx.org/wiki/doku.php/start), including [build instructions](https://mixxx.org/wiki/doku.php/start#compile_mixxx_from_source_code). +We have lots more helpful information for users and developers on the [Mixxx wiki](https://github.com/mixxxdj/mixxx/wiki) and elsewhere, including [build instructions](https://github.com/mixxxdj/mixxx/wiki#compile-mixxx-from-source-code). + +### Important Guidelines and Policies + +* [Git Workflow](#git-workflow) +* [Coding Guidelines](https://github.com/mixxxdj/mixxx/wiki/Coding-Guidelines) & [Setting up `pre-commit`](#pre-commit) +* [Minimum Requirements Policy](https://github.com/mixxxdj/mixxx/wiki/Coding-Guidelines) +* [Internationalization Workflow](https://github.com/mixxxdj/mixxx/wiki/Internationalization) +* [Release Checklist](https://github.com/mixxxdj/mixxx/wiki/Release-Checklist-2.5.0) ### Git Repositories @@ -26,6 +34,20 @@ All of these are automatically built and deployed by [GitHub Actions](https://gi ## Git Workflow +### `pre-commit` + +* Install [pre-commit](https://pre-commit.com/#install) to automatically ensure that your commits comply with our code style for both C++ and JavaScript. This saves time reviewing so we don't have to point out nitpicky style issues. Once you have pre-commit installed on your computer, set it up in your local Git repository: + + cd /path/to/your/git/repo + pre-commit install + pre-commit install -t pre-push + + If you have a problems with a particular hook, you can use the `SKIP` environment variable to disable hooks: + + SKIP=clang-format,end-of-file-fixer git commit + + This can also be used to separate logic changes and autoformatting into two subsequent commits. Using the SKIP environment variable is preferable to using `git commit --no-verify` (which also disables the checks) because it won't prevent catching other, unrelated issues. + ### All Contributors * Each feature/bug fix should be done on its own Git branch so they can be reviewed and merged independently. Refer to [Using Git](https://github.com/mixxxdj/mixxx/wiki/using-git) for how to do this. Please ask for help on [Zulip](https://mixxx.zulipchat.com/) if you have questions about using Git after reading that page. @@ -48,19 +70,8 @@ All of these are automatically built and deployed by [GitHub Actions](https://gi Refer to [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) for more details. -* Install [pre-commit](https://pre-commit.com/#install) to automatically ensure that your commits comply with our code style for both C++ and JavaScript. This saves time reviewing so we don't have to point out nitpicky style issues. Once you have pre-commit installed on your computer, set it up in your local Git repository: - - cd /path/to/your/git/repo - pre-commit install - pre-commit install -t pre-push - - If you have a problems with a particular hook, you can use the SKIP environment variable to disable hooks: - - SKIP=clang-format,end-of-file-fixer git commit - - This can also be used to separate logic changes and autoformatting into two subsequent commits. Using the SKIP environment variable is preferable to using `git commit --no-verify` (which also disables the checks) because it won't prevent catching other, unrelated issues. - * Generally, prefer merging over rebasing. Do not rebase unless you have discussed that with whoever is reviewing the pull request. When you rebase a branch with an open pull request, it is no longer possible to distinguish your latest changes from already reviewed parts, resulting in unnecessary extra work for the reviewer. Comments made directly to a single commit will be lost. Rebased commits are likely not tested and there is a risk that building fails in a later `git bisect` run. If you want to correct minor mistakes with a rebase within a few minutes of pushing commits, that is okay as long as no one has started reviewing those commits yet. A `git commit --amend` is possible at any time as long the commit has the limited scope of one topic. +* If you plan to rebase your branch before merge to eliminate forth and back commits or such, you can commit them with the --fixup option or manually add "fixup!" as the first word in the commit message. This prevents mergeing to upstream before the rebase. * If you are helping with someone else's pull request that is not yet merged, open a pull request targeted at their fork. Leave a comment on the upstream pull request (which targets mixxxdj/mixxx) with a link to your pull request so other Mixxx contributors are aware of your changes. * Low risk bug fixes should be targeted at the stable branch (e.g., `2.3`). However, bug fixes for the stable branches must have a direct impact on users. If you spot a minor bug reading the code or only want to clean up the code, target that at the `main` or beta branch. * Controller mappings should be targeted at the stable branch unless they use features that are new in the beta or `main` branch. @@ -80,6 +91,6 @@ Mixxx core team members are contributors who have write access to the [upstream * If there is disagreement about changes in a pull request, do not merge it until a consensus has been reached. * Check CI to ensure builds work and tests pass before merging. If CI timed out, either manually restart it or build the branch and run the tests locally before merging. * When you merge a pull request to a stable branch, merge the stable branch to the beta branch afterwards. If you merge a pull request to a beta branch, merge the beta branch to `main` afterwards. When backporting, cherry-pick or rebase rather than merge. -* Merge PRs using a merge , to keep the original commits valid. Keep the default commit message "Merge pull request ..." with the reference to the pull request. In case where the PR contains broken (non-building) commits, back-and-forth commits or commits without a meaningful commit message that are not worth keeping, ask the author to squash the commits before merge or use squash-and-merge if the author is not fluent in git. +* Merge PRs using a merge, to keep the original commits valid. Keep the default commit message "Merge pull request ..." with the reference to the pull request. In case where the PR contains broken (non-building) commits, back-and-forth commits or commits without a meaningful commit message that are not worth keeping, ask the author to squash the commits before merge. Alternatively you may ask the contributor to check "Allow edits and access to secrets by maintainers". Then you can squash locally or use the `/softfix` comment to squash remotely. See [Softfix](https://github.com/daschuer/softfix/?tab=readme-ov-file#softfix-a-pull-request) * Default to open; only post in the private Zulip stream for discussions that have a reason to be private. Most of the time, post to a public Zulip stream so anyone can participate in the discussion. * When Mixxx participates in Google Summer of Code, you may volunteer as a mentor if you like. 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/README.md b/README.md index 4e78740d6a0..09a41fb37a2 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ For help using Mixxx, there are a variety of options: - [Mixxx manual][manual] - [Mixxx wiki][wiki] -- [Frequently Asked Questions][FAQ] - [Hardware Compatibility] - [Creating Skins] @@ -103,7 +102,6 @@ license. [blog]: https://mixxx.org/news/ [manual]: https://manual.mixxx.org/ [wiki]: https://github.com/mixxxdj/mixxx/wiki -[faq]: https://github.com/mixxxdj/mixxx/wiki/Faq [visualstudio2019]: https://docs.microsoft.com/visualstudio/install/install-visual-studio?view=vs-2019 [easybugs]: https://github.com/mixxxdj/mixxx/issues?q=is%3Aopen+is%3Aissue+label%3Aeasy [creating skins]: https://mixxx.org/wiki/doku.php/Creating-Skins 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 7e907f6794b..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. @@ -48,20 +48,26 @@ include(IsStaticLibrary) find_package(PkgConfig QUIET) if(PkgConfig_FOUND) if(UNIX AND NOT APPLE) - # priorize 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}") + # 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}" + ) 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 9c83d3b6a89..2ccabc533c5 100644 --- a/packaging/CPackConfig.cmake +++ b/packaging/CPackConfig.cmake @@ -15,12 +15,12 @@ set(CPACK_PACKAGE_FILE_NAME "mixxx-${PACKAGE_VERSION}") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-source") # The upstream version must not contain hyphen -# . for nomal versioning + for advance and ~ for decline the version +# . for normal versioning + for advance and ~ for decline the version # 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..53565285414 100644 --- a/res/controllers/Behringer BCR2000.midi.xml +++ b/res/controllers/Behringer BCR2000.midi.xml @@ -1,5 +1,5 @@ - + Behringer BCR2000 Christian @@ -7,1005 +7,10 @@ - - - - - - Pushencoder G1.1 Encoder - 0xB0 - 0x01 - - - - BCR2000.input - - - Pushencoder G1.2 Encoder - 0xB0 - 0x02 - - - - BCR2000.input - - - Pushencoder G1.3 Encoder - 0xB0 - 0x03 - - - - BCR2000.input - - - Pushencoder G1.4 Encoder - 0xB0 - 0x04 - - - - BCR2000.input - - - Pushencoder G1.5 Encoder - 0xB0 - 0x05 - - - - BCR2000.input - - - Pushencoder G1.6 Encoder - 0xB0 - 0x06 - - - - BCR2000.input - - - Pushencoder G1.7 Encoder - 0xB0 - 0x07 - - - - BCR2000.input - - - Pushencoder G1.8 Encoder - 0xB0 - 0x08 - - - - BCR2000.input - - - Pushencoder G1.1 Button - 0xB0 - 0x21 - - - - BCR2000.input - - - Pushencoder G1.2 Button - 0xB0 - 0x22 - - - - BCR2000.input - - - Pushencoder G1.3 Button - 0xB0 - 0x23 - - - - BCR2000.input - - - Pushencoder G1.4 Button - 0xB0 - 0x24 - - - - BCR2000.input - - - Pushencoder G1.5 Button - 0xB0 - 0x25 - - - - BCR2000.input - - - Pushencoder G1.6 Button - 0xB0 - 0x26 - - - - BCR2000.input - - - Pushencoder G1.7 Button - 0xB0 - 0x27 - - - - BCR2000.input - - - Pushencoder G1.8 Button - 0xB0 - 0x28 - - - - BCR2000.input - - - - - Pushencoder G2.1 Encoder - 0xB0 - 0x09 - - - - BCR2000.input - - - Pushencoder G2.2 Encoder - 0xB0 - 0x0A - - - - BCR2000.input - - - Pushencoder G2.3 Encoder - 0xB0 - 0x0B - - - - BCR2000.input - - - Pushencoder G2.4 Encoder - 0xB0 - 0x0C - - - - BCR2000.input - - - Pushencoder G2.5 Encoder - 0xB0 - 0x0D - - - - BCR2000.input - - - Pushencoder G2.6 Encoder - 0xB0 - 0x0E - - - - BCR2000.input - - - Pushencoder G2.7 Encoder - 0xB0 - 0x0F - - - - BCR2000.input - - - Pushencoder G2.8 Encoder - 0xB0 - 0x10 - - - - BCR2000.input - - - Pushencoder G2.1 Button - 0xB0 - 0x29 - - - - BCR2000.input - - - Pushencoder G2.2 Button - 0xB0 - 0x2A - - - - BCR2000.input - - - Pushencoder G2.3 Button - 0xB0 - 0x2B - - - - BCR2000.input - - - Pushencoder G2.4 Button - 0xB0 - 0x2C - - - - BCR2000.input - - - Pushencoder G2.5 Button - 0xB0 - 0x2D - - - - BCR2000.input - - - Pushencoder G2.6 Button - 0xB0 - 0x2E - - - - BCR2000.input - - - Pushencoder G2.7 Button - 0xB0 - 0x2F - - - - BCR2000.input - - - Pushencoder G2.8 Button - 0xB0 - 0x30 - - - - BCR2000.input - - - - - Pushencoder G3.1 Encoder - 0xB0 - 0x11 - - - - BCR2000.input - - - Pushencoder G3.2 Encoder - 0xB0 - 0x12 - - - - BCR2000.input - - - Pushencoder G3.3 Encoder - 0xB0 - 0x13 - - - - BCR2000.input - - - Pushencoder G3.4 Encoder - 0xB0 - 0x14 - - - - BCR2000.input - - - Pushencoder G3.5 Encoder - 0xB0 - 0x15 - - - - BCR2000.input - - - Pushencoder G3.6 Encoder - 0xB0 - 0x16 - - - - BCR2000.input - - - Pushencoder G3.7 Encoder - 0xB0 - 0x17 - - - - BCR2000.input - - - Pushencoder G3.8 Encoder - 0xB0 - 0x18 - - - - BCR2000.input - - - Pushencoder G3.1 Button - 0xB0 - 0x31 - - - - BCR2000.input - - - Pushencoder G3.2 Button - 0xB0 - 0x32 - - - - BCR2000.input - - - Pushencoder G3.3 Button - 0xB0 - 0x33 - - - - BCR2000.input - - - Pushencoder G3.4 Button - 0xB0 - 0x34 - - - - BCR2000.input - - - Pushencoder G3.5 Button - 0xB0 - 0x35 - - - - BCR2000.input - - - Pushencoder G3.6 Button - 0xB0 - 0x36 - - - - BCR2000.input - - - Pushencoder G3.7 Button - 0xB0 - 0x37 - - - - BCR2000.input - - - Pushencoder G3.8 Button - 0xB0 - 0x38 - - - - BCR2000.input - - - - - Pushencoder G4.1 Encoder - 0xB0 - 0x19 - - - - BCR2000.input - - - Pushencoder G4.2 Encoder - 0xB0 - 0x1A - - - - BCR2000.input - - - Pushencoder G4.3 Encoder - 0xB0 - 0x1B - - - - BCR2000.input - - - Pushencoder G4.4 Encoder - 0xB0 - 0x1C - - - - BCR2000.input - - - Pushencoder G4.5 Encoder - 0xB0 - 0x1D - - - - BCR2000.input - - - Pushencoder G4.6 Encoder - 0xB0 - 0x1E - - - - BCR2000.input - - - Pushencoder G4.7 Encoder - 0xB0 - 0x1F - - - - BCR2000.input - - - Pushencoder G4.8 Encoder - 0xB0 - 0x20 - - - - BCR2000.input - - - Pushencoder G4.1 Button - 0xB0 - 0x39 - - - - BCR2000.input - - - Pushencoder G4.2 Button - 0xB0 - 0x3A - - - - BCR2000.input - - - Pushencoder G4.3 Button - 0xB0 - 0x3B - - - - BCR2000.input - - - Pushencoder G4.4 Button - 0xB0 - 0x3C - - - - BCR2000.input - - - Pushencoder G4.5 Button - 0xB0 - 0x3D - - - - BCR2000.input - - - Pushencoder G4.6 Button - 0xB0 - 0x3E - - - - BCR2000.input - - - Pushencoder G4.7 Button - 0xB0 - 0x3F - - - - BCR2000.input - - - Pushbutton G4.8 Button - 0xB0 - 0x40 - - - - BCR2000.input - - - - - Button R1C1 - 0xB0 - 0x41 - - - - BCR2000.input - - - Button R1C2 - 0xB0 - 0x42 - - - - BCR2000.input - - - Button R1C3 - 0xB0 - 0x43 - - - - BCR2000.input - - - Button R1C4 - 0xB0 - 0x44 - - - - BCR2000.input - - - Button R1C5 - 0xB0 - 0x45 - - - - BCR2000.input - - - Button R1C6 - 0xB0 - 0x46 - - - - BCR2000.input - - - Button R1C7 - 0xB0 - 0x47 - - - - BCR2000.input - - - Button R1C8 - 0xB0 - 0x48 - - - - BCR2000.input - - - - - Button R2C1 - 0xB0 - 0x49 - - - - BCR2000.input - - - Button R2C2 - 0xB0 - 0x4A - - - - BCR2000.input - - - Button R2C3 - 0xB0 - 0x4B - - - - BCR2000.input - - - Button R2C4 - 0xB0 - 0x4C - - - - BCR2000.input - - - Button R2C5 - 0xB0 - 0x4D - - - - BCR2000.input - - - Button R2C6 - 0xB0 - 0x4E - - - - BCR2000.input - - - Button R2C7 - 0xB0 - 0x4F - - - - BCR2000.input - - - Button R2C8 - 0xB0 - 0x50 - - - - BCR2000.input - - - - - Encoder R1C1 - 0xB0 - 0x51 - - - - BCR2000.input - - - Encoder R1C2 - 0xB0 - 0x52 - - - - BCR2000.input - - - Encoder R1C3 - 0xB0 - 0x53 - - - - BCR2000.input - - - Encoder R1C4 - 0xB0 - 0x54 - - - - BCR2000.input - - - Encoder R1C5 - 0xB0 - 0x55 - - - - BCR2000.input - - - Encoder R1C6 - 0xB0 - 0x56 - - - - BCR2000.input - - - Encoder R1C7 - 0xB0 - 0x57 - - - - BCR2000.input - - - Encoder R1C8 - 0xB0 - 0x58 - - - - BCR2000.input - - - - - Encoder R2C1 - 0xB0 - 0x59 - - - - BCR2000.input - - - Encoder R2C2 - 0xB0 - 0x5A - - - - BCR2000.input - - - Encoder R2C3 - 0xB0 - 0x5B - - - - BCR2000.input - - - Encoder R2C4 - 0xB0 - 0x5C - - - - BCR2000.input - - - Encoder R2C5 - 0xB0 - 0x5D - - - - BCR2000.input - - - Encoder R2C6 - 0xB0 - 0x5E - - - - BCR2000.input - - - Encoder R2C7 - 0xB0 - 0x5F - - - - BCR2000.input - - - Encoder R2C8 - 0xB0 - 0x60 - - - - BCR2000.input - - - - - Encoder R3C1 - 0xB0 - 0x61 - - - - BCR2000.input - - - Encoder R3C2 - 0xB0 - 0x62 - - - - BCR2000.input - - - Encoder R3C3 - 0xB0 - 0x63 - - - - BCR2000.input - - - Encoder R3C4 - 0xB0 - 0x64 - - - - BCR2000.input - - - Encoder R3C5 - 0xB0 - 0x65 - - - - BCR2000.input - - - Encoder R3C6 - 0xB0 - 0x66 - - - - BCR2000.input - - - Encoder R3C7 - 0xB0 - 0x67 - - - - BCR2000.input - - - Encoder R3C8 - 0xB0 - 0x68 - - - - BCR2000.input - - - - - Button Box Top Left - 0xB0 - 0x69 - - - - BCR2000.input - - - Button Box Top Right - 0xB0 - 0x6A - - - - BCR2000.input - - - Button Box Bottom Left - 0xB0 - 0x6B - - - - BCR2000.input - - - Button Box Bottom Right - 0xB0 - 0x6C - - - - BCR2000.input - - diff --git a/res/controllers/Behringer DDM4000.midi.xml b/res/controllers/Behringer DDM4000.midi.xml index 64b92d076cd..7d2b6922973 100644 --- a/res/controllers/Behringer DDM4000.midi.xml +++ b/res/controllers/Behringer DDM4000.midi.xml @@ -1,5 +1,5 @@ - + Behringer DDM4000 Christian @@ -7,755 +7,9 @@ - - - - - - Channel 1: Button P1 (High) - 0x90 - 0x00 - - - - DDM4000.input - - - Channel 1: Button P2 (Mid) - 0x90 - 0x01 - - - - DDM4000.input - - - Channel 1: Button P3 (Low) - 0x90 - 0x02 - - - - DDM4000.input - - - Channel 1: Mode - 0x90 - 0x03 - - - - DDM4000.input - - - Channel 1: Pot High - 0xB0 - 0x04 - - - - DDM4000.input - - - Channel 1: Pot Mid - 0xB0 - 0x05 - - - - DDM4000.input - - - Channel 1: Pot Low - 0xB0 - 0x06 - - - - DDM4000.input - - - Channel 1: Volume - 0xB0 - 0x07 - - - - DDM4000.input - - - Channel 1: CF Assign - 0x90 - 0x20 - - - - DDM4000.input - - - Channel 1: PFL - 0x90 - 0x3F - - - - DDM4000.input - - - - - Channel 2: Volume - 0x90 - 0x04 - - - - DDM4000.input - - - Channel 2: CF Assign - 0x90 - 0x05 - - - - DDM4000.input - - - Channel 2: PFL - 0x90 - 0x06 - - - - DDM4000.input - - - Channel 2: Mode - 0x90 - 0x07 - - - - DDM4000.input - - - Channel 2: Pot Low - 0xB0 - 0x08 - - - - DDM4000.input - - - Channel 2: Pot Mid - 0xB0 - 0x09 - - - - DDM4000.input - - - Channel 2: Pot High - 0xB0 - 0x0A - - - - DDM4000.input - - - Channel 2: Button P3 (Low) - 0xB0 - 0x0B - - - - DDM4000.input - - - Channel 2: Button P2 (Mid) - 0x90 - 0x22 - - - - DDM4000.input - - - Channel 2: Button P1 (High) - 0x90 - 0x49 - - - - DDM4000.input - - - - - Channel 3: Volume - 0x90 - 0x08 - - - - DDM4000.input - - - Channel 3: CF Assign - 0x90 - 0x09 - - - - DDM4000.input - - - Channel 3: PFL - 0x90 - 0x0A - - - - DDM4000.input - - - Channel 3: Mode - 0x90 - 0x0B - - - - DDM4000.input - - - Channel 3: Pot Low - 0xB0 - 0x0C - - - - DDM4000.input - - - Channel 3: Pot Mid - 0xB0 - 0x0D - - - - DDM4000.input - - - Channel 3: Pot High - 0xB0 - 0x0E - - - - DDM4000.input - - - Channel 3: Button P3 (Low) - 0xB0 - 0x0F - - - - DDM4000.input - - - Channel 3: Button P2 (Mid) - 0x90 - 0x24 - - - - DDM4000.input - - - Channel 3: Button P1 (High) - 0x90 - 0x53 - - - - DDM4000.input - - - - - Channel 4: Volume - 0x90 - 0x0C - - - - DDM4000.input - - - Channel 4: CF Assign - 0x90 - 0x0D - - - - DDM4000.input - - - Channel 4: PFL - 0x90 - 0x0E - - - - DDM4000.input - - - Channel 4: Mode - 0x90 - 0x0F - - - - DDM4000.input - - - Channel 4: Pot Low - 0xB0 - 0x10 - - - - DDM4000.input - - - Channel 4: Pot Mid - 0xB0 - 0x11 - - - - DDM4000.input - - - Channel 4: Pot High - 0xB0 - 0x12 - - - - DDM4000.input - - - Channel 4: Button P3 (Low) - 0xB0 - 0x13 - - - - DDM4000.input - - - Channel 4: Button P2 (Mid) - 0x90 - 0x26 - - - - DDM4000.input - - - Channel 4: Button P1 (High) - 0x90 - 0x5D - - - - DDM4000.input - - - - - Mic: Pot High - 0xB0 - 0x00 - - - - DDM4000.input - - - Mic: Pot Mid - 0xB0 - 0x01 - - - - DDM4000.input - - - Mic: Pot Low - 0xB0 - 0x02 - - - - DDM4000.input - - - Mic: Setup - 0x90 - 0x31 - - - - DDM4000.input - - - Mic: XMC On - 0x90 - 0x32 - - - - DDM4000.input - - - Mic: FX On - 0x90 - 0x33 - - - - DDM4000.input - - - Mic: Talk On - 0x90 - 0x34 - - - - DDM4000.input - - - Mic: On/Off - 0x90 - 0x35 - - - - DDM4000.input - - - - - Crossfader: Curve - 0xB0 - 0x14 - - - - DDM4000.input - - - Crossfader - 0xB0 - 0x15 - - - - DDM4000.input - - - Crossfader: A Full Freq - 0x90 - 0x17 - - - - DDM4000.input - - - Crossfader: A High - 0x90 - 0x18 - - - - DDM4000.input - - - Crossfader: A Mid - 0x90 - 0x19 - - - - DDM4000.input - - - Crossfader: A Low - 0x90 - 0x1A - - - - DDM4000.input - - - Crossfader: B Full Freq - 0x90 - 0x1B - - - - DDM4000.input - - - Crossfader: B High - 0x90 - 0x1C - - - - DDM4000.input - - - Crossfader: B Mid - 0x90 - 0x1D - - - - DDM4000.input - - - Crossfader: B Low - 0x90 - 0x1E - - - - DDM4000.input - - - Crossfader: CF On - 0x90 - 0x1F - - - - DDM4000.input - - - Crossfader: Reverse Tap - 0x90 - 0x28 - - - - DDM4000.input - - - Crossfader: Reverse Hold - 0x90 - 0x29 - - - - DDM4000.input - - - Crossfader: Bounce to MIDI Clock - 0x90 - 0x2A - - - - DDM4000.input - - - Crossfader: Beat (Left) - 0x90 - 0x2B - - - - DDM4000.input - - - Crossfader: Beat (Right) - 0x90 - 0x2C - - - - DDM4000.input - - - - - Sampler: Dry/Wet - 0xB0 - 0x03 - - - - DDM4000.input - - - Sampler: Insert - 0x90 - 0x5F - - - - DDM4000.input - - - Sampler: REC Source Right - 0x90 - 0x60 - - - - DDM4000.input - - - Sampler: REC Source Left - 0x90 - 0x61 - - - - DDM4000.input - - - Sampler: PFL - 0x90 - 0x66 - - - - DDM4000.input - - - Sampler: Sample Length (Right) - 0x90 - 0x67 - - - - DDM4000.input - - - Sampler: Sample Length (Left) - 0x90 - 0x68 - - - - DDM4000.input - - - Sampler: Bank Assign - 0x90 - 0x6C - - - - DDM4000.input - - - Sampler: Record/In - 0x90 - 0x6D - - - - DDM4000.input - - - Sampler: Bank 1 Play/Out - 0x90 - 0x6E - - - - DDM4000.input - - - Sampler: Bank 1 Mode - 0x90 - 0x71 - - - - DDM4000.input - - - Sampler: Bank 2 Play/Out - 0x90 - 0x73 - - - - DDM4000.input - - - Sampler: Bank 2 Mode - 0x90 - 0x76 - - - - DDM4000.input - - - Sampler: FX On - 0x90 - 0x78 - - - - DDM4000.input - - - Sampler: Select - 0x90 - 0x79 - - - - DDM4000.input - - - Sampler: CF Assign - 0x90 - 0x7A - - - - DDM4000.input - - - Sampler: CF Start - 0x90 - 0x7C - - - - DDM4000.input - - 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..b66337ddfb5 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() { @@ -97,6 +97,7 @@ var BCR2000 = new behringer.extension.GenericMidiController({ ], equalizerUnit: { feedback: true, + feedbackOnRelease: true, midi: { enabled: [cc, p.pushEncoderGroup2[0].button], super1: [cc, p.pushEncoderGroup2[0].encoder], @@ -192,6 +193,7 @@ var BCR2000 = new behringer.extension.GenericMidiController({ ], equalizerUnit: { feedback: true, + feedbackOnRelease: true, midi: { enabled: [cc, p.pushEncoderGroup2[4].button], super1: [cc, p.pushEncoderGroup2[4].encoder], @@ -210,6 +212,7 @@ var BCR2000 = new behringer.extension.GenericMidiController({ }], effectUnits: [{ feedback: true, + feedbackOnRelease: true, unitNumbers: [1, 3], midi: { effectFocusButton: [cc, p.buttonRow2[0]], @@ -228,6 +231,7 @@ var BCR2000 = new behringer.extension.GenericMidiController({ }, { feedback: true, + feedbackOnRelease: true, unitNumbers: [2, 4], midi: { effectFocusButton: [cc, p.buttonRow2[4]], diff --git a/res/controllers/Behringer-DDM4000-scripts.js b/res/controllers/Behringer-DDM4000-scripts.js index bf98175b9f2..00eee216f18 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,9 +199,8 @@ 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; e.LongPressButton.call(this, options); }; @@ -254,8 +253,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ } }, {type: c.Button, options: {midi: [note, 0x03], inKey: null}}, // Mode - {type: c.Button, options: {midi: [cc, 0x38], outKey: null}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x37], outKey: null}}, // Mode: Single + {type: c.Button, options: {midi: [cc, 0x38], outKey: undefined}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x37], outKey: undefined}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing @@ -284,8 +283,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ } }, {type: c.Button, options: {midi: [note, 0x07], inKey: null}}, // Mode - {type: c.Button, options: {midi: [cc, 0x42], outKey: null}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x41], outKey: null}}, // Mode: Single + {type: c.Button, options: {midi: [cc, 0x42], outKey: undefined}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x41], outKey: undefined}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing @@ -401,11 +400,11 @@ var DDM4000 = new behringer.extension.GenericMidiController({ {options: {midi: [note, 0x2A], key: null, sendShifted: true}}, // Crossfader: Bounce to MIDI Clock {options: {midi: [note, 0x2B], inKey: null}}, // Crossfader: Beat (Left) {options: {midi: [note, 0x2C], inKey: null}}, // Crossfader: Beat (Right) - {options: {midi: [cc, 0x2B], outKey: null}}, // Crossfader: Beat 1 - {options: {midi: [cc, 0x2C], outKey: null}}, // Crossfader: Beat 2 - {options: {midi: [cc, 0x2D], outKey: null}}, // Crossfader: Beat 4 - {options: {midi: [cc, 0x2E], outKey: null}}, // Crossfader: Beat 8 - {options: {midi: [cc, 0x2F], outKey: null}}, // Crossfader: Beat 16 + {options: {midi: [cc, 0x2B], outKey: undefined}}, // Crossfader: Beat 1 + {options: {midi: [cc, 0x2C], outKey: undefined}}, // Crossfader: Beat 2 + {options: {midi: [cc, 0x2D], outKey: undefined}}, // Crossfader: Beat 4 + {options: {midi: [cc, 0x2E], outKey: undefined}}, // Crossfader: Beat 8 + {options: {midi: [cc, 0x2F], outKey: undefined}}, // Crossfader: Beat 16 ] }, { // Sampler diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index cc912e64d3a..160f2afe284 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 - * @private + * @returns {object} A new prototype based on parent with the given members + * @public */ - 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,11 +1002,12 @@ * @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]); this.activeLayer = new components.ComponentContainer(); + this.inputConnections = {}; components.Component.call(this, options); }; LayerManager.prototype = deriveFrom(components.Component, { @@ -977,11 +1019,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 +1034,7 @@ /** * Retrieve the Shift layer. * - * @return {object} The Shift layer + * @returns {object} The Shift layer * @private */ shiftLayer: function() { @@ -1004,20 +1046,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 +1077,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); } /** @@ -1052,7 +1094,7 @@ , /** - * Add a component to a layer. + * Add a component to a layer, and provide an input connection for its MIDI address. * * @param {components.Component} component A component * @param {boolean} shift Target layer: Shift iff true, otherwise Default @@ -1060,18 +1102,30 @@ */ register: function(component, shift) { this.onRegistry(this.componentRegistry.register, component, shift); + if (component.midi) { + const id = findComponentId(component.midi); + if (this.inputConnections[id] === undefined) { + this.inputConnections[id] = midi.makeInputHandler( + component.midi[0], component.midi[1], this.input.bind(this)); + } + } }, /** - * Remove a component from a layer. + * Remove a component from a layer, + * and disconnect the MIDI input if not used by another component. * * @param {components.Component} component A component * @param {boolean} shift Source layer: Shift iff true, otherwise Default * @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]; + if (!this.findComponent(id)) { + this.inputConnections[id].disconnect(); + delete this.inputConnections[id]; + } }, /** @@ -1134,7 +1188,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; } @@ -1186,6 +1240,11 @@ * | | | to the hardware controller on changes. The address of the MIDI * | | | message is taken from the `midi` property of the affected * | | | component. + * | | +- feedbackOnRelease: Enable controller feedback on button release (boolean, optional) + * | | | When set to `true`, values of the buttons in this unit are sent + * | | | to the hardware controller on release, no matter if changed or not. + * | | | The address of the MIDI message is taken from the `midi` property of the + * | | | affected component. * | | +- output: Additional output definitions (optional). * | | The structure of this object is the same as the structure of * | | `midi`. Every value change of a component contained in `output` @@ -1198,6 +1257,7 @@ * | | `enabled: [0x90, 0x02]` * | | `super1: [0xB0, 0x06]` * | +- feedback: As described for equalizer unit + * | +- feedbackOnRelease: As described for equalizer unit * | +- output: As described for equalizer unit * | * +- effectUnits: An array of effect unit definitions (may be empty or omitted) @@ -1208,6 +1268,7 @@ * | | `effectFocusButton: [0xB0, 0x15]` * | | `knobs: {1: [0xB0, 0x26], 2: [0xB0, 0x25], 3: [0xB0, 0x24]}` * | +- feedback: As described for equalizer unit + * | +- feedbackOnRelease: As described for equalizer unit * | +- output: As described for equalizer unit * | +- sendShiftedFor: Type of components that send shifted MIDI messages (optional) * | When set, all components of this type within this effect unit @@ -1232,7 +1293,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 +1315,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 +1378,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 +1423,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 +1447,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 +1496,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 +1511,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 +1524,14 @@ /* Add publishers for pots */ if (definition.feedback) { - var triggers = rebindTriggers || []; - var 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 triggers = rebindTriggers || []; + const createPublisher = this.createPublisher; // `this` is bound to implementation + implementation.forEachComponent(function(source) { + if (source instanceof components.Pot) { + const publisher = createPublisher(source, publisherStorage); + const prototype = Object.getPrototypeOf(source); triggers.forEach(function(functionName) { - var delegate = prototype[functionName]; + const delegate = prototype[functionName]; if (typeof delegate === "function") { prototype[functionName] = function() { delegate.apply(this, arguments); @@ -1491,6 +1552,16 @@ publisherStorage); }); } + + /* Enable feedback on button release for configured components */ + if (definition.feedbackOnRelease) { + implementation.forEachComponent(function(component) { + if (component instanceof components.Button) { + component.triggerOnRelease = true; + } + }); + } + return implementation; }, @@ -1506,12 +1577,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 +1607,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 +1629,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 +1656,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 +1684,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-MC4000-scripts.js b/res/controllers/Denon-MC4000-scripts.js index fd272b2cb07..6989a1af41a 100644 --- a/res/controllers/Denon-MC4000-scripts.js +++ b/res/controllers/Denon-MC4000-scripts.js @@ -117,7 +117,7 @@ MC4000.Deck = function (channel) { // adds this jog value to an internal 25 sample buffer, and then sets the jog value to zero. // The engine takes the average of the 25 sample buffer, divides by 10, and adds this to the rate at // which the song is playing (e.g. determined by the pitch fader). Since the effect of this depends on many factors - // we can only really give an empirical senstivity which makes jog work "how we like it". + // we can only really give an empirical sensitivity which makes jog work "how we like it". this.jogWheel = function (channel, control, value, status, group) { var numTicks = (value < 0x40) ? value: (value - 0x80); if (engine.isScratching(this.engineChannel)) { diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index 264a3c5c6c3..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 @@ -89,13 +89,36 @@ MC7000.scratchParams = { beta: (1.0/10)/32 }; -// Sensitivity factor of the jog wheel (also depends on audio latency) -// 0.5 for half, 2 for double sensitivity - Recommendation: -// set to 0.5 with audio buffer set to 50ms -// set to 1 with audio buffer set to 25ms -// set to 3 with audio buffer set to 5ms -MC7000.jogSensitivity = 1; +// Jog wheel parameters +MC7000.jogParams = { + // Sensitivity factor of the jog wheel (also depends on audio latency) + // 0.5 for half, 2 for double sensitivity - Recommendation: + // set to 0.5 with audio buffer set to 50ms + // set to 1 with audio buffer set to 25ms + // set to 3 with audio buffer set to 5ms + sensitivity: engine.getSetting("jogSensitivity") || 1, + // Acceleration settings for the jog wheel in vinyl mode + // If enabled, the track speed will accelerate faster than the physical jogwheel movement. Be aware that the absolute track position will drift relative to the jogwheel position in this mode! + // (exponent: 0 and coefficient: 1 = no acceleration) + acceleration: { + // Toggles acceleration entirely. + enabled: engine.getSetting("jogAccelerationEnabled") || false, + // Acceleration function exponent + exponent: engine.getSetting("jogAccelerationExponent") || 0.8, + // Acceleration function scaling factor + coefficient: engine.getSetting("jogAccelerationCoefficient") || 1 + } +}; +// Parameter button settings (the orange buttons at the bottom left/right of the controller). +MC7000.parameterButtonSettings = { + // Parameter button mode. Available modes are `starsAndColor`, `beatjump` and `introOutro`. + mode: engine.getSetting("parameterButtonMode") ?? "starsAndColor", + // Whether to use the parameter buttons to change the pitch range during + // pitch play mode. If this option is enabled, the pitch change + // functionality overrides the normal parameter button mode during pitch play. + parameterButtonPitchPlayOverrideEnabled: engine.getSetting("parameterButtonPitchPlayOverrideEnabled") ?? true, +}; /*///////////////////////////////// // USER VARIABLES END // @@ -149,9 +172,6 @@ MC7000.prevVuLevel = [0, 0, 0, 0]; MC7000.prevJogLED = [0, 0, 0, 0]; MC7000.prevPadLED = [0, 0, 0, 0]; -// Param Buttons for Pitch Play -MC7000.paramButton = [0, 0, 0, 0]; - /* Color Codes: Colors are encoded using the following schema: Take the individual components of the color (R, G, B). Then use @@ -705,7 +725,8 @@ MC7000.wheelTurn = function(channel, control, value, status, group) { // A: For a control that centers on 0: const numTicks = (value < 0x64) ? value : (value - 128); - const adjustedSpeed = numTicks * MC7000.jogSensitivity / 10; + const baseSpeed = numTicks * MC7000.jogParams.sensitivity; + const adjustedSpeed = baseSpeed / 10; const deckNumber = script.deckFromGroup(group); const deckIndex = deckNumber - 1; const libraryMaximized = engine.getValue("[Skin]", "show_maximized_library"); @@ -714,8 +735,14 @@ MC7000.wheelTurn = function(channel, control, value, status, group) { } else if (libraryMaximized === 1 && numTicks < 0) { engine.setValue("[Library]", "MoveUp", 1); } else if (engine.isScratching(deckNumber)) { - // Scratch! - engine.scratchTick(deckNumber, numTicks * MC7000.jogSensitivity); + // Scratch! + let scratchSpeed = baseSpeed; + const acceleration = MC7000.jogParams.acceleration; + if (acceleration && acceleration.enabled) { + const accelerationFactor = Math.pow(Math.abs(baseSpeed), acceleration.exponent) * acceleration.coefficient; + scratchSpeed *= accelerationFactor; + } + engine.scratchTick(deckNumber, scratchSpeed); } else { if (MC7000.shift[deckIndex]) { // While Shift Button pressed -> Search through track @@ -932,35 +959,87 @@ MC7000.censor = function(channel, control, value, status, group) { } } }; -// Param Button for Pitch Play to decrease pitch, or decrease star rating otherwise -MC7000.StarsDown = function(channel, control, value, status, group) { - const deckNumber = script.deckFromGroup(group); - const deckIndex = deckNumber - 1; - if (value >= 0x00) { - if (MC7000.PADMode[deckIndex] === "Pitch") { - for (let padIdx = 0; padIdx < 8; padIdx++) { - MC7000.halftoneToPadMap[deckIndex][padIdx] = MC7000.halftoneToPadMap[deckIndex][padIdx] - 8; // pitch down - } - } else { - engine.setValue(group, "stars_down", true); // stars down - } + +// Parameter Buttons +MC7000.parameterButton = function(value, group, {isLeftButton, isShiftPressed}) { + if (value === 0) { + return; } -}; -// Param Button for Pitch Play to increase pitch, or increase star rating otherwise -MC7000.StarsUp = function(channel, control, value, status, group) { + const deckNumber = script.deckFromGroup(group); const deckIndex = deckNumber - 1; - if (value >= 0x00) { - if (MC7000.PADMode[deckIndex] === "Pitch") { - for (let padIdx = 0; padIdx < 8; padIdx++) { - MC7000.halftoneToPadMap[deckIndex][padIdx] = MC7000.halftoneToPadMap[deckIndex][padIdx] + 8; // pitch up + const settings = MC7000.parameterButtonSettings; + + if (settings.parameterButtonPitchPlayOverrideEnabled && MC7000.PADMode[deckIndex] === "Pitch") { + const pitchDelta = isLeftButton ? -8 : 8; + for (let padIdx = 0; padIdx < 8; padIdx++) { + MC7000.halftoneToPadMap[deckIndex][padIdx] += pitchDelta; + } + } else { + switch (settings.mode) { + case "starsAndColor": + if (isShiftPressed) { + script.triggerControl(group, `track_color_${isLeftButton ? "prev" : "next"}`); + } else { + script.triggerControl(group, `stars_${isLeftButton ? "down" : "up"}`); } - } else { - engine.setValue(group, "stars_up", true); // stars up + break; + case "beatjump": + if (isShiftPressed) { + const beatJumpSize = engine.getValue(group, "beatjump_size"); + const indexDelta = isLeftButton ? -1 : 1; + const newIndex = Math.max(0, Math.min(MC7000.beatJump.length - 1, MC7000.beatJump.indexOf(beatJumpSize) + indexDelta)); + const newBeatJumpSize = MC7000.beatJump[newIndex]; + engine.setValue(group, "beatjump_size", newBeatJumpSize); + } else { + script.triggerControl(group, `beatjump_${isLeftButton ? "backward" : "forward"}`); + } + break; + case "introOutro": + { + const cue = isLeftButton ? "intro_end" : "outro_start"; + const action = isShiftPressed ? "clear" : "activate"; + script.triggerControl(group, `${cue}_${action}`); + } + break; + default: + break; } } }; +// Parameter Button '<' +MC7000.parameterButtonLeft = function(channel, control, value, status, group) { + MC7000.parameterButton(value, group, { + isLeftButton: true, + isShiftPressed: false + }); +}; + +// Parameter Button '>' +MC7000.parameterButtonRight = function(channel, control, value, status, group) { + MC7000.parameterButton(value, group, { + isLeftButton: false, + isShiftPressed: false + }); +}; + +// Parameter Button '<' + 'SHIFT' +MC7000.parameterButtonLeftShifted = function(channel, control, value, status, group) { + MC7000.parameterButton(value, group, { + isLeftButton: true, + isShiftPressed: true + }); +}; + +// Parameter Button '>' + 'SHIFT' +MC7000.parameterButtonRightShifted = function(channel, control, value, status, group) { + MC7000.parameterButton(value, group, { + isLeftButton: false, + isShiftPressed: true + }); +}; + // Set Crossfader Curve MC7000.crossFaderCurve = function(control, value) { script.crossfaderCurve(value); diff --git a/res/controllers/Denon-MC7000.midi.xml b/res/controllers/Denon-MC7000.midi.xml index be6dd02bd88..b36a5a0683e 100644 --- a/res/controllers/Denon-MC7000.midi.xml +++ b/res/controllers/Denon-MC7000.midi.xml @@ -8,6 +8,118 @@ https://github.com/mixxxdj/mixxx/wiki/Denon-MC7000 denon_mc7000 + + + + + + + + + + + + + + + + + + + + + + @@ -1927,7 +2039,7 @@ [Channel1] - MC7000.StarsDown + MC7000.parameterButtonLeft 0x94 0x28 @@ -1937,7 +2049,7 @@ [Channel1] - MC7000.StarsUp + MC7000.parameterButtonRight 0x94 0x29 @@ -1946,28 +2058,28 @@ - [Library] - track_color_prev + [Channel1] + MC7000.parameterButtonLeftShifted 0x94 0x2A - + - [Library] - track_color_next + [Channel1] + MC7000.parameterButtonRightShifted 0x94 0x2B - + [Channel2] - MC7000.StarsDown + MC7000.parameterButtonLeft 0x95 0x28 @@ -1977,7 +2089,7 @@ [Channel2] - MC7000.StarsUp + MC7000.parameterButtonRight 0x95 0x29 @@ -1986,28 +2098,28 @@ - [Library] - track_color_prev + [Channel2] + MC7000.parameterButtonLeftShifted 0x95 0x2A - + - [Library] - track_color_next + [Channel2] + MC7000.parameterButtonRightShifted 0x95 0x2B - + [Channel3] - MC7000.StarsDown + MC7000.parameterButtonLeft 0x96 0x28 @@ -2017,7 +2129,7 @@ [Channel3] - MC7000.StarsUp + MC7000.parameterButtonRight 0x96 0x29 @@ -2026,28 +2138,28 @@ - [Library] - track_color_prev + [Channel3] + MC7000.parameterButtonLeftShifted 0x96 0x2A - + - [Library] - track_color_next + [Channel3] + MC7000.parameterButtonRightShifted 0x96 0x2B - + [Channel4] - MC7000.StarsDown + MC7000.parameterButtonLeft 0x97 0x28 @@ -2057,7 +2169,7 @@ [Channel4] - MC7000.StarsUp + MC7000.parameterButtonRight 0x97 0x29 @@ -2066,23 +2178,23 @@ - [Library] - track_color_prev + [Channel4] + MC7000.parameterButtonLeftShifted 0x97 0x2A - + - [Library] - track_color_next + [Channel4] + MC7000.parameterButtonRightShifted 0x97 0x2B - + 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/Gemini-CDMP-7000-scripts.js b/res/controllers/Gemini-CDMP-7000-scripts.js index 9cfd88320a0..e87e91a57fa 100644 --- a/res/controllers/Gemini-CDMP-7000-scripts.js +++ b/res/controllers/Gemini-CDMP-7000-scripts.js @@ -8,7 +8,7 @@ // Changelog v1.2 // Fixed clearing/lighting leds for hotcues if song has them set or not // Incorporated Pitch Ranges into Button/LCD -// Incorprated BPM in LCD (may still not happen on first load) +// Incorporated BPM in LCD (may still not happen on first load) // Updates LCD with Vinyl and Hotcue messages // ---- remaining issues diff --git a/res/controllers/Hercules DJControl Starlight.midi.xml b/res/controllers/Hercules DJControl Starlight.midi.xml index 78b8cd0a5ca..4d6a3a24452 100644 --- a/res/controllers/Hercules DJControl Starlight.midi.xml +++ b/res/controllers/Hercules DJControl Starlight.midi.xml @@ -440,6 +440,28 @@ // FX + + [EffectRack1_EffectUnit1] + super1 + SHIFT + Filter + 0xb4 + 0x01 + + + + + + + [EffectRack1_EffectUnit1] + super1 + SHIFT + Bass + 0xb4 + 0x02 + + + + + [EffectRack1_EffectUnit1_Effect1] enabled @@ -702,6 +724,28 @@ // FX + + [EffectRack1_EffectUnit2] + super1 + SHIFT + Filter + 0xb5 + 0x01 + + + + + + + [EffectRack1_EffectUnit2] + super1 + SHIFT + Bass + 0xb5 + 0x02 + + + + + [EffectRack1_EffectUnit2_Effect1] enabled diff --git a/res/controllers/Hercules-DJ-Console-4-Mx-scripts.js b/res/controllers/Hercules-DJ-Console-4-Mx-scripts.js index c595e7bdb25..e1d137ed11a 100644 --- a/res/controllers/Hercules-DJ-Console-4-Mx-scripts.js +++ b/res/controllers/Hercules-DJ-Console-4-Mx-scripts.js @@ -80,7 +80,7 @@ Hercules4Mx.userSettings = { // Setting this setting to true, the curve will change to scratch curve when the scratch mode is on (scratch button). // Setting it to false will not change it, so it will use the setting configured in the DJHercules Tray-icon configuration. 'crossfaderScratchCurve': false, - // _Scratching_ Playback speed of the virtual vinyl that is being scratched. 45.00 and 33.33 are the common speeeds. (Lower number equals faster scratch) + // _Scratching_ Playback speed of the virtual vinyl that is being scratched. 45.00 and 33.33 are the common speeds. (Lower number equals faster scratch) 'vinylSpeed': 45, // _Scratching_ You should configure this setting to the same value than in the DJHercules tray icon configuration. (Normal means 1/1). // If crossfaderScratchCurve is true, or the setting is changed while Mixxx is active, this value will be detected automatically. 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/MVave-SMC-Mixer-scripts.js b/res/controllers/MVave-SMC-Mixer-scripts.js new file mode 100644 index 00000000000..63991e03582 --- /dev/null +++ b/res/controllers/MVave-SMC-Mixer-scripts.js @@ -0,0 +1,359 @@ +"use strict"; + +// eslint-disable-next-line no-var +var SMCMixer; +(function(SMCMixer) { + const mapIndexToChannel = function(index) { + switch (Math.abs(index) % 4) { + case 0: return 3; + case 1: return 1; + case 2: return 2; + case 3: return 4; + } + }; + + class Deck extends components.Deck { + constructor() { + super([1, 2, 3, 4]); + // Transport buttons + this.playButton = new components.PlayButton({ + group: "[Channel1]", + midi: [0x90, 0x5E], // Play transport + type: components.Button.prototype.types.toggle, + }); + this.cueButton = new components.CueButton({ + group: "[Channel1]", + midi: [0x90, 0x5D], // Pause transport + type: components.Button.prototype.types.push, + }); + this.backButton = new components.Button({ + group: "[Channel1]", + midi: [0x90, 0x5B], + key: "beatjump_backward", + }); + this.forwardButton = new components.Button({ + group: "[Channel1]", + midi: [0x90, 0x5C], + key: "beatjump_forward", + }); + } + } + + class Encoder extends components.Encoder { + constructor(params) { + super(params); + } + inValueScale(value) { + if (value === 0x41) { + return this.inGetParameter()-0.01; + } else { + return this.inGetParameter()+0.01; + } + } + } + + // LongPressButton is like a normal button of type powerWindow, except that + // it doesn't trigger the short press and the long press. + // Instead it triggers on release and leaves it to the user of the class to + // check if this was a long press or a short press. + class LongPressButton extends components.Button { + constructor(params) { + super(params); + } + + input(channel, control, value, status, _group) { + if (this.isPress(channel, control, value, status)) { + this.isLongPressed = false; + this.longPressTimer = engine.beginTimer(this.longPressTimeout, () => { + this.isLongPressed = true; + this.longPressTimer = 0; + }, true); + } else { + this.inToggle(); + if (!this.isLongPressed && this.triggerOnRelease) { + this.trigger(); + } + if (this.longPressTimer !== 0) { + engine.stopTimer(this.longPressTimer); + this.longPressTimer = 0; + } + this.isLongPressed = false; + } + } + } + + // Pot is the same as components.Pot except that it keeps track of the value + // set by moving one of the hardware faders, and if that value ever doesn't + // match the value of the fader in software it blinks the LED above the + // physical fader to indicate that soft takeover is enabled. + // Right now the LED always blinks when you attempt to turn it on, but + // M-Vave has indicated that in a future firmware update they will make it + // possible to set the LED to be lit steadily. + class Pot extends components.Pot { + constructor(params) { + super(params); + // If the hardware control does not match the software control by + // anything less than the tolerance window, we consider them the + // same. This way we're not constantly blinking the soft takeover + // indicator because we didn't get the control matched up exactly. + this.toleranceWindow = 0.001; + } + input(_channel, _control, value, _status, _group) { + const receivingFirstValue = this.hardwarePos === undefined; + this.hardwarePos = this.inValueScale(value); + engine.setParameter(this.group, this.inKey, this.hardwarePos); + if (receivingFirstValue) { + this.firstValueReceived = true; + this.connect(); + engine.softTakeover(this.group, this.inKey, true); + } + } + output(value) { + if (this.hardwarePos === undefined) { + return; + } + const parameterValue = engine.getParameter(this.group, this.outKey); + const delta = parameterValue - this.hardwarePos; + if (delta > this.toleranceWindow) { + midi.sendShortMsg(this.midi[0], this.hardwarePos, this.inValueScale(value)); + } + } + } + + class EqRack { + constructor(index) { + const channel = mapIndexToChannel(index); + this.knob = new Encoder({ + group: `[Channel${channel}]`, + midi: [0xB0, 0x10 + index], + inKey: "pregain", + }); + + const btnInToggle = () => { + const knob = this.knob; + const origGroup = this.knob.group; + const origInKey = this.knob.inKey; + return function() { + if (this.isLongPressed) { + if (knob.inKey === this.inKey.replace("button_", "") || (this.inKey === "enabled" && this.knob.inKey === "super1")) { + knob.group = origGroup; + knob.inKey = origInKey; + } else { + knob.group = this.group; + let newKey = ""; + if (this.key === "enabled") { + newKey = "super1"; + } else { + newKey = this.inKey.replace("button_", ""); + } + knob.inKey = newKey; + } + } else { + const val = this.inGetParameter(); + if (val > 0) { + this.inSetValue(0); + } else { + this.inSetValue(0x1F); + } + } + }; + }; + this.highKillButton = new LongPressButton({ + type: components.Button.prototype.types.powerWindow, + group: `[EqualizerRack1_[Channel${channel}]_Effect1]`, + midi: [0x90, 0x10 + index], + key: "button_parameter3", + inToggle: btnInToggle(), + }); + this.midKillButton = new LongPressButton({ + type: components.Button.prototype.types.toggle, + group: `[EqualizerRack1_[Channel${channel}]_Effect1]`, + midi: [0x90, 0x08 + index], + key: "button_parameter2", + inToggle: btnInToggle(), + }); + this.lowKillButton = new LongPressButton({ + type: components.Button.prototype.types.toggle, + group: `[EqualizerRack1_[Channel${channel}]_Effect1]`, + midi: [0x90, 0x00 + index], + key: "button_parameter1", + inToggle: btnInToggle(), + }); + this.quickEffectButton = new LongPressButton({ + type: components.Button.prototype.types.toggle, + group: `[QuickEffectRack1_[Channel${channel}]]`, + midi: [0x90, 0x18 + index], + key: "enabled", + inToggle: btnInToggle(), + }); + } + } + class Controller extends components.ComponentContainer { + constructor() { + super({}); + this.activeDeck = new Deck(); + + this.eqButtons = new Array(4); + this.slipButtons = new Array(4); + this.quantizeButtons = new Array(4); + this.keylockButtons = new Array(4); + this.pflButtons = new Array(4); + this.faders = new Array(8); + for (let i = 0; i < 4; i++) { + const channel = mapIndexToChannel(i); + const group = `[Channel${channel}]`; + this.eqButtons[i] = new EqRack(i); + this.slipButtons[i] = new components.Button({ + type: components.Button.prototype.types.toggle, + group: group, + midi: [0x90, i+0x14], + key: "slip_enabled", + }); + this.quantizeButtons[i] = new components.Button({ + type: components.Button.prototype.types.toggle, + group: group, + midi: [0x90, 0x0C+i], + key: "quantize", + }); + this.keylockButtons[i] = new components.Button({ + type: components.Button.prototype.types.toggle, + group: group, + midi: [0x90, 0x04+i], + key: "keylock", + }); + this.pflButtons[i] = new components.Button({ + type: components.Button.prototype.types.toggle, + group: group, + midi: [0x90, 0x1C+i], + key: "pfl", + }); + this.faders[i] = new Pot({ + group: group, + midi: [0xE0+i], + key: "volume", + softTakeover: true, + }); + this.faders[i+4] = new Pot({ + group: group, + midi: [0xE4+i], + key: "rate", + softTakeover: true, + }); + } + + this.gainKnob = new Encoder({ + group: "[Master]", + midi: [0xB0, 0x14], + key: "gain", + }); + this.balanceKnob = new Encoder({ + group: "[Master]", + midi: [0xB0, 0x15], + key: "balance", + }); + this.headGainKnob = new Encoder({ + group: "[Master]", + midi: [0xB0, 0x16], + key: "headGain", + }); + this.headMixKnob = new Encoder({ + group: "[Master]", + midi: [0xB0, 0x17], + key: "headMix", + }); + + // Navigation buttons + this.downButton = new components.Button({ + group: "[Library]", + midi: [0x90, 0x61], + key: "MoveDown", + }); + this.upButton = new components.Button({ + group: "[Library]", + midi: [0x90, 0x60], + key: "MoveUp", + }); + + // For the left and right arrow buttons the controller appears to + // handle the LED itself, so we use inKey so as not to be sending + // output that will never be used. + this.leftButton = new components.Button({ + group: "[Library]", + midi: [0x90, 0x62], + inKey: "focused_widget", + input: function(_channel, _control, value, _status, _group) { + const selected = this.inGetParameter(); + switch (selected) { + case 2: { + // Tree View + engine.setParameter(this.group, "GoToItem", value); + break; + } + case 3: { + // Tracks, goto Tree View + this.inSetParameter(2); + break; + } + } + }, + }); + this.rightButton = new components.Button({ + group: "[Library]", + midi: [0x90, 0x63], + inKey: "focused_widget", + input: function(_channel, _control, value, _status, _group) { + const selected = this.inGetParameter(); + switch (selected) { + case 2: { + // Tree View, goto Library + this.inSetParameter(3); + break; + } + case 3: { + // Tracks + engine.setParameter(this.group, "GoToItem", value); + break; + } + } + }, + }); + this.recordButton = new components.Button({ + group: "[Recording]", + midi: [0x90, 0x5F], + inKey: "toggle_recording", + outKey: "status", + }); + this.deckLeftButton = new components.Button({ + type: components.Button.prototype.types.powerWindow, + group: "[Channel1]", + midi: [0x90, 0x2E], // << Channel Left Button + inToggle: function() { + if (this.isLongPressed) { + SMCMixer.controller.activeDeck.setCurrentDeck("[Channel3]"); + } else { + SMCMixer.controller.activeDeck.setCurrentDeck("[Channel1]"); + } + }, + }); + this.deckRightButton = new components.Button({ + type: components.Button.prototype.types.powerWindow, + group: "[Channel2]", + midi: [0x90, 0x2F], // >> Channel Right button + inToggle: function() { + if (this.isLongPressed) { + SMCMixer.controller.activeDeck.setCurrentDeck("[Channel4]"); + } else { + SMCMixer.controller.activeDeck.setCurrentDeck("[Channel2]"); + } + }, + }); + } + } + + SMCMixer.init = function() { + SMCMixer.controller = new Controller(); + }; + SMCMixer.shutdown = function() { + SMCMixer.controller.shutdown(); + }; +})(SMCMixer || (SMCMixer = {})); diff --git a/res/controllers/MVave-SMC-Mixer.midi.xml b/res/controllers/MVave-SMC-Mixer.midi.xml new file mode 100644 index 00000000000..edc71b9d71d --- /dev/null +++ b/res/controllers/MVave-SMC-Mixer.midi.xml @@ -0,0 +1,611 @@ + + + + M-Vave SMC-Mixer + Sam Whited + MIDI mapping for the M-Vave SMC-Mixer. + https://mixxx.discourse.group/t/m-wave-sinco-smc-mixer-radio-broadcast-mapping/30366 + + + + + + + + + + + + + [EqualizerRack1_[Channel3]_Effect1] + SMCMixer.controller.eqButtons[0].midKillButton.input + First S button. + 0x90 + 0x08 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + SMCMixer.controller.eqButtons[0].lowKillButton.input + First R button. + 0x90 + 0x00 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + SMCMixer.controller.eqButtons[0].highKillButton.input + First M button. + 0x90 + 0x10 + + + + + + [QuickEffectRack1_[Channel3]] + SMCMixer.controller.eqButtons[0].quickEffectButton.input + First square button. + 0x90 + 0x18 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + SMCMixer.controller.eqButtons[1].midKillButton.input + Second S button. + 0x90 + 0x09 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + SMCMixer.controller.eqButtons[1].lowKillButton.input + Second R button. + 0x90 + 0x01 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + SMCMixer.controller.eqButtons[1].highKillButton.input + Second M button. + 0x90 + 0x11 + + + + + + [QuickEffectRack1_[Channel1]] + SMCMixer.controller.eqButtons[1].quickEffectButton.input + Second square button. + 0x90 + 0x19 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + SMCMixer.controller.eqButtons[2].midKillButton.input + Third S button. + 0x90 + 0x0A + + + + + + [EqualizerRack1_[Channel2]_Effect1] + SMCMixer.controller.eqButtons[2].lowKillButton.input + Third R button. + 0x90 + 0x02 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + SMCMixer.controller.eqButtons[2].highKillButton.input + Third M button. + 0x90 + 0x12 + + + + + + [QuickEffectRack1_[Channel2]] + SMCMixer.controller.eqButtons[2].quickEffectButton.input + Third square button. + 0x90 + 0x1A + + + + + + [EqualizerRack1_[Channel4]_Effect1] + SMCMixer.controller.eqButtons[3].midKillButton.input + Fourth S button. + 0x90 + 0x0B + + + + + + [EqualizerRack1_[Channel4]_Effect1] + SMCMixer.controller.eqButtons[3].lowKillButton.input + Third R button. + 0x90 + 0x03 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + SMCMixer.controller.eqButtons[3].highKillButton.input + Fourth M button. + 0x90 + 0x13 + + + + + + [QuickEffectRack1_[Channel4]] + SMCMixer.controller.eqButtons[3].quickEffectButton.input + Fourth square button. + 0x90 + 0x1B + + + + + + + [Channel3] + SMCMixer.controller.slipButtons[0].input + Fifth M button. + 0x90 + 0x14 + + + + + + [Channel1] + SMCMixer.controller.slipButtons[1].input + Sixth M button. + 0x90 + 0x15 + + + + + + [Channel2] + SMCMixer.controller.slipButtons[2].input + Seventh M button. + 0x90 + 0x16 + + + + + + [Channel4] + SMCMixer.controller.slipButtons[3].input + Eighth M button. + 0x90 + 0x17 + + + + + + + [Channel3] + SMCMixer.controller.quantizeButtons[0].input + Fifth S button. + 0x90 + 0x0C + + + + + + [Channel1] + SMCMixer.controller.quantizeButtons[1].input + Sixth S button. + 0x90 + 0x0D + + + + + + [Channel2] + SMCMixer.controller.quantizeButtons[2].input + Seventh S button. + 0x90 + 0x0E + + + + + + [Channel4] + SMCMixer.controller.quantizeButtons[3].input + Eighth S button. + 0x90 + 0x0F + + + + + + + [Channel3] + SMCMixer.controller.keylockButtons[0].input + Fifth R button. + 0x90 + 0x04 + + + + + + [Channel1] + SMCMixer.controller.keylockButtons[1].input + Sixth R button. + 0x90 + 0x05 + + + + + + [Channel2] + SMCMixer.controller.keylockButtons[2].input + Seventh R button. + 0x90 + 0x06 + + + + + + [Channel4] + SMCMixer.controller.keylockButtons[3].input + Eighth R button. + 0x90 + 0x07 + + + + + + + [Channel3] + SMCMixer.controller.pflButtons[0].input + Fifth square button. + 0x90 + 0x1C + + + + + + [Channel1] + SMCMixer.controller.pflButtons[1].input + Sixth square button. + 0x90 + 0x1D + + + + + + [Channel2] + SMCMixer.controller.pflButtons[2].input + Seventh square button. + 0x90 + 0x1E + + + + + + [Channel4] + SMCMixer.controller.pflButtons[3].input + Eighth square button. + 0x90 + 0x1F + + + + + + [Channel3] + SMCMixer.controller.eqButtons[0].knob.input + Knob 1. + 0xB0 + 0x10 + + + + + + [Channel1] + SMCMixer.controller.eqButtons[1].knob.input + Knob 2. + 0xB0 + 0x11 + + + + + + [Channel2] + SMCMixer.controller.eqButtons[2].knob.input + Knob 3. + 0xB0 + 0x12 + + + + + + [Channel4] + SMCMixer.controller.eqButtons[3].knob.input + Knob 3. + 0xB0 + 0x13 + + + + + + [Master] + SMCMixer.controller.gainKnob.input + Knob 4. + 0xB0 + 0x14 + + + + + + [Master] + SMCMixer.controller.balanceKnob.input + Knob 5. + 0xB0 + 0x15 + + + + + + [Master] + SMCMixer.controller.headGainKnob.input + Knob 6. + 0xB0 + 0x16 + + + + + + [Master] + SMCMixer.controller.headMixKnob.input + Knob 7. + 0xB0 + 0x17 + + + + + + [Channel1] + SMCMixer.controller.deckLeftButton.input + Channel Left button. + 0x90 + 0x2E + + + + + + [Channel1] + SMCMixer.controller.deckRightButton.input + Channel Right button. + 0x90 + 0x2F + + + + + + [Channel1] + SMCMixer.controller.activeDeck.backButton.input + Rewind transport button. + 0x90 + 0x5B + + + + + + [Channel1] + SMCMixer.controller.activeDeck.forwardButton.input + Fast forward transport button. + 0x90 + 0x5C + + + + + + [Channel1] + SMCMixer.controller.activeDeck.cueButton.input + Pause transport button (used for Cue). + 0x90 + 0x5D + + + + + + [Channel1] + SMCMixer.controller.activeDeck.playButton.input + Play transport button. + 0x90 + 0x5E + + + + + + [Recording] + SMCMixer.controller.recordButton.input + Record transport button. + 0x90 + 0x5F + + + + + + [Channel1] + SMCMixer.controller.downButton.input + Down button. + 0x90 + 0x61 + + + + + + [Channel1] + SMCMixer.controller.upButton.input + Down button. + 0x90 + 0x60 + + + + + + [Channel1] + SMCMixer.controller.leftButton.input + Left button. + 0x90 + 0x62 + + + + + + [Channel1] + SMCMixer.controller.rightButton.input + Right button. + 0x90 + 0x63 + + + + + + [Channel3] + SMCMixer.controller.faders[0].input + Fader 1. + 0xE0 + + + + + + [Channel1] + SMCMixer.controller.faders[1].input + Fader 2. + 0xE1 + + + + + + [Channel2] + SMCMixer.controller.faders[2].input + Fader 3. + 0xE2 + + + + + + [Channel4] + SMCMixer.controller.faders[3].input + Fader 4. + 0xE3 + + + + + + [Channel3] + SMCMixer.controller.faders[4].input + Fader 5. + 0xE4 + + + + + + [Channel1] + SMCMixer.controller.faders[5].input + Fader 6. + 0xE5 + + + + + + [Channel2] + SMCMixer.controller.faders[6].input + Fader 7. + 0xE6 + + + + + + [Channel4] + SMCMixer.controller.faders[7].input + Fader 8. + 0xE7 + + + + + + + + diff --git a/res/controllers/Midi_for_light-scripts.js b/res/controllers/Midi_for_light-scripts.js index 6c6cc073d8c..a7b1a863143 100644 --- a/res/controllers/Midi_for_light-scripts.js +++ b/res/controllers/Midi_for_light-scripts.js @@ -16,31 +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 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 // @@ -65,6 +67,7 @@ if (enable_vu_mono_current === true || enable_vu_mono_average_min === true || en } else { var enable_vu_meter_global = false; // set to false if you not need complete VU-Meter } +var last_mtc_playposition = -1; /////////////////////////////////////////////////////////////// // FUNCTIONS // @@ -73,16 +76,16 @@ if (enable_vu_mono_current === true || enable_vu_mono_average_min === true || en midi_for_light.init = function(id) { // called when the MIDI device is opened & set up midi_for_light.id = id; // store the ID of this device for later use midi_for_light.directory_mode = false; - midi_for_light.deck_current = 0; - midi_for_light.crossfader_block = false; - midi_for_light.crossfader_change_block_timer = [-1, -1]; - midi_for_light.volumebeat = false; - midi_for_light.volumeBeatBlockStatus = false; - midi_for_light.volumeBeatBlock_timer = [-1, -1]; - midi_for_light.vu_meter_timer = [-1, -1]; - midi_for_light.volumebeat_on_delay_timer = [-1, -1]; + midi_for_light.deck_current = -1; + midi_for_light.decks = [ + {id: 0, priority: 0.0, playing: false}, + {id: 1, priority: 0.0, playing: false}, + {id: 2, priority: 0.0, playing: false}, + {id: 3, priority: 0.0, playing: false} + ]; + midi_for_light.vu_meter_timer = undefined; - engine.connectControl("[Master]", "crossfader", "midi_for_light.crossfaderChange"); + 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); @@ -92,50 +95,101 @@ 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.deckVolumeChange"); - 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.crossfaderChange(); + midi_for_light.calculateDeckPriority(); }; midi_for_light.shutdown = function(id) { // called when the MIDI device is closed - engine.stopTimer(midi_for_light.deck_beat_watchdog_timer[0]); - engine.stopTimer(midi_for_light.deck_beat_watchdog_timer[1]); - engine.stopTimer(midi_for_light.deck_beat_watchdog_timer[2]); - engine.stopTimer(midi_for_light.deck_beat_watchdog_timer[3]); - engine.stopTimer(midi_for_light.vu_meter_timer); - engine.stopTimer(midi_for_light.volumeBeatBlock_timer); - engine.stopTimer(midi_for_light.crossfader_change_block_timer); - engine.stopTimer(midi_for_light.volumebeat_on_delay_timer); + for (let i = 0; i <= 3; i++) { + if (deck_beat_watchdog_timer[i]) { + engine.stopTimer(deck_beat_watchdog_timer[i]); + } + } + for (const timer of ["vu_meter_timer"]) { + if (midi_for_light[timer]) { + engine.stopTimer(midi_for_light[timer]); + midi_for_light[timer] = undefined; + } + } +}; + +midi_for_light.calculateDeckPriority = function() { + // Calculate each channels Volume to figure out the most important + const crossfader = engine.getValue("[Master]", "crossfader"); + const crossfader_left = Math.min((1 - crossfader) * 1.33, 1); + const crossfader_right = Math.min((1 + crossfader) * 1.33, 1); + const crossfader_factors = [crossfader_left, 1.0, crossfader_right]; + + for (let i = 0; i < 4; i++) { + const channel = `[Channel${ i + 1 }]`; + midi_for_light.decks[i].playing = engine.getParameter(channel, "play") === 1; + if (! midi_for_light.decks[i].playing) { + midi_for_light.decks[i].priority = 0.0; + continue; + } + + midi_for_light.decks[i].priority = engine.getParameter(channel, "volume") * crossfader_factors[engine.getValue(channel, "orientation")]; + + // Decrease Priority of ending Tracks + const duration = engine.getValue(channel, "duration"); + const playposition = duration * engine.getValue(channel, "playposition"); + if (duration - playposition < deck_ending_time) { + midi_for_light.decks[i].priority *= deck_ending_priority_factor; + } + } + + // Sort Decks by priority + const sorted = midi_for_light.decks.slice(); + sorted.sort(function(a, b) { return b.priority - a.priority; }); + if (sorted[0].priority < 0.25) { + midi_for_light.deck_current = -1; + return; + } + + // Avoid Jumping between Decks + if (midi_for_light.deck_current !== -1) { + if (sorted[0].priority < midi_for_light.decks[midi_for_light.deck_current].priority + 0.05) { + return; + } + } + + // check deck change and send change message + if (midi_for_light.deck_current !== sorted[0].id) { + midi_for_light.deck_current = sorted[0].id; + midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x64 + sorted[0].id); // Note C on with 64 and add deck + } }; midi_for_light.deckButtonPlay = function(value, group, control) { // called when click a play button var deck = parseInt(group.substring(8, 9)) - 1; - if (value == 1) { // deck play on + if (deck_beat_watchdog_timer[deck]) { engine.stopTimer(deck_beat_watchdog_timer[deck]); + } + + if (value === 1) { // deck play on beat_watchdog[deck] = false; deck_beat_watchdog_timer[deck] = engine.beginTimer(beat_watchdog_time, () => { midi_for_light.deckBeatWatchdog(deck); }); } else { // deck play stop - engine.stopTimer(deck_beat_watchdog_timer[deck]); beat_watchdog[deck] = true; + deck_beat_watchdog_timer[deck] = undefined; } - if (midi_for_light.volumebeat === true) { - midi_for_light.deckVolumeChange(); - } else { - midi_for_light.crossfaderChange(); - } - + midi_for_light.calculateDeckPriority(); }; midi_for_light.deckBeatWatchdog = function(deck) { // if current deck beat lost without reason, search a new current deck - engine.stopTimer(deck_beat_watchdog_timer[deck]); + if (deck_beat_watchdog_timer[deck]) { + engine.stopTimer(deck_beat_watchdog_timer[deck]); + deck_beat_watchdog_timer[deck] = undefined; + } beat_watchdog[deck] = true; - if (midi_for_light.volumebeat === false) midi_for_light.crossfaderChange(); + midi_for_light.calculateDeckPriority(); }; midi_for_light.vuMeter = function() { // read, calculate and send vu-meter values @@ -391,98 +445,21 @@ midi_for_light.vuMeter = function() { // read, calculate and send vu-meter value } }; -midi_for_light.deckVolumeChange = function(value, group, control) { // deck volume changed - if (midi_for_light.volumebeat === false) return; // out if volumebeat is not active - if (midi_for_light.volumeBeatBlockStatus === true) return; // out if volumebeat is blocked - - var deckvolume = new Array(0, 0, 0, 0); - var volumemax = 0; - var deckneu = -1; - - // get volume from the decks and check it for use - for (var z = 0; z <= 3; z++) { - deckvolume[z] = engine.getValue("[Channel" + (z + 1) + "]", "volume"); - print("beat_watchdog " + z + ": " + beat_watchdog[z]); - if (deckvolume[z] > 0 && deckvolume[z] > volumemax && beat_watchdog[z] === false) { - volumemax = deckvolume[z]; - deckneu = z; - } - } - - if (deckneu == -1) return; // out if no new valid deck - - // check deck change and send change message - if (deckneu != midi_for_light.deck_current) { - midi_for_light.deck_current = deckneu; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x64 + deckneu); // Note C on with 64 and add deck - midi_for_light.volumeBeatBlockStatus = true; - midi_for_light.volumeBeatBlock_timer = engine.beginTimer(1000, midi_for_light.volumeBeatBlock); - } -}; - -midi_for_light.volumeBeatBlock = function() { // prevent deck change for one second - engine.stopTimer(midi_for_light.volumeBeatBlock_timer); - midi_for_light.volumeBeatBlockStatus = false; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x0); // note C on with value 0 - midi.sendShortMsg(0x7F + midi_channel, 0x30, 0x0); // note C off with value 0 -}; - -midi_for_light.volumeBeatOnDelay = function() { // allow deck change with volume after 3 second fader do nothing - engine.stopTimer(midi_for_light.volumebeat_on_delay_timer); - midi_for_light.volumebeat = true; -}; - -midi_for_light.crossfaderChange = function() { // crossfader chenge, check deck change - // if fader prevent, go out - if (midi_for_light.crossfader_block === true) return; - - // check changing to "deck change by volume" method - midi_for_light.volumebeat = false; - engine.stopTimer(midi_for_light.volumebeat_on_delay_timer); - if (engine.getValue("[Master]", "crossfader") > -0.25) { // crossfader more than 25% left; - if (engine.getValue("[Master]", "crossfader") < 0.25) { // crossfader more then 25% right; - midi_for_light.volumebeat_on_delay_timer = engine.beginTimer(3000, midi_for_light.volumeBeatOnDelay); - } - } - - // if crossfader in middle position, go out - if (engine.getValue("[Master]", "crossfader") === 0) return; - - // check what deck is current, crossfader exact 0 is defined as left - var deck = 0; - if (engine.getValue("[Master]", "crossfader") > 0) { // crossfader is right, not middle - deck = 1; - if (beat_watchdog[1] === true) deck = 3; - } else { - deck = 0; - if (beat_watchdog[0] === true) deck = 2; - } - - // check if deck has been changed - if (deck != midi_for_light.deck_current) { - midi_for_light.deck_current = deck; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x64 + deck); // note C on with value 64 + deck - midi_for_light.crossfader_block = true; - midi_for_light.crossfader_change_block_timer = engine.beginTimer(1000, midi_for_light.crossfaderChangeBlock); - } -}; - -midi_for_light.crossfaderChangeBlock = function() { // prevent deck change for one second - engine.stopTimer(midi_for_light.crossfader_change_block_timer); - midi_for_light.crossfader_block = false; - midi.sendShortMsg(0x8F + midi_channel, 0x30, 0x0); // note C on with value 0 - midi.sendShortMsg(0x7F + midi_channel, 0x30, 0x0); // note C off with value 0 - midi_for_light.crossfaderChange(); // check deck is current -}; - midi_for_light.sendMidiMtcFullFrame = function(value, group, control) { // sends an MTC full frame var deck = parseInt(group.substring(8, 9)) - 1; - if (deck != midi_for_light.deck_current) return; + if (deck !== midi_for_light.deck_current) { return; } var fps = 2; // 2 = 25 FPS var duration = engine.getValue(group, "track_samples") / engine.getValue(group, "track_samplerate") / 2; var PlayPositionRest = duration * engine.getValue(group, "playposition"); + // Prevent outputting the same Position twice + const current_mtc_playposition = Math.floor(PlayPositionRest * 25); + if (current_mtc_playposition === last_mtc_playposition) { + return; + } + last_mtc_playposition = current_mtc_playposition; + if (PlayPositionRest < 0) PlayPositionRest = 0; // calculate position hour and stripping from PlayPositionRest @@ -506,7 +483,9 @@ midi_for_light.deckBeatOutputToMidi = function(value, group, control) { // send var deck_bpm = parseInt(engine.getValue(group, "bpm")) - 50; // reset deck beat watchdog - engine.stopTimer(deck_beat_watchdog_timer[deck]); + if (deck_beat_watchdog_timer[deck]) { + engine.stopTimer(deck_beat_watchdog_timer[deck]); + } beat_watchdog[deck] = false; deck_beat_watchdog_timer[deck] = engine.beginTimer(beat_watchdog_time, () => { midi_for_light.deckBeatWatchdog(deck); }); 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 Mixtrack Platinum FX.midi.xml b/res/controllers/Numark Mixtrack Platinum FX.midi.xml new file mode 100644 index 00000000000..92be36f2cd0 --- /dev/null +++ b/res/controllers/Numark Mixtrack Platinum FX.midi.xml @@ -0,0 +1,3875 @@ + + + + Numark Mixtrack Platinum FX + Evoixmr mixxx 2.4 version updated from - QGazQ tweaks of Octopussy based on mapping from h67ma, bad1dea5, photoenix, Matthew Nicholson, Ending, Kaj Bostrom + Mapping for the Numark Mixtrack Platinum FX. + https://mixxx.discourse.group/t/numark-mixtrack-platinum-fx-mapping/19985 + + + + + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].playButton.input + 0x80 + 0x00 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].playButton.input + 0x81 + 0x00 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].playButton.input + 0x82 + 0x00 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].playButton.input + 0x83 + 0x00 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x84 + 0x00 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x85 + 0x00 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x86 + 0x00 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x87 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effect1 + 0x88 + 0x00 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].playButton.input + 0x90 + 0x00 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].playButton.input + 0x91 + 0x00 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].playButton.input + 0x92 + 0x00 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].playButton.input + 0x93 + 0x00 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x94 + 0x00 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x95 + 0x00 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x96 + 0x00 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x97 + 0x00 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effect1 + 0x98 + 0x00 + + + + + + [Library] + MoveVertical + MIDI Learned from 1 messages. + 0xBF + 0x00 + + + + + + [Library] + ScrollVertical + MIDI Learned from 2 messages. + 0xBF + 0x01 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].cueButton.input + 0x80 + 0x01 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].cueButton.input + 0x81 + 0x01 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].cueButton.input + 0x82 + 0x01 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].cueButton.input + 0x83 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effect2 + 0x88 + 0x01 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].cueButton.input + 0x90 + 0x01 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].cueButton.input + 0x91 + 0x01 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].cueButton.input + 0x92 + 0x01 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[3].cueButton.input + 0x93 + 0x01 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effect2 + 0x98 + 0x01 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].syncButton.input + 0x80 + 0x02 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].syncButton.input + 0x81 + 0x02 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].syncButton.input + 0x82 + 0x02 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].syncButton.input + 0x83 + 0x02 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x84 + 0x02 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x85 + 0x02 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x86 + 0x02 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x87 + 0x02 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effect3 + 0x88 + 0x02 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loadButton.input + 0x8F + 0x02 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].syncButton.input + 0x90 + 0x02 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].syncButton.input + 0x91 + 0x02 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].syncButton.input + 0x92 + 0x02 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].syncButton.input + 0x93 + 0x02 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x94 + 0x02 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x95 + 0x02 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x96 + 0x02 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x97 + 0x02 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effect3 + 0x98 + 0x02 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loadButton.input + 0x9F + 0x02 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].syncButton.input + 0x80 + 0x03 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].syncButton.input + 0x81 + 0x03 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].syncButton.input + 0x82 + 0x03 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].syncButton.input + 0x83 + 0x03 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effect1 + 0x89 + 0x03 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loadButton.input + 0x8F + 0x03 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].syncButton.input + 0x90 + 0x03 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].syncButton.input + 0x91 + 0x03 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].syncButton.input + 0x92 + 0x03 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].syncButton.input + 0x93 + 0x03 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effect1 + 0x99 + 0x03 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loadButton.input + 0x9F + 0x03 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].enableSwitch + 0xB8 + 0x03 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].enableSwitch + 0xB9 + 0x03 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].playButtonbeatgrid + 0x80 + 0x04 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].playButtonbeatgrid + 0x81 + 0x04 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].playButtonbeatgrid + 0x82 + 0x04 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].playButtonbeatgrid + 0x83 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effect2 + 0x89 + 0x04 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loadButton.input + 0x8F + 0x04 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].playButtonbeatgrid + 0x90 + 0x04 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].playButtonbeatgrid + 0x91 + 0x04 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].playButtonbeatgrid + 0x92 + 0x04 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].playButtonbeatgrid + 0x93 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effect2 + 0x99 + 0x04 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loadButton.input + 0x9F + 0x04 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].dryWetKnob.input + 0xB8 + 0x04 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].dryWetKnob.input + 0xB8 + 0x04 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].cueButton.input + 0x80 + 0x05 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].cueButton.input + 0x81 + 0x05 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].cueButton.input + 0x82 + 0x05 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].cueButton.input + 0x83 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effect3 + 0x89 + 0x05 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loadButton.input + 0x8F + 0x05 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].cueButton.input + 0x90 + 0x05 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].cueButton.input + 0x91 + 0x05 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].cueButton.input + 0x92 + 0x05 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].cueButton.input + 0x93 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effect3 + 0x99 + 0x05 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loadButton.input + 0x9F + 0x05 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effectParam.input + 0xB8 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effectParam.input + 0xB8 + 0x05 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effectParam2.input + 0xB8 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effectParam2.input + 0xB8 + 0x05 + + + + + + [EffectRack1_EffectUnit1] + MixtrackPlatinumFX.effect[0].effectParam3.input + 0xB8 + 0x05 + + + + + + [EffectRack1_EffectUnit2] + MixtrackPlatinumFX.effect[1].effectParam3.input + 0xB8 + 0x05 + + + + + + [Library] + MixtrackPlatinumFX.browse.knobButton.input + 0x8F + 0x06 + + + + + + [Channel1] + MixtrackPlatinumFX.wheelTouch + 0x90 + 0x06 + + + + + + [Channel2] + MixtrackPlatinumFX.wheelTouch + 0x91 + 0x06 + + + + + + [Channel3] + MixtrackPlatinumFX.wheelTouch + 0x92 + 0x06 + + + + + + [Channel4] + MixtrackPlatinumFX.wheelTouch + 0x93 + 0x06 + + + + + + [Library] + MixtrackPlatinumFX.browse.knobButton.input + 0x9F + 0x06 + + + + + + [Channel1] + MixtrackPlatinumFX.wheelTurn + 0xB0 + 0x06 + + + + + + [Channel2] + MixtrackPlatinumFX.wheelTurn + 0xB1 + 0x06 + + + + + + [Channel3] + MixtrackPlatinumFX.wheelTurn + 0xB2 + 0x06 + + + + + + [Channel4] + MixtrackPlatinumFX.wheelTurn + 0xB3 + 0x06 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].scratchToggle.input + 0x80 + 0x07 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].scratchToggle.input + 0x81 + 0x07 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].scratchToggle.input + 0x82 + 0x07 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].scratchToggle.input + 0x83 + 0x07 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x84 + 0x07 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x85 + 0x07 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x86 + 0x07 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x87 + 0x07 + + + + + + [Library] + MixtrackPlatinumFX.browse.knobButton.input + 0x8F + 0x07 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].scratchToggle.input + 0x90 + 0x07 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].scratchToggle.input + 0x91 + 0x07 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].scratchToggle.input + 0x92 + 0x07 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].scratchToggle.input + 0x93 + 0x07 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x94 + 0x07 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x95 + 0x07 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x96 + 0x07 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x97 + 0x07 + + + + + + [Library] + MixtrackPlatinumFX.browse.knobButton.input + 0x9F + 0x07 + + + + + + [Channel1] + MixtrackPlatinumFX.deckSwitch + 0x80 + 0x08 + + + + + + [Channel2] + MixtrackPlatinumFX.deckSwitch + 0x81 + 0x08 + + + + + + [Channel3] + MixtrackPlatinumFX.deckSwitch + 0x82 + 0x08 + + + + + + [Channel4] + MixtrackPlatinumFX.deckSwitch + 0x83 + 0x08 + + + + + + [Channel1] + MixtrackPlatinumFX.deckSwitch + 0x90 + 0x08 + + + + + + [Channel2] + MixtrackPlatinumFX.deckSwitch + 0x91 + 0x08 + + + + + + [Channel3] + MixtrackPlatinumFX.deckSwitch + 0x92 + 0x08 + + + + + + [Channel4] + MixtrackPlatinumFX.deckSwitch + 0x93 + 0x08 + + + + + + [Master] + crossfader + 0xB1 + 0x08 + + + + + + [Master] + crossfader + 0xBF + 0x08 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].tap.input + 0x88 + 0x09 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].tap.input + 0x89 + 0x09 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].tap.input + 0x98 + 0x09 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].tap.input + 0x99 + 0x09 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitch.inputMSB + 0xB0 + 0x09 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitch.inputMSB + 0xB1 + 0x09 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitch.inputMSB + 0xB2 + 0x09 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitch.inputMSB + 0xB3 + 0x09 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendUp.input + 0x80 + 0x0B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendUp.input + 0x81 + 0x0B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendUp.input + 0x82 + 0x0B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendUp.input + 0x83 + 0x0B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x84 + 0x0B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x85 + 0x0B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x86 + 0x0B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x87 + 0x0B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendUp.input + 0x90 + 0x0B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendUp.input + 0x91 + 0x0B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendUp.input + 0x92 + 0x0B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendUp.input + 0x93 + 0x0B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x94 + 0x0B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x95 + 0x0B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x96 + 0x0B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x97 + 0x0B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendDown.input + 0x80 + 0x0C + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendDown.input + 0x81 + 0x0C + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendDown.input + 0x82 + 0x0C + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendDown.input + 0x83 + 0x0C + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendDown.input + 0x90 + 0x0C + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendDown.input + 0x91 + 0x0C + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendDown.input + 0x92 + 0x0C + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendDown.input + 0x93 + 0x0C + + + + + + [Master] + MixtrackPlatinumFX.gains.cueGain.input + 0xBF + 0x0C + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x84 + 0x0D + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x85 + 0x0D + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x86 + 0x0D + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x87 + 0x0D + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x94 + 0x0D + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x95 + 0x0D + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x96 + 0x0D + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x97 + 0x0D + + + + + + [Master] + MixtrackPlatinumFX.gains.cueMix.input + 0xBF + 0x0D + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x84 + 0x0F + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x85 + 0x0F + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x86 + 0x0F + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x87 + 0x0F + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.modeButtonPress + 0x94 + 0x0F + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.modeButtonPress + 0x95 + 0x0F + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.modeButtonPress + 0x96 + 0x0F + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.modeButtonPress + 0x97 + 0x0F + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x14 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x14 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x14 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x14 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x14 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x14 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x14 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x14 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x15 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x15 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x15 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x15 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x15 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x15 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x15 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x15 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x16 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x16 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x16 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x16 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x16 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x16 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x16 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x16 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].gain.input + 0xB0 + 0x16 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].gain.input + 0xB1 + 0x16 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].gain.input + 0xB2 + 0x16 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].gain.input + 0xB3 + 0x16 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x17 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x17 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x17 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x17 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x17 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x17 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x17 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x17 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + MixtrackPlatinumFX.deck[0].treble.input + 0xB0 + 0x17 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + MixtrackPlatinumFX.deck[1].treble.input + 0xB1 + 0x17 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + MixtrackPlatinumFX.deck[2].treble.input + 0xB2 + 0x17 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + MixtrackPlatinumFX.deck[3].treble.input + 0xB3 + 0x17 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x18 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x18 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x18 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x18 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x18 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x18 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x18 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x18 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + MixtrackPlatinumFX.deck[0].mid.input + 0xB0 + 0x18 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + MixtrackPlatinumFX.deck[1].mid.input + 0xB1 + 0x18 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + MixtrackPlatinumFX.deck[2].mid.input + 0xB2 + 0x18 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + MixtrackPlatinumFX.deck[3].mid.input + 0xB3 + 0x18 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x19 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x19 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x19 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x19 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x19 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x19 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x19 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x19 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + MixtrackPlatinumFX.deck[0].bass.input + 0xB0 + 0x19 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + MixtrackPlatinumFX.deck[1].bass.input + 0xB1 + 0x19 + + + + + + [EqualizerRack1_[Channel3]_Effect1] + MixtrackPlatinumFX.deck[2].bass.input + 0xB2 + 0x19 + + + + + + [EqualizerRack1_[Channel4]_Effect1] + MixtrackPlatinumFX.deck[3].bass.input + 0xB3 + 0x19 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x1A + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x1A + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x1A + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x1A + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x1A + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x1A + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x1A + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x1A + + + + + + [QuickEffectRack1_[Channel1]] + MixtrackPlatinumFX.deck[0].filter.input + 0xB0 + 0x1A + + + + + + [QuickEffectRack1_[Channel2]] + MixtrackPlatinumFX.deck[1].filter.input + 0xB1 + 0x1A + + + + + + [QuickEffectRack1_[Channel3]] + MixtrackPlatinumFX.deck[2].filter.input + 0xB2 + 0x1A + + + + + + [QuickEffectRack1_[Channel4]] + MixtrackPlatinumFX.deck[3].filter.input + 0xB3 + 0x1A + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pflButton.input + 0x80 + 0x1B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pflButton.input + 0x81 + 0x1B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pflButton.input + 0x82 + 0x1B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pflButton.input + 0x83 + 0x1B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x1B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x1B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x1B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x1B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pflButton.input + 0x90 + 0x1B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pflButton.input + 0x91 + 0x1B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pflButton.input + 0x92 + 0x1B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pflButton.input + 0x93 + 0x1B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x1B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x1B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x1B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x1B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x1C + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x1C + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x1C + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x1C + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x1C + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x1C + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x1C + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x1C + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].volume.input + 0xB0 + 0x1C + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].volume.input + 0xB1 + 0x1C + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].volume.input + 0xB2 + 0x1C + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].volume.input + 0xB3 + 0x1C + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x1D + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x1D + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x1D + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x1D + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x1D + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x1D + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x1D + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x1D + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x1E + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x1E + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x1E + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x1E + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x1E + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x1E + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x1E + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x1E + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x1F + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x1F + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x1F + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x1F + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x1F + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x1F + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x1F + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x1F + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x80 + 0x20 + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x81 + 0x20 + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x82 + 0x20 + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x83 + 0x20 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x20 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x20 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x20 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x20 + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x90 + 0x20 + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x91 + 0x20 + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x92 + 0x20 + + + + + + [Master] + MixtrackPlatinumFX.shiftToggle + The shift key. + 0x93 + 0x20 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x20 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x20 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x20 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x20 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x21 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x21 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x21 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x21 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x21 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x21 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x21 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x21 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x22 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x22 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x22 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x22 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x22 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x22 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x22 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x22 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x84 + 0x23 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x85 + 0x23 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x86 + 0x23 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x87 + 0x23 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].padSection.padPress + 0x94 + 0x23 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].padSection.padPress + 0x95 + 0x23 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].padSection.padPress + 0x96 + 0x23 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].padSection.padPress + 0x97 + 0x23 + + + + + + [Master] + MixtrackPlatinumFX.gains.mainGain.input + 0xBE + 0x23 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitch.inputLSB + 0xB0 + 0x29 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitch.inputLSB + 0xB1 + 0x29 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitch.inputLSB + 0xB2 + 0x29 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitch.inputLSB + 0xB3 + 0x29 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendUp.input + 0x80 + 0x2B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendUp.input + 0x81 + 0x2B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendUp.input + 0x82 + 0x2B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendUp.input + 0x83 + 0x2B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendUp.input + 0x90 + 0x2B + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendUp.input + 0x91 + 0x2B + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendUp.input + 0x92 + 0x2B + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendUp.input + 0x93 + 0x2B + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendDown.input + 0x80 + 0x2C + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendDown.input + 0x81 + 0x2C + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendDown.input + 0x82 + 0x2C + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendDown.input + 0x83 + 0x2C + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].pitchBendDown.input + 0x90 + 0x2C + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].pitchBendDown.input + 0x91 + 0x2C + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].pitchBendDown.input + 0x92 + 0x2C + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].pitchBendDown.input + 0x93 + 0x2C + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopHalf.input + 0x84 + 0x34 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopHalf.input + 0x85 + 0x34 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopHalf.input + 0x86 + 0x34 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopHalf.input + 0x87 + 0x34 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopHalf.input + 0x94 + 0x34 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopHalf.input + 0x95 + 0x34 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopHalf.input + 0x96 + 0x34 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopHalf.input + 0x97 + 0x34 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopDouble.input + 0x84 + 0x35 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopDouble.input + 0x85 + 0x35 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopDouble.input + 0x86 + 0x35 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopDouble.input + 0x87 + 0x35 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopDouble.input + 0x94 + 0x35 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopDouble.input + 0x95 + 0x35 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopDouble.input + 0x96 + 0x35 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopDouble.input + 0x97 + 0x35 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopHalf.input + 0x84 + 0x36 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopHalf.input + 0x85 + 0x36 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopHalf.input + 0x86 + 0x36 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopHalf.input + 0x87 + 0x36 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopHalf.input + 0x94 + 0x36 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopHalf.input + 0x95 + 0x36 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopHalf.input + 0x96 + 0x36 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopHalf.input + 0x97 + 0x36 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopDouble.input + 0x84 + 0x37 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopDouble.input + 0x85 + 0x37 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopDouble.input + 0x86 + 0x37 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopDouble.input + 0x87 + 0x37 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loopDouble.input + 0x94 + 0x37 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loopDouble.input + 0x95 + 0x37 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loopDouble.input + 0x96 + 0x37 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loopDouble.input + 0x97 + 0x37 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loop.input + 0x84 + 0x40 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loop.input + 0x85 + 0x40 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loop.input + 0x86 + 0x40 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loop.input + 0x87 + 0x40 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loop.input + 0x94 + 0x40 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loop.input + 0x95 + 0x40 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loop.input + 0x96 + 0x40 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loop.input + 0x97 + 0x40 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loop.input + 0x84 + 0x41 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loop.input + 0x85 + 0x41 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loop.input + 0x86 + 0x41 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loop.input + 0x87 + 0x41 + + + + + + [Channel1] + MixtrackPlatinumFX.deck[0].loop.input + 0x94 + 0x41 + + + + + + [Channel2] + MixtrackPlatinumFX.deck[1].loop.input + 0x95 + 0x41 + + + + + + [Channel3] + MixtrackPlatinumFX.deck[2].loop.input + 0x96 + 0x41 + + + + + + [Channel4] + MixtrackPlatinumFX.deck[3].loop.input + 0x97 + 0x41 + + + + + + + + [Channel1] + keylock + 0x90 + 0x0D + 0.5 + + + [Channel1] + keylock + 0x80 + 0x0D + 0.5 + + + [Channel2] + keylock + 0x91 + 0x0D + 0.5 + + + [Channel2] + keylock + 0x81 + 0x0D + 0.5 + + + [Channel3] + keylock + 0x92 + 0x0D + 0.5 + + + [Channel3] + keylock + 0x82 + 0x0D + 0.5 + + + [Channel4] + keylock + 0x93 + 0x0D + 0.5 + + + [Channel4] + keylock + 0x83 + 0x0D + 0.5 + + + + 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-Mixtrack-3-scripts.js b/res/controllers/Numark-Mixtrack-3-scripts.js index ec7b548e96e..d2cfc51c8b1 100644 --- a/res/controllers/Numark-Mixtrack-3-scripts.js +++ b/res/controllers/Numark-Mixtrack-3-scripts.js @@ -288,7 +288,7 @@ LED.prototype.onOff = function(value) { // if not set, it considers it as a switch off (default=false) // valueoff : like "value". That permits for instance with two colors (once red(on), once blue(off), once red(on), etc...) -LED.prototype.flashOn = function(num_ms_on, value, num_ms_off, flashCount, relight, valueoff) { +LED.prototype.flashOn = function(num_ms_on, value, num_ms_off, flashCount) { // stop pending timers this.flashOff(); @@ -298,8 +298,6 @@ LED.prototype.flashOn = function(num_ms_on, value, num_ms_off, flashCount, relig this.valueon = value; this.num_ms_off = num_ms_off; this.flashCount = flashCount; - this.relight = relight; - this.valueoff = valueoff; // 1st flash // This is because the permanent timer below takes @@ -320,7 +318,9 @@ LED.prototype.flashOn = function(num_ms_on, value, num_ms_off, flashCount, relig // so we don't need this part if flashcount=1 // temporary timer. The end of this timer stops the permanent flashing - this.flashTimer2 = engine.beginTimer(flashCount * (num_ms_on + num_ms_off) - num_ms_off, ()=>this.stopflash(relight), true); + if (flashCount > 0) { + this.flashTimer2 = engine.beginTimer(flashCount * (num_ms_on + num_ms_off) - num_ms_off, ()=>this.stopflash(this.relight), true); + } } }; @@ -752,7 +752,7 @@ NumarkMixtrack3.deck = function(decknum) { this.LEDs.hotCue2 = new LED(0x90 + j, leds.hotCue2); this.LEDs.hotCue3 = new LED(0x90 + j, leds.hotCue3); this.LEDs.hotCue4 = new LED(0x90 + j, leds.hotCue4); - this.LEDs.cue = new LED(0x90 + j, leds.Cue); + this.LEDs.cue = new LED(0x90 + j, leds.cue); this.LEDs.sync = new LED(0x90 + j, leds.sync); this.LEDs.play = new LED(0x90 + j, leds.play); this.LEDs.fx1 = new LED(0x90 + j, leds.fx1); @@ -971,7 +971,6 @@ NumarkMixtrack3.connectDeckControls = function(group, remove) { "hotcue_2_enabled": "NumarkMixtrack3.OnHotcueChange", "hotcue_3_enabled": "NumarkMixtrack3.OnHotcueChange", "hotcue_4_enabled": "NumarkMixtrack3.OnHotcueChange", - "track_samples": "NumarkMixtrack3.OnTrackLoaded", "vu_meter": "NumarkMixtrack3.OnVuMeterChange", "playposition": "NumarkMixtrack3.OnPlaypositionChange", "volume": "NumarkMixtrack3.OnVolumeChange", @@ -1161,7 +1160,6 @@ NumarkMixtrack3.LoadButton = function(channel, control, value, status, group) { var deck = NumarkMixtrack3.deckFromGroup(group); if (value === DOWN) { - deck.LEDs["headphones"].onOff(ON); deck.faderstart = false; if (smartPFL) { @@ -1174,7 +1172,6 @@ NumarkMixtrack3.LoadButton = function(channel, control, value, status, group) { if (deck.shiftKey) { // SHIFT + Load = fader start activated deck.faderstart = true; - deck.LEDs["headphones"].flashOn(250, ON, 250); if (!deck.trackLoaded()) { engine.setValue(deck.group, "LoadSelectedTrack", true); @@ -1986,23 +1983,6 @@ NumarkMixtrack3.OnPlaypositionChange = function(value, group, control) { } }; -NumarkMixtrack3.OnTrackLoaded = function(value, group, control) { - var deck = NumarkMixtrack3.deckFromGroup(group); - - if (value !== 0) { - if (!deck.faderstart) { - // Light up the PFL light indicating that a track is loaded - deck.LEDs["headphones"].onOff(ON); - } else { - // Flash up the PFL light button indicating that a track is loaded with fader start - deck.LEDs["headphones"].flashOn(300, ON, 300); - } - } else { - // Switch off the PFL light indicating that a track is ejected - deck.LEDs["headphones"].onOff(OFF); - } -}; - NumarkMixtrack3.OnPlayIndicatorChange = function(value, group, control) { var deck = NumarkMixtrack3.deckFromGroup(group); deck.LEDs.play.onOff((value) ? ON : OFF); diff --git a/res/controllers/Numark-Mixtrack-Platinum-FX-scripts.js b/res/controllers/Numark-Mixtrack-Platinum-FX-scripts.js new file mode 100644 index 00000000000..92b213abb1b --- /dev/null +++ b/res/controllers/Numark-Mixtrack-Platinum-FX-scripts.js @@ -0,0 +1,1884 @@ +// eslint-disable-next-line no-var +var MixtrackPlatinumFX = {}; + +// FX +// FX toggles +MixtrackPlatinumFX.toggleFXControlEnable = true; +MixtrackPlatinumFX.toggleFXControlSuper = false; + +MixtrackPlatinumFX.shiftBrowseIsZoom = false; + +// setting this to false sets tap the file bpm, but without a way to reset its dangerous +MixtrackPlatinumFX.tapChangesTempo = true; + +// pitch ranges +// add/remove/modify steps to your liking +// default step must be set in Mixxx settings +// setting is stored per deck in pitchRange.currentRangeIdx +MixtrackPlatinumFX.pitchRanges = [0.08, 0.16, 0.5]; + +MixtrackPlatinumFX.HIGH_LIGHT = 0x7F; +MixtrackPlatinumFX.LOW_LIGHT = 0x01; + +// whether the corresponding Mixxx option is enabled +// (Settings -> Preferences -> Waveforms -> Synchronize zoom level across all waveforms) +MixtrackPlatinumFX.waveformsSynced = true; + +// jogwheel +MixtrackPlatinumFX.jogScratchSensitivity = 1024; +MixtrackPlatinumFX.jogScratchAlpha = 1; // do NOT set to 2 or higher +MixtrackPlatinumFX.jogScratchBeta = 1/32; +MixtrackPlatinumFX.jogPitchSensitivity = 10; +MixtrackPlatinumFX.jogSeekSensitivity = 10000; + +// blink settings +MixtrackPlatinumFX.enableBlink = true; +MixtrackPlatinumFX.blinkDelay = 700; + +// autoloop sizes, for available values see: +// https://manual.mixxx.org/2.3/en/chapters/appendix/mixxx_controls.html#control-[ChannelN]-beatloop_X_toggle +MixtrackPlatinumFX.autoLoopSizes = [ + "0.0625", + "0.125", + "0.25", + "0.5", + "1", + "2", + "4", + "8" +]; + +// beatjump values, for available values see: +// https://manual.mixxx.org/2.3/en/chapters/appendix/mixxx_controls.html#control-[ChannelN]-beatjump_X_forward +// underscores (_) at the end are needed because numeric values (e.g. 8) have two underscores (e.g. beatjump_8_forward), +// but "beatjump_forward"/"beatjump_backward" have only one underscore +MixtrackPlatinumFX.beatJumpValues = [ + "0.0625_", + "0.125_", + "0.25_", + "0.5_", + "1_", + "2_", + "", // "beatjump_forward"/"beatjump_backward" - jump by the value selected in Mixxx GUI (4 by default) + "8_" +]; + +// dim all lights when inactive instead of turning them off +components.Button.prototype.off = MixtrackPlatinumFX.LOW_LIGHT; + +// pad modes control codes +MixtrackPlatinumFX.PadModeControls = { + HOTCUE: 0x00, + AUTOLOOP: 0x0D, + FADERCUTS: 0x07, + SAMPLE1: 0x0B, + BEATJUMP: 0x01, // DUMMY not used by controller + SAMPLE2: 0x0F, + AUTOLOOP2: 0x0E, // DUMMY not used by controller + KEYPLAY: 0x0C, // DUMMY not used by controller + HOTCUE2: 0x02, + FADERCUTS2: 0x03, // DUMMY not used by controller + FADERCUTS3: 0x04, // DUMMY not used by controller + AUTOLOOP3: 0x05, // DUMMY not used by controller +}; + +// enables 4 bottom pads "fader cuts" for 8 +MixtrackPlatinumFX.faderCutSysex8 = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0xF7]; +// enables only 4 top pads "fader cuts" +MixtrackPlatinumFX.faderCutSysex4 = [0xF0, 0x00, 0x20, 0x7F, 0x13, 0xF7]; + +// state variable, don't touch +MixtrackPlatinumFX.shifted = false; + +MixtrackPlatinumFX.initComplete=false; + +MixtrackPlatinumFX.bpms = []; +MixtrackPlatinumFX.trackBPM = function(value, group, _control) { + // file_bpm always seems to be 0? + // this doesn't work if we have to scan for bpm as it will be zero initially + // so we hook into the bpm change as well, and if we have 0 then set it to the first value seen (in bpm output) + MixtrackPlatinumFX.bpms[script.deckFromGroup(group) - 1] = engine.getValue(group, "bpm"); +}; + +MixtrackPlatinumFX.BlinkTimer=0; +MixtrackPlatinumFX.BlinkState=true; +MixtrackPlatinumFX.BlinkStateSlow=true; +MixtrackPlatinumFX.CallBacks=[]; +MixtrackPlatinumFX.CallSpeed=[]; +MixtrackPlatinumFX.BlinkStart = function(callback, slow) { + for (const i in MixtrackPlatinumFX.CallBacks) { + if (!MixtrackPlatinumFX.CallBacks[i]) { + // empty slot + MixtrackPlatinumFX.CallBacks[i]=callback; + MixtrackPlatinumFX.CallSpeed[i]=slow; + return Number(i)+1; + } + } + const idx = MixtrackPlatinumFX.CallBacks.push(callback); + MixtrackPlatinumFX.CallSpeed[idx-1]=slow; + return idx; +}; +MixtrackPlatinumFX.BlinkStop = function(index) { + MixtrackPlatinumFX.CallBacks[index-1]=null; +}; +MixtrackPlatinumFX.BlinkFunc = function() { + // toggle the global blink variables + MixtrackPlatinumFX.BlinkState = !MixtrackPlatinumFX.BlinkState; + if (MixtrackPlatinumFX.BlinkState) { + MixtrackPlatinumFX.BlinkStateSlow = !MixtrackPlatinumFX.BlinkStateSlow; + } + + // if we should be blinking the fx, then call its function + if (MixtrackPlatinumFX.FxBlinkState) { + MixtrackPlatinumFX.FxBlinkUpdateLEDs(); + } + // fire any callbacks + for (const i in MixtrackPlatinumFX.CallBacks) { + if (MixtrackPlatinumFX.CallBacks[i]) { + if (MixtrackPlatinumFX.CallSpeed[i]) { + if (MixtrackPlatinumFX.BlinkState) { + MixtrackPlatinumFX.CallBacks[i](MixtrackPlatinumFX.BlinkStateSlow); + } + } else { + MixtrackPlatinumFX.CallBacks[i](MixtrackPlatinumFX.BlinkState); + } + } + } +}; + +MixtrackPlatinumFX.init = function(id, debug) { + MixtrackPlatinumFX.id = id; + MixtrackPlatinumFX.debug = debug; + // print("init MixtrackPlatinumFX " + id + " debug: " + debug); + + // disable demo lightshow + const exitDemoSysex = [0xF0, 0x7E, 0x00, 0x06, 0x01, 0xF7]; + midi.sendSysexMsg(exitDemoSysex, exitDemoSysex.length); + + // status, extra 04 is just more device id, not sure what the 05 is + //F0 00 20 04 7F 03 01 05 F7 + + // wake (not sure what the extra 07 is for? + //F0 7E 00 07 06 01 F7 + + // I think these are the dial updates + //F0 00 20 04 7F 02 02 04 08 00 00 04 00 00 00 05 F7 + //F0 00 20 04 7F 04 01 04 00 00 00 04 00 00 00 05 F7 + //F0 00 20 04 7F 02 04 04 08 00 00 04 00 00 00 07 00 00 F7 + //F0 00 20 04 7F 03 02 04 08 00 00 04 00 00 00 05 F7 + //F0 00 20 04 7F 03 04 04 08 00 00 04 00 00 00 07 00 00 F7 + //F0 00 20 04 7F 01 04 04 08 00 00 04 00 00 00 07 00 00 F7 + //F0 00 20 04 7F 04 04 04 08 00 00 04 00 00 00 07 00 00 F7 + + // default to just the top 4 + midi.sendSysexMsg(MixtrackPlatinumFX.faderCutSysex4, MixtrackPlatinumFX.faderCutSysex4.length); + + // initialize component containers + MixtrackPlatinumFX.deck = new components.ComponentContainer(); + MixtrackPlatinumFX.effect = new components.ComponentContainer(); + let i; + for (i = 0; i < 4; i++) { + const group=`[Channel${ i+1 }]`; + MixtrackPlatinumFX.deck[i] = new MixtrackPlatinumFX.Deck(i + 1); + MixtrackPlatinumFX.updateRateRange(i, group, MixtrackPlatinumFX.pitchRanges[0]); + // refresh keylock state (the output mapping in the xml doesn't seem to do it + midi.sendShortMsg(0x80 | i, 0x0D, engine.getValue(group, "keylock")?0x7F:0x00); + midi.sendShortMsg(0x90 | i, 0x0D, engine.getValue(group, "keylock")?0x7F:0x00); + // Hook into this and save the bpm_file when loaded so we can reset it later + engine.makeConnection(group, "track_loaded", MixtrackPlatinumFX.trackBPM).trigger(); + } + for (i = 0; i < 2; i++) { + MixtrackPlatinumFX.effect[i] = new MixtrackPlatinumFX.EffectUnit((i % 2)+1); + } + // turn effect for master and headphones off to avoid confusion + engine.setValue("[EffectRack1_EffectUnit1]", "group_[Headphone]_enable", 0); + engine.setValue("[EffectRack1_EffectUnit2]", "group_[Headphone]_enable", 0); + engine.setValue("[EffectRack1_EffectUnit1]", "group_[Master]_enable", 0); + engine.setValue("[EffectRack1_EffectUnit2]", "group_[Master]_enable", 0); + + MixtrackPlatinumFX.browse = new MixtrackPlatinumFX.Browse(); + MixtrackPlatinumFX.gains = new MixtrackPlatinumFX.Gains(); + + const statusSysex = [0xF0, 0x00, 0x20, 0x7F, 0x03, 0x01, 0xF7]; + midi.sendSysexMsg(statusSysex, statusSysex.length); + + engine.makeConnection("[Channel1]", "VuMeter", MixtrackPlatinumFX.vuCallback); + engine.makeConnection("[Channel2]", "VuMeter", MixtrackPlatinumFX.vuCallback); + engine.makeConnection("[Channel3]", "VuMeter", MixtrackPlatinumFX.vuCallback); + engine.makeConnection("[Channel4]", "VuMeter", MixtrackPlatinumFX.vuCallback); + + engine.makeConnection("[Channel1]", "rate", MixtrackPlatinumFX.rateCallback).trigger(); + engine.makeConnection("[Channel2]", "rate", MixtrackPlatinumFX.rateCallback).trigger(); + engine.makeConnection("[Channel3]", "rate", MixtrackPlatinumFX.rateCallback).trigger(); + engine.makeConnection("[Channel4]", "rate", MixtrackPlatinumFX.rateCallback).trigger(); + + // trigger is needed to initialize lights to 0x01 + MixtrackPlatinumFX.deck.forEachComponent(function(component) { + component.trigger(); + }); + MixtrackPlatinumFX.effect.forEachComponent(function(component) { + component.trigger(); + }); + + // set FX buttons init light) + midi.sendShortMsg(0x98, 0x00, MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x98, 0x01, MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x98, 0x02, MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x99, 0x03, MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x99, 0x04, MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x99, 0x05, MixtrackPlatinumFX.LOW_LIGHT); + + // since we default to active on deck 1 and 2 make sure the controller does too + midi.sendShortMsg(0x90, 0x08, 0x7F); + midi.sendShortMsg(0x91, 0x08, 0x7F); + + // setup elapsed/remaining tracking + engine.makeConnection("[Controls]", "ShowDurationRemaining", MixtrackPlatinumFX.timeElapsedCallback); + MixtrackPlatinumFX.initComplete=true; + MixtrackPlatinumFX.updateArrows(true); + + MixtrackPlatinumFX.BlinkTimer = engine.beginTimer(MixtrackPlatinumFX.blinkDelay/2, MixtrackPlatinumFX.BlinkFunc); +}; + +MixtrackPlatinumFX.shutdown = function() { + const shutdownSysex = [0xF0, 0x00, 0x20, 0x7F, 0x02, 0xF7]; + let i; + + if (MixtrackPlatinumFX.BlinkTimer!==0) { + engine.stopTimer(MixtrackPlatinumFX.BlinkTimer); + MixtrackPlatinumFX.BlinkTimer=0; + } + + for (i=0; i<4; i++) { + // update spinner and position indicator + midi.sendShortMsg(0xB0 | i, 0x3F, 0); + midi.sendShortMsg(0xB0 | i, 0x06, 0); + // keylock indicator + midi.sendShortMsg(0x80 | i, 0x0D, 0x00); + // turn off bpm arrows + midi.sendShortMsg(0x80 | i, 0x0A, 0x00); // down arrow off + midi.sendShortMsg(0x80 | i, 0x09, 0x00); // up arrow off + + MixtrackPlatinumFX.sendScreenRateMidi(i+1, 0); + midi.sendShortMsg(0x90+i, 0x0e, 0); + MixtrackPlatinumFX.sendScreenBpmMidi(i+1, 0); + MixtrackPlatinumFX.sendScreenTimeMidi(i+1, 0); + MixtrackPlatinumFX.sendScreenDurationMidi(i+1, 0); + } + + // switch to decks 1 and 2 + midi.sendShortMsg(0x90, 0x08, 0x7F); + midi.sendShortMsg(0x91, 0x08, 0x7F); + + midi.sendSysexMsg(shutdownSysex, shutdownSysex.length); +}; + +MixtrackPlatinumFX.shift = function() { + MixtrackPlatinumFX.shifted = true; + MixtrackPlatinumFX.deck.shift(); + MixtrackPlatinumFX.browse.shift(); + MixtrackPlatinumFX.effect.shift(); + MixtrackPlatinumFX.gains.cueGain.shift(); +}; + +MixtrackPlatinumFX.unshift = function() { + MixtrackPlatinumFX.shifted = false; + MixtrackPlatinumFX.deck.unshift(); + MixtrackPlatinumFX.browse.unshift(); + MixtrackPlatinumFX.effect.unshift(); + MixtrackPlatinumFX.gains.cueGain.unshift(); +}; + +MixtrackPlatinumFX.allEffectOff = function() { + MixtrackPlatinumFX.effect[0].effects=[false, false, false]; + MixtrackPlatinumFX.effect[1].effects=[false, false, false]; + MixtrackPlatinumFX.FxBlinkUpdateLEDs(); + MixtrackPlatinumFX.effect[0].updateEffects(); + MixtrackPlatinumFX.effect[1].updateEffects(); +}; + +MixtrackPlatinumFX.FxBlinkUpdateLEDs = function() { + let newStates1=[false, false, false]; + let newStates2=[false, false, false]; + if (!MixtrackPlatinumFX.FxBlinkState || MixtrackPlatinumFX.BlinkState) { + newStates1=MixtrackPlatinumFX.effect[0].effects; + newStates2=MixtrackPlatinumFX.effect[1].effects; + } + midi.sendShortMsg(0x98, 0x00, newStates1[0] ? MixtrackPlatinumFX.HIGH_LIGHT:MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x98, 0x01, newStates1[1] ? MixtrackPlatinumFX.HIGH_LIGHT:MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x98, 0x02, newStates1[2] ? MixtrackPlatinumFX.HIGH_LIGHT:MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x99, 0x03, newStates2[0] ? MixtrackPlatinumFX.HIGH_LIGHT:MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x99, 0x04, newStates2[1] ? MixtrackPlatinumFX.HIGH_LIGHT:MixtrackPlatinumFX.LOW_LIGHT); + midi.sendShortMsg(0x99, 0x05, newStates2[2] ? MixtrackPlatinumFX.HIGH_LIGHT:MixtrackPlatinumFX.LOW_LIGHT); +}; + +MixtrackPlatinumFX.FxBlinkTimer=0; +MixtrackPlatinumFX.FxBlinkState=true; +MixtrackPlatinumFX.FxBlink = function() { + const start = MixtrackPlatinumFX.effect[0].isSwitchHolded || MixtrackPlatinumFX.effect[1].isSwitchHolded; + + if (start) { + MixtrackPlatinumFX.FxBlinkState = true; + MixtrackPlatinumFX.FxBlinkUpdateLEDs(); + } else { + // stop + MixtrackPlatinumFX.FxBlinkState = false; + MixtrackPlatinumFX.FxBlinkUpdateLEDs(); + } +}; + +// TODO in 2.3 it is not possible to "properly" map the FX selection buttons. +// this should be done with load_preset and QuickEffects instead (when effect +// chain preset saving/loading is available in Mixxx) +MixtrackPlatinumFX.EffectUnit = function(deckNumber) { + this.effects = [false, false, false]; + this.isSwitchHolded = false; + + this.updateEffects = function() { + if (MixtrackPlatinumFX.toggleFXControlEnable) { + for (let i = 1; i <= this.effects.length; i++) { + engine.setValue(`[EffectRack1_EffectUnit${ deckNumber }_Effect${i}]`, "enabled", this.effects[i-1]); + } + } + }; + + // switch values are: + // 0 - switch in the middle + // 1 - switch up + // 2 - switch down + this.enableSwitch = function(channel, control, value, status, group) { + this.isSwitchHolded = value !== 0; + + if (MixtrackPlatinumFX.toggleFXControlSuper) { + engine.setValue(group, "super1", Math.min(value, 1.0)); + } + + let fxDeck=deckNumber; + if (!MixtrackPlatinumFX.deck[deckNumber-1].active) { + fxDeck+=2; + } + engine.setValue("[EffectRack1_EffectUnit1]", `group_[Channel${ fxDeck }]_enable`, (value !== 0)); + engine.setValue("[EffectRack1_EffectUnit2]", `group_[Channel${ fxDeck }]_enable`, (value !== 0)); + + this.updateEffects(); + + MixtrackPlatinumFX.FxBlink(); + }; + + this.dryWetKnob = new components.Pot({ + group: `[EffectRack1_EffectUnit${ deckNumber }]`, + inKey: "mix" + }); + + this.effect1 = function(channel, control, value, status, _group) { + if (value === 0x7F) { + if (!MixtrackPlatinumFX.shifted) { + MixtrackPlatinumFX.allEffectOff(); + } + this.effects[0] = !this.effects[0]; + midi.sendShortMsg(status, control, this.effects[0] ? MixtrackPlatinumFX.HIGH_LIGHT : MixtrackPlatinumFX.LOW_LIGHT); + } + + + this.updateEffects(); + }; + + this.effect2 = function(channel, control, value, status, _group) { + if (value === 0x7F) { + if (!MixtrackPlatinumFX.shifted) { + MixtrackPlatinumFX.allEffectOff(); + } + this.effects[1] = !this.effects[1]; + midi.sendShortMsg(status, control, this.effects[1] ? MixtrackPlatinumFX.HIGH_LIGHT : MixtrackPlatinumFX.LOW_LIGHT); + } + + this.updateEffects(); + }; + + this.effect3 = function(channel, control, value, status, _group) { + if (value === 0x7F) { + if (!MixtrackPlatinumFX.shifted) { + MixtrackPlatinumFX.allEffectOff(); + } + this.effects[2] = !this.effects[2]; + midi.sendShortMsg(status, control, this.effects[2] ? MixtrackPlatinumFX.HIGH_LIGHT : MixtrackPlatinumFX.LOW_LIGHT); + } + + this.updateEffects(); + }; + + // copy paste since I'm not sure if we want to handle it like this or not + this.effectParam = new components.Encoder({ + group: `[EffectRack1_EffectUnit${ deckNumber }_Effect1]`, + shift: function() { + this.inKey = "meta"; + }, + unshift: function() { + this.inKey = "parameter1"; + }, + input: function(channel, control, value) { + this.inSetParameter(this.inGetParameter() + this.inValueScale(value)); + }, + inValueScale: function(value) { + return (value < 0x40) ? 0.05 : -0.05; + } + }); + this.effectParam2 = new components.Encoder({ + group: `[EffectRack1_EffectUnit${ deckNumber }_Effect2]`, + shift: function() { + this.inKey = "meta"; + }, + unshift: function() { + this.inKey = "parameter1"; + }, + input: function(channel, control, value) { + this.inSetParameter(this.inGetParameter() + this.inValueScale(value)); + }, + inValueScale: function(value) { + return (value < 0x40) ? 0.05 : -0.05; + } + }); + this.effectParam3 = new components.Encoder({ + group: `[EffectRack1_EffectUnit${ deckNumber }_Effect3]`, + shift: function() { + this.inKey = "meta"; + }, + unshift: function() { + this.inKey = "parameter1"; + }, + input: function(channel, control, value) { + this.inSetParameter(this.inGetParameter() + this.inValueScale(value)); + }, + inValueScale: function(value) { + return (value < 0x40) ? 0.05 : -0.05; + } + }); +}; + + +MixtrackPlatinumFX.EffectUnit.prototype = new components.ComponentContainer(); + +MixtrackPlatinumFX.activeForTap = function(value) { + // fuzzy logic + // to tap we probably want a playing deck + // and we probably don't want it "live" + // we will need it "active" + // so best is a playing deck with pfl = 5 + // next best a stopped deck with pfl = 4 + // then a playing deck = 3 + // then a stopped deck without pfl (which by this point is any loaded) = 2 + // and as a fallback a deck that isn't active + // if there are multiple then the lowest number wins (1,2,3,4) + // if no decks with loaded tracks then -1 so caller should check for that + let i=0; + let winner=-1; + let winnerScore=0; + for (i=0; i<4; i++) { + if (engine.getValue(`[Channel${ i+1 }]`, "track_loaded")) { + if (MixtrackPlatinumFX.deck[i].active) { + var localscore=0; + if (engine.getValue(`[Channel${ i+1 }]`, "pfl")) { + if (engine.getValue(`[Channel${ i+1 }]`, "play")) { + localscore=5; + } else { + localscore=4; + } + } else { + if (engine.getValue(`[Channel${ i+1 }]`, "play")) { + localscore=3; + } else { + localscore=2; + } + } + } else { + localscore=1; + } + if (localscore>winnerScore) { + winnerScore=localscore; + winner=i; + } + } + } + + if (winner>=0) { + if (value>0) { + MixtrackPlatinumFX.updateArrows(false, true, winner); + } else { + MixtrackPlatinumFX.updateArrows(true); + } + } + + return winner; +}; + +MixtrackPlatinumFX.Deck = function(number) { + components.Deck.call(this, number); + + const channel = number - 1; + const deck = this; + this.scratchModeEnabled = true; + this.active = (number === 1 || number === 2); + + this.setActive = function(active) { + this.active = active; + + if (!active) { + // trigger soft takeover on the pitch control + this.pitch.disconnect(); + } + }; + + this.bpm = new components.Component({ + outKey: "bpm", + output: function(value, group, _control) { + if (MixtrackPlatinumFX.bpms[script.deckFromGroup(group) - 1]===0) { + MixtrackPlatinumFX.bpms[script.deckFromGroup(group) - 1] = engine.getValue(group, "bpm"); + } + MixtrackPlatinumFX.sendScreenBpmMidi(number, Math.round(value * 100)); + }, + }); + + this.duration = new components.Component({ + outKey: "duration", + output: function(duration, _group, _control) { + // update duration + MixtrackPlatinumFX.sendScreenDurationMidi(number, duration * 1000); + + // when the duration changes, we need to update the play position + deck.position.trigger(); + }, + }); + + this.position = new components.Component({ + outKey: "playposition", + output: function(playposition, _group, _control) { + // the controller appears to expect a value in the range of 0-52 + // representing the position of the track. Here we send a message to the + // controller to update the position display with our current position. + let pos = Math.round(playposition * 52); + if (pos < 0) { + pos = 0; + } + midi.sendShortMsg(0xB0 | channel, 0x3F, pos); + + // get the current duration + const duration = deck.duration.outGetValue(); + + // update the time display + const time = MixtrackPlatinumFX.timeMs(number, playposition, duration); + MixtrackPlatinumFX.sendScreenTimeMidi(number, time); + + // update the spinner (range 64-115, 52 values) + // + // the visual spinner in the mixxx interface takes 1.8 seconds to loop + // (60 seconds/min divided by 33 1/3 revolutions per min) + const period = 60 / (33+1/3); + const midiResolution = 52; // the controller expects a value range of 64-115 + const timeElapsed = duration * playposition; + let spinner = Math.round(timeElapsed % period * (midiResolution / period)); + if (spinner < 0) { + spinner += 115; + } else { + spinner += 64; + } + + midi.sendShortMsg(0xB0 | channel, 0x06, spinner); + }, + }); + + this.playButton = new components.PlayButton({ + midi: [0x90 + channel, 0x00], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x04, + }); + + this.playButtonbeatgrid = function(channel, control, value, status, group) { + engine.setValue(group, "beats_translate_curpos", value?1:0); + }; + + + this.cueButton = new components.CueButton({ + midi: [0x90 + channel, 0x01], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x04 + }); + + this.syncButton = new components.SyncButton({ + midi: [0x90 + channel, 0x02], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x01 + }); + + // we get two midi callbacks for tap, but double taps will be confusing so we just ignore the second set + if (number===1) { + if (MixtrackPlatinumFX.tapChangesTempo) { + this.tap = new components.Button({ + unshift: function() { + this.disconnect(); + this.input = function(channel, control, value, _status, _group) { + const tapch=MixtrackPlatinumFX.activeForTap(value)+1; + if (tapch) { + if (value>0) { + const prelen = bpm.tap.length; + const predelta = bpm.previousTapDelta; + bpm.tapButton(tapch); + // if the array reset, or changed then the tap was "accepted" + if ((bpm.tap.length===0) || (bpm.tap.length!==prelen) || predelta!==bpm.previousTapDelta) { + this.send(this.outValueScale(value)); + } else { + this.send(0); + } + } else { + this.send(this.outValueScale(value)); + } + } + }; + }, + shift: function() { + // reset rate to 0 (i.e. no tempo change) + this.disconnect(); + this.input = function(channel, control, value, _status, _group) { + const tapch=MixtrackPlatinumFX.activeForTap(value)+1; + if (value>0 && tapch) { + engine.setValue(`[Channel${ tapch }]`, "rate", 0); + } + }; + }, + midi: [0x88, 0x09] + }); + this.tap.output(0); + } else { + this.tap = new components.Button({ + shift: function() { + this.disconnect(); + this.input = function(channel, control, value, _status, _group) { + const tapch=MixtrackPlatinumFX.activeForTap(value)+1; + if (tapch) { + // This doesn't work, it doesn't set the bpm, it sets the rate to achieve this bpm + if (value>0 && MixtrackPlatinumFX.bpms[tapch-1]) { + engine.setValue(`[Channel${ tapch }]`, "bpm", MixtrackPlatinumFX.bpms[tapch-1]); + } + this.send(this.outValueScale(value)); + } + }; + }, + unshift: function() { + /* + this.disconnect(); + this.input = components.Button.prototype.input; + this.inKey = "bpm_tap"; + this.outKey = "bpm_tap"; + this.connect(); + this.trigger(); + */ + this.input = function(channel, control, value, _status, _group) { + const tapch=MixtrackPlatinumFX.activeForTap(value)+1; + if (tapch) { + engine.setValue(`[Channel${ tapch }]`, "bpm_tap", value); + this.send(this.outValueScale(value)); + } + }; + }, + //key: "bpm_tap", + midi: [0x88, 0x09] + }); + this.tap.output(0); + } + } else { + // ignore callbacks other than the first + this.tap = new components.Button({ + // null, ignore the second mapping + input: function(_channel, _control, _value, _status, _group) { }, + }); + } + + this.pflButton = new components.Button({ + shift: function() { + this.disconnect(); + this.inKey = "slip_enabled"; + this.outKey = "slip_enabled"; + this.connect(); + this.trigger(); + }, + unshift: function() { + this.disconnect(); + this.inKey = "pfl"; + this.outKey = "pfl"; + this.connect(); + this.trigger(); + }, + type: components.Button.prototype.types.toggle, + midi: [0x90 + channel, 0x1B], + }); + + this.loadButton = new components.Button({ + shift: function() { + this.inKey = "eject"; + }, + unshift: function() { + this.inKey = "LoadSelectedTrack"; + }, + }); + + this.volume = new components.Pot({ + inKey: "volume" + }); + + this.treble = new components.Pot({ + group: `[EqualizerRack1_${ this.currentDeck }_Effect1]`, + inKey: "parameter3" + }); + + this.mid = new components.Pot({ + group: `[EqualizerRack1_${ this.currentDeck }_Effect1]`, + inKey: "parameter2" + }); + + this.bass = new components.Pot({ + group: `[EqualizerRack1_${ this.currentDeck }_Effect1]`, + inKey: "parameter1" + }); + + this.filter = new components.Pot({ + group: `[QuickEffectRack1_${ this.currentDeck }]`, + inKey: "super1" + }); + + this.gain = new components.Pot({ + inKey: "pregain" + }); + + this.pitch = new components.Pot({ + inKey: "rate", + invert: true + }); + + this.padSection = new MixtrackPlatinumFX.PadSection(number); + + this.loop = new components.Button({ + outKey: "loop_enabled", + midi: [0x94 + channel, 0x40], + input: function(channel, control, value, status, group) { + if (!this.isPress(channel, control, value)) { + return; + } + + if (!MixtrackPlatinumFX.shifted) { + if (engine.getValue(group, "loop_enabled") === 0) { + script.triggerControl(group, "beatloop_activate"); + } else { + script.triggerControl(group, "beatlooproll_activate"); + } + } else { + if (engine.getValue(group, "loop_enabled") === 0) { + script.triggerControl(group, "reloop_toggle"); + } else { + script.triggerControl(group, "reloop_andstop"); + } + } + }, + shiftControl: true, + sendShifted: true, + shiftOffset: 0x01 + }); + + this.loopHalf = new components.Button({ + midi: [0x94 + channel, 0x34], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x02, + shift: function() { + this.disconnect(); + this.inKey = "loop_in"; + this.outKey = "loop_in"; + this.connect(); + this.trigger(); + }, + unshift: function() { + this.disconnect(); + this.inKey = "loop_halve"; + this.outKey = "loop_halve"; + this.connect(); + this.trigger(); + } + }); + + this.loopDouble = new components.Button({ + midi: [0x94 + channel, 0x35], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x02, + shift: function() { + this.disconnect(); + this.inKey = "loop_out"; + this.outKey = "loop_out"; + this.connect(); + this.trigger(); + }, + unshift: function() { + this.disconnect(); + this.inKey = "loop_double"; + this.outKey = "loop_double"; + this.connect(); + this.trigger(); + } + }); + + this.scratchToggle = new components.Button({ + // disconnects/connects are needed for the following scenario: + // 1. scratch mode is enabled (light on) + // 2. shift down + // 3. scratch button down + // 4. shift up + // 5. scratch button up + // scratch mode light is now off, should be on + key: "reverseroll", + midi: [0x90 + channel, 0x07], + unshift: function() { + this.disconnect(); // disconnect reverseroll light + this.input = function(channel, control, value) { + if (!this.isPress(channel, control, value)) { + return; + } + deck.scratchModeEnabled = !deck.scratchModeEnabled; + + // change the scratch mode status light + this.send(deck.scratchModeEnabled ? this.on : this.off); + }; + // set current scratch mode status light + this.send(deck.scratchModeEnabled ? this.on : this.off); + }, + sendShifted: false + }); + + this.pitchBendUp = new components.Button({ + shiftControl: true, + shiftOffset: 0x20, + shift: function() { + this.type = components.Button.prototype.types.toggle; + this.inKey = "keylock"; + }, + unshift: function() { + this.type = components.Button.prototype.types.push; + this.inKey = "rate_temp_up"; + } + }); + + this.pitchBendDown = new components.Button({ + currentRangeIdx: 0, + shift: function() { + this.input = function(channel, control, value) { + if (!this.isPress(channel, control, value)) { + return; + } + this.currentRangeIdx = (this.currentRangeIdx + 1) % MixtrackPlatinumFX.pitchRanges.length; + MixtrackPlatinumFX.updateRateRange(channel, this.group, MixtrackPlatinumFX.pitchRanges[this.currentRangeIdx]); + }; + }, + unshift: function() { + this.inKey = "rate_temp_down"; + this.input = components.Button.prototype.input; + } + }); + + this.setBeatgrid = new components.Button({ + key: "beats_translate_curpos", + midi: [0x98 + channel, 0x01 + (channel * 3)] + }); + + this.reconnectComponents(function(component) { + if (component.group === undefined) { + component.group = this.currentDeck; + } + }); +}; + +MixtrackPlatinumFX.Deck.prototype = new components.Deck(); + +MixtrackPlatinumFX.PadSection = function(deckNumber) { + components.ComponentContainer.call(this); + + this.blinkTimer = 0; + + this.longPressTimer = 0; + this.longPressMode = 0; + this.longPressHeld = false; + + // initialize leds + const ledOff = components.Button.prototype.off; + const ledOn = components.Button.prototype.on; + midi.sendShortMsg(0x93 + deckNumber, 0x00, ledOn); // hotcue + midi.sendShortMsg(0x93 + deckNumber, 0x0D, ledOff); // auto loop + midi.sendShortMsg(0x93 + deckNumber, 0x07, ledOff); // "fader cuts" + midi.sendShortMsg(0x93 + deckNumber, 0x0B, ledOff); // sample1 + + // shifted leds + midi.sendShortMsg(0x93 + deckNumber, 0x0F, ledOff); // sample2 + midi.sendShortMsg(0x93 + deckNumber, 0x02, ledOff); // beatjump + + this.modes = {}; + this.modes[MixtrackPlatinumFX.PadModeControls.HOTCUE] = new MixtrackPlatinumFX.ModeHotcue(deckNumber, false); + this.modes[MixtrackPlatinumFX.PadModeControls.AUTOLOOP] = new MixtrackPlatinumFX.ModeAutoLoop(deckNumber, false); + this.modes[MixtrackPlatinumFX.PadModeControls.FADERCUTS] = new MixtrackPlatinumFX.ModeFaderCuts(deckNumber, false); + this.modes[MixtrackPlatinumFX.PadModeControls.FADERCUTS2] = new MixtrackPlatinumFX.ModeFaderCuts(deckNumber, 1); + this.modes[MixtrackPlatinumFX.PadModeControls.FADERCUTS3] = new MixtrackPlatinumFX.ModeFaderCuts(deckNumber, 2); + this.modes[MixtrackPlatinumFX.PadModeControls.SAMPLE1] = new MixtrackPlatinumFX.ModeSample(deckNumber, false); + this.modes[MixtrackPlatinumFX.PadModeControls.BEATJUMP] = new MixtrackPlatinumFX.ModeBeatjump(deckNumber, 2); + this.modes[MixtrackPlatinumFX.PadModeControls.SAMPLE2] = new MixtrackPlatinumFX.ModeSample(deckNumber, 1); + this.modes[MixtrackPlatinumFX.PadModeControls.AUTOLOOP2] = new MixtrackPlatinumFX.ModeAutoLoop(deckNumber, 1); + this.modes[MixtrackPlatinumFX.PadModeControls.KEYPLAY] = new MixtrackPlatinumFX.ModeKeyPlay(deckNumber, 2); + this.modes[MixtrackPlatinumFX.PadModeControls.HOTCUE2] = new MixtrackPlatinumFX.ModeHotcue(deckNumber, 1); + this.modes[MixtrackPlatinumFX.PadModeControls.AUTOLOOP3] = new MixtrackPlatinumFX.ModeCueLoop(deckNumber, 2); + + this.modeButtonPress = function(channel, control, value) { + // always stop the time, its either the off, which should stop it + // or another button has been pressed, so that's now the "focus" + if (this.longPressTimer!==0) { + // release button, leave the timer going, but mark as not held so it won't go off (still using it for double press) + this.longPressHeld = false; + if (value === 0x7F) { + engine.stopTimer(this.longPressTimer); + // there was a time, see if its for this button, if it is then this is a double press so active the same as if it has been a long press + // cancel the timer eitherway + if (control===MixtrackPlatinumFX.PadModeControls.SAMPLE1 && this.longPressMode===MixtrackPlatinumFX.PadModeControls.KEYPLAY) { + this.setMode(channel, this.longPressMode); + this.longPressTimer = 0; + return; + } + if (control===MixtrackPlatinumFX.PadModeControls.HOTCUE && this.longPressMode===MixtrackPlatinumFX.PadModeControls.BEATJUMP) { + this.setMode(channel, this.longPressMode); + this.longPressTimer = 0; + return; + } + if (control===MixtrackPlatinumFX.PadModeControls.FADERCUTS && this.longPressMode===MixtrackPlatinumFX.PadModeControls.FADERCUTS3) { + this.setMode(channel, this.longPressMode); + this.longPressTimer = 0; + return; + } + if (control===MixtrackPlatinumFX.PadModeControls.AUTOLOOP && this.longPressMode===MixtrackPlatinumFX.PadModeControls.AUTOLOOP3) { + this.setMode(channel, this.longPressMode); + this.longPressTimer = 0; + return; + } + this.longPressTimer = 0; + } + } + + if (value !== 0x7F) { + return; + } + this.setMode(channel, control); + }; + + this.padPress = function(channel, control, value, status, group) { + const i = (control - 0x14) % 8; + this.currentMode.pads[i].input(channel, control, value, status, group); + }; + + this.setMode = function(channel, control) { + let ctrl2=control; + if (ctrl2===MixtrackPlatinumFX.PadModeControls.SAMPLE2 && this.currentMode.name===MixtrackPlatinumFX.PadModeControls.KEYPLAY) { + // this specific case we aren't setting a mode, we change the parameter for pitch play start + this.currentMode.nextRange(); + return; + } + // The mixer doesn't consider these to have shift, so we have to make it up by looking at shift and the original key + if (ctrl2===MixtrackPlatinumFX.PadModeControls.AUTOLOOP && MixtrackPlatinumFX.shifted) { + ctrl2=MixtrackPlatinumFX.PadModeControls.AUTOLOOP2; + } + if (ctrl2===MixtrackPlatinumFX.PadModeControls.FADERCUTS && MixtrackPlatinumFX.shifted) { + ctrl2=MixtrackPlatinumFX.PadModeControls.FADERCUTS2; + } + + // this stops the timeout from setting another timer! + if (this.longPressTimer===0) { + if (ctrl2===MixtrackPlatinumFX.PadModeControls.SAMPLE1 || ctrl2===MixtrackPlatinumFX.PadModeControls.HOTCUE || ctrl2===MixtrackPlatinumFX.PadModeControls.FADERCUTS || ctrl2===MixtrackPlatinumFX.PadModeControls.AUTOLOOP) { + if (ctrl2===MixtrackPlatinumFX.PadModeControls.AUTOLOOP) { + this.longPressMode=MixtrackPlatinumFX.PadModeControls.AUTOLOOP3; + } + if (ctrl2===MixtrackPlatinumFX.PadModeControls.SAMPLE1) { + this.longPressMode=MixtrackPlatinumFX.PadModeControls.KEYPLAY; + } + if (ctrl2===MixtrackPlatinumFX.PadModeControls.HOTCUE) { + this.longPressMode=MixtrackPlatinumFX.PadModeControls.BEATJUMP; + } + if (ctrl2===MixtrackPlatinumFX.PadModeControls.FADERCUTS) { + this.longPressMode=MixtrackPlatinumFX.PadModeControls.FADERCUTS3; + } + this.longPressHeld = true; + + const thirdaryMode = this; // Can't use 'this' in function below + this.longPressTimer = engine.beginTimer(components.Button.prototype.longPressTimeout*2, function() { + if (thirdaryMode.longPressHeld) { + thirdaryMode.setMode(channel, thirdaryMode.longPressMode); + } + thirdaryMode.longPressTimer = 0; + thirdaryMode.longPressHeld = false; + }, true); + } + } + + const newMode = this.modes[ctrl2]; + if ((this.currentMode.control === newMode.control) && (this.currentMode.secondaryMode === newMode.secondaryMode)) { + return; // selected mode already set, no need to change anything + } + + this.currentMode.forEachComponent(function(component) { + component.disconnect(); + }); + + // set the correct shift state for new mode + if (this.isShifted) { + newMode.shift(); + } else { + newMode.unshift(); + } + + newMode.forEachComponent(function(component) { + component.connect(); + component.trigger(); + }); + if (newMode.activate) { + newMode.activate(); + } + + if (MixtrackPlatinumFX.enableBlink) { + // stop blinking if old mode was secondary mode + if (this.currentMode.secondaryMode) { + this.blinkLedOff(); + + // disable light on the old control in case it ended up in 0x7F state + midi.sendShortMsg(0x90 + channel, this.currentMode.unshiftedControl, 0x01); + } + + // start blinking if new mode is a secondary mode + if (newMode.secondaryMode) { + this.blinkLedOn(0x90 + channel, newMode.unshiftedControl, newMode.lightOnValue, newMode.secondaryMode); + } + } + + // light off on old mode select button + midi.sendShortMsg(0x90 + channel, this.currentMode.control, 0x01); + + // light on on new mode select button + midi.sendShortMsg(0x90 + channel, newMode.control, newMode.lightOnValue); + + this.currentMode = newMode; + }; + + // start an infinite timer that toggles led state + this.blinkLedOn = function(midi1, midi2, onVal, secondMode) { + this.blinkLedOff(); + this.blinkTimer = MixtrackPlatinumFX.BlinkStart(function(isOn) { + midi.sendShortMsg(midi1, midi2, isOn ? onVal : 0x01); + }, (secondMode!==2)); + }; + + // stop the blink timer + this.blinkLedOff = function() { + if (this.blinkTimer === 0) { + return; + } + + MixtrackPlatinumFX.BlinkStop(this.blinkTimer); + this.blinkTimer = 0; + }; + + this.disablePadLights = function() { + for (let i = 0; i < 16; i++) { // 0-7 = unshifted; 8-15 = shifted + midi.sendShortMsg(0x93 + deckNumber, 0x14 + i, 0x01); + } + }; + + this.currentMode = this.modes[MixtrackPlatinumFX.PadModeControls.HOTCUE]; +}; +MixtrackPlatinumFX.PadSection.prototype = Object.create(components.ComponentContainer.prototype); + +MixtrackPlatinumFX.ModeHotcue = function(deckNumber, secondaryMode) { + components.ComponentContainer.call(this); + + this.control = MixtrackPlatinumFX.PadModeControls.HOTCUE; + this.unshiftedControl = MixtrackPlatinumFX.PadModeControls.HOTCUE; + this.secondaryMode = secondaryMode; + this.lightOnValue = 0x7F; + + this.name = MixtrackPlatinumFX.PadModeControls.HOTCUE; + let offset=0; + if (secondaryMode===1) { + this.name = MixtrackPlatinumFX.PadModeControls.HOTCUE2; + this.control = MixtrackPlatinumFX.PadModeControls.HOTCUE2; + offset=8; + } + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 8; i++) { + this.pads[i] = new components.HotcueButton({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + number: i + 1 + offset, + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + outConnect: false + }); + } +}; +MixtrackPlatinumFX.ModeHotcue.prototype = Object.create(components.ComponentContainer.prototype); + +MixtrackPlatinumFX.ModeAutoLoop = function(deckNumber, secondaryMode) { + components.ComponentContainer.call(this); + + this.name = MixtrackPlatinumFX.PadModeControls.AUTOLOOP; + if (secondaryMode) { + this.name = MixtrackPlatinumFX.PadModeControls.AUTOLOOP2; + } + this.control = MixtrackPlatinumFX.PadModeControls.AUTOLOOP; + this.unshiftedControl = MixtrackPlatinumFX.PadModeControls.AUTOLOOP; + this.secondaryMode = secondaryMode; + this.lightOnValue = 0x7F; + + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 8; i++) { + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + size: MixtrackPlatinumFX.autoLoopSizes[i], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + shift: function() { + if (!secondaryMode) { + this.inKey = `beatlooproll_${ this.size }_activate`; + this.outKey = `beatlooproll_${ this.size }_activate`; + } else { + this.inKey = `beatloop_${ this.size }_toggle`; + this.outKey = `beatloop_${ this.size }_enabled`; + } + }, + unshift: function() { + if (!secondaryMode) { + this.inKey = `beatloop_${ this.size }_toggle`; + this.outKey = `beatloop_${ this.size }_enabled`; + } else { + this.inKey = `beatlooproll_${ this.size }_activate`; + this.outKey = `beatlooproll_${ this.size }_activate`; + } + }, + outConnect: false + }); + } +}; +MixtrackPlatinumFX.ModeAutoLoop.prototype = Object.create(components.ComponentContainer.prototype); + +MixtrackPlatinumFX.ModeCueLoop = function(deckNumber, secondaryMode) { + components.ComponentContainer.call(this); + + this.name = MixtrackPlatinumFX.PadModeControls.AUTOLOOP; + if (secondaryMode) { + this.name = MixtrackPlatinumFX.PadModeControls.AUTOLOOP3; + } + this.control = MixtrackPlatinumFX.PadModeControls.AUTOLOOP; + this.unshiftedControl = MixtrackPlatinumFX.PadModeControls.AUTOLOOP; + this.secondaryMode = secondaryMode; + this.lightOnValue = 0x7F; + + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 8; i++) { + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + size: MixtrackPlatinumFX.autoLoopSizes[i], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + keynum: i, + shift: function() { + this.input=function(channel, control, value, _status, _group) { + engine.setValue(this.group, "slip_enabled", value); + engine.setValue(this.group, `hotcue_${ this.keynum+1 }_activate`, value); + engine.setValue(this.group, "beatlooproll_activate", value); + midi.sendShortMsg(this.midi[0], this.midi[1] + this.shiftOffset, this.outValueScale(engine.getValue(this.group, `hotcue_${ this.keynum+1 }_enabled`))); + }; + midi.sendShortMsg(this.midi[0], this.midi[1] + this.shiftOffset, this.outValueScale(engine.getValue(this.group, `hotcue_${ this.keynum+1 }_enabled`))); + }, + unshift: function() { + this.input=function(channel, control, value, _status, _group) { + if (value > 0) { + engine.setValue(this.group, `hotcue_${ this.keynum+1 }_activate`, value); + script.triggerControl(this.group, "beatloop_activate"); + } else { + engine.setValue(this.group, `hotcue_${ this.keynum+1 }_activate`, value); + } + midi.sendShortMsg(this.midi[0], this.midi[1], this.outValueScale(engine.getValue(this.group, `hotcue_${ this.keynum+1 }_enabled`))); + }; + midi.sendShortMsg(this.midi[0], this.midi[1], this.outValueScale(engine.getValue(this.group, `hotcue_${ this.keynum+1 }_enabled`))); + }, + outConnect: false + }); + } +}; +MixtrackPlatinumFX.ModeCueLoop.prototype = Object.create(components.ComponentContainer.prototype); + +MixtrackPlatinumFX.mykey=0; +MixtrackPlatinumFX.ModeKeyPlay = function(deckNumber, secondaryMode) { + components.ComponentContainer.call(this); + + this.name = MixtrackPlatinumFX.PadModeControls.KEYPLAY; + this.control = MixtrackPlatinumFX.PadModeControls.SAMPLE1; + this.unshiftedControl = MixtrackPlatinumFX.PadModeControls.SAMPLE1; + this.secondaryMode = secondaryMode; + this.lightOnValue = 0x7F; + + this.nextRange = function() { + switch (this.pads.keyshiftStart) { + case 0: + this.pads.keyshiftStart=4; + break; + case 4: + this.pads.keyshiftStart=7; + break; + case 7: + this.pads.keyshiftStart=0; + break; + default: + this.pads.keyshiftStart=4; + break; + } + }; + + this.pads = new components.ComponentContainer(); + const parentPads_ = this.pads; + this.pads.cueP=1; + this.pads.keyshiftStart=4; + for (let i = 0; i < 8; i++) { + this.pads[i] = new components.Button({ + parentPads: parentPads_, + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + shiftOffset: 0x08, + keynum: i, + shift: function() { + this.input=function(channel, control, value, _status, _group) { + if (value>0) { + this.parentPads.cueP=this.keynum+1; + } + }; + this.off=components.Button.prototype.off; + midi.sendShortMsg(this.midi[0], this.midi[1] + this.shiftOffset, this.outValueScale(engine.getValue(this.group, `hotcue_${ this.keynum+1 }_enabled`))); + }, + unshift: function() { + // serato has them the opposite way to how I'd expect so shift the two rows around + const thiskeynum=((this.keynum+4)%8); + this.thiskey=thiskeynum-this.parentPads.keyshiftStart; + this.input=function(channel, control, value, _status, _group) { + if (value>0) { + engine.setValue(this.group, "pitch_adjust", this.thiskey); + MixtrackPlatinumFX.mykey=this.keynum; + this.parentPads.forEachComponent(function(apad) { apad.output(0); }); + this.output(value); + } else { + if (this.keynum===MixtrackPlatinumFX.mykey) { + //engine.setValue(this.group,"key",engine.getValue(this.group,"file_key")); + } + } + engine.setValue(this.group, `hotcue_${ this.parentPads.cueP }_activate`, value); + }; + this.off=thiskeynum===this.parentPads.keyshiftStart?5:components.Button.prototype.off; + if (engine.getValue(this.group, "pitch_adjust")===this.thiskey) { + this.output(0x7F); + } else { + this.output(0); + } + }, + trigger: function() { + this.output(0); + + }, + }); + } +}; +MixtrackPlatinumFX.ModeKeyPlay.prototype = Object.create(components.ComponentContainer.prototype); + +// when pads are in "fader cuts" mode, they rapidly move the crossfader. +// holding a pad activates a "fader cut", releasing it causes the GUI crossfader +// to return to the position of physical crossfader +MixtrackPlatinumFX.ModeFaderCuts = function(deckNumber, secondaryMode) { + components.ComponentContainer.call(this); + + this.name = MixtrackPlatinumFX.PadModeControls.FADERCUTS; + if (secondaryMode===1) { + this.name = MixtrackPlatinumFX.PadModeControls.FADERCUTS2; + } + if (secondaryMode===2) { + this.name = MixtrackPlatinumFX.PadModeControls.FADERCUTS3; + } + this.control = MixtrackPlatinumFX.PadModeControls.FADERCUTS; + this.unshiftedControl = MixtrackPlatinumFX.PadModeControls.FADERCUTS; + this.secondaryMode = secondaryMode; + this.lightOnValue = 0x09; // for "fader cuts" 0x09 works better than 0x7F for some reason (0x7F turns the other lamps to a bit brighter) + + this.activate = function() { + if (this.secondaryMode===1) { + midi.sendSysexMsg(MixtrackPlatinumFX.faderCutSysex8, MixtrackPlatinumFX.faderCutSysex8.length); + } else { + midi.sendSysexMsg(MixtrackPlatinumFX.faderCutSysex4, MixtrackPlatinumFX.faderCutSysex4.length); + } + }; + + // fadercut pads are controlled by hardware of firmware in this mode + let numFader=4; + if (secondaryMode===1) { + numFader=8; + } + this.pads = new components.ComponentContainer(); + let i; + for (i = 0; i < numFader; i++) { + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + input: function(channel, control, value, _status, _group) { + this.output(value); + }, + trigger: function() { + // in "fader cuts" mode pad lights need to be disabled manually, + // as pads are controlled by hardware or firmware in this mode + // and don't have associated controls. without this, lights from + // previously selected mode would still be on after changing mode + // to "fader cuts" + this.output(0); + }, + outConnect: false, + }); + } + if (secondaryMode===false) { + i=4; + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + key: "play_stutter", + outConnect: false, + }); + i++; + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + key: "start", + outConnect: false, + }); + i++; + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + key: "back", + outConnect: false, + }); + i++; + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + key: "fwd", + outConnect: false, + }); + } + if (secondaryMode===2) { + i=4; + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + key: "reverseroll", + outConnect: false, + }); + i++; + this.pads[i] = new components.Button({ + type: 2, + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + key: "reverse", + outConnect: false, + }); + i++; + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + shift: function() { + this.disconnect(); + this.inKey = "reset_key"; + this.outKey = "reset_key"; + }, + unshift: function() { + this.disconnect(); + this.inKey = "sync_key"; + this.outKey = "sync_key"; + }, + outConnect: false, + }); + i++; + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + outConnect: false, + unshift: function() { + this.disconnect(); + this.input = function(channel, control, value, _status, _group) { + if (value>0) { + const prelen = bpm.tap.length; + const predelta = bpm.previousTapDelta; + bpm.tapButton(deckNumber); + // if the array reset, or changed then the tap was "accepted" + if ((bpm.tap.length===0) || (bpm.tap.length!==prelen) || predelta!==bpm.previousTapDelta) { + this.send(this.outValueScale(value)); + } else { + this.send(0); + } + } else { + this.send(this.outValueScale(value)); + } + }; + }, + shift: function() { + // reset rate to 0 (i.e. no tempo change) + this.disconnect(); + this.input = function(channel, control, value, _status, _group) { + if (value>0) { + engine.setValue(this.group, "rate", 0); + } + }; + }, + }); + } +}; + +MixtrackPlatinumFX.ModeFaderCuts.prototype = Object.create(components.ComponentContainer.prototype); + +MixtrackPlatinumFX.ModeSample = function(deckNumber, secondaryMode) { + components.ComponentContainer.call(this); + + if (!secondaryMode) { + // samples 1-8 + this.name = MixtrackPlatinumFX.PadModeControls.SAMPLE1; + this.control = MixtrackPlatinumFX.PadModeControls.SAMPLE1; + this.firstSampleNumber = 1; + } else { + // samples 9-16 + this.name = MixtrackPlatinumFX.PadModeControls.SAMPLE2; + this.control = MixtrackPlatinumFX.PadModeControls.SAMPLE2; + this.unshiftedControl = MixtrackPlatinumFX.PadModeControls.SAMPLE1; + this.firstSampleNumber = 9; + } + this.secondaryMode = secondaryMode; + this.lightOnValue = 0x7F; + + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 8; i++) { + this.pads[i] = new components.SamplerButton({ + midi: [0x93 + deckNumber, 0x14 + i], + number: this.firstSampleNumber + i, + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + outConnect: false, + loaded: 0x05, + looping: 0x0F, + playing: 0x0F, + }); + } +}; +MixtrackPlatinumFX.ModeSample.prototype = Object.create(components.ComponentContainer.prototype); + +MixtrackPlatinumFX.ModeBeatjump = function(deckNumber, secondaryMode) { + components.ComponentContainer.call(this); + + this.name = MixtrackPlatinumFX.PadModeControls.BEATJUMP; + this.control = MixtrackPlatinumFX.PadModeControls.HOTCUE; + this.secondaryMode = secondaryMode; + this.unshiftedControl = MixtrackPlatinumFX.PadModeControls.HOTCUE; + this.lightOnValue = 0x7F; + + this.pads = new components.ComponentContainer(); + for (let i = 0; i < 8; i++) { + this.pads[i] = new components.Button({ + group: `[Channel${ deckNumber }]`, + midi: [0x93 + deckNumber, 0x14 + i], + size: MixtrackPlatinumFX.beatJumpValues[i], + shiftControl: true, + sendShifted: true, + shiftOffset: 0x08, + shift: function() { + this.disconnect(); + this.inKey = `beatjump_${ this.size }backward`; + this.outKey = `beatjump_${ this.size }backward`; + this.connect(); + this.trigger(); + }, + unshift: function() { + this.disconnect(); + this.inKey = `beatjump_${ this.size }forward`; + this.outKey = `beatjump_${ this.size }forward`; + this.connect(); + this.trigger(); + }, + outConnect: false + }); + } +}; +MixtrackPlatinumFX.ModeBeatjump.prototype = Object.create(components.ComponentContainer.prototype); + +MixtrackPlatinumFX.Browse = function() { + this.knob = new components.Encoder({ + speed: 0, + speedTimer: 0, + shiftControl: true, + shiftOffset: 0x01, + input: function(channel, control, value) { + let direction; + if (MixtrackPlatinumFX.shifted && MixtrackPlatinumFX.shiftBrowseIsZoom) { + direction = (value > 0x40) ? "up" : "down"; + engine.setParameter("[Channel1]", `waveform_zoom_${ direction}`, 1); + + // need to zoom both channels if waveform sync is disabled in Mixxx settings. + // and when it's enabled then no need to zoom 2nd channel, as it will cause + // the zoom to jump 2 levels at once + if (!MixtrackPlatinumFX.waveformsSynced) { + engine.setParameter("[Channel2]", `waveform_zoom_${ direction}`, 1); + } + } else { + if (this.speedTimer !== 0) { + engine.stopTimer(this.speedTimer); + this.speedTimer = 0; + } + this.speedTimer = engine.beginTimer(100, function() { + this.speed=0; + this.speedTimer = 0; + }, true); + this.speed++; + direction = (value > 0x40) ? value - 0x80 : value; + if (MixtrackPlatinumFX.shifted) { + // when shifted go fast (consecutive squared!) + direction *= this.speed*this.speed; + } else { + // normal, up to 3 consecutive do one for fine control, then speed up + if (this.speed>3) { direction *= Math.min(4, (this.speed-3)); } + } + engine.setParameter("[Library]", "MoveVertical", direction); + } + } + }); + + this.knobButton = new components.Button({ + group: "[Library]", + shiftControl: true, + shiftOffset: 0x01, + previewing: false, + shift: function() { + this.inKey = "GoToItem"; + this.input = function(channel, control, value, _status, _group) { + if (value>0) { + if (MixtrackPlatinumFX.rightShift) { + if (this.previewing) { + script.triggerControl("[PreviewDeck1]", "stop"); + this.previewing = false; + } else { + script.triggerControl("[PreviewDeck1]", "LoadSelectedTrackAndPlay"); + this.previewing = true; + } + } else { + script.triggerControl("[Library]", "GoToItem"); + } + } + }; + }, + unshift: function() { + this.input = components.Button.prototype.input; + this.inKey = "MoveFocusForward"; + } + }); +}; +MixtrackPlatinumFX.Browse.prototype = new components.ComponentContainer(); + +MixtrackPlatinumFX.Gains = function() { + this.mainGain = new components.Pot({ + group: "[Master]", + inKey: "gain" + }); + + this.cueGain = new components.Pot({ + group: "[Master]", + inKey: "headGain", + shift: function() { + this.disconnect(); + this.group = "[Sampler1]"; + this.inKey = "pregain"; + this.input = function(channel, control, value, _status, _group) { + const newValue = this.inValueScale(value); + for (let i=1; i<=16; i++) { + engine.setParameter(`[Sampler${ i }]`, "pregain", newValue); + } + }; + }, + unshift: function() { + this.disconnect(); + this.firstValueReceived=false; + this.group = "[Master]"; + this.inKey = "headGain"; + this.input = components.Pot.prototype.input; + }, + }); + + this.cueMix = new components.Pot({ + group: "[Master]", + inKey: "headMix" + }); +}; +MixtrackPlatinumFX.Gains.prototype = new components.ComponentContainer(); + +MixtrackPlatinumFX.vuCallback = function(value, group) { + const level = value * 90; + const deckOffset = script.deckFromGroup(group) - 1; + midi.sendShortMsg(0xB0 + deckOffset, 0x1F, level); +}; + +MixtrackPlatinumFX.wheelTouch = function(channel, control, value) { + const deckNumber = channel + 1; + + if (!MixtrackPlatinumFX.shifted && MixtrackPlatinumFX.deck[channel].scratchModeEnabled && value === 0x7F) { + // touch start + + engine.scratchEnable(deckNumber, MixtrackPlatinumFX.jogScratchSensitivity, 33+1/3, MixtrackPlatinumFX.jogScratchAlpha, MixtrackPlatinumFX.jogScratchBeta, true); + } else if (value === 0) { + // touch end + engine.scratchDisable(deckNumber, true); + } +}; + +MixtrackPlatinumFX.wheelTurn = function(channel, control, value, status, group) { + const deckNumber = channel + 1; + + let newValue = value; + + if (value >= 64) { + // correct the value if going backwards + newValue -= 128; + } + + if (MixtrackPlatinumFX.shifted) { + // seek + const oldPos = engine.getValue(group, "playposition"); + + engine.setValue(group, "playposition", oldPos + newValue / MixtrackPlatinumFX.jogSeekSensitivity); + } else if (MixtrackPlatinumFX.deck[channel].scratchModeEnabled && engine.isScratching(deckNumber)) { + // scratch + engine.scratchTick(deckNumber, newValue); + } else { + // pitch bend + engine.setValue(group, "jog", newValue / MixtrackPlatinumFX.jogPitchSensitivity); + } +}; + +MixtrackPlatinumFX.timeElapsedCallback = function(value, _group, _control) { + // 0 = elapsed + // 1 = remaining + // 2 = both (we ignore this as the controller can't show both) + let onoff; + if (value === 0) { + // show elapsed + onoff = 0x00; + } else if (value === 1) { + // show remaining + onoff = 0x7F; + } else { + // both, ignore the event + return; + } + + // update all 4 decks on the controller + midi.sendShortMsg(0x90, 0x46, onoff); + midi.sendShortMsg(0x91, 0x46, onoff); + midi.sendShortMsg(0x92, 0x46, onoff); + midi.sendShortMsg(0x93, 0x46, onoff); +}; + +MixtrackPlatinumFX.timeMs = function(deck, position, duration) { + return Math.round(duration * position * 1000); +}; + +MixtrackPlatinumFX.encodeNumToArray = function(number, drop, unsigned) { + const numberarray = [ + (number >> 28) & 0x0F, + (number >> 24) & 0x0F, + (number >> 20) & 0x0F, + (number >> 16) & 0x0F, + (number >> 12) & 0x0F, + (number >> 8) & 0x0F, + (number >> 4) & 0x0F, + number & 0x0F, + ]; + + if (drop !== undefined) { + numberarray.splice(0, drop); + } + + if (number < 0) { numberarray[0] = 0x07; } else if (!unsigned) { numberarray[0] = 0x08; } + + return numberarray; +}; + +MixtrackPlatinumFX.sendScreenDurationMidi = function(deck, duration) { + if (duration < 1) { + duration = 1; + } + const durationArray = MixtrackPlatinumFX.encodeNumToArray(duration - 1); + + const bytePrefix = [0xF0, 0x00, 0x20, 0x7F, deck, 0x03]; + const bytePostfix = [0xF7]; + const byteArray = bytePrefix.concat(durationArray, bytePostfix); + midi.sendSysexMsg(byteArray, byteArray.length); +}; + +MixtrackPlatinumFX.sendScreenTimeMidi = function(deck, time) { + const timeArray = MixtrackPlatinumFX.encodeNumToArray(time); + + const bytePrefix = [0xF0, 0x00, 0x20, 0x7F, deck, 0x04]; + const bytePostfix = [0xF7]; + const byteArray = bytePrefix.concat(timeArray, bytePostfix); + midi.sendSysexMsg(byteArray, byteArray.length); +}; + +MixtrackPlatinumFX.sendScreenBpmMidi = function(deck, bpm) { + const bpmArray = MixtrackPlatinumFX.encodeNumToArray(bpm); + bpmArray.shift(); + bpmArray.shift(); + + const bytePrefix = [0xF0, 0x00, 0x20, 0x7F, deck, 0x01]; + const bytePostfix = [0xF7]; + const byteArray = bytePrefix.concat(bpmArray, bytePostfix); + midi.sendSysexMsg(byteArray, byteArray.length); + + MixtrackPlatinumFX.updateArrows(); +}; + +MixtrackPlatinumFX.rightShift=false; +MixtrackPlatinumFX.shiftToggle = function(channel, control, value, status, _group) { + if (value === 0x7F) { + if (status===0x91 || status===0x93) { + MixtrackPlatinumFX.rightShift=true; + } + MixtrackPlatinumFX.shift(); + } else { + MixtrackPlatinumFX.rightShift=false; + MixtrackPlatinumFX.unshift(); + } +}; + +MixtrackPlatinumFX.deckSwitch = function(channel, control, value, _status, _group) { + // Ignore the release the deck switch callback + // called both when actually releasing the button and for the alt deck when switching + if (value) { + const deck = channel; + MixtrackPlatinumFX.deck[deck].setActive(value === 0x7F); + // turn "off" the other deck + // this can't reliably be done with the release as it also trigger for this deck when the button is released + let other = 4-deck; + if (deck===0 || deck===2) { other = 2-deck; } + MixtrackPlatinumFX.deck[other].setActive(false); + // also zero vu meters + if (value === 0x7F) { + midi.sendShortMsg(0xBF, 0x44, 0); + midi.sendShortMsg(0xBF, 0x45, 0); + } + MixtrackPlatinumFX.updateArrows(true); + } +}; + +var sendSysex = function(buffer) { + midi.sendSysexMsg(buffer, buffer.length); +}; + +MixtrackPlatinumFX.sendScreenRateMidi = function(deck, rate) { + const rateArray = MixtrackPlatinumFX.encodeNumToArray(rate, 2); + + const bytePrefix = [0xF0, 0x00, 0x20, 0x7F, deck, 0x02]; + const bytePostfix = [0xF7]; + const byteArray = bytePrefix.concat(rateArray, bytePostfix); + sendSysex(byteArray); +}; + +// arrow data state (and cache to prevent midi spam) +MixtrackPlatinumFX.arrowsData = { + arrowsUpdateOn: true, + uparrow: [0, 0, 0, 0], + downarrow: [0, 0, 0, 0], +}; + +// force refresh turns arrow behaviour back to normal, and forces a refresh bypressing the cache +// force show turns both arrows on and suspends normal operation +MixtrackPlatinumFX.updateArrows = function(forceRefresh, forceShow, deck) { + if (!MixtrackPlatinumFX.initComplete) { + return; + } + + if (forceShow) { + // both arrows on to indicate the deck we are tapping + midi.sendShortMsg(0x80 | deck, 0x0A, 1); + midi.sendShortMsg(0x80 | deck, 0x09, 1); + // and stop other updates changing them + MixtrackPlatinumFX.arrowsData.arrowsUpdateOn=false; + } else { + if (forceRefresh) { + MixtrackPlatinumFX.arrowsData.arrowsUpdateOn=true; + } + if (MixtrackPlatinumFX.arrowsData.arrowsUpdateOn) { + const activeA = MixtrackPlatinumFX.deck[0].active ? 0 : 2; + const activeB = MixtrackPlatinumFX.deck[1].active ? 1 : 3; + + const bpmA = engine.getValue(`[Channel${ activeA+1 }]`, "bpm"); + const bpmB = engine.getValue(`[Channel${ activeB+1 }]`, "bpm"); + + let i; + for (i=0; i<4; i++) { + const bpmMy = engine.getValue(`[Channel${ i+1 }]`, "bpm"); + let bpmAlt = bpmA; + if (i===0 || i===2) { + bpmAlt = bpmB; + } + + let down=0; + let up=0; + + // only display if both decks have a bpm + if (bpmAlt && bpmMy) { + // and have a 0.05 bpm tolerance (else they only go off when you use sync) + if (bpmAlt>(bpmMy+0.05)) { + down=1; + } + if (bpmAlt<(bpmMy-0.05)) { + up=1; + } + } + + if (forceRefresh || MixtrackPlatinumFX.arrowsData.downarrow[i]!==down) { + MixtrackPlatinumFX.arrowsData.downarrow[i]=down; + midi.sendShortMsg(0x80 | i, 0x0A, down); // down arrow update + } + if (forceRefresh || MixtrackPlatinumFX.arrowsData.uparrow[i]!==up) { + MixtrackPlatinumFX.arrowsData.uparrow[i]=up; + midi.sendShortMsg(0x80 | i, 0x09, up); // up arrow update + } + } + } + } +}; + +MixtrackPlatinumFX.rateCallback = function(rate, group, _control) { + const channel = script.deckFromGroup(group) - 1; + const rateEffective = engine.getValue(group, "rateRange") * -rate; + + MixtrackPlatinumFX.sendScreenRateMidi(channel+1, Math.round(rateEffective*10000)); +}; + +MixtrackPlatinumFX.updateRateRange = function(channel, group, range) { + //engine.setParameter(group, "rateRange", (range-0.01)*0.25); + engine.setValue(group, "rateRange", range); + midi.sendShortMsg(0x90+channel, 0x0e, range*100); +}; 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/Numark-Scratch-scripts.js b/res/controllers/Numark-Scratch-scripts.js index fb5d7c2ac18..60605806900 100644 --- a/res/controllers/Numark-Scratch-scripts.js +++ b/res/controllers/Numark-Scratch-scripts.js @@ -3,36 +3,35 @@ var NumarkScratch = {}; /* * USER CONFIGURABLE SETTINGS. + * Change settings in the preferences */ // Defines the Beatloop Roll sizes for the 4 pads, for available values see: -// https://manual.mixxx.org/2.4/en/chapters/appendix/mixxx_controls.html#control-[ChannelN]-beatlooproll_X_activate +// https://manual.mixxx.org/latest/en/chapters/appendix/mixxx_controls.html#control-[ChannelN]-beatlooproll_X_activate // Default: [0.25, 0.5, 1, 2] NumarkScratch.autoLoopSizes = [ - "0.25", - "0.5", - "1", - "2", + engine.getSetting("beatLoopRollsSize1") || 0.25, + engine.getSetting("beatLoopRollsSize2") || 0.5, + engine.getSetting("beatLoopRollsSize3") || 1, + engine.getSetting("beatLoopRollsSize4") || 2, ]; // Defines how the Loop Encoder functions. // If 'true' the Encoder scrolls the library/loads track. Shift + Encoder manages looping. // If 'false' the Encoder manages looping. Shift + Encoder scrolls the library/loads track (Serato default). // Default: false -NumarkScratch.invertLoopEncoderFunction = false; +NumarkScratch.invertLoopEncoderFunction = !!engine.getSetting("invertLoopEncoderFunction"); -// Defines the bightness of button LEDs when they inactive. -// '0x00' sets the LEDSs to completely off. '0x01 sets the LEDs to dim. -// Default: 0x01 (dim) -NumarkScratch.LOW_LIGHT = 0x01; +// Define whether or not to keep LEDs dimmed if they are inactive. +// 0x01 ('true' in UI) will keep them dimmed, 0x00 ('false' in UI) will turn them off. Default: 0x01 ('true') +NumarkScratch.noLight = 0x00; +NumarkScratch.dimLight = 0x01; +components.Button.prototype.off = engine.getSetting("inactiveLightsAlwaysBacklit") ? NumarkScratch.dimLight : NumarkScratch.noLight; /* * CODE */ - -components.Button.prototype.off = NumarkScratch.LOW_LIGHT; - NumarkScratch.init = function() { // Initialize component containers NumarkScratch.deck = new components.ComponentContainer(); @@ -169,8 +168,8 @@ NumarkScratch.XfaderContainer = function() { break; case 0x7F: // Picnic Bench/Fast Cut engine.setValue("[Mixer Profile]", "xFaderMode", 0); - engine.setValue("[Mixer Profile]", "xFaderCalibration", 0.9); - engine.setValue("[Mixer Profile]", "xFaderCurve", 7.0); + engine.setValue("[Mixer Profile]", "xFaderCalibration", 1); + engine.setValue("[Mixer Profile]", "xFaderCurve", 999.6); break; } }; diff --git a/res/controllers/Numark-Scratch.midi.xml b/res/controllers/Numark-Scratch.midi.xml index d550bf831a3..a23674152c1 100644 --- a/res/controllers/Numark-Scratch.midi.xml +++ b/res/controllers/Numark-Scratch.midi.xml @@ -1,12 +1,102 @@ - + Numark Scratch Al Hadebe(NotYourAverageAl) Mapping for the Numark Scratch Mixer - NumarkScratch - NumarkScratch + https://mixxx.discourse.group/t/numark-scratch-mapping/25186 + https://manual.mixxx.org/latest/en/hardware/controllers/numark_scratch + + + + + + + + + + + + + + + + diff --git a/res/controllers/Pioneer-DDJ-200-scripts.js b/res/controllers/Pioneer-DDJ-200-scripts.js index 6514982d87c..5c9c3928ea8 100644 --- a/res/controllers/Pioneer-DDJ-200-scripts.js +++ b/res/controllers/Pioneer-DDJ-200-scripts.js @@ -233,7 +233,7 @@ DDJ200.rateMSB = function(channel, control, value, status, group) { DDJ200.rateLSB = function(channel, control, value, status, group) { var vDeckNo = DDJ200.vDeckNo[script.deckFromGroup(group)]; var vgroup = "[Channel" + vDeckNo + "]"; - // calculte rate value from its most and least significant bytes + // calculate rate value from its most and least significant bytes var rateMSB = DDJ200.vDeck[vDeckNo]["rateMSB"]; var rate = 1 - (((rateMSB << 7) + value) / 0x1FFF); engine.setValue(vgroup, "rate", rate); @@ -248,7 +248,7 @@ DDJ200.volumeMSB = function(channel, control, value, status, group) { DDJ200.volumeLSB = function(channel, control, value, status, group) { var vDeckNo = DDJ200.vDeckNo[script.deckFromGroup(group)]; var vgroup = "[Channel" + vDeckNo + "]"; - // calculte volume value from its most and least significant bytes + // calculate volume value from its most and least significant bytes var volMSB = DDJ200.vDeck[vDeckNo]["volMSB"]; var vol = ((volMSB << 7) + value) / 0x3FFF; //var vol = ((volMSB << 7) + value); // use for linear correction 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/Pioneer-DDJ-SB3-scripts.js b/res/controllers/Pioneer-DDJ-SB3-scripts.js index cf50a615004..29f0b2bae8c 100755 --- a/res/controllers/Pioneer-DDJ-SB3-scripts.js +++ b/res/controllers/Pioneer-DDJ-SB3-scripts.js @@ -654,10 +654,9 @@ PioneerDDJSB3.bindDeckControlConnections = function(channelGroup, isUnbinding) { "pfl": PioneerDDJSB3.headphoneCueLed, "keylock": PioneerDDJSB3.keyLockLed, "loop_enabled": PioneerDDJSB3.autoLoopLed, + "slip_enabled": PioneerDDJSB3.slipLed, }; - controlsToFunctions.slipEnabled = PioneerDDJSB3.slipLed; - for (i = 1; i <= 8; i++) { controlsToFunctions["hotcue_" + i + "_enabled"] = PioneerDDJSB3.hotCueLeds; } @@ -771,7 +770,7 @@ PioneerDDJSB3.vinylButton = function(channel, control, value, status, group) { PioneerDDJSB3.slipButton = function(channel, control, value, status, group) { if (value) { - script.toggleControl(group, "slipEnabled"); + script.toggleControl(group, "slip_enabled"); } }; @@ -1210,11 +1209,11 @@ PioneerDDJSB3.jogTouch = function(channel, control, value, status, group) { } else { engine.scratchDisable(deck + 1, true); - if (engine.getValue(group, "slipEnabled")) { - engine.setValue(group, "slipEnabled", false); + if (engine.getValue(group, "slip_enabled")) { + engine.setValue(group, "slip_enabled", false); engine.beginTimer(250, () => { - engine.setValue(group, "slipEnabled", true); + engine.setValue(group, "slip_enabled", true); }, true); } } @@ -1416,6 +1415,11 @@ PioneerDDJSB3.EffectUnit = function(unitNumber) { }, }); + this.shiftKnob = new components.Pot({ + inKey: "super1", + group: eu.group + }); + this.knobSoftTakeoverHandler = engine.makeConnection(eu.group, "focused_effect", function(value) { if (value === 0) { engine.softTakeoverIgnoreNextValue(eu.group, "mix"); @@ -1538,8 +1542,10 @@ PioneerDDJSB3.Slicer.prototype.generateBeatPositions = function() { if (sample < this.trackSamples) { var bp = { sample: sample, - positionIn: (this.PLAY_POSITION_RANGE * sample - 1) / this.trackSamples, - positionOut: (this.PLAY_POSITION_RANGE * nextSample - 1) / this.trackSamples, + position: { + in: (this.PLAY_POSITION_RANGE * sample - 1) / this.trackSamples, + out: (this.PLAY_POSITION_RANGE * nextSample - 1) / this.trackSamples, + } }; this.beatPositions.push(bp); @@ -1595,7 +1601,7 @@ PioneerDDJSB3.Slicer.prototype.playPositionChange = function(value) { for (var i = 0; i < this.beatPositions.length; i++) { var beatPosition = this.beatPositions[i]; - if (value >= beatPosition.positionIn && value < beatPosition.positionOut) { + if (value >= beatPosition.position.in && value < beatPosition.position.out) { this.currentBeat = i; found = true; } diff --git a/res/controllers/Pioneer-DDJ-SB3.midi.xml b/res/controllers/Pioneer-DDJ-SB3.midi.xml index ec79699afee..589a539c259 100755 --- a/res/controllers/Pioneer-DDJ-SB3.midi.xml +++ b/res/controllers/Pioneer-DDJ-SB3.midi.xml @@ -1255,6 +1255,46 @@ + + [Master] + gain + Master Level + 0xB6 + 0x08 + + + + + + [Master] + gain + Master Level + 0xB6 + 0x28 + + + + + + [Master] + headGain + Headphones Level + 0xB6 + 0x0D + + + + + + [Master] + headGain + Headphones Level + 0xB6 + 0x2D + + + + [Playlist] PioneerDDJSB3.rotarySelector @@ -1456,21 +1496,21 @@ - [EffectRack1_EffectUnit] - PioneerDDJSB3.effectUnit[1].knob.inputMSB + [EffectRack1_EffectUnit1] + PioneerDDJSB3.effectUnit[1].shiftKnob.inputMSB Shift FX1 (MSB), Knob: left SHIFT & FX1 0xB4 - 0x00 + 0x12 - [EffectRack1_EffectUnit] - PioneerDDJSB3.effectUnit[1].knob.inputLSB + [EffectRack1_EffectUnit1] + PioneerDDJSB3.effectUnit[1].shiftKnob.inputLSB Shift FX1 (LSB), Knob: left SHIFT & FX1 0xB4 - 0x20 + 0x32 @@ -1557,20 +1597,20 @@ [EffectRack1_EffectUnit2] - PioneerDDJSB3.effectUnit[2].knob.inputMSB + PioneerDDJSB3.effectUnit[2].shiftKnob.inputMSB Shift FX2 (MSB), Knob: right SHIFT & FX1 0xB5 - 0x00 + 0x12 - [Channel2] - PioneerDDJSB3.effectUnit[2].knob.inputLSB + [EffectRack1_EffectUnit2] + PioneerDDJSB3.effectUnit[2].shiftKnob.inputLSB Shift FX2 (LSB), Knob: right SHIFT & FX2 0xB5 - 0x20 + 0x32 diff --git a/res/controllers/Reloop Beatmix 2-4.midi.xml b/res/controllers/Reloop Beatmix 2-4.midi.xml index 31c55bfa635..f22aed38c5a 100644 --- a/res/controllers/Reloop Beatmix 2-4.midi.xml +++ b/res/controllers/Reloop Beatmix 2-4.midi.xml @@ -2805,7 +2805,7 @@ [Channel1] - hotcue_1_enabled + hotcue_1_status 0x91 0x00 0x55 @@ -2813,7 +2813,7 @@ [Channel1] - hotcue_2_enabled + hotcue_2_status 0x91 0x01 0x55 @@ -2821,7 +2821,7 @@ [Channel1] - hotcue_3_enabled + hotcue_3_status 0x91 0x02 0x55 @@ -2829,7 +2829,7 @@ [Channel1] - hotcue_4_enabled + hotcue_4_status 0x91 0x03 0x55 @@ -2869,7 +2869,7 @@ [Channel1] - hotcue_1_enabled + hotcue_1_status 0x91 0x10 0x55 @@ -2877,7 +2877,7 @@ [Channel1] - hotcue_2_enabled + hotcue_2_status 0x91 0x11 0x55 @@ -2885,7 +2885,7 @@ [Channel1] - hotcue_3_enabled + hotcue_3_status 0x91 0x12 0x55 @@ -2893,7 +2893,7 @@ [Channel1] - hotcue_4_enabled + hotcue_4_status 0x91 0x13 0x55 @@ -2901,7 +2901,7 @@ [Channel1] - hotcue_1_enabled + hotcue_1_status 0x91 0x40 0x55 @@ -2909,7 +2909,7 @@ [Channel1] - hotcue_2_enabled + hotcue_2_status 0x91 0x41 0x55 @@ -2917,7 +2917,7 @@ [Channel1] - hotcue_3_enabled + hotcue_3_status 0x91 0x42 0x55 @@ -2925,7 +2925,7 @@ [Channel1] - hotcue_4_enabled + hotcue_4_status 0x91 0x43 0x55 @@ -2941,7 +2941,7 @@ [Channel1] - hotcue_1_enabled + hotcue_1_status 0x91 0x18 0x55 @@ -2949,7 +2949,7 @@ [Channel1] - hotcue_2_enabled + hotcue_2_status 0x91 0x19 0x55 @@ -2957,7 +2957,7 @@ [Channel1] - hotcue_3_enabled + hotcue_3_status 0x91 0x1A 0x55 @@ -2965,7 +2965,7 @@ [Channel1] - hotcue_4_enabled + hotcue_4_status 0x91 0x1B 0x55 @@ -3069,7 +3069,7 @@ [Channel2] - hotcue_1_enabled + hotcue_1_status 0x92 0x00 0x55 @@ -3077,7 +3077,7 @@ [Channel2] - hotcue_2_enabled + hotcue_2_status 0x92 0x01 0x55 @@ -3085,7 +3085,7 @@ [Channel2] - hotcue_3_enabled + hotcue_3_status 0x92 0x02 0x55 @@ -3093,7 +3093,7 @@ [Channel2] - hotcue_4_enabled + hotcue_4_status 0x92 0x03 0x55 @@ -3133,7 +3133,7 @@ [Channel2] - hotcue_1_enabled + hotcue_1_status 0x92 0x10 0x55 @@ -3141,7 +3141,7 @@ [Channel2] - hotcue_2_enabled + hotcue_2_status 0x92 0x11 0x55 @@ -3149,7 +3149,7 @@ [Channel2] - hotcue_3_enabled + hotcue_3_status 0x92 0x12 0x55 @@ -3157,7 +3157,7 @@ [Channel2] - hotcue_4_enabled + hotcue_4_status 0x92 0x13 0x55 @@ -3165,7 +3165,7 @@ [Channel2] - hotcue_1_enabled + hotcue_1_status 0x92 0x40 0x55 @@ -3173,7 +3173,7 @@ [Channel2] - hotcue_2_enabled + hotcue_2_status 0x92 0x41 0x55 @@ -3181,7 +3181,7 @@ [Channel2] - hotcue_3_enabled + hotcue_3_status 0x92 0x42 0x55 @@ -3189,7 +3189,7 @@ [Channel2] - hotcue_4_enabled + hotcue_4_status 0x92 0x43 0x55 @@ -3205,7 +3205,7 @@ [Channel2] - hotcue_1_enabled + hotcue_1_status 0x92 0x18 0x55 @@ -3213,7 +3213,7 @@ [Channel2] - hotcue_2_enabled + hotcue_2_status 0x92 0x19 0x55 @@ -3221,7 +3221,7 @@ [Channel2] - hotcue_3_enabled + hotcue_3_status 0x92 0x1A 0x55 @@ -3229,7 +3229,7 @@ [Channel2] - hotcue_4_enabled + hotcue_4_status 0x92 0x1B 0x55 @@ -3333,7 +3333,7 @@ [Channel3] - hotcue_1_enabled + hotcue_1_status 0x93 0x00 0x55 @@ -3341,7 +3341,7 @@ [Channel3] - hotcue_2_enabled + hotcue_2_status 0x93 0x01 0x55 @@ -3349,7 +3349,7 @@ [Channel3] - hotcue_3_enabled + hotcue_3_status 0x93 0x02 0x55 @@ -3357,7 +3357,7 @@ [Channel3] - hotcue_4_enabled + hotcue_4_status 0x93 0x03 0x55 @@ -3397,7 +3397,7 @@ [Channel3] - hotcue_1_enabled + hotcue_1_status 0x93 0x10 0x55 @@ -3405,7 +3405,7 @@ [Channel3] - hotcue_2_enabled + hotcue_2_status 0x93 0x11 0x55 @@ -3413,7 +3413,7 @@ [Channel3] - hotcue_3_enabled + hotcue_3_status 0x93 0x12 0x55 @@ -3421,7 +3421,7 @@ [Channel3] - hotcue_4_enabled + hotcue_4_status 0x93 0x13 0x55 @@ -3429,7 +3429,7 @@ [Channel3] - hotcue_1_enabled + hotcue_1_status 0x93 0x40 0x55 @@ -3437,7 +3437,7 @@ [Channel3] - hotcue_2_enabled + hotcue_2_status 0x93 0x41 0x55 @@ -3445,7 +3445,7 @@ [Channel3] - hotcue_3_enabled + hotcue_3_status 0x93 0x42 0x55 @@ -3453,7 +3453,7 @@ [Channel3] - hotcue_4_enabled + hotcue_4_status 0x93 0x43 0x55 @@ -3469,7 +3469,7 @@ [Channel3] - hotcue_1_enabled + hotcue_1_status 0x93 0x18 0x55 @@ -3477,7 +3477,7 @@ [Channel3] - hotcue_2_enabled + hotcue_2_status 0x93 0x19 0x55 @@ -3485,7 +3485,7 @@ [Channel3] - hotcue_3_enabled + hotcue_3_status 0x93 0x1A 0x55 @@ -3493,7 +3493,7 @@ [Channel3] - hotcue_4_enabled + hotcue_4_status 0x93 0x1B 0x55 @@ -3597,7 +3597,7 @@ [Channel4] - hotcue_1_enabled + hotcue_1_status 0x94 0x00 0x55 @@ -3605,7 +3605,7 @@ [Channel4] - hotcue_2_enabled + hotcue_2_status 0x94 0x01 0x55 @@ -3613,7 +3613,7 @@ [Channel4] - hotcue_3_enabled + hotcue_3_status 0x94 0x02 0x55 @@ -3621,7 +3621,7 @@ [Channel4] - hotcue_4_enabled + hotcue_4_status 0x94 0x03 0x55 @@ -3661,7 +3661,7 @@ [Channel4] - hotcue_1_enabled + hotcue_1_status 0x94 0x10 0x55 @@ -3669,7 +3669,7 @@ [Channel4] - hotcue_2_enabled + hotcue_2_status 0x94 0x11 0x55 @@ -3677,7 +3677,7 @@ [Channel4] - hotcue_3_enabled + hotcue_3_status 0x94 0x12 0x55 @@ -3685,7 +3685,7 @@ [Channel4] - hotcue_4_enabled + hotcue_4_status 0x94 0x13 0x55 @@ -3693,7 +3693,7 @@ [Channel4] - hotcue_1_enabled + hotcue_1_status 0x94 0x40 0x55 @@ -3701,7 +3701,7 @@ [Channel4] - hotcue_2_enabled + hotcue_2_status 0x94 0x41 0x55 @@ -3709,7 +3709,7 @@ [Channel4] - hotcue_3_enabled + hotcue_3_status 0x94 0x42 0x55 @@ -3717,7 +3717,7 @@ [Channel4] - hotcue_4_enabled + hotcue_4_status 0x94 0x43 0x55 @@ -3733,7 +3733,7 @@ [Channel4] - hotcue_1_enabled + hotcue_1_status 0x94 0x18 0x55 @@ -3741,7 +3741,7 @@ [Channel4] - hotcue_2_enabled + hotcue_2_status 0x94 0x19 0x55 @@ -3749,7 +3749,7 @@ [Channel4] - hotcue_3_enabled + hotcue_3_status 0x94 0x1A 0x55 @@ -3757,7 +3757,7 @@ [Channel4] - hotcue_4_enabled + hotcue_4_status 0x94 0x1B 0x55 diff --git a/res/controllers/Reloop-Beatmix-2-4-scripts.js b/res/controllers/Reloop-Beatmix-2-4-scripts.js index afe82f45307..66872351b1b 100644 --- a/res/controllers/Reloop-Beatmix-2-4-scripts.js +++ b/res/controllers/Reloop-Beatmix-2-4-scripts.js @@ -19,9 +19,9 @@ const JogFlashCriticalTime = 15; // number of seconds to quickly blink at the en ********************************************************************** * User References * --------------- - * Wiki/manual : http://www.mixxx.org/wiki/doku.php/reloop_beatmix_2 - * Wiki/manual : http://www.mixxx.org/wiki/doku.php/reloop_beatmix_4 - * support forum : http://mixxx.org/forums/viewtopic.php?f=7&t=8428 + * Wiki/manual : https://github.com/mixxxdj/mixxx/wiki/reloop-beatmix-2 + * Wiki/manual : https://github.com/mixxxdj/mixxx/wiki/Reloop-Beatmix-4 + * support forum : https://mixxx.discourse.group/t/reloop-beatmix-2-4-mapping/16049 * * Thanks * ---------------- @@ -35,10 +35,11 @@ const JogFlashCriticalTime = 15; // number of seconds to quickly blink at the en * 2016-08-15 - v1.2.1 - fix small typos and bugs * 2016-08-17 - v1.3 - sync each item on the controller with Mixxx at launch * 2016-08-19 - v1.3.1 - fix nex/prev effect button release and superknob responsiveness + * 2024-08-25 - v2.0.0 - Fixes & rewrites for Mixxx 2.4 *********************************************************************** * GPL v2 licence * -------------- - * Reloop Beatmix controller script script 1.3.1 for Mixxx 2.1.0 + * Reloop Beatmix controller script script 2.0.0 for Mixxx 2.4+ * Copyright (C) 2016 Sébastien Blaisot * * This program is free software; you can redistribute it and/or @@ -158,19 +159,25 @@ ReloopBeatmix24.connectControls = function() { // Channels controls for (let i = 1; i <= 4; i++) { group = "[Channel" + i + "]"; - engine.connectControl(group, "track_samples", - "ReloopBeatmix24.deckLoaded"); - engine.connectControl(group, "play", - "ReloopBeatmix24.ChannelPlay"); - engine.connectControl(group, "playposition", - "ReloopBeatmix24.JogLed"); - engine.connectControl(group, "loop_end_position", - "ReloopBeatmix24.loopDefined"); + engine.makeConnection(group, "track_loaded", + ReloopBeatmix24.deckLoaded); + engine.trigger(group, "track_loaded"); + engine.makeConnection(group, "play", + ReloopBeatmix24.ChannelPlay); + engine.trigger(group, "play"); + engine.makeConnection(group, "playposition", + ReloopBeatmix24.JogLed); + engine.trigger(group, "playposition"); + engine.makeConnection(group, "loop_end_position", + ReloopBeatmix24.loopDefined); + engine.trigger(group, "loop_end_position"); engine.softTakeover(group, "rate", true); engine.setValue("[EffectRack1_EffectUnit1]", "group_" + group + "_enable", 0); engine.setValue("[EffectRack1_EffectUnit2]", "group_" + group + "_enable", 0); + engine.trigger("[EffectRack1_EffectUnit1]", `group_${ group }_enable`); + engine.trigger("[EffectRack1_EffectUnit2]", `group_${ group }_enable`); channelPlaying[group] = !!engine.getValue(group, "play"); JogBlinking[group] = false; } @@ -178,10 +185,12 @@ ReloopBeatmix24.connectControls = function() { // Samplers controls for (let i = 1; i <= 8; i++) { group = "[Sampler" + i + "]"; - engine.connectControl(group, "track_samples", - "ReloopBeatmix24.deckLoaded"); - engine.connectControl(group, "play", - "ReloopBeatmix24.SamplerPlay"); + engine.makeConnection(group, "track_loaded", + ReloopBeatmix24.deckLoaded); + engine.trigger(group, "track_loaded"); + engine.makeConnection(group, "play", + ReloopBeatmix24.SamplerPlay); + engine.trigger(group, "play"); } // Effects reset @@ -195,23 +204,29 @@ ReloopBeatmix24.init = function(id, _debug) { if (engine.getValue("[App]", "num_samplers") < 8) { engine.setValue("[App]", "num_samplers", 8); } - ReloopBeatmix24.connectControls(false); + ReloopBeatmix24.connectControls(); for (let i = 1; i <= 4; i++) { engine.trigger("[Channel" + i + "]", "loop_end_position"); } - // After midi controller receive this Outbound Message request SysEx Message, - // midi controller will send the status of every item on the - // control surface. (Mixxx will be initialized with current values) - midi.sendSysexMsg(ControllerStatusSysex, ControllerStatusSysex.length); + // Delay controller status request to give time to the controller to be ready + engine.beginTimer(1500, + () => { + console.log(`Reloop Beatmix: ${ id } Requesting Controller Status`); - print("Reloop Beatmix: " + id + " initialized."); + // After midi controller receive this Outbound Message request SysEx Message, + // midi controller will send the status of every item on the + // control surface. (Mixxx will be initialized with current values) + midi.sendSysexMsg(ControllerStatusSysex, ControllerStatusSysex.length); + + }, true); + console.log(`Reloop Beatmix: ${ id } initialized.`); }; ReloopBeatmix24.shutdown = function() { ReloopBeatmix24.TurnLEDsOff(); // Turn off all LEDs - print("Reloop Beatmix: " + ReloopBeatmix24.id + " shut down."); + console.log(`Reloop Beatmix: ${ ReloopBeatmix24.id } shut down.`); }; // Button functions. @@ -332,7 +347,7 @@ ReloopBeatmix24.LoadButton = function(channel, control, value, status, group) { if (value === DOWN) { loadButtonLongPressed[group] = false; loadButtonTimers[group] = engine.beginTimer(1000, - () => {RegloopBeatmix24.LoadButtonEject(group); }, true); + () => { ReloopBeatmix24.LoadButtonEject(group); }, true); } else { // UP if (!loadButtonLongPressed[group]) { // Short press engine.stopTimer(loadButtonTimers[group]); @@ -350,7 +365,7 @@ ReloopBeatmix24.LoadButton = function(channel, control, value, status, group) { // ======================================================== ReloopBeatmix24.SamplerPad = function(channel, control, value, status, group) { if (value === DOWN) { - if (engine.getValue(group, "track_samples")) { //Sampler loaded (playing or not) + if (engine.getValue(group, "track_loaded")) { //Sampler loaded (playing or not) engine.setValue(group, "cue_gotoandplay", 1); } else { engine.setValue(group, "LoadSelectedTrack", 1); @@ -361,7 +376,7 @@ ReloopBeatmix24.SamplerPad = function(channel, control, value, status, group) { ReloopBeatmix24.ShiftSamplerPad = function(channel, control, value, status, group) { if (value === DOWN) { - if (engine.getValue(group, "track_samples")) { //Sampler loaded (playing or not) + if (engine.getValue(group, "track_loaded")) { //Sampler loaded (playing or not) if (engine.getValue(group, "play")) { // Sampler is playing engine.setValue(group, "cue_gotoandstop", 1); } else { @@ -371,7 +386,7 @@ ReloopBeatmix24.ShiftSamplerPad = function(channel, control, value, status, engine.setValue(group, "LoadSelectedTrack", 1); } } else { // UP - if (!engine.getValue(group, "track_samples")) { // if empty + if (!engine.getValue(group, "track_loaded")) { // if empty // Set eject back to 0 to turn off the eject button on screen engine.setValue(group, "eject", 0); } @@ -426,7 +441,7 @@ ReloopBeatmix24.deckLoaded = function(value, group, _control) { { const channelChan = parseInt(channelRegEx.exec(group)[1]); if (channelChan <= 4) { - // shut down load button + // shut down load button midi.sendShortMsg(0x90 + channelChan, 0x50, value ? ON : OFF); @@ -473,7 +488,7 @@ ReloopBeatmix24.SamplerPlay = function(value, group, _control) { if (value) { ledColor = VIOLET; } else { - ledColor = engine.getValue(group, "track_samples") ? RED : OFF; + ledColor = engine.getValue(group, "track_loaded") ? RED : OFF; } // We need to switch off pad lights before changing color otherwise @@ -528,6 +543,9 @@ ReloopBeatmix24.ChannelPlay = function(value, group, _control) { // so value here represent the position in the track in the range [-0.14..1.14] // (0 = beginning, 1 = end) ReloopBeatmix24.JogLed = function(value, group, _control) { + if (engine.getValue(group, "track_loaded") === 0) { + return; + } // time computation const trackDuration = engine.getValue(group, "duration"); const timeLeft = trackDuration * (1.0 - value); diff --git a/res/controllers/Reloop-Beatpad-scripts.js b/res/controllers/Reloop-Beatpad-scripts.js index a6fb4e125e0..ad60a18a954 100644 --- a/res/controllers/Reloop-Beatpad-scripts.js +++ b/res/controllers/Reloop-Beatpad-scripts.js @@ -2733,3 +2733,4 @@ ReloopBeatpad.InboundSysex = function(data, length) { // Automatically done by Mixxx : // midi.sendSysexMsg(ControllerStatusSysex, ControllerStatusSysex.length); }; +ReloopBeatpad.incomingData = ReloopBeatpad.InboundSysex; diff --git a/res/controllers/Reloop-Digital-Jockey-2-IE.midi.xml b/res/controllers/Reloop-Digital-Jockey-2-IE.midi.xml new file mode 100644 index 00000000000..8489cb34dcf --- /dev/null +++ b/res/controllers/Reloop-Digital-Jockey-2-IE.midi.xml @@ -0,0 +1,1293 @@ + + + + Reloop Digital Jockey 2 Interface Edition + DJ aK + Controller mapping for Reloop Digital Jockey 2 Interface Edition + https://mixxx.discourse.group/t/reloop-digital-jockey-2-mapping-by-dj-ak/23971 + reloop_digital_jockey_2_interface_edition + + + + + + + + + + + + [Master] + gain + 0xB0 + 0x0F + + + + + + [Master] + headGain + 0xB0 + 0x10 + + + + + + [Master] + headMix + 0xB0 + 0x11 + + + + + + [Master] + crossfader + 0xB0 + 0x12 + + + + + + + + [Channel1] + RDJ2.leftDeck.playButton.input + 0x90 + 0x19 + + + + + + [Channel1] + RDJ2.leftDeck.playButton.input + 0x90 + 0x37 + + + + + + [Channel1] + RDJ2.leftDeck.cueButton.input + 0x90 + 0x18 + + + + + + [Channel1] + RDJ2.leftDeck.cueButton.input + 0x90 + 0x36 + + + + + + [Channel1] + cue_play + 0x90 + 0x17 + + + + + + [Channel1] + cue_gotoandplay + 0x90 + 0x35 + + + + + + [Channel1] + RDJ2.leftDeck.syncButton.input + 0x90 + 0x01 + + + + + + [Channel1] + RDJ2.leftDeck.syncButton.input + 0x90 + 0x1F + + + + + + [Channel1] + RDJ2.leftDeck.bendPlusButton.input + 0x90 + 0x04 + + + + + + [Library] + GoToItem + 0x90 + 0x22 + + + + + + [Channel1] + RDJ2.leftDeck.bendMinusButton.input + 0x90 + 0x03 + + + + + + [Library] + GoToItem + 0x90 + 0x21 + + + + + + [Channel1] + RDJ2.leftDeck.onJogTouch + 0x90 + 0x1D + + + + + + [Channel1] + RDJ2.leftDeck.onJogSpin + 0xB0 + 0x0B + + + + + + [Channel1] + RDJ2.leftDeck.onJogSpin + 0xB0 + 0x29 + + + + + + [Channel1] + RDJ2.leftDeck.jogModeSelector.input + 0x90 + 0x1A + + + + + + [Channel1] + RDJ2.leftDeck.jogModeSelector.input + 0x90 + 0x38 + + + + + + [Channel1] + RDJ2.leftDeck.jogModeSelector.input + 0x90 + 0x1B + + + + + + [Channel1] + RDJ2.leftDeck.jogModeSelector.input + 0x90 + 0x39 + + + + + + [Channel1] + RDJ2.leftDeck.jogModeSelector.input + 0x90 + 0x1C + + + + + + [Channel1] + RDJ2.leftDeck.jogModeSelector.input + 0x90 + 0x3A + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter1 + 0xB0 + 0x05 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter1 + 0x90 + 0x16 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter2 + 0xB0 + 0x04 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter2 + 0x90 + 0x15 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + parameter3 + 0xB0 + 0x03 + + + + + + [QuickEffectRack1_[Channel1]] + super1 + 0xB0 + 0x21 + + + + + + [EqualizerRack1_[Channel1]_Effect1] + RDJ2.leftDeck.highKillQuickEffectButton.input + 0x90 + 0x14 + + + + + + [QuickEffectRack1_[Channel1]_Effect1] + RDJ2.leftDeck.highKillQuickEffectButton.input + 0x90 + 0x32 + + + + + + [Channel1] + volume + 0xB0 + 0x06 + + + + + + [Channel1] + pregain + 0xB0 + 0x02 + + + + + + [Channel1] + rate + 0xE0 + + + + + + [Channel1] + keylock + 0x90 + 0x02 + + + + + + [Channel1] + pfl + 0x90 + 0x05 + + + + + + [Channel1] + RDJ2.leftShiftButton.input + 0x90 + 0x0A + + + + + + [Channel1] + RDJ2.leftDeck.loadButton.input + 0x90 + 0x13 + + + + + + [Channel1] + RDJ2.leftDeck.loadButton.input + 0x90 + 0x31 + + + + + + + + [Channel2] + RDJ2.rightDeck.playButton.input + 0x90 + 0x55 + + + + + + [Channel2] + RDJ2.rightDeck.playButton.input + 0x90 + 0x73 + + + + + + [Channel2] + RDJ2.rightDeck.cueButton.input + 0x90 + 0x54 + + + + + + [Channel2] + RDJ2.rightDeck.cueButton.input + 0x90 + 0x72 + + + + + + [Channel2] + cue_play + 0x90 + 0x53 + + + + + + [Channel2] + cue_gotoandplay + 0x90 + 0x71 + + + + + + [Channel2] + RDJ2.rightDeck.syncButton.input + 0x90 + 0x3D + + + + + + [Channel2] + RDJ2.rightDeck.syncButton.input + 0x90 + 0x5B + + + + + + [Channel2] + RDJ2.rightDeck.bendPlusButton.input + 0x90 + 0x40 + + + + + + [Library] + GoToItem + 0x90 + 0x5E + + + + + + [Channel2] + RDJ2.rightDeck.bendMinusButton.input + 0x90 + 0x3F + + + + + + [Library] + GoToItem + 0x90 + 0x5D + + + + + + [Channel2] + RDJ2.rightDeck.onJogTouch + 0x90 + 0x59 + + + + + + [Channel2] + RDJ2.rightDeck.onJogSpin + 0xB0 + 0x47 + + + + + + [Channel2] + RDJ2.rightDeck.onJogSpin + 0xB0 + 0x65 + + + + + + [Channel2] + RDJ2.rightDeck.jogModeSelector.input + 0x90 + 0x56 + + + + + + [Channel2] + RDJ2.rightDeck.jogModeSelector.input + 0x90 + 0x74 + + + + + + [Channel2] + RDJ2.rightDeck.jogModeSelector.input + 0x90 + 0x57 + + + + + + [Channel2] + RDJ2.rightDeck.jogModeSelector.input + 0x90 + 0x75 + + + + + + [Channel2] + RDJ2.rightDeck.jogModeSelector.input + 0x90 + 0x58 + + + + + + [Channel2] + RDJ2.rightDeck.jogModeSelector.input + 0x90 + 0x76 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter1 + 0xB0 + 0x41 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter1 + 0x90 + 0x52 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter2 + 0xB0 + 0x40 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter2 + 0x90 + 0x51 + + + + + + [EqualizerRack1_[Channel2]_Effect1] + parameter3 + 0xB0 + 0x3F + + + + + + [QuickEffectRack1_[Channel2]] + super1 + 0xB0 + 0x5D + + + + + + [EqualizerRack1_[Channel2]_Effect1] + RDJ2.rightDeck.highKillQuickEffectButton.input + 0x90 + 0x50 + + + + + + [QuickEffectRack1_[Channel2]_Effect1] + RDJ2.rightDeck.highKillQuickEffectButton.input + 0x90 + 0x6E + + + + + + [Channel2] + volume + 0xB0 + 0x42 + + + + + + [Channel2] + pregain + 0xB0 + 0x3E + + + + + + [Channel2] + rate + 0xE1 + + + + + + [Channel2] + keylock + 0x90 + 0x3E + + + + + + [Channel2] + pfl + 0x90 + 0x41 + + + + + + [Channel2] + RDJ2.rightShiftButton.input + 0x90 + 0x46 + + + + + + [Channel2] + RDJ2.rightDeck.loadButton.input + 0x90 + 0x4F + + + + + + [Channel2] + RDJ2.rightDeck.loadButton.input + 0x90 + 0x6D + + + + + + + + [Channel1] + RDJ2.fx1.enableButtons[1].input + 0x90 + 0x07 + + + + + + [Channel1] + RDJ2.fx1.enableButtons[1].input + 0x90 + 0x25 + + + + + + [Channel1] + RDJ2.fx1.enableButtons[2].input + 0x90 + 0x0C + + + + + + [Channel1] + RDJ2.fx1.enableButtons[2].input + 0x90 + 0x2A + + + + + + [Channel1] + RDJ2.fx1.enableButtons[3].input + 0x90 + 0x09 + + + + + + [Channel1] + RDJ2.leftDeck.fx1AssignmentButton.input + 0x90 + 0x27 + + + + + + [Channel1] + RDJ2.fx1.knobs[1].input + 0xB0 + 0x07 + + + + + + [Channel1] + RDJ2.fx1.knobs[1].input + 0xB0 + 0x25 + + + + + + [Channel1] + RDJ2.fx1.knobs[2].input + 0xB0 + 0x08 + + + + + + [Channel1] + RDJ2.fx1.knobs[2].input + 0xB0 + 0x26 + + + + + + [Channel1] + RDJ2.fx1.knobs[3].input + 0xB0 + 0x09 + + + + + + [Channel1] + RDJ2.fx1.knobs[3].input + 0xB0 + 0x27 + + + + + + [Channel1] + RDJ2.fx1.dryWetKnob.input + 0xB0 + 0x0A + + + + + + [Channel1] + RDJ2.leftDeck.loopsizeKnob.input + 0xB0 + 0x28 + + + + + + [Channel1] + RDJ2.fx1.effectFocusButton.input + 0x90 + 0x0E + + + + + + [Channel1] + RDJ2.leftDeck.fx2AssignmentButton.input + 0x90 + 0x2C + + + + + + + + [Channel2] + RDJ2.fx2.enableButtons[1].input + 0x90 + 0x48 + + + + + + [Channel2] + RDJ2.fx2.enableButtons[1].input + 0x90 + 0x66 + + + + + + [Channel2] + RDJ2.fx2.enableButtons[2].input + 0x90 + 0x43 + + + + + + [Channel2] + RDJ2.fx2.enableButtons[2].input + 0x90 + 0x61 + + + + + + [Channel2] + RDJ2.fx2.enableButtons[3].input + 0x90 + 0x4A + + + + + + [Channel2] + RDJ2.rightDeck.fx1AssignmentButton.input + 0x90 + 0x68 + + + + + + [Channel2] + RDJ2.fx2.knobs[1].input + 0xB0 + 0x44 + + + + + + [Channel2] + RDJ2.fx2.knobs[1].input + 0xB0 + 0x62 + + + + + + [Channel2] + RDJ2.fx2.knobs[2].input + 0xB0 + 0x43 + + + + + + [Channel2] + RDJ2.fx2.knobs[2].input + 0xB0 + 0x61 + + + + + + [Channel2] + RDJ2.fx2.knobs[3].input + 0xB0 + 0x46 + + + + + + [Channel2] + RDJ2.fx2.knobs[3].input + 0xB0 + 0x64 + + + + + + [Channel2] + RDJ2.fx2.dryWetKnob.input + 0xB0 + 0x45 + + + + + + [Channel2] + RDJ2.rightDeck.loopsizeKnob.input + 0xB0 + 0x63 + + + + + + [Channel2] + RDJ2.fx2.effectFocusButton.input + 0x90 + 0x45 + + + + + + [Channel2] + RDJ2.rightDeck.fx2AssignmentButton.input + 0x90 + 0x63 + + + + + + + + [Channel1] + RDJ2.leftDeck.loopInButton.input + 0x90 + 0x0F + + + + + + [Channel1] + RDJ2.leftDeck.loopInButton.input + 0x90 + 0x2D + + + + + + [Channel1] + RDJ2.leftDeck.loopOutButton.input + 0x90 + 0x10 + + + + + + [Channel1] + RDJ2.leftDeck.loopOutButton.input + 0x90 + 0x2E + + + + + + [Channel1] + RDJ2.leftDeck.autoLoopButton.input + 0x90 + 0x11 + + + + + + [Channel1] + RDJ2.leftDeck.autoLoopButton.input + 0x90 + 0x2F + + + + + + [Channel1] + RDJ2.leftDeck.loopActiveButton.input + 0x90 + 0x12 + + + + + + [Channel1] + RDJ2.leftDeck.loopActiveButton.input + 0x90 + 0x30 + + + + + + + + [Channel2] + RDJ2.rightDeck.loopInButton.input + 0x90 + 0x4B + + + + + + [Channel2] + RDJ2.rightDeck.loopInButton.input + 0x90 + 0x69 + + + + + + [Channel2] + RDJ2.rightDeck.loopOutButton.input + 0x90 + 0x4C + + + + + + [Channel2] + RDJ2.rightDeck.loopOutButton.input + 0x90 + 0x6A + + + + + + [Channel2] + RDJ2.rightDeck.autoLoopButton.input + 0x90 + 0x4D + + + + + + [Channel2] + RDJ2.rightDeck.autoLoopButton.input + 0x90 + 0x6B + + + + + + [Channel2] + RDJ2.rightDeck.loopActiveButton.input + 0x90 + 0x4E + + + + + + [Channel2] + RDJ2.rightDeck.loopActiveButton.input + 0x90 + 0x6C + + + + + + + + [Master] + RDJ2.trax.traxButton.input + 0x90 + 0x1E + + + + + + [Master] + RDJ2.trax.traxButton.input + 0x90 + 0x3C + + + + + + [Library] + RDJ2.trax.traxKnob.input + 0xB0 + 0x13 + + + + + + [Library] + RDJ2.trax.traxKnob.input + 0xB0 + 0x31 + + + + + + + + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter1 + 0x90 + 0x16 + 0.5 + + + [EqualizerRack1_[Channel1]_Effect1] + button_parameter2 + 0x90 + 0x15 + 0.5 + + + + [Channel1] + keylock + 0x90 + 0x02 + 0.5 + + + [Channel1] + pfl + 0x90 + 0x05 + 0.5 + + + [Channel1] + cue_indicator + 0x90 + 0x17 + 0.5 + + + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter1 + 0x90 + 0x52 + 0.5 + + + [EqualizerRack1_[Channel2]_Effect1] + button_parameter2 + 0x90 + 0x51 + 0.5 + + + + [Channel2] + keylock + 0x90 + 0x3E + 0.5 + + + [Channel2] + pfl + 0x90 + 0x41 + 0.5 + + + [Channel2] + cue_indicator + 0x90 + 0x53 + 0.5 + + + + diff --git a/res/controllers/Reloop-Digital-Jockey-2-IE.scripts.js b/res/controllers/Reloop-Digital-Jockey-2-IE.scripts.js new file mode 100644 index 00000000000..a199e9a425b --- /dev/null +++ b/res/controllers/Reloop-Digital-Jockey-2-IE.scripts.js @@ -0,0 +1,828 @@ +//////////////////////////////////////////////////////////////////////// +// Controller: Reloop Digital Jockey 2 +// URL: https://mixxx.discourse.group/t/reloop-digital-jockey-2-mapping-by-dj-ak/23971 +// Author: DJ aK +// Credits: Uwe Klotz a/k/a tapir (baseline: Denon MC6000MK2 script) +//////////////////////////////////////////////////////////////////////// + +var RDJ2 = {}; + + +//////////////////////////////////////////////////////////////////////// +// Tunable constants // +//////////////////////////////////////////////////////////////////////// + +RDJ2.JOG_SPIN_CUE_PEAK = 0.2; // [0.0, 1.0] +RDJ2.JOG_SPIN_CUE_EXPONENT = 0.7; // 1.0 = linear response + +RDJ2.JOG_SPIN_PLAY_PEAK = 0.3; // [0.0, 1.0] +RDJ2.JOG_SPIN_PLAY_EXPONENT = 0.7; // 1.0 = linear response + +RDJ2.JOG_SCRATCH_RPM = 33.333333; // 33 1/3 +RDJ2.JOG_SCRATCH_ALPHA = 0.125; // 1/8 +RDJ2.JOG_SCRATCH_BETA = RDJ2.JOG_SCRATCH_ALPHA / 32.0; +RDJ2.JOG_SCRATCH_RAMP = true; // required for back spins + +// Seeking: Number of revolutions needed to seek from the beginning +// to the end of the track. +RDJ2.JOG_SEEK_REVOLUTIONS = 2; + + +//////////////////////////////////////////////////////////////////////// +// Fixed constants // +//////////////////////////////////////////////////////////////////////// + +// Controller constants +RDJ2.DECK_COUNT = 2; +RDJ2.JOG_RESOLUTION = 148; // measured/estimated +RDJ2.SHIFT_OFFSET = 0x1E; + + +// Jog constants +RDJ2.MIDI_JOG_DELTA_BIAS = 0x40; // center value of relative movements +RDJ2.MIDI_JOG_DELTA_RANGE = 0x3F; // both forward (= positive) and reverse (= negative) + + +// Mixxx constants +RDJ2.MIXXX_JOG_RANGE = 3.0; +RDJ2.MIXXX_LOOP_POSITION_UNDEFINED = -1; + + +//////////////////////////////////////////////////////////////////////// +// Button/Knob map // +//////////////////////////////////////////////////////////////////////// + +/* This map is necessary as Reloop has designed the controller in such + a way that not all buttons/knobs have the same offset comparing + CH0 and CH1. By looking at the MIDI messages sent by the controller, + we can see that the hardware is designed as symmetric halves. + + In other words, constant offset in hardware corresponds to symmetric + halves, but the controller layout is not fully symmetric. + (e.x. ACTIVATE 1 buttons) + + Thus we need a map to preserve object oriented approach . + + The below map is only for the unshifted controls as shifted ones + have the same handlers mapped in the xml file and the outputs + always refer to the unshifted controls. */ +RDJ2.BUTTONMAP_CH0_CH1 = { + load: [0x13, 0x4F], + play: [0x19, 0x55], + cue: [0x18, 0x54], + sync: [0x01, 0x3D], + search: [0x1A, 0x56], + scratch: [0x1B, 0x57], + fxdrywet: [0x1C, 0x58], + bendminus: [0x03, 0x3F], + bendplus: [0x04, 0x40], + loopin: [0x0F, 0x4B], + loopout: [0x10, 0x4C], + autoloop: [0x11, 0x4D], + loopactive: [0x12, 0x4E], + fx1assign: [0x27, 0x68], //this is the shifted Activate 3 button + fx2assign: [0x2C, 0x63], //this is the shifted FX ON button + highkill: [0x14, 0x50], +}; + +RDJ2.KNOBMAP_CH0_CH1 = { + loopSize: [0x28, 0x63], //this is the shifted Dry/Wet Knob +}; + + +//////////////////////////////////////////////////////////////////////// +// Logging functions // +//////////////////////////////////////////////////////////////////////// + +RDJ2.logDebug = function(msg) { + if (RDJ2.debug) { + print("[" + RDJ2.id + " DEBUG] " + msg); + } +}; + +RDJ2.logInfo = function(msg) { + print("[" + RDJ2.id + " INFO] " + msg); +}; + +RDJ2.logWarning = function(msg) { + print("[" + RDJ2.id + " WARNING] " + msg); +}; + +RDJ2.logError = function(msg) { + print("[" + RDJ2.id + " ERROR] " + msg); +}; + + +//////////////////////////////////////////////////////////////////////// +// Buttons // +//////////////////////////////////////////////////////////////////////// + +RDJ2.MIDI_ON = 0x7F; +RDJ2.MIDI_OFF = 0x00; + +RDJ2.isButtonPressed = function(midiValue) { + switch (midiValue) { + case RDJ2.MIDI_ON: + return true; + case RDJ2.MIDI_OFF: + return false; + default: + RDJ2.logError("Unexpected MIDI button value: " + midiValue); + return undefined; + } +}; + +/* Custom buttons */ + +RDJ2.ShiftButton = function(options) { + this.state = false; + this.connectedContainers = []; + components.Button.call(this, options); +}; +RDJ2.ShiftButton.prototype = new components.Button({ + input: function(channel, control, value) { + //update shift state + this.state = RDJ2.isButtonPressed(value); + this.send(this.outValueScale(this.state)); + + //call shift()/unshift() for each connected container + if (this.state) { + this.connectedContainers.forEach(function(container) { + container.shift(); + }); + } else { + this.connectedContainers.forEach(function(container) { + container.unshift(); + }); + } + }, + isActive: function() { + return this.state; + }, + connectContainer: function(container) { + if (container instanceof components.ComponentContainer === false) { + RDJ2.logError("Container type mismatch"); + } else { + this.connectedContainers.push(container); + RDJ2.logDebug("Connected container " + this.connectedContainers.indexOf(container) + " to shift button 0x" + this.midi[1].toString(16)); + } + } +}); + +RDJ2.LoopInButton = function(options) { + components.Button.call(this, options); +}; +RDJ2.LoopInButton.prototype = new components.Button({ + outKey: "loop_start_position", + outValueScale: function(value) { return value >= 0 ? this.on : this.off; }, + unshift: function() { + this.inKey = "loop_in"; + this.input = components.Button.prototype.input; + }, + shift: function() { + //pressing when shifted will delete loop start marker + this.inKey = "loop_start_position"; + this.input = function(channel, control, value, status) { + if (this.isPress(channel, control, value, status)) { + this.inSetValue(RDJ2.MIXXX_LOOP_POSITION_UNDEFINED); + } + }; + }, +}); + +RDJ2.LoopOutButton = function(options) { + components.Button.call(this, options); +}; +RDJ2.LoopOutButton.prototype = new components.Button({ + outKey: "loop_end_position", + outValueScale: function(value) { return value >= 0 ? this.on : this.off; }, + unshift: function() { + this.inKey = "loop_out"; + this.input = components.Button.prototype.input; + }, + shift: function() { + //pressing when shifted will delete loop end marker + this.inKey = "loop_end_position"; + this.input = function(channel, control, value, status) { + if (this.isPress(channel, control, value, status)) { + this.inSetValue(RDJ2.MIXXX_LOOP_POSITION_UNDEFINED); + } + }; + }, +}); + +RDJ2.AutoLoopButton = function(options) { + components.Button.call(this, options); +}; +RDJ2.AutoLoopButton.prototype = new components.Button({ + outKey: "beatloop_activate", + unshift: function() { + this.inKey = "beatloop_activate"; + }, + shift: function() { + this.inKey = "beatlooproll_activate"; + }, +}); + +RDJ2.LoopActiveButton = function(options) { + components.Button.call(this, options); +}; +RDJ2.LoopActiveButton.prototype = new components.Button({ + outKey: "loop_enabled", + unshift: function() { + this.inKey = "reloop_toggle"; + }, + shift: function() { + this.inKey = "reloop_andstop"; + }, +}); + + +//////////////////////////////////////////////////////////////////////// +// Knobs // +//////////////////////////////////////////////////////////////////////// + +RDJ2.MIDI_KNOB_INC = 0x41; +RDJ2.MIDI_KNOB_DEC = 0x3F; +RDJ2.MIDI_KNOB_DELTA_BIAS = 0x40; // center value of relative movements +//RDJ2.MIDI_KNOB_STEPS = 20; // 20 is full knob's rotation (360deg) +RDJ2.MIDI_KNOB_STEPS = 16; // 16 is more like volume knobs + +RDJ2.getKnobDelta = function(midiValue) { + return midiValue - RDJ2.MIDI_KNOB_DELTA_BIAS; +}; + +RDJ2.knobInput = function(channel, control, value) { + var knobDelta = RDJ2.getKnobDelta(value); + this.inSetParameter(this.inGetParameter() + knobDelta / RDJ2.MIDI_KNOB_STEPS); +}; + +/* Custom knobs */ +RDJ2.LoopSizeKnob = function(options) { + components.Pot.call(this, options); +}; +RDJ2.LoopSizeKnob.prototype = new components.Pot({ + input: function(channel, control, value) { + var knobDelta = RDJ2.getKnobDelta(value); + + if (knobDelta > 0) { + engine.setValue(this.group, "loop_double", true); + } else { + engine.setValue(this.group, "loop_halve", true); + } + } +}); + + +//////////////////////////////////////////////////////////////////////// +// Decks // +//////////////////////////////////////////////////////////////////////// + +RDJ2.JOGMODES = { + normal: 0, + vinyl: 1, + search: 2, + fxdrywet: 3, + trax: 4, +}; + +RDJ2.getJogDeltaValue = function(value) { + if (value === 0x00) { + return 0x00; + } else { + return value - RDJ2.MIDI_JOG_DELTA_BIAS; + } +}; + +/* Constructor */ + +RDJ2.Deck = function(number) { + RDJ2.logDebug("Creating Deck " + number); + + this.number = number; + this.group = "[Channel" + number + "]"; + this.filterGroup = "[QuickEffectRack1_" + this.group + "_Effect1]"; + this.rateDirBackup = this.getValue("rate_dir"); + this.setValue("rate_dir", -1); + this.jogTouchState = false; + + components.Deck.call(this, number); + + //primary buttons + this.loadButton = new RDJ2.LoadButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.load[number - 1]]); + this.playButton = new components.PlayButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.play[number - 1]]); + this.cueButton = new components.CueButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.cue[number - 1]]); + this.syncButton = new components.SyncButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.sync[number - 1]]); + this.bendMinusButton = new RDJ2.BendMinusButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.bendminus[number - 1]]); + this.bendPlusButton = new RDJ2.BendPlusButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.bendplus[number - 1]]); + this.jogModeSelector = new RDJ2.JogModeSelector(number, + RDJ2.BUTTONMAP_CH0_CH1.search[number - 1], + RDJ2.BUTTONMAP_CH0_CH1.scratch[number - 1], + RDJ2.BUTTONMAP_CH0_CH1.fxdrywet[number - 1]); + + //loops + this.loopsizeKnob = new RDJ2.LoopSizeKnob([0xB0, RDJ2.KNOBMAP_CH0_CH1.loopSize[number - 1]]); + this.loopInButton = new RDJ2.LoopInButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.loopin[number - 1]]); + this.loopOutButton = new RDJ2.LoopOutButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.loopout[number - 1]]); + this.autoLoopButton = new RDJ2.AutoLoopButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.autoloop[number - 1]]); + this.loopActiveButton = new RDJ2.LoopActiveButton([0x90, RDJ2.BUTTONMAP_CH0_CH1.loopactive[number - 1]]); + + //effect assignment switches + this.fx1AssignmentButton = new components.EffectAssignmentButton({ + midi: [0x90, RDJ2.BUTTONMAP_CH0_CH1.fx1assign[number - 1]], + effectUnit: 1, + group: "[Channel" + number + "]", + }); + this.fx2AssignmentButton = new components.EffectAssignmentButton({ + midi: [0x90, RDJ2.BUTTONMAP_CH0_CH1.fx2assign[number - 1]], + effectUnit: 2, + group: "[Channel" + number + "]", + }); + + // high kill / quick effect enable button + this.highKillQuickEffectButton = new RDJ2.HighKillQuickEffectButton({ + midi: [0x90, RDJ2.BUTTONMAP_CH0_CH1.highkill[number - 1]], + channelNr: number, + }); + + + // Set the group properties of the above Components and connect their output callback functions + // Without this, the group property for each Component would have to be specified to its + // constructor. + this.reconnectComponents(function(component) { + if (component.group === undefined) { + // 'this' inside a function passed to reconnectComponents refers to the ComponentContainer + // so 'this' refers to the custom Deck object being constructed + component.group = this.currentDeck; + } + }); +}; + +// give our custom Deck all the methods of the generic Deck in the Components library +RDJ2.Deck.prototype = Object.create(components.Deck.prototype); + +/* get/set Values */ + +RDJ2.Deck.prototype.getValue = function(key) { + return engine.getValue(this.group, key); +}; + +RDJ2.Deck.prototype.setValue = function(key, value) { + engine.setValue(this.group, key, value); +}; + +/* Load Track */ + +RDJ2.LoadButton = function(options) { + components.Button.call(this, options); +}; +RDJ2.LoadButton.prototype = new components.Button({ + outKey: "track_loaded", + unshift: function() { + this.inKey = "LoadSelectedTrack"; + }, + shift: function() { + this.inKey = "eject"; + }, +}); + +/* Cue & Play */ + +RDJ2.Deck.prototype.isPlaying = function() { + return this.getValue("play"); +}; + +/* Pitch Bend / Track Search */ + +RDJ2.BendMinusButton = function(options) { + components.Button.call(this, options); +}; +RDJ2.BendMinusButton.prototype = new components.Button({ + key: "rate_temp_down", + input: function(channel, control, value, status) { + var isPlaying = engine.getValue(this.group, "play"); + if (isPlaying) { + engine.setValue(this.group, "back", false); + this.inSetValue(this.isPress(channel, control, value, status)); + } else { + engine.setValue(this.group, "back", this.isPress(channel, control, value, status)); + } + }, +}); + +RDJ2.BendPlusButton = function(options) { + components.Button.call(this, options); +}; +RDJ2.BendPlusButton.prototype = new components.Button({ + key: "rate_temp_up", + input: function(channel, control, value, status) { + var isPlaying = engine.getValue(this.group, "play"); + if (isPlaying) { + engine.setValue(this.group, "fwd", false); + this.inSetValue(this.isPress(channel, control, value, status)); + } else { + engine.setValue(this.group, "fwd", this.isPress(channel, control, value, status)); + } + }, +}); + +/* Jog Mode */ + +RDJ2.JogModeSelector = function(number, searchMidiCtrl, scratchMidiCtrl, fxDryWetMidiCtrl) { + this.number = number; + this.searchMidiCtrl = searchMidiCtrl; + this.scratchMidiCtrl = scratchMidiCtrl; + this.fxDryWetMidiCtrl = fxDryWetMidiCtrl; + this.jogMode = RDJ2.JOGMODES.normal; + this.lastNonTraxJogMode = this.jogMode; + this.input = this.inputNormal; + + components.Component.call(this); + this.updateControls(); +}; +RDJ2.JogModeSelector.prototype = new components.Component({ + updateControls: function() { + var searchValue = this.jogMode === RDJ2.JOGMODES.search ? RDJ2.MIDI_ON : RDJ2.MIDI_OFF; + var scratchValue = this.jogMode === RDJ2.JOGMODES.vinyl ? RDJ2.MIDI_ON : RDJ2.MIDI_OFF; + var fxDryWetValue = this.jogMode === RDJ2.JOGMODES.fxdrywet ? RDJ2.MIDI_ON : RDJ2.MIDI_OFF; + + if (midi.sendShortMsg && this.searchMidiCtrl !== undefined && this.scratchMidiCtrl !== undefined && this.fxDryWetMidiCtrl !== undefined) { + midi.sendShortMsg(0x90, this.searchMidiCtrl, searchValue); + midi.sendShortMsg(0x90, this.scratchMidiCtrl, scratchValue); + midi.sendShortMsg(0x90, this.fxDryWetMidiCtrl, fxDryWetValue); + } + }, + inputNormal: function(channel, control, value) { + var isButtonPressed = RDJ2.isButtonPressed(value); + if (isButtonPressed) { + switch (control) { + case this.searchMidiCtrl: + this.jogMode = this.jogMode === RDJ2.JOGMODES.search ? RDJ2.JOGMODES.normal : RDJ2.JOGMODES.search; + break; + case this.scratchMidiCtrl: + this.jogMode = this.jogMode === RDJ2.JOGMODES.vinyl ? RDJ2.JOGMODES.normal : RDJ2.JOGMODES.vinyl; + break; + case this.fxDryWetMidiCtrl: + this.jogMode = this.jogMode === RDJ2.JOGMODES.fxdrywet ? RDJ2.JOGMODES.normal : RDJ2.JOGMODES.fxdrywet; + break; + default: + RDJ2.logError("Unexpected MIDI ctrl value: " + control); + } + if (this.jogMode !== RDJ2.JOGMODES.vinyl && engine.isScratching(this.number)) { + engine.scratchDisable(this.number, RDJ2.JOG_SCRATCH_RAMP); + } + this.updateControls(); + } + }, + inputTrax: function(channel, control, value) { + var isButtonPressed = RDJ2.isButtonPressed(value); + if (isButtonPressed) { + switch (control) { + case this.searchMidiCtrl: + case this.searchMidiCtrl + RDJ2.SHIFT_OFFSET: + engine.setValue("[Library]", "MoveRight", 1); + break; + case this.scratchMidiCtrl: + case this.scratchMidiCtrl + RDJ2.SHIFT_OFFSET: + engine.setValue("[Library]", "MoveLeft", 1); + break; + case this.fxDryWetMidiCtrl: + case this.fxDryWetMidiCtrl + RDJ2.SHIFT_OFFSET: + //'MoveFocusForward' is equivalent to pressing TAB key on the keyboard + engine.setValue("[Library]", "MoveFocusForward", 1); + break; + /* The below code will probably be removed. + It allowed absolute referencing to buttons to enable + moving focus backward/forward. It seems that using only + 'MoveFocusForward' is enough. + + case RDJ2.BUTTONMAP_CH0_CH1.fxdrywet[0]: + case RDJ2.BUTTONMAP_CH0_CH1.fxdrywet[0] + RDJ2.SHIFT_OFFSET: + engine.setValue('[Library]', 'MoveFocusBackward', 1); + break; + case RDJ2.BUTTONMAP_CH0_CH1.fxdrywet[1]: + case RDJ2.BUTTONMAP_CH0_CH1.fxdrywet[1] + RDJ2.SHIFT_OFFSET: + engine.setValue('[Library]', 'MoveFocusForward', 1); + break;*/ + default: + RDJ2.logError("Unexpected MIDI ctrl value: " + control); + } + } + }, + setTraxMode: function(isTraxModeEnabled) { + if (isTraxModeEnabled) { + if (this.jogMode !== RDJ2.JOGMODES.trax) { + this.lastNonTraxJogMode = this.jogMode; + this.jogMode = RDJ2.JOGMODES.trax; + this.input = this.inputTrax; + //set all LEDs on to indicate trax mode + if (midi.sendShortMsg) { + midi.sendShortMsg(0x90, this.searchMidiCtrl, RDJ2.MIDI_ON); + midi.sendShortMsg(0x90, this.scratchMidiCtrl, RDJ2.MIDI_ON); + midi.sendShortMsg(0x90, this.fxDryWetMidiCtrl, RDJ2.MIDI_ON); + } + } + } else { + this.jogMode = this.lastNonTraxJogMode; + this.input = this.inputNormal; + this.updateControls(); + } + }, + unshift: function() { + var isLibraryModeEnabled = engine.getValue("[Master]", "maximize_library"); + if (!isLibraryModeEnabled) { + this.setTraxMode(false); + } + }, + shift: function() { + var isLibraryModeEnabled = engine.getValue("[Master]", "maximize_library"); + if (!isLibraryModeEnabled) { + this.setTraxMode(true); + } + }, +}); + +/* Jog Wheel */ + +RDJ2.Deck.prototype.onJogTouch = function(channel, control, value) { + var currentJogMode = this.jogModeSelector.jogMode; + this.jogTouchState = RDJ2.isButtonPressed(value); + + if (currentJogMode === RDJ2.JOGMODES.vinyl && this.jogTouchState) { + engine.scratchEnable(this.number, + RDJ2.JOG_RESOLUTION, + RDJ2.JOG_SCRATCH_RPM, + RDJ2.JOG_SCRATCH_ALPHA, + RDJ2.JOG_SCRATCH_BETA, + RDJ2.JOG_SCRATCH_RAMP); + } else if (!this.jogTouchState && engine.isScratching(this.number)) { + engine.scratchDisable(this.number, RDJ2.JOG_SCRATCH_RAMP); + } +}; + +RDJ2.Deck.prototype.onJogSpin = function(channel, control, value) { + var currentJogMode = this.jogModeSelector.jogMode; + var jogDelta = RDJ2.getJogDeltaValue(value); + + if (currentJogMode === RDJ2.JOGMODES.vinyl) { + engine.scratchTick(this.number, jogDelta); + } else if (currentJogMode === RDJ2.JOGMODES.fxdrywet) { + var currMixValue = engine.getParameter("[EffectRack1_EffectUnit" + this.number + "]", "mix"); + engine.setParameter("[EffectRack1_EffectUnit" + this.number + "]", "mix", currMixValue + jogDelta / RDJ2.JOG_RESOLUTION); + } else if (currentJogMode === RDJ2.JOGMODES.search) { + var playPos = engine.getValue(this.group, "playposition"); + if (undefined !== playPos) { + var seekPos = playPos + (jogDelta / (RDJ2.JOG_RESOLUTION * RDJ2.JOG_SEEK_REVOLUTIONS)); + this.setValue("playposition", Math.max(0.0, Math.min(1.0, seekPos))); + } + } else if (currentJogMode === RDJ2.JOGMODES.trax) { + engine.setValue("[Library]", "MoveVertical", jogDelta); + } else if (currentJogMode === RDJ2.JOGMODES.normal) { + var normalizedDelta = jogDelta / RDJ2.MIDI_JOG_DELTA_RANGE; + var scaledDelta; + var jogExponent; + if (this.isPlaying()) { + // bending + scaledDelta = normalizedDelta / RDJ2.JOG_SPIN_PLAY_PEAK; + jogExponent = RDJ2.JOG_SPIN_PLAY_EXPONENT; + } else { + // cueing + scaledDelta = normalizedDelta / RDJ2.JOG_SPIN_CUE_PEAK; + jogExponent = RDJ2.JOG_SPIN_CUE_EXPONENT; + } + var direction; + var scaledDeltaAbs; + if (scaledDelta < 0.0) { + direction = -1.0; + scaledDeltaAbs = -scaledDelta; + } else { + direction = 1.0; + scaledDeltaAbs = scaledDelta; + } + var scaledDeltaPow = direction * Math.pow(scaledDeltaAbs, jogExponent); + var jogValue = RDJ2.MIXXX_JOG_RANGE * scaledDeltaPow; + this.setValue("jog", jogValue); + } else { + RDJ2.logError("onJogSpin unknown mode error!"); + } +}; + + +//////////////////////////////////////////////////////////////////////// +// Effects // +//////////////////////////////////////////////////////////////////////// + +//functions for overriding default unshift/shift functions of efx unit knobs +RDJ2.efxUnitKnobUnshift = function() { + this.input = function(channel, control, value) { + var knobDelta = RDJ2.getKnobDelta(value); + this.inSetParameter(this.inGetParameter() + knobDelta / RDJ2.MIDI_KNOB_STEPS); + }; +}; +RDJ2.efxUnitKnobShift = function() { + this.input = function(channel, control, value) { + var knobDelta = RDJ2.getKnobDelta(value); + var effectGroup = "[EffectRack1_EffectUnit" + + this.eu.currentUnitNumber + "_Effect" + + this.number + "]"; + engine.setValue(effectGroup, "effect_selector", knobDelta); + }; +}; + +//note: the shifted dry/wet knob is mapped to beatloop size + + +//////////////////////////////////////////////////////////////////////// +// Quick Effects // +//////////////////////////////////////////////////////////////////////// + +/* HIGH kill / QuickEffect enable button */ + +RDJ2.HighKillQuickEffectButton = function(options) { + this.channelNr = options.channelNr; + components.Button.call(this, options); +}; +RDJ2.HighKillQuickEffectButton.prototype = new components.Button({ + type: components.Button.prototype.types.powerWindow, + unshift: function() { + this.disconnect(); + this.group = "[EqualizerRack1_[Channel" + this.channelNr + "]_Effect1]"; + this.inKey = "button_parameter3"; + this.outKey = "button_parameter3"; + this.connect(); + this.trigger(); + }, + shift: function() { + this.disconnect(); + this.group = "[QuickEffectRack1_[Channel" + this.channelNr + "]_Effect1]"; + this.inKey = "enabled"; + this.outKey = "enabled"; + this.connect(); + this.trigger(); + }, +}); + + +//////////////////////////////////////////////////////////////////////// +// Library // +//////////////////////////////////////////////////////////////////////// + +/* Trax knob */ + +RDJ2.TraxKnob = function(options) { + components.Encoder.call(this, options); +}; +RDJ2.TraxKnob.prototype = new components.Encoder({ + group: "[Library]", + unshift: function() { + this.inKey = "MoveVertical"; + }, + shift: function() { + this.inKey = "ScrollVertical"; + }, + input: function(channel, control, value) { + var knobDelta = RDJ2.getKnobDelta(value); + this.inSetValue(knobDelta); + } +}); + +/* Trax button */ + +RDJ2.TraxButton = function(obj) { + this.detectedDecks = []; + /* group and/or outKey cannot be defined at prototype initialization + (inside anonymous object passed to components.Button constructor + below) as this would cause additional premature engine.makeConnection + call that binds prototype object's output callback to the outKey. + This would cause uncaught exception when invoking the callback: + + "TypeError: Result of expression 'this.detectedDecks' [undefined] is not an object" + + which happens because at the time we create the prototype object, + we have no information yet about detectedDecks, as detectedDecks is + created at new TraxButton object construction, when its prototype is + long time existing. */ + this.group = "[Master]"; + this.outKey = "maximize_library"; + + this.detectDecks(obj); + components.Button.call(this); +}; +RDJ2.TraxButton.prototype = new components.Button({ + unshift: function() { + this.type = components.Button.prototype.types.toggle; + this.group = "[Master]"; + this.inKey = "maximize_library"; + }, + shift: function() { + this.type = components.Button.prototype.types.push; + this.group = "[Library]"; + this.inKey = "MoveFocusForward"; + }, + output: function(value) { + this.updateTraxMode(value); + }, + updateTraxMode: function(mode) { + this.detectedDecks.forEach(function(deck) { + deck.jogModeSelector.setTraxMode(mode); + }); + }, + detectDecks: function(obj) { + // find decks in the passed object and store them in the array + for (var memberName in obj) { + if (Object.prototype.hasOwnProperty.call(obj, memberName) && obj[memberName] instanceof components.Deck) { + RDJ2.logDebug("Detected " + memberName); + this.detectedDecks.push(obj[memberName]); + } + } + }, +}); + +/* Trax container */ + +RDJ2.Trax = function(obj) { + this.traxKnob = new RDJ2.TraxKnob(); + this.traxButton = new RDJ2.TraxButton(obj); +}; +RDJ2.Trax.prototype = new components.ComponentContainer(); + + +//////////////////////////////////////////////////////////////////////// +// Mixxx Callback Functions // +//////////////////////////////////////////////////////////////////////// + +RDJ2.init = function(id, debug) { + RDJ2.id = id; + RDJ2.debug = debug; + + RDJ2.logInfo("Initializing controller"); + + // left and right shift button + RDJ2.leftShiftButton = new RDJ2.ShiftButton([0x90, 0x0A]); + RDJ2.rightShiftButton = new RDJ2.ShiftButton([0x90, 0x46]); + + // left and right deck + RDJ2.leftDeck = new RDJ2.Deck(1); + RDJ2.rightDeck = new RDJ2.Deck(2); + + // effect unit 1 + RDJ2.fx1 = new components.EffectUnit(1); + RDJ2.fx1.EffectUnitKnob.prototype.unshift = RDJ2.efxUnitKnobUnshift; + RDJ2.fx1.EffectUnitKnob.prototype.shift = RDJ2.efxUnitKnobShift; + RDJ2.fx1.EffectUnitKnob.prototype.eu = RDJ2.fx1; // hack for use by reimplemented unshift/shift + RDJ2.fx1.enableButtons[1].midi = [0x90, 0x07]; + RDJ2.fx1.enableButtons[2].midi = [0x90, 0x0C]; + RDJ2.fx1.enableButtons[3].midi = [0x90, 0x09]; + RDJ2.fx1.knobs[1].midi = [0xB0, 0x07]; + RDJ2.fx1.knobs[2].midi = [0xB0, 0x08]; + RDJ2.fx1.knobs[3].midi = [0xB0, 0x09]; + RDJ2.fx1.dryWetKnob.midi = [0xB0, 0x0A]; + RDJ2.fx1.dryWetKnob.input = RDJ2.knobInput; + RDJ2.fx1.effectFocusButton.midi = [0x90, 0x0E]; + // We need to call unshift() again for each EffectUnitKnob as we + // swapped its implementation after fx object construction (when + // it is called automatically) + for (var n = 1; n <= 3; n++) { + RDJ2.fx1.knobs[n].unshift(); + } + // Now init the fx unit + RDJ2.fx1.init(); + + // effect unit 2 + RDJ2.fx2 = new components.EffectUnit(2); + RDJ2.fx2.EffectUnitKnob.prototype.unshift = RDJ2.efxUnitKnobUnshift; + RDJ2.fx2.EffectUnitKnob.prototype.shift = RDJ2.efxUnitKnobShift; + RDJ2.fx2.EffectUnitKnob.prototype.eu = RDJ2.fx2; // hack for use by reimplemented unshift/shift + RDJ2.fx2.enableButtons[1].midi = [0x90, 0x48]; + RDJ2.fx2.enableButtons[2].midi = [0x90, 0x43]; + RDJ2.fx2.enableButtons[3].midi = [0x90, 0x4A]; + RDJ2.fx2.knobs[1].midi = [0xB0, 0x44]; + RDJ2.fx2.knobs[2].midi = [0xB0, 0x43]; + RDJ2.fx2.knobs[3].midi = [0xB0, 0x46]; + RDJ2.fx2.dryWetKnob.midi = [0xB0, 0x45]; + RDJ2.fx2.dryWetKnob.input = RDJ2.knobInput; + RDJ2.fx2.effectFocusButton.midi = [0x90, 0x45]; + // We need to call unshift() again for each EffectUnitKnob as we + // swapped its implementation after fx object construction (when + // it is called automatically) + for (n = 1; n <= 3; n++) { + RDJ2.fx2.knobs[n].unshift(); + } + // Now init the fx unit + RDJ2.fx2.init(); + + // Trax/library + RDJ2.trax = new RDJ2.Trax(RDJ2); + + // connect decks, efx units and trax to shift buttons + RDJ2.leftShiftButton.connectContainer(RDJ2.leftDeck); + RDJ2.leftShiftButton.connectContainer(RDJ2.fx1); + RDJ2.leftShiftButton.connectContainer(RDJ2.trax); + RDJ2.rightShiftButton.connectContainer(RDJ2.rightDeck); + RDJ2.rightShiftButton.connectContainer(RDJ2.fx2); + RDJ2.rightShiftButton.connectContainer(RDJ2.trax); +}; + +RDJ2.shutdown = function() { + RDJ2.logInfo("Shutting down controller"); +}; 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/Sony-SixxAxis.js b/res/controllers/Sony-SixxAxis.js index cffb07ce240..ba0def6e24e 100644 --- a/res/controllers/Sony-SixxAxis.js +++ b/res/controllers/Sony-SixxAxis.js @@ -139,7 +139,7 @@ SonySixxAxis.registerCallbacks = function(id) { return; } if (controller==undefined) { - HIDDebug("Error registrering callbacks: controller is undefined"); + HIDDebug("Error registering callbacks: controller is undefined"); return; } diff --git a/res/controllers/Stanton-SCS1d-scripts.js b/res/controllers/Stanton-SCS1d-scripts.js index de561e7ff5d..b69d73b1f5b 100644 --- a/res/controllers/Stanton-SCS1d-scripts.js +++ b/res/controllers/Stanton-SCS1d-scripts.js @@ -284,6 +284,7 @@ StantonSCS1d.inboundSysex = function (data, length) { } } } +StantonSCS1d.incomingData = StantonSCS1d.inboundSysex; StantonSCS1d.checkInSetup = function () { if (StantonSCS1d.inSetup) print ("StantonSCS1d: In setup mode, ignoring command."); diff --git a/res/controllers/Stanton-SCS3d-scripts.js b/res/controllers/Stanton-SCS3d-scripts.js index a17fa3ca83a..b0c14bd5618 100644 --- a/res/controllers/Stanton-SCS3d-scripts.js +++ b/res/controllers/Stanton-SCS3d-scripts.js @@ -296,6 +296,7 @@ StantonSCS3d.statusResponse = function (data, length) { } StantonSCS3d.init2(); } +StantonSCS3d.incomingData = StantonSCS3d.statusResponse; StantonSCS3d.shutdown = function () { // called when the MIDI device is closed diff --git a/res/controllers/Traktor Kontrol S4 MK3.hid.xml b/res/controllers/Traktor Kontrol S4 MK3.hid.xml index 51a34b13de8..a254772e6c5 100644 --- a/res/controllers/Traktor Kontrol S4 MK3.hid.xml +++ b/res/controllers/Traktor Kontrol S4 MK3.hid.xml @@ -589,19 +589,6 @@ - - + diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index c7e9d3b8ef1..f520af6e0fd 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -64,7 +64,7 @@ TraktorS3.QuickEffectModeChannelColors = false; // * Hold shift to move the pitch slider without adjusting the rate // * Hold keylock and move the pitch slider to adjust musical pitch // * keylock will still toggle on, but on release, not press. -TraktorS3.PitchSliderRelativeMode = true; +TraktorS3.PitchSliderRelativeMode = false; // The Samplers can operate two ways. // With SamplerModePressAndHold = false, tapping a Sampler button will start the @@ -297,6 +297,11 @@ TraktorS3.Controller = class { TraktorS3.incomingData([inputReportIdx, ...reportData]); } + // The controller state may have overridden our preferred default values, so set them now. + for (const ch in this.Channels) { + TraktorS3.Channel.prototype.setDefaults(ch); + } + // NOTE: Soft takeovers must only be enabled after setting the initial // value, or the above line won't have any effect for (let ch = 1; ch <= 4; ch++) { @@ -1610,6 +1615,17 @@ TraktorS3.Channel = class { this.clipConnection = {}; this.hotcueCallbacks = []; + // The visual order of the channels in Mixxx is 3, 1, 2, 4, but we want + // the crossfader assignments array to match the visual layout + const visualChannelIndex = {3: 0, 1: 1, 2: 2, 4: 3}[this.groupNumber]; + if (TraktorS3.DefaultCrossfaderAssignments[visualChannelIndex] !== null) { + // This goes 0-2 for left, right, and center, but having the values + // in this script's config be -1, 0, and 1 makes much more sense + engine.setValue(group, "orientation", TraktorS3.DefaultCrossfaderAssignments[visualChannelIndex] + 1); + } + } + + setDefaults(group) { // The script by default doesn't change any of the deck's settings, but it's // useful to be able to initialize these settings to your preferences when // you turn on the controller @@ -1628,14 +1644,6 @@ TraktorS3.Channel = class { if (TraktorS3.DefaultKeylockEnabled !== null) { engine.setValue(group, "keylock", TraktorS3.DefaultKeylockEnabled); } - // The visual order of the channels in Mixxx is 4, 2, 1, 3, but we want - // the crossfader assignments array to match the visual layout - const visualChannelIndex = {3: 0, 1: 1, 2: 2, 4: 3}[this.groupNumber]; - if (TraktorS3.DefaultCrossfaderAssignments[visualChannelIndex] !== null) { - // This goes 0-2 for left, right, and center, but having the values - // in this script's config be -1, 0, and 1 makes much more sense - engine.setValue(group, "orientation", TraktorS3.DefaultCrossfaderAssignments[visualChannelIndex] + 1); - } } trackLoadedHandler() { @@ -2283,7 +2291,7 @@ TraktorS3.debugLights = function() { "00" ]; - const data = [Array(), Array(), Array()]; + const data = [[], [], []]; for (let i = 0; i < data.length; i++) { diff --git a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js index 6b400a15a2e..36c76ca8128 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S4-MK2-hid-scripts.js @@ -679,7 +679,7 @@ TraktorS4MK2.lightDeck = function(group) { }; TraktorS4MK2.pointlessLightShow = function() { - var packets = [Object(), Object(), Object()]; + const packets = [[], [], []]; packets[0].length = 52; packets[1].length = 62; diff --git a/res/controllers/Traktor-Kontrol-S4-MK3.js b/res/controllers/Traktor-Kontrol-S4-MK3.js index 3f3fb65fbc2..7d4f7c3d840 100644 --- a/res/controllers/Traktor-Kontrol-S4-MK3.js +++ b/res/controllers/Traktor-Kontrol-S4-MK3.js @@ -95,10 +95,6 @@ const WheelLedBlinkOnTrackEnd = !!engine.getSetting("wheelLedBlinkOnTrackEnd"); // Default: false const MixerControlsMixAuxOnShift = !!engine.getSetting("mixerControlsMicAuxOnShift"); -// Define how many wheel moves are sampled to compute the speed. The more you have, the more the speed is accurate, but the -// less responsive it gets in Mixxx. Default: 5 -const WheelSpeedSample = engine.getSetting("wheelSpeedSample") || 5; - // Make the sampler tab a beatlooproll tab instead // Default: false const UseBeatloopRollInsteadOfSampler = !!engine.getSetting("useBeatloopRollInsteadOfSampler"); @@ -117,7 +113,7 @@ const BeatLoopRolls = [ ]; -// Define the speed of the jogwheel. This will impact the speed of the LED playback indicator, the sratch, and the speed of +// Define the speed of the jogwheel. This will impact the speed of the LED playback indicator, the scratch, and the speed of // the motor if enable. Recommended value are 33 + 1/3 or 45. // Default: 33 + 1/3 const BaseRevolutionsPerMinute = engine.getSetting("baseRevolutionsPerMinute") || 33 + 1/3; @@ -127,11 +123,9 @@ const BaseRevolutionsPerMinute = engine.getSetting("baseRevolutionsPerMinute") | // Default: false const UseMotors = !!engine.getSetting("useMotors"); -// Define how many wheel moves are sampled to compute the speed when using the motor. This is helpful to mitigate delay that -// occurs in communication as well as Mixxx limitation to 20ms latency. -// The more you have, the more the speed is accurate. -// less responsive it gets in Mixxx. Default: 20 -const TurnTableSpeedSample = engine.getSetting("turnTableSpeedSample") || 20; +// Define whether or not the jog wheel plater provide a haptic feedback when going over the cue point. +// Default: true +const CueHapticFeedback = UseMotors && !!engine.getSetting("cueHapticFeedback"); // Define how much the wheel will resist. It is a similar setting that the Grid+Wheel in Tracktor // Value must defined between 0 to 1. 0 is very tight, 1 is very loose. @@ -469,13 +463,6 @@ class Deck extends ComponentContainer { }, true); } } - - if (currentModes.wheelMode === wheelModes.motor) { - this.wheelTouch.touched = true; - engine.beginTimer(MotorWindDownMilliseconds, () => { - this.wheelTouch.touched = false; - }, true); - } this.reconnectComponents(function(component) { if (component.group === undefined || component.group.search(script.channelRegEx) !== -1) { @@ -1141,17 +1128,19 @@ class Mixer extends ComponentContainer { switch (value) { case 0x00: // Picnic Bench / Fast Cut engine.setValue("[Mixer Profile]", "xFaderMode", 0); - engine.setValue("[Mixer Profile]", "xFaderCalibration", 0.9); engine.setValue("[Mixer Profile]", "xFaderCurve", 7.0); break; case 0x01: // Constant Power engine.setValue("[Mixer Profile]", "xFaderMode", 1); - engine.setValue("[Mixer Profile]", "xFaderCalibration", 0.3); engine.setValue("[Mixer Profile]", "xFaderCurve", 0.6); + // Constant power requires to set an appropriate calibration value + // in order to get a smooth curve. + // This is the output of EngineXfader::getPowerCalibration() for + // the "xFaderCurve" 0.6 (pow(0.5, 1.0 / 0.6)) + engine.setValue("[Mixer Profile]", "xFaderCalibration", 0.31498); break; case 0x02: // Additive engine.setValue("[Mixer Profile]", "xFaderMode", 0); - engine.setValue("[Mixer Profile]", "xFaderCalibration", 0.4); engine.setValue("[Mixer Profile]", "xFaderCurve", 0.9); } }, @@ -1275,7 +1264,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) { @@ -1309,7 +1298,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(); @@ -1330,7 +1319,7 @@ class QuickEffectButton extends Button { } } presetLoaded(presetNumber) { - this.color = QuickEffectPresetColors[presetNumber - 2]; + this.color = QuickEffectPresetColors[presetNumber - 1]; this.outConnections[1].trigger(); } outConnect() { @@ -1395,7 +1384,7 @@ Button.prototype.colorMap = new ColorMapper({ 0xCCCCCC: LedColors.white, }); -const wheelRelativeMax = 2 ** 16 - 1; +const wheelRelativeMax = 2 ** 32 - 1; const wheelAbsoluteMax = 2879; const wheelTimerMax = 2 ** 32 - 1; @@ -2371,11 +2360,6 @@ class S4Mk3Deck extends Deck { }); this.wheelMode = wheelModes.vinyl; - let motorWindDownTimer = 0; - const motorWindDownTimerCallback = () => { - engine.stopTimer(motorWindDownTimer); - motorWindDownTimer = 0; - }; this.turntableButton = UseMotors ? new Button({ deck: this, input: function(press) { @@ -2384,14 +2368,10 @@ class S4Mk3Deck extends Deck { this.deck.fluxButton.loopModeOff(true); if (this.deck.wheelMode === wheelModes.motor) { this.deck.wheelMode = wheelModes.vinyl; - motorWindDownTimer = engine.beginTimer(MotorWindDownMilliseconds, motorWindDownTimerCallback, true); engine.setValue(this.group, "scratch2_enable", false); } else { this.deck.wheelMode = wheelModes.motor; const group = this.group; - engine.beginTimer(MotorWindUpMilliseconds, () => { - engine.setValue(group, "scratch2_enable", true); - }, true); } this.outTrigger(); } @@ -2412,9 +2392,6 @@ class S4Mk3Deck extends Deck { if (this.deck.wheelMode === wheelModes.vinyl) { this.deck.wheelMode = wheelModes.jog; } else { - if (this.deck.wheelMode === wheelModes.motor) { - motorWindDownTimer = engine.beginTimer(MotorWindDownMilliseconds, motorWindDownTimerCallback, true); - } this.deck.wheelMode = wheelModes.vinyl; } engine.setValue(this.group, "scratch2_enable", false); @@ -2445,7 +2422,7 @@ class S4Mk3Deck extends Deck { } }, stopScratchWhenOver: function() { - if (this.touched || this.deck.wheelMode === wheelModes.motor) { + if (this.touched) { return; } @@ -2469,45 +2446,36 @@ class S4Mk3Deck extends Deck { this.wheelRelative = new Component({ oldValue: null, deck: this, - // We use a rolling average on a sample of speed received. An alternative could - // be to compute precise speed as soon as two points have been received. While the - // alternative is likely going to reduce the delay. it may introduce imprefection due - // to delays that could occurred at various level, so we stick with the naive average for now - stack: [], - stackIdx: 0, - avgSpeed: 0, - // There is a second sampling group, larger, that improve precision but increase delay, which - // is used in TT mode - stackAvg: [], - stackAvgIdx: 0, - ttAvgSpeed: 0, - input: function(value) { - const oldValue = this.oldValue; - this.oldValue = value; - if (oldValue === null) { + speed: 0, + input: function(value, timestamp) { + if (this.oldValue === null) { // This is to avoid the issue where the first time, we diff with 0, leading to the absolute value + this.oldValue = [value, timestamp, 0]; return; } + let [oldValue, oldTimestamp, speed] = this.oldValue; - let diff = value - oldValue; + if (timestamp < oldTimestamp) { + oldTimestamp -= wheelTimerMax; + } + let diff = value - oldValue; if (diff > wheelRelativeMax / 2) { - diff = (wheelRelativeMax - value + oldValue) * -1; - } else if (diff < -1 * (wheelRelativeMax / 2)) { - diff = wheelRelativeMax - oldValue + value; + oldValue += wheelRelativeMax; + } else if (diff < -wheelRelativeMax / 2) { + oldValue -= wheelRelativeMax; } - this.stack[this.stackIdx] = diff / wheelTimerDelta; - this.stackIdx = (this.stackIdx + 1) % WheelSpeedSample; - - this.avgSpeed = (this.stack.reduce((ps, v) => ps + v, 0) / this.stack.length) * wheelTicksPerTimerTicksToRevolutionsPerSecond; - - this.stackAvg[this.stackAvgIdx] = this.avgSpeed; - this.stackAvgIdx = (this.stackAvgIdx + 1) % TurnTableSpeedSample; - - this.ttAvgSpeed = this.stackAvg.reduce((ps, v) => ps + v, 0) / this.stackAvg.length; + const currentSpeed = (value - oldValue)/(timestamp - oldTimestamp); + if ((currentSpeed <= 0) === (speed <= 0)) { + speed = (speed + currentSpeed)/2; + } else { + speed = currentSpeed; + } + this.oldValue = [value, timestamp, speed]; + this.speed = wheelAbsoluteMax*speed*10; - if (this.avgSpeed === 0 && + if (this.speed === 0 && engine.getValue(this.group, "scratch2") === 0 && engine.getValue(this.group, "jog") === 0 && this.deck.wheelMode !== wheelModes.motor) { @@ -2516,13 +2484,13 @@ class S4Mk3Deck extends Deck { switch (this.deck.wheelMode) { case wheelModes.motor: - engine.setValue(this.group, "scratch2", this.ttAvgSpeed / baseRevolutionsPerSecond); + engine.setValue(this.group, "scratch2", this.speed); break; case wheelModes.loopIn: { const loopStartPosition = engine.getValue(this.group, "loop_start_position"); const loopEndPosition = engine.getValue(this.group, "loop_end_position"); - const value = Math.min(loopStartPosition + (this.avgSpeed * LoopWheelMoveFactor), loopEndPosition - LoopWheelMoveFactor); + const value = Math.min(loopStartPosition + (this.speed * LoopWheelMoveFactor), loopEndPosition - LoopWheelMoveFactor); engine.setValue( this.group, "loop_start_position", @@ -2533,7 +2501,7 @@ class S4Mk3Deck extends Deck { case wheelModes.loopOut: { const loopEndPosition = engine.getValue(this.group, "loop_end_position"); - const value = loopEndPosition + (this.avgSpeed * LoopWheelMoveFactor); + const value = loopEndPosition + (this.speed * LoopWheelMoveFactor); engine.setValue( this.group, "loop_end_position", @@ -2543,24 +2511,81 @@ class S4Mk3Deck extends Deck { break; case wheelModes.vinyl: if (this.deck.wheelTouch.touched || engine.getValue(this.group, "scratch2") !== 0) { - engine.setValue(this.group, "scratch2", this.avgSpeed); + engine.setValue(this.group, "scratch2", this.speed); } else { - engine.setValue(this.group, "jog", this.avgSpeed); + engine.setValue(this.group, "jog", this.speed); } break; default: - engine.setValue(this.group, "jog", this.avgSpeed); + engine.setValue(this.group, "jog", this.speed); } }, }); this.wheelLED = new Component({ deck: this, - outKey: "playposition", - output: function(fractionOfTrack) { + lastPos: 0, + lastMode: null, + outConnect: function() { + if (this.group !== undefined) { + const connection0 = engine.makeConnection(this.group, "playposition", (position) => this.output.bind(this)(position, true, true)); + // This is useful for case where effect would have been fully disabled in Mixxx. This appears to be the case during unit tests. + if (connection0) { + this.outConnections[0] = connection0; + } else { + console.warn(`Unable to connect ${this.group}.playposition' to the controller output. The control appears to be unavailable.`); + } + const connection1 = engine.makeConnection(this.group, "play", (play) => this.output.bind(this)(engine.getValue(this.group, "playposition"), play, play || engine.getValue(this.group, "track_loaded"))); + // This is useful for case where effect would have been fully disabled in Mixxx. This appears to be the case during unit tests. + if (connection1) { + this.outConnections[1] = connection1; + } else { + console.warn(`Unable to connect ${this.group}.play' to the controller output. The control appears to be unavailable.`); + } + const connection2 = engine.makeConnection(this.group, "track_loaded", (trackLoaded) => this.output.bind(this)(engine.getValue(this.group, "playposition"), !trackLoaded ? false : engine.getValue(this.group, "play"), trackLoaded)); + // This is useful for case where effect would have been fully disabled in Mixxx. This appears to be the case during unit tests. + if (connection2) { + this.outConnections[2] = connection2; + } else { + console.warn(`Unable to connect ${this.group}.track_loaded' to the controller output. The control appears to be unavailable.`); + } + } + }, + output: function(fractionOfTrack, playstate, trackLoaded) { if (this.deck.wheelMode > wheelModes.motor) { return; } + // Emit cue haptic feedback if enabled + const samplePos = Math.round(fractionOfTrack * engine.getValue(this.group, "track_samples")); + if (this.deck.wheelTouch.touched && CueHapticFeedback) { + const cuePos = engine.getValue(this.group, "cue_point"); + const forward = this.lastPos <= samplePos; + let fired = false; + const motorDeckData = new Uint8Array([ + 1, 0x20, 1, MaxWheelForce & 0xff, MaxWheelForce >> 8, + ]); + if (forward && this.lastPos < cuePos && cuePos < samplePos) { + fired = true; + } else if (!forward && cuePos < this.lastPos && samplePos <= cuePos) { + motorDeckData[1] = 0xe0; + motorDeckData[2] = 0xfe; + fired = true; + } + if (fired) { + const motorData = new Uint8Array([ + 1, 0x20, 1, 0, 0, + 1, 0x20, 1, 0, 0, + ]); + if (this.deck === TraktorS4MK3.leftDeck) { + motorData.set(motorDeckData); + } else { + motorData.set(motorDeckData, 5); + } + controller.sendOutputReport(49, motorData.buffer, true); + } + } + this.lastPos = samplePos; + const durationSeconds = engine.getValue(this.group, "duration"); const positionSeconds = fractionOfTrack * durationSeconds; const revolutions = positionSeconds * baseRevolutionsPerSecond; @@ -2569,18 +2594,24 @@ class S4Mk3Deck extends Deck { const wheelOutput = new Uint8Array(40).fill(0); wheelOutput[0] = decks[0] - 1; + wheelOutput[4] = this.color + Button.prototype.brightnessOn; - if (engine.getValue(this.group, "end_of_track") && WheelLedBlinkOnTrackEnd) { + if (!trackLoaded) { + wheelOutput[1] = wheelLEDmodes.off; + } else if (playstate && fractionOfTrack < 1 && engine.getValue(this.group, "end_of_track") && WheelLedBlinkOnTrackEnd && !this.deck.wheelTouch.touched) { wheelOutput[1] = wheelLEDmodes.ringFlash; } else { wheelOutput[1] = wheelLEDmodes.spot; wheelOutput[2] = LEDposition & 0xff; wheelOutput[3] = LEDposition >> 8; + if (this.lastMode === wheelLEDmodes.ringFlash) { + wheelOutput[4] = Button.prototype.brightnessOff; + engine.beginTimer(200, () => this.output(fractionOfTrack, playstate, trackLoaded), true); + } } - wheelOutput[4] = this.color + Button.prototype.brightnessOn; + this.lastMode = wheelOutput[1]; controller.sendOutputReport(50, wheelOutput.buffer, true); - } }); @@ -2648,6 +2679,87 @@ class S4Mk3Deck extends Deck { } } +class S4Mk3MotorManager { + constructor(deck) { + this.deck = deck; + this.userHold = 0; + this.oldValue = [0, 0]; + this.baseFactor = parseInt(110 * BaseRevolutionsPerMinute); + this.zeroSpeedForce = 1650; + this.currentMaxWheelForce = MaxWheelForce; + } + tick() { + const motorData = new Uint8Array([ + 1, 0x20, 1, 0, 0, + ]); + const maxVelocity = 10; + let velocity = 0; + + let expectedSpeed = 0; + + const currentSpeed = this.deck.wheelRelative.speed / baseRevolutionsPerSecond; + + if (this.deck.wheelMode === wheelModes.motor + && engine.getValue(this.deck.group, "play")) { + expectedSpeed = engine.getValue(this.deck.group, "rate_ratio"); + const normalisationFactor = 1/expectedSpeed/5; + velocity = expectedSpeed + Math.pow(-5 * (expectedSpeed / 1) * (currentSpeed - expectedSpeed), 3); + } else if (this.deck.wheelMode !== wheelModes.motor) { + if (TightnessFactor > 0.5) { + // Super loose + const reduceFactor = (Math.min(0.5, TightnessFactor - 0.5) / 0.5) * 0.7; + velocity = currentSpeed * reduceFactor; + } else if (TightnessFactor < 0.5) { + // Super tight + const reduceFactor = (2 - Math.max(0, TightnessFactor) * 4); + velocity = expectedSpeed + Math.min( + maxVelocity, + Math.max( + -maxVelocity, + (expectedSpeed - currentSpeed) * reduceFactor + ) + ); + } + } + + velocity *= this.baseFactor; + + if (velocity < 0) { + motorData[1] = 0xe0; + motorData[2] = 0xfe; + velocity = -velocity; + } else if (this.deck.wheelMode === wheelModes.motor && engine.getValue(this.deck.group, "play")) { + velocity += this.zeroSpeedForce; + } + + if (!this.isBlockedByUser() && velocity > MaxWheelForce) { + this.userHold++; + } else if (velocity < MaxWheelForce / 2 && this.userHold > 0) { + this.userHold--; + } + + if (this.isBlockedByUser()) { + engine.setValue(this.deck.group, "scratch2_enable", true); + this.currentMaxWheelForce = this.zeroSpeedForce + parseInt(this.baseFactor * expectedSpeed); + } else if (expectedSpeed && this.userHold === 0 && !this.deck.wheelTouch.touched) { + engine.setValue(this.deck.group, "scratch2_enable", false); + this.currentMaxWheelForce = MaxWheelForce; + } + + velocity = Math.min( + this.currentMaxWheelForce, + Math.floor(velocity) + ); + + motorData[3] = velocity & 0xff; + motorData[4] = velocity >> 8; + return motorData; + } + isBlockedByUser() { + return this.userHold >= 10; + } +} + class S4Mk3MixerColumn extends ComponentContainer { constructor(idx, inReports, outReport, io) { super(); @@ -2798,6 +2910,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); } @@ -2998,176 +3113,15 @@ class S4MK3 { controller.sendOutputReport(129, deckMeters.buffer); }); if (UseMotors) { - engine.beginTimer(20, this.motorCallback.bind(this)); - this.leftVelocityFactor = wheelAbsoluteMax * baseRevolutionsPerSecond * 2; - this.rightVelocityFactor = wheelAbsoluteMax * baseRevolutionsPerSecond * 2; - - this.leftFactor = [this.leftVelocityFactor]; - this.leftFactorIdx = 1; - this.rightFactor = [this.rightVelocityFactor]; - this.rightFactorIdx = 1; - - this.averageLeftCorrectness = []; - this.averageLeftCorrectnessIdx = 0; - this.averageRightCorrectness = []; - this.averageRightCorrectnessIdx = 0; + this.leftMotor = new S4Mk3MotorManager(this.leftDeck); + this.rightMotor = new S4Mk3MotorManager(this.rightDeck); + engine.beginTimer(1, this.motorCallback.bind(this)); } - } motorCallback() { - const motorData = new Uint8Array([ - 1, 0x20, 1, 0, 0, - 1, 0x20, 1, 0, 0, - - ]); - const maxVelocity = 10; - - let velocityLeft = 0; - let velocityRight = 0; - - let expectedLeftSpeed = 0; - let expectedRightSpeed = 0; - - if (this.leftDeck.wheelMode === wheelModes.motor - && engine.getValue(this.leftDeck.group, "play")) { - expectedLeftSpeed = engine.getValue(this.leftDeck.group, "rate_ratio"); - } - - if (this.rightDeck.wheelMode === wheelModes.motor - && engine.getValue(this.rightDeck.group, "play")) { - expectedRightSpeed = engine.getValue(this.rightDeck.group, "rate_ratio"); - } - - const currentLeftSpeed = this.leftDeck.wheelRelative.avgSpeed / baseRevolutionsPerSecond; - const currentRightSpeed = this.rightDeck.wheelRelative.avgSpeed / baseRevolutionsPerSecond; - - if (expectedLeftSpeed) { - velocityLeft = expectedLeftSpeed + Math.min( - maxVelocity, - Math.max( - -maxVelocity, - (expectedLeftSpeed - currentLeftSpeed) - ) - ); - } else { - if (TightnessFactor > 0.5) { - // Super loose - const reduceFactor = (Math.min(0.5, TightnessFactor - 0.5) / 0.5) * 0.7; - velocityLeft = currentLeftSpeed * reduceFactor; - } else if (TightnessFactor < 0.5) { - // Super tight - const reduceFactor = (2 - Math.max(0, TightnessFactor) * 4); - velocityLeft = expectedLeftSpeed + Math.min( - maxVelocity, - Math.max( - -maxVelocity, - (expectedLeftSpeed - currentLeftSpeed) * reduceFactor - ) - ); - - } - } - - if (expectedRightSpeed) { - velocityRight = expectedRightSpeed + Math.min( - maxVelocity, - Math.max( - -maxVelocity, - (expectedRightSpeed - currentRightSpeed) - ) - ); - } else { - if (TightnessFactor > 0.5) { - // Super loose - const reduceFactor = (Math.min(0.5, TightnessFactor - 0.5) / 0.5) * 0.7; - velocityRight = currentRightSpeed * reduceFactor; - } else if (TightnessFactor < 0.5) { - // Super tight - const reduceFactor = (2 - Math.max(0, TightnessFactor) * 4); - console.log(reduceFactor); - velocityRight = expectedRightSpeed + Math.min( - maxVelocity, - Math.max( - -maxVelocity, - (expectedRightSpeed - currentRightSpeed) * reduceFactor - ) - ); - - } - } - - if (velocityLeft < 0) { - motorData[1] = 0xe0; - motorData[2] = 0xfe; - velocityLeft = -velocityLeft; - } - - if (velocityRight < 0) { - motorData[6] = 0xe0; - motorData[7] = 0xfe; - velocityRight = -velocityRight; - } - - const roundedCurrentLeftSpeed = Math.round(currentLeftSpeed * 100); - const roundedCurrentRightSpeed = Math.round(currentRightSpeed * 100); - - velocityLeft = velocityLeft * this.leftVelocityFactor; - velocityRight = velocityRight * this.rightVelocityFactor; - - const minNormalFactor = 0.8 * wheelAbsoluteMax * baseRevolutionsPerSecond * 2; - const maxNormalFactor = 1.2 * wheelAbsoluteMax * baseRevolutionsPerSecond * 2; - - if (velocityLeft > minNormalFactor && velocityLeft < maxNormalFactor) { - this.averageLeftCorrectness[this.averageLeftCorrectnessIdx] = roundedCurrentLeftSpeed; - this.averageLeftCorrectnessIdx = (this.averageLeftCorrectnessIdx + 1) % 10; - const averageCorrectness = Math.round(this.averageLeftCorrectness.reduce((a, b) => a+b, 0) / this.averageLeftCorrectness.length); - this.leftFactor[this.leftFactorIdx] = velocityLeft; - this.leftFactorIdx = (this.leftFactorIdx + 1) % 10; - const averageFactor = Math.round(this.leftFactor.reduce((a, b) => a+b, 0) / this.leftFactor.length); - - - if ((averageCorrectness < 100 && velocityLeft > this.leftVelocityFactor) || (averageCorrectness > 100 && velocityLeft < this.leftVelocityFactor)) { - this.leftVelocityFactor = averageFactor; - } - } - - if (velocityRight > minNormalFactor && velocityRight < maxNormalFactor) { - this.averageRightCorrectness[this.averageRightCorrectnessIdx] = roundedCurrentRightSpeed / (expectedRightSpeed || 0.001); - this.averageRightCorrectnessIdx = (this.averageRightCorrectnessIdx + 1) % 20; - const averageCorrectness = Math.round(this.averageRightCorrectness.reduce((a, b) => a+b, 0) / this.averageRightCorrectness.length); - this.rightFactor[this.rightFactorIdx] = velocityRight; - this.rightFactorIdx = (this.rightFactorIdx + 1) % 20; - const averageFactor = Math.round(this.rightFactor.reduce((a, b) => a+b, 0) / this.rightFactor.length); - - - if ((averageCorrectness < 100 && velocityRight > this.rightVelocityFactor) || (averageCorrectness > 100 && velocityRight < this.rightVelocityFactor)) { - this.rightVelocityFactor = averageFactor; - } - } - - if (velocityLeft) { - velocityLeft += wheelAbsoluteMax / 2; - } - - if (velocityRight) { - velocityRight += wheelAbsoluteMax / 2; - } - - velocityLeft = Math.min( - MaxWheelForce, - Math.floor(velocityLeft) - ); - - velocityRight = Math.min( - MaxWheelForce, - Math.floor(velocityRight) - ); - - motorData[3] = velocityLeft & 0xff; - motorData[4] = velocityLeft >> 8; - - motorData[8] = velocityRight & 0xff; - motorData[9] = velocityRight >> 8; + var motorData = new Uint8Array(10); + motorData.set(this.leftMotor.tick()); + motorData.set(this.rightMotor.tick(), 5); controller.sendOutputReport(49, motorData.buffer, true); } incomingData(data) { @@ -3188,9 +3142,8 @@ class S4MK3 { if (wheelTimerDelta < 0) { wheelTimerDelta += wheelTimerMax; } - - this.leftDeck.wheelRelative.input(view.getUint16(12, true)); - this.rightDeck.wheelRelative.input(view.getUint16(40, true)); + this.leftDeck.wheelRelative.input(view.getUint32(12, true), view.getUint32(8, true)); + this.rightDeck.wheelRelative.input(view.getUint32(40, true), view.getUint32(36, true)); } else { console.warn(`Unsupported HID repord with ID ${reportId}. Contains: ${data}`); } diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 4199439026e..36d0ff74298 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -140,58 +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"; -}; - -script.isSimpleObject = isSimpleObject; - -/** - * 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 - */ -const deepMerge = function(target, source) { - 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]}); - } - }); - } -}; - -script.deepMerge = deepMerge; - // ----------------- Mapping constants --------------------- // Library column value, which can be used to interact with the CO for "[Library] sort_column" @@ -249,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 e40f67db0d2..0aa26d09804 100644 --- a/res/controllers/engine-api.d.ts +++ b/res/controllers/engine-api.d.ts @@ -18,6 +18,17 @@ declare interface ScriptConnection { * Note: To execute all callback functions connected to a ControlObject at once, use {@link engine.trigger} instead */ trigger(): void; + + /** + * String representation of the unique UUID of this connection instance + */ + readonly id: string; + + /** + * whether the connection instance is actually responds to changes to the ControlObject it was created for. + * This is always true for a newly created instance and usually false after calling {@link disconnect} + */ + readonly isConnected: boolean; } @@ -123,7 +134,7 @@ declare namespace engine { * @param callback JS function, which will be called every time, the value of the connected control changes. * @returns Returns script connection object on success, otherwise 'undefined'' */ - function makeConnection(group: string, name: string, callback: CoCallback): ScriptConnection |undefined; + function makeConnection(group: string, name: string, callback: CoCallback): ScriptConnection | undefined; /** * Connects a specified Mixxx Control with a callback function, which is executed if the value of the control changes @@ -148,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; @@ -162,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; @@ -290,4 +304,42 @@ 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 + WesternEurope, // 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) + Latin1 // ISO_8859_1, available on Qt < 6.5 + } + + /** + * 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/lodash.mixxx.js b/res/controllers/lodash.mixxx.js index 1bda64e52b9..2d5b3f2d477 100644 --- a/res/controllers/lodash.mixxx.js +++ b/res/controllers/lodash.mixxx.js @@ -8,6 +8,12 @@ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ ;(function() { + const DEPRECATION_MSG = ( + "Lodash is deprecated and will be removed in a future release of Mixxx; refer to the page " + + "https://github.com/mixxxdj/mixxx/wiki/Lodash-Migration for migrations instructions" + ); + + console.warn(DEPRECATION_MSG); /** Used as a safe reference for `undefined` in pre-ES5 environments. */ var undefined; @@ -17068,8 +17074,17 @@ /*--------------------------------------------------------------------------*/ // Export lodash. + /** + * @deprecated since 2.5.0 + * @global + */ var _ = runInContext(); // Export to the global object. - root._ = _; + Object.defineProperty(root, "_", { + get() { + console.warn(DEPRECATION_MSG); + return _; + } + }) }.call(this)); diff --git a/res/controllers/midi-components-0.0.js b/res/controllers/midi-components-0.0.js index 30518aac633..3332be0b16e 100644 --- a/res/controllers/midi-components-0.0.js +++ b/res/controllers/midi-components-0.0.js @@ -27,6 +27,7 @@ */ (function(global) { + const NO_TIMER = 0; const Component = function(options) { if (Array.isArray(options) && typeof options[0] === "number") { this.midi = options; @@ -176,6 +177,9 @@ // in any Buttons that act differently with short and long presses // to keep the timeouts uniform. longPressTimeout: 275, + triggerOnRelease: false, + isLongPressed: false, + longPressTimer: NO_TIMER, isPress: function(channel, control, value, _status) { return value > 0; }, @@ -192,15 +196,17 @@ this.isLongPressed = false; this.longPressTimer = engine.beginTimer(this.longPressTimeout, () => { this.isLongPressed = true; - this.longPressTimer = 0; + this.longPressTimer = NO_TIMER; }, true); } else { if (this.isLongPressed) { this.inToggle(); + } else if (this.triggerOnRelease) { + this.trigger(); } - if (this.longPressTimer !== 0) { + if (this.longPressTimer !== NO_TIMER) { engine.stopTimer(this.longPressTimer); - this.longPressTimer = 0; + this.longPressTimer = NO_TIMER; } this.isLongPressed = false; } @@ -257,15 +263,15 @@ engine.setValue(this.group, "beatsync", 1); this.longPressTimer = engine.beginTimer(this.longPressTimeout, () => { engine.setValue(this.group, "sync_enabled", 1); - this.longPressTimer = 0; + this.longPressTimer = NO_TIMER; }, true); } else { engine.setValue(this.group, "sync_enabled", 0); } } else { - if (this.longPressTimer !== 0) { + if (this.longPressTimer !== NO_TIMER) { engine.stopTimer(this.longPressTimer); - this.longPressTimer = 0; + this.longPressTimer = NO_TIMER; } } }; @@ -481,8 +487,7 @@ if (this.max === Component.prototype.max) { this.max = (1 << 14) - 1; } - value = (value << 7) + (this._firstLSB ? this._firstLSB : 0); - this.input(channel, control, value, status, group); + this.input(channel, control, (value << 7) + (this._firstLSB ? this._firstLSB : 0), status, group); } this.MSB = value; }, @@ -651,6 +656,10 @@ // Unset isShifted for each ComponentContainer recursively this.isShifted = false; }, + /** + * @param newLayer Layer to apply to this + * @param reconnectComponents Whether components should be reconnected or not + */ applyLayer: function(newLayer, reconnectComponents) { if (reconnectComponents !== false) { reconnectComponents = true; @@ -661,7 +670,7 @@ }); } - script.deepMerge(this, newLayer); + Object.assign(this, newLayer); if (reconnectComponents === true) { this.forEachComponent(function(component) { @@ -733,16 +742,50 @@ }); const JogWheelBasic = function(options) { + if (options.deck !== undefined && options.group !== undefined) { + console.warn( + "options.deck and option.group are both set; " + + "options.deck will take priority" + ); + } + + const deck = options.deck; + const group = script.deckFromGroup(options.group); + delete options.deck; + delete options.group; + Component.call(this, options); - if (!Number.isInteger(this.deck)) { - console.warn("missing scratch deck"); - return; - } - if (this.deck <= 0) { - console.warn("invalid deck number: " + this.deck); - return; + this._deck = undefined; + + Object.defineProperties(this, { + deck: { + get: () => this._deck, + set: (value) => { + if (Number.isInteger(value) && value > 0) { + this._deck = value; + this.reset(); + } + }, + }, + group: { + get: () => `[Channel${deck}]`, + set: value => { + const deck = script.deckFromGroup(value); + if (deck > 0) { + this._deck = deck; + this.reset(); + } + }, + } + }); + + this.deck = deck; + + if (!this.deck) { + this.group = group; // try setting deck from group } + if (!Number.isInteger(this.wheelResolution)) { console.warn("missing jogwheel resolution"); return; @@ -757,9 +800,7 @@ if (!Number.isFinite(this.rpm)) { this.rpm = 33 + 1/3; } - if (this.group === undefined) { - this.group = "[Channel" + this.deck + "]"; - } + this.inKey = "jog"; }; @@ -771,6 +812,10 @@ return value < 0x40 ? value : value - (this.max + 1); }, inputWheel: function(_channel, _control, value, _status, _group) { + if (!this.deck) { + return; + } + value = this.inValueScale(value); if (engine.isScratching(this.deck)) { engine.scratchTick(this.deck, value); @@ -779,6 +824,10 @@ } }, inputTouch: function(channel, control, value, status, _group) { + if (!this.deck) { + return; + } + if (this.isPress(channel, control, value, status) && this.vinylMode) { engine.scratchEnable(this.deck, this.wheelResolution, @@ -793,13 +842,7 @@ throw "Called wrong input handler for " + status + ": " + control + ".\n" + "Please bind jogwheel-related messages to inputWheel and inputTouch!\n"; }, - // this is needed for features such as "deck switching" that work - // by changing the component group. It is assumed they call `connect` - // afterwards. - connect: function() { - Component.prototype.connect.call(this); - this.deck = parseInt(script.channelRegEx.exec(this.group)[1]); - } + reset() {}, }); const EffectUnit = function(unitNumbers, allowFocusWhenParametersHidden, colors) { @@ -1135,8 +1178,6 @@ this.effectFocusButton = new Button({ group: this.group, - longPressed: false, - longPressTimer: 0, pressedWhenParametersHidden: false, previouslyFocusedEffect: 0, startEffectFocusChooseMode: function() { @@ -1162,9 +1203,10 @@ this.input = function(channel, control, value, status, _group) { const showParameters = engine.getValue(this.group, "show_parameters"); if (this.isPress(channel, control, value, status)) { - this.longPressTimer = engine.beginTimer(this.longPressTimeout, - this.startEffectFocusChooseMode.bind(this), - true); + this.longPressTimer = engine.beginTimer(this.longPressTimeout, () => { + this.startEffectFocusChooseMode(); + this.longPressTimer = NO_TIMER; + }, true); if (!showParameters) { if (!allowFocusWhenParametersHidden) { engine.setValue(this.group, "show_parameters", 1); @@ -1174,8 +1216,9 @@ this.pressedWhenParametersHidden = true; } } else { - if (this.longPressTimer) { + if (this.longPressTimer !== NO_TIMER) { engine.stopTimer(this.longPressTimer); + this.longPressTimer = NO_TIMER; } if (eu.focusChooseModeActive) { 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/keyboard/cs_CZ.kbd.cfg b/res/keyboard/cs_CZ.kbd.cfg index 6af18ba2a3f..d12ee77db1b 100644 --- a/res/keyboard/cs_CZ.kbd.cfg +++ b/res/keyboard/cs_CZ.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+\ + +[Library] +EditItem r diff --git a/res/keyboard/da_DK.kbd.cfg b/res/keyboard/da_DK.kbd.cfg index 323a0942d82..9255c56f1bf 100644 --- a/res/keyboard/da_DK.kbd.cfg +++ b/res/keyboard/da_DK.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+< + +[Library] +EditItem r diff --git a/res/keyboard/de_CH.kbd.cfg b/res/keyboard/de_CH.kbd.cfg index 99ccb5361ec..b4da055bdfe 100644 --- a/res/keyboard/de_CH.kbd.cfg +++ b/res/keyboard/de_CH.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+< + +[Library] +EditItem r diff --git a/res/keyboard/de_DE.kbd.cfg b/res/keyboard/de_DE.kbd.cfg index 9fdb8e712b6..b912eef63e6 100644 --- a/res/keyboard/de_DE.kbd.cfg +++ b/res/keyboard/de_DE.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+< + +[Library] +EditItem r diff --git a/res/keyboard/el_GR.kbd.cfg b/res/keyboard/el_GR.kbd.cfg index 140ce77cf7a..4674298283f 100644 --- a/res/keyboard/el_GR.kbd.cfg +++ b/res/keyboard/el_GR.kbd.cfg @@ -168,3 +168,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+` + +[Library] +EditItem r diff --git a/res/keyboard/en_US.kbd.cfg b/res/keyboard/en_US.kbd.cfg index 7fe10956a9f..3b8050bfe72 100644 --- a/res/keyboard/en_US.kbd.cfg +++ b/res/keyboard/en_US.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+` + +[Library] +EditItem r diff --git a/res/keyboard/es_ES.kbd.cfg b/res/keyboard/es_ES.kbd.cfg index 72423c40319..cfe492491ec 100644 --- a/res/keyboard/es_ES.kbd.cfg +++ b/res/keyboard/es_ES.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+< + +[Library] +EditItem r diff --git a/res/keyboard/fi_FI.kbd.cfg b/res/keyboard/fi_FI.kbd.cfg index ea197ca1f3a..9ec9721d138 100644 --- a/res/keyboard/fi_FI.kbd.cfg +++ b/res/keyboard/fi_FI.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+< + +[Library] +EditItem r diff --git a/res/keyboard/fr_CH.kbd.cfg b/res/keyboard/fr_CH.kbd.cfg index d3831aeb8d0..ed65a7de8a4 100644 --- a/res/keyboard/fr_CH.kbd.cfg +++ b/res/keyboard/fr_CH.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+< + +[Library] +EditItem r diff --git a/res/keyboard/fr_FR.kbd.cfg b/res/keyboard/fr_FR.kbd.cfg index eb1a5ba474a..7c1f50d2077 100644 --- a/res/keyboard/fr_FR.kbd.cfg +++ b/res/keyboard/fr_FR.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+` + +[Library] +EditItem r diff --git a/res/keyboard/it_IT.kbd.cfg b/res/keyboard/it_IT.kbd.cfg index 9ef866ec571..46063ceb6c3 100644 --- a/res/keyboard/it_IT.kbd.cfg +++ b/res/keyboard/it_IT.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+< + +[Library] +EditItem r diff --git a/res/keyboard/ru_RU.kbd.cfg b/res/keyboard/ru_RU.kbd.cfg index beda6b6fa7b..f506f422c51 100644 --- a/res/keyboard/ru_RU.kbd.cfg +++ b/res/keyboard/ru_RU.kbd.cfg @@ -163,3 +163,6 @@ OptionsMenu_DeveloperStatsExperiment Ctrl+Shift+E OptionsMenu_DeveloperStatsBase Ctrl+Shift+B DeveloperMenu_EnableDebugger Ctrl+Shift+D OptionsMenu_EnableShortcuts Ctrl+| + +[Library] +EditItem r 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 f10bef3d2c5..acacbbf60ac 100644 --- a/res/linux/org.mixxx.Mixxx.metainfo.xml +++ b/res/linux/org.mixxx.Mixxx.metainfo.xml @@ -96,88 +96,1034 @@ Do not edit it manually. --> - + +

+ Engine +

+
    +
  • + fix: sync rate using the current BPM instead of the file one + #13671 + #12738 +
  • +
  • + fix: prevent null CO access when cloning sampler or preview + #13740 +
  • +
  • + Tooltips: fix cue mode setting location + #14045 +
  • +
+

+ Preferences +

+
    +
  • + (fix) Sound preferences: don't set m_settingsModified in update slots + #13450 +
  • +
  • + 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 +
  • +
+

+ Skins +

+
    +
  • + LegacySkinParser: Short-circuit if template fails to open + #13488 +
  • +
  • + Update waveforms_container.xml + #13501 +
  • +
+

+ Library +

+
    +
  • + feat: static color coding for key column + #13390 +
  • +
  • + fix: Key text is elided from left, should be right + #13475 +
  • +
  • + Add Key Color Palettes + #13497 +
  • +
  • + Fix BPM and Bitrate columns were wider than normal + #13571 +
  • +
  • + 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 +
  • +
+

+ Effects +

+
    +
  • + Compressor effect: Adjust Makeup Time constant calculation + #13261 + #13237 +
  • +
  • + fix: prevent quickFX model out of bound + #13668 +
  • +

Waveforms

  • - Simplify waveform combobox in preferences - #13220 - #6428 - #13226 + Simplify waveform combobox in preferences + #13220 + #6428 + #13226 +
  • +
  • + Disable textured waveforms when using OpenGL ES + #13381 + #13380 +
  • +
  • + ControllerRenderingEngine: Patch out unavailable APIs when using GL ES + #13382 +
  • +
  • + fix: invalid slip render marker + #13422 +
  • +
  • + Add minute markers on horizontal waveform overview + #13401 + #5843 + #13648 +
  • +
  • + Fix high details waveforms wrapping around after visual index 65K + #13491 +
  • +
  • + Fix: support for new WaveformData struct in shaders + #13474 + #13472 +
  • +
  • + Overview waveform: draw minute markers on top of played overlay + #13489 +
  • +
  • + fix: remove scaleSignal in waveform analyzer + #13416 +
  • +
  • + feat: improve screen rendering framework + #13737 +
  • +
  • + fix: prevent double free on DigitsRenderer + #13859 +
  • +
  • + fix: waveform overview seeking + #13947 + #13946 +
  • +
  • + rendergraph: add rendergraph library + #14007 +
  • +
  • + mark rendering improvements + #13969 +
  • +
+

+ STEM support +

+
    +
  • + Add simple support for STEM files + #13044 +
  • +
  • + Multithreaded Rubberband + #13143 + #13649 +
  • +
  • + Add support for stem in the engine + #13070 +
  • +
  • + Add analyser support for stem + #13106 +
  • +
  • + Add stem controls + #13086 +
  • +
  • + Add quick effect support on stem + #13123 +
  • +
  • + Add stem files to the taglib lookup table + #13612 +
  • +
  • + fix: exclude stem samples for QML waveform + #13655 +
  • +
  • + Non-floating stem controls for LateNight + #13537 +
  • +
  • + (fix) make "stem_group,mute" a powerwindow button + #13751 + #13749 +
  • +
  • + feat: add advanced stem loading COs + #13268 +
  • +
  • + Fix build with -DSTEM=OFF + #13948 +
  • +
  • + Stem control test fix + #13960 +
  • +
  • + Solves problem with special characters in path to stems + #13784 +
  • +
+

+ Controller Backend +

+
    +
  • + Add screen renderer to support controllers with a screen + #11407 + #13334 +
  • +
  • + Deprecate + lodash.mixxx.js + , and + script.deepMerge + #13460 +
  • +
  • + Don't return in JogWheelBasic on deck absent in option + #13425 +
  • +
  • + Refactor: modernize softtakeover code + #13553 +
  • +
  • + document + ScriptConnection + readonly properties & slight cleanup + #13630 +
  • +
  • + Modernize Hid/Bulk Lists + #13622 +
  • +
  • + Prevent deadlock with BULK transfer and reduce log noise + #13735 +
  • +
  • + feat: add file and color controller setting types + #13669 +
  • +
  • + Controllers: allow to enable MIDI Through Port in non-developer sessions + #13909 +
  • +
  • + Expose convertCharset convenience function to controllers + #13935 +
  • +
+

+ Auto-DJ +

+
    +
  • + Add AutoDJ xfader recenter option (default off) + #13303 + #11571 +
  • +
  • + Auto DJ: Add context menu action for enabling/disabling the Auto DJ + #13593 +
  • +
  • + Auto DJ Cross fader center + #13628 +
  • +
+

+ Experimental Features +

+
    +
  • + SoundManagerIOS: Remove unsupported/redundant options + #13487 +
  • +
  • + ControllerRenderingEngine: Disable BGRA when targeting Wasm + #13502 +
  • +
  • + BaseTrackTableModel: Disable inline track editing on iOS + #13494 +
  • +
  • + set QQuickStyle to "basic" + #13696 + #13600 +
  • +
  • + fix: trigger QML waveform slot at init + #13736 +
  • +
+

+ Target support +

+
    +
  • + DlgPrefSound: Add missing ifdefs for building without Rubberband + #13577 +
  • +
  • + Update Linux-GitHub runner to Ubuntu 24.04.01 LTS + #13781 + #13880 +
  • +
  • + Add missing qt6-declarative-private-dev and qt6-base-private-dev package + #13904 +
  • +
+

+ Misc Refactorings +

+
    +
  • + Refactor/shrink modernize scopedtimer + #13258 +
  • +
  • + Improve use of parented_ptr + #13411 +
  • +
  • + Pre-allocate memory in basetrackcache to avoid multiple reallocations + #13368 +
  • +
  • + Bump actions/checkout from 4.1.6 to 4.1.7 + #13386 +
  • +
  • + Bump actions/checkout from 4.1.7 to 4.2.0 + #13713 +
  • +
  • + 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 +

+
    +
  • + Behringer DDM4000 & BCR2000: Update mappings to 2.5 + #14232 + #14349 +
  • +
  • + Hercules Inpulse 300: add toneplay, slicer, and beatmatch functionalities + #14051 + #14057 +
  • +
  • + Numark NS6II: New mapping + #11075 +
  • +
  • + Numark Platinum FX: New mapping + #12872 +
  • +
  • + Pioneer-DDJ-SB3: Fixes slip mode and adds missing knob controls + #11307 +
  • +
  • + Reloop Digital Jockey 2 IE: New mapping + #4614 + #14328 +
  • +
  • + Traktor S4mk3: Set 4 decks, avoid CO warnings for decks 3/4, eg. VU meter + #14249 +
  • +
  • + Traktor S4mk3: Smooth xfader curve for Const Power mode + #14305 + #14329 + #14103 +
  • +
  • + Traktor S4mk3: stop wheel led blinking when track is over/stopped + #14028 + #13995 +
  • +
  • + Traktor Kontrol S3: Use pitch absolute mode as described in the manual + #14123

- STEM support + Controller Backend

  • - Add simple support for STEM files - #13044 + Controllers: Avoid timer warning on button release + #14323
  • - Multithreaded Rubberband - #13143 + Controller preferences: Fix notify of pending changes when closing preferences + #14234 + #14220 +
  • +
  • + Controller preferences: Fix broken overwrite dialog ('Save as..' not working) + #14263 +
  • +
  • + Controller preferences: Don't break support link texts + #14079 +
  • +
  • + Controller preferences: Fix wrong mapping change confirmation request caused by MidiController::makeInputHandler() + #14281 + #14280 + #14292 +
  • +
  • + Controller mapping info: Fix cropped description text + #14332 + #14117 +
  • +
  • + MIDI controller learning: Make control box search usable + #14260 +
  • +
  • + MIDI controller learning: Don't reload mapping after learn + #14253 +
  • +
  • + MIDI controller learning: Correct skin control for mic/aux section + #14221 +
  • +
  • + Allow + midino + 0 in `MidiController::makeInputHandler() + #14266 + #14265 +
  • +
  • + Fix: provide + incomingData + to MIDI sysex mappings + #14368 + #13133 +
  • +
  • + Fix log spam when using Midi for light mapping + #14326 + #14327 + #14333 + #14338 + #14371 +
  • +
  • + Fix for + TypeError + in + midi-components-0.0.js + #14203 + #14197

- Controller Backend + Skins

  • - Add screen renderer to support controllers with a screen - #11407 - #13334 + Deere (64 samplers): Bring back library in regular view + #14101 + #14097 +
  • +
  • + Fix crash when hiding waveforms in Deere + #14170 +
  • +
  • + Waveform Overview: Abort play pos dragging if cursor is released outside the valid area + #13741 + #13732 +
  • +
  • + Waveform Overview: Also render analysis progress when triggered by track menu or analysis feature + #14150 +
  • +
  • + Don't show 'menubar hide' dialog when switching skins + #14254 +
  • +
  • + Key Wheel: Move to View menu and make it a floating tool window + #14256 + #14239

- Auto-DJ + Library

  • - Add AutoDJ xfader recenter option (default off) - #13303 - #11571 + Add Ctrl+Shift+C to copy the content of the selected cell(s) (The Mxxx 2.4 behaviour of Ctrl+C). + #14114 + #14065 +
  • +
  • + Fix MusicBrainz lookup on Windows and macOS + #14216 +
  • +
  • + Library scanner: Update cached 'missing' flag when file is redicovered + #14250 +
  • +
  • + Hidden Tracks: Allow 'load to' via track context manu + #14077 +
  • +
  • + Update to libdjinterop 0.24.3 - support for Engine 4.1/4.2 + #14172 + #14289 +
  • +
  • + Fix writing metadata via symlink + #13711 +
  • +
  • + Library menu: change "Engine DJ Prime" to "Engine DJ" + #14248 +
  • +
  • + Fix file extension handling during playlist export + #14381 +
  • +
  • + Fix manual key metadata editing in track properties dialog + #14022 + #14400 + #14295 + #14294 +
  • +
  • + History: Don't allow joining with locked previous playlist + #14401 + #14399 +
  • +
  • + Track Menu: Reset + eject + after moving track file to trash + #14402

- Misc Refactorings + Other Fixes

  • - Refactor/shrink modernize scopedtimer - #13258 + Enable R3 time-stretching with Rubberband 4.0.0 API version numbers + #14100 +
  • +
  • + Preferences Effects: add Hide/Unhide (move) buttons to Effects tab + #13329 +
  • +
  • + Preferences Effects: left/right key in effect lists trigger hide/unhide + #14205 +
  • +
  • + Fix beat sync in Flanger effect + #14351 +
  • +
  • + Apply talkover ducking after main effects to allow using a compressor effect + #13844 + #12451 +
  • +
  • + Fix sporadic deadlocks when closing Mixxx or changing sound devices + #14208 + #14055 +
  • +
  • + PositionScratchController: Fix loop wrap-around case + #14379 +
  • +
  • + Allow seeking to a hotcue during waveform scratching + #14357 + #13981 +
  • +
+

+ Target support +

+
    +
  • + Allow to build with git "showSignature = true" + #14115 + #12997 +
  • +
  • + Support building with Qt 6.8/6.9 + #14080 + #14071 + #14200 + #14204 +
  • +
  • + Welcome Ubuntu Plucky Puffin; Good bye Mantic Minotaur + #14148 + #14158 +
  • +
  • + Add more translations to Linux desktop file + #14153 + #14169 +
  • +
  • + Debian: recommend qt6-translations-l10n + #14147 +
  • +
  • + Update FindFFTW3.cmake to not find version 2 + #13937 + #13931 +
  • +
  • + Allow building without tests-tools via new CMake options BUILD_TESTING and BUILD_BENCH + #14269 +
  • +
  • + Fix and improve "missing env" error message + #14321 +
  • +
  • + Qt6.8: Ensure Mixxx uses "windowsvista" Qt style on Windows + #14228
- +

- Waveforms + Modernized Platform: Update to Qt6

  • - SlipMode waveform visual for RGB GLSL - #13002 - #13256 + Mixxx is now using Qt6, offering improved performance and enhanced compatibility with modern systems. + #11863 + #11892
  • - Show beats and time until next marker in the waveform - #12994 - #13311 + Build system defaults to Qt6. Qt5 build support will be dropped with Mixxx 2.6 + #11934
  • - Waveforms: don't elide hotcue labels - #13219 - #10722 + Drop support for macOS versions earlier than 11
  • - Waveforms: Allshader RGB, Filtered and Stacked Waveforms using textures for waveform data - #13151 - #12641 + Drop support for Windows versions earlier than Windows 10 build 1809 +
  • +
  • + Drop support for Ubuntu versions earlier than 22.04 +
  • +
  • + Require a C++20 compiler +
  • +
  • + Support GCC 14 + #13504 + #13467 +
  • +
  • + DlgAbout: Add Qt version to the dialog + #11862 +
  • +
  • + WWidget: Disable touch events on macOS (fixing trackpad issues on Qt 6) + #11870 +
  • +
  • + Various Skin adjustments + #11970 + #11957 + #12050 + #12939 + #13242 + #14014 + #13535 + #14013 + #13959 + #14034 + #12972 + #14035 +
  • +
  • + Various Library adjustments + #12380 + #12478 + #13035 + #13033 + #12488 + #12216 + #13448 +
  • +
+

+ Engine +

+
    +
  • + Beats: allow undoing the last BPM/beats change + #12954 + #12774 + #10138 + #13339 +
  • +
  • + Add beatloop anchor to set and adjust loop from either start or end + #12745 + #13241 +
  • +
  • + Add Rate Tap button + #12104 +
  • +
  • + Store/restore regular loop when toggling rolling loops + #12475 + #8947 +
  • +
  • + Add + beats_translate_move + ControlEncoder + #12376 +
  • +
  • + Looping/Beatjump: use seconds if track has no beats + #12961 + #11124 +
  • +
  • + Add Track colour palette cycling controls + track_color_next + and + track_color_prev + to library, decks and samplers + #13066 + #12905 +
  • +
  • + Add Tempo locking controls + #13041 + #13041 + #13038 + #13199 +
  • +
  • + Recording: Fix bogus timestamp in CUE sheet after restarting a recording + #13966 + #13964 +
  • +
  • + Improve Taglib/SoundSource logging + #13541

@@ -192,10 +1138,13 @@

  • Fullscreen toggle rework #11566 + #13189 + #13030
  • Allow to edit track title and artist directly within the decks via a delayed double-click #11755 + #13930
  • Require a minimum movement before initiating the drag&drop of tracks @@ -205,33 +1154,23 @@ Add type toggle to cue popup #13215
  • -
  • - add WEffectMetaKnob, draws arc from default meta position - #12638 - #12634 -
  • Handle not supported files when dragging to waveforms and spinnies #13206
  • - Improve + Tooltips: Improve rate_up/down - tooltips, pitch vs. speed + description regarding pitch vs. speed #12590
  • - Add tooltip for expand/collapse samplers button + Tooltips: Add description for expand/collapse samplers buttons #13005 - #12998 -
  • -
  • - LateNight: Merge vinyl control toggle and status light - #12947 - #10192 + #12998
  • - Track label widgets: set + Track label widgets: Set show_track_menu only for main decks #12978 @@ -241,20 +1180,25 @@ #12116
  • - PreviewDeckN,LoadSelectedTrackAndPlay toggles play/pause if the track is already loaded - #12920 - #9819 + Auto DJ: Force-show decks 3/4 if we are going to use them + #13455
  • - Add command line option - --start-autodj - to start Auto DJ immediately after Mixxx start. - #13017 - #10189 + Auto DJ: Add new random tracks if one track does not exists + #13551
  • - Logging: Include timestamps in messages by default - #11861 + Allow to set LaunchImage style per color scheme + #13731 +
  • +
  • + Show wait cursor when re/loading a skin (not during startup) + #13747 +
  • +
  • + LateNight: Merge vinyl control toggle and status light + #12947 + #10192
  • LateNight, Deere, Tango: Deactivate beatgrid edit controls if BPM is locked @@ -262,57 +1206,49 @@ #13323 #13325
  • - -

    - Engine -

    -
    • - Beats: allow undoing the last BPM/beats change - #12954 - #12774 - #10138 + LateNight: Add/tweak CueDelete icons + #13495 + #13492
    • - Add beatloop anchor to set and adjust loop from either start or end - #12745 - #13241 + LateNight: Use Classic launch image style also for 64 samplers version + #13796
    • - Add Rate Tap button - #12104 + Adjust some skin controls, to allow point-and-click mapping + #13906
    • - Store/restore regular loop when toggling rolling loops - #12475 - #8947 + PreviewDeckN,LoadSelectedTrackAndPlay toggles play/pause if the track is already loaded + #12920 + #9819
    • - Add - beats_translate_move - ControlEncoder - #12376 + Command line interface: Determine whether to color output based on + TERM + variable + #13486
    • - Looping/Beatjump: use seconds if track has no beats - #12961 - #11124 + Command line interface: Add option + --start-autodj + to start Auto DJ immediately after Mixxx start. + #13017 + #10189
    • - Add Track colour palette cycling controls - track_color_next - and - track_color_prev - to library, decks and samplers - #13066 - #12905 + Logging: Include timestamps in messages by default + #11861
    • - Add Tempo locking controls - #13041 - #13041 - #13038 - #13199 + Logging: Limit mixxx.log size to 100 MB or via --log-max-file-size + #13684 + #13660 +
    • +
    • + Fix skin reload after changing color scheme + #13847

    @@ -330,12 +1266,19 @@

  • 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 +
  • Library @@ -344,15 +1287,16 @@

  • Shortkeys Cut, Copy, Paste for track list management #12020 -
  • -
  • - Track menu: Rephrase "Reset" to "Clear" - #12955 + #13361 + #13364 + #13958 + #13100
  • Playlists: move tracks with Alt + Up/Down/PageUp/PageDown/Home/End #13092 #10826 + #13098
  • Search: Add special BPM filters @@ -365,44 +1309,69 @@ #8881
  • - Search: add 'type' filter + Search: Add 'type' filter #13338
  • +
  • + Search: Add 'id' filter + #13694 +
  • Search related Tracks menu: Allow to use multiple filters at once #12213 #12211
  • +
  • + Track menu: Rephrase "Reset" to "Clear" + #12955 +
  • +
  • + Track menu: Add support for scaling BPM by different ratios + #12934 + #9133 +
  • +
  • + Track menu: Remove from disk: stop and eject all affected decks + #13214 +
  • +
  • + Track menu: add star rating + #12700 + #10652 +
  • +
  • + Track menu: Show Properties in Missing and Hidden view + #13426 +
  • Add multi-track property editor / batch tag editor #12548 #9023 #13299 + #13609 + #13597 + #13631
  • - Computer feature: add sidebar action "Refresh directory tree" - #12908 + Track property editor: focus the editing field in the track properties that corresponds to the focused column + #13841 + #14036
  • - Library: Custom color for missing tracks - #12895 + Computer feature: add sidebar action "Refresh directory tree" + #12908
  • - Library: Add feedback to directory operations (add, remove, relink) + Add feedback to directory operations (add, remove, relink) #12436 #10481
  • - Library: Add support for scaling BPM by different ratios - #12934 - #9133 -
  • -
  • - Library: Add ability to import external playlists as crates + Add ability to import external playlists as crates #11852
  • - Library: Add 'Shuffle playlist' sidebar action + Add 'Shuffle playlist' sidebar action #12498 #6988
  • @@ -411,11 +1380,16 @@ #12866 #12761 +
  • + Tracks: Custom color for missing tracks + #12895 +
  • Tracks: Custom text color for played tracks (qss) #12744 #5911 #12912 + #13538
  • History: Show track count and duration in sidebar @@ -423,41 +1397,43 @@ #12788
  • - Fixes around cratetablemodel, remove tracks + don't allow pasting tracks into locked playlists/crates or History + Don't allow pasting tracks into locked playlists/crates or History #12926
  • - Track menu, Remove from disk: stop and eject all affected decks - #13214 + Optimize Library scrolling + #13358
  • - Track menu: add star rating - #12700 - #10652 + Keep the metadata key text unchanged, use it as the origin of information + #11096 + #11095 + #13650 + #14011 + #14008 + #14020
  • - Playlists: move tracks with Alt + Up/Down/PageUp/PageDown/Home/End - #13092 - #10826 + Center date values, right-align Track # + #13674 +
  • +
  • + Analysis: Fix stop button when analyzing crate/playlist + #13902
  • - Library control: make use of WLibrary::getCurrentTrackTableView() - #13335 + Add a debug message, which appears when event loop processing in Mixxx application takes very long + #12094 + #13900 + #13889 + #13903 + #14012
  • Preferences

      -
    • - Add missing spacer in Interface preferences - #13094 -
    • -
    • - Fix fetching of soundcard sample rate - #11951 - 11949 -
    • Add load point option 'First hotcue' #12869 @@ -467,371 +1443,245 @@ MIDI Input editor: allow selecting multiple Options #12348
    • -
    • - Fix incorrect reboot required notification on preference updates - #13058 -
    • Apply changes only after pressing Apply in color preferences #13302
    • -
    -

    - Controller Mappings -

    -
      -
    • - Pioneer DDJ-FLX4: Mapping improvements - #12842 -
    • -
    • - Traktor S4 MK3: Add setting definition for - #12995 -
    • -
    • - Traktor S4 MK3: Software mixer support and default pad layout customisation - #13059 -
    • -
    -

    - Controller Backend -

    -
      -
    • - Send sysex to all handlers - #12827 -
    • -
    • - Add control for showing a deck's track menu - #10825 -
    • -
    • - Removed old examples HID keyboard and HID trackpad - #12977 -
    • -
    • - Reduce log noise with HID device - #13010 - #13125 -
    • -
    • - Allow controller mapping to discard polling - #12558 -
    • -
    • - Add support for mapping user settings - #11300 - #13046 - #13057 - #13045 -
    • -
    • - Registering MIDI Input Handlers From Javascript - #12781 - #13089 -
    • -
    • - Controller IO table: Fix display text for Action/control delegate - #13188 -
    • -
    • - Drop lodash dependency in ComponentJS - #12779 -
    • - Support for bulk devices on Windows and Mac - #13008 + Add/reorder tabstops in Library and Waveform preferences + #13846
    • - Registering MIDI Input Handlers From Javascript - #12781 - #13089 + Add missing spacer in interface preferences + #13094
    • - Drop lodash dependency in ComponentJS - #12779 + Fix fetching of soundcard sample rate + #11951 + 11949

    - Experimental QML Skin + Controller Mappings

    • - Add Experimental QML Skin that can be tested via the --qml command line option - #13152 -
    • -
    • - Fix type error in - Slider.qml - #11423 + Denon MC7000: Add optional jog wheel acceleration to the controller mapping + #4684
    • - Allow switching between legacy and new QML UI with command arg - #12139 + Denon MC7000: Unify parameter button logic and add customizable modes + #13589
    • - Add PlayerProxy missing current track when created after loading - #12559 + Denon MC7000: Add sampler options to mapping settings + #13950
    • - Fix: Add - qt6-qpa-plugins - to dependencies - #12549 + MIDI for light: Implement new Active deck heuristic + #13513
    • - Fix: Improve knobs by applying selective 4xMSAA on the Arc shape - #12541 + MIDI for light: Add settings GUI + #13721
    • - Add QML interceptor to auto reload on file change - #12795 - #12844 + Numark Scratch: Add controller settings + #13404
    • - Add multi-sampling settings for QML - #12546 - #12794 - #12536 - #13058 + Pioneer DDJ-FLX4: Mapping improvements + #12842
    • - Install qml module on Windows - #12604 + Traktor Kontrol S4 MK3: Add setting definition for + #12995
    • - Add scrolling waveforms - #3967 - #13009 + Traktor Kontrol S4 MK3: Software mixer support and default pad layout customisation + #13059
    • - Fix: handle case where Waveform data is missing - #13009 + Traktor Kontrol S4 Mk3: Rework jogwheel speed compute and motorized platter + #13393
    • - Fix: allow missing COs on QML component - #13011 + Traktor Kontrol S4 Mk3: Revert QuickEffect preset offset + #13997
    • - Initialize CmdlineArgs::m_qml - #13152 + Traktor Kontrol S4 Mk3: Correct wheel timestamp wrap-around + #14016

    - Update to Qt6 + Controller Backend

    • - Qt6 prepare - #11863 -
    • -
    • - Qt6 switch - #11892 -
    • -
    • - CMakeLists: Default - QT6 - to - ON - #11934 -
    • -
    • - Build with Qt6 and optionally with QML - #11608 -
    • -
    • - Use constInsert() template - #11847 -
    • -
    • - DlgAbout: Add Qt version to the dialog - #11862 -
    • -
    • - CMakeLists: Fix - QT_TRANSLATION_FILE - path for Qt6 - #11880 -
    • -
    • - LibraryControl: Enable control inputs for Qt6 - #11877 -
    • -
    • - Fix wrong Windows buildenv name and missing Qt6 switch for non CI builds - #11895 -
    • -
    • - WWidget: Disable touch events on macOS (fixing trackpad issues on Qt 6) - #11870 -
    • -
    • - Install libjpeg-turbo::jpeg to fix cover display with Qt6 - #11922 -
    • -
    • - Skins: Remove - border: 0px - from sidebar item styling - #11970 - #11957 -
    • -
    • - Skins: Fix checkbox styling on Qt 6 - #12050 + Send sysex to all handlers + #12827
    • - Skins: Fix Tango waveform splitter - #12939 + Speed up midi sysex receive + #12843
    • - Skin: Fix Tango rate range label position - #13242 + Add control for showing a deck's track menu + #10825
    • - Introduce wrapper for non const iterators for erase and insert - #12201 + Removed old examples HID keyboard and HID trackpad + #12977
    • - Fix Qt6/QML build - #12255 + Reduce log noise with HID device + #13010 + #13125
    • - Fix track color background with Qt6 - #12380 + Allow controller mapping to discard polling + #12558
    • - multi-line delegate: fix bg color, Qt6 on Linux - #12478 + Add support for mapping user settings + #11300 + #13046 + #13057 + #13045 + #13656 + #13738 + #13979 + #13990
    • - Revert "BaseTrackPlayer: Remove references to WaveformWidgetRenderer when using Qt6" - #12342 + Registering MIDI Input Handlers From Javascript + #12781 + #13089
    • - Fix: Replace deprecated - qAsConst - with - std::as_const - #13028 + Controller IO table: Fix display text for Action/control delegate + #13188
    • - Fix Drag'n'drop: avoid unintended drag on hover (WTrackProperty, WCoverArt etc.) - #13035 - #13033 + Drop lodash dependency in ComponentJS + #12779
    • - Fix ambiguous overload error due to native qDebug impl for std::optional - #12981 + Support for bulk devices on Windows and Mac + #13008
    • - Workaround for Qt6 'selected click' bug - #12488 + Fix pending reference to the old mapping after selecting 'No mapping' + #13907
    • - Fix menu icon position - #12216 + Fix crash with GoToItem when no app windows has the focus + #13657

    - Experimental iOs support + Waveforms

    • - CMakeLists: Support building for iOS - #12672 + Visualize slip mode position by splitting waveform (RGB GLSL only) + #13002 + #13256 + #10063
    • - DlgPrefInterface: Disable tooltips on iOS by default - #12689 + Show beats and time until next marker in the waveform + #12994 + #13311 + #13953 + #13314
    • - SoundManager: Set up - AVAudioSession - on iOS - #12714 + Don't elide hotcue labels + #13219 + #10722
    • - SoundManager: Use correct PortAudio backend on iOS - #12716 + Allshader RGB, Filtered and Stacked Waveforms using textures for waveform data + #13151 + #12641
    • - DesktopHelper: Add openUrl abstraction to support iOS - #12698 + Allow changing the waveform overview type without reloading the skin + #13273
    • - iOS packaging: Add Info.plist, launch screen and app icon - #12676 + Overview: Update immediately, when the normalize option or global gain changed + #13634
    • - CmdlineArgs: Move config directory to a user-accessible location on iOS - #12688 + Overview: Clear pickup position display when opening cue menu + #13693

    - Experimental WebAssembly support + Experimental Features

    • - CMakeLists: Add support for targeting Emscripten/WebAssembly - #12918 + QML Skin: Can be tested via the --qml command line option + #13152 + #12139 + #13152
    • - CMakeLists: Emit better errors for exotic target platforms - #12910 + QML Skin related changes + #11423 + #12559 + #12549 + #12541 + #12795 + #12844 + #12546 + #12794 + #12536 + #13058 + #12604 + #3967 + #13009 + #13009 + #13011 + #13506
    • - Build: Add - PORTMIDI - flag for compiling with(out) PortMidi - #12913 + iOS support: Mixxx can be built for iOS + #12672
    • - DesktopHelper: Compile out process-spawning on WASM too - #12916 + iOS support related changes + #12689 + #12714 + #12716 + #12698 + #12676 + #12688 + #13379 + #13378 + #13383
    • - MixxxApplication: Use - QWasmIntegrationPlugin - when targeting WebAssembly - #12915 + Emscripten/WebAssembly support, to run Mixxx hardware independent in a browser + #12918
    • - CMakeLists: Enable asyncify when targeting WASM + Emscripten/WebAssembly related changes + #12910 + #12913 + #12916 + #12915 #12921 -
    • -
    • - Resources: Bundle resources for preloading when targeting Emscripten/WASM #12922 -
    • -
    • - CMakeLists: Add - WASM_ASSERTIONS - option #12931 -
    • -
    • - VersionStore: Recognize Emscripten/WebAssembly #12940 -
    • -
    • - OpenGLWindow: Fix sizing on Wasm by setting - Qt::FramelessWindowHint #12945 -
    • -
    • - CMakeLists: Require WebGL 2.0 when building for Wasm #12952 -
    • -
    • - ScreenSaverHelper: Add no-op implementation for WASM #12930 -
    • -
    • - SSE: Check - !defined(__EMSCRIPTEN__) - where intrinsics are unavailable on WASM #12917
    @@ -840,8 +1690,8 @@

    • - Lenient taglib 2.0 guard - #12793 + Maintain GL ES support + #13485
    • Tools: Add @@ -850,53 +1700,51 @@ #13069
    • - README: Recommend running buildenvs over sourcing them on Linux - #13071 + Lenient taglib 2.0 guard + #12793 +
    • +
    • + MixxxApplication: Support linking Qt statically on Linux + #12284
    • FindSndFile: Link mpg123 in static builds #13087
    • - macOS packaging: Enable app sandbox in ad-hoc-packaged (i.e. non-notarized) bundles too - #12101 + FindPortMidi: Link ALSA in static builds on Linux + #12292 + #12291
    • -
    -

    - Misc Refactorings -

    -
    • - Add missing - <Qt> - include in - defs.h - #11348 + FindLibudev: Link hidapi and libusb with libudev in static builds on Linux + #12294
    • - Engine: Minor refactor to prefer simplified ranged-for-loop - #11234 + FindVorbis: Link ogg in static builds + #12297
    • - Delete unused EngineFilter - #11559 + FindSleef: Use OpenMP in static builds + #12295
    • - AnalyzerWaveform: Fix commented out code - #11561 + macOS packaging: Enable app sandbox in ad-hoc-packaged (i.e. non-notarized) bundles too + #12101
    • - Remove usage of ControlObject::getControl - #11643 + CMakeLists: Match arbitrary + arm64-osx + triplets + #11933
    • - Fix unnecessary transfer of the ownership before release which returns the pointer itself - #11726 + Disable warning in lib/apple code + #13522
    • - Add - ConfigObject::get-/setValue<EnumType> - #11883 + GitHub CI: Use retry loop for CPack to work around macOS issue + #13991
    • Github CI: Enable @@ -904,296 +1752,269 @@ on macOS, too #11905
    • +
    +
    +
    + + +

    + Controller Mappings +

    +
    • - Refactor timers - #11807 - #11850 + Denon MC7000: Fix star up/down logic by only handling button down events + #13588
    • - Use - mixxx::audio::ChannelCount - type instead of - int - / - unsigned char - /etc. - #11941 + Intech TEK2: Add initial mapping + #13521
    • - Refactor util/timer: cleanup includes - #11937 + Korg Kaoss DJ: Update script + #12683
    • - Use - SampleRate - type consistently - #11904 + MIDI for light: Fix unsound timer handling + #13117
    • - CMakeLists: Match arbitrary - arm64-osx - triplets - #11933 + Novation Dicer: Remove Flanger mapping with quickeffect toggle + #13196 + #13134
    • - Reduce sample buffer memory usage - #11988 + Novation Launchpad X: Fix detection on macOS + #13691 + #13633
    • - Fix clazy issues on - main - #12028 + Numark PartyMix: Fix EQ (script binding) display name + #13255
    • - Tidy and modernize SampleBuffer - #11987 + Numark Scratch: Add initial mapping + #4834 + #13375
    • - Refactor parented_ptr: make trivially destructible in release mode, delete move operations - #11981 + Pioneer DDJ-400 and DDJ-FLX4: Remove tap beat mapping to resolve conflict with toggle quantize and fix shift + play + #13815 + #13813 + #13857
    • - Labeler: Add more labels to the auto-labeler - #12106 + Reloop Beatmix 2/4: Fix eject button and jog LED being lit on track unload + #13601 + #13605
    • - FindPortMidi: Link ALSA in static builds on Linux - #12292 - #12291 + Reloop Mixage MK1, MK2, Controller Edition: Add initial mapping + #12296
    • - privat generated ui headers - #12060 - #11407 + Sony SIXAXIS: Fix mapping + #13319
    • +
    +

    + Fixes +

    +
    • - Github CI: workaround runner-image issue - #12233 + Handle not supported files when dragging to waveforms and spinnies + #13208 + #13271 + #13275
    • - FindLibudev: Link hidapi and libusb with libudev in static builds on Linux - #12294 + Fix Sqlite 3.45 builds by using only single quotes for SQL strings + #13247 + #13257
    • - FindVorbis: Link ogg in static builds - #12297 + LateNight: Use default colors for sampler overviews (like main decks) + #13274
    • - MixxxApplication: Support linking Qt statically on Linux - #12284 + Library: Allow to drop files to decks with unsupported or no file extensions + #13209 + #13204
    • - FindSleef: Use OpenMP in static builds - #12295 + Update build environment with libdjinterop 0.21.0 + #13288
    • - Happy New Year 2024! - #12486 + Move to GitHub workflow runner macos-12 + #13296 + #13248
    • - fix/History: remove obsolete placeholder playlists - #12494 + Recording: with empty config, save default split size immediately + #13304
    • - Add missing Taglib dependency - #12830 + Add support for Ubuntu Oracular Oriole and remove Lunar Lobster + #13348
    • - fix: typo ;) - #12726 + Recordbox: Fix string decoding issues + #13293 + #13291
    • - refactor: Avoid temporary qlist allocation on midi sysex receive - #12843 + Mixer preferences: Don't update EQs/QuickEffects while applying + #13333
    • - Labeler: Add - qml - to labeler config - #12911 + Hardware preferences: Fix UX when applying config with missing/busy devices + #13312
    • - WTrackMenu: Add missing wcoverartlabel.h include - #12924 + Fix minor 64 bit CPU performance issue + #13355
    • - Fix clazy complaints and naming - #12935 + Fix clicks at loop-out when looping into lead-in + #13294
    • - src/library: Sort files into sub-directories - #12956 + Fix wrong pitch value on startup, caused by + components.Pot + #11814 + #13463
    • - CMakeLists: Fix deduplication trap with - --preload-file - #12944 + Engine Prime: Fix build-failure + #13397
    • - GitHub CI: Add runner that allows cleaning up the download server - #12957 + Engine Prime: Friendlier error message if export fails + #13524
    • - GitHub CI: Skip the manifest update job on forks - #13278 + macOs: Fix Keyboard shortcuts by not catching num key modifier + #13481 + #13305
    • - Refactor FFmpeg soundsource to allow other soundsource to inherit it - #13042 + Skins: fix time display to allow AM/PM + #13430 + #13421
    • - Code Style: Add branches around single line blocks. - #13097 + Fix detection last sound if track does not end with silence. + #13545 + #13449
    • - Add missing member in copy ctor - #13229 + Remove false positive critical warning related to library columns + #13165 + #13164
    • - Refactor/preferences enums - #12798 + Fix reading metadata for files with wrong extensions + #13218 + #13205
    • - Update to latest vcpkg dependencies - #11649 - #12512 - #12067 - #12898 - #13155 + History: remove purged tracks, auto-remove empty playlists + #13579 + #13578
    • - 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 + Synchronize AutoDJ next deck with top track in queue + #12909 + #8956
    • -
    -
    -
    - - -

    - Controller Mappings -

    -
    • - Korg Kaoss DJ: Update script - #12683 + Playlists: Update play duration and bold state in sidebar when dragging tracks into the playlist table + #13591 + #13590 + #13575
    • - Novation Dicer: Remove flanger mapping with quickeffect toggle - #13196 - #13134 + Playlists: Keep correct track selection (# position) when sorting + #13103
    • - Numark PartyMix: Fix EQ (script binding) display name - #13255 + Track file export: Various fixes + #13610
    • - Numark Scratch: Add initial mapping - #4834 + Controller engine: Unify/improve logging, expand error dialog's Details box + #13626
    • - Sony SIXAXIS: Fix mapping - #13319 + Fix quantization in the effect engine (metronome effect) + #13636 + #13733
    • -
    -

    - Fixes -

    -
    • - Handle not supported files when dragging to waveforms and spinnies - #13208 - #13271 - #13275 + Musicbrainz: Improved messages + #13672 + #13673
    • - Fix Sqlite 3.45 builds by using only single quotes for SQL strings - #13247 - #13257 + Fix ReplayGain detection in case of short tracks + #13680 + #13676 + #13702 + #13703
    • - LateNight: Use default colors for sampler overviews (like main decks) - #13274 + Track menu: Avoid crash and UX issues with track nullptr + #13685
    • - Library: Allow to drop files to decks with unsupported or no file extensions - #13209 - #13204 + Disable Properties shortcut in Computer feature views + #13698
    • - Update build environment with libdjinterop 0.21.0 - #13288 + Overview waveform: Add tooltip info about left-click dragging + #13739
    • - Move to GitHub workflow runner macos-12 - #13296 - #13248 + Make + hotcue_focus_color_next + / + _prev + COs + ControlPushButton + s to allow direct mappings + #13764
    • - Recording: with empty config, save default split size immediately - #13304 + Scaled svg cache to speed up drawing in hidpi mode + #13679
    • - Allow to drop files with supported MIME type regardless off the file extensions - #13209 - #13204 + Update to libdjinterop 0.22.1 for Enigine Prime 4.0.1 support + #13790
    • - Add support for Ubuntu Oracular Oriole and remove Lunar Lobster - #13348 + HID: Avoid repeated error messages from hid_write()/hid_read() in case of errors + #13692 + #13660
    • - Recordbox: Fix string decoding issues - #13293 - #13291 + Fix unnecessary painting with covers in library + #13715
    • - Mixer preferences: Don't update EQs/QuickEffects while applying - #13333 + Fix check for unrelated decks playing when starting Auto DJ + #13762 + #13734
    • - Hardware preferences: Fix UX when applying config with missing/busy devices - #13312 + Fix read before m_bufferInt during scratching + #13917 + #13916
    • - Fix minor 64 bit CPU performance issue - #13355 + Fix waveform EQ High&Mid visualization + #13923 + #13922
    @@ -1566,7 +2387,7 @@ #11872
  • - Add support for overriding analyzis settings about variable/constant BPM on a per-track basis + Add support for overriding analysis settings about variable/constant BPM on a per-track basis #10931
  • @@ -3550,7 +4371,7 @@ #11397
  • - Fix drift in analyzis data after exporting metadata to MP3 files with ID3v1.1 tags + Fix drift in analysis data after exporting metadata to MP3 files with ID3v1.1 tags #11168 #11159
  • @@ -4957,7 +5778,7 @@ #2743
  • - Only apply ducking gain in manual ducking mode when talkover is enabed + Only apply ducking gain in manual ducking mode when talkover is enabled #7668 #8995 #8795 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/ComboBox.qml b/res/qml/ComboBox.qml index adf44947213..b1f36f5be5e 100644 --- a/res/qml/ComboBox.qml +++ b/res/qml/ComboBox.qml @@ -6,6 +6,9 @@ import "Theme" ComboBox { id: root + property alias popupWidth: popup.width + property bool clip: false + background: Skin.EmbeddedBackground { } @@ -40,10 +43,12 @@ ComboBox { font: root.font color: Theme.deckTextColor verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + elide: root.clip ? Text.ElideNone : Text.ElideRight + clip: root.clip } popup: Popup { + id: popup y: root.height width: root.width implicitHeight: contentItem.implicitHeight diff --git a/res/qml/EqColumn.qml b/res/qml/EqColumn.qml index acf1f3ab1c2..0404379cbe8 100644 --- a/res/qml/EqColumn.qml +++ b/res/qml/EqColumn.qml @@ -1,44 +1,107 @@ import "." as Skin import QtQuick 2.12 import QtQuick.Shapes 1.12 +import QtQuick.Layouts +import Mixxx 1.0 as Mixxx import "Theme" Column { id: root required property string group + property var player: Mixxx.PlayerManager.getPlayer(root.group) - spacing: 4 + Mixxx.ControlProxy { + id: stemCountControl - Skin.EqKnob { - statusKey: "button_parameter3" - knob.group: "[EqualizerRack1_" + root.group + "_Effect1]" - knob.key: "parameter3" - knob.color: Theme.eqHighColor + group: root.group + key: "stem_count" } - Skin.EqKnob { - statusKey: "button_parameter2" - knob.group: "[EqualizerRack1_" + root.group + "_Effect1]" - knob.key: "parameter2" - knob.color: Theme.eqMidColor + function stemGroup(group, index) { + return `${group.substr(0, group.length-1)}_Stem${index + 1}]` } - Skin.EqKnob { - knob.group: "[EqualizerRack1_" + root.group + "_Effect1]" - knob.key: "parameter1" - statusKey: "button_parameter1" - knob.color: Theme.eqLowColor - } + Row { + Column { + id: stem + spacing: 4 + width: 10 + visible: opacity != 0 + Repeater { + model: root.player.stemsModel + + Skin.StemKnob { + required property int index + + id: stem + stemGroup: root.stemGroup(root.group, index) + property alias color: stem.stemColor + } + } + } + Column { + id: eq + spacing: 4 + width: 10 + visible: opacity != 0 + Skin.EqKnob { + statusKey: "button_parameter3" + knob.group: "[EqualizerRack1_" + root.group + "_Effect1]" + knob.key: "parameter3" + knob.color: Theme.eqHighColor + } + + Skin.EqKnob { + statusKey: "button_parameter2" + knob.group: "[EqualizerRack1_" + root.group + "_Effect1]" + knob.key: "parameter2" + knob.color: Theme.eqMidColor + } + + Skin.EqKnob { + knob.group: "[EqualizerRack1_" + root.group + "_Effect1]" + knob.key: "parameter1" + statusKey: "button_parameter1" + knob.color: Theme.eqLowColor + } + + Skin.QuickFxKnob { + group: "[QuickEffectRack1_" + root.group + "]" + knob.arcStyle: ShapePath.DashLine + knob.arcStylePattern: [2, 2] + knob.color: Theme.eqFxColor + } + } + states: [ + State { + name: "eq" + when: stemCountControl.value == 0 + PropertyChanges { target: stem; opacity: 0; width: 0} + }, + State { + name: "stem" + when: stemCountControl.value != 0 + PropertyChanges { target: eq; opacity: 0; width: 0 } + } + ] - Skin.EqKnob { - knob.group: "[QuickEffectRack1_" + root.group + "]" - knob.key: "super1" - statusGroup: "[QuickEffectRack1_" + root.group + "_Effect1]" - statusKey: "enabled" - knob.arcStyle: ShapePath.DashLine - knob.arcStylePattern: [2, 2] - knob.color: Theme.eqFxColor + transitions: [ + Transition { + from: "eq" + to: "stem" + ParallelAnimation { + PropertyAnimation { targets: [eq, stem]; properties: "opacity,width"; duration: 1000} + } + }, + Transition { + from: "stem" + to: "eq" + ParallelAnimation { + PropertyAnimation { targets: [eq, stem]; properties: "opacity,width"; duration: 1000} + } + } + ] } Skin.OrientationToggleButton { diff --git a/res/qml/Mixer.qml b/res/qml/Mixer.qml index 05e362920a8..9a6b5bd165c 100644 --- a/res/qml/Mixer.qml +++ b/res/qml/Mixer.qml @@ -1,4 +1,5 @@ import "." as Skin +import Mixxx 1.0 as Mixxx import QtQuick 2.12 Item { diff --git a/res/qml/Mixxx/Controls/Knob.qml b/res/qml/Mixxx/Controls/Knob.qml index 5f269af8c33..7ac3828329d 100644 --- a/res/qml/Mixxx/Controls/Knob.qml +++ b/res/qml/Mixxx/Controls/Knob.qml @@ -17,7 +17,7 @@ Item { property alias foreground: foreground.data property real min: 0 property real max: 1 - property real wheelStepSize: (root.max - root.min) / 10 + property real wheelStepSize: (root.max - root.min) / 100 property real angle: 130 property bool arc: false property int arcStart: Knob.Center diff --git a/res/qml/QuickFxKnob.qml b/res/qml/QuickFxKnob.qml new file mode 100644 index 00000000000..6efa1a0bdc4 --- /dev/null +++ b/res/qml/QuickFxKnob.qml @@ -0,0 +1,83 @@ +import "." as Skin +import Mixxx 1.0 as Mixxx +import QtQuick 2.12 +import "Theme" + +Rectangle { + id: root + + property alias knob: knob + required property string group + + color: Theme.knobBackgroundColor + width: 56 + height: 56 + radius: 5 + + Skin.ControlKnob { + id: knob + + group: root.group + key: "super1" + + anchors.horizontalCenter: root.horizontalCenter + anchors.top: root.top + width: 40 + height: 40 + } + + Mixxx.ControlProxy { + id: statusControl + + group: root.group + key: "enabled" + } + + Rectangle { + id: statusButton + + anchors.left: root.left + anchors.bottom: root.bottom + anchors.leftMargin: 4 + anchors.bottomMargin: 4 + width: 8 + height: width + radius: width / 2 + border.width: 1 + border.color: Theme.buttonNormalColor + color: statusControl.value ? knob.color : "transparent" + + TapHandler { + onTapped: statusControl.value = !statusControl.value + } + } + + Mixxx.ControlProxy { + id: fxSelect + + group: root.group + key: "loaded_chain_preset" + } + + Skin.ComboBox { + id: effectSelector + anchors.left: statusButton.right + anchors.leftMargin: 2 + anchors.right: root.right + anchors.top: knob.bottom + anchors.margins: 1 + spacing: 2 + indicator.width: 0 + popupWidth: 150 + clip: true + + opacity: statusControl.value ? 1 : 0.5 + textRole: "display" + font.pixelSize: 10 + model: Mixxx.EffectsManager.quickChainPresetModel + currentIndex: fxSelect.value == -1 ? 0 : fxSelect.value + onActivated: (index) => { + fxSelect.value = index + } + } +} diff --git a/res/qml/StemKnob.qml b/res/qml/StemKnob.qml new file mode 100644 index 00000000000..b1525bf5eae --- /dev/null +++ b/res/qml/StemKnob.qml @@ -0,0 +1,143 @@ +import "." as Skin +import Mixxx 1.0 as Mixxx +import QtQuick 2.12 +import "Theme" + +Rectangle { + id: root + + property alias knob: volume + + required property string stemGroup + required property string label + required property color stemColor + + readonly property string fxGroup: `[QuickEffectRack1_${stemGroup}]` + + width: 56 + height: 56 + radius: 5 + color: stemColor + opacity: statusControl.value ? 0.5 : 1 + + Mixxx.ControlProxy { + id: statusControl + + group: root.stemGroup + key: "mute" + } + + Mixxx.ControlProxy { + id: fxControl + + group: root.fxGroup + key: "enabled" + } + + Rectangle { + id: statusButton + + anchors.left: root.left + anchors.top: root.top + anchors.leftMargin: 4 + anchors.topMargin: 4 + width: 8 + height: width + radius: width / 2 + border.width: 1 + border.color: Theme.buttonNormalColor + color: statusControl.value ? volume.color : "transparent" + + TapHandler { + onTapped: statusControl.value = !statusControl.value + } + } + + Text { + id: stemLabel + anchors.top: root.top + anchors.right: root.right + anchors.topMargin: 2 + anchors.rightMargin: 2 + elide: Text.ElideRight + text: label + font.pixelSize: 10 + } + + Skin.ControlKnob { + id: volume + group: root.stemGroup + key: "volume" + color: Theme.gainKnobColor + anchors.leftMargin: 1 + anchors.top: statusButton.bottom + anchors.left: root.left + + arcStart: 0 + + width: 32 + height: 32 + } + + Rectangle { + id: fxButton + + anchors.top: stemLabel.bottom + anchors.right: root.right + anchors.left: volume.right + anchors.leftMargin: 8 + anchors.rightMargin: 8 + width: 8 + height: width + radius: width / 2 + border.width: 1 + border.color: Theme.buttonNormalColor + color: fxControl.value ? effectSuperKnob.color : "transparent" + + TapHandler { + onTapped: fxControl.value = !fxControl.value + } + } + + Skin.ControlMiniKnob { + id: effectSuperKnob + + anchors.right: root.right + anchors.left: volume.right + anchors.bottom: effectSelector.top + anchors.margins: 2 + arcStart: Knob.ArcStart.Minimum + group: root.fxGroup + key: "super1" + color: Theme.effectColor + opacity: fxControl.value ? 1 : 0.5 + } + + Mixxx.ControlProxy { + id: fxSelect + + group: root.fxGroup + key: "loaded_chain_preset" + } + + Skin.ComboBox { + id: effectSelector + anchors.left: root.left + anchors.right: root.right + anchors.bottom: root.bottom + anchors.margins: 1 + spacing: 2 + indicator.width: 0 + popupWidth: 150 + clip: true + + opacity: fxControl.value ? 1 : 0.5 + textRole: "display" + font.pixelSize: 10 + model: Mixxx.EffectsManager.quickChainPresetModel + currentIndex: fxSelect.value == -1 ? 0 : fxSelect.value + onActivated: (index) => { + fxSelect.value = index + } + } +} diff --git a/res/shaders/CMakeLists.txt b/res/shaders/CMakeLists.txt index c54470accf9..a37026c96eb 100644 --- a/res/shaders/CMakeLists.txt +++ b/res/shaders/CMakeLists.txt @@ -3,3 +3,10 @@ qt_add_shaders(mixxx-lib "waveform_shaders" FILES "rgbsignal_qml.frag" ) + +# Workaround for https://bugreports.qt.io/browse/QTBUG-118500 that can be +# removed once Qt fixes the shader target generation for Xcode. +# See also https://github.com/mixxxdj/mixxx/issues/13378 +if(CMAKE_GENERATOR STREQUAL "Xcode") + add_dependencies(mixxx-lib_resources_1 mixxx-lib_waveform_shaders) +endif() 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