diff options
-rw-r--r-- | .github/workflows/build.yml | 74 | ||||
-rw-r--r-- | CMakeLists.txt | 6 | ||||
-rw-r--r-- | res/controllers/Denon-MC7000-scripts.js | 334 | ||||
-rw-r--r-- | res/controllers/Numark-Mixtrack-3-scripts.js | 238 | ||||
-rw-r--r-- | src/waveform/renderers/waveformrendermark.cpp | 4 | ||||
-rw-r--r-- | tools/deploy.py | 360 | ||||
-rwxr-xr-x | tools/deploy.sh | 54 | ||||
-rw-r--r-- | tools/generate_download_metadata.py | 211 |
8 files changed, 758 insertions, 523 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74cf792483..da66c7cf78 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,6 +31,7 @@ jobs: buildenv_script: tools/debian_buildenv.sh artifacts_name: Ubuntu 20.04 DEB artifacts_path: build/*.deb + artifacts_slug: ubuntu-focal qt_qpa_platform: offscreen - name: macOS 10.15 os: macos-10.15 @@ -82,7 +83,7 @@ jobs: buildenv_script: tools/windows_buildenv.bat artifacts_name: Windows Installer artifacts_path: build/*.msi - artifacts_slug: windows-windows64 + artifacts_slug: windows-win64 qt_qpa_platform: windows env: @@ -95,8 +96,8 @@ jobs: runs-on: ${{ matrix.os }} name: ${{ matrix.name }} outputs: - artifact-macos-macosintel: ${{ steps.generate_artifact_metadata.outputs.artifact-macos-macosintel }} - artifact-windows-windows64: ${{ steps.generate_artifact_metadata.outputs.artifact-windows-windows64 }} + artifact-macos-macosintel: ${{ steps.prepare_deploy.outputs.artifact-macos-macosintel }} + artifact-windows-win64: ${{ steps.prepare_deploy.outputs.artifact-windows-win64 }} steps: # sccache's handling of the /fp:fast MSVC compiler option is broken, so use our fork with the fix. @@ -123,6 +124,12 @@ jobs: with: fetch-depth: 0 + - name: "Ensure that all tags are fetched" + # Works around an issue where not the latest tag is not fetched when the + # workflow is triggered by a tag push event. + # Possibly related: actions/checkout#290 + run: git fetch origin --force --tags + - name: "Set up cmake" uses: jwlawson/actions-setup-cmake@v1.4 with: @@ -306,6 +313,27 @@ jobs: run: signtool sign /f $Env:WINDOWS_CODESIGN_CERTIFICATE_PATH /p $Env:WINDOWS_CODESIGN_CERTIFICATE_PASSWORD *.msi working-directory: build + - name: "Prepare for deployment" + # Copy the desired directory structure to the deploy/ directory. This + # also generates metadata for file artifact and write it to the job + # output using the artifacts_slug value. + id: prepare_deploy + if: github.event_name == 'push' + shell: bash + run: > + if [[ "${GITHUB_REF}" =~ ^refs/tags/.* ]]; + then + export DEPLOY_PATH='mixxx-{git_describe}/mixxx-{git_describe}-{package_slug}{ext}'; + else + export DEPLOY_PATH='builds/{git_branch}/mixxx-{git_describe}-{package_slug}{ext}'; + fi; + python3 tools/deploy.py prepare-deployment + --slug '${{ matrix.artifacts_slug }}' + --output-dir 'deploy/' + --dest-path "${DEPLOY_PATH}" + --dest-url 'https://downloads.mixxx.org' + ${{ matrix.artifacts_path }} + - name: "[Windows] Install rsync and openssh" env: SSH_PASSWORD: ${{ secrets.DOWNLOADS_HOSTGATOR_DOT_MIXXX_DOT_ORG_KEY_PASSWORD }} @@ -315,30 +343,16 @@ jobs: pacman -S --noconfirm coreutils bash rsync openssh Add-Content -Path "$Env:GITHUB_ENV" -Value "PATH=$Env:PATH" - - name: "Generate Artifact Metadata" - # Generate metadata for file artifact and write it to the job output - # using the artifacts_slug value. This also sets the DEPLOY_DIR - # environment variable that is used in the deploy.sh script in the next - # step. - id: generate_artifact_metadata - if: github.event_name == 'push' - run: python3 tools/generate_download_metadata.py artifact ${{ matrix.artifacts_path }} "${{ matrix.artifacts_slug }}" - env: - DEPLOY_BASEURL: "https://downloads.mixxx.org" - DESTDIR: builds/{git_branch}/${{ runner.os }} - - name: "[macOS/Windows] Upload build to downloads.mixxx.org" # skip deploying Ubuntu builds to downloads.mixxx.org because these are deployed to the PPA if: runner.os != 'Linux' && github.event_name == 'push' && env.SSH_PASSWORD != null - run: bash tools/deploy.sh ${{ matrix.artifacts_path }} + run: bash tools/deploy.sh deploy/ env: DESTDIR: public_html/downloads/ - DEPLOY_ONLY: 0 SSH_HOST: downloads-hostgator.mixxx.org SSH_KEY: packaging/certificates/downloads-hostgator.mixxx.org.key SSH_PASSWORD: ${{ secrets.DOWNLOADS_HOSTGATOR_DOT_MIXXX_DOT_ORG_KEY_PASSWORD }} SSH_USER: mixxx - UPLOAD_ID: ${{ github.run_id }} # Workaround for https://github.com/actions/cache/issues/531 - name: Use system tar & zstd from Chocolatey for caching @@ -363,28 +377,40 @@ jobs: with: fetch-depth: 0 + - name: "Ensure that all tags are fetched" + # Works around an issue where not the latest tag is not fetched when the + # workflow is triggered by a tag push event. + # Possibly related: actions/checkout#290 + run: git fetch origin --force --tags + - name: "Collect Artifacts Metadata & Write Manifest" # Retrieve the metadata from the matrix job's outputs, merge them into a # single JSON document and then deploy to the server. if: github.event_name == 'push' && env.SSH_PASSWORD != null - run: python3 tools/generate_download_metadata.py manifest + run: > + if [[ "${GITHUB_REF}" =~ ^refs/tags/.* ]]; + then + export DEPLOY_PATH='mixxx-{git_describe}/manifest.json'; + else + export DEPLOY_PATH='builds/{git_branch}/manifest.json'; + fi; + python3 tools/deploy.py generate-manifest + --output-dir 'deploy/' + --dest-path "${DEPLOY_PATH}" + --dest-url 'https://downloads.mixxx.org' env: JOB_DATA: ${{ toJSON(needs.build) }} - MANIFEST_URL: "https://downloads.mixxx.org/builds/{git_branch}/manifest.json" - DESTDIR: "builds/{git_branch}" SSH_PASSWORD: ${{ secrets.DOWNLOADS_HOSTGATOR_DOT_MIXXX_DOT_ORG_KEY_PASSWORD }} - name: "Deploy Manifest" if: github.event_name == 'push' && env.SSH_PASSWORD != null && env.MANIFEST_DIRTY != null && env.MANIFEST_DIRTY != '0' - run: bash tools/deploy.sh manifest.json + run: bash tools/deploy.sh deploy/ env: DESTDIR: public_html/downloads/ - DEPLOY_ONLY: 1 SSH_HOST: downloads-hostgator.mixxx.org SSH_KEY: packaging/certificates/downloads-hostgator.mixxx.org.key SSH_PASSWORD: ${{ secrets.DOWNLOADS_HOSTGATOR_DOT_MIXXX_DOT_ORG_KEY_PASSWORD }} SSH_USER: mixxx - UPLOAD_ID: ${{ github.run_id }} - name: "Trigger Netlify build" if: env.NETLIFY_BUILD_HOOK != null && env.MANIFEST_DIRTY != null && env.MANIFEST_DIRTY != '0' diff --git a/CMakeLists.txt b/CMakeLists.txt index 717a1d83b5..72be447a74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ if(POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() -project(mixxx VERSION 2.3.0) +project(mixxx VERSION 2.4.0) 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.") @@ -1721,6 +1721,8 @@ if(ENGINEPRIME) set(DJINTEROP_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/lib/libdjinterop-install") set(DJINTEROP_LIBRARY "lib/${CMAKE_STATIC_LIBRARY_PREFIX}djinterop${CMAKE_STATIC_LIBRARY_SUFFIX}") file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/download") + # External project sources must be downloaded as an archive into DOWNLOAD_DIR + # from an URL to keep offline builds like for Fedora/RPM Fusion working! ExternalProject_Add(libdjinterop URL "https://github.com/xsco/libdjinterop/archive/0.15.1.tar.gz" URL_HASH SHA256=87b3e6c726c208333d55b7e7e3af0a7230c9ad9edb3ca0ca81feffe17b3fc008 @@ -1828,6 +1830,8 @@ if(KEYFINDER) 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}") file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/download") + # External project sources must be downloaded as an archive into DOWNLOAD_DIR + # from an URL to keep offline builds like for Fedora/RPM Fusion working! ExternalProject_Add(libkeyfinder URL "https://github.com/mixxxdj/libkeyfinder/archive/v2.2.4.zip" URL_HASH SHA256=cb3fea8c7213257281b6c7006b2430809f466ad8a1d485663324378d6fc0782c diff --git a/res/controllers/Denon-MC7000-scripts.js b/res/controllers/Denon-MC7000-scripts.js index fea38f8b2e..9381cb9365 100644 --- a/res/controllers/Denon-MC7000-scripts.js +++ b/res/controllers/Denon-MC7000-scripts.js @@ -32,6 +32,13 @@ var MC7000 = {}; // USER VARIABLES BEGIN // ///////////////////////////////////*/ +// switch on experimental features +// experimental features are: +// 1) Beat LED in Slicer mode (counts every 8 beats AFTER the CUE point) +// only works for CONSTANT TEMPO tracks +// needs beat grid and CUE point set +MC7000.experimental = false; + // Wanna have Needle Search active while playing a track ? // In any case Needle Search is available holding "SHIFT" down. // can be true or false (recommended: false) @@ -61,18 +68,18 @@ MC7000.VinylModeOn = true; // default: true // Scratch algorithm parameters MC7000.scratchParams = { - recordSpeed: 33.3, // default: 33.3 + recordSpeed: 33 + 1/3, // default: 33 + 1/3 alpha: (1.0/10), // default: (1.0/10) beta: (1.0/10)/32 // default: (1.0/10)/32 }; -// Sensitivity of the jog wheel (also depends on audio latency) -MC7000.jogParams = { - // Sensitivity factor (0.5 for half, 2 for double sensitivity) - jogSensitivity: 1, // default: 1 - // this will limit the parameter of "jog" (keep between 0.5 and 3) - maxJogValue: 3 // default: 3 -}; +// 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; // default: 1.0 with audio buffer set to 23ms /*///////////////////////////////// // USER VARIABLES END // @@ -99,7 +106,7 @@ MC7000.currentRateRangeIndex = [0, 0, 0, 0]; // initialize the "factor" function for Spinback MC7000.factor = []; -//Set Shift button state to false for default +//Set Shift button state to false as default MC7000.shift = [false, false, false, false]; // initialize the PAD Mode to Hot Cue and all others off when starting @@ -114,32 +121,46 @@ MC7000.PADModeSampler = [false, false, false, false]; MC7000.PADModeVelSamp = [false, false, false, false]; MC7000.PADModePitch = [false, false, false, false]; -// Beatloop Roll sizes +// PAD Mode 'Beatloop Roll' sizes MC7000.beatLoopRoll = [1 / 16, 1 / 8, 1 / 4, 1 / 2, 1, 2, 4, 8]; +// PAD Mode - 'Fixed Loop' sizes +MC7000.fixedLoop = [1, 2, 4, 8, 16, 32, 64, 128]; + +// PAD Mode - 'Beatjump' sizes +MC7000.beatJump = [1, 2, 4, 8, 16, 32, 64, 128]; + // Define the MIDI signal for red LED at VU Meters MC7000.VuMeterLEDPeakValue = 0x76; +// initialize variables to compare LED status for VU, Jog and PAD LEDs +MC7000.prevVuLevel = [0, 0, 0, 0]; +MC7000.prevJogLED = [0, 0, 0, 0]; +MC7000.prevPadLED = [0, 0, 0, 0]; + // PAD Mode Colors MC7000.padColor = { "alloff": 0x01, // switch off completely // Hot Cue - "hotcueon": 0x04, // darkblue Hot Cue active - "hotcueoff": 0x02, // lightblue Hot Cue inactive + "hotcueon": 0x04, // blue Hot Cue active + "hotcueoff": 0x02, // dark blue Hot Cue inactive // Cue Loop - "cueloopon": 0x0D, // Cueloop colour for activated cue point - "cueloopoff": 0x1A, // Cueloop colour inactive + "cueloopon": 0x0D, // green Cueloop colour for activated cue point + "cueloopoff": 0x1A, // dark green Cueloop colour inactive // Roll - "rollon": 0x20, // BeatloopRoll active colour - "rolloff": 0x06, // BeatloopRoll off colour + "rollon": 0x20, // cyan BeatloopRoll active colour + "rolloff": 0x06, // dark cyan BeatloopRoll off colour + // Saved Loop + "fixedloopon": 0x3D, // yellow Saved Loop active + "fixedloopoff": 0x15, // dark yellow Saved Loop active // Slicer - "sliceron": 0x11, // activated Slicer - "slicerJumpFwd": 0x31, // Sliver forward jump - "slicerJumpBack": 0x31, // Sliver backward jump + "sliceron": 0x11, // dark red activated Slicer + "slicerJumpFwd": 0x31, // red Sliver forward jump + "slicerJumpBack": 0x31, // red Sliver backward jump // Sampler - "samplerloaded": 0x38, // dark pink Sampler loaded colour + "samplerloaded": 0x38, // pink Sampler loaded colour "samplerplay": 0x09, // green Sampler playing - "sampleroff": 0x12 // light pink Sampler standard colour + "sampleroff": 0x12 // dark pink Sampler standard colour }; /* DECK INITIALIZATION */ @@ -158,15 +179,17 @@ MC7000.init = function() { engine.makeConnection("[Channel3]", "VuMeter", MC7000.VuMeter); engine.makeConnection("[Channel4]", "VuMeter", MC7000.VuMeter); - // Platter Ring LED + // Platter Ring LED mode midi.sendShortMsg(0x90, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x91, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x92, 0x64, MC7000.modeSingleLED); midi.sendShortMsg(0x93, 0x64, MC7000.modeSingleLED); - engine.makeConnection("[Channel1]", "playposition", MC7000.JogLed); - engine.makeConnection("[Channel2]", "playposition", MC7000.JogLed); - engine.makeConnection("[Channel3]", "playposition", MC7000.JogLed); - engine.makeConnection("[Channel4]", "playposition", MC7000.JogLed); + + // Track Position LEDs for Jog Wheel and Slicer + engine.makeConnection("[Channel1]", "playposition", MC7000.TrackPositionLEDs); + engine.makeConnection("[Channel2]", "playposition", MC7000.TrackPositionLEDs); + engine.makeConnection("[Channel3]", "playposition", MC7000.TrackPositionLEDs); + engine.makeConnection("[Channel4]", "playposition", MC7000.TrackPositionLEDs); // Vinyl mode LEDs midi.sendShortMsg(0x90, 0x07, MC7000.isVinylMode ? 0x7F: 0x01); @@ -188,8 +211,8 @@ MC7000.init = function() { engine.makeConnection("[Sampler"+i+"]", "play", MC7000.SamplerLED); } - // Activate Timer for Controller Status SysEx to avoid conflicts with Softtakeover - engine.beginTimer(3000, MC7000.delayedSysEx, true); + // send Controller Status SysEx message delayed to avoid conflicts with Softtakeover + engine.beginTimer(2000, MC7000.delayedSysEx, true); }; // SysEx message to receive all knob and fader positions @@ -215,6 +238,7 @@ MC7000.samplerLevel = function(channel, control, value) { // PAD Mode Hot Cue MC7000.padModeCue = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -232,9 +256,9 @@ MC7000.padModeCue = function(channel, control, value, status, group) { // change PAD color when switching to Hot Cue Mode for (var i = 1; i <= 8; i++) { if (engine.getValue(group, "hotcue_" + i + "_enabled", true)) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueon); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.hotcueon); } else { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.hotcueoff); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.hotcueoff); } } }; @@ -242,6 +266,7 @@ MC7000.padModeCue = function(channel, control, value, status, group) { // PAD Mode Cue Loop MC7000.padModeCueLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -257,14 +282,15 @@ MC7000.padModeCueLoop = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); } }; // PAD Mode Flip MC7000.padModeFlip = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -280,14 +306,15 @@ MC7000.padModeFlip = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i, MC7000.padColor.alloff); } }; // PAD Mode Roll MC7000.padModeRoll = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -303,14 +330,15 @@ MC7000.padModeRoll = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // change PAD color when switching to Roll Mode - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.rolloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.rolloff); } }; // PAD Mode Saved Loop MC7000.padModeSavedLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -325,15 +353,17 @@ MC7000.padModeSavedLoop = function(channel, control, value, status, group) { MC7000.PADModeVelSamp[deckNumber] = false; MC7000.PADModePitch[deckNumber] = false; - // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + // change PAD color when switching to Saved Loop Mode + for (var i = 0; i < 8; i++) { + var activeLED = engine.getValue(group, "beatloop_" + MC7000.fixedLoop[i] + "_enabled") ? MC7000.padColor.fixedloopon : MC7000.padColor.fixedloopoff; + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, activeLED); } }; // PAD Mode Slicer MC7000.padModeSlicer = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -349,14 +379,15 @@ MC7000.padModeSlicer = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // change PAD color when switching to Slicer Mode - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.sliceron); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.sliceron); } }; // PAD Mode Slicer Loop MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -372,14 +403,15 @@ MC7000.padModeSlicerLoop = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); } }; // PAD Mode Sampler MC7000.padModeSampler = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -397,11 +429,11 @@ MC7000.padModeSampler = function(channel, control, value, status, group) { // change PAD color when switching to Sampler Mode for (var i = 1; i <= 8; i++) { if (engine.getValue("[Sampler" + i + "]", "play")) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.samplerplay); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.samplerplay); } else if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.sampleroff); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.sampleroff); } else if (engine.getValue("[Sampler" + i + "]", "track_loaded") === 1 && engine.getValue("[Sampler" + i + "]", "play") === 0) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.samplerloaded); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i - 1, MC7000.padColor.samplerloaded); } } }; @@ -409,6 +441,7 @@ MC7000.padModeSampler = function(channel, control, value, status, group) { // PAD Mode Velocity Sampler MC7000.padModeVelSamp = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -424,14 +457,15 @@ MC7000.padModeVelSamp = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = false; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); } }; // PAD Mode Pitch MC7000.padModePitch = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (value === 0x00) { return; // don't respond to note off messages } @@ -447,19 +481,21 @@ MC7000.padModePitch = function(channel, control, value, status, group) { MC7000.PADModePitch[deckNumber] = true; // switch off PAD illumination - for (var i = 1; i <= 8; i++) { - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i - 1, MC7000.padColor.alloff); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.alloff); + for (var i = 0; i < 8; i++) { + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.alloff); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i, MC7000.padColor.alloff); } }; // PAD buttons MC7000.PadButtons = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; + var i, j; // activate and clear Hot Cues if (MC7000.PADModeCue[deckNumber] && engine.getValue(group, "track_loaded") === 1) { - for (var i = 1; i <= 8; i++) { + for (i = 1; i <= 8; i++) { if (control === 0x14 + i - 1 && value >= 0x01) { engine.setValue(group, "hotcue_" + i + "_activate", true); } else { @@ -467,7 +503,7 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } if (control === 0x1C + i - 1 && value >= 0x01) { engine.setValue(group, "hotcue_" + i + "_clear", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.hotcueoff); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i - 1, MC7000.padColor.hotcueoff); } } } else if (MC7000.PADModeCueLoop[deckNumber]) { @@ -475,30 +511,48 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } else if (MC7000.PADModeFlip[deckNumber]) { return; } else if (MC7000.PADModeRoll[deckNumber]) { - // todo: - // check for actual beatloop_size and apply back after a PAD Roll + // TODO(all): check for actual beatloop_size and apply back after a PAD Roll i = control - 0x14; if (control === 0x14 + i && value > 0x00) { engine.setValue(group, "beatlooproll_" + MC7000.beatLoopRoll[i] + "_activate", true); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i, MC7000.padColor.rollon); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.rollon); } else if (control === 0x14 + i && value === 0x00) { engine.setValue(group, "beatlooproll_activate", false); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x14 + i, MC7000.padColor.rolloff); + midi.sendShortMsg(0x94 + deckOffset, 0x14 + i, MC7000.padColor.rolloff); } } else if (MC7000.PADModeSavedLoop[deckNumber]) { - return; + if (value === 0x00) { + return; // don't respond to note off messages + } + i = control - 0x14; + engine.setValue(group, "beatloop_" + MC7000.fixedLoop[i] + "_toggle", true); + for (j =0; j < 8; j++) { + var activeLED = engine.getValue(group, "beatloop_" + MC7000.fixedLoop[j] + "_enabled") ? MC7000.padColor.fixedloopon : MC7000.padColor.fixedloopoff; + midi.sendShortMsg(0x94 + deckOffset, 0x14 + j, activeLED); + } } else if (MC7000.PADModeSlicer[deckNumber]) { if (value > 0) { - var beats = 1 << (control % 4); - if (control > 0x17) { - engine.setValue(group, "beatjump_" + beats + "_backward", value); - midi.sendShortMsg(0x94 + deckNumber - 1, control, MC7000.padColor.slicerJumpBack); - } else { - engine.setValue(group, "beatjump_" + beats + "_forward", value); - midi.sendShortMsg(0x94 + deckNumber - 1, control, MC7000.padColor.slicerJumpFwd); + i = control - 0x14; // unshifted button + j = control - 0x1C; // shifted button + // forward buttons (PAD buttons upper row) + if (control >= 0x14 && control <= 0x17) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[i] + "_forward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpFwd); + // backward buttons (PAD buttons lower row) + } else if (control >= 0x18 && control <= 0x1B) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[i - 4] + "_backward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpBack); + // forward buttons (PAD buttons upper row - shifted controls) + } else if (control >= 0x1C && control <= 0x1F) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[j + 4] + "_forward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpFwd); + // backward buttons (PAD buttons lower row - shifted controls) + } else if (control >= 0x20 && control <= 0x23) { + engine.setValue(group, "beatjump_" + MC7000.beatJump[j] + "_backward", true); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.slicerJumpBack); } } else { - midi.sendShortMsg(0x94 + deckNumber - 1, control, MC7000.padColor.sliceron); + midi.sendShortMsg(0x94 + deckOffset, control, MC7000.padColor.sliceron); } } else if (MC7000.PADModeSlicerLoop[deckNumber]) { return; @@ -513,10 +567,10 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } else if (control === 0x1C + i - 1 && value >= 0x01) { if (engine.getValue("[Sampler" + i + "]", "play") === 1) { engine.setValue("[Sampler" + i + "]", "cue_gotoandstop", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.samplerloaded); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i - 1, MC7000.padColor.samplerloaded); } else { engine.setValue("[Sampler" + i + "]", "eject", 1); - midi.sendShortMsg(0x94 + deckNumber - 1, 0x1C + i - 1, MC7000.padColor.sampleroff); + midi.sendShortMsg(0x94 + deckOffset, 0x1C + i - 1, MC7000.padColor.sampleroff); engine.setValue("[Sampler" + i + "]", "eject", 0); } } @@ -528,12 +582,12 @@ MC7000.PadButtons = function(channel, control, value, status, group) { } }; -// Toggle Shift Button +// Shift Button MC7000.shiftButton = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); - MC7000.shift[deckNumber - 1] = ! MC7000.shift[deckNumber - 1]; - midi.sendShortMsg(0x90 + deckNumber - 1, 0x32, - MC7000.shift[deckNumber - 1] ? 0x7F : 0x01); + var deckOffset = script.deckFromGroup(group) - 1; + MC7000.shift[deckOffset] = ! MC7000.shift[deckOffset]; + midi.sendShortMsg(0x90 + deckOffset, 0x32, + MC7000.shift[deckOffset] ? 0x7F : 0x01); }; // Toggle Vinyl Mode @@ -541,10 +595,10 @@ MC7000.vinylModeToggle = function(channel, control, value, status, group) { if (value === 0x00) { return; // don't respond to note off messages } - var deckNumber = script.deckFromGroup(group); - MC7000.isVinylMode[deckNumber - 1] = !MC7000.isVinylMode[deckNumber - 1]; - midi.sendShortMsg(0x90 + deckNumber - 1, 0x07, - MC7000.isVinylMode[deckNumber - 1] ? 0x7F : 0x01); + var deckOffset = script.deckFromGroup(group) - 1; + MC7000.isVinylMode[deckOffset] = !MC7000.isVinylMode[deckOffset]; + midi.sendShortMsg(0x90 + deckOffset, 0x07, + MC7000.isVinylMode[deckOffset] ? 0x7F : 0x01); }; // Use select button to load and eject track from deck @@ -585,7 +639,8 @@ MC7000.loadButton = function(channel, control, value, status, group) { // The button that enables/disables scratching MC7000.wheelTouch = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); - if (MC7000.isVinylMode[deckNumber - 1]) { + var deckOffset = deckNumber - 1; + if (MC7000.isVinylMode[deckOffset]) { if (value === 0x7F) { engine.scratchEnable(deckNumber, MC7000.jogWheelTicksPerRevolution, MC7000.scratchParams.recordSpeed, @@ -599,22 +654,27 @@ MC7000.wheelTouch = function(channel, control, value, status, group) { // The wheel that actually controls the scratching MC7000.wheelTurn = function(channel, control, value, status, group) { + // TODO(all): check for latency and use it to normalize the jog factor so jog wont be + // depending on audio latency anymore. + // A: For a control that centers on 0: var numTicks = (value < 0x64) ? value : (value - 128); + var adjustedSpeed = numTicks * MC7000.jogSensitivity * 25; var deckNumber = script.deckFromGroup(group); + var deckOffset = deckNumber - 1; if (engine.isScratching(deckNumber)) { // Scratch! - engine.scratchTick(deckNumber, numTicks); + engine.scratchTick(deckNumber, numTicks * MC7000.jogSensitivity); } else { - if (MC7000.shift[deckNumber - 1]) { + if (MC7000.shift[deckOffset]) { // While Shift Button pressed -> Search through track - var jogSearch = 5000 * numTicks / MC7000.jogWheelTicksPerRevolution * MC7000.jogParams.jogSensitivity; + var jogSearch = 300 * adjustedSpeed / MC7000.jogWheelTicksPerRevolution; engine.setValue(group, "jog", jogSearch); } else { // While Shift Button released -> Pitch Bend - var jogDelta = numTicks / MC7000.jogWheelTicksPerRevolution * MC7000.jogParams.jogSensitivity * 30; + var jogDelta = adjustedSpeed / MC7000.jogWheelTicksPerRevolution; var jogAbsolute = jogDelta + engine.getValue(group, "jog"); - engine.setValue(group, "jog", Math.max(-MC7000.jogParams.maxJogValue, Math.min(MC7000.jogParams.maxJogValue, jogAbsolute))); + engine.setValue(group, "jog", jogAbsolute); } } }; @@ -646,7 +706,7 @@ MC7000.needleSearchStripPosition = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (MC7000.needleSearchTouched[deckNumber]) { - var fullValue = (MC7000.needleDropMSB << 7) + value; // move MSB 7 binary gigits to the left and add LSB + var fullValue = (MC7000.needleDropMSB << 7) + value; // move MSB 7 binary digits to the left and add LSB var position = (fullValue / 0x3FFF); // divide by all possible positions to get relative between 0 - 1 engine.setParameter(group, "playposition", position); } @@ -669,13 +729,13 @@ MC7000.nextRateRange = function(midichan, control, value, status, group) { if (value === 0) { return; // don't respond to note off messages } - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; // increment currentRateRangeIndex and check for overflow - if (++MC7000.currentRateRangeIndex[deckNumber - 1] === + if (++MC7000.currentRateRangeIndex[deckOffset] === MC7000.rateRanges.length) { - MC7000.currentRateRangeIndex[deckNumber - 1] = 0; + MC7000.currentRateRangeIndex[deckOffset] = 0; } - engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber - 1]]); + engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckOffset]]); }; // Previous Rate range toggle @@ -683,19 +743,19 @@ MC7000.prevRateRange = function(midichan, control, value, status, group) { if (value === 0) { return; // don't respond to note off messages } - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; // decrement currentRateRangeIndex and check for underflow - if (--MC7000.currentRateRangeIndex[deckNumber - 1] < 0) { - MC7000.currentRateRangeIndex[deckNumber - 1] = MC7000.rateRanges.length - 1; + if (--MC7000.currentRateRangeIndex[deckOffset] < 0) { + MC7000.currentRateRangeIndex[deckOffset] = MC7000.rateRanges.length - 1; } - engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckNumber - 1]]); + engine.setValue(group, "rateRange", MC7000.rateRanges[MC7000.currentRateRangeIndex[deckOffset]]); }; // Key & Waveform zoom Select MC7000.keySelect = function(midichan, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; // While Shift Button is pressed: Waveform Zoom - if (MC7000.shift[deckNumber - 1]) { + if (MC7000.shift[deckOffset]) { if (value === 0x7F) { script.triggerControl(group, "waveform_zoom_up", 100); } else { @@ -713,12 +773,12 @@ MC7000.keySelect = function(midichan, control, value, status, group) { // Key & Waveform zoom Reset MC7000.keyReset = function(channel, control, value, status, group) { - var deckNumber = script.deckFromGroup(group); + var deckOffset = script.deckFromGroup(group) - 1; if (value === 0x00) { return; } // While Shift Button is pressed: Waveform Zoom Reset - if (MC7000.shift[deckNumber - 1]) { + if (MC7000.shift[deckOffset]) { script.triggerControl(group, "waveform_zoom_set_default", 100); // While Shift Button is released: Key Reset } else { @@ -746,12 +806,12 @@ MC7000.stopTime = function(channel, control, value, status, group) { MC7000.factor[deckNumber] = (1.1 - (value / 127)) * 30 - 2; }; -// Use the CENSOR button as Spinback with STOP TIME adjusted length +// Use SHIFT + CENSOR button as Spinback with STOP TIME adjusted length MC7000.reverse = function(channel, control, value, status, group) { var deckNumber = script.deckFromGroup(group); if (value > 0) { // while the button is pressed spin back - engine.brake(deckNumber, true, MC7000.factor[deckNumber], -15); // start at a rate of -15 and decrease by "factor" + engine.brake(deckNumber, true, MC7000.factor[deckNumber], - 15); // start at a rate of -15 and decrease by "factor" } else { // when releasing the button the track starts softly again engine.softStart(deckNumber, true, MC7000.factor[deckNumber]); @@ -784,7 +844,7 @@ MC7000.crossFaderCurve = function(control, value) { // Set FX wet/dry value MC7000.fxWetDry = function(channel, control, value, status, group) { - var numTicks = (value < 0x64) ? value: (value - 128); + var numTicks = (value < 0x64) ? value : (value - 128); var newVal = engine.getValue(group, "mix") + numTicks/64*2; engine.setValue(group, "mix", Math.max(0, Math.min(1, newVal))); }; @@ -815,40 +875,86 @@ MC7000.sortLibrary = function(channel, control, value) { /* LEDs for VuMeter */ // VuMeters only for Channel 1-4 / Master is on Hardware MC7000.VuMeter = function(value, group) { - var deckNumber = script.deckFromGroup(group), - vuLevelOutValue = engine.getValue(group, "PeakIndicator" |