From 3732aef000e39ca1262682c73e8639dbd284405d Mon Sep 17 00:00:00 2001 From: Pierre Le Gall Date: Wed, 6 Jan 2021 18:13:07 +0100 Subject: [PATCH 001/267] Hercules P32 mapping: pregain instead of dry/wet knob (optional) --- res/controllers/Hercules P32 DJ.midi.xml | 8 ++++---- res/controllers/Hercules-P32-scripts.js | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/res/controllers/Hercules P32 DJ.midi.xml b/res/controllers/Hercules P32 DJ.midi.xml index 0f528f3fe57..81d4debd550 100644 --- a/res/controllers/Hercules P32 DJ.midi.xml +++ b/res/controllers/Hercules P32 DJ.midi.xml @@ -512,7 +512,7 @@ [Channel1] - P32.leftDeck.effectUnit.dryWetKnob.input + P32.leftDeck.dryWetKnobOrPregain.input 0xB1 0x09 @@ -521,7 +521,7 @@ [Channel1] - P32.leftDeck.effectUnit.dryWetKnob.input + P32.leftDeck.dryWetKnobOrPregain.input 0xB4 0x09 @@ -1548,7 +1548,7 @@ [Channel1] - P32.rightDeck.effectUnit.dryWetKnob.input + P32.rightDeck.dryWetKnobOrPregain.input 0xB2 0x09 @@ -1557,7 +1557,7 @@ [Channel1] - P32.rightDeck.effectUnit.dryWetKnob.input + P32.rightDeck.dryWetKnobOrPregain.input 0xB5 0x09 diff --git a/res/controllers/Hercules-P32-scripts.js b/res/controllers/Hercules-P32-scripts.js index 01165c03ee3..6c94ec06432 100644 --- a/res/controllers/Hercules-P32-scripts.js +++ b/res/controllers/Hercules-P32-scripts.js @@ -16,6 +16,8 @@ var loopEnabledDot = false; var samplerCrossfaderAssign = true; // Toggle effect units between 1 & 3 on left and 2 & 4 on right when toggling decks var toggleEffectUnitsWithDecks = false; +// Set the dry/wet knob as a pregain +var dryWetKnobAsPregain = false; /** * Hercules P32 DJ controller script for Mixxx 2.1 @@ -378,7 +380,16 @@ P32.Deck = function(deckNumbers, channel) { this.effectUnit.knobs[1].midi = [0xB0 + channel, 0x06]; this.effectUnit.knobs[2].midi = [0xB0 + channel, 0x07]; this.effectUnit.knobs[3].midi = [0xB0 + channel, 0x08]; - this.effectUnit.dryWetKnob.midi = [0xB0 + channel, 0x09]; + if (dryWetKnobAsPregain) { + this.dryWetKnobOrPregain = new components.Pot({ + midi: [0xB0 + channel, 0x09], + group: "[Channel" + channel + "]", + inKey: "pregain", + }); + } else { + this.effectUnit.dryWetKnob.midi = [0xB0 + channel, 0x09]; + this.dryWetKnobOrPregain = this.effectUnit.dryWetKnob; + } this.effectUnit.enableButtons[1].midi = [0x90 + channel, 0x03]; this.effectUnit.enableButtons[2].midi = [0x90 + channel, 0x04]; this.effectUnit.enableButtons[3].midi = [0x90 + channel, 0x05]; From c94f55e03025bc39982de2a6e60dc188bb28277f Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Fri, 22 Jul 2022 19:14:35 +0200 Subject: [PATCH 002/267] Added support for Windows builds with clang-cl --- CMakeLists.txt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cff78026fd6..6116627a9ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,8 +141,13 @@ else() endif() if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # using regular Clang or AppleClang - set(LLVM_CLANG true) + if (CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") + set(LLVM_CLANG false) + set(MSVC true) + else() + # using regular Clang or AppleClang + set(LLVM_CLANG true) + endif() else() set(LLVM_CLANG false) endif() @@ -2056,7 +2061,10 @@ target_link_libraries(mixxx-lib PRIVATE FLAC::FLAC) # inlining It is compiled without optimization and allows to use these function # 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(GNU_GCC OR LLVM_CLANG) + +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` # to silence a warning on Clang 14 target_compile_options(FpClassify PRIVATE -ffp-contract=on -fno-fast-math) From 7a9a84441521edf1f3ab319ad60a513c369e3c93 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sun, 14 Aug 2022 17:17:06 +0200 Subject: [PATCH 003/267] Implemented fast lookup map for output fields --- res/controllers/common-hid-packet-parser.js | 50 ++++++++++----------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index dee4d174881..2852cb6a98a 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1115,6 +1115,12 @@ class HIDController { */ this.OutputPackets = {}; + /** A map to determine the ouptut Bit or bytewise field by group and name, + * accross all OutputPackets + * @type {Map} + */ + this.OutputFieldLookup = new Map(); + /** * Default input packet name: can be modified for controllers * which can swap modes (wiimote for example) @@ -1415,33 +1421,11 @@ class HIDController { * can't be found. */ getOutputField(m_group, m_name) { - for (const packet_name in this.OutputPackets) { - const packet = this.OutputPackets[packet_name]; - for (const group_name in packet.groups) { - const group = packet.groups[group_name]; - for (const field_name in group) { - const field = group[field_name]; - if (field.type === "bitvector") { - for (const bit_id in field.value.bits) { - const bit = field.value.bits[bit_id]; - if (bit.mapped_group === m_group && bit.mapped_name === m_name) { - return bit; - } - if (bit.group === m_group && bit.name === m_name) { - return bit; - } - } - continue; - } - if (field.mapped_group === m_group && field.mapped_name === m_name) { - return field; - } - if (field.group === m_group && field.name === m_name) { - return field; - } - } - } + const field = this.OutputFieldLookup.get([m_group, m_name].toString()); + if (field) { + return field; } + return undefined; } /** @@ -1660,6 +1644,20 @@ class HIDController { for (const bit_id in field.value.bits) { const bit = field.value.bits[bit_id]; bit.packet = packet; + // Fill lookup map + if (bit.mapped_group && bit.mapped_name) { + this.OutputFieldLookup.set([bit.mapped_group, bit.mapped_name].toString(), bit); + } + if (bit.group && bit.name) { + this.OutputFieldLookup.set([bit.group, bit.name].toString(), bit); + } + } + } else { + if (field.mapped_group && field.mapped_name) { + this.OutputFieldLookup.set([field.mapped_group, field.mapped_name].toString(), field); + } + if (field.group && field.name) { + this.OutputFieldLookup.set([field.group, field.name].toString(), field); } } } From 119d65a42329727f38ffdeef3c7ac4dc738fe3e6 Mon Sep 17 00:00:00 2001 From: JoergAtGithub Date: Sun, 14 Aug 2022 18:52:45 +0200 Subject: [PATCH 004/267] Pre-commit fixes --- res/controllers/common-hid-packet-parser.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 2852cb6a98a..db3cb3dd227 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1115,11 +1115,13 @@ class HIDController { */ this.OutputPackets = {}; - /** A map to determine the ouptut Bit or bytewise field by group and name, - * accross all OutputPackets - * @type {Map} - */ - this.OutputFieldLookup = new Map(); + /** + * A map to determine the output Bit or bytewise field by group and name, + * across all OutputPackets + * + * @type {Map} + */ + this.OutputFieldLookup = new Map(); /** * Default input packet name: can be modified for controllers From ec2bd25b01c1213933fa6ff498b2a5177f840762 Mon Sep 17 00:00:00 2001 From: Ties Jan Hefting Date: Sat, 24 Sep 2022 17:35:16 +0200 Subject: [PATCH 005/267] Pioneer DDJ-400: make Beat FX section more intuitive This patch makes the mapping of the Beat FX section slightly more intuitive: - Beat < (LEFT) and Beat > (RIGHT) buttons now cycle through the focus of the three Effects in Effect Unit 1 leftward and rightward, respectively. - FX SELECT v (DOWN) and FX SELECT v +SHIFT (UP) buttons now select the next and previous effect entry for the focused Effect, respectively. This is more in line with the button names and the Mixxx interface. Based on the work proposed in #4662. --- res/controllers/Pioneer-DDJ-400-script.js | 53 ++++++++++++++--------- res/controllers/Pioneer-DDJ-400.midi.xml | 30 +++++-------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/res/controllers/Pioneer-DDJ-400-script.js b/res/controllers/Pioneer-DDJ-400-script.js index 0399d1d6a51..5848b0cec90 100644 --- a/res/controllers/Pioneer-DDJ-400-script.js +++ b/res/controllers/Pioneer-DDJ-400-script.js @@ -1,7 +1,7 @@ // Pioneer-DDJ-400-script.js // **************************************************************************** // * Mixxx mapping script file for the Pioneer DDJ-400. -// * Authors: Warker, nschloe, dj3730, jusko +// * Authors: Warker, nschloe, dj3730, jusko, tiesjan // * Reviewers: Be-ing, Holzhaus // * Manual: https://manual.mixxx.org/2.3/en/hardware/controllers/pioneer_ddj_400.html // **************************************************************************** @@ -19,14 +19,14 @@ // // Custom (Mixxx specific mappings): // * BeatFX: Assigned Effect Unit 1 -// < LEFT focus EFFECT1 -// > RIGHT focus EFFECT2 -// v FX_SELECT focus EFFECT3. +// < LEFT toggles focus between Effects 1, 2 and 3 leftward +// > RIGHT toggles focus between Effects 1, 2 and 3 rightward +// v DOWN loads next effect entry for focused Effect +// SHIFT + v UP loads previous effect entry for focused Effect +// LEVEL/DEPTH controls the Mix knob of the Effect Unit +// SHIFT + LEVEL/DEPTH controls the Meta knob of the focused Effect // ON/OFF toggles focused effect slot // SHIFT + ON/OFF disables all three effect slots. -// SHIFT + < loads previous effect -// SHIFT + > loads next effect -// // * 32 beat jump forward & back (Shift + CUE/LOOP CALL arrows) // * Toggle quantize (Shift + channel cue) // @@ -34,7 +34,6 @@ // * Loop Section: // * -4BEAT auto loop (hacky---prefer a clean way to set a 4 beat loop // from a previous position on long press) -// // * CUE/LOOP CALL - memory & delete (complex and not useful. Hot cues are sufficient) // // * Secondary pad modes (trial attempts complex and too experimental) @@ -243,30 +242,44 @@ PioneerDDJ400.beatFxLevelDepthRotate = function(_channel, _control, value) { } }; -PioneerDDJ400.beatFxSelectPreviousEffect = function(_channel, _control, value) { - engine.setValue(PioneerDDJ400.focusedFxGroup(), "prev_effect", value); -}; - -PioneerDDJ400.beatFxSelectNextEffect = function(_channel, _control, value) { - engine.setValue(PioneerDDJ400.focusedFxGroup(), "next_effect", value); -}; - PioneerDDJ400.beatFxLeftPressed = function(_channel, _control, value) { if (value === 0) { return; } - engine.setValue("[EffectRack1_EffectUnit1]", "focused_effect", 1); + var focusedEffect = engine.getValue("[EffectRack1_EffectUnit1]", "focused_effect"); + + if (focusedEffect <= 1) { + focusedEffect = 3; + } else { + focusedEffect -= 1; + } + + engine.setValue("[EffectRack1_EffectUnit1]", "focused_effect", focusedEffect); }; PioneerDDJ400.beatFxRightPressed = function(_channel, _control, value) { if (value === 0) { return; } - engine.setValue("[EffectRack1_EffectUnit1]", "focused_effect", 2); + var focusedEffect = engine.getValue("[EffectRack1_EffectUnit1]", "focused_effect"); + + if (focusedEffect >= 3) { + focusedEffect = 1; + } else { + focusedEffect += 1; + } + + engine.setValue("[EffectRack1_EffectUnit1]", "focused_effect", focusedEffect); }; PioneerDDJ400.beatFxSelectPressed = function(_channel, _control, value) { if (value === 0) { return; } - engine.setValue("[EffectRack1_EffectUnit1]", "focused_effect", 3); + engine.setValue(PioneerDDJ400.focusedFxGroup(), "next_effect", value); +}; + +PioneerDDJ400.beatFxSelectShiftPressed = function(_channel, _control, value) { + if (value === 0) { return; } + + engine.setValue(PioneerDDJ400.focusedFxGroup(), "prev_effect", value); }; PioneerDDJ400.beatFxOnOffPressed = function(_channel, _control, value) { @@ -426,7 +439,7 @@ PioneerDDJ400.syncLongPressed = function(channel, control, value, status, group) }; PioneerDDJ400.cycleTempoRange = function(_channel, _control, value, _status, group) { - if (value === 0) return; // ignore release + if (value === 0) { return; } // ignore release var currRange = engine.getValue(group, "rateRange"); var idx = 0; diff --git a/res/controllers/Pioneer-DDJ-400.midi.xml b/res/controllers/Pioneer-DDJ-400.midi.xml index a2cc1d28eba..2e320ebc168 100644 --- a/res/controllers/Pioneer-DDJ-400.midi.xml +++ b/res/controllers/Pioneer-DDJ-400.midi.xml @@ -2,7 +2,7 @@ Pioneer DDJ-400 - Warker/nschloe/dj3730/jusko + Warker, nschloe, dj3730, jusko, tiesjan Midi Mapping for the Pioneer DDJ-400 pioneer_ddj_400 https://mixxx.discourse.group/t/pioneer-ddj-400/17476 @@ -998,7 +998,7 @@ - BEAT LEFT - press - select effect unit 1 + BEAT LEFT - press - toggle focused Effect leftward [EffectRack1_EffectUnit1] PioneerDDJ400.beatFxLeftPressed 0x94 @@ -1007,19 +1007,9 @@ - - BEAT LEFT + shift - select previous effect for the current unit - [EffectRack1_EffectUnit1] - PioneerDDJ400.beatFxSelectPreviousEffect - 0x94 - 0x66 - - - - - BEAT RIGHT - press - select effect unit 2 + BEAT RIGHT - press - toggle focused Effect rightward [EffectRack1_EffectUnit1] PioneerDDJ400.beatFxRightPressed 0x94 @@ -1028,23 +1018,23 @@ + - BEAT RIGHT + shift - select next effect for the current unit + BEAT FX SELECT - press - load next effect entry for focused Effect [EffectRack1_EffectUnit1] - PioneerDDJ400.beatFxSelectNextEffect + PioneerDDJ400.beatFxSelectPressed 0x94 - 0x6B + 0x63 - - BEAT FX SELECT - press once - select effect unit 3 - press again - clear focus back to wet/dry mix + BEAT FX SELECT +SHIFT - press - load previous effect entry for focused Effect [EffectRack1_EffectUnit1] - PioneerDDJ400.beatFxSelectPressed + PioneerDDJ400.beatFxSelectShiftPressed 0x94 - 0x63 + 0x64 From 22cf8924afd23f67f299c18ec64ed547b7ad62d1 Mon Sep 17 00:00:00 2001 From: Joerg Date: Tue, 11 Oct 2022 01:21:48 +0200 Subject: [PATCH 006/267] Optimized Dataview based implementation of HIDPacket.pack --- res/controllers/common-hid-packet-parser.js | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 5305e24c617..2987d0468bb 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -384,11 +384,9 @@ class HIDPacket { console.error(`HIDPacket.pack - Parsing packed value: invalid pack format ${field.pack}`); return; } - const bytes = this.packSizes[field.pack]; - const signed = this.signedPackFormats.includes(field.pack); if (field.type === "bitvector") { const bitVector = /** @type {HIDBitVector} */ (field.value); - if (bytes > 1) { + if (this.packSizes[field.pack] > 1) { console.error("HIDPacket.pack - Packing multibyte bit vectors not yet supported"); return; } @@ -406,17 +404,19 @@ class HIDPacket { return; } - for (let byte_index = 0; byte_index < bytes; byte_index++) { - const index = field.offset + byte_index; - if (signed) { - if (value >= 0) { - data[index] = (value >> (byte_index * 8)) & 255; - } else { - data[index] = 255 - ((-(value + 1) >> (byte_index * 8)) & 255); - } - } else { - data[index] = (value >> (byte_index * 8)) & 255; - } + const dataView = new DataView(data.buffer); + if (field.pack === "b") { + dataView.setInt8(field.offset, value); + } else if (field.pack === "B") { + dataView.setUint8(field.offset, value); + } else if (field.pack === "h") { + dataView.setInt16(field.offset, value, true); + } else if (field.pack === "H") { + dataView.setUint16(field.offset, value, true); + } else if (field.pack === "i") { + dataView.setInt32(field.offset, value, true); + } else if (field.pack === "I") { + dataView.setUint32(field.offset, value, true); } } /** From 5a59555ac9f765acdbb2c4b6f8ce057cf234657b Mon Sep 17 00:00:00 2001 From: Joerg Date: Tue, 11 Oct 2022 20:31:05 +0200 Subject: [PATCH 007/267] Optimized Dataview based implementation of HIDPacket.unpack --- res/controllers/common-hid-packet-parser.js | 35 +++++++++------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 2987d0468bb..659f9593460 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -435,30 +435,23 @@ class HIDPacket { * @returns {number} Value for the field in data, represented according the fields packing type */ unpack(data, field) { - - if (!(field.pack in this.packSizes)) { + const dataView = new DataView(data.buffer); + if (field.pack === "b") { + return dataView.getInt8(field.offset); + } else if (field.pack === "B") { + return dataView.getUint8(field.offset); + } else if (field.pack === "h") { + return dataView.getInt16(field.offset, true); + } else if (field.pack === "H") { + return dataView.getUint16(field.offset, true); + } else if (field.pack === "i") { + return dataView.getInt32(field.offset, true); + } else if (field.pack === "I") { + return dataView.getUint32(field.offset, true); + } else { console.error(`HIDPacket.unpack - Parsing packed value: invalid pack format ${field.pack}`); return undefined; } - const bytes = this.packSizes[field.pack]; - const signed = this.signedPackFormats.includes(field.pack); - - let value = 0; - for (let field_byte = 0; field_byte < bytes; field_byte++) { - if (data[field.offset + field_byte] === 255 && field_byte === 4) { - value += 0; - } else { - value += data[field.offset + field_byte] * Math.pow(2, (field_byte * 8)); - } - } - if (signed) { - const max_value = Math.pow(2, bytes * 8); - const split = max_value / 2 - 1; - if (value > split) { - value = value - max_value; - } - } - return value; } /** * Find HID packet group matching name. From 9f6bc1b42277f5d8632ad4dddf472cb66b4f592c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 15 Oct 2022 00:07:35 +0200 Subject: [PATCH 008/267] Retire deprecated Ubuntu Bionic workflow runner. --- .github/workflows/build.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34fa530f46a..47d5b4aaa08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,28 +12,6 @@ jobs: fail-fast: false matrix: include: - - name: Ubuntu 18.04 (gcc) - os: ubuntu-18.04 - cmake_args: >- - -DWARNINGS_FATAL=ON - -DBULK=ON - -DFAAD=ON - -DFFMPEG=OFF - -DLOCALECOMPARE=ON - -DMAD=ON - -DMODPLUG=ON - -DWAVPACK=ON - -DINSTALL_USER_UDEV_RULES=OFF - ctest_args: [] - compiler_cache: ccache - compiler_cache_path: ~/.ccache - cpack_generator: DEB - buildenv_basepath: /home/runner/buildenv - buildenv_script: tools/debian_buildenv.sh - artifacts_name: Ubuntu 18.04 DEB - artifacts_path: build/*.deb - artifacts_slug: ubuntu-bionic - qt_qpa_platform: offscreen - name: Ubuntu 20.04 (gcc) os: ubuntu-20.04 cmake_args: >- From 5edf90c331d481a34a158ec623dc87c3ce5e6f71 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 20 Nov 2022 19:33:39 +0100 Subject: [PATCH 009/267] Removed outdated ToDo --- res/controllers/common-hid-packet-parser.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 6178c019a55..da0a737e4b7 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -1406,8 +1406,6 @@ class HIDController { /** * Find Output control matching give group and name * - * @todo The current implementation of this often called function is very slow and does not - * scale, due to several nested loops. * @param {string} m_group Mapped group, must be a valid Mixxx control group name e.g. "[Channel1]" * @param {string} m_name Name of mapped control, must be a valid Mixxx control name "VuMeter" * @returns {bitObject|packetField} Bit or bytewise field - Returns undefined if output field From 36814e8cb73b0a9fcf62eb10341352567f315ab8 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sat, 26 Nov 2022 16:00:03 +0100 Subject: [PATCH 010/267] Used more efficient loop over objects Fixed some typos in documentation --- res/controllers/common-hid-packet-parser.js | 83 ++++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index da0a737e4b7..3ae60003e0f 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -384,7 +384,7 @@ class HIDPacket { * Can only pack bits and byte values, patches welcome. * * @todo Implement multi byte bit vector outputs - * @param {Uint8Array} data Data received as InputReport from the device + * @param {Uint8Array} data Data to be send as OutputReport to the device * @param {packetField} field Object that describes a field inside of a packet, which can often * mapped to a Mixxx control. */ @@ -399,9 +399,10 @@ class HIDPacket { console.error("HIDPacket.pack - Packing multibyte bit vectors not yet supported"); return; } - for (const bit_id in bitVector.bits) { - const bit = bitVector.bits[bit_id]; - data[field.offset] = data[field.offset] | bit.value; + const bitVectorKeyArr = Object.keys(bitVector.bits); + let bitVectorIdx = bitVectorKeyArr.length; + while (bitVectorIdx--) { + data[field.offset] |= bitVector.bits[bitVectorKeyArr[bitVectorIdx]].value; } return; } @@ -592,8 +593,11 @@ class HIDPacket { } const bitVector = /** @type {HIDBitVector} */ (field.value); const bit_id = `${group}.${name}`; - for (const bit_name in bitVector.bits) { - const bit = bitVector.bits[bit_name]; + + const bitVectorKeyArr = Object.keys(bitVector.bits); + let bitVectorKeyIdx = bitVectorKeyArr.length; + while (bitVectorKeyIdx--) { + const bit = bitVector.bits[bitVectorKeyArr[bitVectorKeyIdx]]; if (bit.id === bit_id) { return bit; } @@ -952,11 +956,15 @@ class HIDPacket { return undefined; } const bitVector = /** @type {HIDBitVector} */ (field.value); - for (const bit_id in bitVector.bits) { - const bit = bitVector.bits[bit_id]; + + + const bitVectorKeyArr = Object.keys(bitVector.bits); + let bitVectorKeyIdx = bitVectorKeyArr.length; + while (bitVectorKeyIdx--) { + const bit = bitVector.bits[bitVectorKeyArr[bitVectorKeyIdx]]; const new_value = (bit.bitmask & value) >> bit.bit_offset; if (bit.value !== undefined && bit.value !== new_value) { - bits[bit_id] = bit; + bits[bitVectorKeyArr[bitVectorKeyIdx]] = bit; } bit.value = new_value; } @@ -978,25 +986,34 @@ class HIDPacket { */ const field_changes = {}; - for (const group_name in this.groups) { - const group = this.groups[group_name]; - for (const field_id in group) { - const field = group[field_id]; + const groupKeyArr = Object.keys(this.groups); + let groupKeyIdx = groupKeyArr.length; + while (groupKeyIdx--) { + const group = this.groups[groupKeyArr[groupKeyIdx]]; + + const fieldKeyArr = Object.keys(group); + let fieldKeyIdx = fieldKeyArr.length; + while (fieldKeyIdx--) { + const field = group[fieldKeyArr[fieldKeyIdx]]; + if (field === undefined) { continue; } const value = this.unpack(data, field); if (value === undefined) { - console.error(`HIDPacket.parse - Parsing packet field value for ${field_id}`); + console.error(`HIDPacket.parse - Parsing packet field value for ${group}.${field}`); return; } if (field.type === "bitvector") { // Bitvector deltas are checked in parseBitVector - const changed_bits = this.parseBitVector(field, value); - for (const bit_name in changed_bits) { - field_changes[bit_name] = changed_bits[bit_name]; + const changedBits = this.parseBitVector(field, value); + + const changedBitsKeyArr = Object.keys(changedBits); + let changedBitsKeyIdx = changedBitsKeyArr.length; + while (changedBitsKeyIdx--) { + field_changes[changedBitsKeyArr[changedBitsKeyIdx]] = changedBits[changedBitsKeyArr[changedBitsKeyIdx]]; } } else if (field.type === "control") { @@ -1049,11 +1066,17 @@ class HIDPacket { data[header_byte] = this.header[header_byte]; } } + const groupKeyArr = Object.keys(this.groups); + let groupKeyIdx = groupKeyArr.length; + while (groupKeyIdx--) { + const group = this.groups[groupKeyArr[groupKeyIdx]]; - for (const group_name in this.groups) { - const group = this.groups[group_name]; - for (const field_name in group) { - this.pack(data, group[field_name]); + const fieldKeyArr = Object.keys(group); + let fieldKeyIdx = fieldKeyArr.length; + while (fieldKeyIdx--) { + const field = group[fieldKeyArr[fieldKeyIdx]]; + + this.pack(data, field); } } @@ -1669,9 +1692,11 @@ class HIDController { if (this.InputPackets === undefined) { return; } - for (const name in this.InputPackets) { + const InputPacketsKeyArr = Object.keys(this.InputPackets); + let InputPacketsIdx = InputPacketsKeyArr.length; + while (InputPacketsIdx--) { /** @type {HIDPacket} */ - let packet = this.InputPackets[name]; + let packet = this.InputPackets[InputPacketsKeyArr[InputPacketsIdx]]; // When the device uses ReportIDs to enumerate the reports, hidapi // prepends the report ID to the data sent to Mixxx. If the device @@ -1739,16 +1764,18 @@ class HIDController { * @param {Object.} delta */ processIncomingPacket(packet, delta) { - /** @type {packetField} */ - for (const name in delta) { - // @ts-ignore ignoredControlChanges should be defined in the users mapping + + const deltaKeyArr = Object.keys(delta); + let deltaIdx = deltaKeyArr.length; + while (deltaIdx--) { + // @ts-ignore ignoredControlChanges should be defined in the users mapping // see EKS-Otus.js for an example if (this.ignoredControlChanges !== undefined && // @ts-ignore - this.ignoredControlChanges.indexOf(name) !== -1) { + this.ignoredControlChanges.indexOf(deltaKeyArr[deltaIdx]) !== -1) { continue; } - const field = delta[name]; + const field = delta[deltaKeyArr[deltaIdx]]; if (field.type === "button") { // Button/Boolean field this.processButton(field); From 2920aa7d8f5b866f85f6522460d9918a4bf8e92e Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Mon, 29 Aug 2022 19:48:32 +0200 Subject: [PATCH 011/267] feature(effects): EffectPreset::updateParametersFrom(EffectPreset&) --- src/effects/presets/effectpreset.cpp | 19 ++++++++++++++++++- src/effects/presets/effectpreset.h | 10 +++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/effects/presets/effectpreset.cpp b/src/effects/presets/effectpreset.cpp index 7e77f2d19ff..225fadbf98c 100644 --- a/src/effects/presets/effectpreset.cpp +++ b/src/effects/presets/effectpreset.cpp @@ -99,5 +99,22 @@ const QDomElement EffectPreset::toXml(QDomDocument* doc) const { return effectElement; } -EffectPreset::~EffectPreset() { +void EffectPreset::updateParametersFrom(const EffectPreset& other) { + DEBUG_ASSERT(backendType() == other.backendType()); + + // technically algorithmically inefficient solution O(n²). May be + // optimizable by sorting first, gains depend on parameter count + for (const auto& parameterToCopy : other.m_effectParameterPresets) { + auto currentParameterIt = + std::find_if(m_effectParameterPresets.begin(), + m_effectParameterPresets.end(), + [&](const auto& ourParameter) { + return ourParameter.id() == parameterToCopy.id(); + }); + if (currentParameterIt == m_effectParameterPresets.end()) { + continue; + } + // overwrite our parameter by taking a copy of the same parameter from `other` + *currentParameterIt = parameterToCopy; + } } diff --git a/src/effects/presets/effectpreset.h b/src/effects/presets/effectpreset.h index 2f98420ee94..4df5b7b5575 100644 --- a/src/effects/presets/effectpreset.h +++ b/src/effects/presets/effectpreset.h @@ -15,7 +15,6 @@ class EffectPreset { EffectPreset(const QDomElement& element); EffectPreset(const EffectSlotPointer pEffectSlot); EffectPreset(const EffectManifestPointer pManifest); - ~EffectPreset(); const QDomElement toXml(QDomDocument* doc) const; @@ -39,6 +38,15 @@ class EffectPreset { return m_effectParameterPresets; } + /// updates all of the parameters of `this` with the parameters + /// of `preset`. + /// The operation is not symmetric: + /// Parameters which are present on `preset` but not on `this` will + /// not be added to `this` + /// Parameters present on `this` but not `preset` will keep their previous + /// settings + void updateParametersFrom(const EffectPreset& preset); + private: QString m_id; EffectBackendType m_backendType; From 1f46f972739bd9cbb2b8bdb344430af5c0dcb2c0 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Mon, 29 Aug 2022 20:37:55 +0200 Subject: [PATCH 012/267] fix(effects): Fix parameters of effect not showing up when not in default settings file fixes mixxxdj/mixxx/issues/10834 --- src/effects/presets/effectpresetmanager.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/effects/presets/effectpresetmanager.cpp b/src/effects/presets/effectpresetmanager.cpp index d266608e3f3..06101ca4b4a 100644 --- a/src/effects/presets/effectpresetmanager.cpp +++ b/src/effects/presets/effectpresetmanager.cpp @@ -39,11 +39,13 @@ void EffectPresetManager::loadDefaultEffectPresets() { file.close(); continue; } - EffectPresetPointer pEffectPreset(new EffectPreset(doc.documentElement())); - if (!pEffectPreset->isEmpty()) { + auto presetFromFile = EffectPreset(doc.documentElement()); + if (!presetFromFile.isEmpty()) { EffectManifestPointer pManifest = m_pBackendManager->getManifest( - pEffectPreset->id(), pEffectPreset->backendType()); + presetFromFile.id(), presetFromFile.backendType()); if (pManifest) { + auto pEffectPreset = EffectPresetPointer(new EffectPreset(pManifest)); + pEffectPreset->updateParametersFrom(presetFromFile); m_defaultPresets.insert(pManifest, pEffectPreset); } } From 88d6d931773c4a38ec9b748c13e9638b7c6cca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 21 Dec 2022 21:39:42 +0100 Subject: [PATCH 013/267] CMake: Read version from DjInteropt --- cmake/modules/FindDjInterop.cmake | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cmake/modules/FindDjInterop.cmake b/cmake/modules/FindDjInterop.cmake index c007a138e86..bc5e384046a 100644 --- a/cmake/modules/FindDjInterop.cmake +++ b/cmake/modules/FindDjInterop.cmake @@ -61,12 +61,15 @@ find_library(DjInterop_LIBRARY ) mark_as_advanced(DjInterop_LIBRARY) +if(DEFINED PC_DjInterop_VERSION AND NOT PC_DjInterop_VERSION STREQUAL "") + set(DjInterop_VERSION "${PC_DjInterop_VERSION}") +endif() + include(FindPackageHandleStandardArgs) find_package_handle_standard_args( DjInterop - DEFAULT_MSG - DjInterop_LIBRARY - DjInterop_INCLUDE_DIR + REQUIRED_VARS DjInterop_LIBRARY DjInterop_INCLUDE_DIR + VERSION_VAR DjInterop_VERSION ) if(DjInterop_FOUND) From 7693b6e791807d4416fac1d999a52fa61705bd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 21 Dec 2022 21:46:12 +0100 Subject: [PATCH 014/267] CMake: Allow only DJInterop 0.16.0 because 0.17.0 is not yet compatible. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e35d8dd4e12..7321cdc6139 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1951,7 +1951,8 @@ if(ENGINEPRIME) set(LIBDJINTEROP_VERSION 0.16.0) # Look for an existing installation of libdjinterop and use that if available. # Otherwise, download and build from GitHub. - find_package(DjInterop ${LIBDJINTEROP_VERSION}) + # Note: Version 0.17.0 is not yet compatible + find_package(DjInterop ${LIBDJINTEROP_VERSION} EXACT) if(DjInterop_FOUND) # An existing installation of djinterop is available. message(STATUS "Using existing system installation of libdjinterop") From 17edd06a38d11b01fb49f17e9f9679263ee21a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 29 Dec 2022 17:25:49 +0100 Subject: [PATCH 015/267] Improve handling of m_madStream.error --- src/sources/soundsourcemp3.cpp | 108 +++++++++++++++------------------ 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 8f23a19261b..d82af36dc00 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -100,32 +100,30 @@ void logFrameHeader(QDebug logger, const mad_header& madHeader) { << "flags:" << formatHeaderFlags(madHeader.flags); } -inline bool isRecoverableError(const mad_stream& madStream) { - return MAD_RECOVERABLE(madStream.error); +inline bool isUnrecoverableError(mad_error error) { + return (MAD_ERROR_NONE != error) && !MAD_RECOVERABLE(error); } -inline bool isUnrecoverableError(const mad_stream& madStream) { - return (MAD_ERROR_NONE != madStream.error) && !isRecoverableError(madStream); -} - -inline bool isStreamValid(const mad_stream& madStream) { - return !isUnrecoverableError(madStream); +inline bool hasUnrecoverableError(const mad_stream* pMadStream) { + if (pMadStream) { + return isUnrecoverableError(pMadStream->error); + } + return true; } bool decodeFrameHeader( mad_header* pMadHeader, mad_stream* pMadStream, bool skipId3Tag) { - DEBUG_ASSERT(pMadStream); - DEBUG_ASSERT(isStreamValid(*pMadStream)); + DEBUG_ASSERT(!hasUnrecoverableError(pMadStream)); if (mad_header_decode(pMadHeader, pMadStream)) { // Something went wrong when decoding the frame header... + DEBUG_ASSERT(pMadStream->error != MAD_ERROR_NONE); if (MAD_ERROR_BUFLEN == pMadStream->error) { // EOF return false; } - if (isUnrecoverableError(*pMadStream)) { - DEBUG_ASSERT(!isStreamValid(*pMadStream)); + if (isUnrecoverableError(pMadStream->error)) { kLogger.warning() << "Unrecoverable MP3 header decoding error:" << mad_stream_errorstr(pMadStream); return false; @@ -135,28 +133,26 @@ bool decodeFrameHeader( // for debugging purposes. logFrameHeader(kLogger.debug(), *pMadHeader); #endif - if (isRecoverableError(*pMadStream)) { - if ((MAD_ERROR_LOSTSYNC == pMadStream->error) && skipId3Tag) { - long tagsize = id3_tag_query(pMadStream->this_frame, - pMadStream->bufend - pMadStream->this_frame); - if (0 < tagsize) { - // Skip ID3 tag data - mad_stream_skip(pMadStream, tagsize); - // Return immediately to suppress lost - // synchronization warnings - return false; - } + if ((MAD_ERROR_LOSTSYNC == pMadStream->error) && skipId3Tag) { + long tagsize = id3_tag_query(pMadStream->this_frame, + pMadStream->bufend - pMadStream->this_frame); + if (0 < tagsize) { + // Skip ID3 tag data + mad_stream_skip(pMadStream, tagsize); + // Return immediately to suppress lost + // synchronization warnings + return false; } - // These recoverable errors occur for many MP3 files and might - // worry users when logged as a warning. The issue will become - // obsolete once we switched to FFmpeg for MP3 decoding. - kLogger.info() << "Recoverable MP3 header decoding error:" - << mad_stream_errorstr(pMadStream); - logFrameHeader(kLogger.warning(), *pMadHeader); - return false; } + // These recoverable errors occur for many MP3 files and might + // worry users when logged as a warning. The issue will become + // obsolete once we switched to FFmpeg for MP3 decoding. + kLogger.info() << "Recoverable MP3 header decoding error:" + << mad_stream_errorstr(pMadStream); + logFrameHeader(kLogger.warning(), *pMadHeader); + return false; } - DEBUG_ASSERT(isStreamValid(*pMadStream)); + DEBUG_ASSERT(pMadStream->error == MAD_ERROR_NONE); return true; } @@ -250,21 +246,20 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( auto maxChannelCount = audio::ChannelCount(); do { if (!decodeFrameHeader(&madHeader, &m_madStream, true)) { - if (isStreamValid(m_madStream)) { - // Skip frame - continue; - } else { + if (isUnrecoverableError(m_madStream.error)) { // Abort decoding break; + } else { + // skip frame + continue; } } // Grab data from madHeader const unsigned int madSampleRate = madHeader.samplerate; - // TODO(XXX): Replace DEBUG_ASSERT with static_assert // MAD must not change its enum values! - DEBUG_ASSERT(MAD_UNITS_8000_HZ == 8000); + static_assert(MAD_UNITS_8000_HZ == 8000); const mad_units madUnits = static_cast(madSampleRate); const long madFrameLength = mad_timer_count(madHeader.duration, madUnits); @@ -466,7 +461,7 @@ void SoundSourceMp3::restartDecoding( mad_synth_mute(&m_madSynth); } - if (decodeFrameHeader(&m_madFrame.header, &m_madStream, false) && isStreamValid(m_madStream)) { + if (decodeFrameHeader(&m_madFrame.header, &m_madStream, false)) { m_curFrameIndex = seekFrame.frameIndex; } else { // Failure -> Seek to EOF @@ -604,7 +599,6 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( // Something went wrong when decoding the frame... if (MAD_ERROR_BUFLEN == m_madStream.error) { // Abort when reaching the end of the stream - DEBUG_ASSERT(isUnrecoverableError(m_madStream)); if (m_madStream.next_frame != nullptr) { // Decoding of the last MP3 frame fails if it is not padded // with 0 bytes. MAD requires that the last frame ends with @@ -638,32 +632,32 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( } break; } - if (isUnrecoverableError(m_madStream)) { + if (isUnrecoverableError(m_madStream.error)) { kLogger.warning() << "Unrecoverable MP3 frame decoding error:" << mad_stream_errorstr(&m_madStream); // Abort decoding break; } - if (isRecoverableError(m_madStream)) { - if (pMadThisFrame != m_madStream.this_frame) { - if (!pSampleBuffer || - (m_madStream.error == MAD_ERROR_LOSTSYNC)) { - // Don't bother the user with warnings from recoverable - // errors while skipping decoded samples or that even - // might occur for files that are perfectly ok. - if (kLogger.debugEnabled()) { - kLogger.debug() - << "Recoverable MP3 frame decoding error:" - << mad_stream_errorstr(&m_madStream); - } - } else { - kLogger.info() << "Recoverable MP3 frame decoding error:" - << mad_stream_errorstr(&m_madStream); + if (pMadThisFrame != m_madStream.this_frame) { + if (!pSampleBuffer || + (m_madStream.error == MAD_ERROR_LOSTSYNC)) { + // Don't bother the user with warnings from recoverable + // errors while skipping decoded samples or that even + // might occur for files that are perfectly ok. + if (kLogger.debugEnabled()) { + kLogger.debug() + << "Recoverable MP3 frame decoding error:" + << mad_stream_errorstr(&m_madStream); } + } else { + kLogger.info() << "Recoverable MP3 frame decoding error:" + << mad_stream_errorstr(&m_madStream); } - // Continue decoding } + // Continue decoding } + DEBUG_ASSERT(!isUnrecoverableError(m_madStream.error)); + if (pMadThisFrame == m_madStream.this_frame) { // Retry decoding, but only once for each position to // prevent infinite loops when decoding corrupt files @@ -684,8 +678,6 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( } } - DEBUG_ASSERT(isStreamValid(m_madStream)); - #ifndef QT_NO_DEBUG_OUTPUT const auto madFrameChannelCount = mixxx::audio::ChannelCount{MAD_NCHANNELS(&m_madFrame.header)}; From a572cda0299bc89c2205c5e9c8e707f6844d1fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 30 Dec 2022 15:06:06 +0100 Subject: [PATCH 016/267] Improve logging in SoundSourceMp3::decodeFrameHeader() --- src/sources/soundsourcemp3.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index d82af36dc00..17aa236ce28 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -128,11 +128,6 @@ bool decodeFrameHeader( << mad_stream_errorstr(pMadStream); return false; } -#ifndef QT_NO_DEBUG_OUTPUT - // Logging of MP3 frame headers should only be enabled - // for debugging purposes. - logFrameHeader(kLogger.debug(), *pMadHeader); -#endif if ((MAD_ERROR_LOSTSYNC == pMadStream->error) && skipId3Tag) { long tagsize = id3_tag_query(pMadStream->this_frame, pMadStream->bufend - pMadStream->this_frame); @@ -149,7 +144,7 @@ bool decodeFrameHeader( // obsolete once we switched to FFmpeg for MP3 decoding. kLogger.info() << "Recoverable MP3 header decoding error:" << mad_stream_errorstr(pMadStream); - logFrameHeader(kLogger.warning(), *pMadHeader); + logFrameHeader(kLogger.info(), *pMadHeader); return false; } DEBUG_ASSERT(pMadStream->error == MAD_ERROR_NONE); From 3f5e26458c63dadf98fd23da5a2000ddd9e87efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 30 Dec 2022 15:16:43 +0100 Subject: [PATCH 017/267] Make the sanity check for advanced frames more reliable. --- src/sources/soundsourcemp3.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 17aa236ce28..790573c6a0d 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -589,7 +589,9 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( // of mad_frame_decode() has proven to be extremely tricky. // Don't change anything at the following lines of code // unless you know what you are doing!!! - unsigned char const* pMadThisFrame = m_madStream.this_frame; + + // In addition to the error value we verify that the stream has been advanced + unsigned char const* pMadNextFrame = m_madStream.next_frame; if (mad_frame_decode(&m_madFrame, &m_madStream)) { // Something went wrong when decoding the frame... if (MAD_ERROR_BUFLEN == m_madStream.error) { @@ -633,7 +635,8 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( // Abort decoding break; } - if (pMadThisFrame != m_madStream.this_frame) { + if (pMadNextFrame != m_madStream.next_frame) { + // stream has been advanced if (!pSampleBuffer || (m_madStream.error == MAD_ERROR_LOSTSYNC)) { // Don't bother the user with warnings from recoverable @@ -653,7 +656,8 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( } DEBUG_ASSERT(!isUnrecoverableError(m_madStream.error)); - if (pMadThisFrame == m_madStream.this_frame) { + if (pMadNextFrame == m_madStream.next_frame) { + // stream has not been advanced // Retry decoding, but only once for each position to // prevent infinite loops when decoding corrupt files if (retryFrameIndex != m_curFrameIndex) { @@ -661,7 +665,9 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( if (kLogger.debugEnabled()) { kLogger.debug() << "Retry decoding MP3 frame @" - << m_curFrameIndex; + << m_curFrameIndex + << "error:" + << mad_stream_errorstr(&m_madStream); } continue; } else { From 86c505801038c5e57b5500f581f80be5f3eed0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 30 Dec 2022 15:39:22 +0100 Subject: [PATCH 018/267] Preserve bit reservoir when decoding the last frame --- src/sources/soundsourcemp3.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 790573c6a0d..f361a98e8b4 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -618,7 +618,14 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( // ...append the required guard bytes... std::fill(pLeftoverBuffer + remainingBytes, pLeftoverBuffer + leftoverBytes, 0); // ...and retry decoding. - mad_stream_buffer(&m_madStream, pLeftoverBuffer, leftoverBytes); + // Note: We must not use mad_stream_buffer() here, + // because this will clear the bit reservoir used + // for VBR + m_madStream.buffer = pLeftoverBuffer; + m_madStream.bufend = pLeftoverBuffer + leftoverBytes; + m_madStream.this_frame = pLeftoverBuffer; + m_madStream.next_frame = pLeftoverBuffer; + m_madStream.sync = 1; m_madStream.error = MAD_ERROR_NONE; continue; } From deabb05afe8ca584d3c6fe3ad164e4d9d212588d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 30 Dec 2022 15:56:08 +0100 Subject: [PATCH 019/267] extract code to SoundSourceMp3::copyLeftoverFrame() --- src/sources/soundsourcemp3.cpp | 83 +++++++++++++++++++--------------- src/sources/soundsourcemp3.h | 2 + 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index f361a98e8b4..4a5b0210db8 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -596,43 +596,12 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( // Something went wrong when decoding the frame... if (MAD_ERROR_BUFLEN == m_madStream.error) { // Abort when reaching the end of the stream - if (m_madStream.next_frame != nullptr) { - // Decoding of the last MP3 frame fails if it is not padded - // with 0 bytes. MAD requires that the last frame ends with - // at least MAD_BUFFER_GUARD of 0 bytes. - // https://www.mars.org/pipermail/mad-dev/2001-May/000262.html - // "The reason for MAD_BUFFER_GUARD has to do with the way decoding is performed. - // In Layer III, Huffman decoding may inadvertently read a few bytes beyond the - // end of the buffer in the case of certain invalid input. This is not detected - // until after the fact. To prevent this from causing problems, and also to - // ensure the next frame's main_data_begin pointer is always accessible, MAD - // requires MAD_BUFFER_GUARD (currently 8) bytes to be present in the buffer past - // the end of the current frame in order to decode the frame." - const SINT remainingBytes = m_madStream.bufend - m_madStream.next_frame; - DEBUG_ASSERT(remainingBytes <= kMaxBytesPerMp3Frame); // only last MP3 frame - const SINT leftoverBytes = remainingBytes + MAD_BUFFER_GUARD; - if ((remainingBytes > 0) && (leftoverBytes <= SINT(m_leftoverBuffer.size()))) { - // Copy the data of the last MP3 frame into the leftover buffer... - unsigned char* pLeftoverBuffer = &*m_leftoverBuffer.begin(); - std::copy(m_madStream.next_frame, m_madStream.next_frame + remainingBytes, pLeftoverBuffer); - // ...append the required guard bytes... - std::fill(pLeftoverBuffer + remainingBytes, pLeftoverBuffer + leftoverBytes, 0); - // ...and retry decoding. - // Note: We must not use mad_stream_buffer() here, - // because this will clear the bit reservoir used - // for VBR - m_madStream.buffer = pLeftoverBuffer; - m_madStream.bufend = pLeftoverBuffer + leftoverBytes; - m_madStream.this_frame = pLeftoverBuffer; - m_madStream.next_frame = pLeftoverBuffer; - m_madStream.sync = 1; - m_madStream.error = MAD_ERROR_NONE; - continue; - } - if (m_curFrameIndex < frameIndexMax()) { - kLogger.warning() << "Failed to decode the end of the MP3 stream" - << m_curFrameIndex << "<" << frameIndexMax(); - } + if (copyLeftoverFrame()) { + continue; + } + if (m_curFrameIndex < frameIndexMax()) { + kLogger.warning() << "Failed to decode the end of the MP3 stream" + << m_curFrameIndex << "<" << frameIndexMax(); } break; } @@ -785,4 +754,44 @@ ReadableSampleFrames SoundSourceMp3::readSampleFramesClamped( std::min(writableSampleFrames.writableLength(), getSignalInfo().frames2samples(numberOfFrames)))); } +bool SoundSourceMp3::copyLeftoverFrame() { + if (m_madStream.next_frame != nullptr) { + // Decoding of the last MP3 frame fails if it is not padded + // with 0 bytes. MAD requires that the last frame ends with + // at least MAD_BUFFER_GUARD of 0 bytes. + // https://www.mars.org/pipermail/mad-dev/2001-May/000262.html + // "The reason for MAD_BUFFER_GUARD has to do with the way decoding is performed. + // In Layer III, Huffman decoding may inadvertently read a few bytes beyond the + // end of the buffer in the case of certain invalid input. This is not detected + // until after the fact. To prevent this from causing problems, and also to + // ensure the next frame's main_data_begin pointer is always accessible, MAD + // requires MAD_BUFFER_GUARD (currently 8) bytes to be present in the buffer past + // the end of the current frame in order to decode the frame." + const SINT remainingBytes = m_madStream.bufend - m_madStream.next_frame; + DEBUG_ASSERT(remainingBytes <= kMaxBytesPerMp3Frame); // only last MP3 frame + const SINT leftoverBytes = remainingBytes + MAD_BUFFER_GUARD; + if ((remainingBytes > 0) && (leftoverBytes <= SINT(m_leftoverBuffer.size()))) { + // Copy the data of the last MP3 frame into the leftover buffer... + unsigned char* pLeftoverBuffer = &*m_leftoverBuffer.begin(); + std::copy(m_madStream.next_frame, + m_madStream.next_frame + remainingBytes, + pLeftoverBuffer); + // ...append the required guard bytes... + std::fill(pLeftoverBuffer + remainingBytes, pLeftoverBuffer + leftoverBytes, 0); + // ...and retry decoding. + // Note: We must not use mad_stream_buffer() here, + // because this will clear the bit reservoir used + // for VBR + m_madStream.buffer = pLeftoverBuffer; + m_madStream.bufend = pLeftoverBuffer + leftoverBytes; + m_madStream.this_frame = pLeftoverBuffer; + m_madStream.next_frame = pLeftoverBuffer; + m_madStream.sync = 1; + m_madStream.error = MAD_ERROR_NONE; + return true; + } + } + return false; +} + } // namespace mixxx diff --git a/src/sources/soundsourcemp3.h b/src/sources/soundsourcemp3.h index bb83cd9b709..e355203d069 100644 --- a/src/sources/soundsourcemp3.h +++ b/src/sources/soundsourcemp3.h @@ -56,6 +56,8 @@ class SoundSourceMp3 final : public SoundSource { /** Returns the position in m_seekFrameList of the requested frame index. */ SINT findSeekFrameIndex(SINT frameIndex) const; + bool copyLeftoverFrame(); + SINT m_curFrameIndex; // NOTE(uklotzde): Each invocation of initDecoding() must be From 85fc879834a3b05cd1f742922837e189783d0051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 30 Dec 2022 17:05:48 +0100 Subject: [PATCH 020/267] Call SoundSourceMp3::copyLeftoverFrame() only once. --- src/sources/soundsourcemp3.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 4a5b0210db8..81755365e75 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -767,12 +767,15 @@ bool SoundSourceMp3::copyLeftoverFrame() { // ensure the next frame's main_data_begin pointer is always accessible, MAD // requires MAD_BUFFER_GUARD (currently 8) bytes to be present in the buffer past // the end of the current frame in order to decode the frame." + unsigned char* pLeftoverBuffer = &*m_leftoverBuffer.begin(); + if (pLeftoverBuffer == m_madStream.buffer) { + return false; + } const SINT remainingBytes = m_madStream.bufend - m_madStream.next_frame; DEBUG_ASSERT(remainingBytes <= kMaxBytesPerMp3Frame); // only last MP3 frame const SINT leftoverBytes = remainingBytes + MAD_BUFFER_GUARD; if ((remainingBytes > 0) && (leftoverBytes <= SINT(m_leftoverBuffer.size()))) { // Copy the data of the last MP3 frame into the leftover buffer... - unsigned char* pLeftoverBuffer = &*m_leftoverBuffer.begin(); std::copy(m_madStream.next_frame, m_madStream.next_frame + remainingBytes, pLeftoverBuffer); From 28169f0ab97fe2b1ba201213886dd01552132db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 30 Dec 2022 17:15:13 +0100 Subject: [PATCH 021/267] Use copyLeftoverFrames() when decoding frame headers This is a workaround for an early abort when missing MAD_BUFFER_GUARD padding bytes. --- src/sources/soundsourcemp3.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 81755365e75..4fc68f17f19 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -241,6 +241,12 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( auto maxChannelCount = audio::ChannelCount(); do { if (!decodeFrameHeader(&madHeader, &m_madStream, true)) { + if (MAD_ERROR_BUFLEN == m_madStream.error) { + // try again with the copy and MAD_BUFFER_GUARD bytes + if (copyLeftoverFrame()) { + continue; + } + } if (isUnrecoverableError(m_madStream.error)) { // Abort decoding break; @@ -308,8 +314,9 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( m_curFrameIndex += madFrameLength; DEBUG_ASSERT(m_madStream.this_frame); - DEBUG_ASSERT(0 <= (m_madStream.this_frame - m_pFileData)); - } while (quint64(m_madStream.this_frame - m_pFileData) < m_fileSize); + DEBUG_ASSERT(0 <= (m_madStream.this_frame - m_pFileData) || + m_madStream.this_frame == &*m_leftoverBuffer.begin()); + } while (m_madStream.next_frame < m_madStream.bufend); mad_header_finish(&madHeader); @@ -471,7 +478,8 @@ void SoundSourceMp3::addSeekFrame( (m_seekFrameList.back().frameIndex < frameIndex)); DEBUG_ASSERT(m_seekFrameList.empty() || (nullptr == pInputData) || - (0 < (pInputData - m_seekFrameList.back().pInputData))); + (0 < (pInputData - m_seekFrameList.back().pInputData)) || + pInputData == &*m_leftoverBuffer.begin()); SeekFrameType seekFrame; seekFrame.pInputData = pInputData; seekFrame.frameIndex = frameIndex; From e7236a46cd2a8d77c82a12839c6802774e67b0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 1 Jan 2023 15:13:06 +0100 Subject: [PATCH 022/267] Fix the of-by-one issue that leads to a decoding error when decoding the last frame. This is fixed by skipping the first frame likely containing the MP3 Info Tag explicit during tryOpen(): Explain why this can't be made conditional without handling the offset of the analysis data. --- src/sources/soundsourcemp3.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 4fc68f17f19..8869933e62d 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -235,6 +235,18 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( quint64 sumBitrateFrames = 0; // nominator quint64 cntBitrateFrames = 0; // denominator + // Normally mp3 files starts with an extra frame of silence containing + // engoder infos called "LAME Tag". Since the early days of we skip the + // first frame uncoditionally, to not have these extra portion of silence + // in the track. This has the isseue that with files without this frame real + // samples are dropped. + // Since this issue exists since the early days of Mixxx the analysis data + // is affected by the offset. Fixing this without fixing the amalysis data + // will silently invalidate analysis, cues and loops. + // Note: A relates issue with not accurate seeks has been fixed in Mixxx 2.1.0 2015 + // https://github.com/mixxxdj/mixxx/pull/411 + bool mp3InfoTagSkipped = false; + mad_header madHeader; mad_header_init(&madHeader); @@ -272,6 +284,13 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( continue; } + if (!mp3InfoTagSkipped) { + // We assume that the first frame contains the mp3 info frame + // which needs to be skipped + mp3InfoTagSkipped = true; + continue; + } + const audio::ChannelCount madChannelCount(MAD_NCHANNELS(&madHeader)); if (madChannelCount.isValid()) { if (maxChannelCount.isValid() && (madChannelCount != maxChannelCount)) { @@ -463,12 +482,7 @@ void SoundSourceMp3::restartDecoding( mad_synth_mute(&m_madSynth); } - if (decodeFrameHeader(&m_madFrame.header, &m_madStream, false)) { - m_curFrameIndex = seekFrame.frameIndex; - } else { - // Failure -> Seek to EOF - m_curFrameIndex = frameIndexMax(); - } + m_curFrameIndex = seekFrame.frameIndex; } void SoundSourceMp3::addSeekFrame( From 3244f065a8b45925fe95947c08ed3b8534aca95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 1 Jan 2023 20:14:09 +0100 Subject: [PATCH 023/267] Read out MP3 info tag --- src/sources/soundsourcemp3.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 8869933e62d..eee74adb198 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -20,6 +20,10 @@ constexpr SINT kMaxBytesPerMp3Frame = 1441; // mp3 supports 9 different sample rates constexpr int kSampleRateCount = 9; +// Possible labels in the Mp3 Info Frame +// constexpr char kVbrTag0[] = "Xing"; +// constexpr char kVbrTag1[] = "Info"; + int getIndexBySampleRate(audio::SampleRate sampleRate) { switch (sampleRate) { case 8000: @@ -287,6 +291,33 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( if (!mp3InfoTagSkipped) { // We assume that the first frame contains the mp3 info frame // which needs to be skipped + // https://linux.m2osw.com/mp3-info-tag-specifications-rev0-lame-3100 + // https://github.com/Iunusov/libmp3lame-CMAKE/blob/bb770fb6e4b4dfc963860380a5e7765c370aaef1/libmp3lame/VbrTag.c#L333 + int mp3InfoFrameOffset = 0; + if (madHeader.flags & MAD_FLAG_LSF_EXT) { + // MPEG Version 2 (ISO/IEC 13818-3) + // or MPEG Version 2.5 + if (madHeader.mode == MAD_MODE_SINGLE_CHANNEL) { + mp3InfoFrameOffset = (9 + 4); + } else { + mp3InfoFrameOffset = (17 + 4); + } + } else { + // MPEG Version 1 (ISO/IEC 11172-3) + if (madHeader.mode == MAD_MODE_SINGLE_CHANNEL) { + mp3InfoFrameOffset = (17 + 4); + } else { + mp3InfoFrameOffset = (32 + 4); + } + } + + QString mp3InfoLabel = + QString::fromLatin1(reinterpret_cast( + &m_madStream.this_frame[mp3InfoFrameOffset])); + kLogger.debug() + << "Skipping MP3 Info Frame:" + << mp3InfoLabel; + mp3InfoTagSkipped = true; continue; } From bc1b598a51356a4e9d93f6f9c48ade12ae9c6dce Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 22 Jan 2023 23:18:18 +0100 Subject: [PATCH 024/267] JSdoc improvement: Made argument bitmask of addOutput optional (The default undefined is handled in the code already. In existing mappings this argument is seldom used) --- res/controllers/common-hid-packet-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/common-hid-packet-parser.js b/res/controllers/common-hid-packet-parser.js index 3ae60003e0f..7e8889bd9c0 100644 --- a/res/controllers/common-hid-packet-parser.js +++ b/res/controllers/common-hid-packet-parser.js @@ -779,7 +779,7 @@ class HIDPacket { * - H unsigned short (Uint16 Little-Endian) * - i signed integer (Int32 Little-Endian) * - I unsigned integer (Uint32 Little-Endian) - * @param {number} bitmask A bitwise mask of up to 32 bit. All bits set to'1' in this mask are + * @param {number} [bitmask=undefined] A bitwise mask of up to 32 bit. All bits set to'1' in this mask are * considered. * @param {fieldChangeCallback} [callback=undefined] Callback function for the control */ From ffb3999a63a6db59b923325ee560ab66881d6501 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sat, 4 Feb 2023 13:30:00 +0100 Subject: [PATCH 025/267] Unified range checking behavior for BeatJumpSize and for BeatLoopSize between GUI-Widget and controller CO, by moving the check from the widget to the engine. --- src/engine/controls/loopingcontrol.cpp | 22 ++++++++++++++++++++++ src/engine/controls/loopingcontrol.h | 1 + src/widget/wbeatspinbox.cpp | 7 +------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 9b052efb6f0..8217644ba0c 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -17,6 +17,9 @@ namespace { constexpr mixxx::audio::FrameDiff_t kMinimumAudibleLoopSizeFrames = 150; +constexpr double kMinBeatJumpSize = 0.03125; +constexpr double kMaxBeatJumpSize = 512; + // returns true if a is valid and is fairly close to target (within +/- 1 frame). bool positionNear(mixxx::audio::FramePos a, mixxx::audio::FramePos target) { return a.isValid() && a > target - 1 && a < target + 1; @@ -175,6 +178,9 @@ LoopingControl::LoopingControl(const QString& group, this, &LoopingControl::slotBeatJump, Qt::DirectConnection); m_pCOBeatJumpSize = new ControlObject(ConfigKey(group, "beatjump_size"), true, false, false, 4.0); + m_pCOBeatJumpSize->connectValueChangeRequest(this, + &LoopingControl::slotBeatJumpSizeChangeRequest, + Qt::DirectConnection); m_pCOBeatJumpSizeHalve = new ControlPushButton(ConfigKey(group, "beatjump_size_halve")); connect(m_pCOBeatJumpSizeHalve, @@ -1429,6 +1435,14 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable void LoopingControl::slotBeatLoopSizeChangeRequest(double beats) { // slotBeatLoop will call m_pCOBeatLoopSize->setAndConfirm if // new beatloop_size is valid + + double maxBeatSize = s_dBeatSizes[sizeof(s_dBeatSizes) / sizeof(s_dBeatSizes[0]) - 1]; + double minBeatSize = s_dBeatSizes[0]; + if ((beats < minBeatSize) || (beats > maxBeatSize)) { + // Don't clamp the value here to not fall out of a measure + return; + } + slotBeatLoop(beats, true, false); } @@ -1495,6 +1509,14 @@ void LoopingControl::slotBeatJump(double beats) { } } +void LoopingControl::slotBeatJumpSizeChangeRequest(double beats) { + if ((beats < kMinBeatJumpSize) || (beats > kMaxBeatJumpSize)) { + // Don't clamp the value here to not fall out of a measure + return; + } + m_pCOBeatJumpSize->setAndConfirm(beats); +} + void LoopingControl::slotBeatJumpSizeHalve(double pressed) { if (pressed > 0) { m_pCOBeatJumpSize->set(m_pCOBeatJumpSize->get() / 2); diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 3f3eebdf7ee..7aa7cebf8a0 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -87,6 +87,7 @@ class LoopingControl : public EngineControl { // Jump forward or backward by beats. void slotBeatJump(double beats); + void slotBeatJumpSizeChangeRequest(double beats); void slotBeatJumpSizeHalve(double pressed); void slotBeatJumpSizeDouble(double pressed); void slotBeatJumpForward(double pressed); diff --git a/src/widget/wbeatspinbox.cpp b/src/widget/wbeatspinbox.cpp index c41dc7da094..663bab35fd9 100644 --- a/src/widget/wbeatspinbox.cpp +++ b/src/widget/wbeatspinbox.cpp @@ -54,12 +54,7 @@ void WBeatSpinBox::stepBy(int steps) { QString temp = text(); int cursorPos = lineEdit()->cursorPosition(); if (validate(temp, cursorPos) == QValidator::Acceptable) { - double editValue = valueFromText(temp); - newValue = editValue * pow(2, steps); - if (newValue < minimum() || newValue > maximum()) { - // don't clamp the value here to not fall out of a measure - newValue = editValue; - } + newValue = valueFromText(temp) * pow(2, steps); } else { // here we have an unacceptable edit, going back to the old value first newValue = oldValue; From 8f35f9bf3b788d85473ac0be6506399b2b45dece Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 5 Feb 2023 00:32:18 +0100 Subject: [PATCH 026/267] Fixed slotLoopHalve/Double --- src/engine/controls/loopingcontrol.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 8217644ba0c..29a227aa860 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -346,7 +346,7 @@ void LoopingControl::slotLoopHalve(double pressed) { return; } - slotBeatLoop(m_pCOBeatLoopSize->get() / 2.0, true, false); + m_pCOBeatLoopSize->set(m_pCOBeatLoopSize->get() / 2.0); } void LoopingControl::slotLoopDouble(double pressed) { @@ -354,7 +354,7 @@ void LoopingControl::slotLoopDouble(double pressed) { return; } - slotBeatLoop(m_pCOBeatLoopSize->get() * 2.0, true, false); + m_pCOBeatLoopSize->set(m_pCOBeatLoopSize->get() * 2.0); } void LoopingControl::process(const double dRate, From a7351e101a6d39585312c6f856dc8732506f4336 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 5 Feb 2023 15:08:55 +0100 Subject: [PATCH 027/267] Fix error : use of overloaded operator '<<' is ambiguous (with operand types 'QDebug' and 'TagLib::FileName') --- src/track/taglib/trackmetadata_file.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/track/taglib/trackmetadata_file.cpp b/src/track/taglib/trackmetadata_file.cpp index 39ea76bfe32..d11eb6f17d2 100644 --- a/src/track/taglib/trackmetadata_file.cpp +++ b/src/track/taglib/trackmetadata_file.cpp @@ -114,8 +114,20 @@ bool hasMP4Tag(TagLib::MP4::File& file) { bool readAudioPropertiesFromFile( TrackMetadata* pTrackMetadata, const TagLib::File& file) { + // The declaration of TagLib::FileName is platform specific. +#ifdef _WIN32 + // For _WIN32 there are two types std::string and std::wstring + // we must pick one explicit, + // to prevent "use of overloaded operator '<<' is ambiguous" error + // on clang-cl builds. + const auto filename = file.name().wstr(); +#else + const auto filename = file.name(); +#endif if (!file.isValid()) { - kLogger.warning() << "Cannot read audio properties from inaccessible/unreadable/invalid file:" << file.name(); + kLogger.warning() << "Cannot read audio properties from " + "inaccessible/unreadable/invalid file:" + << filename; return false; } if (!pTrackMetadata) { @@ -126,7 +138,7 @@ bool readAudioPropertiesFromFile( file.audioProperties(); if (!pAudioProperties) { kLogger.warning() << "Failed to read audio properties from file" - << file.name(); + << filename; return false; } readAudioProperties(pTrackMetadata, *pAudioProperties); From 77a00ef1610a35e25ec6699f50f4d3c91253e09b Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 5 Feb 2023 15:14:01 +0100 Subject: [PATCH 028/267] Fixed unused variable warning --- src/engine/sidechain/enginenetworkstream.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/sidechain/enginenetworkstream.cpp b/src/engine/sidechain/enginenetworkstream.cpp index bd4c3f1efa1..83d17951048 100644 --- a/src/engine/sidechain/enginenetworkstream.cpp +++ b/src/engine/sidechain/enginenetworkstream.cpp @@ -147,7 +147,6 @@ qint64 EngineNetworkStream::getNetworkTimeUs() { return ((qint64)ft.dwHighDateTime << 32 | ft.dwLowDateTime) / 10; } else { static qint64 oldNow = 0; - static qint64 incCount = 0; static PerformanceTimer timerSinceInc; GetSystemTimeAsFileTime(&ft); qint64 now = ((qint64)ft.dwHighDateTime << 32 | ft.dwLowDateTime) / 10; From 28d705d22923d7b766964cb887b7ccfcb1ab78f8 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 5 Feb 2023 00:35:19 +0100 Subject: [PATCH 029/267] Added test cases for the loopsize / beatjumpsize range check --- src/test/looping_control_test.cpp | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/test/looping_control_test.cpp b/src/test/looping_control_test.cpp index ef05bc79b1f..3b53f9cf157 100644 --- a/src/test/looping_control_test.cpp +++ b/src/test/looping_control_test.cpp @@ -67,6 +67,10 @@ class LoopingControlTest : public MockedEngineBackendTest { m_pButtonBeatLoopActivate = std::make_unique( m_sGroup1, "beatloop_activate"); m_pBeatJumpSize = std::make_unique(m_sGroup1, "beatjump_size"); + m_pButtonBeatJumpSizeDouble = std::make_unique( + m_sGroup1, "beatjump_size_double"); + m_pButtonBeatJumpSizeHalve = std::make_unique( + m_sGroup1, "beatjump_size_halve"); m_pButtonBeatJumpForward = std::make_unique( m_sGroup1, "beatjump_forward"); m_pButtonBeatJumpBackward = std::make_unique( @@ -121,6 +125,8 @@ class LoopingControlTest : public MockedEngineBackendTest { std::unique_ptr m_pBeatLoopSize; std::unique_ptr m_pButtonBeatLoopActivate; std::unique_ptr m_pBeatJumpSize; + std::unique_ptr m_pButtonBeatJumpSizeHalve; + std::unique_ptr m_pButtonBeatJumpSizeDouble; std::unique_ptr m_pButtonBeatJumpForward; std::unique_ptr m_pButtonBeatJumpBackward; std::unique_ptr m_pButtonBeatLoopRoll1Activate; @@ -712,6 +718,45 @@ TEST_F(LoopingControlTest, BeatLoopSize_IsSetByNumberedControl) { EXPECT_EQ(2.0, m_pBeatLoopSize->get()); } +TEST_F(LoopingControlTest, BeatLoopSize_SetRangeCheck) { + m_pBeatLoopSize->set(512.0); + EXPECT_EQ(512, m_pBeatLoopSize->get()); + + m_pBeatLoopSize->set(150.0); + EXPECT_EQ(150, m_pBeatLoopSize->get()); + + m_pBeatLoopSize->set(513.0); + EXPECT_EQ(150, m_pBeatLoopSize->get()); + + m_pButtonLoopDouble->set(1.0); + m_pButtonLoopDouble->set(0.0); + EXPECT_EQ(300.0, m_pBeatLoopSize->get()); + + m_pButtonLoopDouble->set(1.0); + m_pButtonLoopDouble->set(0.0); + EXPECT_EQ(300.0, m_pBeatLoopSize->get()); + + m_pBeatLoopSize->set(1 / 32.0); + EXPECT_EQ(1 / 32.0, m_pBeatLoopSize->get()); + + m_pBeatLoopSize->set(1 / 10.0); + EXPECT_EQ(1 / 10.0, m_pBeatLoopSize->get()); + + m_pBeatLoopSize->set(1 / 33.0); + EXPECT_EQ(1 / 10.0, m_pBeatLoopSize->get()); + + m_pBeatLoopSize->set(0); + EXPECT_EQ(1 / 10.0, m_pBeatLoopSize->get()); + + m_pButtonLoopHalve->set(1.0); + m_pButtonLoopHalve->set(0.0); + EXPECT_EQ(1 / 20.0, m_pBeatLoopSize->get()); + + m_pButtonLoopHalve->set(1.0); + m_pButtonLoopHalve->set(0.0); + EXPECT_EQ(1 / 20.0, m_pBeatLoopSize->get()); +} + TEST_F(LoopingControlTest, BeatLoopSize_SetDoesNotStartLoop) { m_pTrack1->trySetBpm(120.0); m_pBeatLoopSize->set(16.0); @@ -814,6 +859,45 @@ TEST_F(LoopingControlTest, LegacyBeatLoopControl) { EXPECT_EQ(6.0, m_pBeatLoopSize->get()); } +TEST_F(LoopingControlTest, BeatjumpSize_SetRangeCheck) { + m_pBeatJumpSize->set(512.0); + EXPECT_EQ(512, m_pBeatJumpSize->get()); + + m_pBeatJumpSize->set(150.0); + EXPECT_EQ(150, m_pBeatJumpSize->get()); + + m_pBeatJumpSize->set(513.0); + EXPECT_EQ(150, m_pBeatJumpSize->get()); + + m_pButtonBeatJumpSizeDouble->set(1.0); + m_pButtonBeatJumpSizeDouble->set(0.0); + EXPECT_EQ(300.0, m_pBeatJumpSize->get()); + + m_pButtonBeatJumpSizeDouble->set(1.0); + m_pButtonBeatJumpSizeDouble->set(0.0); + EXPECT_EQ(300.0, m_pBeatJumpSize->get()); + + m_pBeatJumpSize->set(1 / 32.0); + EXPECT_EQ(1 / 32.0, m_pBeatJumpSize->get()); + + m_pBeatJumpSize->set(1 / 10.0); + EXPECT_EQ(1 / 10.0, m_pBeatJumpSize->get()); + + m_pBeatJumpSize->set(1 / 33.0); + EXPECT_EQ(1 / 10.0, m_pBeatJumpSize->get()); + + m_pBeatJumpSize->set(0); + EXPECT_EQ(1 / 10.0, m_pBeatJumpSize->get()); + + m_pButtonBeatJumpSizeHalve->set(1.0); + m_pButtonBeatJumpSizeHalve->set(0.0); + EXPECT_EQ(1 / 20.0, m_pBeatJumpSize->get()); + + m_pButtonBeatJumpSizeHalve->set(1.0); + m_pButtonBeatJumpSizeHalve->set(0.0); + EXPECT_EQ(1 / 20.0, m_pBeatJumpSize->get()); +} + TEST_F(LoopingControlTest, Beatjump_JumpsByBeats) { const auto bpm = mixxx::Bpm{120}; m_pTrack1->trySetBpm(bpm); From fdfe68c1915775e6db8f88ef8aa3b597f27937c1 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 13 Feb 2023 00:28:57 +0100 Subject: [PATCH 030/267] Preferences: add Mixer page (joint EQ and Crossfader pages) --- CMakeLists.txt | 6 +- src/preferences/dialog/dlgprefcrossfader.cpp | 221 ------ src/preferences/dialog/dlgprefcrossfader.h | 46 -- .../dialog/dlgprefcrossfaderdlg.ui | 234 ------ src/preferences/dialog/dlgprefeqdlg.ui | 436 ------------ src/preferences/dialog/dlgpreferences.cpp | 13 +- src/preferences/dialog/dlgpreferences.h | 3 +- .../{dlgprefeq.cpp => dlgprefmixer.cpp} | 332 +++++++-- .../dialog/{dlgprefeq.h => dlgprefmixer.h} | 31 +- src/preferences/dialog/dlgprefmixerdlg.ui | 667 ++++++++++++++++++ 10 files changed, 964 insertions(+), 1025 deletions(-) delete mode 100644 src/preferences/dialog/dlgprefcrossfader.cpp delete mode 100644 src/preferences/dialog/dlgprefcrossfader.h delete mode 100644 src/preferences/dialog/dlgprefcrossfaderdlg.ui delete mode 100644 src/preferences/dialog/dlgprefeqdlg.ui rename src/preferences/dialog/{dlgprefeq.cpp => dlgprefmixer.cpp} (75%) rename src/preferences/dialog/{dlgprefeq.h => dlgprefmixer.h} (81%) create mode 100644 src/preferences/dialog/dlgprefmixerdlg.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 27d67fa50c6..ddfe7af6fc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -839,14 +839,12 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/preferences/dialog/dlgprefbeatsdlg.ui src/preferences/dialog/dlgprefcolors.cpp src/preferences/dialog/dlgprefcolorsdlg.ui - src/preferences/dialog/dlgprefcrossfader.cpp - src/preferences/dialog/dlgprefcrossfaderdlg.ui + src/preferences/dialog/dlgprefmixer.cpp + src/preferences/dialog/dlgprefmixerdlg.ui src/preferences/dialog/dlgprefdeck.cpp src/preferences/dialog/dlgprefdeckdlg.ui src/preferences/dialog/dlgprefeffects.cpp src/preferences/dialog/dlgprefeffectsdlg.ui - src/preferences/dialog/dlgprefeq.cpp - src/preferences/dialog/dlgprefeqdlg.ui src/preferences/dialog/dlgpreferencepage.cpp src/preferences/dialog/dlgpreferences.cpp src/preferences/dialog/dlgpreferencesdlg.ui diff --git a/src/preferences/dialog/dlgprefcrossfader.cpp b/src/preferences/dialog/dlgprefcrossfader.cpp deleted file mode 100644 index 94dd086466c..00000000000 --- a/src/preferences/dialog/dlgprefcrossfader.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "preferences/dialog/dlgprefcrossfader.h" - -#include -#include - -#include "control/controlobject.h" -#include "engine/enginexfader.h" -#include "moc_dlgprefcrossfader.cpp" -#include "util/math.h" -#include "util/rescaler.h" - -DlgPrefCrossfader::DlgPrefCrossfader( - QWidget* parent, UserSettingsPointer config) - : DlgPreferencePage(parent), - m_config(config), - m_pxfScene(nullptr), - m_xFaderMode(MIXXX_XFADER_ADDITIVE), - m_transform(EngineXfader::kTransformDefault), - m_cal(0.0), - m_mode(EngineXfader::kXfaderConfigKey, "xFaderMode"), - m_curve(EngineXfader::kXfaderConfigKey, "xFaderCurve"), - m_calibration(EngineXfader::kXfaderConfigKey, "xFaderCalibration"), - m_reverse(EngineXfader::kXfaderConfigKey, "xFaderReverse"), - m_crossfader("[Master]", "crossfader"), - m_xFaderReverse(false) { - setupUi(this); - - QButtonGroup crossfaderModes; - crossfaderModes.addButton(radioButtonAdditive); - crossfaderModes.addButton(radioButtonConstantPower); - - loadSettings(); - - connect(SliderXFader, - QOverload::of(&QSlider::valueChanged), - this, - &DlgPrefCrossfader::slotUpdateXFader); - connect(SliderXFader, &QSlider::sliderMoved, this, &DlgPrefCrossfader::slotUpdateXFader); - connect(SliderXFader, &QSlider::sliderReleased, this, &DlgPrefCrossfader::slotUpdateXFader); - connect(SliderXFader, &QSlider::sliderReleased, this, &DlgPrefCrossfader::slotApply); - - // Update the crossfader curve graph and other settings when the - // crossfader mode is changed. - connect(radioButtonAdditive, &QRadioButton::clicked, this, &DlgPrefCrossfader::slotUpdate); - connect(radioButtonConstantPower, &QRadioButton::clicked, this, &DlgPrefCrossfader::slotUpdate); -} - -DlgPrefCrossfader::~DlgPrefCrossfader() { - delete m_pxfScene; -} - -// Loads the config keys and sets the widgets in the dialog to match -void DlgPrefCrossfader::loadSettings() { - // Range xFaderCurve EngineXfader::kTransformMin .. EngineXfader::kTransformMax - m_transform = m_config->getValue( - ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderCurve"), - EngineXfader::kTransformDefault); - - // Range SliderXFader 0 .. 100 - double sliderVal = RescalerUtils::oneByXToLinear( - m_transform - EngineXfader::kTransformMin + 1, - EngineXfader::kTransformMax - EngineXfader::kTransformMin + 1, - SliderXFader->minimum(), - SliderXFader->maximum()); - SliderXFader->setValue(static_cast(sliderVal + 0.5)); - - m_xFaderMode = - m_config->getValueString(ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderMode")).toInt(); - - if (m_xFaderMode == MIXXX_XFADER_CONSTPWR) { - radioButtonConstantPower->setChecked(true); - } else { - radioButtonAdditive->setChecked(true); - } - - m_xFaderReverse = m_config->getValueString( - ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderReverse")).toInt() == 1; - checkBoxReverse->setChecked(m_xFaderReverse); - - slotUpdateXFader(); - slotApply(); -} - -// Set the default values for all the widgets -void DlgPrefCrossfader::slotResetToDefaults() { - double sliderVal = RescalerUtils::oneByXToLinear( - EngineXfader::kTransformDefault - EngineXfader::kTransformMin + 1, - EngineXfader::kTransformMax - EngineXfader::kTransformMin + 1, - SliderXFader->minimum(), - SliderXFader->maximum()); - SliderXFader->setValue(static_cast(sliderVal)); - - m_xFaderMode = MIXXX_XFADER_ADDITIVE; - radioButtonAdditive->setChecked(true); - checkBoxReverse->setChecked(false); - slotUpdate(); - slotApply(); -} - -// Apply and save any changes made in the dialog -void DlgPrefCrossfader::slotApply() { - m_mode.set(m_xFaderMode); - m_curve.set(m_transform); - m_calibration.set(m_cal); - if (checkBoxReverse->isChecked() != m_xFaderReverse) { - m_reverse.set(checkBoxReverse->isChecked()); - double position = m_crossfader.get(); - m_crossfader.set(0.0 - position); - m_xFaderReverse = checkBoxReverse->isChecked(); - } - slotUpdateXFader(); -} - -// Update the dialog when the crossfader mode is changed -void DlgPrefCrossfader::slotUpdate() { - if (radioButtonAdditive->isChecked()) { - m_xFaderMode = MIXXX_XFADER_ADDITIVE; - } - if (radioButtonConstantPower->isChecked()) { - m_xFaderMode = MIXXX_XFADER_CONSTPWR; - } - - slotUpdateXFader(); -} - -// Draw the crossfader curve graph. Only needs to get drawn when a change -// has been made. -void DlgPrefCrossfader::drawXfaderDisplay() -{ - constexpr int kGrindXLines = 4; - constexpr int kGrindYLines = 6; - - int sizeX = graphicsViewXfader->width(); - int sizeY = graphicsViewXfader->height(); - - // Initialize Scene - if (m_pxfScene) { - delete m_pxfScene; - m_pxfScene = nullptr; - } - m_pxfScene = new QGraphicsScene(); - m_pxfScene->setSceneRect(0,0,sizeX, sizeY); - m_pxfScene->setBackgroundBrush(Qt::black); - - // Initialize QPens - QPen gridPen(Qt::green); - QPen graphLinePen(Qt::white); - - // draw grid - for (int i = 1; i < kGrindXLines; i++) { - m_pxfScene->addLine( - QLineF(0, i * (sizeY / kGrindXLines), sizeX, - i * (sizeY / kGrindXLines)), gridPen); - } - for (int i = 1; i < kGrindYLines; i++) { - m_pxfScene->addLine( - QLineF(i * (sizeX / kGrindYLines), 0, - i * (sizeX / kGrindYLines), sizeY), gridPen); - } - - // Draw graph lines - QPointF pointTotal, point1, point2; - QPointF pointTotalPrev, point1Prev, point2Prev; - int pointCount = sizeX - 4; - // reduced by 2 x 1 for border + 2 x 1 for inner distance to border - double xfadeStep = 2. / (pointCount - 1); - for (int i = 0; i < pointCount; i++) { - CSAMPLE_GAIN gain1, gain2; - EngineXfader::getXfadeGains((-1. + (xfadeStep * i)), - m_transform, m_cal, - m_xFaderMode, - checkBoxReverse->isChecked(), - &gain1, &gain2); - - double gain = sqrt(gain1 * gain1 + gain2 * gain2); - // scale for graph - gain1 *= 0.71f; - gain2 *= 0.71f; - gain *= 0.71f; - - // draw it - pointTotal = QPointF(i + 1, (1. - gain) * (sizeY) - 3); - point1 = QPointF(i + 1, (1. - gain1) * (sizeY) - 3); - point2 = QPointF(i + 1, (1. - gain2) * (sizeY) - 3); - - if (i > 0) { - m_pxfScene->addLine(QLineF(pointTotal, pointTotalPrev), QPen(Qt::red)); - m_pxfScene->addLine(QLineF(point1, point1Prev), graphLinePen); - m_pxfScene->addLine(QLineF(point2, point2Prev), graphLinePen); - } - - // Save old values - pointTotalPrev = pointTotal; - point1Prev = point1; - point2Prev = point2; - } - - graphicsViewXfader->setScene(m_pxfScene); - graphicsViewXfader->show(); - graphicsViewXfader->repaint(); -} - -// Update and save the crossfader's parameters from the dialog's widgets. -void DlgPrefCrossfader::slotUpdateXFader() { - // m_transform is in the range of 1 to 1000 while 50 % slider results - // to ~2, which represents a medium rounded fader curve. - m_transform = RescalerUtils::linearToOneByX( - SliderXFader->value(), - SliderXFader->minimum(), - SliderXFader->maximum(), - EngineXfader::kTransformMax) - 1 + EngineXfader::kTransformMin; - m_cal = EngineXfader::getPowerCalibration(m_transform); - m_config->set(ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderMode"), - ConfigValue(m_xFaderMode)); - m_config->set(ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderCurve"), - ConfigValue(QString::number(m_transform))); - m_config->set(ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderReverse"), - ConfigValue(checkBoxReverse->isChecked() ? 1 : 0)); - - drawXfaderDisplay(); -} diff --git a/src/preferences/dialog/dlgprefcrossfader.h b/src/preferences/dialog/dlgprefcrossfader.h deleted file mode 100644 index f556251d85b..00000000000 --- a/src/preferences/dialog/dlgprefcrossfader.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include - -#include "control/pollingcontrolproxy.h" -#include "preferences/dialog/dlgpreferencepage.h" -#include "preferences/dialog/ui_dlgprefcrossfaderdlg.h" -#include "preferences/usersettings.h" - -class DlgPrefCrossfader : public DlgPreferencePage, public Ui::DlgPrefCrossfaderDlg { - Q_OBJECT - public: - DlgPrefCrossfader(QWidget* parent, UserSettingsPointer _config); - virtual ~DlgPrefCrossfader(); - - public slots: - // Update X-Fader - void slotUpdateXFader(); - // Apply changes to widget - void slotApply(); - void slotUpdate(); - void slotResetToDefaults(); - - signals: - void apply(const QString &); - - private: - void loadSettings(); - void drawXfaderDisplay(); - - // Pointer to config object - UserSettingsPointer m_config; - - QGraphicsScene* m_pxfScene; - - // X-fader values - double m_xFaderMode, m_transform, m_cal; - - PollingControlProxy m_mode; - PollingControlProxy m_curve; - PollingControlProxy m_calibration; - PollingControlProxy m_reverse; - PollingControlProxy m_crossfader; - - bool m_xFaderReverse; -}; diff --git a/src/preferences/dialog/dlgprefcrossfaderdlg.ui b/src/preferences/dialog/dlgprefcrossfaderdlg.ui deleted file mode 100644 index 5bc00ad1ddd..00000000000 --- a/src/preferences/dialog/dlgprefcrossfaderdlg.ui +++ /dev/null @@ -1,234 +0,0 @@ - - - DlgPrefCrossfaderDlg - - - - 0 - 0 - 427 - 333 - - - - - 0 - 0 - - - - Crossfader Preferences - - - - - - Crossfader Curve - - - - - - - 0 - 0 - - - - Slow fade/Fast cut (additive) - - - - - - - Constant power - - - - - - - - - - - - - Mixing - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Scratching - - - - - - - - - 0 - - - 100 - - - 1 - - - 1 - - - 50 - - - Qt::Horizontal - - - 5 - - - - - - - - - - - - Linear - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Logarithmic - - - false - - - - - - - - - - - - 0 - 0 - - - - - 125 - 80 - - - - - 125 - 80 - - - - - 300 - 0 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - - - - - - - - 0 - 0 - - - - Reverse crossfader (Hamster Style) - - - - - checkBoxReverse - radioButtonConstantPower - radioButtonAdditive - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - radioButtonAdditive - radioButtonConstantPower - SliderXFader - checkBoxReverse - graphicsViewXfader - - - - diff --git a/src/preferences/dialog/dlgprefeqdlg.ui b/src/preferences/dialog/dlgprefeqdlg.ui deleted file mode 100644 index 61b37caef7a..00000000000 --- a/src/preferences/dialog/dlgprefeqdlg.ui +++ /dev/null @@ -1,436 +0,0 @@ - - - DlgPrefEQDlg - - - - 0 - 0 - 540 - 523 - - - - Equalizer Preferences - - - - - - Deck Equalizers - - - - - - Only allow EQ knobs to control EQ-specific effects - - - true - - - Uncheck to allow any effect to be loaded into the EQ knobs. - - - - - - - Use the same EQ filter for all decks - - - true - - - Uncheck to allow different decks to use different EQ effects. - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - 75 - true - - - - Equalizer Plugin - - - 3 - - - - - - - - 75 - true - - - - 3 - - - Quick Effect - - - - - - - - - - - - - High Shelf EQ - - - - - - 80 - - - 480 - - - 1 - - - 1 - - - 80 - - - Qt::Horizontal - - - 5 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 16 Hz - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 20.05 kHz - - - false - - - - - - - textLabel1 - - - false - - - SliderHiEQ - - - - - - - - - - Low Shelf EQ - - - - - - 80 - - - 480 - - - 1 - - - 1 - - - 80 - - - Qt::Horizontal - - - 5 - - - - - - - - - - - - 16 Hz - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - textLabel2 - - - false - - - SliderLoEQ - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 20.05 kHz - - - false - - - - - - - - - - - - Main EQ - - - - - - - - - - - Reset Parameter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Miscellaneous - - - - - - Resets the equalizers to their default values when loading a track. - - - Reset equalizers on track load - - - - - - - Resets the deck gain to unity when loading a track. - - - Reset gain on track load - - - - - - - Bypass EQ effect processing - - - When checked, EQs are not processed, improving performance on slower computers. - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - CheckBoxEqOnly - CheckBoxSingleEqEffect - - SliderHiEQ - SliderLoEQ - comboBoxMainEq - pbResetMainEq - - CheckBoxEqAutoReset - CheckBoxGainAutoReset - CheckBoxBypass - - - - diff --git a/src/preferences/dialog/dlgpreferences.cpp b/src/preferences/dialog/dlgpreferences.cpp index f18c09b9fec..b21ef9ac585 100644 --- a/src/preferences/dialog/dlgpreferences.cpp +++ b/src/preferences/dialog/dlgpreferences.cpp @@ -21,11 +21,10 @@ #include "preferences/dialog/dlgprefautodj.h" #include "preferences/dialog/dlgprefcolors.h" -#include "preferences/dialog/dlgprefcrossfader.h" #include "preferences/dialog/dlgprefdeck.h" #include "preferences/dialog/dlgprefeffects.h" -#include "preferences/dialog/dlgprefeq.h" #include "preferences/dialog/dlgprefinterface.h" +#include "preferences/dialog/dlgprefmixer.h" #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include "preferences/dialog/dlgprefwaveform.h" #endif @@ -184,15 +183,9 @@ DlgPreferences::DlgPreferences( "ic_preferences_decks.svg"); addPageWidget(PreferencesPage( - new DlgPrefEQ(this, pEffectsManager, m_pConfig), + new DlgPrefMixer(this, pEffectsManager, m_pConfig), new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type)), - tr("Equalizers"), - "ic_preferences_equalizers.svg"); - - addPageWidget(PreferencesPage( - new DlgPrefCrossfader(this, m_pConfig), - new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type)), - tr("Crossfader"), + tr("Mixer"), "ic_preferences_crossfader.svg"); addPageWidget(PreferencesPage( diff --git a/src/preferences/dialog/dlgpreferences.h b/src/preferences/dialog/dlgpreferences.h index 89d7443dba4..2aeb8efd4eb 100644 --- a/src/preferences/dialog/dlgpreferences.h +++ b/src/preferences/dialog/dlgpreferences.h @@ -26,9 +26,8 @@ class DlgPrefInterface; class DlgPrefWaveform; class DlgPrefDeck; class DlgPrefColors; -class DlgPrefEQ; class DlgPrefEffects; -class DlgPrefCrossfader; +class DlgPrefMixer; class DlgPrefAutoDJ; #ifdef __BROADCAST__ class DlgPrefBroadcast; diff --git a/src/preferences/dialog/dlgprefeq.cpp b/src/preferences/dialog/dlgprefmixer.cpp similarity index 75% rename from src/preferences/dialog/dlgprefeq.cpp rename to src/preferences/dialog/dlgprefmixer.cpp index 0976f86ced9..6a4f7aa9c18 100644 --- a/src/preferences/dialog/dlgprefeq.cpp +++ b/src/preferences/dialog/dlgprefmixer.cpp @@ -1,5 +1,6 @@ -#include "preferences/dialog/dlgprefeq.h" +#include "preferences/dialog/dlgprefmixer.h" +#include #include #include #include @@ -11,8 +12,11 @@ #include "effects/chains/equalizereffectchain.h" #include "effects/chains/quickeffectchain.h" #include "effects/effectslot.h" +#include "engine/enginexfader.h" #include "mixer/playermanager.h" -#include "moc_dlgprefeq.cpp" +#include "moc_dlgprefmixer.cpp" +#include "util/math.h" +#include "util/rescaler.h" namespace { const QString kConfigGroup = QStringLiteral("[Mixer Profile]"); @@ -38,14 +42,24 @@ bool isMainEQ(EffectManifest* pManifest) { } } // anonymous namespace -DlgPrefEQ::DlgPrefEQ( +DlgPrefMixer::DlgPrefMixer( QWidget* pParent, std::shared_ptr pEffectsManager, UserSettingsPointer pConfig) : DlgPreferencePage(pParent), + m_pConfig(pConfig), + m_pxfScene(nullptr), + m_xFaderMode(MIXXX_XFADER_ADDITIVE), + m_transform(EngineXfader::kTransformDefault), + m_cal(0.0), + m_mode(EngineXfader::kXfaderConfigKey, "xFaderMode"), + m_curve(EngineXfader::kXfaderConfigKey, "xFaderCurve"), + m_calibration(EngineXfader::kXfaderConfigKey, "xFaderCalibration"), + m_reverse(EngineXfader::kXfaderConfigKey, "xFaderReverse"), + m_crossfader("[Master]", "crossfader"), + m_xFaderReverse(false), m_COLoFreq(kConfigGroup, QStringLiteral("LoEQFrequency")), m_COHiFreq(kConfigGroup, QStringLiteral("HiEQFrequency")), - m_pConfig(pConfig), m_lowEqFreq(0.0), m_highEqFreq(0.0), m_pChainPresetManager(pEffectsManager->getChainPresetManager()), @@ -57,6 +71,26 @@ DlgPrefEQ::DlgPrefEQ( m_bEqAutoReset(false), m_bGainAutoReset(false) { setupUi(this); + + QButtonGroup crossfaderModes; + crossfaderModes.addButton(radioButtonAdditive); + crossfaderModes.addButton(radioButtonConstantPower); + + loadSettings(); + + connect(SliderXFader, + QOverload::of(&QSlider::valueChanged), + this, + &DlgPrefMixer::slotUpdateXFader); + connect(SliderXFader, &QSlider::sliderMoved, this, &DlgPrefMixer::slotUpdateXFader); + connect(SliderXFader, &QSlider::sliderReleased, this, &DlgPrefMixer::slotUpdateXFader); + connect(SliderXFader, &QSlider::sliderReleased, this, &DlgPrefMixer::slotApply); + + // Update the crossfader curve graph and other settings when the + // crossfader mode is changed. + connect(radioButtonAdditive, &QRadioButton::clicked, this, &DlgPrefMixer::slotUpdate); + connect(radioButtonConstantPower, &QRadioButton::clicked, this, &DlgPrefMixer::slotUpdate); + // Set the focus policy for comboboxes and sliders and connect them to the // custom event filter. See eventFilter() for details. // Deck EQ & QuickEffect comboxboxes are set up accordingly in slotNumDecksChanged(), @@ -68,53 +102,57 @@ DlgPrefEQ::DlgPrefEQ( comboBoxMainEq->setFocusPolicy(Qt::StrongFocus); comboBoxMainEq->installEventFilter(this); - loadSettings(); - // Connection - connect(SliderHiEQ, &QSlider::valueChanged, this, &DlgPrefEQ::slotUpdateHiEQ); - connect(SliderHiEQ, &QSlider::sliderMoved, this, &DlgPrefEQ::slotUpdateHiEQ); - connect(SliderHiEQ, &QSlider::sliderReleased, this, &DlgPrefEQ::slotUpdateHiEQ); + connect(SliderHiEQ, &QSlider::valueChanged, this, &DlgPrefMixer::slotUpdateHiEQ); + connect(SliderHiEQ, &QSlider::sliderMoved, this, &DlgPrefMixer::slotUpdateHiEQ); + connect(SliderHiEQ, &QSlider::sliderReleased, this, &DlgPrefMixer::slotUpdateHiEQ); - connect(SliderLoEQ, &QSlider::valueChanged, this, &DlgPrefEQ::slotUpdateLoEQ); - connect(SliderLoEQ, &QSlider::sliderMoved, this, &DlgPrefEQ::slotUpdateLoEQ); - connect(SliderLoEQ, &QSlider::sliderReleased, this, &DlgPrefEQ::slotUpdateLoEQ); + connect(SliderLoEQ, &QSlider::valueChanged, this, &DlgPrefMixer::slotUpdateLoEQ); + connect(SliderLoEQ, &QSlider::sliderMoved, this, &DlgPrefMixer::slotUpdateLoEQ); + connect(SliderLoEQ, &QSlider::sliderReleased, this, &DlgPrefMixer::slotUpdateLoEQ); - connect(CheckBoxEqAutoReset, &QCheckBox::stateChanged, this, &DlgPrefEQ::slotUpdateEqAutoReset); + connect(CheckBoxEqAutoReset, + &QCheckBox::stateChanged, + this, + &DlgPrefMixer::slotUpdateEqAutoReset); connect(CheckBoxGainAutoReset, &QCheckBox::stateChanged, this, - &DlgPrefEQ::slotUpdateGainAutoReset); - connect(CheckBoxBypass, &QCheckBox::stateChanged, this, &DlgPrefEQ::slotBypass); + &DlgPrefMixer::slotUpdateGainAutoReset); + connect(CheckBoxBypass, + &QCheckBox::stateChanged, + this, + &DlgPrefMixer::slotBypassEqChanged); connect(CheckBoxEqOnly, &QCheckBox::stateChanged, this, - &DlgPrefEQ::slotPopulateDeckEffectSelectors); + &DlgPrefMixer::slotPopulateDeckEffectSelectors); connect(CheckBoxSingleEqEffect, &QCheckBox::stateChanged, this, - &DlgPrefEQ::slotSingleEqChecked); + &DlgPrefMixer::slotSingleEqCheckboxChanged); // Quick hack to update the checkbox "Use the same EQ filter for all decks" // to not use the default state (checked) when slotNumDecksChanged() calls - // slotSingleEqChecked(state) here in constructor, because that would be written - // to config immediateley and thus reset the previous unchecked state. + // slotSingleEqCheckboxChanged(state) here in constructor, because that would + // be written to config immediateley and thus reset the previous unchecked state. // TODO(ronso0) Write only in slotApply(), read from config only in slotUpdate(). // Currently config is read in both slotUpdate() and loadSettings(). - CheckBoxSingleEqEffect->setChecked(m_pConfig->getValue( - ConfigKey(kConfigGroup, kSingleEq), "yes") == "yes"); - slotSingleEqChecked(CheckBoxSingleEqEffect->isChecked()); + CheckBoxSingleEqEffect->setChecked( + m_pConfig->getValue(ConfigKey(kConfigGroup, kSingleEq), "yes") == "yes"); + slotSingleEqCheckboxChanged(CheckBoxSingleEqEffect->isChecked()); // Add drop down lists for current decks and connect num_decks control // to slotNumDecksChanged m_pNumDecks = new ControlProxy("[Master]", "num_decks", this); - m_pNumDecks->connectValueChanged(this, &DlgPrefEQ::slotNumDecksChanged); + m_pNumDecks->connectValueChanged(this, &DlgPrefMixer::slotNumDecksChanged); slotNumDecksChanged(m_pNumDecks->get()); connect(m_pChainPresetManager.data(), &EffectChainPresetManager::quickEffectChainPresetListUpdated, this, - &DlgPrefEQ::slotPopulateDeckEffectSelectors); + &DlgPrefMixer::slotPopulateDeckEffectSelectors); setUpMainEQ(); @@ -122,7 +160,9 @@ DlgPrefEQ::DlgPrefEQ( slotApply(); } -DlgPrefEQ::~DlgPrefEQ() { +DlgPrefMixer::~DlgPrefMixer() { + delete m_pxfScene; + qDeleteAll(m_deckEqEffectSelectors); m_deckEqEffectSelectors.clear(); @@ -135,7 +175,7 @@ DlgPrefEQ::~DlgPrefEQ() { // This avoids undesired value changes of unfocused widget when scrolling the page. // Values can be changed only by explicit selection, dragging sliders and with // Up/Down (Left/Right respectively), PageUp/PageDown as well as Home/End keys. -bool DlgPrefEQ::eventFilter(QObject* obj, QEvent* e) { +bool DlgPrefMixer::eventFilter(QObject* obj, QEvent* e) { if (e->type() == QEvent::Wheel) { // Reject scrolling only if widget is unfocused. // Object to widget cast is needed to check the focus state. @@ -149,7 +189,7 @@ bool DlgPrefEQ::eventFilter(QObject* obj, QEvent* e) { return QObject::eventFilter(obj, e); } -void DlgPrefEQ::slotNumDecksChanged(double numDecks) { +void DlgPrefMixer::slotNumDecksChanged(double numDecks) { int oldDecks = m_deckEqEffectSelectors.size(); while (m_deckEqEffectSelectors.size() < static_cast(numDecks)) { int deckNo = m_deckEqEffectSelectors.size() + 1; @@ -166,7 +206,7 @@ void DlgPrefEQ::slotNumDecksChanged(double numDecks) { connect(pEqComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, - &DlgPrefEQ::slotEffectChangedOnDeck); + &DlgPrefMixer::slotEffectChangedOnDeck); // Create the drop down list for Quick Effects QComboBox* pQuickEffectComboBox = new QComboBox(this); @@ -176,7 +216,7 @@ void DlgPrefEQ::slotNumDecksChanged(double numDecks) { connect(pQuickEffectComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, - &DlgPrefEQ::slotQuickEffectChangedOnDeck); + &DlgPrefMixer::slotQuickEffectChangedOnDeck); QString deckGroupName = PlayerManager::groupForDeck(deckNo - 1); QString unitGroup = QuickEffectChain::formatEffectChainGroup(deckGroupName); @@ -192,7 +232,7 @@ void DlgPrefEQ::slotNumDecksChanged(double numDecks) { if (deckNo == 1) { m_firstSelectorLabel = label; - if (CheckBoxEqOnly->isChecked()) { + if (CheckBoxSingleEqEffect->isChecked()) { m_firstSelectorLabel->clear(); } } @@ -203,7 +243,7 @@ void DlgPrefEQ::slotNumDecksChanged(double numDecks) { gridLayout_3->addWidget(pQuickEffectComboBox, deckNo, 2); gridLayout_3->addItem( new QSpacerItem( - 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum), + 40, 1, QSizePolicy::Expanding, QSizePolicy::Minimum), deckNo, 3, 1, @@ -229,10 +269,10 @@ void DlgPrefEQ::slotNumDecksChanged(double numDecks) { m_deckEqEffectSelectors[i]->setCurrentIndex(selectedEQEffectIndex); } applySelections(); - slotSingleEqChecked(CheckBoxSingleEqEffect->isChecked()); + slotSingleEqCheckboxChanged(CheckBoxSingleEqEffect->isChecked()); } -void DlgPrefEQ::slotPopulateDeckEffectSelectors() { +void DlgPrefMixer::slotPopulateDeckEffectSelectors() { m_inSlotPopulateDeckEffectSelectors = true; // prevents a recursive call EffectManifestFilterFnc filterEQ; @@ -250,7 +290,7 @@ void DlgPrefEQ::slotPopulateDeckEffectSelectors() { m_inSlotPopulateDeckEffectSelectors = false; } -void DlgPrefEQ::populateDeckEqBoxList( +void DlgPrefMixer::populateDeckEqBoxList( const QList& boxList, EffectManifestFilterFnc filterFunc) { const QList pManifestList = getFilteredManifests(filterFunc); @@ -289,7 +329,7 @@ void DlgPrefEQ::populateDeckEqBoxList( } } -void DlgPrefEQ::populateDeckQuickEffectBoxList( +void DlgPrefMixer::populateDeckQuickEffectBoxList( const QList& boxList) { QList presetList = m_pChainPresetManager->getQuickEffectPresetsSorted(); @@ -315,7 +355,7 @@ void DlgPrefEQ::populateDeckQuickEffectBoxList( } } -void DlgPrefEQ::slotSingleEqChecked(int checked) { +void DlgPrefMixer::slotSingleEqCheckboxChanged(int checked) { bool do_hide = static_cast(checked); m_pConfig->set(ConfigKey(kConfigGroup, kSingleEq), do_hide ? QString("yes") : QString("no")); @@ -346,11 +386,43 @@ void DlgPrefEQ::slotSingleEqChecked(int checked) { applySelections(); } -QUrl DlgPrefEQ::helpUrl() const { +QUrl DlgPrefMixer::helpUrl() const { return QUrl(MIXXX_MANUAL_EQ_URL); } -void DlgPrefEQ::loadSettings() { +void DlgPrefMixer::loadSettings() { + // Range xFaderCurve EngineXfader::kTransformMin .. EngineXfader::kTransformMax + m_transform = m_pConfig->getValue( + ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderCurve"), + EngineXfader::kTransformDefault); + + // Range SliderXFader 0 .. 100 + double sliderVal = RescalerUtils::oneByXToLinear( + m_transform - EngineXfader::kTransformMin + 1, + EngineXfader::kTransformMax - EngineXfader::kTransformMin + 1, + SliderXFader->minimum(), + SliderXFader->maximum()); + SliderXFader->setValue(static_cast(sliderVal + 0.5)); + + m_xFaderMode = m_pConfig->getValueString( + ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderMode")) + .toInt(); + + if (m_xFaderMode == MIXXX_XFADER_CONSTPWR) { + radioButtonConstantPower->setChecked(true); + } else { + radioButtonAdditive->setChecked(true); + } + + m_xFaderReverse = m_pConfig->getValueString( + ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderReverse")) + .toInt() == 1; + checkBoxReverse->setChecked(m_xFaderReverse); + + slotUpdateXFader(); + slotApply(); + + // EQ //////////////////////////////////////////////// QString highEqCourse = m_pConfig->getValueString(ConfigKey(kConfigGroup, "HiEQFrequency")); QString highEqPrecise = m_pConfig->getValueString( ConfigKey(kConfigGroup, "HiEQFrequencyPrecise")); @@ -411,14 +483,26 @@ void DlgPrefEQ::loadSettings() { } } -void DlgPrefEQ::setDefaultShelves() { +void DlgPrefMixer::setDefaultShelves() { m_pConfig->set(ConfigKey(kConfigGroup, "HiEQFrequency"), ConfigValue(2500)); m_pConfig->set(ConfigKey(kConfigGroup, "LoEQFrequency"), ConfigValue(250)); m_pConfig->set(ConfigKey(kConfigGroup, "HiEQFrequencyPrecise"), ConfigValue(2500.0)); m_pConfig->set(ConfigKey(kConfigGroup, "LoEQFrequencyPrecise"), ConfigValue(250.0)); } -void DlgPrefEQ::slotResetToDefaults() { +void DlgPrefMixer::slotResetToDefaults() { + double sliderVal = RescalerUtils::oneByXToLinear( + EngineXfader::kTransformDefault - EngineXfader::kTransformMin + 1, + EngineXfader::kTransformMax - EngineXfader::kTransformMin + 1, + SliderXFader->minimum(), + SliderXFader->maximum()); + SliderXFader->setValue(static_cast(sliderVal)); + + m_xFaderMode = MIXXX_XFADER_ADDITIVE; + radioButtonAdditive->setChecked(true); + checkBoxReverse->setChecked(false); + + // EQ ////////////////////////////////// slotMainEQToDefault(); setDefaultShelves(); for (const auto& pCombo : std::as_const(m_deckEqEffectSelectors)) { @@ -439,7 +523,7 @@ void DlgPrefEQ::slotResetToDefaults() { slotApply(); } -void DlgPrefEQ::slotEffectChangedOnDeck(int effectIndex) { +void DlgPrefMixer::slotEffectChangedOnDeck(int effectIndex) { QComboBox* c = qobject_cast(sender()); // Check if qobject_cast was successful if (!c || m_inSlotPopulateDeckEffectSelectors) { @@ -464,7 +548,7 @@ void DlgPrefEQ::slotEffectChangedOnDeck(int effectIndex) { slotPopulateDeckEffectSelectors(); } -void DlgPrefEQ::slotQuickEffectChangedOnDeck(int effectIndex) { +void DlgPrefMixer::slotQuickEffectChangedOnDeck(int effectIndex) { QComboBox* c = qobject_cast(sender()); // Check if qobject_cast was successful if (!c || m_inSlotPopulateDeckEffectSelectors) { @@ -494,7 +578,7 @@ void DlgPrefEQ::slotQuickEffectChangedOnDeck(int effectIndex) { } } -void DlgPrefEQ::applySelections() { +void DlgPrefMixer::applySelections() { if (m_inSlotPopulateDeckEffectSelectors) { return; } @@ -502,7 +586,7 @@ void DlgPrefEQ::applySelections() { applySelectionsToDecks(); } -void DlgPrefEQ::applySelectionsToDecks() { +void DlgPrefMixer::applySelectionsToDecks() { int deck = 0; for (QComboBox* box : std::as_const(m_deckEqEffectSelectors)) { const EffectManifestPointer pManifest = @@ -545,7 +629,7 @@ void DlgPrefEQ::applySelectionsToDecks() { } } -void DlgPrefEQ::slotUpdateHiEQ() { +void DlgPrefMixer::slotUpdateHiEQ() { if (SliderHiEQ->value() < SliderLoEQ->value()) { SliderHiEQ->setValue(SliderLoEQ->value()); } @@ -566,7 +650,7 @@ void DlgPrefEQ::slotUpdateHiEQ() { slotApply(); } -void DlgPrefEQ::slotUpdateLoEQ() { +void DlgPrefMixer::slotUpdateLoEQ() { if (SliderLoEQ->value() > SliderHiEQ->value()) { SliderLoEQ->setValue(SliderHiEQ->value()); } @@ -587,7 +671,7 @@ void DlgPrefEQ::slotUpdateLoEQ() { slotApply(); } -void DlgPrefEQ::slotApplyMainEQParameter(int value) { +void DlgPrefMixer::slotApplyMainEQParameter(int value) { EffectSlotPointer pEffectSlot(m_pEffectMainEQ); if (!pEffectSlot.isNull()) { QSlider* slider = qobject_cast(sender()); @@ -612,7 +696,7 @@ void DlgPrefEQ::slotApplyMainEQParameter(int value) { } } -int DlgPrefEQ::getSliderPosition(double eqFreq, int minValue, int maxValue) { +int DlgPrefMixer::getSliderPosition(double eqFreq, int minValue, int maxValue) { if (eqFreq >= kFrequencyUpperLimit) { return maxValue; } else if (eqFreq <= kFrequencyLowerLimit) { @@ -624,7 +708,19 @@ int DlgPrefEQ::getSliderPosition(double eqFreq, int minValue, int maxValue) { return static_cast(dsliderPos); } -void DlgPrefEQ::slotApply() { +void DlgPrefMixer::slotApply() { + m_mode.set(m_xFaderMode); + m_curve.set(m_transform); + m_calibration.set(m_cal); + if (checkBoxReverse->isChecked() != m_xFaderReverse) { + m_reverse.set(checkBoxReverse->isChecked()); + double position = m_crossfader.get(); + m_crossfader.set(0.0 - position); + m_xFaderReverse = checkBoxReverse->isChecked(); + } + slotUpdateXFader(); + + // EQ //////////////////////////////////////// m_COLoFreq.set(m_lowEqFreq); m_COHiFreq.set(m_highEqFreq); m_pConfig->set(ConfigKey(kConfigGroup, "EqAutoReset"), @@ -635,7 +731,16 @@ void DlgPrefEQ::slotApply() { } // supposed to set the widgets to match internal state -void DlgPrefEQ::slotUpdate() { +void DlgPrefMixer::slotUpdate() { + if (radioButtonAdditive->isChecked()) { + m_xFaderMode = MIXXX_XFADER_ADDITIVE; + } + if (radioButtonConstantPower->isChecked()) { + m_xFaderMode = MIXXX_XFADER_CONSTPWR; + } + + slotUpdateXFader(); + slotUpdateLoEQ(); slotUpdateHiEQ(); slotPopulateDeckEffectSelectors(); @@ -648,15 +753,112 @@ void DlgPrefEQ::slotUpdate() { } } -void DlgPrefEQ::slotUpdateEqAutoReset(int i) { +// Draw the crossfader curve graph. Only needs to get drawn when a change +// has been made. +void DlgPrefMixer::drawXfaderDisplay() { + constexpr int kGrindXLines = 4; + constexpr int kGrindYLines = 6; + + int sizeX = graphicsViewXfader->width(); + int sizeY = graphicsViewXfader->height(); + + // Initialize Scene + if (m_pxfScene) { + delete m_pxfScene; + m_pxfScene = nullptr; + } + m_pxfScene = new QGraphicsScene(); + m_pxfScene->setSceneRect(0, 0, sizeX, sizeY); + m_pxfScene->setBackgroundBrush(Qt::black); + + // Initialize QPens + QPen gridPen(Qt::green); + QPen graphLinePen(Qt::white); + + // draw grid + for (int i = 1; i < kGrindXLines; i++) { + m_pxfScene->addLine( + QLineF(0, i * (sizeY / kGrindXLines), sizeX, i * (sizeY / kGrindXLines)), gridPen); + } + for (int i = 1; i < kGrindYLines; i++) { + m_pxfScene->addLine( + QLineF(i * (sizeX / kGrindYLines), 0, i * (sizeX / kGrindYLines), sizeY), gridPen); + } + + // Draw graph lines + QPointF pointTotal, point1, point2; + QPointF pointTotalPrev, point1Prev, point2Prev; + int pointCount = sizeX - 4; + // reduced by 2 x 1 for border + 2 x 1 for inner distance to border + double xfadeStep = 2. / (pointCount - 1); + for (int i = 0; i < pointCount; i++) { + CSAMPLE_GAIN gain1, gain2; + EngineXfader::getXfadeGains((-1. + (xfadeStep * i)), + m_transform, + m_cal, + m_xFaderMode, + checkBoxReverse->isChecked(), + &gain1, + &gain2); + + double gain = sqrt(gain1 * gain1 + gain2 * gain2); + // scale for graph + gain1 *= 0.71f; + gain2 *= 0.71f; + gain *= 0.71f; + + // draw it + pointTotal = QPointF(i + 1, (1. - gain) * (sizeY)-3); + point1 = QPointF(i + 1, (1. - gain1) * (sizeY)-3); + point2 = QPointF(i + 1, (1. - gain2) * (sizeY)-3); + + if (i > 0) { + m_pxfScene->addLine(QLineF(pointTotal, pointTotalPrev), QPen(Qt::red)); + m_pxfScene->addLine(QLineF(point1, point1Prev), graphLinePen); + m_pxfScene->addLine(QLineF(point2, point2Prev), graphLinePen); + } + + // Save old values + pointTotalPrev = pointTotal; + point1Prev = point1; + point2Prev = point2; + } + + graphicsViewXfader->setScene(m_pxfScene); + graphicsViewXfader->show(); + graphicsViewXfader->repaint(); +} + +// Update and save the crossfader's parameters from the dialog's widgets. +void DlgPrefMixer::slotUpdateXFader() { + // m_transform is in the range of 1 to 1000 while 50 % slider results + // to ~2, which represents a medium rounded fader curve. + m_transform = RescalerUtils::linearToOneByX( + SliderXFader->value(), + SliderXFader->minimum(), + SliderXFader->maximum(), + EngineXfader::kTransformMax) - + 1 + EngineXfader::kTransformMin; + m_cal = EngineXfader::getPowerCalibration(m_transform); + m_pConfig->set(ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderMode"), + ConfigValue(m_xFaderMode)); + m_pConfig->set(ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderCurve"), + ConfigValue(QString::number(m_transform))); + m_pConfig->set(ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderReverse"), + ConfigValue(checkBoxReverse->isChecked() ? 1 : 0)); + + drawXfaderDisplay(); +} + +void DlgPrefMixer::slotUpdateEqAutoReset(int i) { m_bEqAutoReset = static_cast(i); } -void DlgPrefEQ::slotUpdateGainAutoReset(int i) { +void DlgPrefMixer::slotUpdateGainAutoReset(int i) { m_bGainAutoReset = static_cast(i); } -void DlgPrefEQ::slotBypass(int state) { +void DlgPrefMixer::slotBypassEqChanged(int state) { if (state) { m_pConfig->set(ConfigKey(kConfigGroup, kEnableEqs), QString("no")); // Disable effect processing for all decks by setting the appropriate @@ -684,13 +886,13 @@ void DlgPrefEQ::slotBypass(int state) { slotApply(); } -void DlgPrefEQ::setUpMainEQ() { - connect(pbResetMainEq, &QPushButton::clicked, this, &DlgPrefEQ::slotMainEQToDefault); +void DlgPrefMixer::setUpMainEQ() { + connect(pbResetMainEq, &QPushButton::clicked, this, &DlgPrefMixer::slotMainEQToDefault); connect(comboBoxMainEq, QOverload::of(&QComboBox::currentIndexChanged), this, - &DlgPrefEQ::slotMainEqEffectChanged); + &DlgPrefMixer::slotMainEqEffectChanged); const QString configuredEffectId = m_pConfig->getValue(ConfigKey(kConfigGroup, "EffectForGroup_[Master]"), @@ -733,7 +935,7 @@ void DlgPrefEQ::setUpMainEQ() { } } -void DlgPrefEQ::slotMainEqEffectChanged(int effectIndex) { +void DlgPrefMixer::slotMainEqEffectChanged(int effectIndex) { // clear parameters view first qDeleteAll(m_mainEQSliders); m_mainEQSliders.clear(); @@ -797,12 +999,12 @@ void DlgPrefEQ::slotMainEqEffectChanged(int effectIndex) { connect(slider, &QSlider::sliderMoved, this, - &DlgPrefEQ::slotApplyMainEQParameter); + &DlgPrefMixer::slotApplyMainEQParameter); // catch scroll event connect(slider, &QSlider::valueChanged, this, - &DlgPrefEQ::slotApplyMainEQParameter); + &DlgPrefMixer::slotApplyMainEQParameter); QLabel* valueLabel = new QLabel(this); m_mainEQValues.append(valueLabel); @@ -829,7 +1031,7 @@ void DlgPrefEQ::slotMainEqEffectChanged(int effectIndex) { } } -double DlgPrefEQ::getEqFreq(int sliderVal, int minValue, int maxValue) { +double DlgPrefMixer::getEqFreq(int sliderVal, int minValue, int maxValue) { // We're mapping f(x) = x^4 onto the range kFrequencyLowerLimit, // kFrequencyUpperLimit with x [minValue, maxValue]. First translate x into // [0.0, 1.0], raise it to the 4th power, and then scale the result from @@ -843,7 +1045,7 @@ double DlgPrefEQ::getEqFreq(int sliderVal, int minValue, int maxValue) { return result; } -void DlgPrefEQ::validate_levels() { +void DlgPrefMixer::validate_levels() { m_highEqFreq = math_clamp(m_highEqFreq, kFrequencyLowerLimit, kFrequencyUpperLimit); m_lowEqFreq = math_clamp(m_lowEqFreq, kFrequencyLowerLimit, kFrequencyUpperLimit); if (m_lowEqFreq == m_highEqFreq) { @@ -857,12 +1059,12 @@ void DlgPrefEQ::validate_levels() { } } -QString DlgPrefEQ::getEQEffectGroupForDeck(int deck) const { +QString DlgPrefMixer::getEQEffectGroupForDeck(int deck) const { return EqualizerEffectChain::formatEffectSlotGroup( PlayerManager::groupForDeck(deck)); } -void DlgPrefEQ::slotMainEQToDefault() { +void DlgPrefMixer::slotMainEQToDefault() { EffectSlotPointer pEffectSlot(m_pEffectMainEQ); if (!pEffectSlot.isNull()) { int knobNum = pEffectSlot->numParameters(EffectManifestParameter::ParameterType::Knob); @@ -877,7 +1079,7 @@ void DlgPrefEQ::slotMainEQToDefault() { } } -void DlgPrefEQ::setMainEQParameter(int i, double value) { +void DlgPrefMixer::setMainEQParameter(int i, double value) { EffectSlotPointer pEffectSlot(m_pEffectMainEQ); if (!pEffectSlot.isNull()) { auto pParameterSlot = pEffectSlot->getEffectParameterSlot( @@ -902,7 +1104,7 @@ void DlgPrefEQ::setMainEQParameter(int i, double value) { } } -const QList DlgPrefEQ::getFilteredManifests( +const QList DlgPrefMixer::getFilteredManifests( EffectManifestFilterFnc filterFunc) const { const QList allManifests = m_pBackendManager->getManifests(); diff --git a/src/preferences/dialog/dlgprefeq.h b/src/preferences/dialog/dlgprefmixer.h similarity index 81% rename from src/preferences/dialog/dlgprefeq.h rename to src/preferences/dialog/dlgprefmixer.h index bc3398263de..e1ac7de0e91 100644 --- a/src/preferences/dialog/dlgprefeq.h +++ b/src/preferences/dialog/dlgprefmixer.h @@ -5,19 +5,20 @@ #include #include "control/controlproxy.h" +#include "control/pollingcontrolproxy.h" #include "effects/effectsmanager.h" #include "preferences/dialog/dlgpreferencepage.h" -#include "preferences/dialog/ui_dlgprefeqdlg.h" +#include "preferences/dialog/ui_dlgprefmixerdlg.h" #include "preferences/usersettings.h" -class DlgPrefEQ : public DlgPreferencePage, public Ui::DlgPrefEQDlg { +class DlgPrefMixer : public DlgPreferencePage, public Ui::DlgPrefMixerDlg { Q_OBJECT public: - DlgPrefEQ( + DlgPrefMixer( QWidget* parent, std::shared_ptr pEffectsManager, UserSettingsPointer _config); - virtual ~DlgPrefEQ(); + virtual ~DlgPrefMixer(); QUrl helpUrl() const override; @@ -28,12 +29,13 @@ class DlgPrefEQ : public DlgPreferencePage, public Ui::DlgPrefEQDlg { void slotApply() override; void slotUpdate() override; void slotResetToDefaults() override; + void slotUpdateXFader(); private slots: void slotEffectChangedOnDeck(int effectIndex); void slotQuickEffectChangedOnDeck(int effectIndex); void slotNumDecksChanged(double numDecks); - void slotSingleEqChecked(int checked); + void slotSingleEqCheckboxChanged(int checked); // Slot for toggling between advanced and basic views void slotPopulateDeckEffectSelectors(); // Update Hi EQ @@ -43,7 +45,7 @@ class DlgPrefEQ : public DlgPreferencePage, public Ui::DlgPrefEQDlg { void slotUpdateEqAutoReset(int); void slotUpdateGainAutoReset(int); - void slotBypass(int state); + void slotBypassEqChanged(int state); // Update the Main EQ void slotApplyMainEQParameter(int value); void slotMainEQToDefault(); @@ -52,6 +54,7 @@ class DlgPrefEQ : public DlgPreferencePage, public Ui::DlgPrefEQDlg { private: void loadSettings(); + void drawXfaderDisplay(); void setDefaultShelves(); double getEqFreq(int value, int minimum, int maximum); int getSliderPosition(double eqFreq, int minimum, int maximum); @@ -73,9 +76,23 @@ class DlgPrefEQ : public DlgPreferencePage, public Ui::DlgPrefEQDlg { bool eventFilter(QObject* obj, QEvent* e) override; + UserSettingsPointer m_pConfig; + + QGraphicsScene* m_pxfScene; + + // X-fader values + double m_xFaderMode, m_transform, m_cal; + + PollingControlProxy m_mode; + PollingControlProxy m_curve; + PollingControlProxy m_calibration; + PollingControlProxy m_reverse; + PollingControlProxy m_crossfader; + + bool m_xFaderReverse; + ControlProxy m_COLoFreq; ControlProxy m_COHiFreq; - UserSettingsPointer m_pConfig; double m_lowEqFreq, m_highEqFreq; EffectChainPresetManagerPointer m_pChainPresetManager; diff --git a/src/preferences/dialog/dlgprefmixerdlg.ui b/src/preferences/dialog/dlgprefmixerdlg.ui new file mode 100644 index 00000000000..39e91c062b8 --- /dev/null +++ b/src/preferences/dialog/dlgprefmixerdlg.ui @@ -0,0 +1,667 @@ + + + DlgPrefMixerDlg + + + + 0 + 0 + 427 + 333 + + + + + 0 + 0 + + + + Crossfader Preferences + + + + + + + Crossfader Curve + + + + + + + 0 + 0 + + + + Slow fade/Fast cut (additive) + + + + + + + Constant power + + + + + + + + + + + + + Mixing + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Scratching + + + + + + + + + 0 + + + 100 + + + 1 + + + 1 + + + 50 + + + Qt::Horizontal + + + 5 + + + + + + + + + Linear + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Logarithmic + + + false + + + + + + + + + + + + 0 + 0 + + + + + 125 + 80 + + + + + 125 + 80 + + + + + 300 + 0 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + 0 + 0 + + + + Reverse crossfader (Hamster Style) + + + + + + + + + + + Deck Equalizers + + + + + + Only allow EQ knobs to control EQ-specific effects + + + true + + + Uncheck to allow any effect to be loaded into the EQ knobs. + + + + + + + Use the same EQ filter for all decks + + + true + + + Uncheck to allow different decks to use different EQ effects. + + + + + + + + + + + + + + 75 + true + + + + Equalizer Plugin + + + 3 + + + + + + + + 75 + true + + + + 3 + + + Quick Effect + + + + + + + Qt::Horizontal + + + + 40 + 5 + + + + + + + + + + + + + + + + + + + + High Shelf EQ + + + + 75 + true + + + + false + + + + + + + + 80 + + + 480 + + + 1 + + + 1 + + + 80 + + + Qt::Horizontal + + + 5 + + + + + + + + + + 16 Hz + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + textLabel1 + + + false + + + SliderHiEQ + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 20.05 kHz + + + false + + + + + + + + + + + + + + + + + + Low Shelf EQ + + + + 75 + true + + + + false + + + + + + + + 80 + + + 480 + + + 1 + + + 1 + + + 80 + + + Qt::Horizontal + + + 5 + + + + + + + + + + 16 Hz + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + textLabel2 + + + false + + + SliderLoEQ + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 20.05 kHz + + + false + + + + + + + + + + + + + + + + + + + + + + Main EQ + + + + + + + + + + + Reset Parameter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Miscellaneous + + + + + + Resets the equalizers to their default values when loading a track. + + + Reset equalizers on track load + + + + + + + Resets the deck gain to unity when loading a track. + + + Reset gain on track load + + + + + + + Bypass EQ effect processing + + + When checked, EQs are not processed, improving performance on slower computers. + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + radioButtonAdditive + radioButtonConstantPower + SliderXFader + checkBoxReverse + graphicsViewXfader + CheckBoxEqOnly + CheckBoxSingleEqEffect + + SliderHiEQ + SliderLoEQ + comboBoxMainEq + pbResetMainEq + + CheckBoxEqAutoReset + CheckBoxGainAutoReset + CheckBoxBypass + + + + From c1a7a2b1242b168aa405aa53aebd1b6f357f7937 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 13 Feb 2023 12:21:08 +0100 Subject: [PATCH 031/267] Pref Mixer: group EQ shelve sliders --- src/preferences/dialog/dlgprefmixerdlg.ui | 516 +++++++++++----------- 1 file changed, 256 insertions(+), 260 deletions(-) diff --git a/src/preferences/dialog/dlgprefmixerdlg.ui b/src/preferences/dialog/dlgprefmixerdlg.ui index 39e91c062b8..562c398b9b0 100644 --- a/src/preferences/dialog/dlgprefmixerdlg.ui +++ b/src/preferences/dialog/dlgprefmixerdlg.ui @@ -287,239 +287,275 @@ - - + + + Bypass EQ effect processing + + + When checked, EQs are not processed, improving performance on slower computers. + + + - - - + + + + Resets the equalizers to their default values when loading a track. + + + Reset equalizers on track load + + + - - - - High Shelf EQ - - - - 75 - true - - - - false - - - + + + + Resets the deck gain to unity when loading a track. + + + Reset gain on track load + + + - - - - 80 - - - 480 - - - 1 - - - 1 - - - 80 - - - Qt::Horizontal - - - 5 - - - + + + - - - - - - 16 Hz - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - textLabel1 - - - false - - - SliderHiEQ - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 20.05 kHz - - - false - - - - - + + + + Equalizer frequency Shelves + + - + + + + + + + + High EQ + + + + 75 + true + + + + false + + + + + + + + 80 + + + 480 + + + 1 + + + 1 + + + 80 + + + Qt::Horizontal + + + 5 + - - + + + + + 16 Hz + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + textLabel1 + + + false + + + SliderHiEQ + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 20.05 kHz + + + false + + + + + - - - - Low Shelf EQ - - - - 75 - true - - - - false - - - + + + - - - - 80 - - - 480 - - - 1 - - - 1 - - - 80 - - - Qt::Horizontal - - - 5 - - - + + + - - - - - - 16 Hz - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - textLabel2 - - - false - - - SliderLoEQ - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 20.05 kHz - - - false - - - - - + + + + Low EQ + + + + 75 + true + + + + false + + + - + + + + 80 + + + 480 + + + 1 + + + 1 + + + 80 + + + Qt::Horizontal + + + 5 + + + + + + + 16 Hz + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + textLabel2 + + + false + + + SliderLoEQ + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 20.05 kHz + + + false + + + + + + @@ -585,46 +621,6 @@ - - - - Miscellaneous - - - - - - Resets the equalizers to their default values when loading a track. - - - Reset equalizers on track load - - - - - - - Resets the deck gain to unity when loading a track. - - - Reset gain on track load - - - - - - - Bypass EQ effect processing - - - When checked, EQs are not processed, improving performance on slower computers. - - - - - - - @@ -654,13 +650,13 @@ Dynamically add Eq / Quick Effect comboboxes --> SliderHiEQ SliderLoEQ + CheckBoxEqAutoReset + CheckBoxGainAutoReset + CheckBoxBypass comboBoxMainEq pbResetMainEq - CheckBoxEqAutoReset - CheckBoxGainAutoReset - CheckBoxBypass From 6220bbd3eeb2148c39254742e0cf1c5fc21417f3 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 13 Feb 2023 02:01:41 +0100 Subject: [PATCH 032/267] Pref Mixer: fit xfader graphic into frame --- src/preferences/dialog/dlgprefmixer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/preferences/dialog/dlgprefmixer.cpp b/src/preferences/dialog/dlgprefmixer.cpp index 6a4f7aa9c18..8ad88c936e9 100644 --- a/src/preferences/dialog/dlgprefmixer.cpp +++ b/src/preferences/dialog/dlgprefmixer.cpp @@ -759,8 +759,9 @@ void DlgPrefMixer::drawXfaderDisplay() { constexpr int kGrindXLines = 4; constexpr int kGrindYLines = 6; - int sizeX = graphicsViewXfader->width(); - int sizeY = graphicsViewXfader->height(); + int frameWidth = graphicsViewXfader->frameWidth(); + int sizeX = graphicsViewXfader->width() - 2 * frameWidth; + int sizeY = graphicsViewXfader->height() - 2 * frameWidth; // Initialize Scene if (m_pxfScene) { From 2bfdfab601e725ba5ccbfbdee8fa1fb27b15af64 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 13 Feb 2023 12:25:05 +0100 Subject: [PATCH 033/267] Pref Mixer: don't allow the xfader graph getting keyboard focus --- src/preferences/dialog/dlgprefmixer.cpp | 2 ++ src/preferences/dialog/dlgprefmixerdlg.ui | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/preferences/dialog/dlgprefmixer.cpp b/src/preferences/dialog/dlgprefmixer.cpp index 8ad88c936e9..b4022875dd9 100644 --- a/src/preferences/dialog/dlgprefmixer.cpp +++ b/src/preferences/dialog/dlgprefmixer.cpp @@ -101,6 +101,8 @@ DlgPrefMixer::DlgPrefMixer( SliderLoEQ->installEventFilter(this); comboBoxMainEq->setFocusPolicy(Qt::StrongFocus); comboBoxMainEq->installEventFilter(this); + // Don't allow the xfader graph getting keyboard focus + graphicsViewXfader->setFocusPolicy(Qt::NoFocus); // Connection connect(SliderHiEQ, &QSlider::valueChanged, this, &DlgPrefMixer::slotUpdateHiEQ); diff --git a/src/preferences/dialog/dlgprefmixerdlg.ui b/src/preferences/dialog/dlgprefmixerdlg.ui index 562c398b9b0..878892ef87f 100644 --- a/src/preferences/dialog/dlgprefmixerdlg.ui +++ b/src/preferences/dialog/dlgprefmixerdlg.ui @@ -643,7 +643,6 @@ radioButtonConstantPower SliderXFader checkBoxReverse - graphicsViewXfader CheckBoxEqOnly CheckBoxSingleEqEffect + + +
    +
  • + Fix empty waveform overview after loading a track. This fixes a Mixxx 2.3.4 regression + since + #11162 + . + Fixed by + #11333 + #11359 + #11344 +
  • +
  • + Fullscreen: Fix a crash that occurs on Linux after enabling fullsceen and using menu + shortcuts e.g. Alt-F. + #11328 + #11320 +
  • +
  • + Fullscreen: Rebuild & reconnect menu only on desktops with global menu + #11350 +
  • +
  • + macOS: Request Microphone and line-in access permission. + #11367 + #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 + #11341 +
  • +
  • + Pioneer DDJ-400: Make Beat FX section more intuitive + #10912 +
  • +
  • + Playlist export: Fix chained file extensions after changing the playlist type. + #11332 + #11327 +
  • +
  • + LateNight: brighter fx parameter buttons + #11397 +
  • +
+
+
    From bb11d5e171dac6b9c84924f92c3519e7b048c587 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 24 Mar 2023 03:28:17 +0100 Subject: [PATCH 128/267] ITunesMacOSImporter: Add doc comment --- src/library/itunes/itunesmacosimporter.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/library/itunes/itunesmacosimporter.h b/src/library/itunes/itunesmacosimporter.h index a3b3ad0589c..c106d675ef6 100644 --- a/src/library/itunes/itunesmacosimporter.h +++ b/src/library/itunes/itunesmacosimporter.h @@ -7,6 +7,8 @@ #include "library/itunes/itunesimporter.h" #include "library/libraryfeature.h" +/// An importer that reads the user's default iTunes/Music.app library +/// using the native `iTunesLibrary` framework on macOS. class ITunesMacOSImporter : public ITunesImporter { public: ITunesMacOSImporter(LibraryFeature* parentFeature, From f10b7bf710eb5805c4e3c327e88901b824333fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 28 Mar 2023 22:32:03 +0200 Subject: [PATCH 129/267] Manage lifetime of m_pStyle using a std::unique_ptr --- src/dialog/dlgreplacecuecolor.cpp | 7 ++----- src/dialog/dlgreplacecuecolor.h | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/dialog/dlgreplacecuecolor.cpp b/src/dialog/dlgreplacecuecolor.cpp index b1047173e58..c8c5e112f47 100644 --- a/src/dialog/dlgreplacecuecolor.cpp +++ b/src/dialog/dlgreplacecuecolor.cpp @@ -77,8 +77,8 @@ DlgReplaceCueColor::DlgReplaceCueColor( // from the rest of the application (when not styled via QSS), but that's // better than having buttons without any colors (which would make the // color picker unusable). - pushButtonNewColor->setStyle(m_pStyle); - pushButtonCurrentColor->setStyle(m_pStyle); + pushButtonNewColor->setStyle(m_pStyle.get()); + pushButtonCurrentColor->setStyle(m_pStyle.get()); // Set up new color button ColorPaletteSettings colorPaletteSettings(pConfig); @@ -173,9 +173,6 @@ DlgReplaceCueColor::DlgReplaceCueColor( slotUpdateWidgets(); } -DlgReplaceCueColor::~DlgReplaceCueColor() { -} - void DlgReplaceCueColor::setColorPalette(const ColorPalette& palette) { m_pNewColorPickerAction->setColorPalette(palette); QResizeEvent resizeNewColorMenuEvent(QSize(), m_pNewColorMenu->size()); diff --git a/src/dialog/dlgreplacecuecolor.h b/src/dialog/dlgreplacecuecolor.h index 397595f7292..47af6642a95 100644 --- a/src/dialog/dlgreplacecuecolor.h +++ b/src/dialog/dlgreplacecuecolor.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "dialog/ui_dlgreplacecuecolordlg.h" #include "library/dao/trackdao.h" @@ -35,7 +36,7 @@ class DlgReplaceCueColor : public QDialog, public Ui::DlgReplaceCueColor { mixxx::DbConnectionPoolPtr dbConnectionPool, TrackCollectionManager* pTrackCollectionManager, QWidget* pParent); - ~DlgReplaceCueColor(); + ~DlgReplaceCueColor() override = default; void setColorPalette(const ColorPalette& palette); @@ -66,6 +67,6 @@ class DlgReplaceCueColor : public QDialog, public Ui::DlgReplaceCueColor { parented_ptr m_pCurrentColorPickerAction; mixxx::RgbColor::optional_t m_lastAutoSetNewColor; mixxx::RgbColor::optional_t m_lastAutoSetCurrentColor; - QStyle* m_pStyle; + std::unique_ptr m_pStyle; }; Q_DECLARE_OPERATORS_FOR_FLAGS(DlgReplaceCueColor::Conditions); From ba7dd92aeb5140d3e26dc505f517829918afd208 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 29 Mar 2023 00:19:55 +0200 Subject: [PATCH 130/267] debug: omit 'QFileInfo()' when printing track file locations --- src/analyzer/analyzerthread.cpp | 4 ++-- src/library/autodj/autodjfeature.cpp | 2 +- src/library/basetrackcache.cpp | 2 +- src/library/dao/trackdao.cpp | 30 ++++++++++++++-------------- src/musicbrainz/chromaprinter.cpp | 2 +- src/sources/soundsourceproxy.cpp | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/analyzer/analyzerthread.cpp b/src/analyzer/analyzerthread.cpp index 2402ab27349..19427ad9df2 100644 --- a/src/analyzer/analyzerthread.cpp +++ b/src/analyzer/analyzerthread.cpp @@ -127,7 +127,7 @@ void AnalyzerThread::doRun() { while (awaitWorkItemsFetched()) { DEBUG_ASSERT(m_currentTrack); - kLogger.debug() << "Analyzing" << m_currentTrack->getFileInfo(); + kLogger.debug() << "Analyzing" << m_currentTrack->getLocation(); // Get the audio const auto audioSource = @@ -135,7 +135,7 @@ void AnalyzerThread::doRun() { if (!audioSource) { kLogger.warning() << "Failed to open file for analyzing:" - << m_currentTrack->getFileInfo(); + << m_currentTrack->getLocation(); emitDoneProgress(kAnalyzerProgressUnknown); continue; } diff --git a/src/library/autodj/autodjfeature.cpp b/src/library/autodj/autodjfeature.cpp index 6c2823f0604..1b54bc5b213 100644 --- a/src/library/autodj/autodjfeature.cpp +++ b/src/library/autodj/autodjfeature.cpp @@ -265,7 +265,7 @@ void AutoDJFeature::slotAddRandomTrack() { if (!pRandomTrack->checkFileExists()) { qWarning() << "Track does not exist:" << pRandomTrack->getInfo() - << pRandomTrack->getFileInfo(); + << pRandomTrack->getLocation(); pRandomTrack.reset(); } } diff --git a/src/library/basetrackcache.cpp b/src/library/basetrackcache.cpp index ae3931bf19f..829b1908659 100644 --- a/src/library/basetrackcache.cpp +++ b/src/library/basetrackcache.cpp @@ -194,7 +194,7 @@ bool BaseTrackCache::updateTrackInIndex( return false; } if (sDebug) { - qDebug() << "updateTrackInIndex:" << pTrack->getFileInfo(); + qDebug() << "updateTrackInIndex:" << pTrack->getLocation(); } int numColumns = columnCount(); diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index b581980968f..07f0f8ab6a3 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -279,8 +279,8 @@ void TrackDAO::saveTrack(Track* pTrack) const { return; } qDebug() << "TrackDAO: Saving track" - << trackId - << pTrack->getFileInfo(); + << trackId + << pTrack->getLocation(); if (updateTrack(pTrack)) { // BaseTrackCache must be informed separately, because the // track has already been disconnected and TrackDAO does @@ -534,13 +534,13 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { VERIFY_OR_DEBUG_ASSERT(m_pQueryLibraryInsert || m_pQueryTrackLocationInsert || m_pQueryLibrarySelect || m_pQueryTrackLocationSelect) { qDebug() << "TrackDAO::addTracksAddTrack: needed SqlQuerys have not " - "been prepared. Skipping track" - << pTrack->getFileInfo(); + "been prepared. Skipping track" + << pTrack->getLocation(); return TrackId(); } qDebug() << "TrackDAO: Adding track" - << pTrack->getFileInfo(); + << pTrack->getLocation(); TrackId trackId; @@ -572,10 +572,10 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { m_pQueryLibrarySelect->bindValue(":location", trackLocationId.toVariant()); if (!m_pQueryLibrarySelect->exec()) { - LOG_FAILED_QUERY(*m_pQueryLibrarySelect) - << "Failed to query existing track: " - << pTrack->getFileInfo(); - return TrackId(); + LOG_FAILED_QUERY(*m_pQueryLibrarySelect) + << "Failed to query existing track: " + << pTrack->getLocation(); + return TrackId(); } if (m_queryLibraryIdColumn == UndefinedRecordIndex) { QSqlRecord queryLibraryRecord = m_pQueryLibrarySelect->record(); @@ -601,7 +601,7 @@ TrackId TrackDAO::addTracksAddTrack(const TrackPointer& pTrack, bool unremove) { if (!m_pQueryLibraryUpdate->exec()) { LOG_FAILED_QUERY(*m_pQueryLibraryUpdate) << "Failed to unremove existing track: " - << pTrack->getFileInfo(); + << pTrack->getLocation(); return TrackId(); } } @@ -716,8 +716,8 @@ TrackPointer TrackDAO::addTracksAddFile(const TrackFile& trackFile, bool unremov const TrackId newTrackId = addTracksAddTrack(pTrack, unremove); if (!newTrackId.isValid()) { qWarning() << "TrackDAO::addTracksAddTrack:" - << "Failed to add track to database" - << pTrack->getFileInfo(); + << "Failed to add track to database" + << pTrack->getLocation(); // GlobalTrackCache will be unlocked implicitly return TrackPointer(); } @@ -1418,9 +1418,9 @@ bool TrackDAO::updateTrack(Track* pTrack) const { DEBUG_ASSERT(trackId.isValid()); qDebug() << "TrackDAO:" - << "Updating track in database" - << trackId - << pTrack->getFileInfo(); + << "Updating track in database" + << trackId + << pTrack->getLocation(); SqlTransaction transaction(m_database); // PerformanceTimer time; diff --git a/src/musicbrainz/chromaprinter.cpp b/src/musicbrainz/chromaprinter.cpp index f4c4971eb2d..4a053c69a1d 100644 --- a/src/musicbrainz/chromaprinter.cpp +++ b/src/musicbrainz/chromaprinter.cpp @@ -119,7 +119,7 @@ QString ChromaPrinter::getFingerprint(TrackPointer pTrack) { if (!pAudioSource) { qDebug() << "Failed to open file for fingerprinting" - << pTrack->getFileInfo(); + << pTrack->getLocation(); return QString(); } diff --git a/src/sources/soundsourceproxy.cpp b/src/sources/soundsourceproxy.cpp index 23cc7a6af0b..d4f0d22ceae 100644 --- a/src/sources/soundsourceproxy.cpp +++ b/src/sources/soundsourceproxy.cpp @@ -656,7 +656,7 @@ void SoundSourceProxy::updateTrackFromSource( << "Parsing missing" << (splitArtistTitle ? "artist/title" : "title") << "from file name:" - << trackFile; + << trackFile.location(); if (trackMetadata.refTrackInfo().parseArtistTitleFromFileName( trackFile.fileName(), splitArtistTitle)) { // Pretend that metadata import succeeded From 85523f4907dae2e24ebb826554bf6e1f1f85333f Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 29 Mar 2023 14:55:20 +0200 Subject: [PATCH 131/267] Revert "Disable qml-formatter unit #11386 is fixed" This reverts commit 39a5b35be02147a63d56e201bfbb64d1be49e5dc. --- .pre-commit-config.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2df5363e8e5..c459495d97e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -118,11 +118,10 @@ repos: hooks: - id: prettier types: [yaml] - # disabled until https://github.com/mixxxdj/mixxx/issues/11386 is fixed - #- repo: https://github.com/qarmin/qml_formatter.git - # rev: 0.2.0 - # hooks: - # - id: qml_formatter + - repo: https://github.com/qarmin/qml_formatter.git + rev: 0.2.0 + hooks: + - id: qml_formatter - repo: local hooks: - id: qsscheck From 3b97f61da7977614c67da3e74596be59a8238d4e Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 29 Mar 2023 15:49:17 +0200 Subject: [PATCH 132/267] ITunesMacOSImporter: Skip DRM and remote tracks --- src/library/itunes/itunesmacosimporter.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/library/itunes/itunesmacosimporter.mm b/src/library/itunes/itunesmacosimporter.mm index 6a5bffa5298..ecae7cee935 100644 --- a/src/library/itunes/itunesmacosimporter.mm +++ b/src/library/itunes/itunesmacosimporter.mm @@ -230,6 +230,12 @@ void importPlaylist(ITLibPlaylist* playlist, } void importMediaItem(ITLibMediaItem* item, QSqlQuery& query) { + // Skip DRM-protected and non-downloaded tracks + bool isRemote = item.locationType == ITLibMediaItemLocationTypeRemote; + if (item.drmProtected || isRemote) { + return; + } + query.bindValue(":id", dbIdFromPersistentId(item.persistentID)); query.bindValue(":artist", qStringFrom(item.artist.name)); query.bindValue(":title", qStringFrom(item.title)); From 16b699e5d4a02da04ddf03f13dda09c6d7b5d8d0 Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 29 Mar 2023 16:09:37 +0200 Subject: [PATCH 133/267] ITunesXMLImporter: Add missing m_dbfile initialization --- src/library/itunes/itunesxmlimporter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/library/itunes/itunesxmlimporter.cpp b/src/library/itunes/itunesxmlimporter.cpp index 42ed0a195fb..cdf6139f1d0 100644 --- a/src/library/itunes/itunesxmlimporter.cpp +++ b/src/library/itunes/itunesxmlimporter.cpp @@ -51,6 +51,7 @@ ITunesXMLImporter::ITunesXMLImporter(LibraryFeature* parentFeature, : m_parentFeature(parentFeature), m_file(filePath), m_xml(&m_file), + m_dbfile(filePath), m_database(database), m_pathMapping(pathMapping), m_cancelImport(cancelImport) { From ffe621699a1aa3047acfb8396d8c1233ca1faaee Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 29 Mar 2023 16:12:33 +0200 Subject: [PATCH 134/267] ITunesXMLImporter: Use better names for various members --- src/library/itunes/itunesxmlimporter.cpp | 50 ++++++++++++------------ src/library/itunes/itunesxmlimporter.h | 6 +-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/library/itunes/itunesxmlimporter.cpp b/src/library/itunes/itunesxmlimporter.cpp index cdf6139f1d0..443ce5873c4 100644 --- a/src/library/itunes/itunesxmlimporter.cpp +++ b/src/library/itunes/itunesxmlimporter.cpp @@ -44,14 +44,14 @@ const QString kRemote = "Remote"; } // anonymous namespace ITunesXMLImporter::ITunesXMLImporter(LibraryFeature* parentFeature, - const QString& filePath, + const QString& xmlFilePath, const QSqlDatabase& database, ITunesPathMapping& pathMapping, const std::atomic& cancelImport) : m_parentFeature(parentFeature), - m_file(filePath), - m_xml(&m_file), - m_dbfile(filePath), + m_xmlFilePath(xmlFilePath), + m_xmlFile(xmlFilePath), + m_xml(&m_xmlFile), m_database(database), m_pathMapping(pathMapping), m_cancelImport(cancelImport) { @@ -63,7 +63,7 @@ ITunesImport ITunesXMLImporter::importLibrary() { ITunesImport iTunesImport; iTunesImport.isMusicFolderLocatedAfterTracks = false; - if (!m_file.open(QIODevice::ReadOnly)) { + if (!m_xmlFile.open(QIODevice::ReadOnly)) { qDebug() << "Could not open iTunes music collection"; return iTunesImport; } @@ -106,22 +106,22 @@ void ITunesXMLImporter::guessMusicLibraryMountpoint() { // iTunes/Album Artwork // iTunes/iTunes Media <- this is the "Music Folder" // iTunes/iTunes Music Library.xml <- this location we already knew - QString music_folder = QUrl(m_xml.readElementText()).toLocalFile(); + QString musicFolder = QUrl(m_xml.readElementText()).toLocalFile(); - QString music_folder_test = music_folder; - music_folder_test.replace(kiTunesLocalhostToken, ""); - QDir music_folder_dir(music_folder_test); + QString musicFolderTest = musicFolder; + musicFolderTest.replace(kiTunesLocalhostToken, ""); + QDir musicFolderDir(musicFolderTest); // The music folder exists, so a simple transformation // of replacing localhost token with nothing will work. - if (music_folder_dir.exists()) { + if (musicFolderDir.exists()) { // Leave defaults intact. return; } // The iTunes Music Library doesn't exist! This means we are likely loading // the library from a system that is different from the one that wrote the - // iTunes configuration. The configuration file path, m_dbfile is a readable + // iTunes configuration. The configuration file path, m_xmlFilePath is a readable // location that in most situation is "close" to the music library path so // since we can read that file we will try to infer the music library mount // point from it. @@ -129,56 +129,56 @@ void ITunesXMLImporter::guessMusicLibraryMountpoint() { // Examples: // Windows with non-itunes-managed music: - // m_dbfile: c:/Users/LegacyII/Music/iTunes/iTunes Music Library.xml + // m_xmlFilePath: c:/Users/LegacyII/Music/iTunes/iTunes Music Library.xml // Music Folder: file://localhost/C:/Users/LegacyII/Music/ // Transformation: "//localhost/" -> "" // Mac OS X with iTunes-managed music: - // m_dbfile: /Users/rjryan/Music/iTunes/iTunes Music Library.xml + // m_xmlFilePath: /Users/rjryan/Music/iTunes/iTunes Music Library.xml // Music Folder: file://localhost/Users/rjryan/Music/iTunes/iTunes Media/ // Transformation: "//localhost" -> "" // Linux reading an OS X partition mounted at /media/foo to an // iTunes-managed music folder: - // m_dbfile: /media/foo/Users/rjryan/Music/iTunes/iTunes Music Library.xml + // m_xmlFilePath: /media/foo/Users/rjryan/Music/iTunes/iTunes Music Library.xml // Music Folder: file://localhost/Users/rjryan/Music/iTunes/iTunes Media/ // Transformation: "//localhost" -> "/media/foo" // Linux reading a Windows partition mounted at /media/foo to an // non-itunes-managed music folder: - // m_dbfile: /media/foo/Users/LegacyII/Music/iTunes/iTunes Music Library.xml + // m_xmlFilePath: /media/foo/Users/LegacyII/Music/iTunes/iTunes Music Library.xml // Music Folder: file://localhost/C:/Users/LegacyII/Music/ // Transformation: "//localhost/C:" -> "/media/foo" // Algorithm: - // 1. Find the largest common subsequence shared between m_dbfile and "Music - // Folder" + // 1. Find the largest common subsequence shared between m_xmlFilePath and + // "Music Folder" // 2. For all tracks, replace the left-side of of the LCS in "Music Folder" - // with the left-side of the LCS in m_dbfile. + // with the left-side of the LCS in m_xmlFilePath. - QString lcs = LCS(m_dbfile, music_folder); + QString lcs = LCS(m_xmlFilePath, musicFolder); if (lcs.size() <= 1) { qDebug() << "ERROR: Couldn't find a suitable transformation to load " "iTunes data files. Leaving defaults intact."; } - int musicFolderLcsIndex = music_folder.indexOf(lcs); + int musicFolderLcsIndex = musicFolder.indexOf(lcs); if (musicFolderLcsIndex < 0) { qDebug() << "ERROR: Detected LCS" << lcs - << "is not present in music_folder:" << music_folder; + << "is not present in musicFolder:" << musicFolder; return; } - int dbfileLcsIndex = m_dbfile.indexOf(lcs); + int dbfileLcsIndex = m_xmlFilePath.indexOf(lcs); if (dbfileLcsIndex < 0) { qDebug() << "ERROR: Detected LCS" << lcs - << "is not present in m_dbfile" << m_dbfile; + << "is not present in m_xmlFilePath" << m_xmlFilePath; return; } - m_pathMapping.dbITunesRoot = music_folder.left(musicFolderLcsIndex); - m_pathMapping.mixxxITunesRoot = m_dbfile.left(dbfileLcsIndex); + m_pathMapping.dbITunesRoot = musicFolder.left(musicFolderLcsIndex); + m_pathMapping.mixxxITunesRoot = m_xmlFilePath.left(dbfileLcsIndex); qDebug() << "Detected translation rule for iTunes files:" << m_pathMapping.dbITunesRoot << "->" << m_pathMapping.mixxxITunesRoot; } diff --git a/src/library/itunes/itunesxmlimporter.h b/src/library/itunes/itunesxmlimporter.h index 47f8b4b5e71..4eed8c2ad83 100644 --- a/src/library/itunes/itunesxmlimporter.h +++ b/src/library/itunes/itunesxmlimporter.h @@ -13,7 +13,7 @@ class ITunesXMLImporter : public ITunesImporter { public: ITunesXMLImporter(LibraryFeature* parentFeature, - const QString& filePath, + const QString& xmlFilePath, const QSqlDatabase& database, ITunesPathMapping& pathMapping, const std::atomic& cancelImport); @@ -22,9 +22,9 @@ class ITunesXMLImporter : public ITunesImporter { private: LibraryFeature* m_parentFeature; - QFile m_file; + const QString m_xmlFilePath; + QFile m_xmlFile; QXmlStreamReader m_xml; - QString m_dbfile; // The values behind these references are owned by the parent `ITunesFeature`, // thus there is an implicit contract here that this `ITunesXMLImporter` cannot // outlive the feature (which should not happen anyway, since importers are short-lived). From de5188d2ac634b031002bf753a2929d9c645aabf Mon Sep 17 00:00:00 2001 From: fwcd Date: Wed, 29 Mar 2023 16:16:15 +0200 Subject: [PATCH 135/267] ITunesXMLImporter: Use camelCase more consistently --- src/library/itunes/itunesxmlimporter.cpp | 66 ++++++++++++------------ src/library/itunes/itunesxmlimporter.h | 4 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/library/itunes/itunesxmlimporter.cpp b/src/library/itunes/itunesxmlimporter.cpp index 443ce5873c4..1d9f4eeb407 100644 --- a/src/library/itunes/itunesxmlimporter.cpp +++ b/src/library/itunes/itunesxmlimporter.cpp @@ -184,8 +184,8 @@ void ITunesXMLImporter::guessMusicLibraryMountpoint() { } void ITunesXMLImporter::parseTracks() { - bool in_container_dictionary = false; - bool in_track_dictionary = false; + bool inContainerDictionary = false; + bool inTrackDictionary = false; QSqlQuery query(m_database); query.prepare( "INSERT INTO itunes_library (id, artist, title, album, " @@ -207,12 +207,12 @@ void ITunesXMLImporter::parseTracks() { if (m_xml.isStartElement()) { if (m_xml.name() == kDict) { - if (!in_track_dictionary && !in_container_dictionary) { - in_container_dictionary = true; + if (!inTrackDictionary && !inContainerDictionary) { + inContainerDictionary = true; continue; - } else if (in_container_dictionary && !in_track_dictionary) { + } else if (inContainerDictionary && !inTrackDictionary) { // We are in a tag that holds track information - in_track_dictionary = true; + inTrackDictionary = true; // Parse track here parseTrack(query); } @@ -220,10 +220,10 @@ void ITunesXMLImporter::parseTracks() { } if (m_xml.isEndElement() && m_xml.name() == kDict) { - if (in_track_dictionary && in_container_dictionary) { - in_track_dictionary = false; + if (inTrackDictionary && inContainerDictionary) { + inTrackDictionary = false; continue; - } else if (in_container_dictionary && !in_track_dictionary) { + } else if (inContainerDictionary && !inTrackDictionary) { // Done parsing tracks. break; } @@ -237,7 +237,7 @@ void ITunesXMLImporter::parseTrack(QSqlQuery& query) { QString title; QString artist; QString album; - QString album_artist; + QString albumArtist; QString year; QString genre; QString grouping; @@ -284,7 +284,7 @@ void ITunesXMLImporter::parseTrack(QSqlQuery& query) { continue; } if (key == kAlbumArtist) { - album_artist = content; + albumArtist = content; continue; } if (key == kGenre) { @@ -358,7 +358,7 @@ void ITunesXMLImporter::parseTrack(QSqlQuery& query) { query.bindValue(":artist", artist); query.bindValue(":title", title); query.bindValue(":album", album); - query.bindValue(":album_artist", album_artist); + query.bindValue(":album_artist", albumArtist); query.bindValue(":genre", genre); query.bindValue(":grouping", grouping); query.bindValue(":year", year); @@ -381,13 +381,13 @@ void ITunesXMLImporter::parseTrack(QSqlQuery& query) { std::unique_ptr ITunesXMLImporter::parsePlaylists() { qDebug() << "Parse iTunes playlists"; std::unique_ptr pRootItem = TreeItem::newRoot(m_parentFeature); - QSqlQuery query_insert_to_playlists(m_database); - query_insert_to_playlists.prepare( + QSqlQuery queryInsertToPlaylists(m_database); + queryInsertToPlaylists.prepare( "INSERT INTO itunes_playlists (id, name) " "VALUES (:id, :name)"); - QSqlQuery query_insert_to_playlist_tracks(m_database); - query_insert_to_playlist_tracks.prepare( + QSqlQuery queryInsertToPlaylistTracks(m_database); + queryInsertToPlaylistTracks.prepare( "INSERT INTO itunes_playlist_tracks (playlist_id, track_id, position) " "VALUES (:playlist_id, :track_id, :position)"); @@ -395,8 +395,8 @@ std::unique_ptr ITunesXMLImporter::parsePlaylists() { m_xml.readNext(); // We process and iterate the tags holding playlist summary information here if (m_xml.isStartElement() && m_xml.name() == kDict) { - parsePlaylist(query_insert_to_playlists, - query_insert_to_playlist_tracks, + parsePlaylist(queryInsertToPlaylists, + queryInsertToPlaylistTracks, *pRootItem); continue; } @@ -420,8 +420,8 @@ bool ITunesXMLImporter::readNextStartElement() { return false; } -void ITunesXMLImporter::parsePlaylist(QSqlQuery& query_insert_to_playlists, - QSqlQuery& query_insert_to_playlist_tracks, +void ITunesXMLImporter::parsePlaylist(QSqlQuery& queryInsertToPlaylists, + QSqlQuery& queryInsertToPlaylistTracks, TreeItem& root) { // qDebug() << "Parse Playlist"; @@ -470,27 +470,27 @@ void ITunesXMLImporter::parsePlaylist(QSqlQuery& query_insert_to_playlists, if (isSystemPlaylist) { continue; } - query_insert_to_playlists.bindValue(":id", playlist_id); - query_insert_to_playlists.bindValue(":name", playlistname); + queryInsertToPlaylists.bindValue(":id", playlist_id); + queryInsertToPlaylists.bindValue(":name", playlistname); - bool success = query_insert_to_playlists.exec(); + bool success = queryInsertToPlaylists.exec(); if (!success) { - if (query_insert_to_playlists.lastError() + if (queryInsertToPlaylists.lastError() .nativeErrorCode() == QString::number(SQLITE_CONSTRAINT)) { // We assume a duplicate Playlist name playlistname += QString(" #%1").arg(playlist_id); - query_insert_to_playlists.bindValue(":name", playlistname); + queryInsertToPlaylists.bindValue(":name", playlistname); - bool success = query_insert_to_playlists.exec(); + bool success = queryInsertToPlaylists.exec(); if (!success) { // unexpected error - LOG_FAILED_QUERY(query_insert_to_playlists); + LOG_FAILED_QUERY(queryInsertToPlaylists); break; } } else { // unexpected error - LOG_FAILED_QUERY(query_insert_to_playlists); + LOG_FAILED_QUERY(queryInsertToPlaylists); return; } } @@ -503,14 +503,14 @@ void ITunesXMLImporter::parsePlaylist(QSqlQuery& query_insert_to_playlists, readNextStartElement(); track_reference = m_xml.readElementText().toInt(); - query_insert_to_playlist_tracks.bindValue(":playlist_id", playlist_id); - query_insert_to_playlist_tracks.bindValue(":track_id", track_reference); - query_insert_to_playlist_tracks.bindValue(":position", playlist_position++); + queryInsertToPlaylistTracks.bindValue(":playlist_id", playlist_id); + queryInsertToPlaylistTracks.bindValue(":track_id", track_reference); + queryInsertToPlaylistTracks.bindValue(":position", playlist_position++); // Insert tracks if we are not in a pre-build playlist - if (!isSystemPlaylist && !query_insert_to_playlist_tracks.exec()) { + if (!isSystemPlaylist && !queryInsertToPlaylistTracks.exec()) { qDebug() << "SQL Error in ITunesXMLImporter.cpp: line" << __LINE__ << " " - << query_insert_to_playlist_tracks.lastError(); + << queryInsertToPlaylistTracks.lastError(); qDebug() << "trackid" << track_reference; qDebug() << "playlistname; " << playlistname; qDebug() << "-----------------"; diff --git a/src/library/itunes/itunesxmlimporter.h b/src/library/itunes/itunesxmlimporter.h index 4eed8c2ad83..3a5e6ae3183 100644 --- a/src/library/itunes/itunesxmlimporter.h +++ b/src/library/itunes/itunesxmlimporter.h @@ -37,7 +37,7 @@ class ITunesXMLImporter : public ITunesImporter { void parseTrack(QSqlQuery& query); std::unique_ptr parsePlaylists(); bool readNextStartElement(); - void parsePlaylist(QSqlQuery& query_insert_to_playlists, - QSqlQuery& query_insert_to_playlist_tracks, + void parsePlaylist(QSqlQuery& queryInsertToPlaylists, + QSqlQuery& queryInsertToPlaylistTracks, TreeItem& root); }; From fec242234c66570c889843d232b4ef8ba9cc2e7d Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 29 Mar 2023 17:20:53 +0200 Subject: [PATCH 136/267] chore(ci): apply eslint --- res/controllers/common-controller-scripts.js | 22 +++++++------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 7de34d5d70e..c59ea7a77e1 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -41,23 +41,19 @@ var printObject = function(obj, maxdepth) { }; var stringifyObject = function(obj, maxdepth, checked, prefix) { - if (!maxdepth) - maxdepth = 2; + if (!maxdepth) { maxdepth = 2; } try { return JSON.stringify(obj, null, maxdepth); } catch (e) { - if (!checked) - checked = []; - if (!prefix) - prefix = ""; + if (!checked) { checked = []; } + if (!prefix) { prefix = ""; } if (maxdepth > 0 && typeof obj === "object" && obj !== null && Object.getPrototypeOf(obj) !== "" && !arrayContains(checked, obj)) { checked.push(obj); var output = "{\n"; for (var property in obj) { var value = obj[property]; - if (typeof value === "function") - continue; + if (typeof value === "function") { continue; } output += prefix + property + ": " + stringifyObject(value, maxdepth - 1, checked, prefix + " ") + "\n"; @@ -70,8 +66,7 @@ var stringifyObject = function(obj, maxdepth, checked, prefix) { var arrayContains = function(array, elem) { for (var i = 0; i < array.length; i++) { - if (array[i] === elem) - return true; + if (array[i] === elem) { return true; } } return false; }; @@ -94,8 +89,7 @@ var msecondstominutes = function(msecs) { var secs = (msecs / 1000) | 0; msecs %= 1000; msecs = Math.round(msecs * 100 / 1000); - if (msecs === 100) - msecs = 99; + if (msecs === 100) { msecs = 99; } return (m < 10 ? "0" + m : m) + ":" @@ -357,7 +351,7 @@ script.crossfaderCurve = function(value, min, max) { Output: none -------- ------------------------------------------------------ */ script.loopMove = function(group, direction, numberOfBeats) { - if (!numberOfBeats || numberOfBeats === 0) numberOfBeats = 0.5; + if (!numberOfBeats || numberOfBeats === 0) { numberOfBeats = 0.5; } if (direction < 0) { engine.setValue(group, "loop_move", -numberOfBeats); @@ -491,7 +485,7 @@ bpm.tapButton = function(deck) { bpm.previousTapDelta = tapDelta; bpm.tap.push(60 / tapDelta); // Keep the last 8 samples for averaging - if (bpm.tap.length > 8) bpm.tap.shift(); + if (bpm.tap.length > 8) { bpm.tap.shift(); } var sum = 0; for (var i=0; i Date: Wed, 29 Mar 2023 17:21:55 +0200 Subject: [PATCH 137/267] feat(controllers) add `script.posMod` for euclidian modulo --- res/controllers/common-controller-scripts.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index c59ea7a77e1..669815b20bd 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -341,6 +341,17 @@ script.crossfaderCurve = function(value, min, max) { } }; +/* -------- ------------------------------------------------------ + script.posMod + Purpose: Computes the euclidean modulo of m % n. The result is always + in the range [0, m[ + Input: dividend `a` and divisor `m` for modulo (a % m) + Output: positive remainder + -------- ------------------------------------------------------ */ +script.posMod = function(a, m) { + return ((a % m) + m) % m; +}; + /* -------- ------------------------------------------------------ script.loopMove Purpose: Moves the current loop by the specified number of beats (default 1/2) From 8f24b5f728d206f6a55bccc5067e3dc5444bcacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 29 Mar 2023 21:53:40 +0200 Subject: [PATCH 138/267] Fix typos in comment Co-authored-by: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> --- src/sources/soundsourcemp3.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index eee74adb198..7f929dbecee 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -240,12 +240,12 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( quint64 cntBitrateFrames = 0; // denominator // Normally mp3 files starts with an extra frame of silence containing - // engoder infos called "LAME Tag". Since the early days of we skip the + // encoder infos called "LAME Tag". Since the early days of we skip the // first frame uncoditionally, to not have these extra portion of silence - // in the track. This has the isseue that with files without this frame real + // in the track. This has the issue that with files without this frame real // samples are dropped. // Since this issue exists since the early days of Mixxx the analysis data - // is affected by the offset. Fixing this without fixing the amalysis data + // is affected by the offset. Fixing this without fixing the analysis data // will silently invalidate analysis, cues and loops. // Note: A relates issue with not accurate seeks has been fixed in Mixxx 2.1.0 2015 // https://github.com/mixxxdj/mixxx/pull/411 From 0168048e4b79cb4aa10bc321da696dc71392b78b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 29 Mar 2023 21:56:55 +0200 Subject: [PATCH 139/267] Swap condition for a better readability. Co-authored-by: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> --- src/sources/soundsourcemp3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 7f929dbecee..83c44d3721f 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -132,10 +132,10 @@ bool decodeFrameHeader( << mad_stream_errorstr(pMadStream); return false; } - if ((MAD_ERROR_LOSTSYNC == pMadStream->error) && skipId3Tag) { + if ((pMadStream->error == MAD_ERROR_LOSTSYNC) && skipId3Tag) { long tagsize = id3_tag_query(pMadStream->this_frame, pMadStream->bufend - pMadStream->this_frame); - if (0 < tagsize) { + if (tagsize > 0) { // Skip ID3 tag data mad_stream_skip(pMadStream, tagsize); // Return immediately to suppress lost From 88c59e90b131a833decd999cfa0b23f2dfa2927a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 29 Mar 2023 22:43:40 +0200 Subject: [PATCH 140/267] Read only the expected 4 bytes when reading the mp3 Info Frame Tag --- src/sources/soundsourcemp3.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index 83c44d3721f..6c9f5283538 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -20,9 +20,10 @@ constexpr SINT kMaxBytesPerMp3Frame = 1441; // mp3 supports 9 different sample rates constexpr int kSampleRateCount = 9; -// Possible labels in the Mp3 Info Frame +// Possible tags in the Mp3 Info Frame // constexpr char kVbrTag0[] = "Xing"; // constexpr char kVbrTag1[] = "Info"; +constexpr int kInfoTagStrLen = 4; int getIndexBySampleRate(audio::SampleRate sampleRate) { switch (sampleRate) { @@ -311,12 +312,13 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( } } - QString mp3InfoLabel = + QString mp3InfoTag = QString::fromLatin1(reinterpret_cast( - &m_madStream.this_frame[mp3InfoFrameOffset])); + &m_madStream.this_frame[mp3InfoFrameOffset]), + kInfoTagStrLen); kLogger.debug() << "Skipping MP3 Info Frame:" - << mp3InfoLabel; + << mp3InfoTag; mp3InfoTagSkipped = true; continue; From 6e80d1609fc0894a431ddc7e18b9fd5e106113cf Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 30 Mar 2023 02:51:26 +0200 Subject: [PATCH 141/267] ITunesFeature: Factor out makeImporter and guard macOS version --- src/library/itunes/itunesfeature.cpp | 48 +++++++++++++++++----------- src/library/itunes/itunesfeature.h | 1 + 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/library/itunes/itunesfeature.cpp b/src/library/itunes/itunesfeature.cpp index 53fa99d70a1..d3e95ac6f01 100644 --- a/src/library/itunes/itunesfeature.cpp +++ b/src/library/itunes/itunesfeature.cpp @@ -34,6 +34,16 @@ namespace { const QString ITDB_PATH_KEY = "mixxx.itunesfeature.itdbpath"; +bool isMacOSImporterAvailable() { +#ifdef __MACOS_ITUNES_LIBRARY__ + // The iTunesLibrary framework is only available on macOS 10.13+ + if (__builtin_available(macOS 10.13, *)) { + return true; + } +#endif + return false; +} + } // anonymous namespace ITunesFeature::ITunesFeature(Library* pLibrary, UserSettingsPointer pConfig) @@ -170,14 +180,11 @@ void ITunesFeature::activate(bool forceReload) { if (!dbSetting.isEmpty() && QFile::exists(dbSetting)) { m_dbfile = dbSetting; } else { -#ifndef __MACOS_ITUNES_LIBRARY__ // No Path in settings, try the default - // if not on macOS, where we use the ITunesMacOSImporter if m_dbfile is empty m_dbfile = getiTunesMusicPath(); -#endif } - if (!m_dbfile.isEmpty()) { + if (!m_dbfile.isEmpty() || !isMacOSImporterAvailable()) { mixxx::FileInfo fileInfo(m_dbfile); if (fileInfo.checkFileExists()) { // Users of Mixxx <1.12.0 didn't support sandboxing. If we are sandboxed @@ -273,6 +280,11 @@ void ITunesFeature::onRightClick(const QPoint& globalPos) { QString ITunesFeature::getiTunesMusicPath() { QString musicFolder; + if (isMacOSImporterAvailable()) { + qDebug() << "Using empty iTunes music path (which we interpret as " + "using ITunesMacOSImporter)"; + return ""; + } #if defined(__APPLE__) musicFolder = QStandardPaths::writableLocation(QStandardPaths::MusicLocation) + "/iTunes/iTunes Music Library.xml"; @@ -286,6 +298,18 @@ QString ITunesFeature::getiTunesMusicPath() { return musicFolder; } +std::unique_ptr ITunesFeature::makeImporter() { +#ifdef __MACOS_ITUNES_LIBRARY__ + if (isMacOSImporterAvailable()) { + qDebug() << "Using ITunesMacOSImporter to read default iTunes library"; + return std::make_unique(this, m_database, m_cancelImport); + } +#endif + qDebug() << "Using ITunesXMLImporter to read iTunes library from " << m_dbfile; + return std::make_unique( + this, m_dbfile, m_database, m_pathMapping, m_cancelImport); +} + // This method is executed in a separate thread // via QtConcurrent::run TreeItem* ITunesFeature::importLibrary() { @@ -306,21 +330,7 @@ TreeItem* ITunesFeature::importLibrary() { m_pathMapping.dbITunesRoot = kiTunesLocalhostToken; ITunesImport iTunesImport; - std::unique_ptr importer; - -#ifdef __MACOS_ITUNES_LIBRARY__ - if (m_dbfile.isEmpty()) { - qDebug() << "Using ITunesMacOSImporter to read default iTunes library"; - importer = std::make_unique(this, m_database, m_cancelImport); - } else { - qDebug() << "Using ITunesXMLImporter to read iTunes library from " << m_dbfile; - importer = std::make_unique( - this, m_dbfile, m_database, m_pathMapping, m_cancelImport); - } -#else - importer = std::make_unique( - this, m_dbfile, m_database, m_pathMapping, m_cancelImport); -#endif + std::unique_ptr importer = makeImporter(); iTunesImport = importer->importLibrary(); diff --git a/src/library/itunes/itunesfeature.h b/src/library/itunes/itunesfeature.h index 9810f08a8b4..0ea4a1275b1 100644 --- a/src/library/itunes/itunesfeature.h +++ b/src/library/itunes/itunesfeature.h @@ -43,6 +43,7 @@ class ITunesFeature : public BaseExternalLibraryFeature { std::unique_ptr createPlaylistModelForPlaylist( const QString& playlist) override; static QString getiTunesMusicPath(); + std::unique_ptr makeImporter(); // returns the invisible rootItem for the sidebar model TreeItem* importLibrary(); void guessMusicLibraryMountpoint(QXmlStreamReader& xml); From 16926b628c95585add359e9690c6c77d2c785d4b Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 30 Mar 2023 10:38:33 +0200 Subject: [PATCH 142/267] Prefs > Controller: don't expand page horizontally if mapping description has very long lines --- src/controllers/dlgprefcontrollerdlg.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/dlgprefcontrollerdlg.ui b/src/controllers/dlgprefcontrollerdlg.ui index 87b03045864..99f88ea8f53 100644 --- a/src/controllers/dlgprefcontrollerdlg.ui +++ b/src/controllers/dlgprefcontrollerdlg.ui @@ -237,7 +237,7 @@ - + 0 0 From 5dacf948f8986c74a45f030d6e1bb71b2929f13c Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 30 Mar 2023 13:38:21 +0200 Subject: [PATCH 143/267] Fix track selection if no track selected --- src/library/library.cpp | 3 +++ src/widget/wlibrary.cpp | 7 +++++++ src/widget/wtracktableview.cpp | 11 ++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/library/library.cpp b/src/library/library.cpp index 2b96ead04a5..5cb1ef8f2ca 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -689,6 +689,9 @@ std::unique_ptr Library::makeLibraryExporter( #endif bool Library::isTrackIdInCurrentLibraryView(const TrackId& trackId) { + VERIFY_OR_DEBUG_ASSERT(trackId.isValid()) { + return false; + } if (m_pLibraryWidget) { return m_pLibraryWidget->isTrackInCurrentView(trackId); } else { diff --git a/src/widget/wlibrary.cpp b/src/widget/wlibrary.cpp index f87fc1f3eb2..9165346bdf0 100644 --- a/src/widget/wlibrary.cpp +++ b/src/widget/wlibrary.cpp @@ -95,6 +95,9 @@ LibraryView* WLibrary::getActiveView() const { bool WLibrary::isTrackInCurrentView(const TrackId& trackId) { //qDebug() << "WLibrary::isTrackInCurrentView" << trackId; + VERIFY_OR_DEBUG_ASSERT(trackId.isValid()) { + return false; + } QWidget* current = currentWidget(); WTrackTableView* tracksView = qobject_cast(current); if (!tracksView) { @@ -115,6 +118,10 @@ bool WLibrary::isTrackInCurrentView(const TrackId& trackId) { void WLibrary::slotSelectTrackInActiveTrackView(const TrackId& trackId) { //qDebug() << "WLibrary::slotSelectTrackInActiveTrackView" << trackId; + if (!trackId.isValid()) { + return; + } + QWidget* current = currentWidget(); WTrackTableView* tracksView = qobject_cast(current); if (!tracksView) { diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 36537afdd78..7dd0a9395b1 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1022,6 +1022,9 @@ TrackId WTrackTableView::getCurrentTrackId() const { } bool WTrackTableView::isTrackInCurrentView(const TrackId& trackId) { + VERIFY_OR_DEBUG_ASSERT(trackId.isValid()) { + return false; + } //qDebug() << "WTrackTableView::isTrackInCurrentView" << trackId; TrackModel* pTrackModel = getTrackModel(); VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { @@ -1057,6 +1060,10 @@ void WTrackTableView::setSelectedTracks(const QList& trackIds) { } bool WTrackTableView::setCurrentTrackId(const TrackId& trackId, int column, bool scrollToTrack) { + VERIFY_OR_DEBUG_ASSERT(trackId.isValid()) { + return false; + } + QItemSelectionModel* pSelectionModel = selectionModel(); VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { qWarning() << "No selection model"; @@ -1124,8 +1131,10 @@ void WTrackTableView::slotAddToAutoDJReplace() { } void WTrackTableView::slotSelectTrack(const TrackId& trackId) { - if (setCurrentTrackId(trackId, 0, true)) { + if (trackId.isValid() && setCurrentTrackId(trackId, 0, true)) { setSelectedTracks({trackId}); + } else { + setSelectedTracks({}); } } From ae537d52f5323366c50634be47fee0ed0f89f820 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Wed, 29 Mar 2023 23:54:05 +0200 Subject: [PATCH 144/267] feat(engine): improve mono-compatibility for Rubberband R3. --- src/engine/bufferscalers/enginebufferscalerubberband.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/bufferscalers/enginebufferscalerubberband.cpp b/src/engine/bufferscalers/enginebufferscalerubberband.cpp index 03c09df4df2..66f75b0e59c 100644 --- a/src/engine/bufferscalers/enginebufferscalerubberband.cpp +++ b/src/engine/bufferscalers/enginebufferscalerubberband.cpp @@ -103,7 +103,11 @@ void EngineBufferScaleRubberBand::onSampleRateChanged() { RubberBandStretcher::OptionProcessRealTime; #if RUBBERBANDV3 if (m_useEngineFiner) { - rubberbandOptions |= RubberBandStretcher::OptionEngineFiner; + rubberbandOptions |= + RubberBandStretcher::OptionEngineFiner | + // Process Channels Together. otherwise the result is not + // mono-compatible. See #11361 + RubberBandStretcher::OptionChannelsTogether; } #endif From f4ab748810454d4fa0a00bd3f7dbb0fc73062c7c Mon Sep 17 00:00:00 2001 From: fwcd Date: Thu, 30 Mar 2023 19:38:17 +0200 Subject: [PATCH 145/267] ITunesFeature: Comment on __builtin_available --- src/library/itunes/itunesfeature.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/library/itunes/itunesfeature.cpp b/src/library/itunes/itunesfeature.cpp index d3e95ac6f01..23e558f0c68 100644 --- a/src/library/itunes/itunesfeature.cpp +++ b/src/library/itunes/itunesfeature.cpp @@ -37,6 +37,10 @@ const QString ITDB_PATH_KEY = "mixxx.itunesfeature.itdbpath"; bool isMacOSImporterAvailable() { #ifdef __MACOS_ITUNES_LIBRARY__ // The iTunesLibrary framework is only available on macOS 10.13+ + // Note that this uses a Clang directive to check the macOS version at + // runtime, which is the suggested approach for conditionally accessing + // macOS frameworks from C++ depending on availability: + // https://stackoverflow.com/a/57825758 if (__builtin_available(macOS 10.13, *)) { return true; } From 3f9939136a8f6fc3b603c97d57fec5a3832fe5cd Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Fri, 31 Mar 2023 00:32:25 +0200 Subject: [PATCH 146/267] refactor(effects): rename parameter --- src/effects/effectchain.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/effects/effectchain.cpp b/src/effects/effectchain.cpp index 69124a40c66..e21f9d54b1a 100644 --- a/src/effects/effectchain.cpp +++ b/src/effects/effectchain.cpp @@ -200,23 +200,23 @@ const QString& EffectChain::presetName() const { return m_presetName; } -void EffectChain::loadChainPreset(EffectChainPresetPointer pPreset) { +void EffectChain::loadChainPreset(EffectChainPresetPointer pChainPreset) { slotControlClear(1); - VERIFY_OR_DEBUG_ASSERT(pPreset) { + VERIFY_OR_DEBUG_ASSERT(pChainPreset) { return; } int effectSlotIndex = 0; - for (const auto& pEffectPreset : pPreset->effectPresets()) { + for (const auto& pEffectPreset : pChainPreset->effectPresets()) { EffectSlotPointer pEffectSlot = m_effectSlots.at(effectSlotIndex); pEffectSlot->loadEffectFromPreset(pEffectPreset); effectSlotIndex++; } - setMixMode(pPreset->mixMode()); - m_pControlChainSuperParameter->setDefaultValue(pPreset->superKnob()); + setMixMode(pChainPreset->mixMode()); + m_pControlChainSuperParameter->setDefaultValue(pChainPreset->superKnob()); - m_presetName = pPreset->name(); + m_presetName = pChainPreset->name(); emit chainPresetChanged(m_presetName); setControlLoadedPresetIndex(presetIndex()); From f6151d9b3aa1f50b32cdefc85d89792aebace7bd Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Fri, 31 Mar 2023 00:35:45 +0200 Subject: [PATCH 147/267] fix(effects): don't apply presets to non-existent slots Only load effect preset from chain presets to slots that actually have been created. The rest gets ignored. --- src/effects/effectchain.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/effects/effectchain.cpp b/src/effects/effectchain.cpp index e21f9d54b1a..9c8c143cb1f 100644 --- a/src/effects/effectchain.cpp +++ b/src/effects/effectchain.cpp @@ -206,11 +206,17 @@ void EffectChain::loadChainPreset(EffectChainPresetPointer pChainPreset) { return; } - int effectSlotIndex = 0; - for (const auto& pEffectPreset : pChainPreset->effectPresets()) { - EffectSlotPointer pEffectSlot = m_effectSlots.at(effectSlotIndex); - pEffectSlot->loadEffectFromPreset(pEffectPreset); - effectSlotIndex++; + const QList effectPresets = pChainPreset->effectPresets(); + + // TODO: use C++23 std::ranges::views::zip instead + // `EffectChain`s can create arbitrary amounts of effectslots and chain presets + // can contain an arbitrary number of effects. This ensures we only load + // as many effects as we have slots available. + const int validPresetSlotCount = std::min(effectPresets.count(), m_effectSlots.count()); + for (int presetSlotIndex = 0; + presetSlotIndex < validPresetSlotCount; + presetSlotIndex++) { + m_effectSlots[presetSlotIndex]->loadEffectFromPreset(effectPresets[presetSlotIndex]); } setMixMode(pChainPreset->mixMode()); From dbbb8546f3f27e8935b3ab240a0cebb3b0038268 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Fri, 31 Mar 2023 13:58:37 +0200 Subject: [PATCH 148/267] fix(effects): disable waveform filtering for eq presets loaded via CO --- src/effects/chains/equalizereffectchain.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/effects/chains/equalizereffectchain.cpp b/src/effects/chains/equalizereffectchain.cpp index 320406a8935..0629f513b39 100644 --- a/src/effects/chains/equalizereffectchain.cpp +++ b/src/effects/chains/equalizereffectchain.cpp @@ -18,6 +18,15 @@ EqualizerEffectChain::EqualizerEffectChain( addEffectSlot(formatEffectSlotGroup(handleAndGroup.name())); enableForInputChannel(handleAndGroup); m_effectSlots[0]->setEnabled(true); + + QObject::connect(this, + &EffectChain::chainPresetChanged, + this, + [this](const QString& presetname) { + Q_UNUSED(presetname); + setFilterWaveform( + m_effectSlots.at(0)->getManifest()->isMixingEQ()); + }); // DlgPrefEq loads the Effect with loadEffectToGroup setupLegacyAliasesForGroup(handleAndGroup.name()); From 6234daff2e8f6ee17144d9c52aa4cb5e86d642c3 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sun, 2 Apr 2023 00:24:12 +0200 Subject: [PATCH 149/267] WTrackTableView: Fix debug assertion in setCurrentTrackId() Invoked by onSearch() with an invalid id. --- src/widget/wtracktableview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 7dd0a9395b1..3fe112c55e8 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1060,7 +1060,7 @@ void WTrackTableView::setSelectedTracks(const QList& trackIds) { } bool WTrackTableView::setCurrentTrackId(const TrackId& trackId, int column, bool scrollToTrack) { - VERIFY_OR_DEBUG_ASSERT(trackId.isValid()) { + if (!trackId.isValid()) { return false; } From b8da147191ea6b36e90da7229463dbcd600397d3 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Sun, 2 Apr 2023 18:09:50 +0200 Subject: [PATCH 150/267] fix(controllers): explicitly overwrite JogWheelBasic input handler This should make it obvious when JogWheelBasic controls are bound to the wrong input handler. --- res/controllers/midi-components-0.0.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/controllers/midi-components-0.0.js b/res/controllers/midi-components-0.0.js index e4151dcf5e3..3c1609e3d19 100644 --- a/res/controllers/midi-components-0.0.js +++ b/res/controllers/midi-components-0.0.js @@ -784,6 +784,10 @@ engine.scratchDisable(this.deck); } }, + input: function(_channel, control, _value, status, _group) { + throw "Called wrong input handler for " + status + ": " + control + ".\n" + + "Please bind jogwheel-related messages to inputWheel and inputTouch!\n"; + } }); var EffectUnit = function(unitNumbers, allowFocusWhenParametersHidden, colors) { From dfa6d2e4cb69aa0a26476f49143ca066aab7aa37 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sun, 2 Apr 2023 23:22:36 +0200 Subject: [PATCH 151/267] Tango: remove VU peak indicators from stacked layouts, remove obsolete images --- res/skins/Tango/graphics/vumeter_clipping.png | Bin 141 -> 0 bytes res/skins/Tango/graphics/vumeter_clipping.svg | 1 - .../Tango/graphics/vumeter_clipping_over.png | Bin 157 -> 0 bytes .../Tango/graphics/vumeter_clipping_over.svg | 1 - res/skins/Tango/graphics/vumeter_level.png | Bin 155 -> 606 bytes res/skins/Tango/graphics/vumeter_level.svg | 58 ++++++++++++- .../Tango/graphics/vumeter_level_over.png | Bin 163 -> 761 bytes .../Tango/graphics/vumeter_level_over.svg | 63 ++++++++++++++- .../Tango/graphics/vumeter_mini_clipping.png | Bin 129 -> 0 bytes .../Tango/graphics/vumeter_mini_clipping.svg | 2 - .../graphics/vumeter_mini_clipping_over.png | Bin 139 -> 599 bytes .../graphics/vumeter_mini_clipping_over.svg | 2 - .../Tango/graphics/vumeter_mini_level.png | Bin 139 -> 592 bytes .../Tango/graphics/vumeter_mini_level.svg | 2 - res/skins/Tango/mic_aux_sampler/aux_unit.xml | 2 +- res/skins/Tango/mic_aux_sampler/mic_unit.xml | 2 +- res/skins/Tango/mic_aux_sampler/sampler.xml | 2 +- res/skins/Tango/mixer/vumeter_mini.xml | 6 +- res/skins/Tango/mixer/vumeter_single.xml | 76 ++++++------------ 19 files changed, 151 insertions(+), 66 deletions(-) delete mode 100644 res/skins/Tango/graphics/vumeter_clipping.png delete mode 100644 res/skins/Tango/graphics/vumeter_clipping.svg delete mode 100644 res/skins/Tango/graphics/vumeter_clipping_over.png delete mode 100644 res/skins/Tango/graphics/vumeter_clipping_over.svg delete mode 100644 res/skins/Tango/graphics/vumeter_mini_clipping.png delete mode 100644 res/skins/Tango/graphics/vumeter_mini_clipping.svg delete mode 100644 res/skins/Tango/graphics/vumeter_mini_clipping_over.svg delete mode 100644 res/skins/Tango/graphics/vumeter_mini_level.svg diff --git a/res/skins/Tango/graphics/vumeter_clipping.png b/res/skins/Tango/graphics/vumeter_clipping.png deleted file mode 100644 index 4ba2bd1485868a36227ca322df96abec4d5a13cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^96+4T!3HF4*D|vJDYhhUcNd2LAh=-f^2tCE&H|6f zVxW%eAk65bF}ngN$X?><>&pI^g_loDKzWT1P>w;$)5S5w diff --git a/res/skins/Tango/graphics/vumeter_clipping.svg b/res/skins/Tango/graphics/vumeter_clipping.svg deleted file mode 100644 index 0b3df2f8a71..00000000000 --- a/res/skins/Tango/graphics/vumeter_clipping.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/res/skins/Tango/graphics/vumeter_clipping_over.png b/res/skins/Tango/graphics/vumeter_clipping_over.png deleted file mode 100644 index c1199eb0e05838762fe20a27dc349423049181f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^96&6>!3HEZNY`WoDVAa<&kznEsNqQI0P;BtJR*x3 z82FBWFymBhK53w!WQl7;NpOBzNqJ&XDnogBxn5>oc5!lIL8@MUQTpt6Hc~)EQl2i3 wAsQ2t|D6Bu-=10SK#PA-$iWp%ue2E$9^GVC%@(M+0aV4{>FVdQ&MBb@0I!}Z$p8QV diff --git a/res/skins/Tango/graphics/vumeter_clipping_over.svg b/res/skins/Tango/graphics/vumeter_clipping_over.svg deleted file mode 100644 index 0101bd4a2ef..00000000000 --- a/res/skins/Tango/graphics/vumeter_clipping_over.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/res/skins/Tango/graphics/vumeter_level.png b/res/skins/Tango/graphics/vumeter_level.png index 4a610422dbbb9d5c901562ea85dc869b0e75823f..d6fe2361f82bd4fd5434ac9926088945b07d5eda 100644 GIT binary patch literal 606 zcmV-k0-^nhP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+MQHea_b-r{nskG1SDY$%W+~eb9Zo;-y;Dj&bVnC zDZC{4=tZF8|G!EfMmYtF%bHV3DS%>$MK)}5Xk_~vhG6VXlv5bQq4?}XAwI+>MOwR9 z&hoMH_5>8WcfOq!LoWFVNZO+20DJC%Q0W_1h%Y>+l2?zqpSSLyrV;rPQ(hNJdlCt% za%jfr-qeQbhSg?OWT*_@AYXkpU(0Dt487`%r!^Hi#=euYoMA_Ec(v}i*2690785RL>Njj8 z&)p8!G^*z!jmb;d4%#^)wfNwSQwj@4!F~hVTEJVlhl;V`_8)}Fgge69!x5^vXB^Co z1sxZ*JJ#f1vGBvGy0d8Q&m0`?){Ng{`mJHDck#03-g=9L#o8i}GT~lfff>5M%T(I$ zDR=8Q+tPQ|sD?cvcfVui6&)JgF3Xo4VsKdE$l<3Lbin2w1EY(w)Dc-r?IpaCu}d{CrWs1r=HeekN^Mx1am@3R0s$N2z&@+hyVZpAW1|)R5;6HWME+U s&wvjwj;a_{F{)xz#i)u=6+=Y@0MZpS0iOa>G5`Po07*qoM6N<$f)wx)@&Et; literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^96+4T!3HF4*D|vJDYhhUcNd2LAh=-f^2tCE&H|6f zVxW%eAk65bF}ngN$X?><>&pI^g_loLw#*=TB~VDi)5S5w!^psW%sg1NAU?y85}Sb4q9e0G_8OssI20 diff --git a/res/skins/Tango/graphics/vumeter_level.svg b/res/skins/Tango/graphics/vumeter_level.svg index 1e59fd29507..07fdc7ed8e9 100644 --- a/res/skins/Tango/graphics/vumeter_level.svg +++ b/res/skins/Tango/graphics/vumeter_level.svg @@ -1 +1,57 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/skins/Tango/graphics/vumeter_level_over.png b/res/skins/Tango/graphics/vumeter_level_over.png index 389574ba82127be89a9842acf1ac8c1417b24c25..c00ea67de7670aa428a885c3b55091c1fe5cbf87 100644 GIT binary patch literal 761 zcmV zaB^>EX>4U6ba`-PAZ2)IW&i+q+MQHumYyIC{m&}81d{Mv4ho#p9d!A<0rXR6=9_7Q z2!wd^*0evryZNP*6C3Aa3ONJ|a@l1r*d#ki_Zuc-*2zl29z-^e_aI~4$-Z5LxY&H> zA1jpykXzbmyI&l9P7ffSi>LzZX&GYj+K_<#iRO?~99FM$SqgeMEcY&?=uN1B*@|$J zgt@edPLYCD-y-l(2%~@>@OGlW0}R^ZB}U^@#x-Tqi%aZ)u_AxrivsHff~8pI>5Aof zah?xAx$}=?9At)hf@`^a96ry?yTq7xnHg?f0`1(|@tvP`%jRlc?=|(_hJ-6Fpg_Z_ zu)}#;awtpFBrOJjAD1&F$=1{8N8HPiO>Yic2!R} zs2DLa&ZIRq`7c}OR4PN6)AT2@18$qb|2ND}1$*DMAxIlK%zA=dtZ89z$)K*VKnz)+ zBeZ1S5#qJv(~=bh`kFN8(R##1e<%B>Hr0<0k6%Cz!a3Kplc{Ylvv_$=rFqwvwn+B$ z>q{a>eyTF#H%6;Qyyfab;p)rgLwPN!Z+0ozXadK(f~RY!MDlDs6{@tHN+a1?1s%>v z=LA(P-GR*$`2(;_;6&YCfcXWymF|2sMHX@+yV`d;`6;~k;+iX7drc%u^VLmwb4Dlt zpCj!#qRRKkzB?BvgpHEVW&dt#u51_|bK6_#)x{%wj_fye1vh#(r`N+JM3Q{EQ0?8? zXC;kqj~~KTd&FaEmqT|TdWn2RjQ;(YP4rqwjm{FJGu&_T_<&mZL(`Uh3-XT@Vg?3oArNM~bhqvgP>{XE)7O>#F$*uB zCVy+E&_ke*u&0Y-h{WaOh^<|W93AS;&hDNJr?0EuOue@-5-82!>FVdQ&MBb@0Dv1S A7ytkO diff --git a/res/skins/Tango/graphics/vumeter_level_over.svg b/res/skins/Tango/graphics/vumeter_level_over.svg index 8cbd579f8cd..ba73becfe31 100644 --- a/res/skins/Tango/graphics/vumeter_level_over.svg +++ b/res/skins/Tango/graphics/vumeter_level_over.svg @@ -1 +1,62 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/skins/Tango/graphics/vumeter_mini_clipping.png b/res/skins/Tango/graphics/vumeter_mini_clipping.png deleted file mode 100644 index b60c3eaba55fcc0b0c06ddba684d2a83308d2703..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^EI=&7!3HGVO5{3#6kC$Fy9>jA5L~c#`DCC7XMsm# zF;K^K5N34Jm|X!BWH0gbb!C6ZDJ;&f@cVsXDNu;d)5S4F;&O6=1nXf@1_s7E42-5f SX72*ZFnGH9xvX - diff --git a/res/skins/Tango/graphics/vumeter_mini_clipping_over.png b/res/skins/Tango/graphics/vumeter_mini_clipping_over.png index 12d266662161adf7733bd124be742311a3e7e2d9..d91f4909125f327f071955fcabf5eb2f42d35eb3 100644 GIT binary patch literal 599 zcmV-d0;v6oP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+MQHec7z}d{bvGX@oKs1OK(WLk6-=g$jK5(D=CX-$qCrf>SEn!-><0z+4_S`# zhrXVGVzqhiP<*nvuCSRuZ!oJyW4ew}MK(0Yhzo?^=L6w;DJNL5zN znBAJ%RIS);RYio#7{(LUNe$e=V12e6b&vd0QDmyHDTdqw(4P67Tn*S1NS0xRb0{R6 z;+tHL@==UI!QE4+b@x%auFSK-glCoUuy%=#v2${iqsJud-D_>ht*w3|)|k+ssdv~2 zlKVZhX;hCzwh=s)+d=z?2rWKF#wpQ+S-@8e+Y3geJ2lLT?q35I6E4hfaHVGE83((@ zf{F`Uk8ScVU-)5E-B`5pXC{ZcZI}Ny!mAfsyW4|cd(d8b^NGdEB9L-GUtz;FRDq{m zYJaAw-%bxJ)XRTKGogZu{YdWKXJ#2y8vU3_R<4@is>243OuzXc)FE@X�nshX8_y z_LO)JVSceTn|p}yL|%cA3y4!3;e|jFAohV=rXoDgg*Mc-os;fQWB5HUZXz5b{Y$RE z>~aJO#lD@19L)2R0s$N+u={(0000INkljA5L~c#`DCC7XMsm# zF;K^K5N34Jm|X!BWH0gbb!C6ZDJ;&<9Uk0a3KWv?ba4!kxSagw{D=Sc%t8rC4$O<0 coI@EH_N-<${inb2KTr*Wr>mdKI;Vst0DA`{NdN!< diff --git a/res/skins/Tango/graphics/vumeter_mini_clipping_over.svg b/res/skins/Tango/graphics/vumeter_mini_clipping_over.svg deleted file mode 100644 index baefa0392b8..00000000000 --- a/res/skins/Tango/graphics/vumeter_mini_clipping_over.svg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/res/skins/Tango/graphics/vumeter_mini_level.png b/res/skins/Tango/graphics/vumeter_mini_level.png index d8bca475bf1b197c28e02ece6be654ce41c3ce43..c02aa50401d9c02ab8c3d7032ee555c512c19922 100644 GIT binary patch delta 580 zcmV-K0=xZ-0nh}H7k?cH0ssI20!mtX0005zdQ@0+Qek%>aB^>EX>4U6ba`-PAZ2)I zW&i+q+MQHua)clZ{-0Cy2qZy-SRf0DG>EP^ueNhzFKKigWS0o|kGM*AT-z`4H!c!ZV0CRoOIxt2H%NU9j4$X629w zX4ctIOdHncE=NT{6q!nF7hUdvQ8VAkp^qT6B+Ibq8j6$s;y5=zJIm_{_Q14#O&zSR8H2mTN8Z^`nYr%8XLz{XnvB*BCq;fxK z9Uh_iV?>;So4^VC1;f^avAES`@Ph8&2$Kn)%yCG1wD~mwNgucQ8HGforr!KYcDOPRY>(V=IWW#w`UpY>5SKvdlcF0$3Do|Czs^5s zvkX!r+&usQ00VPENmK|32;1RL-~a#s7D+@wR2b7^6c-m~fB_~3BrwXMDF*&YK SN-`b*0000(#<$QyhMhBje&u|twgQ^NUEak7aXDE;T>Ss}29_BT7Y?jA5FpCH(EWw6 UX5Vf57@!&kPgg&ebxsLQ0NH6HNdN!< diff --git a/res/skins/Tango/graphics/vumeter_mini_level.svg b/res/skins/Tango/graphics/vumeter_mini_level.svg deleted file mode 100644 index a33a4a23de5..00000000000 --- a/res/skins/Tango/graphics/vumeter_mini_level.svg +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/res/skins/Tango/mic_aux_sampler/aux_unit.xml b/res/skins/Tango/mic_aux_sampler/aux_unit.xml index ff149326f8d..97a165506ac 100644 --- a/res/skins/Tango/mic_aux_sampler/aux_unit.xml +++ b/res/skins/Tango/mic_aux_sampler/aux_unit.xml @@ -44,7 +44,7 @@ Variables: 3f,1min 1f,1min diff --git a/res/skins/Tango/mic_aux_sampler/mic_unit.xml b/res/skins/Tango/mic_aux_sampler/mic_unit.xml index 8ac8ae1c76f..08e35ed8d61 100644 --- a/res/skins/Tango/mic_aux_sampler/mic_unit.xml +++ b/res/skins/Tango/mic_aux_sampler/mic_unit.xml @@ -48,7 +48,7 @@ Variables: 1f,1min diff --git a/res/skins/Tango/mic_aux_sampler/sampler.xml b/res/skins/Tango/mic_aux_sampler/sampler.xml index 315cda6dccf..9d13a501db8 100644 --- a/res/skins/Tango/mic_aux_sampler/sampler.xml +++ b/res/skins/Tango/mic_aux_sampler/sampler.xml @@ -226,7 +226,7 @@ Variables: 1f,0min diff --git a/res/skins/Tango/mixer/vumeter_mini.xml b/res/skins/Tango/mixer/vumeter_mini.xml index f3fa01e310d..b9812f87e4b 100644 --- a/res/skins/Tango/mixer/vumeter_mini.xml +++ b/res/skins/Tango/mixer/vumeter_mini.xml @@ -10,10 +10,11 @@ Refer to https://bugs.launchpad.net/mixxx/+bug/1759433 -->