From 7b34673a4d2438e041094c5b2675818d4ae11b53 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 21 Sep 2021 06:42:44 +0200 Subject: [PATCH 001/201] fix(ddm4000): handle mappings without key properly --- res/controllers/Behringer-DDM4000-scripts.js | 96 +++++++++---------- .../Behringer-Extension-scripts.js | 21 ++-- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/res/controllers/Behringer-DDM4000-scripts.js b/res/controllers/Behringer-DDM4000-scripts.js index e624e9b8fc56..eaf9ff489cfe 100644 --- a/res/controllers/Behringer-DDM4000-scripts.js +++ b/res/controllers/Behringer-DDM4000-scripts.js @@ -251,9 +251,9 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x3F], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x03], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x38], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x37], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x03], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x38], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x37], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing @@ -281,9 +281,9 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x49], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x07], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x42], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x41], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x07], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x42], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x41], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing @@ -311,9 +311,9 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x53], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x0B], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x4C], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x4B], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x0B], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x4C], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x4B], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing @@ -341,9 +341,9 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: [note, 0x5D], key: "pfl", type: toggle, sendShifted: true } }, - {type: c.Button, options: {midi: [note, 0x0F], inKey: ""}}, // Mode - {type: c.Button, options: {midi: [cc, 0x56], outKey: ""}}, // Mode: Multi - {type: c.Button, options: {midi: [cc, 0x55], outKey: ""}}, // Mode: Single + {type: c.Button, options: {midi: [note, 0x0F], inKey: null}}, // Mode + {type: c.Button, options: {midi: [cc, 0x56], outKey: null}}, // Mode: Multi + {type: c.Button, options: {midi: [cc, 0x55], outKey: null}}, // Mode: Single ], equalizerUnit: { // P3 / Low, P2 / Mid, P1 / High midi: { // eslint-disable-next-line key-spacing @@ -361,14 +361,14 @@ var DDM4000 = new behringer.extension.GenericMidiController({ { // Microphone defaultDefinition: {type: c.Button, options: {group: "[Microphone]"}}, components: [ - {options: {midi: [cc, 0x02], inKey: ""}, type: c.Pot}, // Mic: EQ Low - {options: {midi: [cc, 0x01], inKey: ""}, type: c.Pot}, // Mic: EQ Mid - {options: {midi: [cc, 0x00], inKey: ""}, type: c.Pot}, // Mic: EQ High - {options: {midi: [note, 0x31], key: "", sendShifted: true}}, // Mic: Setup - {options: {midi: [note, 0x32], key: "", sendShifted: true}}, // Mic: XMC On - {options: {midi: [note, 0x33], key: "", sendShifted: true}}, // Mic: FX On + {options: {midi: [cc, 0x02], inKey: null}, type: c.Pot}, // Mic: EQ Low + {options: {midi: [cc, 0x01], inKey: null}, type: c.Pot}, // Mic: EQ Mid + {options: {midi: [cc, 0x00], inKey: null}, type: c.Pot}, // Mic: EQ High + {options: {midi: [note, 0x31], key: null, sendShifted: true}}, // Mic: Setup + {options: {midi: [note, 0x32], key: null, sendShifted: true}}, // Mic: XMC On + {options: {midi: [note, 0x33], key: null, sendShifted: true}}, // Mic: FX On {options: {midi: [note, 0x34], key: "talkover", sendShifted: true}}, // Mic: Talk On - {options: {midi: [note, 0x35], key: "", sendShifted: true}}, // Mic: On/Off + {options: {midi: [note, 0x35], key: null, sendShifted: true}}, // Mic: On/Off ] }, { // Crossfader @@ -382,14 +382,14 @@ var DDM4000 = new behringer.extension.GenericMidiController({ { // Crossfader defaultDefinition: {type: c.Button, options: {group: "[Master]"}}, components: [ - {options: {midi: [note, 0x17], key: "", sendShifted: true}}, // Crossfader: A Full Freq - {options: {midi: [note, 0x18], key: "", sendShifted: true}}, // Crossfader: A High - {options: {midi: [note, 0x19], key: "", sendShifted: true}}, // Crossfader: A Mid - {options: {midi: [note, 0x1A], key: "", sendShifted: true}}, // Crossfader: A Low - {options: {midi: [note, 0x1B], key: "", sendShifted: true}}, // Crossfader: B Full Freq - {options: {midi: [note, 0x1C], key: "", sendShifted: true}}, // Crossfader: B High - {options: {midi: [note, 0x1D], key: "", sendShifted: true}}, // Crossfader: B Mid - {options: {midi: [note, 0x1E], key: "", sendShifted: true}}, // Crossfader: B Low + {options: {midi: [note, 0x17], key: null, sendShifted: true}}, // Crossfader: A Full Freq + {options: {midi: [note, 0x18], key: null, sendShifted: true}}, // Crossfader: A High + {options: {midi: [note, 0x19], key: null, sendShifted: true}}, // Crossfader: A Mid + {options: {midi: [note, 0x1A], key: null, sendShifted: true}}, // Crossfader: A Low + {options: {midi: [note, 0x1B], key: null, sendShifted: true}}, // Crossfader: B Full Freq + {options: {midi: [note, 0x1C], key: null, sendShifted: true}}, // Crossfader: B High + {options: {midi: [note, 0x1D], key: null, sendShifted: true}}, // Crossfader: B Mid + {options: {midi: [note, 0x1E], key: null, sendShifted: true}}, // Crossfader: B Low { // Crossfader: On type: CrossfaderUnit, options: { @@ -397,29 +397,29 @@ var DDM4000 = new behringer.extension.GenericMidiController({ crossfader: {midi: [cc, 0x15]} }, }, - {options: {midi: [note, 0x2A], key: "", sendShifted: true}}, // Crossfader: Bounce to MIDI Clock - {options: {midi: [note, 0x2B], inKey: ""}}, // Crossfader: Beat (Left) - {options: {midi: [note, 0x2C], inKey: ""}}, // Crossfader: Beat (Right) - {options: {midi: [cc, 0x2B], outKey: ""}}, // Crossfader: Beat 1 - {options: {midi: [cc, 0x2C], outKey: ""}}, // Crossfader: Beat 2 - {options: {midi: [cc, 0x2D], outKey: ""}}, // Crossfader: Beat 4 - {options: {midi: [cc, 0x2E], outKey: ""}}, // Crossfader: Beat 8 - {options: {midi: [cc, 0x2F], outKey: ""}}, // Crossfader: Beat 16 + {options: {midi: [note, 0x2A], key: null, sendShifted: true}}, // Crossfader: Bounce to MIDI Clock + {options: {midi: [note, 0x2B], inKey: null}}, // Crossfader: Beat (Left) + {options: {midi: [note, 0x2C], inKey: null}}, // Crossfader: Beat (Right) + {options: {midi: [cc, 0x2B], outKey: null}}, // Crossfader: Beat 1 + {options: {midi: [cc, 0x2C], outKey: null}}, // Crossfader: Beat 2 + {options: {midi: [cc, 0x2D], outKey: null}}, // Crossfader: Beat 4 + {options: {midi: [cc, 0x2E], outKey: null}}, // Crossfader: Beat 8 + {options: {midi: [cc, 0x2F], outKey: null}}, // Crossfader: Beat 16 ] }, { // Sampler defaultDefinition: {type: c.Button, options: {group: "[Sampler1]"}}, components: [ {options: {midi: [cc, 0x03], inKey: "volume"}, type: c.Pot}, // Sampler: Volume/Mix - {options: {midi: [note, 0x5F], key: "", sendShifted: true}}, // Sampler: Insert - {options: {midi: [note, 0x60], inKey: ""}}, // Sampler: REC Source (Right) - {options: {midi: [note, 0x61], inKey: ""}}, // Sampler: REC Source (Left) - {options: {midi: [cc, 0x60], outKey: ""}}, // Sampler: REC Source 1 - {options: {midi: [cc, 0x61], outKey: ""}}, // Sampler: REC Source 2 - {options: {midi: [cc, 0x62], outKey: ""}}, // Sampler: REC Source 3 - {options: {midi: [cc, 0x63], outKey: ""}}, // Sampler: REC Source 4 - {options: {midi: [cc, 0x64], outKey: ""}}, // Sampler: REC Source Microphone - {options: {midi: [cc, 0x65], outKey: ""}}, // Sampler: REC Source Master + {options: {midi: [note, 0x5F], key: null, sendShifted: true}}, // Sampler: Insert + {options: {midi: [note, 0x60], inKey: null}}, // Sampler: REC Source (Right) + {options: {midi: [note, 0x61], inKey: null}}, // Sampler: REC Source (Left) + {options: {midi: [cc, 0x60], outKey: null}}, // Sampler: REC Source 1 + {options: {midi: [cc, 0x61], outKey: null}}, // Sampler: REC Source 2 + {options: {midi: [cc, 0x62], outKey: null}}, // Sampler: REC Source 3 + {options: {midi: [cc, 0x63], outKey: null}}, // Sampler: REC Source 4 + {options: {midi: [cc, 0x64], outKey: null}}, // Sampler: REC Source Microphone + {options: {midi: [cc, 0x65], outKey: null}}, // Sampler: REC Source Master {options: {midi: [note, 0x66], key: "pfl", sendShifted: true}}, // Sampler: PFL {options: {midi: [note, 0x67], inKey: "beatloop_size", values: [1, 2, 4, 8, 16, 256]}, type: e.EnumToggleButton}, // Sampler: Sample Length (Right) {options: {midi: [note, 0x68], inKey: "beatloop_size", values: [256, 16, 8, 4, 2, 1]}, type: e.EnumToggleButton}, // Sampler: Sample Length (Left) @@ -429,8 +429,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ {options: {midi: [cc, 0x6A], outKey: "beatloop_size", onValue: 8}, type: e.CustomButton}, // Sampler: Sample Length 8 {options: {midi: [cc, 0x6B], outKey: "beatloop_size", onValue: 16}, type: e.CustomButton}, // Sampler: Sample Length 16 {options: {midi: [cc, 0x6C], outKey: "beatloop_size", onValue: 256}, type: e.CustomButton}, // Sampler: Sample Length ∞ - {options: {midi: [note, 0x6D], key: "", sendShifted: true}}, // Sampler: Record / In - {options: {midi: [note, 0x6C], inKey: ""}}, // Sampler: Bank Assign + {options: {midi: [note, 0x6D], key: null, sendShifted: true}}, // Sampler: Record / In + {options: {midi: [note, 0x6C], inKey: null}}, // Sampler: Bank Assign { // Sampler Bank 1 type: SamplerBank, options: { @@ -465,14 +465,14 @@ var DDM4000 = new behringer.extension.GenericMidiController({ blinkDuration: DEFAULT_BLINK_DURATION, } }, - {options: {midi: [note, 0x79], key: "", sendShifted: true}}, // Sampler: Select + {options: {midi: [note, 0x79], key: null, sendShifted: true}}, // Sampler: Select { // Sampler: CF Assign type: e.EnumToggleButton, options: {midi: [note, 0x7A], inKey: "orientation", values: [center, left, right]}, }, {type: CrossfaderAssignLED, options: {midi: [cc, 0x7A], onValue: left}}, // Sampler: CF Assign A {type: CrossfaderAssignLED, options: {midi: [cc, 0x7B], onValue: right}}, // Sampler: CF Assign B - {options: {midi: [note, 0x7C], key: "", sendShifted: true}}, // Sampler: CF Start + {options: {midi: [note, 0x7C], key: null, sendShifted: true}}, // Sampler: CF Start ] } ], diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index d2050ff18ba5..6439006f64bc 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -55,14 +55,18 @@ * @private */ var stringifyComponent = function(component) { - if (component === undefined) { + if (!component) { return; } - var id = findComponentId(component.midi); - if (id !== undefined) { - var key = component.inKey || component.outKey; - return "(" + id + ": " + component.group + "," + key + ")"; + var key = component.inKey || component.outKey; + var value = component.group + "," + key; + if (component.midi) { + var id = findComponentId(component.midi); + if (id !== undefined) { + value = id + ": " + value; + } } + return "(" + value + ")"; }; /** @@ -759,6 +763,11 @@ log.error("Missing component"); return; } + if (!component.midi) { + log.debug(containerName + ": ignore " + + stringifyComponent(component) + " without MIDI address"); + return; + } var id = findComponentId(component.midi); if (!Object.prototype.hasOwnProperty.call(this.containers, containerName)) { this.createContainer(containerName); @@ -1254,7 +1263,7 @@ * @private */ processMidiAddresses: function(definition, implementation, action) { - if (Array.isArray(definition)) { + if (Array.isArray(definition) || !definition) { action.call(this, definition, implementation); } else if (typeof definition === "object") { Object.keys(definition).forEach(function(name) { From 9ff3bbd6693d6daf981d4f6c51900a02707e5c99 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 21 Sep 2021 06:48:23 +0200 Subject: [PATCH 002/201] style(ddm4000): fix indentation --- res/controllers/Behringer-Extension-scripts.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index 6439006f64bc..cf3299f4b4d5 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -1118,9 +1118,9 @@ } /* - * Contains all decks and effect units so that a (un)shift operation - * is delegated to the decks, effect units and their children. - */ + * Contains all decks and effect units so that a (un)shift operation + * is delegated to the decks, effect units and their children. + */ this.componentContainers = []; this.layerManager = this.createLayerManager( From 425ec6fe3210988683e1ee7eaee62187d5ffeb3b Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 21 Sep 2021 06:58:40 +0200 Subject: [PATCH 003/201] style(ddm4000): explicitely list unmapped controls --- res/controllers/Behringer-DDM4000-scripts.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/res/controllers/Behringer-DDM4000-scripts.js b/res/controllers/Behringer-DDM4000-scripts.js index eaf9ff489cfe..c3bc55d2dbd4 100644 --- a/res/controllers/Behringer-DDM4000-scripts.js +++ b/res/controllers/Behringer-DDM4000-scripts.js @@ -259,6 +259,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x06], 2: [cc, 0x05], 3: [cc, 0x04]}, parameterButtons: {1: [note, 0x02], 2: [note, 0x01], 3: [note, 0x00]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x3D], 2: [cc, 0x3B], 3: [cc, 0x39]}, // Amber @@ -289,6 +291,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x0A], 2: [cc, 0x09], 3: [cc, 0x08]}, parameterButtons: {1: [note, 0x06], 2: [note, 0x05], 3: [note, 0x04]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x47], 2: [cc, 0x45], 3: [cc, 0x43]}, // Amber @@ -319,6 +323,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x0E], 2: [cc, 0x0D], 3: [cc, 0x0C]}, parameterButtons: {1: [note, 0x0A], 2: [note, 0x09], 3: [note, 0x08]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x51], 2: [cc, 0x4F], 3: [cc, 0x4D]}, // Amber @@ -349,6 +355,8 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi: { // eslint-disable-next-line key-spacing parameterKnobs: {1: [cc, 0x12], 2: [cc, 0x11], 3: [cc, 0x10]}, parameterButtons: {1: [note, 0x0E], 2: [note, 0x0D], 3: [note, 0x0C]}, + enabled: null, + super1: null, }, output: { parameterButtons: {1: [cc, 0x5B], 2: [cc, 0x59], 3: [cc, 0x57]}, // Amber @@ -468,7 +476,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ {options: {midi: [note, 0x79], key: null, sendShifted: true}}, // Sampler: Select { // Sampler: CF Assign type: e.EnumToggleButton, - options: {midi: [note, 0x7A], inKey: "orientation", values: [center, left, right]}, + options: {midi: [note, 0x7A], inKey: "orientation", values: [center, left, right]}, }, {type: CrossfaderAssignLED, options: {midi: [cc, 0x7A], onValue: left}}, // Sampler: CF Assign A {type: CrossfaderAssignLED, options: {midi: [cc, 0x7B], onValue: right}}, // Sampler: CF Assign B From 9b371788db2d65d9b57c3b35a7cfc3ff728fe829 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 24 Sep 2021 22:04:25 +0200 Subject: [PATCH 004/201] fix(ddm4000): allow completion of controller init by throttling midi messages --- res/controllers/Behringer-DDM4000-scripts.js | 1 + .../Behringer-Extension-scripts.js | 82 ++++++++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/res/controllers/Behringer-DDM4000-scripts.js b/res/controllers/Behringer-DDM4000-scripts.js index c3bc55d2dbd4..0f76091f1c4b 100644 --- a/res/controllers/Behringer-DDM4000-scripts.js +++ b/res/controllers/Behringer-DDM4000-scripts.js @@ -225,6 +225,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ SamplerBank.prototype = e.deriveFrom(c.ComponentContainer); return { + throttleDelay: 40, init: function() { /* diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index cf3299f4b4d5..b8a8cc1f5ae1 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -99,6 +99,22 @@ return _.merge(Object.create(parent.prototype), members || {}); }; + /** + * Perform an action, throttled if the owner supports throttling. + * + * @param {function} action The action to perform + * @param {object} owner Object used as `this` for the action + * @private + * @see `Throttler` + */ + var throttle = function(action, owner) { + if (owner.throttler) { + owner.throttler.schedule(action, owner); + } else { + action.call(owner); + } + }; + /** * A button to toggle un-/shift on a target component. * @@ -225,6 +241,53 @@ } }; + /** + * An object that enforces a constant delay between the execution of consecutive actions. + * + * Use `schedule(action, owner)` to perform an action on the owner as soon as the delay has + * elapsed after the preceding action has finished. + * + * @constructor + * @param {number} options.delay Minimal delay between two consecutive actions (in ms) + * @public + */ + var Throttler = function(options) { + options = options || {}; + options.delay = options.delay || 0; + _.assign(this, options); + this.locked = false; + this.jobs = []; + this.unlockTimer = new Timer( + {timeout: this.delay, oneShot: true, action: this.unlock, owner: this}); + }; + Throttler.prototype = { + schedule: function(action, owner) { + this.jobs.push({action: action, owner: owner}); + this.notify(); + }, + + notify: function() { + if (this.jobs.length > 0 && this.acquireLock()) { + var job = this.jobs.shift(); + job.action.call(job.owner); + this.unlockTimer.start(); + } + }, + + acquireLock: function() { + var unlocked = !this.locked; + if (unlocked) { + this.locked = true; + } + return unlocked; + }, + + unlock: function() { + this.locked = false; + this.notify(); + } + }; + /** * A button that supports different actions on short and long press. * @@ -1029,6 +1092,11 @@ * +- init: (optional) A function that is called when Mixxx is started * +- shutdown: (optional) A function that is called when Mixxx is shutting down * | + * +- throttleDelay (optional): A positive number (in ms) that is used to slow down the + * | initialization of the controller; this option is useful if + * | the hardware is limited to process a certain number of MIDI + * | messages per time. + * | * +- decks: An array of deck definitions (may be empty or omitted) * | +- deck: * | +- deckNumbers: As defined by {components.Deck} @@ -1113,6 +1181,12 @@ this.controllerId = controllerId; this.debug = debug; + var delay = this.config.throttleDelay; + if (delay > 0) { + log.debug("Component registration is throttled using a delay of " + delay + "ms"); + this.throttler = new Throttler({delay: delay}); + } + if (typeof this.config.init === "function") { this.config.init(controllerId, debug); } @@ -1210,9 +1284,11 @@ }].forEach(function(context) { if (Array.isArray(context.definitions)) { context.definitions.forEach(function(definition) { - var implementation = context.factory.call(this, definition, target); - target.push(implementation); - context.register(definition, implementation); + throttle(function() { + var implementation = context.factory.call(this, definition, target); + target.push(implementation); + context.register(definition, implementation); + }, this); }, this); } else { log.error(this.controllerId + ": Skipping a part of the configuration because " From 79804b543bb62153379bdcdfb1cc348f9806c887 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 24 Sep 2021 22:06:32 +0200 Subject: [PATCH 005/201] refactor(ddm4000): extract reusable ParameterComponent --- .../Behringer-Extension-scripts.js | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index b8a8cc1f5ae1..bb57e7468d5d 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -115,6 +115,27 @@ } }; + /** + * A component that uses the parameter instead of the value as output. + * + * @constructor + * @extends {components.Component} + * @param {object} options Options object + * @public + */ + var ParameterComponent = function(options) { + components.Component.call(this, options); + }; + ParameterComponent.prototype = deriveFrom(components.Component, { + outValueScale: function(_value) { + /* + * We ignore the argument and use the parameter (0..1) instead because value scale is + * arbitrary and thus cannot be mapped to MIDI values (0..127) properly. + */ + return convertToMidiValue.call(this, this.outGetParameter()); + }, + }); + /** * A button to toggle un-/shift on a target component. * @@ -647,16 +668,9 @@ } this.source = options.source; this.sync(); - components.Component.call(this, options); + ParameterComponent.call(this, options); }; - Publisher.prototype = deriveFrom(components.Component, { - outValueScale: function(_value) { - /* - * We ignore the argument and use the parameter (0..1) instead because value scale is - * arbitrary and thus cannot be mapped to MIDI values (0..127) properly. - */ - return convertToMidiValue.call(this, this.outGetParameter()); - }, + Publisher.prototype = deriveFrom(ParameterComponent, { sync: function() { this.midi = this.source.midi; this.group = this.source.group; @@ -1517,6 +1531,7 @@ var exports = {}; exports.deriveFrom = deriveFrom; + exports.ParameterComponent = ParameterComponent; exports.ShiftButton = ShiftButton; exports.Trigger = Trigger; exports.CustomButton = CustomButton; From a3072302875ddbed3fcfcdee6afe715ad66149d8 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 24 Sep 2021 22:08:49 +0200 Subject: [PATCH 006/201] refactor(ddm4000): remove duplicate code by reusing component creation --- res/controllers/Behringer-Extension-scripts.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index bb57e7468d5d..ba13126174e5 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -1325,14 +1325,9 @@ createDeck: function(deckDefinition, componentStorage) { var deck = new components.Deck(deckDefinition.deckNumbers); deckDefinition.components.forEach(function(componentDefinition, index) { - if (componentDefinition && componentDefinition.type) { - var options = _.merge({group: deck.currentDeck}, componentDefinition.options); - deck[index] = new componentDefinition.type(options); - } else { - log.error("Skipping component without type on Deck of " + deck.currentDeck - + ": " + stringifyObject(componentDefinition)); - deck[index] = null; - } + var options = _.merge({group: deck.currentDeck}, componentDefinition.options); + var definition = _.merge(componentDefinition, {options: options}); + deck[index] = this.createComponent(definition); }, this); if (deckDefinition.equalizerUnit) { deck.equalizerUnit = this.setupMidi( From f804e3c4c2b280372b42443adce414dc8f494707 Mon Sep 17 00:00:00 2001 From: Christian Date: Fri, 24 Sep 2021 22:09:15 +0200 Subject: [PATCH 007/201] docs(ddm4000): add duration unit --- res/controllers/Behringer-Extension-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index ba13126174e5..f5ad2d140e11 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -222,7 +222,7 @@ * Use `start(action)` to start and `reset()` to reset. * * @constructor - * @param {number} options.timeout Duration between start and action + * @param {number} options.timeout Duration between start and action (in ms) * @param {boolean} options.oneShot If `true`, the action is run once; * otherwise, it is run periodically until the timer is reset. * @param {function} options.action Function that is executed whenever the timer expires From c856d1eb97322534062ef6eaf73541b9662c91e3 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 27 Sep 2021 06:04:55 +0200 Subject: [PATCH 008/201] docs(ddm4000): fix docs for EqualizerUnit.output --- res/controllers/Behringer-Extension-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index f5ad2d140e11..5b57d795aaa6 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -1136,7 +1136,7 @@ * | | component. * | +- output: Additional output definitions (optional). * | The structure of this object is the same as the structure of - * | `components`. Every value change of a component contained in `output` + * | `midi`. Every value change of a component contained in `output` * | causes a MIDI message to be sent to the hardware controller, using * | the configured address instead of the component's `midi` property. * | This option is independent of the `feedback` option. From a7ea384c9f52a9d41792757f0d4c157edc7a8cff Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 27 Sep 2021 07:26:31 +0200 Subject: [PATCH 009/201] docs(ddm4000): fix docs for LoopMoveEncoder --- res/controllers/Behringer-Extension-scripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index 5b57d795aaa6..28b837ead995 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -558,7 +558,7 @@ * `sizeControl` being preferred. * * @constructor - * @extends {components.Encoder} + * @extends {DirectionEncoder} * @param {object} options Options object * @param {number} options.size (optional) Size given in number of beats; default: 0.5 * @param {string} options.sizeControl (optional) Name of a control that contains `size` From e02d36297c397a4b5bd3d4012ffb4d171e02ea9c Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 29 Sep 2021 07:58:34 +0200 Subject: [PATCH 010/201] feat(components): add soft-takeover for EnumEncoder --- res/controllers/Behringer-Extension-scripts.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/res/controllers/Behringer-Extension-scripts.js b/res/controllers/Behringer-Extension-scripts.js index 28b837ead995..dda62a9174a4 100644 --- a/res/controllers/Behringer-Extension-scripts.js +++ b/res/controllers/Behringer-Extension-scripts.js @@ -503,7 +503,9 @@ * @extends {components.Encoder} * @param {object} options Options object * @param {Array} options.values An array containing the enumeration values + * @param {boolean} options.softTakeover (optional) Enable soft-takeover; default: `true` * @public + * @see https://github.com/mixxxdj/mixxx/wiki/Midi-Scripting#soft-takeover */ var EnumEncoder = function(options) { options = options || {}; @@ -511,10 +513,22 @@ log.error("EnumEncoder constructor was called without specifying enum values."); options.values = []; } + if (options.softTakeover === undefined) { // do not use '||' to allow false + options.softTakeover = true; + } options.maxIndex = options.values.length - 1; components.Encoder.call(this, options); }; EnumEncoder.prototype = deriveFrom(components.Encoder, { + input: function(_channel, _control, value, _status, _group) { + var scaledValue = this.inValueScale(value); + if (!this.softTakeover + || this.previousValue === undefined + || this.previousValue === this.inGetValue()) { + this.inSetParameter(scaledValue); + } + this.previousValue = scaledValue; + }, inValueScale: function(value) { var normalizedValue = value / this.max; var index = Math.round(normalizedValue * this.maxIndex); From 04350923fe3b8078bd21f5e9d5b6557bd2b537fb Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 29 Sep 2021 08:08:12 +0200 Subject: [PATCH 011/201] style(ddm4000): move throttle delay to top --- res/controllers/Behringer-DDM4000-scripts.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/res/controllers/Behringer-DDM4000-scripts.js b/res/controllers/Behringer-DDM4000-scripts.js index 0f76091f1c4b..b8c89b2ab97f 100644 --- a/res/controllers/Behringer-DDM4000-scripts.js +++ b/res/controllers/Behringer-DDM4000-scripts.js @@ -10,6 +10,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ var DEFAULT_LONGPRESS_DURATION = 500; var DEFAULT_BLINK_DURATION = 425; + var THROTTLE_DELAY = 40; /* Shortcut variables */ var c = components; @@ -201,6 +202,7 @@ var DDM4000 = new behringer.extension.GenericMidiController({ var ModeButton = function(options) { options = options || {}; options.key = options.key || "mode"; + options.longPressTimeout = options.longPressTimeout || DEFAULT_LONGPRESS_DURATION; e.LongPressButton.call(this, options); }; ModeButton.prototype = e.deriveFrom(e.LongPressButton, { @@ -219,13 +221,12 @@ var DDM4000 = new behringer.extension.GenericMidiController({ midi.sendShortMsg(cc, this.midi[1] + 1, this.outValueScale(value)); }, }); - this.modeButton = new ModeButton( - {midi: bankOptions.mode, group: bankOptions.group, longPressTimeout: DEFAULT_LONGPRESS_DURATION}); + this.modeButton = new ModeButton({midi: bankOptions.mode, group: bankOptions.group}); }; SamplerBank.prototype = e.deriveFrom(c.ComponentContainer); return { - throttleDelay: 40, + throttleDelay: THROTTLE_DELAY, init: function() { /* @@ -391,6 +392,12 @@ var DDM4000 = new behringer.extension.GenericMidiController({ { // Crossfader defaultDefinition: {type: c.Button, options: {group: "[Master]"}}, components: [ + { // Crossfader: On + type: CrossfaderUnit, options: { + crossfader: {midi: [cc, 0x15]}, + button: {group: "[Skin]", midi: [note, 0x1F], sendShifted: true}, + }, + }, {options: {midi: [note, 0x17], key: null, sendShifted: true}}, // Crossfader: A Full Freq {options: {midi: [note, 0x18], key: null, sendShifted: true}}, // Crossfader: A High {options: {midi: [note, 0x19], key: null, sendShifted: true}}, // Crossfader: A Mid @@ -399,13 +406,6 @@ var DDM4000 = new behringer.extension.GenericMidiController({ {options: {midi: [note, 0x1C], key: null, sendShifted: true}}, // Crossfader: B High {options: {midi: [note, 0x1D], key: null, sendShifted: true}}, // Crossfader: B Mid {options: {midi: [note, 0x1E], key: null, sendShifted: true}}, // Crossfader: B Low - { // Crossfader: On - type: CrossfaderUnit, - options: { - button: {group: "[Skin]", midi: [note, 0x1F], sendShifted: true}, - crossfader: {midi: [cc, 0x15]} - }, - }, {options: {midi: [note, 0x2A], key: null, sendShifted: true}}, // Crossfader: Bounce to MIDI Clock {options: {midi: [note, 0x2B], inKey: null}}, // Crossfader: Beat (Left) {options: {midi: [note, 0x2C], inKey: null}}, // Crossfader: Beat (Right) From cc86bf388c911f24b6ea87967e3385c116c2d955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nino=20Mi=C5=A1ki=C4=87-Pletenac?= Date: Sun, 26 Dec 2021 02:30:00 +0100 Subject: [PATCH 012/201] Prevent preferences dialog from going out of screen This fixes https://bugs.launchpad.net/mixxx/+bug/1938653. --- src/preferences/dialog/dlgpreferences.cpp | 29 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/preferences/dialog/dlgpreferences.cpp b/src/preferences/dialog/dlgpreferences.cpp index 5001fdb1247a..a489c89f03f9 100644 --- a/src/preferences/dialog/dlgpreferences.cpp +++ b/src/preferences/dialog/dlgpreferences.cpp @@ -363,6 +363,8 @@ void DlgPreferences::onShow() { } int newX = m_geometry[0].toInt(); int newY = m_geometry[1].toInt(); + int newWidth = m_geometry[2].toInt(); + int newHeight = m_geometry[3].toInt(); const QScreen* const pScreen = mixxx::widgethelper::getScreen(*this); QSize screenSpace; @@ -371,12 +373,29 @@ void DlgPreferences::onShow() { screenSpace = QSize(800, 600); } else { - screenSpace = pScreen->size(); + screenSpace = pScreen->availableSize(); } - newX = std::max(0, std::min(newX, screenSpace.width() - m_geometry[2].toInt())); - newY = std::max(0, std::min(newY, screenSpace.height() - m_geometry[3].toInt())); + + // Make sure the entire window is visible on screen and is not occluded by taskbar + // Note: Window geometry excludes window decoration + int windowDecorationWidth = frameGeometry().width() - geometry().width(); + int windowDecorationHeight = frameGeometry().height() - geometry().height(); + if (windowDecorationWidth <= 0) { + windowDecorationWidth = 2; + } + if (windowDecorationHeight <= 0) { + windowDecorationHeight = 30; + } + int availableWidth = screenSpace.width() - windowDecorationWidth; + int availableHeight = screenSpace.height() - windowDecorationHeight; + newWidth = std::min(newWidth, availableWidth); + newHeight = std::min(newHeight, availableHeight); + newX = std::max(0, std::min(newX, availableWidth - newWidth)); + newY = std::max(0, std::min(newY, availableHeight - newHeight)); m_geometry[0] = QString::number(newX); m_geometry[1] = QString::number(newY); + m_geometry[2] = QString::number(newWidth); + m_geometry[3] = QString::number(newHeight); // Update geometry with last values #ifdef __WINDOWS__ @@ -393,8 +412,8 @@ void DlgPreferences::onShow() { newY += offsetY; setGeometry(newX, // x position newY, // y position - m_geometry[2].toInt(), // width - m_geometry[3].toInt()); // height + newWidth, // width + newHeight); // height #endif // __LINUX__ / __MACOS__ // Move is also needed on linux. move(newX, newY); From a4a93ee3236516496631790485ac642ba21d0b17 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Sat, 12 Feb 2022 14:26:12 -0500 Subject: [PATCH 013/201] Traktor S3: Fix issues with sampler and hotcue buttons * Properly re-light buttons on track load * Fix hotcues not changing color when color is updated * Fix sampler buttons not changing color on play * Default to 8, not 16 samplers (deck 2 sampler buttons duplicate samplers 1-8) --- .../Traktor-Kontrol-S3-hid-scripts.js | 60 ++++++++++++++----- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js index 1b0984d8fc1f..8655caf9bff5 100644 --- a/res/controllers/Traktor-Kontrol-S3-hid-scripts.js +++ b/res/controllers/Traktor-Kontrol-S3-hid-scripts.js @@ -41,6 +41,10 @@ TraktorS3.SamplerModePressAndHold = false; // enables scratch mode. TraktorS3.JogDefaultOn = true; +// If true, the sampler buttons on Deck 1 are samplers 1-8 and the sampler buttons on Deck 2 are +// 9-16. If false, both decks are samplers 1-8. +TraktorS3.SixteenSamplers = false; + // You can choose the colors you want for each channel. The list of colors is: // RED, CARROT, ORANGE, HONEY, YELLOW, LIME, GREEN, AQUA, CELESTE, SKY, BLUE, // PURPLE, FUCHSIA, MAGENTA, AZALEA, SALMON, WHITE @@ -417,7 +421,7 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { // Samples mode var sampler = padNumber; - if (field.group === "deck2") { + if (field.group === "deck2" && TraktorS3.SixteenSamplers) { sampler += 8; } @@ -452,6 +456,7 @@ TraktorS3.Deck.prototype.numberButtonHandler = function(field) { } return; } + // Play on an empty sampler loads that track into that sampler engine.setValue("[Sampler" + sampler + "]", "LoadSelectedTrack", field.value); }; @@ -937,7 +942,7 @@ TraktorS3.Deck.prototype.lightPads = function() { this.colorOutput(1, "samples"); for (var i = 1; i <= 8; i++) { var idx = i; - if (this.group === "deck2") { + if (this.group === "deck2" && TraktorS3.SixteenSamplers) { idx += 8; } var loaded = engine.getValue("[Sampler" + idx + "]", "track_loaded"); @@ -1030,6 +1035,7 @@ TraktorS3.Channel.prototype.trackLoadedHandler = function() { var trackSampleRate = engine.getValue(this.group, "track_samplerate"); // Assume stereo. this.trackDurationSec = trackSamples / 2.0 / trackSampleRate; + this.parentDeck.lightPads(); }; TraktorS3.Channel.prototype.endOfTrackHandler = function(value) { @@ -1080,6 +1086,8 @@ TraktorS3.Channel.prototype.linkOutputs = function() { TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutput, this))); this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_activate", TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutput, this))); + this.hotcueCallbacks.push(engine.makeConnection(this.group, "hotcue_" + j + "_color", + TraktorS3.bind(TraktorS3.Channel.prototype.hotcuesOutput, this))); } }; @@ -1107,6 +1115,9 @@ TraktorS3.Channel.prototype.hotcuesOutput = function(_value, group, key) { // Not active, ignore return; } + if (deck.padModeState !== 0) { + return; + } var matches = key.match(/hotcue_(\d+)_/); if (matches.length !== 2) { HIDDebug("Didn't get expected hotcue number from string: " + key); @@ -1935,7 +1946,7 @@ TraktorS3.Controller.prototype.registerOutputPackets = function() { // Sampler callbacks for (i = 1; i <= 8; ++i) { this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "track_loaded", TraktorS3.bind(TraktorS3.Controller.prototype.samplesOutput, this))); - this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play", TraktorS3.bind(TraktorS3.Controller.prototype.samplesOutput, this))); + this.samplerCallbacks.push(engine.makeConnection("[Sampler" + i + "]", "play_indicator", TraktorS3.bind(TraktorS3.Controller.prototype.samplesOutput, this))); } }; @@ -2026,8 +2037,12 @@ TraktorS3.Controller.prototype.resolveSampler = function(group) { return undefined; } - // Return sample number - return result[1]; + // Return sampler as number if we can + var strResult = result[1]; + if (strResult === undefined) { + return undefined; + } + return parseInt(strResult); }; TraktorS3.Controller.prototype.samplesOutput = function(value, group, key) { @@ -2039,22 +2054,37 @@ TraktorS3.Controller.prototype.samplesOutput = function(value, group, key) { if (sampler === undefined) { return; } else if (sampler > 8 && sampler < 17) { + if (!TraktorS3.SixteenSamplers) { + // These samplers are ignored + return; + } deck = this.Decks.deck2; num = sampler - 8; } // If we are in samples modes light corresponding LED - if (this.padModeState === 1) { - if (key === "play" && engine.getValue(group, "track_loaded")) { - if (value) { - // Green light on play - deck.colorOutput(0x9E, "!pad_" + num); - } else { - // Reset LED to full white light - deck.colorOutput(1, "!pad_" + num); + if (deck.padModeState !== 1) { + return; + } + if (key === "play_indicator" && engine.getValue(group, "track_loaded")) { + if (value) { + // Green light on play + this.hid.setOutput("deck1", "!pad_" + num, this.hid.LEDColors.GREEN + TraktorS3.LEDBrightValue, !this.batchingOutputs); + // Also light deck2 samplers in 8-sampler mode. + if (!TraktorS3.SixteenSamplers && this.Decks.deck2.padModeState === 1) { + this.hid.setOutput("deck2", "!pad_" + num, this.hid.LEDColors.GREEN + TraktorS3.LEDBrightValue, !this.batchingOutputs); } - } else if (key === "track_loaded") { - deck.colorOutput(value, "!pad_" + num); + } else { + // Reset LED to base color + deck.colorOutput(1, "!pad_" + num); + if (!TraktorS3.SixteenSamplers && this.Decks.deck2.padModeState === 1) { + this.Decks.deck2.colorOutput(1, "!pad_" + num); + } + } + } else if (key === "track_loaded") { + deck.colorOutput(value, "!pad_" + num); + if (!TraktorS3.SixteenSamplers && this.Decks.deck2.padModeState === 1) { + this.Decks.deck2.colorOutput(value, "!pad_" + num); } } }; From 150080284129872be028175b4d16cdfdac6f0164 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 5 Mar 2022 13:29:42 +0100 Subject: [PATCH 014/201] Fix rare SEGFAULT when closing the progress dialog Happened when aborting a batch operation on multiple tracks. --- src/util/taskmonitor.cpp | 14 +++++++++++++- src/util/taskmonitor.h | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/util/taskmonitor.cpp b/src/util/taskmonitor.cpp index 633f580fa11a..45a53da0d482 100644 --- a/src/util/taskmonitor.cpp +++ b/src/util/taskmonitor.cpp @@ -28,6 +28,7 @@ TaskMonitor::~TaskMonitor() { << "pending tasks"; abortAllTasks(); } + closeProgressDialog(); } Task* TaskMonitor::senderTask() const { @@ -143,11 +144,22 @@ void TaskMonitor::abortAllTasks() { updateProgress(); } +void TaskMonitor::closeProgressDialog() { + DEBUG_ASSERT(m_taskInfos.isEmpty()); + auto* const pProgressDlg = m_pProgressDlg.release(); + // Deleting the progress dialog immediately might cause + // segmentation faults due to pending signals! The deletion + // has to be deferred until re-entering the event loop. + if (pProgressDlg) { + pProgressDlg->deleteLater(); + } +} + void TaskMonitor::updateProgress() { DEBUG_ASSERT_MAIN_THREAD_AFFINITY(); DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this); if (m_taskInfos.isEmpty()) { - m_pProgressDlg.reset(); + closeProgressDialog(); return; } const int currentProgress = static_cast(std::round(sumEstimatedPercentageOfCompletion())); diff --git a/src/util/taskmonitor.h b/src/util/taskmonitor.h index f10cc2cc8a18..de7cfa287c27 100644 --- a/src/util/taskmonitor.h +++ b/src/util/taskmonitor.h @@ -82,6 +82,7 @@ class TaskMonitor private: Task* senderTask() const; void updateProgress(); + void closeProgressDialog(); const QString m_labelText; const Duration m_minimumProgressDuration; From fe5583e4342f4a3530ee872c0c94990be495f097 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 5 Mar 2022 22:32:37 +0100 Subject: [PATCH 015/201] Hide the progress dialog immediately --- src/util/taskmonitor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/taskmonitor.cpp b/src/util/taskmonitor.cpp index 45a53da0d482..1e3d48e27a88 100644 --- a/src/util/taskmonitor.cpp +++ b/src/util/taskmonitor.cpp @@ -146,11 +146,12 @@ void TaskMonitor::abortAllTasks() { void TaskMonitor::closeProgressDialog() { DEBUG_ASSERT(m_taskInfos.isEmpty()); - auto* const pProgressDlg = m_pProgressDlg.release(); // Deleting the progress dialog immediately might cause // segmentation faults due to pending signals! The deletion // has to be deferred until re-entering the event loop. + auto* const pProgressDlg = m_pProgressDlg.release(); if (pProgressDlg) { + pProgressDlg->setVisible(false); pProgressDlg->deleteLater(); } } From 45493b94b298d554aa77e121450296c6abdcfe4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 27 Feb 2022 11:47:48 +0100 Subject: [PATCH 016/201] AutoDJ: Added more debug and assert statements --- src/library/autodj/autodjprocessor.cpp | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 87a9e7cf9c35..14c22b11e791 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -621,11 +621,6 @@ void AutoDJProcessor::crossfaderChanged(double value) { void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, double thisPlayPosition) { - if (sDebug) { - //qDebug() << this << "playerPositionChanged" << pAttributes->group - // << thisPlayPosition; - } - // Auto-DJ needs at least two decks VERIFY_OR_DEBUG_ASSERT(m_decks.length() > 1) { return; @@ -778,6 +773,12 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, // that was "on deck" from the top of the queue. // Note: This is a DB call and takes long. removeLoadedTrackFromTopOfQueue(*otherDeck); + } else { + if (sDebug) { + qDebug() << this << "playerPositionChanged()" + << pAttributes->group << thisPlayPosition + << "but not playing"; + } } } @@ -1137,23 +1138,23 @@ double AutoDJProcessor::samplePositionToSeconds(double samplePosition, DeckAttri void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, DeckAttributes* pToDeck, bool seekToStartPoint) { - if (pFromDeck == nullptr || pToDeck == nullptr) { + VERIFY_OR_DEBUG_ASSERT(pFromDeck && pToDeck) { return; } - if (pFromDeck->loading || pToDeck->loading) { + VERIFY_OR_DEBUG_ASSERT(!pFromDeck->loading && !pToDeck->loading) { // don't use halve new halve old data during // changing of tracks return; } - qDebug() << "player" << pFromDeck->group << "calculateTransition()"; - // We require ADJ_IDLE to prevent changing the thresholds in the middle of a // fade. VERIFY_OR_DEBUG_ASSERT(m_eState == ADJ_IDLE) { return; } + qDebug() << "player" << pFromDeck->group << "calculateTransition()"; + const double fromDeckEndPosition = getEndSecond(pFromDeck); const double toDeckEndPosition = getEndSecond(pToDeck); // Since the end position is measured in seconds from 0:00 it is also @@ -1169,7 +1170,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, pToDeck->startPos = kKeepPosition; return; } - if (toDeckDuration <= 0) { + VERIFY_OR_DEBUG_ASSERT(toDeckDuration > 0) { // Playing Track has no duration. This should not happen, because short // tracks are skipped after load. loadNextTrackFromQueue(*pToDeck, false); @@ -1459,6 +1460,10 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra // repeat a probably missed update playerPositionChanged(fromDeck, 1.0); } + } else { + if (sDebug) { + qDebug() << this << "playerTrackLoaded()" << pDeck->group << "but not a toDeck"; + } } } } @@ -1468,8 +1473,8 @@ void AutoDJProcessor::playerLoadingTrack(DeckAttributes* pDeck, TrackPointer pNewTrack, TrackPointer pOldTrack) { if (sDebug) { qDebug() << this << "playerLoadingTrack" << pDeck->group - << "new:"<< (pNewTrack ? pNewTrack->getLocation() : "(null)") - << "old:"<< (pOldTrack ? pOldTrack->getLocation() : "(null)"); + << "new:" << (pNewTrack ? pNewTrack->getLocation() : "(null)") + << "old:" << (pOldTrack ? pOldTrack->getLocation() : "(null)"); } pDeck->loading = true; From 49819ef33f7d359b89e30ad922cdd8a06d6160ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 2 Mar 2022 11:34:34 +0100 Subject: [PATCH 017/201] Improve comments in AutoDJProcessor::playerPlayChanged --- src/library/autodj/autodjprocessor.cpp | 43 ++++++++++++++------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 14c22b11e791..6e7dda311c6b 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -957,26 +957,29 @@ void AutoDJProcessor::playerPlayChanged(DeckAttributes* thisDeck, bool playing) return; } - if (playing && !otherDeck->isPlaying()) { - // In case both decks were stopped and now this one just started, make - // this deck the "from deck". - calculateTransition(thisDeck, getOtherDeck(thisDeck), false); - } - - // If the user manually pressed play on the "to deck" before fading, for - // example to adjust the intro/outro cues, and lets the deck play until the - // end, seek back to the start point instead of keeping the deck stopped at - // the end. - if (!playing && thisDeck->playPosition() >= 1.0 && !thisDeck->isFromDeck) { - // toDeck has stopped at the end. Recalculate the transition, because - // it has been done from a now irrelevant previous position. - // This forces the other deck to be the fromDeck. - thisDeck->startPos = kKeepPosition; - calculateTransition(otherDeck, thisDeck, true); - if (thisDeck->startPos != kKeepPosition) { - // Note: this seek will trigger the playerPositionChanged slot - // which may calls the calculateTransition() again without seek = true; - thisDeck->setPlayPosition(thisDeck->startPos); + if (playing) { + if (!otherDeck->isPlaying()) { + // In case both decks were stopped and now this one just started, make + // this deck the "from deck". + calculateTransition(thisDeck, getOtherDeck(thisDeck), false); + } + } else { + // Deck paused + // This may happen if the user has previously pressed play on the "to deck" + // before fading, for example to adjust the intro/outro cues, and lets the + // deck play until the end, seek back to the start point instead of keeping + // the deck stopped at the end. + if (thisDeck->playPosition() >= 1.0 && !thisDeck->isFromDeck) { + // toDeck has stopped at the end. Recalculate the transition, because + // it has been done from a now irrelevant previous position. + // This forces the other deck to be the fromDeck. + thisDeck->startPos = kKeepPosition; + calculateTransition(otherDeck, thisDeck, true); + if (thisDeck->startPos != kKeepPosition) { + // Note: this seek will trigger the playerPositionChanged slot + // which may calls the calculateTransition() again without seek = true; + thisDeck->setPlayPosition(thisDeck->startPos); + } } } } From c12cc2c6ab90e58cb5d22d2958f73080a59dbccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 2 Mar 2022 12:10:04 +0100 Subject: [PATCH 018/201] Add debug message before every calculateTransition() call --- src/library/autodj/autodjprocessor.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 6e7dda311c6b..d5398d54b280 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -692,6 +692,10 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, } else { // At least right deck is playing // Set crossfade thresholds for right deck. + if (sDebug) { + qDebug() << this << "playerPositionChanged" + << "right deck playing"; + } calculateTransition(rightDeck, leftDeck, false); } emitAutoDJStateChanged(m_eState); @@ -738,6 +742,10 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, // recalculated here. // Don't adjust transition when reaching the end. In this case it is // always stopped. + if (sDebug) { + qDebug() << this << "playerPositionChanged" + << "cueing seek"; + } calculateTransition(otherDeck, thisDeck, false); } else if (thisDeck->isRepeat()) { // repeat pauses auto DJ @@ -964,11 +972,10 @@ void AutoDJProcessor::playerPlayChanged(DeckAttributes* thisDeck, bool playing) calculateTransition(thisDeck, getOtherDeck(thisDeck), false); } } else { - // Deck paused - // This may happen if the user has previously pressed play on the "to deck" - // before fading, for example to adjust the intro/outro cues, and lets the - // deck play until the end, seek back to the start point instead of keeping - // the deck stopped at the end. + // Deck paused + // This may happen if the user has previously pressed play on the "to deck" + // before fading, for example to adjust the intro/outro cues, and lets the + // deck play until the end, seek back to the start point instead of keeping if (thisDeck->playPosition() >= 1.0 && !thisDeck->isFromDeck) { // toDeck has stopped at the end. Recalculate the transition, because // it has been done from a now irrelevant previous position. From 6bf68be972751d860a059230d6e19b487036b22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 2 Mar 2022 12:41:33 +0100 Subject: [PATCH 019/201] Debug intro and outro length --- src/library/autodj/autodjprocessor.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index d5398d54b280..aee5370c652d 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1236,6 +1236,12 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } double introLength = introEnd - introStart; + + if (sDebug) { + qDebug() << this << "calculateTransition" + << "introLength" << introLength + << "outroLength" << outroLength; + } switch (m_transitionMode) { case TransitionMode::FullIntroOutro: { From 4cd4e33788c0e6d55eb7d9c7d8d2061d3f5e4a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 4 Mar 2022 21:57:44 +0100 Subject: [PATCH 020/201] Fix unintendend jump-cuts in auto DJ This happened because the track is not able to seek exactly to the intro start point. In this case taking the start point as end point does not lead to a zero length intro. --- src/library/autodj/autodjprocessor.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index aee5370c652d..4efd3dc55da2 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1163,8 +1163,6 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, return; } - qDebug() << "player" << pFromDeck->group << "calculateTransition()"; - const double fromDeckEndPosition = getEndSecond(pFromDeck); const double toDeckEndPosition = getEndSecond(pToDeck); // Since the end position is measured in seconds from 0:00 it is also @@ -1227,16 +1225,15 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, introStart = toDeckPositionSeconds; } - double introEnd = getIntroEndSecond(pToDeck); - if (introEnd < introStart) { - // introEnd is invalid. Assume a zero length intro. - // The introStart is automatically placed by AnalyzerSilence, so use - // that as a fallback if the user has not placed introEnd. - introEnd = introStart; + double introLength = 0; + const double introEndSample = pToDeck->introEndPosition(); + if (introEndSample != Cue::kNoPosition) { + const double introEnd = samplePositionToSeconds(introEndSample, pToDeck); + if (introStart < introEnd) { + introLength = introEnd - introStart; + } } - double introLength = introEnd - introStart; - if (sDebug) { qDebug() << this << "calculateTransition" << "introLength" << introLength From d0e8a68c4bbf95772fbea0e78635d021d9398c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 13 Mar 2022 15:13:51 +0100 Subject: [PATCH 021/201] AutoDJ: make use of if constexpr --- src/library/autodj/autodjprocessor.cpp | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 4efd3dc55da2..0339c4e9e664 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -19,7 +19,7 @@ const double kKeepPosition = -1.0; const mixxx::audio::ChannelCount kChannelCount = mixxx::kEngineChannelCount; -static const bool sDebug = false; +constexpr bool sDebug = false; } // anonymous namespace DeckAttributes::DeckAttributes(int index, @@ -692,7 +692,7 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, } else { // At least right deck is playing // Set crossfade thresholds for right deck. - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerPositionChanged" << "right deck playing"; } @@ -742,7 +742,7 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, // recalculated here. // Don't adjust transition when reaching the end. In this case it is // always stopped. - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerPositionChanged" << "cueing seek"; } @@ -782,7 +782,7 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, // Note: This is a DB call and takes long. removeLoadedTrackFromTopOfQueue(*otherDeck); } else { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerPositionChanged()" << pAttributes->group << thisPlayPosition << "but not playing"; @@ -944,7 +944,7 @@ void AutoDJProcessor::maybeFillRandomTracks() { } void AutoDJProcessor::playerPlayChanged(DeckAttributes* thisDeck, bool playing) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerPlayChanged" << thisDeck->group << playing; } @@ -992,7 +992,7 @@ void AutoDJProcessor::playerPlayChanged(DeckAttributes* thisDeck, bool playing) } void AutoDJProcessor::playerIntroStartChanged(DeckAttributes* pAttributes, double position) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerIntroStartChanged" << pAttributes->group << position; } // nothing to do, because we want not to re-cue the toDeck and the from @@ -1000,7 +1000,7 @@ void AutoDJProcessor::playerIntroStartChanged(DeckAttributes* pAttributes, doubl } void AutoDJProcessor::playerIntroEndChanged(DeckAttributes* pAttributes, double position) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerIntroEndChanged" << pAttributes->group << position; } @@ -1021,7 +1021,7 @@ void AutoDJProcessor::playerIntroEndChanged(DeckAttributes* pAttributes, double } void AutoDJProcessor::playerOutroStartChanged(DeckAttributes* pAttributes, double position) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerOutroStartChanged" << pAttributes->group << position; } @@ -1038,7 +1038,7 @@ void AutoDJProcessor::playerOutroStartChanged(DeckAttributes* pAttributes, doubl } void AutoDJProcessor::playerOutroEndChanged(DeckAttributes* pAttributes, double position) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerOutroEndChanged" << pAttributes->group << position; } @@ -1234,7 +1234,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } } - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "calculateTransition" << "introLength" << introLength << "outroLength" << outroLength; @@ -1384,7 +1384,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, pFromDeck->fadeBeginPos = 1; } - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "calculateTransition" << pFromDeck->group << pFromDeck->fadeBeginPos << pFromDeck->fadeEndPos << pToDeck->startPos; @@ -1435,7 +1435,7 @@ void AutoDJProcessor::useFixedFadeTime( } void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTrack) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerTrackLoaded" << pDeck->group << (pTrack ? pTrack->getLocation() : "(null)"); } @@ -1474,7 +1474,7 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra playerPositionChanged(fromDeck, 1.0); } } else { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerTrackLoaded()" << pDeck->group << "but not a toDeck"; } } @@ -1484,7 +1484,7 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra void AutoDJProcessor::playerLoadingTrack(DeckAttributes* pDeck, TrackPointer pNewTrack, TrackPointer pOldTrack) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerLoadingTrack" << pDeck->group << "new:" << (pNewTrack ? pNewTrack->getLocation() : "(null)") << "old:" << (pOldTrack ? pOldTrack->getLocation() : "(null)"); @@ -1513,7 +1513,7 @@ void AutoDJProcessor::playerLoadingTrack(DeckAttributes* pDeck, } void AutoDJProcessor::playerEmpty(DeckAttributes* pDeck) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerEmpty()" << pDeck->group; } @@ -1530,7 +1530,7 @@ void AutoDJProcessor::playerEmpty(DeckAttributes* pDeck) { } void AutoDJProcessor::playerRateChanged(DeckAttributes* pAttributes) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "playerRateChanged" << pAttributes->group; } @@ -1547,7 +1547,7 @@ void AutoDJProcessor::playerRateChanged(DeckAttributes* pAttributes) { } void AutoDJProcessor::setTransitionTime(int time) { - if (sDebug) { + if constexpr (sDebug) { qDebug() << this << "setTransitionTime" << time; } From 02811a9f6abafcf952ac2e26895751770f18754f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 13 Mar 2022 22:39:22 +0100 Subject: [PATCH 022/201] AutoDJ: remove wrong assertion --- src/library/autodj/autodjprocessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 0339c4e9e664..8d76ee88b04f 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1151,7 +1151,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, VERIFY_OR_DEBUG_ASSERT(pFromDeck && pToDeck) { return; } - VERIFY_OR_DEBUG_ASSERT(!pFromDeck->loading && !pToDeck->loading) { + if (pFromDeck->loading || pToDeck->loading) { // don't use halve new halve old data during // changing of tracks return; From 8d75909d9f9225320ae3b3ddf6caefbc09b23241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 14 Mar 2022 16:03:51 +0100 Subject: [PATCH 023/201] Auto DJ: Do not start a transition when a new track is still loading. This fixes lp1948975 --- src/library/autodj/autodjprocessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 8d76ee88b04f..eb2fa24bc26e 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -757,7 +757,7 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, // - transition into fading mode, play the other deck and fade to it. // - check if fading is done and stop the deck // - update the crossfader - if (thisPlayPosition >= thisDeck->fadeBeginPos && thisDeck->isFromDeck) { + if (thisPlayPosition >= thisDeck->fadeBeginPos && thisDeck->isFromDeck && !otherDeck->loading) { if (m_eState == ADJ_IDLE) { if (thisDeckPlaying || thisPlayPosition >= 1.0) { // Set the state as FADING. From 0e3b3bbdd7cedd9fda2da5c0e8c2b88ac68b0154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 14 Mar 2022 16:10:55 +0100 Subject: [PATCH 024/201] Auto DJ: Introduce getLeftDeck and getRightDeck functions --- src/library/autodj/autodjprocessor.cpp | 37 +++++++++++++++----------- src/library/autodj/autodjprocessor.h | 2 ++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index eb2fa24bc26e..7d3581fc33c1 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1608,26 +1608,33 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { } } +DeckAttributes* AutoDJProcessor::getLeftDeck() { + // find first left deck + foreach (DeckAttributes* pDeck, m_decks) { + if (pDeck->isLeft()) { + return pDeck; + } + } + return nullptr; +} + +DeckAttributes* AutoDJProcessor::getRightDeck() { + // find first right deck + foreach (DeckAttributes* pDeck, m_decks) { + if (pDeck->isRight()) { + return pDeck; + } + } + return nullptr; +} + DeckAttributes* AutoDJProcessor::getOtherDeck( const DeckAttributes* pThisDeck) { if (pThisDeck->isLeft()) { - // find first right deck - foreach(DeckAttributes* pDeck, m_decks) { - if (pDeck->isRight()) { - return pDeck; - } - } - return nullptr; + return getRightDeck(); } - if (pThisDeck->isRight()) { - // find first left deck - foreach(DeckAttributes* pDeck, m_decks) { - if (pDeck->isLeft()) { - return pDeck; - } - } - return nullptr; + return getLeftDeck(); } return nullptr; } diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 769773147991..6b6f6147e34c 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -264,6 +264,8 @@ class AutoDJProcessor : public QObject { double fromDeckSecond, double fadeEndSecond, double toDeckStartSecond); + DeckAttributes* getLeftDeck(); + DeckAttributes* getRightDeck(); DeckAttributes* getOtherDeck(const DeckAttributes* pThisDeck); DeckAttributes* getFromDeck(); From ba5798b07a55c5dfb9feccd1eb3d8901fc79b2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 14 Mar 2022 16:14:22 +0100 Subject: [PATCH 025/201] Auto DJ: Use the real deck orientation set by CO --- src/library/autodj/autodjprocessor.cpp | 12 ++++-------- src/library/autodj/autodjprocessor.h | 9 ++++----- src/test/autodjprocessor_test.cpp | 15 +++++++++------ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 7d3581fc33c1..6f37c113fabe 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -23,8 +23,7 @@ constexpr bool sDebug = false; } // anonymous namespace DeckAttributes::DeckAttributes(int index, - BaseTrackPlayer* pPlayer, - EngineChannel::ChannelOrientation orientation) + BaseTrackPlayer* pPlayer) : index(index), group(pPlayer->getGroup()), startPos(kKeepPosition), @@ -32,7 +31,7 @@ DeckAttributes::DeckAttributes(int index, fadeEndPos(1.0), isFromDeck(false), loading(false), - m_orientation(orientation), + m_orientation(group, "orientation"), m_playPos(group, "playposition"), m_play(group, "play"), m_repeat(group, "repeat"), @@ -153,13 +152,10 @@ AutoDJProcessor::AutoDJProcessor( QString group = PlayerManager::groupForDeck(i); BaseTrackPlayer* pPlayer = pPlayerManager->getPlayer(group); // Shouldn't be possible. - if (pPlayer == nullptr) { - qWarning() << "PROGRAMMING ERROR deck does not exist" << i; + VERIFY_OR_DEBUG_ASSERT(pPlayer) { continue; } - EngineChannel::ChannelOrientation orientation = - (i % 2 == 0) ? EngineChannel::LEFT : EngineChannel::RIGHT; - m_decks.append(new DeckAttributes(i, pPlayer, orientation)); + m_decks.append(new DeckAttributes(i, pPlayer)); } // Auto-DJ needs at least two decks DEBUG_ASSERT(m_decks.length() > 1); diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index 6b6f6147e34c..7784c5b0e238 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -21,16 +21,15 @@ class DeckAttributes : public QObject { Q_OBJECT public: DeckAttributes(int index, - BaseTrackPlayer* pPlayer, - EngineChannel::ChannelOrientation orientation); + BaseTrackPlayer* pPlayer); virtual ~DeckAttributes(); bool isLeft() const { - return m_orientation == EngineChannel::LEFT; + return m_orientation.get() == EngineChannel::LEFT; } bool isRight() const { - return m_orientation == EngineChannel::RIGHT; + return m_orientation.get() == EngineChannel::RIGHT; } bool isPlaying() const { @@ -125,7 +124,7 @@ class DeckAttributes : public QObject { bool loading; // The data is inconsistent during loading a deck private: - EngineChannel::ChannelOrientation m_orientation; + ControlProxy m_orientation; ControlProxy m_playPos; ControlProxy m_play; ControlProxy m_repeat; diff --git a/src/test/autodjprocessor_test.cpp b/src/test/autodjprocessor_test.cpp index 0076aee44f76..ce9f81f06578 100644 --- a/src/test/autodjprocessor_test.cpp +++ b/src/test/autodjprocessor_test.cpp @@ -39,7 +39,7 @@ class FakeMaster { class FakeDeck : public BaseTrackPlayer { public: - FakeDeck(const QString& group) + FakeDeck(const QString& group, EngineChannel::ChannelOrientation orient) : BaseTrackPlayer(NULL, group), trackSamples(ConfigKey(group, "track_samples")), samplerate(ConfigKey(group, "track_samplerate")), @@ -50,11 +50,13 @@ class FakeDeck : public BaseTrackPlayer { introStartPos(ConfigKey(group, "intro_start_position")), introEndPos(ConfigKey(group, "intro_end_position")), outroStartPos(ConfigKey(group, "outro_start_position")), - outroEndPos(ConfigKey(group, "outro_end_position")) { + outroEndPos(ConfigKey(group, "outro_end_position")), + orientation(ConfigKey(group, "orientation")) { play.setButtonMode(ControlPushButton::TOGGLE); repeat.setButtonMode(ControlPushButton::TOGGLE); outroStartPos.set(Cue::kNoPosition); outroEndPos.set(Cue::kNoPosition); + orientation.set(orient); } void fakeTrackLoadedEvent(TrackPointer pTrack) { @@ -107,6 +109,7 @@ class FakeDeck : public BaseTrackPlayer { ControlObject introEndPos; ControlObject outroStartPos; ControlObject outroEndPos; + ControlObject orientation; }; class MockPlayerManager : public PlayerManagerInterface { @@ -174,10 +177,10 @@ class AutoDJProcessorTest : public LibraryTest { } AutoDJProcessorTest() - : deck1("[Channel1]"), - deck2("[Channel2]"), - deck3("[Channel3]"), - deck4("[Channel4]") { + : deck1("[Channel1]", EngineChannel::LEFT), + deck2("[Channel2]", EngineChannel::RIGHT), + deck3("[Channel3]", EngineChannel::LEFT), + deck4("[Channel4]", EngineChannel::RIGHT) { qRegisterMetaType("TrackPointer"); PlaylistDAO& playlistDao = internalCollection()->getPlaylistDAO(); From 21b3a2ee575174f4d04b7ddfc088f3ef266d885c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 14 Mar 2022 16:17:05 +0100 Subject: [PATCH 026/201] Auto DJ: Don't use the index based decks --- src/library/autodj/autodjprocessor.cpp | 255 +++++++++++++------------ 1 file changed, 128 insertions(+), 127 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 6f37c113fabe..74ad820e60b8 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -215,17 +215,20 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::shufflePlaylist( void AutoDJProcessor::fadeNow() { // Auto-DJ needs at least two decks - VERIFY_OR_DEBUG_ASSERT(m_decks.length() > 1) { - return; - } if (m_eState != ADJ_IDLE) { // we cannot fade if AutoDj is disabled or already fading return; } double crossfader = getCrossfader(); - DeckAttributes* pLeftDeck = m_decks[0]; - DeckAttributes* pRightDeck = m_decks[1]; + DeckAttributes* pLeftDeck = getLeftDeck(); + DeckAttributes* pRightDeck = getRightDeck(); + if (!pLeftDeck || !pRightDeck) { + // User has changed the orientation, disable Auto DJ + toggleAutoDJ(false); + return; + } + DeckAttributes* pFromDeck; DeckAttributes* pToDeck; @@ -316,26 +319,26 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::skipNext() { if (m_eState == ADJ_DISABLED) { return ADJ_IS_INACTIVE; } - - // Auto-DJ needs at least two decks - VERIFY_OR_DEBUG_ASSERT(m_decks.length() > 1) { + // Load the next song from the queue. + DeckAttributes* pLeftDeck = getLeftDeck(); + DeckAttributes* pRightDeck = getRightDeck(); + if (!pLeftDeck && !pRightDeck) { + // User has changed the orientation, disable Auto DJ + toggleAutoDJ(false); return ADJ_NOT_TWO_DECKS; } - // Load the next song from the queue. - DeckAttributes& leftDeck = *m_decks[0]; - DeckAttributes& rightDeck = *m_decks[1]; - if (!leftDeck.isPlaying()) { - removeLoadedTrackFromTopOfQueue(leftDeck); - loadNextTrackFromQueue(leftDeck); - } else if (!rightDeck.isPlaying()) { - removeLoadedTrackFromTopOfQueue(rightDeck); - loadNextTrackFromQueue(rightDeck); + if (!pLeftDeck->isPlaying()) { + removeLoadedTrackFromTopOfQueue(*pLeftDeck); + loadNextTrackFromQueue(*pLeftDeck); + } else if (!pRightDeck->isPlaying()) { + removeLoadedTrackFromTopOfQueue(*pRightDeck); + loadNextTrackFromQueue(*pRightDeck); } else { // If both decks are playing remove next track in playlist TrackId nextId = m_pAutoDJTableModel->getTrackId(m_pAutoDJTableModel->index(0, 0)); - TrackId leftId = leftDeck.getLoadedTrack()->getId(); - TrackId rightId = rightDeck.getLoadedTrack()->getId(); + TrackId leftId = pLeftDeck->getLoadedTrack()->getId(); + TrackId rightId = pRightDeck->getLoadedTrack()->getId(); if (nextId == leftId || nextId == rightId) { // One of the playing tracks is still on top of playlist, remove second item m_pAutoDJTableModel->removeTrack(m_pAutoDJTableModel->index(1, 0)); @@ -348,17 +351,17 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::skipNext() { } AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { - DeckAttributes* deck1 = m_decks[0]; - DeckAttributes* deck2 = m_decks[1]; - VERIFY_OR_DEBUG_ASSERT(deck1 && deck2) { - return ADJ_NOT_TWO_DECKS; - } + if (enable) { // Enable Auto DJ + DeckAttributes* pLeftDeck = getLeftDeck(); + DeckAttributes* pRightDeck = getRightDeck(); + if (!pLeftDeck || !pRightDeck) { + return ADJ_NOT_TWO_DECKS; + } - bool deck1Playing = deck1->isPlaying(); - bool deck2Playing = deck2->isPlaying(); + bool leftDeckPlaying = pLeftDeck->isPlaying(); + bool rightDeckPlaying = pRightDeck->isPlaying(); - if (enable) { // Enable Auto DJ - if (deck1Playing && deck2Playing) { + if (leftDeckPlaying && rightDeckPlaying) { qDebug() << "One deck must be stopped before enabling Auto DJ mode"; // Keep the current state. emitAutoDJStateChanged(m_eState); @@ -374,28 +377,27 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { } // Never load the same track if it is already playing - if (deck1Playing) { - removeLoadedTrackFromTopOfQueue(*deck1); - } else if (deck2Playing) { - removeLoadedTrackFromTopOfQueue(*deck2); + if (leftDeckPlaying) { + removeLoadedTrackFromTopOfQueue(*pLeftDeck); + } else if (rightDeckPlaying) { + removeLoadedTrackFromTopOfQueue(*pRightDeck); } else { // If the first track is already cued at a position in the first // 2/3 in on of the Auto DJ decks, start it. // If the track is paused at a later position, it is probably too // close to the end. In this case it is loaded again at the stored // cue point. - if (deck1->playPosition() < 0.66 && - removeLoadedTrackFromTopOfQueue(*deck1)) { - deck1->play(); - deck1Playing = true; - } else if (deck2->playPosition() < 0.66 && - removeLoadedTrackFromTopOfQueue(*deck2)) { - deck2->play(); - deck2Playing = true; + if (pLeftDeck->playPosition() < 0.66 && + removeLoadedTrackFromTopOfQueue(*pLeftDeck)) { + pLeftDeck->play(); + leftDeckPlaying = true; + } else if (pRightDeck->playPosition() < 0.66 && + removeLoadedTrackFromTopOfQueue(*pRightDeck)) { + pRightDeck->play(); + rightDeckPlaying = true; } } - TrackPointer nextTrack = getNextTrackFromQueue(); if (!nextTrack) { qDebug() << "Queue is empty now"; @@ -414,97 +416,97 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { m_pCOCrossfader->connectValueChanged(this, &AutoDJProcessor::crossfaderChanged); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::playPositionChanged, this, &AutoDJProcessor::playerPositionChanged); - connect(deck2, + connect(pRightDeck, &DeckAttributes::playPositionChanged, this, &AutoDJProcessor::playerPositionChanged); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::playChanged, this, &AutoDJProcessor::playerPlayChanged); - connect(deck2, + connect(pRightDeck, &DeckAttributes::playChanged, this, &AutoDJProcessor::playerPlayChanged); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::introStartPositionChanged, this, &AutoDJProcessor::playerIntroStartChanged); - connect(deck2, + connect(pRightDeck, &DeckAttributes::introStartPositionChanged, this, &AutoDJProcessor::playerIntroStartChanged); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::introEndPositionChanged, this, &AutoDJProcessor::playerIntroEndChanged); - connect(deck2, + connect(pRightDeck, &DeckAttributes::introEndPositionChanged, this, &AutoDJProcessor::playerIntroEndChanged); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::outroStartPositionChanged, this, &AutoDJProcessor::playerOutroStartChanged); - connect(deck2, + connect(pRightDeck, &DeckAttributes::outroStartPositionChanged, this, &AutoDJProcessor::playerOutroStartChanged); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::outroEndPositionChanged, this, &AutoDJProcessor::playerOutroEndChanged); - connect(deck2, + connect(pRightDeck, &DeckAttributes::outroEndPositionChanged, this, &AutoDJProcessor::playerOutroEndChanged); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::trackLoaded, this, &AutoDJProcessor::playerTrackLoaded); - connect(deck2, + connect(pRightDeck, &DeckAttributes::trackLoaded, this, &AutoDJProcessor::playerTrackLoaded); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::loadingTrack, this, &AutoDJProcessor::playerLoadingTrack); - connect(deck2, + connect(pRightDeck, &DeckAttributes::loadingTrack, this, &AutoDJProcessor::playerLoadingTrack); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::playerEmpty, this, &AutoDJProcessor::playerEmpty); - connect(deck2, + connect(pRightDeck, &DeckAttributes::playerEmpty, this, &AutoDJProcessor::playerEmpty); - connect(deck1, + connect(pLeftDeck, &DeckAttributes::rateChanged, this, &AutoDJProcessor::playerRateChanged); - connect(deck2, + connect(pRightDeck, &DeckAttributes::rateChanged, this, &AutoDJProcessor::playerRateChanged); - if (!deck1Playing && !deck2Playing) { + if (!leftDeckPlaying && !rightDeckPlaying) { // Both decks are stopped. Load a track into deck 1 and start it // playing. Instruct playerPositionChanged to wait for a // playposition update from deck 1. playerPositionChanged for @@ -519,36 +521,35 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { // Load track into the left deck and play. Once it starts playing, // we will receive a playerPositionChanged update for deck 1 which // will load a track into the right deck and switch to IDLE mode. - emitLoadTrackToPlayer(nextTrack, deck1->group, true); + emitLoadTrackToPlayer(nextTrack, pLeftDeck->group, true); } else { // One of the two decks is playing. Switch into IDLE mode and wait // until the playing deck crosses posThreshold to start fading. m_eState = ADJ_IDLE; - if (deck1Playing) { + if (leftDeckPlaying) { // Load track into the right deck. - emitLoadTrackToPlayer(nextTrack, deck2->group, false); + emitLoadTrackToPlayer(nextTrack, pRightDeck->group, false); // Move crossfader to the left. setCrossfader(-1.0); } else { // Load track into the left deck. - emitLoadTrackToPlayer(nextTrack, deck1->group, false); + emitLoadTrackToPlayer(nextTrack, pLeftDeck->group, false); // Move crossfader to the right. setCrossfader(1.0); } } emitAutoDJStateChanged(m_eState); - } else { // Disable Auto DJ - if (m_pEnabledAutoDJ->get() != 0.0) { - m_pEnabledAutoDJ->set(0.0); - } + } else { // Disable Auto DJ + m_pEnabledAutoDJ->set(0.0); qDebug() << "Auto DJ disabled"; m_eState = ADJ_DISABLED; disconnect(m_pCOCrossfader, &ControlProxy::valueChanged, this, &AutoDJProcessor::crossfaderChanged); - deck1->disconnect(this); - deck2->disconnect(this); + foreach (DeckAttributes* pDeck, m_decks) { + pDeck->disconnect(this); + } m_pCOCrossfader->set(0); emitAutoDJStateChanged(m_eState); } @@ -617,11 +618,6 @@ void AutoDJProcessor::crossfaderChanged(double value) { void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, double thisPlayPosition) { - // Auto-DJ needs at least two decks - VERIFY_OR_DEBUG_ASSERT(m_decks.length() > 1) { - return; - } - // qDebug() << "player" << pAttributes->group << "PositionChanged(" << value << ")"; if (m_eState == ADJ_DISABLED) { // nothing to do @@ -1453,27 +1449,27 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra } else if (m_eState == ADJ_IDLE) { // this deck has just changed the track so it becomes the toDeck DeckAttributes* fromDeck = getOtherDeck(pDeck); - if (fromDeck) { - // check if this deck has suitable alignment - DeckAttributes* toDeck = getOtherDeck(fromDeck); - if (toDeck == pDeck) { - pDeck->startPos = kKeepPosition; - calculateTransition(fromDeck, pDeck, true); - if (pDeck->startPos != kKeepPosition) { - // Note: this seek will trigger the playerPositionChanged slot - // which may calls the calculateTransition() again without seek = true; - pDeck->setPlayPosition(pDeck->startPos); - } - // we are her in the relative domain 0..1 - if (!fromDeck->isPlaying() && fromDeck->playPosition() >= 1.0) { - // repeat a probably missed update - playerPositionChanged(fromDeck, 1.0); - } - } else { - if constexpr (sDebug) { - qDebug() << this << "playerTrackLoaded()" << pDeck->group << "but not a toDeck"; - } - } + // check if this deck has suitable alignment + if (fromDeck && getOtherDeck(fromDeck) != pDeck) { + //if constexpr (sDebug) { + qDebug() << this << "playerTrackLoaded()" << pDeck->group << "but not a toDeck"; + qDebug() << this << "playerTrackLoaded()" << fromDeck->group << "but not a toDeck"; + //} + // User has changed the orientation, disable Auto DJ + toggleAutoDJ(false); + return; + } + pDeck->startPos = kKeepPosition; + calculateTransition(fromDeck, pDeck, true); + if (pDeck->startPos != kKeepPosition) { + // Note: this seek will trigger the playerPositionChanged slot + // which may calls the calculateTransition() again without seek = true; + pDeck->setPlayPosition(pDeck->startPos); + } + // we are her in the relative domain 0..1 + if (!fromDeck->isPlaying() && fromDeck->playPosition() >= 1.0) { + // repeat a probably missed update + playerPositionChanged(fromDeck, 1.0); } } } @@ -1547,11 +1543,6 @@ void AutoDJProcessor::setTransitionTime(int time) { qDebug() << this << "setTransitionTime" << time; } - // Auto-DJ needs at least two decks - VERIFY_OR_DEBUG_ASSERT(m_decks.length() > 1) { - return; - } - // Update the transition time first. m_pConfig->set(ConfigKey(kConfigKey, kTransitionPreferenceName), ConfigValue(time)); @@ -1559,13 +1550,16 @@ void AutoDJProcessor::setTransitionTime(int time) { // Then re-calculate fade thresholds for the decks. if (m_eState == ADJ_IDLE) { - DeckAttributes& leftDeck = *m_decks[0]; - DeckAttributes& rightDeck = *m_decks[1]; - if (leftDeck.isPlaying()) { - calculateTransition(&leftDeck, &rightDeck, false); + DeckAttributes* pLeftDeck = getLeftDeck(); + DeckAttributes* pRightDeck = getRightDeck(); + if (!pLeftDeck || !pRightDeck) { + return; + } + if (pLeftDeck->isPlaying()) { + calculateTransition(pLeftDeck, pRightDeck, false); } - if (rightDeck.isPlaying()) { - calculateTransition(&rightDeck, &leftDeck, false); + if (pRightDeck->isPlaying()) { + calculateTransition(pRightDeck, pLeftDeck, false); } } } @@ -1581,22 +1575,26 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { } // Then re-calculate fade thresholds for the decks. - DeckAttributes& leftDeck = *m_decks[0]; - DeckAttributes& rightDeck = *m_decks[1]; + DeckAttributes* pLeftDeck = getLeftDeck(); + DeckAttributes* pRightDeck = getRightDeck(); - if (leftDeck.isPlaying() && !rightDeck.isPlaying()) { - calculateTransition(&leftDeck, &rightDeck, true); - if (rightDeck.startPos != kKeepPosition) { + if (!pLeftDeck || !pRightDeck) { + return; + } + + if (pLeftDeck->isPlaying() && !pRightDeck->isPlaying()) { + calculateTransition(pLeftDeck, pRightDeck, true); + if (pRightDeck->startPos != kKeepPosition) { // Note: this seek will trigger the playerPositionChanged slot // which may calls the calculateTransition() again without seek = true; - rightDeck.setPlayPosition(rightDeck.startPos); + pRightDeck->setPlayPosition(pRightDeck->startPos); } - } else if (rightDeck.isPlaying() && !leftDeck.isPlaying()) { - calculateTransition(&rightDeck, &leftDeck, true); - if (leftDeck.startPos != kKeepPosition) { + } else if (pRightDeck->isPlaying() && pLeftDeck->isPlaying()) { + calculateTransition(pRightDeck, pLeftDeck, true); + if (pLeftDeck->startPos != kKeepPosition) { // Note: this seek will trigger the playerPositionChanged slot // which may calls the calculateTransition() again without seek = true; - leftDeck.setPlayPosition(leftDeck.startPos); + pLeftDeck->setPlayPosition(pLeftDeck->startPos); } } else { // user has manually started the other deck or stopped both. @@ -1650,22 +1648,25 @@ bool AutoDJProcessor::nextTrackLoaded() { return false; } - DeckAttributes& leftDeck = *m_decks[0]; - DeckAttributes& rightDeck = *m_decks[1]; - bool leftDeckPlaying = leftDeck.isPlaying(); - bool rightDeckPlaying = rightDeck.isPlaying(); + DeckAttributes* pLeftDeck = getLeftDeck(); + DeckAttributes* pRightDeck = getRightDeck(); + VERIFY_OR_DEBUG_ASSERT(pLeftDeck && pRightDeck) { + return false; + } + bool leftDeckPlaying = pLeftDeck->isPlaying(); + bool rightDeckPlaying = pRightDeck->isPlaying(); // Calculate idle deck TrackPointer loadedTrack; if (leftDeckPlaying && !rightDeckPlaying) { - loadedTrack = rightDeck.getLoadedTrack(); + loadedTrack = pRightDeck->getLoadedTrack(); } else if (!leftDeckPlaying && rightDeckPlaying) { - loadedTrack = leftDeck.getLoadedTrack(); + loadedTrack = pLeftDeck->getLoadedTrack(); } else if (getCrossfader() < 0.0) { - loadedTrack = rightDeck.getLoadedTrack(); + loadedTrack = pRightDeck->getLoadedTrack(); } else { - loadedTrack = leftDeck.getLoadedTrack(); + loadedTrack = pLeftDeck->getLoadedTrack(); } return loadedTrack == getNextTrackFromQueue(); From c13d09360112964cb786aacce1bb366113378e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 14 Mar 2022 16:05:08 +0100 Subject: [PATCH 027/201] Auto DJ: Restore the play when loading a track during a running transition. --- src/library/autodj/autodjprocessor.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 74ad820e60b8..61d031941320 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1471,6 +1471,16 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra // repeat a probably missed update playerPositionChanged(fromDeck, 1.0); } + } else if (m_eState == ADJ_LEFT_FADING) { + if (pDeck == getRightDeck()) { + // restore the play state lost during loading + pDeck->play(); + } + } else if (m_eState == ADJ_RIGHT_FADING) { + if (pDeck == getLeftDeck()) { + // restore the play state lost during loading + pDeck->play(); + } } } From 7fed042f29a1ed8b1f2ea62ccd1bc3f2c9bf2cf1 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 29 Mar 2022 01:52:59 +0200 Subject: [PATCH 028/201] Spinback: fix infinite acceleration --- src/controllers/controllerengine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 187ee5263d2c..25d330edf191 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1503,8 +1503,8 @@ void ControllerEngine::softTakeoverIgnoreNextValue( Output: - -------- ------------------------------------------------------ */ void ControllerEngine::spinback(int deck, bool activate, double factor, double rate) { - // defaults for args set in header file - brake(deck, activate, factor, rate); + qDebug() << " init spinback"; + brake(deck, activate, -factor, rate); } /* -------- ------------------------------------------------------ From 89dc73832d1bb6709e2d3fe3ebda7b29d97bbe79 Mon Sep 17 00:00:00 2001 From: Swiftb0y <12380386+Swiftb0y@users.noreply.github.com> Date: Tue, 29 Mar 2022 07:17:39 +0200 Subject: [PATCH 029/201] pre-commit: update hook versions fix broken pre-commit caused by psf/black#2969 --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce1f26801a80..e2afa999df27 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,7 +63,7 @@ repos: #args: [--ignore-words, .codespellignore, --ignore-regex, "\\W(?:m_p*(?=[A-Z])|m_(?=\\w)|pp*(?=[A-Z])|k(?=[A-Z])|s_(?=\\w))"] exclude: ^(packaging/wix/LICENSE.rtf|src/dialog/dlgabout\.cpp|.*\.(?:pot?|ts|wxl))$ - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.8.0 + rev: v8.12.0 hooks: - id: eslint args: [--fix, --report-unused-disable-directives] @@ -83,7 +83,7 @@ repos: language: python files: \.(c|cc|cxx|cpp|frag|glsl|h|hpp|hxx|ih|ispc|ipp|java|m|mm|proto|vert)$ - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black files: ^tools/.*$ @@ -102,7 +102,7 @@ repos: hooks: - id: markdownlint-cli2 - repo: https://github.com/sirosen/check-jsonschema - rev: 0.10.2 + rev: 0.14.1 hooks: - id: check-github-workflows - repo: local From 87c23c210004d531f8c425bd27de59248f2e8af6 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 28 Mar 2022 15:00:47 +0200 Subject: [PATCH 030/201] Brake, spinback, softStart: add debug output --- src/controllers/controllerengine.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 25d330edf191..0a47cc0728ad 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1349,15 +1349,19 @@ void ControllerEngine::scratchProcess(int timerId) { // If we're ramping to end scratching and the wheel hasn't been turned very // recently (spinback after lift-off,) feed fixed data + qDebug() << "."; if (m_ramp[deck] && !m_softStartActive[deck] && ((mixxx::Time::elapsed() - m_lastMovement[deck]) >= mixxx::Duration::fromMillis(1))) { + qDebug() << " ramp && !softStart"; filter->observation(m_rampTo[deck] * m_rampFactor[deck]); // Once this code path is run, latch so it always runs until reset //m_lastMovement[deck] += mixxx::Duration::fromSeconds(1); } else if (m_softStartActive[deck]) { + qDebug() << " softStart"; // pretend we have moved by (desired rate*default distance) - filter->observation(m_rampTo[deck]*kAlphaBetaDt); + filter->observation(m_rampTo[deck] * kAlphaBetaDt); } else { + qDebug() << " else"; // This will (and should) be 0 if no net ticks have been accumulated // (i.e. the wheel is stopped) filter->observation(m_dx[deck] * m_intervalAccumulator[deck]); @@ -1375,6 +1379,10 @@ void ControllerEngine::scratchProcess(int timerId) { // Reset accumulator m_intervalAccumulator[deck] = 0; + qDebug() << " old " << oldRate; + qDebug() << " new " << newRate; + qDebug() << " fabs" << fabs(trunc((m_rampTo[deck] - newRate) * 100000) / 100000); + qDebug() << "."; // End scratching if we're ramping and the current rate is really close to the rampTo value if ((m_ramp[deck] && fabs(m_rampTo[deck] - newRate) <= 0.00001) || // or if we brake or softStart and have crossed over the desired value, @@ -1410,6 +1418,8 @@ void ControllerEngine::scratchProcess(int timerId) { m_dx[deck] = 0.0; m_brakeActive[deck] = false; m_softStartActive[deck] = false; + qDebug() << " DONE scratching"; + qDebug() << "."; } } @@ -1514,6 +1524,7 @@ void ControllerEngine::spinback(int deck, bool activate, double factor, double r Output: - -------- ------------------------------------------------------ */ void ControllerEngine::brake(int deck, bool activate, double factor, double rate) { + qDebug() << " init brake"; // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -1582,6 +1593,7 @@ void ControllerEngine::brake(int deck, bool activate, double factor, double rate Output: - -------- ------------------------------------------------------ */ void ControllerEngine::softStart(int deck, bool activate, double factor) { + qDebug() << " init softStart"; // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); From c771b2810aed7272218b01e35e59ccbc526c6569 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 28 Mar 2022 15:02:00 +0200 Subject: [PATCH 031/201] Brake, softStart: early return --- src/controllers/controllerengine.cpp | 150 ++++++++++++++------------- 1 file changed, 77 insertions(+), 73 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 0a47cc0728ad..9f40827eb713 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1541,50 +1541,52 @@ void ControllerEngine::brake(int deck, bool activate, double factor, double rate // used in scratchProcess for the different timer behavior we need m_brakeActive[deck] = activate; - double initRate = rate; - - if (activate) { - // store the new values for this spinback/brake effect - if (initRate == 1.0) {// then rate is really 1.0 or was set to default - // in /res/common-controller-scripts.js so check for real value, - // taking pitch into account - initRate = getDeckRate(group); - } - // stop ramping at a rate which doesn't produce any audible output anymore - m_rampTo[deck] = 0.01; - // if we are currently softStart()ing, stop it - if (m_softStartActive[deck]) { - m_softStartActive[deck] = false; - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (filter != nullptr) { - initRate = filter->predictedVelocity(); - } - } - - // setup timer and set scratch2 - timerId = startTimer(kScratchTimerMs); - m_scratchTimers[timerId] = deck; - ControlObjectScript* pScratch2 = getControlObjectScript(group, "scratch2"); - if (pScratch2 != nullptr) { - pScratch2->slotSet(initRate); - } + if (!activate) { + return; + } - // setup the filter with default alpha and beta*factor - double alphaBrake = 1.0/512; - // avoid decimals for fine adjusting - if (factor>1) { - factor = ((factor-1)/10)+1; - } - double betaBrake = ((1.0/512)/1024)*factor; // default*factor + double initRate = rate; + // store the new values for this spinback/brake effect + if (initRate == 1.0) { // then rate is really 1.0 or was set to default + // in /res/common-controller-scripts.js so check for real value, + // taking pitch into account + initRate = getDeckRate(group); + } + // stop ramping at a rate which doesn't produce any audible output anymore + m_rampTo[deck] = 0.01; + // if we are currently softStart()ing, stop it + if (m_softStartActive[deck]) { + m_softStartActive[deck] = false; AlphaBetaFilter* filter = m_scratchFilters[deck]; if (filter != nullptr) { - filter->init(kAlphaBetaDt, initRate, alphaBrake, betaBrake); + initRate = filter->predictedVelocity(); } + } + + // setup timer and set scratch2 + timerId = startTimer(kScratchTimerMs); + m_scratchTimers[timerId] = deck; + + ControlObjectScript* pScratch2 = getControlObjectScript(group, "scratch2"); + if (pScratch2 != nullptr) { + pScratch2->slotSet(initRate); + } - // activate the ramping in scratchProcess() - m_ramp[deck] = true; + // setup the filter with default alpha and beta*factor + double alphaBrake = 1.0 / 512; + // avoid decimals for fine adjusting + if (factor > 1) { + factor = ((factor - 1) / 10) + 1; } + double betaBrake = ((1.0 / 512) / 1024) * factor; // default*factor + AlphaBetaFilter* filter = m_scratchFilters[deck]; + if (filter != nullptr) { + filter->init(kAlphaBetaDt, initRate, alphaBrake, betaBrake); + } + + // activate the ramping in scratchProcess() + m_ramp[deck] = true; } /* -------- ------------------------------------------------------ @@ -1610,49 +1612,51 @@ void ControllerEngine::softStart(int deck, bool activate, double factor) { // used in scratchProcess for the different timer behavior we need m_softStartActive[deck] = activate; - double initRate = 0.0; - - if (activate) { - // acquire deck rate - m_rampTo[deck] = getDeckRate(group); - // if brake()ing, get current rate from filter - if (m_brakeActive[deck]) { - m_brakeActive[deck] = false; + if (!activate) { + return; + } - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (filter != nullptr) { - initRate = filter->predictedVelocity(); - } - } + double initRate = 0.0; + // acquire deck rate + m_rampTo[deck] = getDeckRate(group); - // setup timer, start playing and set scratch2 - timerId = startTimer(kScratchTimerMs); - m_scratchTimers[timerId] = deck; + // if brake()ing, get current rate from filter + if (m_brakeActive[deck]) { + m_brakeActive[deck] = false; - ControlObjectScript* pPlay = getControlObjectScript(group, "play"); - if (pPlay != nullptr) { - pPlay->slotSet(1.0); + AlphaBetaFilter* filter = m_scratchFilters[deck]; + if (filter != nullptr) { + initRate = filter->predictedVelocity(); } + } - ControlObjectScript* pScratch2 = getControlObjectScript(group, "scratch2"); - if (pScratch2 != nullptr) { - pScratch2->slotSet(initRate); - } + // setup timer, start playing and set scratch2 + timerId = startTimer(kScratchTimerMs); + m_scratchTimers[timerId] = deck; - // setup the filter like in brake(), with default alpha and beta*factor - double alphaSoft = 1.0/512; - // avoid decimals for fine adjusting - if (factor>1) { - factor = ((factor-1)/10)+1; - } - double betaSoft = ((1.0/512)/1024)*factor; // default: (1.0/512)/1024 - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (filter != nullptr) { // kAlphaBetaDt = 1/1000 seconds - filter->init(kAlphaBetaDt, initRate, alphaSoft, betaSoft); - } + ControlObjectScript* pPlay = getControlObjectScript(group, "play"); + if (pPlay != nullptr) { + pPlay->slotSet(1.0); + } - // activate the ramping in scratchProcess() - m_ramp[deck] = true; + ControlObjectScript* pScratch2 = getControlObjectScript(group, "scratch2"); + if (pScratch2 != nullptr) { + pScratch2->slotSet(initRate); } + + // setup the filter like in brake(), with default alpha and beta*factor + double alphaSoft = 1.0 / 512; + // avoid decimals for fine adjusting + if (factor > 1) { + factor = ((factor - 1) / 10) + 1; + } + double betaSoft = ((1.0 / 512) / 1024) * factor; // default: (1.0/512)/1024 + AlphaBetaFilter* filter = m_scratchFilters[deck]; + if (filter != nullptr) { // kAlphaBetaDt = 1/1000 seconds + filter->init(kAlphaBetaDt, initRate, alphaSoft, betaSoft); + } + + // activate the ramping in scratchProcess() + m_ramp[deck] = true; } From a30d4f79d25b1af41be8a33bd079d31f68e1beb4 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 29 Mar 2022 19:43:22 +0200 Subject: [PATCH 032/201] Brake: use const rampTo value --- src/controllers/controllerengine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 9f40827eb713..8299df560781 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -30,6 +30,8 @@ constexpr int kDecks = 16; // timer. constexpr int kScratchTimerMs = 1; constexpr double kAlphaBetaDt = kScratchTimerMs / 1000.0; +// stop ramping at a rate which doesn't produce any audible output anymore +constexpr double kBrakeRampToRate = 0.01; } // namespace ControllerEngine::ControllerEngine( @@ -1555,7 +1557,9 @@ void ControllerEngine::brake(int deck, bool activate, double factor, double rate } // stop ramping at a rate which doesn't produce any audible output anymore m_rampTo[deck] = 0.01; - // if we are currently softStart()ing, stop it + m_rampTo[deck] = kBrakeRampToRate; + + // If we want to brake or spin back and are currently softStart'ing, stop it. if (m_softStartActive[deck]) { m_softStartActive[deck] = false; AlphaBetaFilter* filter = m_scratchFilters[deck]; From e0681a5e24cf9dc129b728fdf0c564ac4a49559a Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 29 Mar 2022 22:24:13 +0200 Subject: [PATCH 033/201] Scratching: handle spinback independently in brake() & scratchProcess() * allow spinback when deck is stopped * use -kBrakeRampToRate for spinback to avoid long, inaudible run out * don't allow brake to interrupt spinback * add stopScratchTimer() to remove redundant code --- src/controllers/controllerengine.cpp | 108 +++++++++++++++++---------- src/controllers/controllerengine.h | 3 +- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 8299df560781..40542a41d853 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -53,6 +53,7 @@ ControllerEngine::ControllerEngine( m_scratchFilters.resize(kDecks); m_rampFactor.resize(kDecks); m_brakeActive.resize(kDecks); + m_spinbackActive.resize(kDecks); m_softStartActive.resize(kDecks); // Initialize arrays used for testing and pointers for (int i = 0; i < kDecks; ++i) { @@ -1162,7 +1163,7 @@ int ControllerEngine::beginTimer(int interval, const QScriptValue& timerCallback Input: ID of timer to stop Output: - -------- ------------------------------------------------------ */ -void ControllerEngine::stopTimer(int timerId) { +void ControllerEngine::stopTimer(const int timerId) { if (!m_timers.contains(timerId)) { qWarning() << "Killing timer" << timerId << ": That timer does not exist!"; return; @@ -1172,6 +1173,16 @@ void ControllerEngine::stopTimer(int timerId) { m_timers.remove(timerId); } +void ControllerEngine::stopScratchTimer(const int timerId) { + if (!m_scratchTimers.contains(timerId)) { + qWarning() << "Killing scratch timer" << timerId << ": That timer does not exist!"; + return; + } + controllerDebug("Killing scratch timer:" << timerId); + killTimer(timerId); + m_scratchTimers.remove(timerId); +} + void ControllerEngine::stopAllTimers() { QMutableHashIterator i(m_timers); while (i.hasNext()) { @@ -1336,6 +1347,7 @@ void ControllerEngine::scratchTick(int deck, int interval) { Output: - -------- ------------------------------------------------------ */ void ControllerEngine::scratchProcess(int timerId) { + // TODO(ronso0) Refuse scratching if no track is loaded int deck = m_scratchTimers[timerId]; // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -1387,17 +1399,16 @@ void ControllerEngine::scratchProcess(int timerId) { qDebug() << "."; // End scratching if we're ramping and the current rate is really close to the rampTo value if ((m_ramp[deck] && fabs(m_rampTo[deck] - newRate) <= 0.00001) || - // or if we brake or softStart and have crossed over the desired value, - ((m_brakeActive[deck] || m_softStartActive[deck]) && ( - (oldRate > m_rampTo[deck] && newRate < m_rampTo[deck]) || - (oldRate < m_rampTo[deck] && newRate > m_rampTo[deck]))) || - // or if the deck was stopped manually during brake or softStart - ((m_brakeActive[deck] || m_softStartActive[deck]) && (!isDeckPlaying(group)))) { + // or if we brake, spin back or softstart and have crossed over the desired value, + (m_brakeActive[deck] && newRate < m_rampTo[deck]) || + ((m_spinbackActive[deck] || m_softStartActive[deck]) && newRate > m_rampTo[deck]) || + // or if the deck was stopped manually during brake or softStart + ((m_brakeActive[deck] || m_softStartActive[deck]) && (!isDeckPlaying(group)))) { // Not ramping no mo' m_ramp[deck] = false; - if (m_brakeActive[deck]) { - // If in brake mode, set scratch2 rate to 0 and turn off the play button. + if (m_brakeActive[deck] || m_spinbackActive[deck]) { + // If in brake mode, set scratch2 rate to 0 and stop the deck. pScratch2->slotSet(0.0); ControlObjectScript* pPlay = getControlObjectScript(group, "play"); if (pPlay != nullptr) { @@ -1413,12 +1424,11 @@ void ControllerEngine::scratchProcess(int timerId) { } pScratch2Enable->slotSet(0); - // Remove timer - killTimer(timerId); - m_scratchTimers.remove(timerId); + stopScratchTimer(timerId); m_dx[deck] = 0.0; m_brakeActive[deck] = false; + m_spinbackActive[deck] = false; m_softStartActive[deck] = false; qDebug() << " DONE scratching"; qDebug() << "."; @@ -1529,44 +1539,62 @@ void ControllerEngine::brake(int deck, bool activate, double factor, double rate qDebug() << " init brake"; // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); - - // kill timer when both enabling or disabling - int timerId = m_scratchTimers.key(deck); - killTimer(timerId); - m_scratchTimers.remove(timerId); - // enable/disable scratch2 mode ControlObjectScript* pScratch2Enable = getControlObjectScript(group, "scratch2_enable"); if (pScratch2Enable != nullptr) { pScratch2Enable->slotSet(activate ? 1 : 0); } - // used in scratchProcess for the different timer behavior we need - m_brakeActive[deck] = activate; + // Used for killing the current timer when both enabling or disabling + // Don't kill timer yet! This may be a brake init while currently spinning back + // and we don't want to interrupt that. + int timerId = m_scratchTimers.key(deck); if (!activate) { + m_brakeActive[deck] = false; + m_spinbackActive[deck] = false; + stopScratchTimer(timerId); return; } - - double initRate = rate; - // store the new values for this spinback/brake effect - if (initRate == 1.0) { // then rate is really 1.0 or was set to default - // in /res/common-controller-scripts.js so check for real value, - // taking pitch into account + // Distinguish spinback and brake. Both ramp to a very low rate to avoid a long, + // inaudible run out. For spinback that rate is negative so we don't cross 0. + // Spinback and brake also require different handling in scratchProcess. + double initRate; + if (rate < -kBrakeRampToRate) { // spinback + m_spinbackActive[deck] = true; + m_brakeActive[deck] = false; + m_rampTo[deck] = -kBrakeRampToRate; + initRate = rate; + } else if (rate > kBrakeRampToRate) { // brake + // It just doesn't make sense to allow brake to interrupt spinback or an + // already running brake process + if (m_spinbackActive[deck] || m_brakeActive[deck]) { + return; + } + m_brakeActive[deck] = true; + m_spinbackActive[deck] = false; + m_rampTo[deck] = kBrakeRampToRate; + // Let's fetch the current rate to create a seamless brake process initRate = getDeckRate(group); + // If we are currently softStart'ing adopt the current scratch rate + if (m_softStartActive[deck]) { + m_softStartActive[deck] = false; + AlphaBetaFilter* filter = m_scratchFilters[deck]; + if (filter != nullptr) { + initRate = filter->predictedVelocity(); + } + } + } else { // -kBrakeRampToRate <= rate <= kBrakeRampToRate + // This filters stopped deck and rare case of very low initial rates + m_brakeActive[deck] = false; + m_spinbackActive[deck] = false; + stopScratchTimer(timerId); + // TODO(ronso0) Stop deck as if we were braking to halt + return; } - // stop ramping at a rate which doesn't produce any audible output anymore - m_rampTo[deck] = 0.01; - m_rampTo[deck] = kBrakeRampToRate; + stopScratchTimer(timerId); // If we want to brake or spin back and are currently softStart'ing, stop it. - if (m_softStartActive[deck]) { - m_softStartActive[deck] = false; - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (filter != nullptr) { - initRate = filter->predictedVelocity(); - } - } // setup timer and set scratch2 timerId = startTimer(kScratchTimerMs); @@ -1605,8 +1633,7 @@ void ControllerEngine::softStart(int deck, bool activate, double factor) { // kill timer when both enabling or disabling int timerId = m_scratchTimers.key(deck); - killTimer(timerId); - m_scratchTimers.remove(timerId); + stopScratchTimer(timerId); // enable/disable scratch2 mode ControlObjectScript* pScratch2Enable = getControlObjectScript(group, "scratch2_enable"); @@ -1625,9 +1652,10 @@ void ControllerEngine::softStart(int deck, bool activate, double factor) { // acquire deck rate m_rampTo[deck] = getDeckRate(group); - // if brake()ing, get current rate from filter - if (m_brakeActive[deck]) { + // if braking or spinning back, get current rate from filter + if (m_brakeActive[deck] || m_spinbackActive[deck]) { m_brakeActive[deck] = false; + m_spinbackActive[deck] = false; AlphaBetaFilter* filter = m_scratchFilters[deck]; if (filter != nullptr) { diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 9caab019b7e6..95da9cb9f827 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -193,6 +193,7 @@ class ControllerEngine : public QObject { // Scratching functions & variables void scratchProcess(int timerId); + void stopScratchTimer(const int timerId); bool isDeckPlaying(const QString& group); double getDeckRate(const QString& group); @@ -216,7 +217,7 @@ class ControllerEngine : public QObject { QVarLengthArray m_intervalAccumulator; QVarLengthArray m_lastMovement; QVarLengthArray m_dx, m_rampTo, m_rampFactor; - QVarLengthArray m_ramp, m_brakeActive, m_softStartActive; + QVarLengthArray m_ramp, m_brakeActive, m_spinbackActive, m_softStartActive; QVarLengthArray m_scratchFilters; QHash m_scratchTimers; QHash m_scriptWrappedFunctionCache; From 7b76d1cb7c9d9d22f69c0504274084d36de87ecb Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 29 Mar 2022 22:47:42 +0200 Subject: [PATCH 034/201] ControllerEngine: add stopDeck() --- src/controllers/controllerengine.cpp | 27 ++++++++++++++++++--------- src/controllers/controllerengine.h | 1 + 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 40542a41d853..20c6c53296d8 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1242,12 +1242,24 @@ bool ControllerEngine::isDeckPlaying(const QString& group) { ControlObjectScript* pPlay = getControlObjectScript(group, "play"); if (pPlay == nullptr) { - QString error = QString("Could not getControlObjectScript()"); - scriptErrorDialog(error, error); - return false; + QString error = QString("Could not get ControlObjectScript(%1, play)").arg(group); + scriptErrorDialog(error, error); + return false; + } + + return pPlay->toBool(); +} + +void ControllerEngine::stopDeck(const QString& group) { + ControlObjectScript* pPlay = getControlObjectScript(group, "play"); + + if (pPlay == nullptr) { + QString error = QString("Could not get ControlObjectScript(%1, play)").arg(group); + scriptErrorDialog(error, error); + return; } - return pPlay->get() > 0.0; + pPlay->set(0.0); } /* -------- ------------------------------------------------------ @@ -1410,10 +1422,7 @@ void ControllerEngine::scratchProcess(int timerId) { if (m_brakeActive[deck] || m_spinbackActive[deck]) { // If in brake mode, set scratch2 rate to 0 and stop the deck. pScratch2->slotSet(0.0); - ControlObjectScript* pPlay = getControlObjectScript(group, "play"); - if (pPlay != nullptr) { - pPlay->slotSet(0.0); - } + stopDeck(group); } // Clear scratch2_enable to end scratching. @@ -1589,7 +1598,7 @@ void ControllerEngine::brake(int deck, bool activate, double factor, double rate m_brakeActive[deck] = false; m_spinbackActive[deck] = false; stopScratchTimer(timerId); - // TODO(ronso0) Stop deck as if we were braking to halt + stopDeck(group); return; } stopScratchTimer(timerId); diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 95da9cb9f827..8d752ce4fb89 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -196,6 +196,7 @@ class ControllerEngine : public QObject { void stopScratchTimer(const int timerId); bool isDeckPlaying(const QString& group); + void stopDeck(const QString& group); double getDeckRate(const QString& group); Controller* m_pController; From 02fba3e5778e9629bdbefec8d4a1b3201b77e5c0 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 29 Mar 2022 22:51:19 +0200 Subject: [PATCH 035/201] ControllerEngine: stop scratching if no track is loaded --- src/controllers/controllerengine.cpp | 17 +++++++++++++++-- src/controllers/controllerengine.h | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 20c6c53296d8..473cfc98b919 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1262,6 +1262,18 @@ void ControllerEngine::stopDeck(const QString& group) { pPlay->set(0.0); } +bool ControllerEngine::isTrackLoaded(const QString& group) { + ControlObjectScript* pTrackLoaded = getControlObjectScript(group, "track_loaded"); + + if (pTrackLoaded == nullptr) { + QString error = QString("Could not get ControlObjectScript(%1, track_loaded)").arg(group); + scriptErrorDialog(error, error); + return false; + } + + return pTrackLoaded->toBool(); +} + /* -------- ------------------------------------------------------ Purpose: Enables scratching for relative controls Input: Virtual deck to scratch, @@ -1359,7 +1371,6 @@ void ControllerEngine::scratchTick(int deck, int interval) { Output: - -------- ------------------------------------------------------ */ void ControllerEngine::scratchProcess(int timerId) { - // TODO(ronso0) Refuse scratching if no track is loaded int deck = m_scratchTimers[timerId]; // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -1415,7 +1426,9 @@ void ControllerEngine::scratchProcess(int timerId) { (m_brakeActive[deck] && newRate < m_rampTo[deck]) || ((m_spinbackActive[deck] || m_softStartActive[deck]) && newRate > m_rampTo[deck]) || // or if the deck was stopped manually during brake or softStart - ((m_brakeActive[deck] || m_softStartActive[deck]) && (!isDeckPlaying(group)))) { + ((m_brakeActive[deck] || m_softStartActive[deck]) && (!isDeckPlaying(group))) || + // or if there is no track loaded (anymore) + !isTrackLoaded(group)) { // Not ramping no mo' m_ramp[deck] = false; diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 8d752ce4fb89..92e6a9c561fd 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -197,6 +197,7 @@ class ControllerEngine : public QObject { bool isDeckPlaying(const QString& group); void stopDeck(const QString& group); + bool isTrackLoaded(const QString& group); double getDeckRate(const QString& group); Controller* m_pController; From ab419aca331ea082d798ce377839c037c39d0315 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 30 Mar 2022 22:43:57 +0200 Subject: [PATCH 036/201] ControllerEngine: more const --- src/controllers/controllerengine.cpp | 17 +++++++++-------- src/controllers/controllerengine.h | 14 ++++++++++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 473cfc98b919..498b37c1190d 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1370,10 +1370,10 @@ void ControllerEngine::scratchTick(int deck, int interval) { Input: ID of timer for this deck Output: - -------- ------------------------------------------------------ */ -void ControllerEngine::scratchProcess(int timerId) { - int deck = m_scratchTimers[timerId]; +void ControllerEngine::scratchProcess(const int timerId) { + const int deck = m_scratchTimers[timerId]; // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); + const QString group = PlayerManager::groupForDeck(deck - 1); AlphaBetaFilter* filter = m_scratchFilters[deck]; if (!filter) { qWarning() << "Scratch filter pointer is null on deck" << deck; @@ -1546,7 +1546,8 @@ void ControllerEngine::softTakeoverIgnoreNextValue( rate (optional) Output: - -------- ------------------------------------------------------ */ -void ControllerEngine::spinback(int deck, bool activate, double factor, double rate) { +void ControllerEngine::spinback( + const int deck, bool activate, const double factor, const double rate) { qDebug() << " init spinback"; brake(deck, activate, -factor, rate); } @@ -1557,10 +1558,10 @@ void ControllerEngine::spinback(int deck, bool activate, double factor, double r rate (optional, necessary for spinback) Output: - -------- ------------------------------------------------------ */ -void ControllerEngine::brake(int deck, bool activate, double factor, double rate) { +void ControllerEngine::brake(const int deck, bool activate, double factor, const double rate) { qDebug() << " init brake"; // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); + const QString group = PlayerManager::groupForDeck(deck - 1); // enable/disable scratch2 mode ControlObjectScript* pScratch2Enable = getControlObjectScript(group, "scratch2_enable"); if (pScratch2Enable != nullptr) { @@ -1648,10 +1649,10 @@ void ControllerEngine::brake(int deck, bool activate, double factor, double rate Input: deck, activate/deactivate, factor (optional) Output: - -------- ------------------------------------------------------ */ -void ControllerEngine::softStart(int deck, bool activate, double factor) { +void ControllerEngine::softStart(const int deck, bool activate, double factor) { qDebug() << " init softStart"; // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); + const QString group = PlayerManager::groupForDeck(deck - 1); // kill timer when both enabling or disabling int timerId = m_scratchTimers.key(deck); diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 92e6a9c561fd..291e4ce23158 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -132,9 +132,15 @@ class ControllerEngine : public QObject { Q_INVOKABLE bool isScratching(int deck); Q_INVOKABLE void softTakeover(const QString& group, const QString& name, bool set); Q_INVOKABLE void softTakeoverIgnoreNextValue(const QString& group, const QString& name); - Q_INVOKABLE void brake(int deck, bool activate, double factor=1.0, double rate=1.0); - Q_INVOKABLE void spinback(int deck, bool activate, double factor=1.8, double rate=-10.0); - Q_INVOKABLE void softStart(int deck, bool activate, double factor=1.0); + Q_INVOKABLE void brake(const int deck, + bool activate, + double factor = 1.0, + const double rate = 1.0); + Q_INVOKABLE void spinback(const int deck, + bool activate, + double factor = 1.8, + const double rate = -10.0); + Q_INVOKABLE void softStart(const int deck, bool activate, double factor = 1.0); // Handler for timers that scripts set. virtual void timerEvent(QTimerEvent *event); @@ -192,7 +198,7 @@ class ControllerEngine : public QObject { ControlObjectScript* getControlObjectScript(const QString& group, const QString& name); // Scratching functions & variables - void scratchProcess(int timerId); + void scratchProcess(const int timerId); void stopScratchTimer(const int timerId); bool isDeckPlaying(const QString& group); From 44dc3f4c5107556c0527f40c93622b641b9ff7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 5 Apr 2022 10:30:55 +0200 Subject: [PATCH 037/201] Use range based loops when accessing m_decks --- src/library/autodj/autodjprocessor.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 61d031941320..1fc77342c79d 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -371,7 +371,7 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { // TODO: This is a total bandaid for making Auto DJ work with decks 3 // and 4. We should design a nicer way to handle this. for (int i = 2; i < m_decks.length(); ++i) { - if (m_decks[i] != NULL && m_decks[i]->isPlaying()) { + if (m_decks[i] && m_decks[i]->isPlaying()) { return ADJ_DECKS_3_4_PLAYING; } } @@ -547,7 +547,7 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { &ControlProxy::valueChanged, this, &AutoDJProcessor::crossfaderChanged); - foreach (DeckAttributes* pDeck, m_decks) { + for (const auto& pDeck : std::as_const(m_decks)) { pDeck->disconnect(this); } m_pCOCrossfader->set(0); @@ -1614,7 +1614,7 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { DeckAttributes* AutoDJProcessor::getLeftDeck() { // find first left deck - foreach (DeckAttributes* pDeck, m_decks) { + for (const auto& pDeck : std::as_const(m_decks)) { if (pDeck->isLeft()) { return pDeck; } @@ -1624,7 +1624,7 @@ DeckAttributes* AutoDJProcessor::getLeftDeck() { DeckAttributes* AutoDJProcessor::getRightDeck() { // find first right deck - foreach (DeckAttributes* pDeck, m_decks) { + for (const auto& pDeck : std::as_const(m_decks)) { if (pDeck->isRight()) { return pDeck; } @@ -1644,7 +1644,7 @@ DeckAttributes* AutoDJProcessor::getOtherDeck( } DeckAttributes* AutoDJProcessor::getFromDeck() { - for (const auto& pDeck : qAsConst(m_decks)) { + for (const auto& pDeck : std::as_const(m_decks)) { if (pDeck->isFromDeck) { return pDeck; } From fe19e4b6513e9587044a4fc8003f67cd8d6b630a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 5 Apr 2022 11:26:10 +0200 Subject: [PATCH 038/201] Silence Debug --- src/library/autodj/autodjprocessor.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 1fc77342c79d..426c5521b2c0 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1451,10 +1451,9 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra DeckAttributes* fromDeck = getOtherDeck(pDeck); // check if this deck has suitable alignment if (fromDeck && getOtherDeck(fromDeck) != pDeck) { - //if constexpr (sDebug) { - qDebug() << this << "playerTrackLoaded()" << pDeck->group << "but not a toDeck"; - qDebug() << this << "playerTrackLoaded()" << fromDeck->group << "but not a toDeck"; - //} + if constexpr (sDebug) { + qDebug() << this << "playerTrackLoaded()" << pDeck->group << "but not a toDeck"; + } // User has changed the orientation, disable Auto DJ toggleAutoDJ(false); return; From 7439b8d0f2a62cc4040dab654e6c5398f72c55aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 5 Apr 2022 18:04:41 +0200 Subject: [PATCH 039/201] Remove debug assertion when unser changes the deck orientation and diable Auto DJ if no suitable follower deck is found --- src/library/autodj/autodjprocessor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 426c5521b2c0..52d21140413d 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -322,7 +322,7 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::skipNext() { // Load the next song from the queue. DeckAttributes* pLeftDeck = getLeftDeck(); DeckAttributes* pRightDeck = getRightDeck(); - if (!pLeftDeck && !pRightDeck) { + if (!pLeftDeck || !pRightDeck) { // User has changed the orientation, disable Auto DJ toggleAutoDJ(false); return ADJ_NOT_TWO_DECKS; @@ -1562,6 +1562,8 @@ void AutoDJProcessor::setTransitionTime(int time) { DeckAttributes* pLeftDeck = getLeftDeck(); DeckAttributes* pRightDeck = getRightDeck(); if (!pLeftDeck || !pRightDeck) { + // User has changed the orientation, disable Auto DJ + toggleAutoDJ(false); return; } if (pLeftDeck->isPlaying()) { @@ -1588,6 +1590,8 @@ void AutoDJProcessor::setTransitionMode(TransitionMode newMode) { DeckAttributes* pRightDeck = getRightDeck(); if (!pLeftDeck || !pRightDeck) { + // User has changed the orientation, disable Auto DJ + toggleAutoDJ(false); return; } @@ -1659,7 +1663,7 @@ bool AutoDJProcessor::nextTrackLoaded() { DeckAttributes* pLeftDeck = getLeftDeck(); DeckAttributes* pRightDeck = getRightDeck(); - VERIFY_OR_DEBUG_ASSERT(pLeftDeck && pRightDeck) { + if (!pLeftDeck || !pRightDeck) { return false; } From 40c08ad99513ca68e043fd7a389e6085e97cf34d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 5 Apr 2022 19:07:08 +0200 Subject: [PATCH 040/201] Disable AutoDJ button when enabling failed --- src/library/autodj/autodjprocessor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 52d21140413d..b10ee1184fd9 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -355,6 +355,8 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { DeckAttributes* pLeftDeck = getLeftDeck(); DeckAttributes* pRightDeck = getRightDeck(); if (!pLeftDeck || !pRightDeck) { + // Keep the current state. + emitAutoDJStateChanged(m_eState); return ADJ_NOT_TWO_DECKS; } @@ -372,6 +374,7 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { // and 4. We should design a nicer way to handle this. for (int i = 2; i < m_decks.length(); ++i) { if (m_decks[i] && m_decks[i]->isPlaying()) { + // Keep the current state. return ADJ_DECKS_3_4_PLAYING; } } From 03129ddb4f904adbd72f645ab075a2b9b2e994ad Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 6 Apr 2022 00:38:10 +0200 Subject: [PATCH 041/201] Scratching: use #if SCRATCH_DEBUG_OUTPUT --- src/controllers/controllerengine.cpp | 45 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 498b37c1190d..59a1ec5e25a7 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -23,6 +23,8 @@ // (closure compatible version of connectControl) #include +#define SCRATCH_DEBUG_OUTPUT false + namespace { constexpr int kDecks = 16; @@ -1371,6 +1373,10 @@ void ControllerEngine::scratchTick(int deck, int interval) { Output: - -------- ------------------------------------------------------ */ void ControllerEngine::scratchProcess(const int timerId) { +#if SCRATCH_DEBUG_OUTPUT + qDebug() << " ."; + qDebug() << " ControllerEngine::scratchProcess"; +#endif const int deck = m_scratchTimers[timerId]; // PlayerManager::groupForDeck is 0-indexed. const QString group = PlayerManager::groupForDeck(deck - 1); @@ -1380,7 +1386,9 @@ void ControllerEngine::scratchProcess(const int timerId) { return; } +#if SCRATCH_DEBUG_OUTPUT const double oldRate = filter->predictedVelocity(); +#endif // Give the filter a data point: @@ -1389,16 +1397,22 @@ void ControllerEngine::scratchProcess(const int timerId) { qDebug() << "."; if (m_ramp[deck] && !m_softStartActive[deck] && ((mixxx::Time::elapsed() - m_lastMovement[deck]) >= mixxx::Duration::fromMillis(1))) { - qDebug() << " ramp && !softStart"; +#if SCRATCH_DEBUG_OUTPUT + qDebug() << " ramp && !softStart"; +#endif filter->observation(m_rampTo[deck] * m_rampFactor[deck]); // Once this code path is run, latch so it always runs until reset //m_lastMovement[deck] += mixxx::Duration::fromSeconds(1); } else if (m_softStartActive[deck]) { - qDebug() << " softStart"; +#if SCRATCH_DEBUG_OUTPUT + qDebug() << " softStart"; +#endif // pretend we have moved by (desired rate*default distance) filter->observation(m_rampTo[deck] * kAlphaBetaDt); } else { - qDebug() << " else"; +#if SCRATCH_DEBUG_OUTPUT + qDebug() << " else"; +#endif // This will (and should) be 0 if no net ticks have been accumulated // (i.e. the wheel is stopped) filter->observation(m_dx[deck] * m_intervalAccumulator[deck]); @@ -1416,10 +1430,13 @@ void ControllerEngine::scratchProcess(const int timerId) { // Reset accumulator m_intervalAccumulator[deck] = 0; - qDebug() << " old " << oldRate; - qDebug() << " new " << newRate; - qDebug() << " fabs" << fabs(trunc((m_rampTo[deck] - newRate) * 100000) / 100000); +#if SCRATCH_DEBUG_OUTPUT + qDebug() << "."; + qDebug() << " oldRate " << oldRate; + qDebug() << " newRate " << newRate; + qDebug() << " fabs " << fabs(trunc((m_rampTo[deck] - newRate) * 100000) / 100000); qDebug() << "."; +#endif // End scratching if we're ramping and the current rate is really close to the rampTo value if ((m_ramp[deck] && fabs(m_rampTo[deck] - newRate) <= 0.00001) || // or if we brake, spin back or softstart and have crossed over the desired value, @@ -1433,6 +1450,9 @@ void ControllerEngine::scratchProcess(const int timerId) { m_ramp[deck] = false; if (m_brakeActive[deck] || m_spinbackActive[deck]) { +#if SCRATCH_DEBUG_OUTPUT + qDebug() << " brake || spinback, stop scratching, stop deck"; +#endif // If in brake mode, set scratch2 rate to 0 and stop the deck. pScratch2->slotSet(0.0); stopDeck(group); @@ -1452,8 +1472,10 @@ void ControllerEngine::scratchProcess(const int timerId) { m_brakeActive[deck] = false; m_spinbackActive[deck] = false; m_softStartActive[deck] = false; +#if SCRATCH_DEBUG_OUTPUT qDebug() << " DONE scratching"; - qDebug() << "."; + qDebug() << " ."; +#endif } } @@ -1548,7 +1570,8 @@ void ControllerEngine::softTakeoverIgnoreNextValue( -------- ------------------------------------------------------ */ void ControllerEngine::spinback( const int deck, bool activate, const double factor, const double rate) { - qDebug() << " init spinback"; + qDebug() << "ControllerEngine::spinback(deck:" << deck << ", activate:" << activate + << ", factor:" << factor << ", rate:" << rate; brake(deck, activate, -factor, rate); } @@ -1559,7 +1582,8 @@ void ControllerEngine::spinback( Output: - -------- ------------------------------------------------------ */ void ControllerEngine::brake(const int deck, bool activate, double factor, const double rate) { - qDebug() << " init brake"; + qDebug() << "ControllerEngine::brake(deck:" << deck << ", activate:" << activate + << ", factor:" << factor << ", rate:" << rate; // PlayerManager::groupForDeck is 0-indexed. const QString group = PlayerManager::groupForDeck(deck - 1); // enable/disable scratch2 mode @@ -1650,7 +1674,8 @@ void ControllerEngine::brake(const int deck, bool activate, double factor, const Output: - -------- ------------------------------------------------------ */ void ControllerEngine::softStart(const int deck, bool activate, double factor) { - qDebug() << " init softStart"; + qDebug() << "ControllerEngine::softStart(deck:" << deck << ", activate:" << activate + << ", factor:" << factor; // PlayerManager::groupForDeck is 0-indexed. const QString group = PlayerManager::groupForDeck(deck - 1); From 045ce55c3e0cd9e068690823064c6a65dbd31944 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Wed, 6 Apr 2022 18:27:49 +0200 Subject: [PATCH 042/201] Scratching: clean up debug output --- src/controllers/controllerengine.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 59a1ec5e25a7..f549fffe1e91 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1376,6 +1376,7 @@ void ControllerEngine::scratchProcess(const int timerId) { #if SCRATCH_DEBUG_OUTPUT qDebug() << " ."; qDebug() << " ControllerEngine::scratchProcess"; + qDebug() << " ."; #endif const int deck = m_scratchTimers[timerId]; // PlayerManager::groupForDeck is 0-indexed. @@ -1394,24 +1395,23 @@ void ControllerEngine::scratchProcess(const int timerId) { // If we're ramping to end scratching and the wheel hasn't been turned very // recently (spinback after lift-off,) feed fixed data - qDebug() << "."; if (m_ramp[deck] && !m_softStartActive[deck] && ((mixxx::Time::elapsed() - m_lastMovement[deck]) >= mixxx::Duration::fromMillis(1))) { #if SCRATCH_DEBUG_OUTPUT - qDebug() << " ramp && !softStart"; + qDebug() << " ramp && !softStart"; #endif filter->observation(m_rampTo[deck] * m_rampFactor[deck]); // Once this code path is run, latch so it always runs until reset //m_lastMovement[deck] += mixxx::Duration::fromSeconds(1); } else if (m_softStartActive[deck]) { #if SCRATCH_DEBUG_OUTPUT - qDebug() << " softStart"; + qDebug() << " softStart"; #endif // pretend we have moved by (desired rate*default distance) filter->observation(m_rampTo[deck] * kAlphaBetaDt); } else { #if SCRATCH_DEBUG_OUTPUT - qDebug() << " else"; + qDebug() << " else"; #endif // This will (and should) be 0 if no net ticks have been accumulated // (i.e. the wheel is stopped) @@ -1431,11 +1431,11 @@ void ControllerEngine::scratchProcess(const int timerId) { m_intervalAccumulator[deck] = 0; #if SCRATCH_DEBUG_OUTPUT - qDebug() << "."; - qDebug() << " oldRate " << oldRate; - qDebug() << " newRate " << newRate; - qDebug() << " fabs " << fabs(trunc((m_rampTo[deck] - newRate) * 100000) / 100000); - qDebug() << "."; + qDebug() << " ."; + qDebug() << " oldRate " << oldRate; + qDebug() << " newRate " << newRate; + qDebug() << " fabs " << fabs(trunc((m_rampTo[deck] - newRate) * 100000) / 100000); + qDebug() << " ."; #endif // End scratching if we're ramping and the current rate is really close to the rampTo value if ((m_ramp[deck] && fabs(m_rampTo[deck] - newRate) <= 0.00001) || From 50258bd95317c40cbe682f669deed8f221fa6e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Wed, 6 Apr 2022 20:03:49 +0200 Subject: [PATCH 043/201] Don't reset xfader when auto DJ is disabled This fixes the volume change when the xfader is configured in const power mode, bug https://bugs.launchpad.net/bugs/1965298 --- src/library/autodj/autodjprocessor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 8d76ee88b04f..e8468d492641 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -553,7 +553,6 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::toggleAutoDJ(bool enable) { &AutoDJProcessor::crossfaderChanged); deck1->disconnect(this); deck2->disconnect(this); - m_pCOCrossfader->set(0); emitAutoDJStateChanged(m_eState); } return ADJ_OK; From 366a8a2c6feb17408ae5d8e41f20bfec0c170cb7 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 7 Apr 2022 13:42:37 +0200 Subject: [PATCH 044/201] ControllerEngine: remove obsolote comment --- src/controllers/controllerengine.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index f549fffe1e91..bbbb46da80fb 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -1641,8 +1641,6 @@ void ControllerEngine::brake(const int deck, bool activate, double factor, const } stopScratchTimer(timerId); - // If we want to brake or spin back and are currently softStart'ing, stop it. - // setup timer and set scratch2 timerId = startTimer(kScratchTimerMs); m_scratchTimers[timerId] = deck; From 5d1e248015895161df785d604ef7c52a8febae51 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Sun, 10 Apr 2022 14:40:20 +0200 Subject: [PATCH 045/201] Samplers, crate, playlists: fix storing import/export paths --- src/library/baseplaylistfeature.cpp | 6 +++--- src/library/crate/cratefeature.cpp | 6 +++--- src/mixer/samplerbank.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/library/baseplaylistfeature.cpp b/src/library/baseplaylistfeature.cpp index e74c6f416520..5acecd7cabe5 100644 --- a/src/library/baseplaylistfeature.cpp +++ b/src/library/baseplaylistfeature.cpp @@ -365,7 +365,7 @@ void BasePlaylistFeature::slotImportPlaylist() { // Update the import/export playlist directory QString fileDirectory(playlistFile); - fileDirectory.truncate(playlistFile.lastIndexOf(QDir::separator())); + fileDirectory.truncate(playlistFile.lastIndexOf("/")); m_pConfig->set(kConfigKeyLastImportExportPlaylistDirectory, ConfigValue(fileDirectory)); @@ -412,7 +412,7 @@ void BasePlaylistFeature::slotCreateImportPlaylist() { // Set last import directory QString fileDirectory(playlistFiles.first()); - fileDirectory.truncate(playlistFiles.first().lastIndexOf(QDir::separator())); + fileDirectory.truncate(playlistFiles.first().lastIndexOf("/")); m_pConfig->set(kConfigKeyLastImportExportPlaylistDirectory, ConfigValue(fileDirectory)); @@ -483,7 +483,7 @@ void BasePlaylistFeature::slotExportPlaylist() { // Update the import/export playlist directory QString fileDirectory(fileLocation); - fileDirectory.truncate(fileLocation.lastIndexOf(QDir::separator())); + fileDirectory.truncate(fileLocation.lastIndexOf("/")); m_pConfig->set(kConfigKeyLastImportExportPlaylistDirectory, ConfigValue(fileDirectory)); diff --git a/src/library/crate/cratefeature.cpp b/src/library/crate/cratefeature.cpp index 67d0962cd4db..b2651e0ce73b 100644 --- a/src/library/crate/cratefeature.cpp +++ b/src/library/crate/cratefeature.cpp @@ -574,7 +574,7 @@ void CrateFeature::slotImportPlaylist() { // Update the import/export crate directory QString fileDirectory(playlistFile); - fileDirectory.truncate(playlistFile.lastIndexOf(QDir::separator())); + fileDirectory.truncate(playlistFile.lastIndexOf("/")); m_pConfig->set(kConfigKeyLastImportExportCrateDirectoryKey, ConfigValue(fileDirectory)); @@ -614,7 +614,7 @@ void CrateFeature::slotCreateImportCrate() { // Set last import directory QString fileDirectory(playlistFiles.first()); - fileDirectory.truncate(playlistFiles.first().lastIndexOf(QDir::separator())); + fileDirectory.truncate(playlistFiles.first().lastIndexOf("/")); m_pConfig->set(kConfigKeyLastImportExportCrateDirectoryKey, ConfigValue(fileDirectory)); @@ -706,7 +706,7 @@ void CrateFeature::slotExportPlaylist() { } // Update the import/export crate directory QString fileDirectory(fileLocation); - fileDirectory.truncate(fileLocation.lastIndexOf(QDir::separator())); + fileDirectory.truncate(fileLocation.lastIndexOf("/")); m_pConfig->set(kConfigKeyLastImportExportCrateDirectoryKey, ConfigValue(fileDirectory)); diff --git a/src/mixer/samplerbank.cpp b/src/mixer/samplerbank.cpp index 732f58ec0bb5..fc4b18a7f0a3 100644 --- a/src/mixer/samplerbank.cpp +++ b/src/mixer/samplerbank.cpp @@ -73,7 +73,7 @@ void SamplerBank::slotSaveSamplerBank(double v) { // Update the import/export directory QString fileDirectory(samplerBankPath); - fileDirectory.truncate(samplerBankPath.lastIndexOf(QDir::separator())); + fileDirectory.truncate(samplerBankPath.lastIndexOf("/")); m_pConfig->set(kConfigkeyLastImportExportDirectory, ConfigValue(fileDirectory)); @@ -154,7 +154,7 @@ void SamplerBank::slotLoadSamplerBank(double v) { // Update the import/export directory QString fileDirectory(samplerBankPath); - fileDirectory.truncate(samplerBankPath.lastIndexOf(QDir::separator())); + fileDirectory.truncate(samplerBankPath.lastIndexOf("/")); m_pConfig->set(kConfigkeyLastImportExportDirectory, ConfigValue(fileDirectory)); From c710a526139eae36cc1b236a6e933a644626fd9e Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 11 Apr 2022 01:32:12 +0200 Subject: [PATCH 046/201] Prefences: prevent false-positive 'dirty' mapping on first show --- src/controllers/dlgprefcontroller.cpp | 22 ++++++++++++++-------- src/controllers/dlgprefcontroller.h | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 8d2db410a8f4..4f52898bc253 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -45,6 +45,7 @@ DlgPrefController::DlgPrefController(QWidget* parent, m_pInputProxyModel(nullptr), m_pOutputTableModel(nullptr), m_pOutputProxyModel(nullptr), + m_GuiInitialized(false), m_bDirty(false) { m_ui.setupUi(this); // Create text color for the file and wiki links @@ -451,8 +452,8 @@ void DlgPrefController::slotUpdate() { enumeratePresets(m_pControllerManager->getConfiguredPresetFileForDevice( m_pController->getName())); - // enumeratePresets will check the m_ui.chkEnabledDevice checkbox if - // there is a valid mapping saved in the mixxx.cfg file. However, the + // enumeratePresets calls slotPresetSelected which will check the m_ui.chkEnabledDevice + // checkbox if there is a valid mapping saved in the mixxx.cfg file. However, the // checkbox should only be checked if the device is currently enabled. m_ui.chkEnabledDevice->setChecked(m_pController->isOpen()); @@ -462,6 +463,9 @@ void DlgPrefController::slotUpdate() { m_ui.btnLearningWizard->setEnabled(isMappable); m_ui.inputMappingsTab->setEnabled(isMappable); m_ui.outputMappingsTab->setEnabled(isMappable); + // When slotUpdate() is run for the first time, this bool keeps slotPresetSelected() + // from setting a false-postive 'dirty' flag when updating the fresh GUI. + m_GuiInitialized = true; } void DlgPrefController::slotResetToDefaults() { @@ -532,21 +536,23 @@ QString DlgPrefController::presetPathFromIndex(int index) const { void DlgPrefController::slotPresetSelected(int chosenIndex) { QString presetPath = presetPathFromIndex(chosenIndex); - if (presetPath.isEmpty()) { - // User picked "No Preset" item + if (presetPath.isEmpty()) { // User picked "No Preset" item m_ui.chkEnabledDevice->setEnabled(false); if (m_ui.chkEnabledDevice->isChecked()) { m_ui.chkEnabledDevice->setChecked(false); - setDirty(true); + if (m_GuiInitialized) { + setDirty(true); + } } - } else { - // User picked a preset + } else { // User picked a preset m_ui.chkEnabledDevice->setEnabled(true); if (!m_ui.chkEnabledDevice->isChecked()) { m_ui.chkEnabledDevice->setChecked(true); - setDirty(true); + if (m_GuiInitialized) { + setDirty(true); + } } } diff --git a/src/controllers/dlgprefcontroller.h b/src/controllers/dlgprefcontroller.h index 6fd4f3e89861..115191c0e641 100644 --- a/src/controllers/dlgprefcontroller.h +++ b/src/controllers/dlgprefcontroller.h @@ -117,5 +117,6 @@ class DlgPrefController : public DlgPreferencePage { QSortFilterProxyModel* m_pInputProxyModel; ControllerOutputMappingTableModel* m_pOutputTableModel; QSortFilterProxyModel* m_pOutputProxyModel; + bool m_GuiInitialized; bool m_bDirty; }; From 91839074bf09078c975e17cbdf05bc065f85d468 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 11 Apr 2022 01:36:43 +0200 Subject: [PATCH 047/201] Prefences > Controller: remove redundant include --- src/controllers/dlgprefcontroller.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/dlgprefcontroller.cpp b/src/controllers/dlgprefcontroller.cpp index 4f52898bc253..879aaccc93d6 100644 --- a/src/controllers/dlgprefcontroller.cpp +++ b/src/controllers/dlgprefcontroller.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "controllers/controller.h" #include "controllers/controllerlearningeventfilter.h" From 9ebad0027aea469124e8e56a60e169266e1b9694 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Mon, 11 Apr 2022 11:42:28 +0200 Subject: [PATCH 048/201] Preferences > Controllers: don't connect pages' slotUpdate() twice --- src/controllers/dlgprefcontrollers.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/controllers/dlgprefcontrollers.cpp b/src/controllers/dlgprefcontrollers.cpp index 803059890e6d..e57560f60510 100644 --- a/src/controllers/dlgprefcontrollers.cpp +++ b/src/controllers/dlgprefcontrollers.cpp @@ -81,9 +81,6 @@ void DlgPrefControllers::slotOpenLocalFile(const QString& file) { } void DlgPrefControllers::slotUpdate() { - for (DlgPrefController* pControllerDlg : qAsConst(m_controllerPages)) { - pControllerDlg->slotUpdate(); - } } void DlgPrefControllers::slotCancel() { From 9c99aea1b1beeecc23bbfc60df389e6b83c30229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 00:37:45 +0200 Subject: [PATCH 049/201] Fix warning: variable length array used [-Wvla] --- src/util/sample.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/sample.cpp b/src/util/sample.cpp index eb744f83593e..6b96b423d039 100644 --- a/src/util/sample.cpp +++ b/src/util/sample.cpp @@ -71,7 +71,7 @@ CSAMPLE* SampleUtil::alloc(SINT size) { // 16-byte aligned. We record a pointer to the true start of the buffer // in the slack space as well so that we can free it correctly. const size_t alignment = kAlignment; - const size_t unaligned_size = sizeof(CSAMPLE[size]) + alignment; + const size_t unaligned_size = sizeof(CSAMPLE) * size + alignment; void* pUnaligned = std::malloc(unaligned_size); if (pUnaligned == NULL) { return NULL; @@ -85,7 +85,7 @@ CSAMPLE* SampleUtil::alloc(SINT size) { #endif } else { // Our platform already produces aligned pointers (or is an exotic target) - return new CSAMPLE[size]; + return static_cast(std::malloc(sizeof(CSAMPLE) * size)); } } From 221754ed118d1b270ea71b217e9321df53c5b552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 00:48:23 +0200 Subject: [PATCH 050/201] Update CAStreamBasicDescription to Version 1.1 2014-07-08 https://developer.apple.com/library/archive/samplecode/CoreAudioUtilityClasses/Introduction/Intro.html --- lib/apple/CADebugMacros.h | 95 +++--- lib/apple/CAMath.h | 88 +++--- lib/apple/CAStreamBasicDescription.cpp | 404 +++++++++++++++---------- lib/apple/CAStreamBasicDescription.h | 169 ++++++----- 4 files changed, 428 insertions(+), 328 deletions(-) diff --git a/lib/apple/CADebugMacros.h b/lib/apple/CADebugMacros.h index 1b11d3f172e7..39fa42e69e38 100644 --- a/lib/apple/CADebugMacros.h +++ b/lib/apple/CADebugMacros.h @@ -1,48 +1,48 @@ /* - File: CADebugMacros.h - Abstract: Part of CoreAudio Utility Classes - Version: 1.0.3 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2013 Apple Inc. All Rights Reserved. - + File: CADebugMacros.h + Abstract: Part of CoreAudio Utility Classes + Version: 1.1 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2014 Apple Inc. All Rights Reserved. + */ #if !defined(__CADebugMacros_h__) #define __CADebugMacros_h__ @@ -88,6 +88,7 @@ // that have been added purely to avert -wshorten64-32 warnings on 64 bit platforms. // For want of a better place to park this, we'll park it here. #define ToUInt32(X) ((UInt32)(X)) +#define ToSInt32(X) ((SInt32)(X)) #pragma mark Basic Definitions @@ -362,7 +363,7 @@ void LogWarning(const char *fmt, ...); // writes to syslog (and stderr if debug #define ThrowIfKernelError(inKernelError, inException, inMessage) \ { \ - unsigned int __Err = (inKernelError); \ + int __Err = (inKernelError); \ if(__Err != 0) \ { \ DebugMessageN1(inMessage ", Error: 0x%X", __Err); \ @@ -376,7 +377,7 @@ void LogWarning(const char *fmt, ...); // writes to syslog (and stderr if debug if(__Err != 0) \ { \ char __4CC[5] = CA4CCToCString(__Err); \ - DebugMessageN2(inMessage ", Error: %d (%s)", (int)__Err, __4CC); \ + DebugMessageN2(inMessage ", Error: %d (%s)", (int)__Err, __4CC); \ Throw(inException); \ } \ } @@ -541,7 +542,7 @@ void LogWarning(const char *fmt, ...); // writes to syslog (and stderr if debug #define ThrowIfKernelError(inKernelError, inException, inMessage) \ { \ - unsigned int __Err = (inKernelError); \ + int __Err = (inKernelError); \ if(__Err != 0) \ { \ Throw(inException); \ diff --git a/lib/apple/CAMath.h b/lib/apple/CAMath.h index 70ff343cec7e..ca7cd385a626 100644 --- a/lib/apple/CAMath.h +++ b/lib/apple/CAMath.h @@ -1,48 +1,48 @@ /* - File: CAMath.h - Abstract: Part of CoreAudio Utility Classes - Version: 1.0.3 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2013 Apple Inc. All Rights Reserved. - + File: CAMath.h + Abstract: Part of CoreAudio Utility Classes + Version: 1.1 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2014 Apple Inc. All Rights Reserved. + */ #ifndef __CAMath_h__ #define __CAMath_h__ diff --git a/lib/apple/CAStreamBasicDescription.cpp b/lib/apple/CAStreamBasicDescription.cpp index cd7c82e9ed85..645b83967a88 100644 --- a/lib/apple/CAStreamBasicDescription.cpp +++ b/lib/apple/CAStreamBasicDescription.cpp @@ -1,48 +1,48 @@ /* - File: CAStreamBasicDescription.cpp - Abstract: CAStreamBasicDescription.h - Version: 1.0.3 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2013 Apple Inc. All Rights Reserved. - + File: CAStreamBasicDescription.cpp + Abstract: CAStreamBasicDescription.h + Version: 1.1 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2014 Apple Inc. All Rights Reserved. + */ #include "CAStreamBasicDescription.h" #include "CAMath.h" @@ -55,37 +55,41 @@ #pragma mark This file needs to compile on earlier versions of the OS, so please keep that in mind when editing it -char *CAStringForOSType (OSType t, char *writeLocation) +char *CAStringForOSType (OSType t, char *writeLocation, size_t bufsize) { - char *p = writeLocation; - unsigned char str[4] = {0}, *q = str; - *(UInt32 *)str = CFSwapInt32HostToBig(t); - - bool hasNonPrint = false; - for (int i = 0; i < 4; ++i) { - if (!(isprint(*q) && *q != '\\')) { - hasNonPrint = true; - break; + if (bufsize > 0) { + char *p = writeLocation, *pend = writeLocation + bufsize; + union { UInt32 i; unsigned char str[4]; } u; + unsigned char *q = u.str; + u.i = CFSwapInt32HostToBig(t); + + bool hasNonPrint = false; + for (int i = 0; i < 4; ++i) { + if (!(isprint(*q) && *q != '\\')) { + hasNonPrint = true; + break; + } + q++; } - q++; - } - q = str; - - if (hasNonPrint) - p += sprintf (p, "0x"); - else - *p++ = '\''; + q = u.str; - for (int i = 0; i < 4; ++i) { - if (hasNonPrint) { - p += sprintf(p, "%02X", *q++); - } else { - *p++ = *q++; + if (hasNonPrint) + p += snprintf (p, pend - p, "0x"); + else if (p < pend) + *p++ = '\''; + + for (int i = 0; i < 4 && p < pend; ++i) { + if (hasNonPrint) { + p += snprintf(p, pend - p, "%02X", *q++); + } else { + *p++ = *q++; + } } + if (!hasNonPrint && p < pend) + *p++ = '\''; + if (p >= pend) p -= 1; + *p = '\0'; } - if (!hasNonPrint) - *p++ = '\''; - *p = '\0'; return writeLocation; } @@ -119,18 +123,54 @@ CAStreamBasicDescription::CAStreamBasicDescription(double inSampleRate, UInt32 mReserved = 0; } -char *CAStreamBasicDescription::AsString(char *buf, size_t _bufsize) const +char *CAStreamBasicDescription::AsString(char *buf, size_t _bufsize, bool brief /*=false*/) const { int bufsize = (int)_bufsize; // must be signed to protect against overflow char *theBuffer = buf; int nc; char formatID[24]; - CAStringForOSType (mFormatID, formatID); - nc = snprintf(buf, bufsize, "%2d ch, %6.0f Hz, %s (0x%08X) ", (int)NumberChannels(), mSampleRate, formatID, (int)mFormatFlags); + CAStringForOSType(mFormatID, formatID, sizeof(formatID)); + if (brief) { + CommonPCMFormat com; + bool interleaved; + if (IdentifyCommonPCMFormat(com, &interleaved) && com != kPCMFormatOther) { + const char *desc; + switch (com) { + case kPCMFormatInt16: + desc = "Int16"; + break; + case kPCMFormatFixed824: + desc = "Int8.24"; + break; + case kPCMFormatFloat32: + desc = "Float32"; + break; + case kPCMFormatFloat64: + desc = "Float64"; + break; + default: + desc = NULL; + break; + } + if (desc) { + const char *inter =""; + if (mChannelsPerFrame > 1) + inter = !interleaved ? ", non-inter" : ", inter"; + snprintf(buf, static_cast(bufsize), "%2d ch, %6.0f Hz, %s%s", (int)mChannelsPerFrame, mSampleRate, desc, inter); + return theBuffer; + } + } + if (mChannelsPerFrame == 0 && mSampleRate == 0.0 && mFormatID == 0) { + snprintf(buf, static_cast(bufsize), "%2d ch, %6.0f Hz", (int)mChannelsPerFrame, mSampleRate); + return theBuffer; + } + } + + nc = snprintf(buf, static_cast(bufsize), "%2d ch, %6.0f Hz, %s (0x%08X) ", (int)NumberChannels(), mSampleRate, formatID, (int)mFormatFlags); buf += nc; if ((bufsize -= nc) <= 0) goto exit; if (mFormatID == kAudioFormatLinearPCM) { bool isInt = !(mFormatFlags & kLinearPCMFormatFlagIsFloat); - int wordSize = SampleWordSize(); + int wordSize = static_cast(SampleWordSize()); const char *endian = (wordSize > 1) ? ((mFormatFlags & kLinearPCMFormatFlagIsBigEndian) ? " big-endian" : " little-endian" ) : ""; const char *sign = isInt ? @@ -156,11 +196,11 @@ char *CAStreamBasicDescription::AsString(char *buf, size_t _bufsize) const else snprintf(bitdepth, sizeof(bitdepth), "%d", (int)mBitsPerChannel); - /* nc =*/ snprintf(buf, bufsize, "%s-bit%s%s %s%s%s%s%s", + /*nc =*/ snprintf(buf, static_cast(bufsize), "%s-bit%s%s %s%s%s%s%s", bitdepth, endian, sign, floatInt, commaSpace, packed, align, deinter); // buf += nc; if ((bufsize -= nc) <= 0) goto exit; - } else if (mFormatID == 'alac') { // kAudioFormatAppleLossless + } else if (mFormatID == kAudioFormatAppleLossless) { int sourceBits = 0; switch (mFormatFlags) { @@ -178,15 +218,15 @@ char *CAStreamBasicDescription::AsString(char *buf, size_t _bufsize) const break; } if (sourceBits) - nc = snprintf(buf, bufsize, "from %d-bit source, ", sourceBits); + nc = snprintf(buf, static_cast(bufsize), "from %d-bit source, ", sourceBits); else - nc = snprintf(buf, bufsize, "from UNKNOWN source bit depth, "); + nc = snprintf(buf, static_cast(bufsize), "from UNKNOWN source bit depth, "); buf += nc; if ((bufsize -= nc) <= 0) goto exit; - /* nc =*/ snprintf(buf, bufsize, "%d frames/packet", (int)mFramesPerPacket); + /*nc =*/ snprintf(buf, static_cast(bufsize), "%d frames/packet", (int)mFramesPerPacket); // buf += nc; if ((bufsize -= nc) <= 0) goto exit; } else - /* nc =*/ snprintf(buf, bufsize, "%d bits/channel, %d bytes/packet, %d frames/packet, %d bytes/frame", + /*nc =*/ snprintf(buf, static_cast(bufsize), "%d bits/channel, %d bytes/packet, %d frames/packet, %d bytes/frame", (int)mBitsPerChannel, (int)mBytesPerPacket, (int)mFramesPerPacket, (int)mBytesPerFrame); exit: return theBuffer; @@ -286,7 +326,7 @@ void CAStreamBasicDescription::GetSimpleName(const AudioStreamBasicDescription& { int theCharactersWritten = snprintf(outName, inMaxNameLength, "%.0f ", inDescription.mSampleRate); outName += theCharactersWritten; - inMaxNameLength -= theCharactersWritten; + inMaxNameLength -= static_cast(theCharactersWritten); } switch(inDescription.mFormatID) @@ -520,49 +560,73 @@ bool operator<(const AudioStreamBasicDescription& x, const AudioStreamBasicDescr return theAnswer; } +void CAStreamBasicDescription::ModifyFormatFlagsForMatching(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y, UInt32& xFlags, UInt32& yFlags, bool converterOnly ) +{ + // match wildcards + if (x.mFormatID == 0 || y.mFormatID == 0 || xFlags == 0 || yFlags == 0) + { + // Obliterate all flags. + xFlags = yFlags = 0; + return; + } + + if (x.mFormatID == kAudioFormatLinearPCM) { + // knock off the all clear flag + xFlags = xFlags & ~kAudioFormatFlagsAreAllClear; + yFlags = yFlags & ~kAudioFormatFlagsAreAllClear; + + // if both kAudioFormatFlagIsPacked bits are set, then we don't care about the kAudioFormatFlagIsAlignedHigh bit. + if (xFlags & yFlags & kAudioFormatFlagIsPacked) { + xFlags = xFlags & ~static_cast(kAudioFormatFlagIsAlignedHigh); + yFlags = yFlags & ~static_cast(kAudioFormatFlagIsAlignedHigh); + } + + // if both kAudioFormatFlagIsFloat bits are set, then we don't care about the kAudioFormatFlagIsSignedInteger bit. + if (xFlags & yFlags & kAudioFormatFlagIsFloat) { + xFlags = xFlags & ~static_cast(kAudioFormatFlagIsSignedInteger); + yFlags = yFlags & ~static_cast(kAudioFormatFlagIsSignedInteger); + } + + // if the bit depth is 8 bits or less and the format is packed, we don't care about endianness + if((x.mBitsPerChannel <= 8) && ((xFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) + { + xFlags = xFlags & ~static_cast(kAudioFormatFlagIsBigEndian); + } + if((y.mBitsPerChannel <= 8) && ((yFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) + { + yFlags = yFlags & ~static_cast(kAudioFormatFlagIsBigEndian); + } + + // if the number of channels is 1, we don't care about non-interleavedness + if (x.mChannelsPerFrame == 1 && y.mChannelsPerFrame == 1) { + xFlags &= ~static_cast(kLinearPCMFormatFlagIsNonInterleaved); + yFlags &= ~static_cast(kLinearPCMFormatFlagIsNonInterleaved); + } + + if (converterOnly) { + CAStreamBasicDescription cas_x = CAStreamBasicDescription(x); + CAStreamBasicDescription cas_y = CAStreamBasicDescription(y); + if (!cas_x.PackednessIsSignificant() && !cas_y.PackednessIsSignificant()) { + xFlags &= ~static_cast(kAudioFormatFlagIsPacked); + yFlags &= ~static_cast(kAudioFormatFlagIsPacked); + } + if (!cas_x.AlignmentIsSignificant() && !cas_y.AlignmentIsSignificant()) { + xFlags &= ~static_cast(kAudioFormatFlagIsAlignedHigh); + yFlags &= ~static_cast(kAudioFormatFlagIsAlignedHigh); + } + // We don't care about whether the streams are mixable in this case + xFlags &= ~static_cast(kAudioFormatFlagIsNonMixable); + yFlags &= ~static_cast(kAudioFormatFlagIsNonMixable); + } + } +} + static bool MatchFormatFlags(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y) { UInt32 xFlags = x.mFormatFlags; UInt32 yFlags = y.mFormatFlags; - // match wildcards - if (x.mFormatID == 0 || y.mFormatID == 0 || xFlags == 0 || yFlags == 0) - return true; - - if (x.mFormatID == kAudioFormatLinearPCM) - { - // knock off the all clear flag - xFlags = xFlags & ~kAudioFormatFlagsAreAllClear; - yFlags = yFlags & ~kAudioFormatFlagsAreAllClear; - - // if both kAudioFormatFlagIsPacked bits are set, then we don't care about the kAudioFormatFlagIsAlignedHigh bit. - if (xFlags & yFlags & kAudioFormatFlagIsPacked) { - xFlags = xFlags & ~kAudioFormatFlagIsAlignedHigh; - yFlags = yFlags & ~kAudioFormatFlagIsAlignedHigh; - } - - // if both kAudioFormatFlagIsFloat bits are set, then we don't care about the kAudioFormatFlagIsSignedInteger bit. - if (xFlags & yFlags & kAudioFormatFlagIsFloat) { - xFlags = xFlags & ~kAudioFormatFlagIsSignedInteger; - yFlags = yFlags & ~kAudioFormatFlagIsSignedInteger; - } - - // if the bit depth is 8 bits or less and the format is packed, we don't care about endianness - if((x.mBitsPerChannel <= 8) && ((xFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) - { - xFlags = xFlags & ~kAudioFormatFlagIsBigEndian; - } - if((y.mBitsPerChannel <= 8) && ((yFlags & kAudioFormatFlagIsPacked) == kAudioFormatFlagIsPacked)) - { - yFlags = yFlags & ~kAudioFormatFlagIsBigEndian; - } - - // if the number of channels is 1, we don't care about non-interleavedness - if (x.mChannelsPerFrame == 1 && y.mChannelsPerFrame == 1) { - xFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; - yFlags &= ~kLinearPCMFormatFlagIsNonInterleaved; - } - } + CAStreamBasicDescription::ModifyFormatFlagsForMatching(x, y, xFlags, yFlags, false); return xFlags == yFlags; } @@ -575,29 +639,35 @@ bool operator==(const AudioStreamBasicDescription& x, const AudioStreamBasicDesc #define MATCH(name) ((x.name) == 0 || (y.name) == 0 || (x.name) == (y.name)) return - // check the sample rate - (fiszero(x.mSampleRate) || fiszero(y.mSampleRate) || fequal(x.mSampleRate, y.mSampleRate)) - - // check the format ids - && MATCH(mFormatID) - - // check the format flags - && MatchFormatFlags(x, y) - - // check the bytes per packet - && MATCH(mBytesPerPacket) - - // check the frames per packet - && MATCH(mFramesPerPacket) - - // check the bytes per frame - && MATCH(mBytesPerFrame) - - // check the channels per frame - && MATCH(mChannelsPerFrame) - - // check the channels per frame - && MATCH(mBitsPerChannel) ; + // check all but the format flags + CAStreamBasicDescription::FlagIndependentEquivalence(x, y) + // check the format flags + && MatchFormatFlags(x, y); +} + +bool CAStreamBasicDescription::FlagIndependentEquivalence(const AudioStreamBasicDescription &x, const AudioStreamBasicDescription &y) +{ + return + // check the sample rate + (fiszero(x.mSampleRate) || fiszero(y.mSampleRate) || fequal(x.mSampleRate, y.mSampleRate)) + + // check the format ids + && MATCH(mFormatID) + + // check the bytes per packet + && MATCH(mBytesPerPacket) + + // check the frames per packet + && MATCH(mFramesPerPacket) + + // check the bytes per frame + && MATCH(mBytesPerFrame) + + // check the channels per frame + && MATCH(mChannelsPerFrame) + + // check the channels per frame + && MATCH(mBitsPerChannel) ; } bool CAStreamBasicDescription::IsEqual(const AudioStreamBasicDescription &other, bool interpretingWildcards) const @@ -607,6 +677,19 @@ bool CAStreamBasicDescription::IsEqual(const AudioStreamBasicDescription &other, return memcmp(this, &other, offsetof(AudioStreamBasicDescription, mReserved)) == 0; } +bool CAStreamBasicDescription::IsFunctionallyEquivalent(const AudioStreamBasicDescription &x, const AudioStreamBasicDescription &y) +{ + UInt32 xFlags = x.mFormatFlags, yFlags = y.mFormatFlags; + CAStreamBasicDescription::ModifyFormatFlagsForMatching(x, y, xFlags, yFlags, true); + + return + // check all but the format flags + CAStreamBasicDescription::FlagIndependentEquivalence(x, y) + // check the format flags with converter focus + && (xFlags == yFlags); + +} + bool SanityCheck(const AudioStreamBasicDescription& x) { // This function returns false if there are sufficiently insane values in any field. @@ -649,11 +732,11 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic #endif } if (p[0] == 'F') { - pcmFlags = (pcmFlags & ~kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsFloat; + pcmFlags = (pcmFlags & ~static_cast(kAudioFormatFlagIsSignedInteger)) | kAudioFormatFlagIsFloat; ++p; } else { if (p[0] == 'U') { - pcmFlags &= ~kAudioFormatFlagIsSignedInteger; + pcmFlags &= ~static_cast(kAudioFormatFlagIsSignedInteger); ++p; } if (p[0] == 'I') @@ -677,7 +760,7 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic if (*++p != 'x') return false; int x; if (sscanf(++p, "%02X", &x) != 1) return false; - buf[i] = x; + buf[i] = static_cast(x); p += 2; } } @@ -688,7 +771,8 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic --p; } - fmt.mFormatID = CFSwapInt32BigToHost(*(UInt32 *)buf); + memcpy(&fmt.mFormatID, buf, 4); + fmt.mFormatID = CFSwapInt32BigToHost(fmt.mFormatID); } } @@ -697,9 +781,9 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic fmt.mFormatFlags = pcmFlags; fmt.mFramesPerPacket = 1; fmt.mChannelsPerFrame = 1; - int bitdepth = 0, fracbits = 0; + UInt32 bitdepth = 0, fracbits = 0; while (isdigit(*p)) - bitdepth = 10 * bitdepth + *p++ - '0'; + bitdepth = 10 * bitdepth + static_cast(*p++ - '0'); if (*p == '.') { ++p; if (!isdigit(*p)) { @@ -707,7 +791,7 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic goto Bail; } while (isdigit(*p)) - fracbits = 10 * fracbits + *p++ - '0'; + fracbits = 10 * fracbits + static_cast(*p++ - '0'); bitdepth += fracbits; fmt.mFormatFlags |= (fracbits << kLinearPCMFormatFlagsSampleFractionShift); } @@ -715,7 +799,7 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic fmt.mBytesPerPacket = fmt.mBytesPerFrame = (bitdepth + 7) / 8; if (bitdepth & 7) { // assume unpacked. (packed odd bit depths are describable but not supported in AudioConverter.) - fmt.mFormatFlags &= ~kLinearPCMFormatFlagIsPacked; + fmt.mFormatFlags &= ~static_cast(kLinearPCMFormatFlagIsPacked); // alignment matters; default to high-aligned. use ':L_' for low. fmt.mFormatFlags |= kLinearPCMFormatFlagIsAlignedHigh; } @@ -730,11 +814,11 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic while (true) { char c = *++p; if (c >= '0' && c <= '9') - flags = (flags << 4) | (c - '0'); + flags = (flags << 4) | static_cast(c - '0'); else if (c >= 'A' && c <= 'F') - flags = (flags << 4) | (c - 'A' + 10); + flags = (flags << 4) | static_cast(c - 'A' + 10); else if (c >= 'a' && c <= 'f') - flags = (flags << 4) | (c - 'a' + 10); + flags = (flags << 4) | static_cast(c - 'a' + 10); else break; } fmt.mFormatFlags = flags; @@ -742,21 +826,21 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic if (*p == '#') { ++p; while (isdigit(*p)) - fmt.mFramesPerPacket = 10 * fmt.mFramesPerPacket + (*p++ - '0'); + fmt.mFramesPerPacket = 10 * fmt.mFramesPerPacket + static_cast(*p++ - '0'); } if (*p == ':') { ++p; - fmt.mFormatFlags &= ~kLinearPCMFormatFlagIsPacked; + fmt.mFormatFlags &= ~static_cast(kLinearPCMFormatFlagIsPacked); if (*p == 'L') - fmt.mFormatFlags &= ~kLinearPCMFormatFlagIsAlignedHigh; + fmt.mFormatFlags &= ~static_cast(kLinearPCMFormatFlagIsAlignedHigh); else if (*p == 'H') fmt.mFormatFlags |= kLinearPCMFormatFlagIsAlignedHigh; else goto Bail; ++p; - int bytesPerFrame = 0; + UInt32 bytesPerFrame = 0; while (isdigit(*p)) - bytesPerFrame = 10 * bytesPerFrame + (*p++ - '0'); + bytesPerFrame = 10 * bytesPerFrame + static_cast(*p++ - '0'); fmt.mBytesPerFrame = fmt.mBytesPerPacket = bytesPerFrame; } if (*p == ',') { @@ -764,7 +848,7 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic int ch = 0; while (isdigit(*p)) ch = 10 * ch + (*p++ - '0'); - fmt.mChannelsPerFrame = ch; + fmt.mChannelsPerFrame = static_cast(ch); if (*p == 'D') { ++p; if (fmt.mFormatID != kAudioFormatLinearPCM) { @@ -775,7 +859,7 @@ bool CAStreamBasicDescription::FromText(const char *inTextDesc, AudioStreamBasic } else { if (*p == 'I') ++p; // default if (fmt.mFormatID == kAudioFormatLinearPCM) - fmt.mBytesPerPacket = fmt.mBytesPerFrame *= ch; + fmt.mBytesPerPacket = fmt.mBytesPerFrame *= static_cast(ch); } } if (*p != '\0') { diff --git a/lib/apple/CAStreamBasicDescription.h b/lib/apple/CAStreamBasicDescription.h index 69860dde759e..8987144443c0 100644 --- a/lib/apple/CAStreamBasicDescription.h +++ b/lib/apple/CAStreamBasicDescription.h @@ -1,48 +1,48 @@ /* - File: CAStreamBasicDescription.h - Abstract: Part of CoreAudio Utility Classes - Version: 1.0.3 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2013 Apple Inc. All Rights Reserved. - + File: CAStreamBasicDescription.h + Abstract: Part of CoreAudio Utility Classes + Version: 1.1 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2014 Apple Inc. All Rights Reserved. + */ #ifndef __CAStreamBasicDescription_h__ #define __CAStreamBasicDescription_h__ @@ -61,7 +61,7 @@ #pragma mark This file needs to compile on more earlier versions of the OS, so please keep that in mind when editing it -extern char *CAStringForOSType (OSType t, char *writeLocation); +extern char *CAStringForOSType (OSType t, char *writeLocation, size_t bufsize); // define Leopard specific symbols for backward compatibility if applicable #if COREAUDIOTYPES_VERSION < 1050 @@ -90,27 +90,28 @@ enum { // It adds a number of convenience routines, but otherwise adds nothing // to the footprint of the original struct. //============================================================================= -class CAStreamBasicDescription : +class CAStreamBasicDescription : public AudioStreamBasicDescription { // Constants public: static const AudioStreamBasicDescription sEmpty; - + enum CommonPCMFormat { kPCMFormatOther = 0, kPCMFormatFloat32 = 1, kPCMFormatInt16 = 2, - kPCMFormatFixed824 = 3 + kPCMFormatFixed824 = 3, + kPCMFormatFloat64 = 4 }; - + // Construction/Destruction public: CAStreamBasicDescription(); - + CAStreamBasicDescription(const AudioStreamBasicDescription &desc); - + CAStreamBasicDescription( double inSampleRate, UInt32 inFormatID, UInt32 inBytesPerPacket, UInt32 inFramesPerPacket, UInt32 inBytesPerFrame, UInt32 inChannelsPerFrame, @@ -134,6 +135,10 @@ class CAStreamBasicDescription : wordsize = 4; mFormatFlags |= kAudioFormatFlagIsFloat; break; + case kPCMFormatFloat64: + wordsize = 8; + mFormatFlags |= kAudioFormatFlagIsFloat; + break; case kPCMFormatInt16: wordsize = 2; mFormatFlags |= kAudioFormatFlagIsSignedInteger; @@ -159,55 +164,55 @@ class CAStreamBasicDescription : { memcpy(this, &desc, sizeof(AudioStreamBasicDescription)); } - + bool FromText(const char *inTextDesc) { return FromText(inTextDesc, *this); } static bool FromText(const char *inTextDesc, AudioStreamBasicDescription &outDesc); // return true if parsing was successful - + static const char *sTextParsingUsageString; - + // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ // // interrogation - + bool IsPCM() const { return mFormatID == kAudioFormatLinearPCM; } - + bool PackednessIsSignificant() const { Assert(IsPCM(), "PackednessIsSignificant only applies for PCM"); return (SampleWordSize() << 3) != mBitsPerChannel; } - + bool AlignmentIsSignificant() const { return PackednessIsSignificant() || (mBitsPerChannel & 7) != 0; } - + bool IsInterleaved() const { - return !IsPCM() || !(mFormatFlags & kAudioFormatFlagIsNonInterleaved); + return !(mFormatFlags & kAudioFormatFlagIsNonInterleaved); } - + bool IsSignedInteger() const { return IsPCM() && (mFormatFlags & kAudioFormatFlagIsSignedInteger); } - + bool IsFloat() const { return IsPCM() && (mFormatFlags & kAudioFormatFlagIsFloat); } - + bool IsNativeEndian() const { return (mFormatFlags & kAudioFormatFlagIsBigEndian) == kAudioFormatFlagsNativeEndian; } - + // for sanity with interleaved/deinterleaved possibilities, never access mChannelsPerFrame, use these: - UInt32 NumberInterleavedChannels() const { return IsInterleaved() ? mChannelsPerFrame : 1; } + UInt32 NumberInterleavedChannels() const { return IsInterleaved() ? mChannelsPerFrame : 1; } UInt32 NumberChannelStreams() const { return IsInterleaved() ? 1 : mChannelsPerFrame; } UInt32 NumberChannels() const { return mChannelsPerFrame; } - UInt32 SampleWordSize() const { + UInt32 SampleWordSize() const { return (mBytesPerFrame > 0 && NumberInterleavedChannels()) ? mBytesPerFrame / NumberInterleavedChannels() : 0; } @@ -236,7 +241,7 @@ class CAStreamBasicDescription : if (wordsize % mChannelsPerFrame != 0) return false; wordsize /= mChannelsPerFrame; } - + if ((mFormatFlags & kAudioFormatFlagIsBigEndian) == kAudioFormatFlagsNativeEndian && wordsize * 8 == mBitsPerChannel) { // packed and native endian, good @@ -246,6 +251,8 @@ class CAStreamBasicDescription : return false; if (wordsize == 4) outFormat = kPCMFormatFloat32; + if (wordsize == 8) + outFormat = kPCMFormatFloat64; } else if (mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) { // signed int unsigned fracbits = (mFormatFlags & kLinearPCMFormatFlagsSampleFractionMask) >> kLinearPCMFormatFlagsSampleFractionShift; @@ -262,6 +269,10 @@ class CAStreamBasicDescription : CommonPCMFormat fmt; return IdentifyCommonPCMFormat(fmt, outIsInterleaved) && fmt == kPCMFormatFloat32; } + bool IsCommonFloat64(bool *outIsInterleaved=NULL) const { + CommonPCMFormat fmt; + return IdentifyCommonPCMFormat(fmt, outIsInterleaved) && fmt == kPCMFormatFloat64; + } bool IsCommonFixed824(bool *outIsInterleaved=NULL) const { CommonPCMFormat fmt; return IdentifyCommonPCMFormat(fmt, outIsInterleaved) && fmt == kPCMFormatFixed824; @@ -274,12 +285,12 @@ class CAStreamBasicDescription : // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ // // manipulation - + void SetCanonical(UInt32 nChannels, bool interleaved) // note: leaves sample rate untouched { mFormatID = kAudioFormatLinearPCM; - int sampleSize = SizeOf32(AudioSampleType); + UInt32 sampleSize = SizeOf32(AudioSampleType); mFormatFlags = kAudioFormatFlagsCanonical; mBitsPerChannel = 8 * sampleSize; mChannelsPerFrame = nChannels; @@ -339,7 +350,7 @@ class CAStreamBasicDescription : mFramesPerPacket = 1; if (interleaved) { mBytesPerPacket = mBytesPerFrame = nChannels * wordSize; - mFormatFlags &= ~kAudioFormatFlagIsNonInterleaved; + mFormatFlags &= ~static_cast(kAudioFormatFlagIsNonInterleaved); } else { mBytesPerPacket = mBytesPerFrame = wordSize; mFormatFlags |= kAudioFormatFlagIsNonInterleaved; @@ -349,15 +360,17 @@ class CAStreamBasicDescription : // _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ // // other - - bool IsEqual(const AudioStreamBasicDescription &other, bool interpretingWildcards=true) const; - + + bool IsEqual(const AudioStreamBasicDescription &other, bool interpretingWildcards=true) const; + static bool FlagIndependentEquivalence(const AudioStreamBasicDescription &x, const AudioStreamBasicDescription &y); + static bool IsFunctionallyEquivalent(const AudioStreamBasicDescription &x, const AudioStreamBasicDescription &y); + void Print() const { Print (stdout); } void Print(FILE* file) const { - PrintFormat (file, "", "AudioStreamBasicDescription:"); + PrintFormat (file, "", "AudioStreamBasicDescription:"); } void PrintFormat(FILE *f, const char *indent, const char *name) const { @@ -370,16 +383,16 @@ class CAStreamBasicDescription : fprintf(f, "%s%s %s", indent, name, AsString(buf, sizeof(buf))); } - char * AsString(char *buf, size_t bufsize) const; + char * AsString(char *buf, size_t bufsize, bool brief=false) const; - static void Print (const AudioStreamBasicDescription &inDesc) - { + static void Print (const AudioStreamBasicDescription &inDesc) + { CAStreamBasicDescription desc(inDesc); desc.Print (); } OSStatus Save(CFPropertyListRef *outData) const; - + OSStatus Restore(CFPropertyListRef &inData); // Operations @@ -389,6 +402,8 @@ class CAStreamBasicDescription : static void ResetFormat(AudioStreamBasicDescription& ioDescription); static void FillOutFormat(AudioStreamBasicDescription& ioDescription, const AudioStreamBasicDescription& inTemplateDescription); static void GetSimpleName(const AudioStreamBasicDescription& inDescription, char* outName, UInt32 inMaxNameLength, bool inAbbreviate, bool inIncludeSampleRate = false); + static void ModifyFormatFlagsForMatching(const AudioStreamBasicDescription& x, const AudioStreamBasicDescription& y, UInt32& xFlags, UInt32& yFlags, bool converterOnly); + #if CoreAudio_Debug static void PrintToLog(const AudioStreamBasicDescription& inDesc); #endif From 488a21961ec0588369ef4ef1fb82944eab6b07a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 01:09:38 +0200 Subject: [PATCH 051/201] Fix deprecation warning in lib/apple --- lib/apple/CAStreamBasicDescription.cpp | 14 +++++++------- lib/apple/CAStreamBasicDescription.h | 23 +++++++++-------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/lib/apple/CAStreamBasicDescription.cpp b/lib/apple/CAStreamBasicDescription.cpp index 645b83967a88..183c2e3fc9e6 100644 --- a/lib/apple/CAStreamBasicDescription.cpp +++ b/lib/apple/CAStreamBasicDescription.cpp @@ -238,11 +238,11 @@ void CAStreamBasicDescription::NormalizeLinearPCMFormat(AudioStreamBasicDescript if((ioDescription.mFormatID == kAudioFormatLinearPCM) && ((ioDescription.mFormatFlags & kIsNonMixableFlag) == 0)) { // the canonical linear PCM format - ioDescription.mFormatFlags = kAudioFormatFlagsCanonical; - ioDescription.mBytesPerPacket = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; + ioDescription.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + ioDescription.mBytesPerPacket = SizeOf32(Float32) * ioDescription.mChannelsPerFrame; ioDescription.mFramesPerPacket = 1; - ioDescription.mBytesPerFrame = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; - ioDescription.mBitsPerChannel = 8 * SizeOf32(AudioSampleType); + ioDescription.mBytesPerFrame = SizeOf32(Float32) * ioDescription.mChannelsPerFrame; + ioDescription.mBitsPerChannel = 8 * SizeOf32(Float32); } } @@ -265,10 +265,10 @@ void CAStreamBasicDescription::NormalizeLinearPCMFormat(bool inNativeEndian, Aud ioDescription.mFormatFlags |= kAudioFormatFlagIsBigEndian; #endif } - ioDescription.mBytesPerPacket = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; + ioDescription.mBytesPerPacket = SizeOf32(Float32) * ioDescription.mChannelsPerFrame; ioDescription.mFramesPerPacket = 1; - ioDescription.mBytesPerFrame = SizeOf32(AudioSampleType) * ioDescription.mChannelsPerFrame; - ioDescription.mBitsPerChannel = 8 * SizeOf32(AudioSampleType); + ioDescription.mBytesPerFrame = SizeOf32(Float32) * ioDescription.mChannelsPerFrame; + ioDescription.mBitsPerChannel = 8 * SizeOf32(Float32); } } diff --git a/lib/apple/CAStreamBasicDescription.h b/lib/apple/CAStreamBasicDescription.h index 8987144443c0..e92440ee3552 100644 --- a/lib/apple/CAStreamBasicDescription.h +++ b/lib/apple/CAStreamBasicDescription.h @@ -64,12 +64,7 @@ extern char *CAStringForOSType (OSType t, char *writeLocation, size_t bufsize); // define Leopard specific symbols for backward compatibility if applicable -#if COREAUDIOTYPES_VERSION < 1050 -typedef Float32 AudioSampleType; -enum { kAudioFormatFlagsCanonical = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked }; -#endif #if COREAUDIOTYPES_VERSION < 1051 -typedef Float32 AudioUnitSampleType; enum { kLinearPCMFormatFlagsSampleFractionShift = 7, kLinearPCMFormatFlagsSampleFractionMask = (0x3F << kLinearPCMFormatFlagsSampleFractionShift), @@ -290,8 +285,8 @@ class CAStreamBasicDescription : // note: leaves sample rate untouched { mFormatID = kAudioFormatLinearPCM; - UInt32 sampleSize = SizeOf32(AudioSampleType); - mFormatFlags = kAudioFormatFlagsCanonical; + UInt32 sampleSize = SizeOf32(Float32); + mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; mBitsPerChannel = 8 * sampleSize; mChannelsPerFrame = nChannels; mFramesPerPacket = 1; @@ -309,8 +304,8 @@ class CAStreamBasicDescription : UInt32 reqFormatFlags; UInt32 flagsMask = (kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsBigEndian | kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagsSampleFractionMask); bool interleaved = (mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; - unsigned sampleSize = SizeOf32(AudioSampleType); - reqFormatFlags = kAudioFormatFlagsCanonical; + unsigned sampleSize = SizeOf32(Float32); + reqFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; UInt32 reqFrameSize = interleaved ? (mChannelsPerFrame * sampleSize) : sampleSize; return ((mFormatFlags & flagsMask) == reqFormatFlags @@ -324,17 +319,17 @@ class CAStreamBasicDescription : { mFormatID = kAudioFormatLinearPCM; #if CA_PREFER_FIXED_POINT - mFormatFlags = kAudioFormatFlagsCanonical | (kAudioUnitSampleFractionBits << kLinearPCMFormatFlagsSampleFractionShift); + mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | (kAudioUnitSampleFractionBits << kLinearPCMFormatFlagsSampleFractionShift); #else - mFormatFlags = kAudioFormatFlagsCanonical; + mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; #endif mChannelsPerFrame = nChannels; mFramesPerPacket = 1; - mBitsPerChannel = 8 * SizeOf32(AudioUnitSampleType); + mBitsPerChannel = 8 * SizeOf32(Float32); if (interleaved) - mBytesPerPacket = mBytesPerFrame = nChannels * SizeOf32(AudioUnitSampleType); + mBytesPerPacket = mBytesPerFrame = nChannels * SizeOf32(Float32); else { - mBytesPerPacket = mBytesPerFrame = SizeOf32(AudioUnitSampleType); + mBytesPerPacket = mBytesPerFrame = SizeOf32(Float32); mFormatFlags |= kAudioFormatFlagIsNonInterleaved; } } From 019231f9ae451f8c5875ef3ec4230782b55b2d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 09:05:45 +0200 Subject: [PATCH 052/201] define undefined macros in lib/apple --- lib/apple/CAStreamBasicDescription.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/apple/CAStreamBasicDescription.cpp b/lib/apple/CAStreamBasicDescription.cpp index 183c2e3fc9e6..a90485d8da67 100644 --- a/lib/apple/CAStreamBasicDescription.cpp +++ b/lib/apple/CAStreamBasicDescription.cpp @@ -44,6 +44,11 @@ Copyright (C) 2014 Apple Inc. All Rights Reserved. */ + +#define DEBUG 0 +#define CoreAudio_Debug 0 +#define CA_PREFER_FIXED_POINT 0 + #include "CAStreamBasicDescription.h" #include "CAMath.h" From fdbb348f87cef99b59139e417c6a9dc7a3613528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 09:19:13 +0200 Subject: [PATCH 053/201] Update runbuffer lib/portaudio to master b94c49ee21a24a12d4fb35f184f89b5ee24cc494 This fixes the deprication warning of OSMemoryBarrier() --- lib/portaudio/pa_memorybarrier.h | 80 ++++++++++++++++++-------------- lib/portaudio/pa_ringbuffer.c | 14 +++--- lib/portaudio/pa_ringbuffer.h | 18 +++---- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/lib/portaudio/pa_memorybarrier.h b/lib/portaudio/pa_memorybarrier.h index 2879ce33adc4..6f07b603e975 100644 --- a/lib/portaudio/pa_memorybarrier.h +++ b/lib/portaudio/pa_memorybarrier.h @@ -30,13 +30,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ @@ -61,42 +61,52 @@ ****************/ #if defined(__APPLE__) -# include - /* Here are the memory barrier functions. Mac OS X only provides - full memory barriers, so the three types of barriers are the same, - however, these barriers are superior to compiler-based ones. */ -# define PaUtil_FullMemoryBarrier() OSMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier() +/* Support for the atomic library was added in C11. + */ +# if (__STDC_VERSION__ < 201112L) || defined(__STDC_NO_ATOMICS__) +# include + /* Here are the memory barrier functions. Mac OS X only provides + full memory barriers, so the three types of barriers are the same, + however, these barriers are superior to compiler-based ones. + These were deprecated in MacOS 10.12. */ +# define PaUtil_FullMemoryBarrier() OSMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier() +# else +# include +# define PaUtil_FullMemoryBarrier() atomic_thread_fence(memory_order_seq_cst) +# define PaUtil_ReadMemoryBarrier() atomic_thread_fence(memory_order_acquire) +# define PaUtil_WriteMemoryBarrier() atomic_thread_fence(memory_order_release) +# endif #elif defined(__GNUC__) /* GCC >= 4.1 has built-in intrinsics. We'll use those */ # if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) -# define PaUtil_FullMemoryBarrier() __sync_synchronize() -# define PaUtil_ReadMemoryBarrier() __sync_synchronize() -# define PaUtil_WriteMemoryBarrier() __sync_synchronize() +# define PaUtil_FullMemoryBarrier() __sync_synchronize() +# define PaUtil_ReadMemoryBarrier() __sync_synchronize() +# define PaUtil_WriteMemoryBarrier() __sync_synchronize() /* as a fallback, GCC understands volatile asm and "memory" to mean it * should not reorder memory read/writes */ /* Note that it is not clear that any compiler actually defines __PPC__, * it can probably removed safely. */ # elif defined( __ppc__ ) || defined( __powerpc__) || defined( __PPC__ ) -# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory") -# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory") -# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory") # elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || \ - defined( __i686__ ) || defined( __x86_64__ ) -# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory") -# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory") -# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory") + defined( __i686__ ) || defined( __x86_64__ ) +# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory") # else -# ifdef ALLOW_SMP_DANGERS -# warning Memory barriers not defined on this system or system unknown -# warning For SMP safety, you should fix this. -# define PaUtil_FullMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() -# else -# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. -# endif +# ifdef ALLOW_SMP_DANGERS +# warning Memory barriers not defined on this system or system unknown +# warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +# else +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# endif # endif #elif (_MSC_VER >= 1400) && !defined(_WIN32_WCE) # include @@ -117,12 +127,12 @@ # define PaUtil_WriteMemoryBarrier() _asm { lock add [esp], 0 } #else # ifdef ALLOW_SMP_DANGERS -# warning Memory barriers not defined on this system or system unknown -# warning For SMP safety, you should fix this. -# define PaUtil_FullMemoryBarrier() -# define PaUtil_ReadMemoryBarrier() -# define PaUtil_WriteMemoryBarrier() +# warning Memory barriers not defined on this system or system unknown +# warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() # else -# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. # endif #endif diff --git a/lib/portaudio/pa_ringbuffer.c b/lib/portaudio/pa_ringbuffer.c index 93b3e430a510..b978d54f195c 100644 --- a/lib/portaudio/pa_ringbuffer.c +++ b/lib/portaudio/pa_ringbuffer.c @@ -7,7 +7,7 @@ * modified for SMP safety on Mac OS X by Bjorn Roche * modified for SMP safety on Linux by Leland Lucius * also, allowed for const where possible - * modified for multiple-byte-sized data elements by Sven Fischer + * modified for multiple-byte-sized data elements by Sven Fischer * * Note that this is safe only for a single-thread reader and a * single-thread writer. @@ -37,13 +37,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ @@ -138,7 +138,7 @@ ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, rin */ ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ) { - /* ensure that previous writes are seen before we update the write index + /* ensure that previous writes are seen before we update the write index (write after write) */ PaUtil_WriteMemoryBarrier(); @@ -176,7 +176,7 @@ ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring *dataPtr2 = NULL; *sizePtr2 = 0; } - + if( available ) PaUtil_ReadMemoryBarrier(); /* (read-after-read) => read barrier */ @@ -186,7 +186,7 @@ ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring */ ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ) { - /* ensure that previous reads (copies out of the ring buffer) are always completed before updating (writing) the read index. + /* ensure that previous reads (copies out of the ring buffer) are always completed before updating (writing) the read index. (write-after-read) => full barrier */ PaUtil_FullMemoryBarrier(); diff --git a/lib/portaudio/pa_ringbuffer.h b/lib/portaudio/pa_ringbuffer.h index 9edba0dd6579..400aaac659b3 100644 --- a/lib/portaudio/pa_ringbuffer.h +++ b/lib/portaudio/pa_ringbuffer.h @@ -8,7 +8,7 @@ * Author: Phil Burk, http://www.softsynth.com * modified for SMP safety on OS X by Bjorn Roche. * also allowed for const where possible. - * modified for multiple-byte-sized data elements by Sven Fischer + * modified for multiple-byte-sized data elements by Sven Fischer * * Note that this is safe only for a single-thread reader * and a single-thread writer. @@ -38,13 +38,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ @@ -58,15 +58,15 @@ a single reader and a single writer (ie. one thread or callback writes to the ring buffer, another thread or callback reads from it). - The PaUtilRingBuffer structure manages a ring buffer containing N - elements, where N must be a power of two. An element may be any size + The PaUtilRingBuffer structure manages a ring buffer containing N + elements, where N must be a power of two. An element may be any size (specified in bytes). - The memory area used to store the buffer elements must be allocated by + The memory area used to store the buffer elements must be allocated by the client prior to calling PaUtil_InitializeRingBuffer() and must outlive the use of the ring buffer. - - @note The ring buffer functions are not normally exposed in the PortAudio libraries. + + @note The ring buffer functions are not normally exposed in the PortAudio libraries. If you want to call them then you will need to add pa_ringbuffer.c to your application source code. */ From a72306f41830facb79a7a8648e3e2fb5eaa1d94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 11:53:52 +0200 Subject: [PATCH 054/201] Remove stray ! in libshout-idjc --- lib/libshout-idjc/src/shout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libshout-idjc/src/shout.c b/lib/libshout-idjc/src/shout.c index 0f90cf7ae9a8..533a382eab1c 100644 --- a/lib/libshout-idjc/src/shout.c +++ b/lib/libshout-idjc/src/shout.c @@ -437,7 +437,7 @@ int shout_set_metadata(shout_t *self, shout_metadata_t *metadata) goto error_socket; if (upgrade[0] == 0) break; - if (!strncasecmp(upgrade, "Content-Length: ", 16) == 0) + if (strncasecmp(upgrade, "Content-Length: ", 16) == 0) len = atoi(upgrade + 16); } while (1); From 3b5cfc72dfc5d62d0e1fcb97254c79a823692d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 12:04:32 +0200 Subject: [PATCH 055/201] Set SET CMP0042 NEW for libebur128 --- lib/libebur128/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/libebur128/CMakeLists.txt b/lib/libebur128/CMakeLists.txt index f741317f501b..5958d376393e 100644 --- a/lib/libebur128/CMakeLists.txt +++ b/lib/libebur128/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(libebur128 C) +# MACOSX_RPATH is set by default +if(POLICY CMP0042) + cmake_policy(SET CMP0042 NEW) +endif() + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) From e0b0630f0d1a70fce0ae3dbad64df11769f85fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 12:40:44 +0200 Subject: [PATCH 056/201] Fix "rlimit.cpp.o has no symbols" warning on MacOs --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 438a9baf34d2..32446e39dd36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -847,7 +847,6 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/util/performancetimer.cpp src/util/rangelist.cpp src/util/readaheadsamplebuffer.cpp - src/util/rlimit.cpp src/util/rotary.cpp src/util/sample.cpp src/util/samplebuffer.cpp @@ -992,6 +991,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL set_target_properties(mixxx-lib PROPERTIES AUTOMOC ON AUTOUIC ON CXX_CLANG_TIDY "${CLANG_TIDY}") target_include_directories(mixxx-lib PUBLIC src "${CMAKE_CURRENT_BINARY_DIR}/src") if(UNIX AND NOT APPLE) + target_sources(mixxx-lib PRIVATE src/util/rlimit.cpp) set(MIXXX_SETTINGS_PATH ".mixxx/") endif() From 61adb81cb97ceb6649afcbcda0079dba7ee5646a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sat, 23 Apr 2022 12:53:07 +0200 Subject: [PATCH 057/201] Fix signed unsigned warning --- src/test/portmidicontroller_test.cpp | 2 +- src/test/soundproxy_test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/portmidicontroller_test.cpp b/src/test/portmidicontroller_test.cpp index cfd8b94bcb66..e625ff577422 100644 --- a/src/test/portmidicontroller_test.cpp +++ b/src/test/portmidicontroller_test.cpp @@ -209,7 +209,7 @@ TEST_F(PortMidiControllerTest, Poll_Read_Basic) { EXPECT_CALL(*m_mockInput, read(NotNull(), _)) .InSequence(read) .WillOnce(DoAll(SetArrayArgument<0>(messages.begin(), messages.end()), - Return(messages.size()))); + Return(static_cast(messages.size())))); EXPECT_CALL(*m_pController, receive(0x90, 0x3C, 0x40, _)) .InSequence(read); diff --git a/src/test/soundproxy_test.cpp b/src/test/soundproxy_test.cpp index b02f73ad0adc..4bf32ca4ca01 100644 --- a/src/test/soundproxy_test.cpp +++ b/src/test/soundproxy_test.cpp @@ -186,7 +186,7 @@ TEST_F(SoundSourceProxyTest, open) { continue; } EXPECT_LT(0, pAudioSource->getSignalInfo().getChannelCount()); - EXPECT_LT(0, pAudioSource->getSignalInfo().getSampleRate()); + EXPECT_LT(0u, pAudioSource->getSignalInfo().getSampleRate()); EXPECT_FALSE(pAudioSource->frameIndexRange().empty()); } } From d4eb55094a4fa076190ef5ff6de3adbc09cc6558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 24 Apr 2022 19:17:56 +0200 Subject: [PATCH 058/201] Disable Auto DJ when the to deck track is too short. --- src/library/autodj/autodjprocessor.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 72b59ec07ffc..bc47e1b354b7 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -16,6 +16,8 @@ const char* kTransitionPreferenceName = "Transition"; const char* kTransitionModePreferenceName = "TransitionMode"; const double kTransitionPreferenceDefault = 10.0; const double kKeepPosition = -1.0; +const double kMinimumTrackDurationSec = + 0.2; // A track needs to be longer than two callbacks to not stop AutoDJ const mixxx::audio::ChannelCount kChannelCount = mixxx::kEngineChannelCount; @@ -254,6 +256,13 @@ void AutoDJProcessor::fadeNow() { // the track duration. Use this alias for better readability. const double fromDeckDuration = fromDeckEndSecond; const double toDeckDuration = toDeckEndSecond; + if (toDeckDuration < kMinimumTrackDurationSec) { + // Deck is empty or track too short, disable AutoDJ + // This happens only if the user has changed deck orientation to such deck. + toggleAutoDJ(false); + return; + } + // playPosition() is in the range of 0..1 const double fromDeckCurrentSecond = fromDeckDuration * pFromDeck->playPosition(); const double toDeckCurrentSecond = toDeckDuration * pToDeck->playPosition(); @@ -1439,7 +1448,7 @@ void AutoDJProcessor::playerTrackLoaded(DeckAttributes* pDeck, TrackPointer pTra // Since the end position is measured in seconds from 0:00 it is also // the track duration. double duration = getEndSecond(pDeck); - if (duration < 0.2) { + if (duration < kMinimumTrackDurationSec) { qWarning() << "Skip track with" << duration << "Duration" << pTrack->getLocation(); // Remove Tack with duration smaller than two callbacks From 265be873f915acc9d0fabdaeb2e116ad0bb518e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 24 Apr 2022 19:48:21 +0200 Subject: [PATCH 059/201] Disable Auto DJ when moving the crossfader to an empty deck --- src/library/autodj/autodjprocessor.cpp | 35 +++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index bc47e1b354b7..01c9405c9197 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -594,35 +594,40 @@ void AutoDJProcessor::crossfaderChanged(double value) { // The user is changing the crossfader manually. If the user has // moved it all the way to the other side, make the deck faded away // from the new "to deck" by loading the next track into it. - DeckAttributes* fromDeck = getFromDeck(); - VERIFY_OR_DEBUG_ASSERT(fromDeck) { + DeckAttributes* pFromDeck = getFromDeck(); + VERIFY_OR_DEBUG_ASSERT(pFromDeck) { // we have always a from deck in case of state IDLE return; } - DeckAttributes* toDeck = getOtherDeck(fromDeck); - VERIFY_OR_DEBUG_ASSERT(toDeck) { + DeckAttributes* pToDeck = getOtherDeck(pFromDeck); + if (!pToDeck) { // we have always a from deck in case of state IDLE + // if the user has not changed the deck orientation return; } double crossfaderPosition = value * (m_pCOCrossfaderReverse->toBool() ? -1 : 1); - - if ((crossfaderPosition == 1.0 && fromDeck->isLeft()) || // crossfader right - (crossfaderPosition == -1.0 && fromDeck->isRight())) { // crossfader left - if (!toDeck->isPlaying()) { - // Re-cue the track if the user has seeked it to the very end - if (toDeck->playPosition() >= toDeck->fadeBeginPos) { - toDeck->setPlayPosition(toDeck->startPos); + if ((crossfaderPosition == 1.0 && pFromDeck->isLeft()) || // crossfader right + (crossfaderPosition == -1.0 && pFromDeck->isRight())) { // crossfader left + if (!pToDeck->isPlaying()) { + if (getEndSecond(pToDeck) >= kMinimumTrackDurationSec) { + // Re-cue the track if the user has seeked it to the very end + if (pToDeck->playPosition() >= pToDeck->fadeBeginPos) { + pToDeck->setPlayPosition(pToDeck->startPos); + } + pToDeck->play(); + } else { + toggleAutoDJ(false); + return; } - toDeck->play(); } - fromDeck->stop(); + pFromDeck->stop(); // Now that we have started the other deck playing, remove the track // that was "on deck" from the top of the queue. - removeLoadedTrackFromTopOfQueue(*toDeck); - loadNextTrackFromQueue(*fromDeck); + removeLoadedTrackFromTopOfQueue(*pToDeck); + loadNextTrackFromQueue(*pFromDeck); } } } From c205b22d1f05ee2444a6b88d8041347efac5191f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 24 Apr 2022 23:13:59 +0200 Subject: [PATCH 060/201] AutoDJ: handle fade now when the "to Track" is already near end --- src/library/autodj/autodjprocessor.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 01c9405c9197..549a084b7b04 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -267,6 +267,12 @@ void AutoDJProcessor::fadeNow() { const double fromDeckCurrentSecond = fromDeckDuration * pFromDeck->playPosition(); const double toDeckCurrentSecond = toDeckDuration * pToDeck->playPosition(); + if (toDeckDuration - toDeckCurrentSecond < kMinimumTrackDurationSec) { + // Remaining Track time is too short, user has has seeked near the end + // Re-cue the track + pToDeck->setPlayPosition(pToDeck->startPos); + } + pFromDeck->fadeBeginPos = fromDeckCurrentSecond; // Do not seek to a calculated start point; start the to deck from wherever // it is if the user has seeked since loading the track. @@ -310,8 +316,10 @@ void AutoDJProcessor::fadeNow() { fadeTime = spinboxTime; } - double timeUntilEndOfFromTrack = fromDeckEndSecond - fromDeckCurrentSecond; - fadeTime = math_min(fadeTime, timeUntilEndOfFromTrack); + fadeTime = math_min(fadeTime, fromDeckEndSecond - fromDeckCurrentSecond); + fadeTime = math_min(fadeTime, + (toDeckEndSecond - toDeckCurrentSecond) / 2); // for fade in and out + pFromDeck->fadeEndPos = fromDeckCurrentSecond + fadeTime; // These are expected to be a fraction of the track length. From 9cf1150458e9b623021390c95da5b8bca0509b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 26 Apr 2022 08:17:45 +0200 Subject: [PATCH 061/201] Rename toDeckStartSeconds for better readability --- src/library/autodj/autodjprocessor.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 549a084b7b04..8c923fa21fcb 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -16,8 +16,8 @@ const char* kTransitionPreferenceName = "Transition"; const char* kTransitionModePreferenceName = "TransitionMode"; const double kTransitionPreferenceDefault = 10.0; const double kKeepPosition = -1.0; -const double kMinimumTrackDurationSec = - 0.2; // A track needs to be longer than two callbacks to not stop AutoDJ +// A track needs to be longer than two callbacks to not stop AutoDJ +const double kMinimumTrackDurationSec = 0.2; const mixxx::audio::ChannelCount kChannelCount = mixxx::kEngineChannelCount; @@ -782,7 +782,7 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, emitAutoDJStateChanged(m_eState); if (!otherDeckPlaying) { - // Re-cue the track if the user has seeked it to the very end + // Re-cue the track if the user has seeked past the fadeBeginPos if (otherDeck->playPosition() >= otherDeck->fadeBeginPos) { otherDeck->setPlayPosition(otherDeck->startPos); } @@ -1356,18 +1356,23 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } } break; case TransitionMode::FixedSkipSilence: { - double startPoint; + double toDeckStartSecond; pToDeck->fadeBeginPos = getLastSoundSecond(pToDeck); if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) { // toDeckPosition >= pToDeck->fadeBeginPos happens when the // user has seeked or played the to track behind fadeBeginPos of // the fade after the next. // In this case we recue the track just before the transition. - startPoint = getFirstSoundSecond(pToDeck); + toDeckStartSecond = getFirstSoundSecond(pToDeck); } else { - startPoint = toDeckPositionSeconds; + toDeckStartSecond = toDeckPositionSeconds; } - useFixedFadeTime(pFromDeck, pToDeck, fromDeckPosition, getLastSoundSecond(pFromDeck), startPoint); + useFixedFadeTime( + pFromDeck, + pToDeck, + fromDeckPosition, + getLastSoundSecond(pFromDeck), + toDeckStartSecond); } break; case TransitionMode::FixedFullTrack: default: { @@ -1422,11 +1427,12 @@ void AutoDJProcessor::useFixedFadeTime( toDeckOutroStart -= m_transitionTime; } if (toDeckOutroStart <= toDeckStartSecond) { - // we are already too late + // we have already passed the outro start // Check OutroEnd as alternative, which is for all transition mode // better than directly default to duration() double end = getOutroEndSecond(pToDeck); if (end <= toDeckStartSecond) { + // we have also passed the outro end end = getEndSecond(pToDeck); VERIFY_OR_DEBUG_ASSERT(end > toDeckStartSecond) { // as last resort move start point From 3ce5bbffafd8daa61af9a93c09cf2d5abcac15b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 26 Apr 2022 09:55:53 +0200 Subject: [PATCH 062/201] Consider kMinimumTrackDurationSec when using a fixed fade time --- src/library/autodj/autodjprocessor.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 8c923fa21fcb..6476fae71c6d 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1426,18 +1426,18 @@ void AutoDJProcessor::useFixedFadeTime( // no outro defined, the toDeck will also use the transition time toDeckOutroStart -= m_transitionTime; } - if (toDeckOutroStart <= toDeckStartSecond) { + if (toDeckOutroStart <= toDeckStartSecond + kMinimumTrackDurationSec) { // we have already passed the outro start // Check OutroEnd as alternative, which is for all transition mode // better than directly default to duration() double end = getOutroEndSecond(pToDeck); - if (end <= toDeckStartSecond) { + if (end <= toDeckStartSecond + kMinimumTrackDurationSec) { // we have also passed the outro end end = getEndSecond(pToDeck); - VERIFY_OR_DEBUG_ASSERT(end > toDeckStartSecond) { + VERIFY_OR_DEBUG_ASSERT(end > toDeckStartSecond + kMinimumTrackDurationSec) { // as last resort move start point // The caller makes sure that this never happens - toDeckStartSecond = end - 1; + toDeckStartSecond = end - kMinimumTrackDurationSec; } } // use the remaining time for fading @@ -1445,7 +1445,11 @@ void AutoDJProcessor::useFixedFadeTime( } double transitionTime = math_min(toDeckOutroStart - toDeckStartSecond, m_transitionTime); - + VERIFY_OR_DEBUG_ASSERT(transitionTime >= kMinimumTrackDurationSec / 2) { + transitionTime = kMinimumTrackDurationSec / 2; + } + // Note: pFromDeck->fadeBeginPos >= pFromDeck->fadeEndPos is handled in + // playerPositionChanged() causing a jump cut. pFromDeck->fadeBeginPos = math_max(fadeEndSecond - transitionTime, fromDeckSecond); pFromDeck->fadeEndPos = fadeEndSecond; pToDeck->startPos = toDeckStartSecond; From ed35600120cc685415310444df58bca243c1d0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 26 Apr 2022 17:39:44 +0200 Subject: [PATCH 063/201] Consider the fade time of the current transition when decide for seeking back before a transition to avoid reaching the end of the "to Deck" --- src/library/autodj/autodjprocessor.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 6476fae71c6d..64a6950932f2 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -781,11 +781,15 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, m_transitionProgress = 0.0; emitAutoDJStateChanged(m_eState); + const double toDeckFadeDisatance = + (thisDeck->fadeEndPos - thisDeck->fadeBeginPos) * + getEndSecond(thisDeck) / getEndSecond(thisDeck); + // Re-cue the track if the user has seeked forward and will miss the fadeBeginPos + if (otherDeck->playPosition() >= otherDeck->fadeBeginPos - toDeckFadeDisatance) { + otherDeck->setPlayPosition(otherDeck->startPos); + } + if (!otherDeckPlaying) { - // Re-cue the track if the user has seeked past the fadeBeginPos - if (otherDeck->playPosition() >= otherDeck->fadeBeginPos) { - otherDeck->setPlayPosition(otherDeck->startPos); - } otherDeck->play(); } From 1b27d0fd419a460952923223750592f3aeb5d1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 26 Apr 2022 17:41:02 +0200 Subject: [PATCH 064/201] Use the transition time for the guessed future transition if no outro is defined. --- src/library/autodj/autodjprocessor.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 64a6950932f2..22a19a6964c8 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1231,8 +1231,13 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, // This is used to check if it will be possible or a re-cue is required. // here it is done for FullIntroOutro and FadeAtOutroStart. // It is adjusted below for the other modes. - pToDeck->fadeBeginPos = getOutroStartSecond(pToDeck); pToDeck->fadeEndPos = getOutroEndSecond(pToDeck); + double toDeckOutroStartSecond = getOutroStartSecond(pToDeck); + if (pToDeck->fadeEndPos == toDeckOutroStartSecond) { + // outro not defined, use transition time. + toDeckOutroStartSecond -= m_transitionTime; + } + pToDeck->fadeBeginPos = toDeckOutroStartSecond; double introStart; if (seekToStartPoint || toDeckPositionSeconds >= pToDeck->fadeBeginPos) { From f42b1b173d9f9f4f69cb271fece6c128b3d19e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 26 Apr 2022 18:20:32 +0200 Subject: [PATCH 065/201] Added a test for the case that the "to Deck" is playing near the end --- src/test/autodjprocessor_test.cpp | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/test/autodjprocessor_test.cpp b/src/test/autodjprocessor_test.cpp index ce9f81f06578..b207f9c43e72 100644 --- a/src/test/autodjprocessor_test.cpp +++ b/src/test/autodjprocessor_test.cpp @@ -1587,6 +1587,60 @@ TEST_F(AutoDJProcessorTest, FadeToDeck2_SeekEnd) { EXPECT_DOUBLE_EQ(1.0, deck2.play.get()); } +TEST_F(AutoDJProcessorTest, FadeToDeck2_SeekBeforeTransition) { + TrackId testId = addTrackToCollection(kTrackLocationTest); + ASSERT_TRUE(testId.isValid()); + + // Crossfader starts on the left. + master.crossfader.set(-1.0); + // Pretend a track is playing on deck 1. + TrackPointer pTrack(newTestTrack(nextTrackId(testId))); + // Load track and mark it playing. + deck1.slotLoadTrack(pTrack, true); + // Indicate the track loaded successfully. + deck1.fakeTrackLoadedEvent(pTrack); + + PlaylistTableModel* pAutoDJTableModel = pProcessor->getTableModel(); + pAutoDJTableModel->appendTrack(testId); + + EXPECT_CALL(*pProcessor, emitAutoDJStateChanged(AutoDJProcessor::ADJ_IDLE)); + EXPECT_CALL(*pProcessor, emitLoadTrackToPlayer(_, QString("[Channel2]"), false)); + + // Enable AutoDJ, we immediately transition into IDLE and request a track + // load on deck2. + AutoDJProcessor::AutoDJError err = pProcessor->toggleAutoDJ(true); + EXPECT_EQ(AutoDJProcessor::ADJ_OK, err); + EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); + + // Pretend the track load succeeds. + deck2.slotLoadTrack(pTrack, false); + deck2.fakeTrackLoadedEvent(pTrack); + + // No change to the mode, crossfader or play states. + EXPECT_EQ(AutoDJProcessor::ADJ_IDLE, pProcessor->getState()); + EXPECT_DOUBLE_EQ(-1.0, master.crossfader.get()); + EXPECT_DOUBLE_EQ(1.0, deck1.play.get()); + EXPECT_DOUBLE_EQ(0.0, deck2.play.get()); + + // Play "to deck" near end + deck2.play.set(1.0); + deck2.playposition.set(0.95); + + // Expect that we will transition into LEFT_FADING mode. + EXPECT_CALL(*pProcessor, emitAutoDJStateChanged(AutoDJProcessor::ADJ_LEFT_FADING)); + + // Seek track to 99.9 % it should fade + // not 100 % because the final step is done by deck2 + deck1.playposition.set(0.999); + EXPECT_EQ(AutoDJProcessor::ADJ_LEFT_FADING, pProcessor->getState()); + + EXPECT_LT(-1.0, master.crossfader.get()); + + EXPECT_DOUBLE_EQ(0.999, deck1.playposition.get()); + // We expect that the "to Deck" has been seeked to the beginning" + EXPECT_DOUBLE_EQ(0, deck2.playposition.get()); +} + TEST_F(AutoDJProcessorTest, TrackZeroLength) { TrackId testId = addTrackToCollection(kTrackLocationTest); ASSERT_TRUE(testId.isValid()); From 7ff6538770ef4c5777c6669e757661d0685d10f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 26 Apr 2022 20:34:40 +0200 Subject: [PATCH 066/201] Fix debug assertion --- src/library/autodj/autodjprocessor.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 22a19a6964c8..384b09dbf7e8 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -1190,16 +1190,21 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, const double fromDeckDuration = fromDeckEndPosition; const double toDeckDuration = toDeckEndPosition; - VERIFY_OR_DEBUG_ASSERT(fromDeckDuration > 0) { - // Playing Track has no duration. This should not happen, because short + VERIFY_OR_DEBUG_ASSERT(fromDeckDuration >= kMinimumTrackDurationSec) { + // Track has no duration or too short. This should not happen, because short // tracks are skipped after load. Play ToDeck immediately. pFromDeck->fadeBeginPos = 0; pFromDeck->fadeEndPos = 0; pToDeck->startPos = kKeepPosition; return; } - VERIFY_OR_DEBUG_ASSERT(toDeckDuration > 0) { - // Playing Track has no duration. This should not happen, because short + if (toDeckDuration == 0) { + // This is a seek call to zero after ejecting the track + // this signal is received before the track pointer becomes null + return; + } + VERIFY_OR_DEBUG_ASSERT(toDeckDuration >= kMinimumTrackDurationSec) { + // Track has no duration or too short. This should not happen, because short // tracks are skipped after load. loadNextTrackFromQueue(*pToDeck, false); return; From 51d4a972c7327585f260ad9eb717e56305073121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 26 Apr 2022 22:26:17 +0200 Subject: [PATCH 067/201] use the correct delete function --- src/util/sample.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/util/sample.cpp b/src/util/sample.cpp index 6b96b423d039..8a586688d44f 100644 --- a/src/util/sample.cpp +++ b/src/util/sample.cpp @@ -1,7 +1,8 @@ -#include +#include "util/sample.h" + #include +#include -#include "util/sample.h" #include "util/math.h" #ifdef __WINDOWS__ @@ -104,7 +105,7 @@ void SampleUtil::free(CSAMPLE* pBuffer) { std::free(*((void**)((void*)pBuffer) - 1)); #endif } else { - delete[] pBuffer; + std::free(pBuffer); } } From 79da2d75989cccfe5164a445120a0aa4b08a0879 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Thu, 14 Apr 2022 12:38:32 +0200 Subject: [PATCH 068/201] Library: keep hidden tracks in history --- src/library/dao/playlistdao.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/library/dao/playlistdao.cpp b/src/library/dao/playlistdao.cpp index 69f572a2e8e4..9813badfc968 100644 --- a/src/library/dao/playlistdao.cpp +++ b/src/library/dao/playlistdao.cpp @@ -757,6 +757,10 @@ void PlaylistDAO::removeTracksFromPlaylists(const QList& trackIds) { it != playlistsTrackIsInCopy.constEnd(); ++it) { if (it.key() == trackId) { const auto playlistId = it.value(); + // keep tracks in history playlists + if (getHiddenType(playlistId) == PlaylistDAO::PLHT_SET_LOG) { + continue; + } removeTracksFromPlaylistByIdInner(playlistId, trackId); playlistIds.insert(playlistId); } From b39db48ee5abcd3533542e1c5cd2053b6e41872f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 2 May 2022 13:39:16 +0200 Subject: [PATCH 069/201] Remove unused and redundant HAVE_STRUCT_TIMESPEC calculation causing a warning --- lib/libshout-idjc/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/libshout-idjc/CMakeLists.txt b/lib/libshout-idjc/CMakeLists.txt index 02b794b6ba18..3e848208e441 100644 --- a/lib/libshout-idjc/CMakeLists.txt +++ b/lib/libshout-idjc/CMakeLists.txt @@ -175,11 +175,6 @@ endif() check_include_file(time.h HAVE_TIME_H) if(HAVE_TIME_H) target_compile_definitions(${PROJECT_NAME} PRIVATE HAVE_TIME_H) - check_struct_has_member( "struct timespec" "tv_sec" "time.h" HAVE_STRUCT_TIMESPEC ) - # HAVE_STRUCT_TIMESPEC must be defined for windows pthreads on newer MSVC - if(HAVE_STRUCT_TIMESPEC) - target_compile_definitions(${PROJECT_NAME} PRIVATE HAVE_STRUCT_TIMESPEC) - endif(HAVE_STRUCT_TIMESPEC) endif() if(HAVE_SYS_TIME_H AND HAVE_TIME_H) From 14e61767df2cfd2e02a38134460aa31f7b42228a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 2 May 2022 16:33:28 +0200 Subject: [PATCH 070/201] [libshout-idjc] use the same warnings as in Mixxx /Wall for MCVC means really all warnings which is much too verbose --- lib/libshout-idjc/CMakeLists.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/libshout-idjc/CMakeLists.txt b/lib/libshout-idjc/CMakeLists.txt index 3e848208e441..a48f9d260797 100644 --- a/lib/libshout-idjc/CMakeLists.txt +++ b/lib/libshout-idjc/CMakeLists.txt @@ -37,7 +37,20 @@ target_compile_definitions(${PROJECT_NAME} LIBSHOUT_MICRO=${PROJECT_VERSION_PATCH} ) -target_compile_options(${PROJECT_NAME} PRIVATE -Wall) +option(WARNINGS_PEDANTIC "Let the compiler show even more warnings" OFF) +if(MSVC) + if(WARNINGS_PEDANTIC) + target_compile_options(${PROJECT_NAME} PUBLIC /W4) + else() + target_compile_options(${PROJECT_NAME} PUBLIC /W3) + target_compile_definitions(${PROJECT_NAME} PUBLIC _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING _CRT_SECURE_NO_WARNINGS) + endif() +else() + target_compile_options(${PROJECT_NAME} PUBLIC -Wall -Wextra -Woverloaded-virtual -Wfloat-conversion -Werror=return-type) + if(WARNINGS_PEDANTIC) + target_compile_options(${PROJECT_NAME} PUBLIC -pedantic) + endif() +endif() cmake_push_check_state() From 0ef5b59d61c9e1c1d92e892ba8d9c509b4904e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 3 May 2022 11:05:20 +0200 Subject: [PATCH 071/201] [libshout-idjc] silence -Wunused-parameter warning --- lib/libshout-idjc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libshout-idjc/CMakeLists.txt b/lib/libshout-idjc/CMakeLists.txt index a48f9d260797..8b5a124d6563 100644 --- a/lib/libshout-idjc/CMakeLists.txt +++ b/lib/libshout-idjc/CMakeLists.txt @@ -46,7 +46,7 @@ if(MSVC) target_compile_definitions(${PROJECT_NAME} PUBLIC _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING _CRT_SECURE_NO_WARNINGS) endif() else() - target_compile_options(${PROJECT_NAME} PUBLIC -Wall -Wextra -Woverloaded-virtual -Wfloat-conversion -Werror=return-type) + target_compile_options(${PROJECT_NAME} PUBLIC -Wall -Wextra -Woverloaded-virtual -Wfloat-conversion -Werror=return-type -Wno-unused-parameter) if(WARNINGS_PEDANTIC) target_compile_options(${PROJECT_NAME} PUBLIC -pedantic) endif() From 392315929514af26a250a922dd000e15f30cba45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Tue, 3 May 2022 11:28:50 +0200 Subject: [PATCH 072/201] [libshout-idjc] silence deprecation warning --- lib/libshout-idjc/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libshout-idjc/CMakeLists.txt b/lib/libshout-idjc/CMakeLists.txt index 8b5a124d6563..794fa6f0a34c 100644 --- a/lib/libshout-idjc/CMakeLists.txt +++ b/lib/libshout-idjc/CMakeLists.txt @@ -43,7 +43,7 @@ if(MSVC) target_compile_options(${PROJECT_NAME} PUBLIC /W4) else() target_compile_options(${PROJECT_NAME} PUBLIC /W3) - target_compile_definitions(${PROJECT_NAME} PUBLIC _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING _CRT_SECURE_NO_WARNINGS) + target_compile_definitions(${PROJECT_NAME} PUBLIC _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING _CRT_SECURE_NO_WARNINGS _WINSOCK_DEPRECATED_NO_WARNINGS) endif() else() target_compile_options(${PROJECT_NAME} PUBLIC -Wall -Wextra -Woverloaded-virtual -Wfloat-conversion -Werror=return-type -Wno-unused-parameter) From da7c13e30fc27594953919b4d74268a4d5fe0f5a Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 5 May 2022 12:21:32 +0200 Subject: [PATCH 073/201] CI: Drop workarounds for sccache v0.2.x ...after sccache v0.3.0 has been released. --- .github/workflows/build.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ead33f0b2a5..1b02515f18b9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,7 +105,6 @@ jobs: qt_qpa_platform: windows env: - SCCACHE_COMMIT: 3f318a8675e4c3de4f5e8ab2d086189f2ae5f5cf # macOS codesigning APPLE_CODESIGN_IDENTITY: 2C2B5D3EDCE82BA55E22E9A67F16F8D03E390870 MACOS_CODESIGN_OPENSSL_PASSWORD: ${{ secrets.MACOS_CODESIGN_OPENSSL_PASSWORD }} @@ -118,24 +117,20 @@ jobs: artifact-windows-win64: ${{ steps.prepare_deploy.outputs.artifact-windows-win64 }} steps: - # sccache's handling of the /fp:fast MSVC compiler option is broken, so use our fork with the fix. - # https://github.com/mozilla/sccache/issues/950 - - name: "[Windows] Set up cargo cache" + - name: "[Windows] Set up Cargo cache" if: runner.os == 'Windows' uses: actions/cache@v2 id: sccache-build-cache with: path: C:\Users\runneradmin\.cargo - # hash of commit to build - key: sccache-${{ env.SCCACHE_COMMIT }} + key: sccache # This needs to be done first because something later messes with $PATH in a way that breaks cargo # by causing it to find link.exe from Git for Windows instead of MSVC. - - name: "[Windows] Build fixed sccache" + - name: "[Windows] Build and install sccache" shell: bash if: runner.os == 'Windows' && steps.sccache-build-cache.outputs.cache-hit != 'true' - # TODO: change this to simply `cargo install sccache` after 0.2.16 has been released - run: cargo install --git https://github.com/mozilla/sccache.git --rev "${SCCACHE_COMMIT}" + run: cargo install sccache - name: "Check out repository" uses: actions/checkout@v2 From d132d24d51b7571fb0346f7c55abed5d2aaff92b Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 5 May 2022 12:29:18 +0200 Subject: [PATCH 074/201] Update GitHub CI actions --- .github/workflows/build-checks.yml | 2 +- .github/workflows/build.yml | 10 +++++----- .github/workflows/pre-commit.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-checks.yml b/.github/workflows/build-checks.yml index 9c0b35574a5f..7b972c99f564 100644 --- a/.github/workflows/build-checks.yml +++ b/.github/workflows/build-checks.yml @@ -17,7 +17,7 @@ jobs: name: ${{ matrix.name }} steps: - name: Check out repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install build dependencies run: | sudo apt-get update && sudo apt-get install -y --no-install-recommends \ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ead33f0b2a5..be2fda1538ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,7 +122,7 @@ jobs: # https://github.com/mozilla/sccache/issues/950 - name: "[Windows] Set up cargo cache" if: runner.os == 'Windows' - uses: actions/cache@v2 + uses: actions/cache@v3 id: sccache-build-cache with: path: C:\Users\runneradmin\.cargo @@ -138,7 +138,7 @@ jobs: run: cargo install --git https://github.com/mozilla/sccache.git --rev "${SCCACHE_COMMIT}" - name: "Check out repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # This is necessary for making `git describe` work. fetch-depth: 0 @@ -170,7 +170,7 @@ jobs: - name: "[macOS/Windows] Set up build environment cache" if: runner.os != 'Linux' - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ matrix.buildenv_basepath }} key: ${{ runner.os }}-buildenv-${{ env.BUILDENV_NAME }} @@ -223,7 +223,7 @@ jobs: if: runner.os != 'windows' - name: "Set up compiler cache" - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ matrix.compiler_cache_path }} key: ${{ matrix.os }}-${{ matrix.compiler_cache }}-${{ github.head_ref }}-${{ github.run_number }} @@ -433,7 +433,7 @@ jobs: needs: build steps: - name: "Check out repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index fd27da043288..cc5471ce9095 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check out repository" - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 - name: "Set up Python" - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 - name: Install clang-format run: sudo apt-get update && sudo apt-get install -y --no-install-recommends clang-format-10 From 785eb2f33090cd4b8632593e232a9beb246a31a6 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 5 May 2022 15:55:20 +0200 Subject: [PATCH 075/201] Pin sccache version to 0.3.0 --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1b02515f18b9..8c347faf7da1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,6 +105,7 @@ jobs: qt_qpa_platform: windows env: + SCCACHE_VERSION: "0.3.0" # macOS codesigning APPLE_CODESIGN_IDENTITY: 2C2B5D3EDCE82BA55E22E9A67F16F8D03E390870 MACOS_CODESIGN_OPENSSL_PASSWORD: ${{ secrets.MACOS_CODESIGN_OPENSSL_PASSWORD }} @@ -123,14 +124,14 @@ jobs: id: sccache-build-cache with: path: C:\Users\runneradmin\.cargo - key: sccache + key: sccache-${{ env.SCCACHE_VERSION }} # This needs to be done first because something later messes with $PATH in a way that breaks cargo # by causing it to find link.exe from Git for Windows instead of MSVC. - name: "[Windows] Build and install sccache" shell: bash if: runner.os == 'Windows' && steps.sccache-build-cache.outputs.cache-hit != 'true' - run: cargo install sccache + run: cargo install --version "${{ env.SCCACHE_VERSION }}" sccache - name: "Check out repository" uses: actions/checkout@v2 From 772e84b4c2326277d9a9b652a624ce30a2a9b109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 5 May 2022 17:40:44 +0200 Subject: [PATCH 076/201] Simplify the code around m_prevPos --- src/widget/knobeventhandler.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/knobeventhandler.h b/src/widget/knobeventhandler.h index 5ab63c85df9d..9949748a9732 100644 --- a/src/widget/knobeventhandler.h +++ b/src/widget/knobeventhandler.h @@ -21,8 +21,9 @@ class KnobEventHandler { } double valueFromMouseEvent(T* pWidget, QMouseEvent* e) { - QPoint cur(e->globalPos()); - QPoint diff(cur - m_prevPos); + QPoint cur = e->globalPos(); + QPoint diff = cur - m_prevPos; + m_prevPos = cur; double dist = sqrt(static_cast(diff.x() * diff.x() + diff.y() * diff.y())); bool y_dominant = abs(diff.y()) > abs(diff.x()); @@ -47,7 +48,6 @@ class KnobEventHandler { double value = valueFromMouseEvent(pWidget, e); pWidget->setControlParameterDown(value); pWidget->inputActivity(); - m_prevPos = e->globalPos(); } } From 87272cf0e8236acf72ecc238ff71639978df3979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 5 May 2022 17:52:45 +0200 Subject: [PATCH 077/201] Replace using to global cursor stack by using the widget cursor. This should be a workaround for the missing cursor, that happens on MacOS probably due to a missing release event in overload situations. lp196978 If this happens again, only one knob is affected instead of whole Mixxx and it heals itself by another mouse click because it is not longer stacking up. --- src/widget/knobeventhandler.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/widget/knobeventhandler.h b/src/widget/knobeventhandler.h index 9949748a9732..348f49770050 100644 --- a/src/widget/knobeventhandler.h +++ b/src/widget/knobeventhandler.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -63,7 +62,7 @@ class KnobEventHandler { m_prevPos = m_startPos; // Somehow using Qt::BlankCursor does not work on Windows // https://mixxx.org/forums/viewtopic.php?p=40298#p40298 - QApplication::setOverrideCursor(m_blankCursor); + pWidget->setCursor(m_blankCursor); break; default: break; @@ -76,7 +75,7 @@ class KnobEventHandler { case Qt::LeftButton: case Qt::MiddleButton: QCursor::setPos(m_startPos); - QApplication::restoreOverrideCursor(); + pWidget->unsetCursor(); value = valueFromMouseEvent(pWidget, e); pWidget->setControlParameterUp(value); pWidget->inputActivity(); From 7b075a0a8d18bfd07267fc93081a6798fb968969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 6 May 2022 11:06:06 +0200 Subject: [PATCH 078/201] Fix a size warning on windows --- src/util/fifo.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/util/fifo.h b/src/util/fifo.h index dac6b28a1b5c..e590a966fbfa 100644 --- a/src/util/fifo.h +++ b/src/util/fifo.h @@ -16,8 +16,10 @@ class FIFO { return; } m_data = new DataType[size]; - PaUtil_InitializeRingBuffer( - &m_ringBuffer, sizeof(DataType), size, m_data); + PaUtil_InitializeRingBuffer(&m_ringBuffer, + static_cast(sizeof(DataType)), + static_cast(size), + m_data); } virtual ~FIFO() { delete [] m_data; From c18b89f3b533fb67353c82e5f9adf0f9003f0d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Fri, 6 May 2022 14:40:31 +0200 Subject: [PATCH 079/201] Refine the version check in Findhidapi.cmake --- cmake/modules/Findhidapi.cmake | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cmake/modules/Findhidapi.cmake b/cmake/modules/Findhidapi.cmake index f321f7e84f43..ffcd10bf3bc5 100644 --- a/cmake/modules/Findhidapi.cmake +++ b/cmake/modules/Findhidapi.cmake @@ -64,10 +64,10 @@ mark_as_advanced(hidapi_LIBRARY) # Version detection -if(DEFINED PC_hidapi_VERSION) +if(DEFINED PC_hidapi_VERSION AND NOT PC_hidapi_VERSION STREQUAL "") set(hidapi_VERSION "${PC_hidapi_VERSION}") else() - if (EXISTS "${hidapi_INCLUDE_DIR}/hidapi.h") + if (EXISTS "${hidapi_LIBRARY}" AND EXISTS "${hidapi_INCLUDE_DIR}/hidapi.h") file(READ "${hidapi_INCLUDE_DIR}/hidapi.h" hidapi_H_CONTENTS) string(REGEX MATCH "#define HID_API_VERSION_MAJOR ([0-9]+)" _dummy "${hidapi_H_CONTENTS}") set(hidapi_VERSION_MAJOR "${CMAKE_MATCH_1}") @@ -81,7 +81,10 @@ else() NOT hidapi_VERSION_MINOR STREQUAL "" AND NOT hidapi_VERSION_PATCH STREQUAL "") set(hidapi_VERSION "${hidapi_VERSION_MAJOR}.${hidapi_VERSION_MINOR}.${hidapi_VERSION_PATCH}") - endif() + else() + # take what we have found, likely "" to fail the version check. + set(hidapi_VERSION "${hidapi_VERSION_MAJOR}") + endif() endif() endif () From 125d97def92fc4baee66bbf3d028ffaa8a885310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Thu, 5 May 2022 18:54:43 +0200 Subject: [PATCH 080/201] Use static functions for overriding curser via base class --- src/util/scopedoverridecursor.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util/scopedoverridecursor.h b/src/util/scopedoverridecursor.h index af08eb50d3de..1287cd0c17c4 100644 --- a/src/util/scopedoverridecursor.h +++ b/src/util/scopedoverridecursor.h @@ -1,16 +1,16 @@ #pragma once -#include +#include class ScopedOverrideCursor { public: inline explicit ScopedOverrideCursor(const QCursor& cursor) { - QApplication::setOverrideCursor(cursor); - QApplication::processEvents(); + QGuiApplication::setOverrideCursor(cursor); + QCoreApplication::processEvents(); } inline virtual ~ScopedOverrideCursor() { - QApplication::restoreOverrideCursor(); + QGuiApplication::restoreOverrideCursor(); } }; From bac58e55409da1c558dca948d86e849c27497991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Sun, 8 May 2022 22:30:34 +0200 Subject: [PATCH 081/201] Make -Woverloaded-virtual CXX only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids "cc1: warning: command line option ‘-Woverloaded-virtual’ is valid for C++/ObjC++ but not for C" --- CMakeLists.txt | 2 +- lib/libshout-idjc/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 32446e39dd36..0f7277b3e639 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1039,7 +1039,7 @@ if(MSVC) target_compile_definitions(mixxx-lib PUBLIC _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING _CRT_SECURE_NO_WARNINGS) endif() else() - target_compile_options(mixxx-lib PUBLIC -Wall -Wextra -Woverloaded-virtual -Wfloat-conversion -Werror=return-type) + target_compile_options(mixxx-lib PUBLIC -Wall -Wextra $<$:-Woverloaded-virtual> -Wfloat-conversion -Werror=return-type) if(WARNINGS_PEDANTIC) target_compile_options(mixxx-lib PUBLIC -pedantic) endif() diff --git a/lib/libshout-idjc/CMakeLists.txt b/lib/libshout-idjc/CMakeLists.txt index 794fa6f0a34c..0615c031ca46 100644 --- a/lib/libshout-idjc/CMakeLists.txt +++ b/lib/libshout-idjc/CMakeLists.txt @@ -46,7 +46,7 @@ if(MSVC) target_compile_definitions(${PROJECT_NAME} PUBLIC _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING _CRT_SECURE_NO_WARNINGS _WINSOCK_DEPRECATED_NO_WARNINGS) endif() else() - target_compile_options(${PROJECT_NAME} PUBLIC -Wall -Wextra -Woverloaded-virtual -Wfloat-conversion -Werror=return-type -Wno-unused-parameter) + target_compile_options(${PROJECT_NAME} PUBLIC -Wall -Wextra $<$:-Woverloaded-virtual> -Wfloat-conversion -Werror=return-type -Wno-unused-parameter) if(WARNINGS_PEDANTIC) target_compile_options(${PROJECT_NAME} PUBLIC -pedantic) endif() From c76493872d92021721024fe97d557d48ecdaa580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 9 May 2022 18:28:24 +0200 Subject: [PATCH 082/201] Remove stray comment --- src/library/autodj/autodjprocessor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index 384b09dbf7e8..ab07c9024e27 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -216,7 +216,6 @@ AutoDJProcessor::AutoDJError AutoDJProcessor::shufflePlaylist( } void AutoDJProcessor::fadeNow() { - // Auto-DJ needs at least two decks if (m_eState != ADJ_IDLE) { // we cannot fade if AutoDj is disabled or already fading return; From 3e46b5f7fd93fc60b620c65876a282084e4afb4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sch=C3=BCrmann?= Date: Mon, 9 May 2022 18:35:03 +0200 Subject: [PATCH 083/201] Fix typo --- src/library/autodj/autodjprocessor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index ab07c9024e27..c7deca4afeaf 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -780,11 +780,11 @@ void AutoDJProcessor::playerPositionChanged(DeckAttributes* pAttributes, m_transitionProgress = 0.0; emitAutoDJStateChanged(m_eState); - const double toDeckFadeDisatance = + const double toDeckFadeDistance = (thisDeck->fadeEndPos - thisDeck->fadeBeginPos) * - getEndSecond(thisDeck) / getEndSecond(thisDeck); + getEndSecond(thisDeck) / getEndSecond(otherDeck); // Re-cue the track if the user has seeked forward and will miss the fadeBeginPos - if (otherDeck->playPosition() >= otherDeck->fadeBeginPos - toDeckFadeDisatance) { + if (otherDeck->playPosition() >= otherDeck->fadeBeginPos - toDeckFadeDistance) { otherDeck->setPlayPosition(otherDeck->startPos); } From 7b1f871adffade485e00106aea1157481b91aba1 Mon Sep 17 00:00:00 2001 From: ronso0 Date: Tue, 10 May 2022 17:59:44 +0200 Subject: [PATCH 084/201] Broadcasting: allow multiple connections to same mount if only one is enabled --- src/preferences/dialog/dlgprefbroadcast.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/preferences/dialog/dlgprefbroadcast.cpp b/src/preferences/dialog/dlgprefbroadcast.cpp index ee98d5ab8904..e81468e88936 100644 --- a/src/preferences/dialog/dlgprefbroadcast.cpp +++ b/src/preferences/dialog/dlgprefbroadcast.cpp @@ -214,6 +214,7 @@ void DlgPrefBroadcast::slotApply() { QString profileName = profile->getProfileName(); QString profileMountpoint = profile->getMountpoint(); + bool profileEnabled = profile->getEnabled(); for (auto it = mountpoints.constBegin(); it != mountpoints.constEnd(); ++it) { if (it.value() == profileMountpoint) { @@ -221,16 +222,19 @@ void DlgPrefBroadcast::slotApply() { BroadcastProfilePtr profileWithSameMountpoint = m_pSettingsModel->getProfileByName(profileNameWithSameMountpoint); - if (!profileWithSameMountpoint.isNull() - && profileWithSameMountpoint->getHost().toLower() - == profile->getHost().toLower() - && profileWithSameMountpoint->getPort() - == profile->getPort() ) { + if (!profileWithSameMountpoint.isNull() && + profileWithSameMountpoint->getHost().toLower() == + profile->getHost().toLower() && + profileWithSameMountpoint->getPort() == + profile->getPort() + // allow same mountpoint if not both connections are enabled + && (profileEnabled && profileWithSameMountpoint->getEnabled())) { QMessageBox::warning(this, tr("Action failed"), tr("'%1' has the same Icecast mountpoint as '%2'.\n" "Two source connections to the same server " - "can't have the same mountpoint.") + "that have the same mountpoint can not be enabled " + "simultaneously.") .arg(profileName, profileNameWithSameMountpoint)); return; From 0b7f3527017a62f769a035bfddbd695a64725396 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 10 May 2022 19:30:16 +0200 Subject: [PATCH 085/201] Fix QStringBuilder errors on GCC 12.0.x --- src/musicbrainz/web/musicbrainzrecordingstask.cpp | 3 ++- src/preferences/dialog/dlgprefvinyl.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp index 2cda4b92d51c..8e3b3e1ef16a 100644 --- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp +++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp @@ -31,7 +31,8 @@ QString userAgentRawHeaderValue() { QStringLiteral("/") + VersionStore::version() + QStringLiteral(" ( ") + - QStringLiteral(MIXXX_WEBSITE_URL) + + // QStringLiteral(MIXXX_WEBSITE_URL) fails to compile on Fedora 36 with GCC 12.0.x + MIXXX_WEBSITE_URL + QStringLiteral(" )"); } diff --git a/src/preferences/dialog/dlgprefvinyl.cpp b/src/preferences/dialog/dlgprefvinyl.cpp index cc4f1102217d..6a7973df3eec 100644 --- a/src/preferences/dialog/dlgprefvinyl.cpp +++ b/src/preferences/dialog/dlgprefvinyl.cpp @@ -81,7 +81,8 @@ DlgPrefVinyl::DlgPrefVinyl(QWidget * parent, VinylControlManager *pVCMan, TroubleshootingLink->setText(coloredLinkString( m_pLinkColor, - QStringLiteral("Troubleshooting"), + // QStringLiteral("Troubleshooting") fails to compile on Fedora 36 with GCC 12.0.x + "Troubleshooting", MIXXX_MANUAL_VINYL_TROUBLESHOOTING_URL)); connect(VinylGain, &QSlider::sliderReleased, this, &DlgPrefVinyl::slotVinylGainApply); From c898ed9480a5b79eeab4149833e3a85e4666ec45 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 10 May 2022 20:01:36 +0200 Subject: [PATCH 086/201] Fix use-after-free errors for QSharedPointer on GCC 12.0.x --- src/preferences/broadcastsettings.cpp | 38 +++++++++++---------------- src/preferences/broadcastsettings.h | 6 ++--- src/test/broadcastsettings_test.cpp | 16 +++++------ 3 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/preferences/broadcastsettings.cpp b/src/preferences/broadcastsettings.cpp index c47af75f756b..940d3fbc81ce 100644 --- a/src/preferences/broadcastsettings.cpp +++ b/src/preferences/broadcastsettings.cpp @@ -68,7 +68,7 @@ void BroadcastSettings::loadProfiles() { loadLegacySettings(defaultProfile); addProfile(defaultProfile); - saveProfile(defaultProfile); + saveProfile(&*defaultProfile); } } @@ -97,13 +97,13 @@ bool BroadcastSettings::addProfile(BroadcastProfilePtr profile) { &BroadcastProfile::connectionStatusChanged, this, &BroadcastSettings::onConnectionStatusChanged); - m_profiles.insert(profile->getProfileName(), BroadcastProfilePtr(profile)); + m_profiles.insert(profile->getProfileName(), profile); emit profileAdded(profile); return true; } -bool BroadcastSettings::saveProfile(BroadcastProfilePtr profile) { +bool BroadcastSettings::saveProfile(BroadcastProfile* profile) { if (!profile) { return false; } @@ -135,20 +135,12 @@ QString BroadcastSettings::filePathForProfile(const QString& profileName) { return QDir(getProfilesFolder()).absoluteFilePath(filename); } -QString BroadcastSettings::filePathForProfile(BroadcastProfilePtr profile) { - if (!profile) { - return QString(); - } - - return filePathForProfile(profile->getProfileName()); +QString BroadcastSettings::filePathForProfile(const BroadcastProfile& profile) { + return filePathForProfile(profile.getProfileName()); } -bool BroadcastSettings::deleteFileForProfile(BroadcastProfilePtr profile) { - if (!profile) { - return false; - } - - QString filename = profile->getLastFilename(); +bool BroadcastSettings::deleteFileForProfile(const BroadcastProfile& profile) { + QString filename = profile.getLastFilename(); if (filename.isEmpty()) { // no file was saved, there is no file to delete return false; @@ -168,8 +160,9 @@ QString BroadcastSettings::getProfilesFolder() { } void BroadcastSettings::saveAll() { - for (const auto& kv : qAsConst(m_profiles)) { - saveProfile(kv); + for (const auto& pProfile : qAsConst(m_profiles)) { + DEBUG_ASSERT(pProfile); + saveProfile(&*pProfile); } emit profilesChanged(); } @@ -184,8 +177,8 @@ void BroadcastSettings::onProfileNameChanged(const QString& oldName, const QStri m_profiles.insert(newName, profile); emit profileRenamed(oldName, profile); - deleteFileForProfile(profile); - saveProfile(profile); + deleteFileForProfile(*profile); + saveProfile(&*profile); } } @@ -194,8 +187,8 @@ void BroadcastSettings::onConnectionStatusChanged(int newStatus) { } BroadcastProfilePtr BroadcastSettings::profileAt(int index) { - auto it = m_profiles.begin() + index; - return it != m_profiles.end() ? it.value() : BroadcastProfilePtr(nullptr); + auto it = std::next(m_profiles.begin(), index); + return it != m_profiles.end() ? it.value() : nullptr; } QList BroadcastSettings::profiles() { @@ -215,7 +208,8 @@ void BroadcastSettings::applyModel(BroadcastSettingsModel* pModel) { // If profile exists in settings but not in the model, // remove the profile from the settings const auto removedProfile = *profileIter; - deleteFileForProfile(removedProfile); + DEBUG_ASSERT(removedProfile); + deleteFileForProfile(*removedProfile); profileIter = m_profiles.erase(profileIter); emit profileRemoved(removedProfile); } else { diff --git a/src/preferences/broadcastsettings.h b/src/preferences/broadcastsettings.h index 6af26e568bf7..a1b92642e629 100644 --- a/src/preferences/broadcastsettings.h +++ b/src/preferences/broadcastsettings.h @@ -14,7 +14,7 @@ class BroadcastSettings : public QObject { public: BroadcastSettings(UserSettingsPointer pConfig, QObject* parent = nullptr); - bool saveProfile(BroadcastProfilePtr profile); + bool saveProfile(BroadcastProfile* profile); void saveAll(); QList profiles(); BroadcastProfilePtr profileAt(int index); @@ -35,9 +35,9 @@ class BroadcastSettings : public QObject { void loadProfiles(); bool addProfile(BroadcastProfilePtr profile); - QString filePathForProfile(BroadcastProfilePtr profile); + QString filePathForProfile(const BroadcastProfile& profile); QString filePathForProfile(const QString& profileName); - bool deleteFileForProfile(BroadcastProfilePtr profile); + bool deleteFileForProfile(const BroadcastProfile& profile); QString getProfilesFolder(); void loadLegacySettings(BroadcastProfilePtr profile); diff --git a/src/test/broadcastsettings_test.cpp b/src/test/broadcastsettings_test.cpp index 4e10a840eba6..f4f82e96cf62 100644 --- a/src/test/broadcastsettings_test.cpp +++ b/src/test/broadcastsettings_test.cpp @@ -16,16 +16,16 @@ TEST_F(BroadcastSettingsTest, SaveLoadAndRename) { BroadcastSettings settings(config()); BroadcastProfilePtr pProfile(new BroadcastProfile(originalProfileName)); - settings.saveProfile(pProfile); + settings.saveProfile(&*pProfile); // call saveProfile() and store the file name - settings.saveProfile(pProfile); + settings.saveProfile(&*pProfile); QString filename = pProfile->getLastFilename(); // rename the profile, the file name shouldn't change QFile::remove(filename); pProfile->setProfileName(newProfileName); - settings.saveProfile(pProfile); + settings.saveProfile(&*pProfile); EXPECT_TRUE(pProfile->getLastFilename() == filename); ASSERT_TRUE(QFile::exists(filename)); @@ -33,7 +33,7 @@ TEST_F(BroadcastSettingsTest, SaveLoadAndRename) { pProfile = BroadcastProfile::loadFromFile(filename); ASSERT_NE(pProfile, nullptr); QFile::remove(filename); - settings.saveProfile(pProfile); + settings.saveProfile(&*pProfile); EXPECT_TRUE(pProfile->getLastFilename() == filename); ASSERT_TRUE(QFile::exists(filename)); } @@ -47,8 +47,8 @@ TEST_F(BroadcastSettingsTest, AvoidExistingFiles) { BroadcastProfilePtr p2(new BroadcastProfile(name2)); // save both profiles and make sure they have different file names - settings.saveProfile(p1); - settings.saveProfile(p2); + settings.saveProfile(&*p1); + settings.saveProfile(&*p2); ASSERT_FALSE(p1->getLastFilename() == p2->getLastFilename()); } @@ -60,9 +60,9 @@ TEST_F(BroadcastSettingsTest, ReuseExistingFile) { BroadcastProfilePtr pProfile(new BroadcastProfile(name)); // save profile twice and make sure the name didn't change between saves - settings.saveProfile(pProfile); + settings.saveProfile(&*pProfile); QString filename = pProfile->getLastFilename(); - settings.saveProfile(pProfile); + settings.saveProfile(&*pProfile); ASSERT_TRUE(pProfile->getLastFilename() == filename); } From 58937c3588c28e72c5a35a5c3fa8a3ed50aca14b Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 10 May 2022 20:37:11 +0200 Subject: [PATCH 087/201] Upgrade googletest to main Commit bda85449f48f2d80a494c8c07766b6aba3170f3b --- lib/googletest/.clang-format | 4 - lib/googletest/.gitignore | 84 - lib/googletest/.travis.yml | 61 - lib/googletest/BUILD.bazel | 200 -- lib/googletest/CMakeLists.txt | 14 +- lib/googletest/CONTRIBUTING.md | 3 +- lib/googletest/CONTRIBUTORS | 2 + lib/googletest/README.md | 15 +- lib/googletest/WORKSPACE | 30 - lib/googletest/googlemock/CMakeLists.txt | 20 +- lib/googletest/googlemock/README.md | 4 - lib/googletest/googlemock/docs/README.md | 4 - .../googlemock/include/gmock/gmock-actions.h | 811 ++++++-- .../include/gmock/gmock-cardinalities.h | 8 +- .../include/gmock/gmock-function-mocker.h | 125 +- .../googlemock/include/gmock/gmock-matchers.h | 718 ++++--- .../include/gmock/gmock-more-actions.h | 603 +++--- .../include/gmock/gmock-more-matchers.h | 15 +- .../include/gmock/gmock-nice-strict.h | 13 +- .../include/gmock/gmock-spec-builders.h | 269 +-- .../googlemock/include/gmock/gmock.h | 8 +- .../include/gmock/internal/custom/README.md | 2 + .../internal/custom/gmock-generated-actions.h | 3 +- .../gmock/internal/custom/gmock-matchers.h | 7 +- .../gmock/internal/custom/gmock-port.h | 5 +- .../gmock/internal/gmock-internal-utils.h | 101 +- .../include/gmock/internal/gmock-port.h | 86 +- lib/googletest/googlemock/scripts/README.md | 5 - .../googlemock/scripts/fuse_gmock_files.py | 256 --- .../googlemock/scripts/generator/LICENSE | 203 -- .../googlemock/scripts/generator/README | 34 - .../scripts/generator/README.cppclean | 115 -- .../scripts/generator/cpp/__init__.py | 0 .../googlemock/scripts/generator/cpp/ast.py | 1773 ----------------- .../scripts/generator/cpp/gmock_class.py | 247 --- .../scripts/generator/cpp/gmock_class_test.py | 570 ------ .../scripts/generator/cpp/keywords.py | 56 - .../scripts/generator/cpp/tokenize.py | 284 --- .../googlemock/scripts/generator/cpp/utils.py | 37 - .../googlemock/scripts/generator/gmock_gen.py | 30 - .../googlemock/src/gmock-cardinalities.cc | 12 +- .../googlemock/src/gmock-internal-utils.cc | 112 +- .../googlemock/src/gmock-matchers.cc | 13 +- .../googlemock/src/gmock-spec-builders.cc | 82 +- lib/googletest/googlemock/src/gmock.cc | 62 +- lib/googletest/googlemock/src/gmock_main.cc | 4 +- lib/googletest/googletest/CMakeLists.txt | 19 +- lib/googletest/googletest/README.md | 8 +- .../googletest/cmake/internal_utils.cmake | 10 +- lib/googletest/googletest/docs/README.md | 4 - .../include/gtest/gtest-assertion-result.h | 232 +++ .../include/gtest/gtest-death-test.h | 89 +- .../googletest/include/gtest/gtest-matchers.h | 78 +- .../googletest/include/gtest/gtest-message.h | 27 +- .../include/gtest/gtest-param-test.h | 87 +- .../googletest/include/gtest/gtest-printers.h | 71 +- .../googletest/include/gtest/gtest-spi.h | 122 +- .../include/gtest/gtest-test-part.h | 14 +- .../include/gtest/gtest-typed-test.h | 38 +- .../googletest/include/gtest/gtest.h | 532 ++--- .../include/gtest/gtest_pred_impl.h | 200 +- .../googletest/include/gtest/gtest_prod.h | 9 +- .../include/gtest/internal/custom/README.md | 12 - .../internal/gtest-death-test-internal.h | 74 +- .../include/gtest/internal/gtest-filepath.h | 17 +- .../include/gtest/internal/gtest-internal.h | 322 +-- .../include/gtest/internal/gtest-param-util.h | 147 +- .../include/gtest/internal/gtest-port-arch.h | 100 +- .../include/gtest/internal/gtest-port.h | 977 ++++----- .../include/gtest/internal/gtest-string.h | 18 +- .../include/gtest/internal/gtest-type-util.h | 21 +- lib/googletest/googletest/scripts/README.md | 5 - lib/googletest/googletest/scripts/common.py | 83 - .../googletest/scripts/fuse_gtest_files.py | 253 --- .../googletest/scripts/gen_gtest_pred_impl.py | 733 ------- .../googletest/scripts/gtest-config.in | 274 --- .../googletest/scripts/release_docs.py | 158 -- .../googletest/scripts/run_with_path.py | 32 - .../googletest/scripts/test/Makefile | 59 - lib/googletest/googletest/scripts/upload.py | 1402 ------------- .../googletest/scripts/upload_gtest.py | 78 - lib/googletest/googletest/src/gtest-all.cc | 3 +- .../googletest/src/gtest-assertion-result.cc | 77 + .../googletest/src/gtest-death-test.cc | 520 +++-- .../googletest/src/gtest-filepath.cc | 82 +- .../googletest/src/gtest-internal-inl.h | 217 +- .../googletest/src/gtest-matchers.cc | 5 +- lib/googletest/googletest/src/gtest-port.cc | 384 ++-- .../googletest/src/gtest-printers.cc | 124 +- .../googletest/src/gtest-test-part.cc | 19 +- .../googletest/src/gtest-typed-test.cc | 7 +- lib/googletest/googletest/src/gtest.cc | 1455 +++++++------- lib/googletest/googletest/src/gtest_main.cc | 5 +- lib/googletest/library.json | 62 - 94 files changed, 5167 insertions(+), 11208 deletions(-) delete mode 100644 lib/googletest/.clang-format delete mode 100644 lib/googletest/.gitignore delete mode 100644 lib/googletest/.travis.yml delete mode 100644 lib/googletest/BUILD.bazel delete mode 100644 lib/googletest/WORKSPACE delete mode 100644 lib/googletest/googlemock/docs/README.md delete mode 100644 lib/googletest/googlemock/scripts/README.md delete mode 100755 lib/googletest/googlemock/scripts/fuse_gmock_files.py delete mode 100644 lib/googletest/googlemock/scripts/generator/LICENSE delete mode 100644 lib/googletest/googlemock/scripts/generator/README delete mode 100644 lib/googletest/googlemock/scripts/generator/README.cppclean delete mode 100755 lib/googletest/googlemock/scripts/generator/cpp/__init__.py delete mode 100755 lib/googletest/googlemock/scripts/generator/cpp/ast.py delete mode 100755 lib/googletest/googlemock/scripts/generator/cpp/gmock_class.py delete mode 100755 lib/googletest/googlemock/scripts/generator/cpp/gmock_class_test.py delete mode 100755 lib/googletest/googlemock/scripts/generator/cpp/keywords.py delete mode 100755 lib/googletest/googlemock/scripts/generator/cpp/tokenize.py delete mode 100755 lib/googletest/googlemock/scripts/generator/cpp/utils.py delete mode 100755 lib/googletest/googlemock/scripts/generator/gmock_gen.py delete mode 100644 lib/googletest/googletest/docs/README.md create mode 100644 lib/googletest/googletest/include/gtest/gtest-assertion-result.h delete mode 100644 lib/googletest/googletest/scripts/README.md delete mode 100644 lib/googletest/googletest/scripts/common.py delete mode 100755 lib/googletest/googletest/scripts/fuse_gtest_files.py delete mode 100755 lib/googletest/googletest/scripts/gen_gtest_pred_impl.py delete mode 100755 lib/googletest/googletest/scripts/gtest-config.in delete mode 100755 lib/googletest/googletest/scripts/release_docs.py delete mode 100755 lib/googletest/googletest/scripts/run_with_path.py delete mode 100644 lib/googletest/googletest/scripts/test/Makefile delete mode 100755 lib/googletest/googletest/scripts/upload.py delete mode 100755 lib/googletest/googletest/scripts/upload_gtest.py create mode 100644 lib/googletest/googletest/src/gtest-assertion-result.cc delete mode 100644 lib/googletest/library.json diff --git a/lib/googletest/.clang-format b/lib/googletest/.clang-format deleted file mode 100644 index 5b9bfe6d2242..000000000000 --- a/lib/googletest/.clang-format +++ /dev/null @@ -1,4 +0,0 @@ -# Run manually to reformat a file: -# clang-format -i --style=file -Language: Cpp -BasedOnStyle: Google diff --git a/lib/googletest/.gitignore b/lib/googletest/.gitignore deleted file mode 100644 index f08cb72a33cd..000000000000 --- a/lib/googletest/.gitignore +++ /dev/null @@ -1,84 +0,0 @@ -# Ignore CI build directory -build/ -xcuserdata -cmake-build-debug/ -.idea/ -bazel-bin -bazel-genfiles -bazel-googletest -bazel-out -bazel-testlogs -# python -*.pyc - -# Visual Studio files -.vs -*.sdf -*.opensdf -*.VC.opendb -*.suo -*.user -_ReSharper.Caches/ -Win32-Debug/ -Win32-Release/ -x64-Debug/ -x64-Release/ - -# Ignore autoconf / automake files -Makefile.in -aclocal.m4 -configure -build-aux/ -autom4te.cache/ -googletest/m4/libtool.m4 -googletest/m4/ltoptions.m4 -googletest/m4/ltsugar.m4 -googletest/m4/ltversion.m4 -googletest/m4/lt~obsolete.m4 -googlemock/m4 - -# Ignore generated directories. -googlemock/fused-src/ -googletest/fused-src/ - -# macOS files -.DS_Store -googletest/.DS_Store -googletest/xcode/.DS_Store - -# Ignore cmake generated directories and files. -CMakeFiles -CTestTestfile.cmake -Makefile -cmake_install.cmake -googlemock/CMakeFiles -googlemock/CTestTestfile.cmake -googlemock/Makefile -googlemock/cmake_install.cmake -googlemock/gtest -/bin -/googlemock/gmock.dir -/googlemock/gmock_main.dir -/googlemock/RUN_TESTS.vcxproj.filters -/googlemock/RUN_TESTS.vcxproj -/googlemock/INSTALL.vcxproj.filters -/googlemock/INSTALL.vcxproj -/googlemock/gmock_main.vcxproj.filters -/googlemock/gmock_main.vcxproj -/googlemock/gmock.vcxproj.filters -/googlemock/gmock.vcxproj -/googlemock/gmock.sln -/googlemock/ALL_BUILD.vcxproj.filters -/googlemock/ALL_BUILD.vcxproj -/lib -/Win32 -/ZERO_CHECK.vcxproj.filters -/ZERO_CHECK.vcxproj -/RUN_TESTS.vcxproj.filters -/RUN_TESTS.vcxproj -/INSTALL.vcxproj.filters -/INSTALL.vcxproj -/googletest-distribution.sln -/CMakeCache.txt -/ALL_BUILD.vcxproj.filters -/ALL_BUILD.vcxproj diff --git a/lib/googletest/.travis.yml b/lib/googletest/.travis.yml deleted file mode 100644 index 982e99c42a1d..000000000000 --- a/lib/googletest/.travis.yml +++ /dev/null @@ -1,61 +0,0 @@ -# Build matrix / environment variable are explained on: -# https://docs.travis-ci.com/user/customizing-the-build/ -# This file can be validated on: -# http://lint.travis-ci.org/ - -language: cpp - -# Define the matrix explicitly, manually expanding the combinations of (os, compiler, env). -# It is more tedious, but grants us far more flexibility. -matrix: - include: - - os: linux - dist: bionic - compiler: gcc - install: ./ci/install-linux.sh && ./ci/log-config.sh - script: ./ci/build-linux-bazel.sh - - os: linux - dist: bionic - compiler: clang - install: ./ci/install-linux.sh && ./ci/log-config.sh - script: ./ci/build-linux-bazel.sh - - os: linux - dist: bionic - compiler: gcc - env: BUILD_TYPE=Debug CXX_FLAGS="-std=c++11 -Wdeprecated" - - os: linux - dist: bionic - compiler: clang - env: BUILD_TYPE=Release CXX_FLAGS="-std=c++11 -Wdeprecated" NO_EXCEPTION=ON NO_RTTI=ON COMPILER_IS_GNUCXX=ON - - os: osx - osx_image: xcode12.2 - compiler: gcc - env: BUILD_TYPE=Release CC=gcc-10 CXX=g++-10 CXX_FLAGS="-std=c++11 -Wdeprecated" HOMEBREW_LOGS=~/homebrew-logs HOMEBREW_TEMP=~/homebrew-temp - - os: osx - osx_image: xcode12.2 - compiler: clang - env: BUILD_TYPE=Release CXX_FLAGS="-std=c++11 -Wdeprecated" HOMEBREW_LOGS=~/homebrew-logs HOMEBREW_TEMP=~/homebrew-temp - -# These are the install and build (script) phases for the most common entries in the matrix. They could be included -# in each entry in the matrix, but that is just repetitive. -install: - - ./ci/install-${TRAVIS_OS_NAME}.sh - - . ./ci/env-${TRAVIS_OS_NAME}.sh - - ./ci/log-config.sh - -script: ./ci/travis.sh - -# This section installs the necessary dependencies. -addons: - apt: - packages: - - g++ - - clang - update: true - homebrew: - packages: - - gcc@10 - update: true - -notifications: - email: false diff --git a/lib/googletest/BUILD.bazel b/lib/googletest/BUILD.bazel deleted file mode 100644 index 14661b6a463b..000000000000 --- a/lib/googletest/BUILD.bazel +++ /dev/null @@ -1,200 +0,0 @@ -# Copyright 2017 Google Inc. -# All Rights Reserved. -# -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Bazel Build for Google C++ Testing Framework(Google Test) - -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") - -package(default_visibility = ["//visibility:public"]) - -licenses(["notice"]) - -exports_files(["LICENSE"]) - -config_setting( - name = "windows", - constraint_values = ["@platforms//os:windows"], -) - -config_setting( - name = "msvc_compiler", - flag_values = { - "@bazel_tools//tools/cpp:compiler": "msvc-cl", - }, - visibility = [":__subpackages__"], -) - -config_setting( - name = "has_absl", - values = {"define": "absl=1"}, -) - -config_setting( - name = "ios", - values = {"apple_platform_type": "ios"}, - visibility = [":__subpackages__"], -) - -# Library that defines the FRIEND_TEST macro. -cc_library( - name = "gtest_prod", - hdrs = ["googletest/include/gtest/gtest_prod.h"], - includes = ["googletest/include"], -) - -# Google Test including Google Mock -cc_library( - name = "gtest", - srcs = glob( - include = [ - "googletest/src/*.cc", - "googletest/src/*.h", - "googletest/include/gtest/**/*.h", - "googlemock/src/*.cc", - "googlemock/include/gmock/**/*.h", - ], - exclude = [ - "googletest/src/gtest-all.cc", - "googletest/src/gtest_main.cc", - "googlemock/src/gmock-all.cc", - "googlemock/src/gmock_main.cc", - ], - ), - hdrs = glob([ - "googletest/include/gtest/*.h", - "googlemock/include/gmock/*.h", - ]), - copts = select({ - ":ios": [ - "-xobjective-c++", - "-pthread", - ], - ":windows": [], - "//conditions:default": ["-pthread"], - }), - defines = select({ - ":has_absl": ["GTEST_HAS_ABSL=1"], - "//conditions:default": [], - }), - features = select({ - ":windows": ["windows_export_all_symbols"], - "//conditions:default": [], - }), - includes = [ - "googlemock", - "googlemock/include", - "googletest", - "googletest/include", - ], - linkopts = select({ - ":windows": [], - "//conditions:default": ["-pthread"], - }), - deps = select({ - ":has_absl": [ - "@com_google_absl//absl/debugging:failure_signal_handler", - "@com_google_absl//absl/debugging:stacktrace", - "@com_google_absl//absl/debugging:symbolize", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:any", - "@com_google_absl//absl/types:optional", - "@com_google_absl//absl/types:variant", - ], - "//conditions:default": [], - }), -) - -cc_library( - name = "gtest_main", - srcs = ["googlemock/src/gmock_main.cc"], - features = select({ - ":windows": ["windows_export_all_symbols"], - "//conditions:default": [], - }), - deps = [":gtest"], -) - -# The following rules build samples of how to use gTest. -cc_library( - name = "gtest_sample_lib", - srcs = [ - "googletest/samples/sample1.cc", - "googletest/samples/sample2.cc", - "googletest/samples/sample4.cc", - ], - hdrs = [ - "googletest/samples/prime_tables.h", - "googletest/samples/sample1.h", - "googletest/samples/sample2.h", - "googletest/samples/sample3-inl.h", - "googletest/samples/sample4.h", - ], - features = select({ - ":windows": ["windows_export_all_symbols"], - "//conditions:default": [], - }), -) - -cc_test( - name = "gtest_samples", - size = "small", - # All Samples except: - # sample9 (main) - # sample10 (main and takes a command line option and needs to be separate) - srcs = [ - "googletest/samples/sample1_unittest.cc", - "googletest/samples/sample2_unittest.cc", - "googletest/samples/sample3_unittest.cc", - "googletest/samples/sample4_unittest.cc", - "googletest/samples/sample5_unittest.cc", - "googletest/samples/sample6_unittest.cc", - "googletest/samples/sample7_unittest.cc", - "googletest/samples/sample8_unittest.cc", - ], - linkstatic = 0, - deps = [ - "gtest_sample_lib", - ":gtest_main", - ], -) - -cc_test( - name = "sample9_unittest", - size = "small", - srcs = ["googletest/samples/sample9_unittest.cc"], - deps = [":gtest"], -) - -cc_test( - name = "sample10_unittest", - size = "small", - srcs = ["googletest/samples/sample10_unittest.cc"], - deps = [":gtest"], -) diff --git a/lib/googletest/CMakeLists.txt b/lib/googletest/CMakeLists.txt index 12fd7450d01e..4daf35b546c5 100644 --- a/lib/googletest/CMakeLists.txt +++ b/lib/googletest/CMakeLists.txt @@ -1,19 +1,21 @@ # Note: CMake support is community-based. The maintainers do not use CMake # internally. -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.5) if (POLICY CMP0048) cmake_policy(SET CMP0048 NEW) endif (POLICY CMP0048) +if (POLICY CMP0077) + cmake_policy(SET CMP0077 NEW) +endif (POLICY CMP0077) + project(googletest-distribution) -set(GOOGLETEST_VERSION 1.10.0) +set(GOOGLETEST_VERSION 1.11.0) -if (CMAKE_VERSION VERSION_GREATER "3.0.2") - if(NOT CYGWIN AND NOT MSYS AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL QNX) - set(CMAKE_CXX_EXTENSIONS OFF) - endif() +if(NOT CYGWIN AND NOT MSYS AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL QNX) + set(CMAKE_CXX_EXTENSIONS OFF) endif() enable_testing() diff --git a/lib/googletest/CONTRIBUTING.md b/lib/googletest/CONTRIBUTING.md index da45e4450ce7..5bdead538958 100644 --- a/lib/googletest/CONTRIBUTING.md +++ b/lib/googletest/CONTRIBUTING.md @@ -36,7 +36,8 @@ PR is acceptable as an alternative. This ensures that work isn't being duplicated and communicating your plan early also generally leads to better patches. 4. If your proposed change is accepted, and you haven't already done so, sign a - Contributor License Agreement (see details above). + Contributor License Agreement + ([see details above](#contributor-license-agreements)). 5. Fork the desired repo, develop and test your code changes. 6. Ensure that your code adheres to the existing style in the sample to which you are contributing. diff --git a/lib/googletest/CONTRIBUTORS b/lib/googletest/CONTRIBUTORS index 76db0b40ffb3..77397a5b53fe 100644 --- a/lib/googletest/CONTRIBUTORS +++ b/lib/googletest/CONTRIBUTORS @@ -34,6 +34,7 @@ Manuel Klimek Mario Tanev Mark Paskin Markus Heule +Martijn Vels Matthew Simmons Mika Raento Mike Bland @@ -55,6 +56,7 @@ Russ Rufer Sean Mcafee Sigurður Ásgeirsson Sverre Sundsdal +Szymon Sobik Takeshi Yoshino Tracy Bialik Vadim Berman diff --git a/lib/googletest/README.md b/lib/googletest/README.md index 7d872a57ed45..30edaecf313a 100644 --- a/lib/googletest/README.md +++ b/lib/googletest/README.md @@ -6,7 +6,8 @@ GoogleTest now follows the [Abseil Live at Head philosophy](https://abseil.io/about/philosophy#upgrade-support). -We recommend using the latest commit in the `master` branch in your projects. +We recommend +[updating to the latest commit in the `main` branch as often as possible](https://github.com/abseil/abseil-cpp/blob/master/FAQ.md#what-is-live-at-head-and-how-do-i-do-it). #### Documentation Updates @@ -14,9 +15,9 @@ Our documentation is now live on GitHub Pages at https://google.github.io/googletest/. We recommend browsing the documentation on GitHub Pages rather than directly in the repository. -#### Release 1.10.x +#### Release 1.11.0 -[Release 1.10.x](https://github.com/google/googletest/releases/tag/release-1.10.0) +[Release 1.11.0](https://github.com/google/googletest/releases/tag/release-1.11.0) is now available. #### Coming Soon @@ -109,8 +110,8 @@ Windows and Linux platforms. [GoogleTest UI](https://github.com/ospector/gtest-gbar) is a test runner that runs your test binary, allows you to track its progress via a progress bar, and -displays a list of test failures. Clicking on one shows failure text. Google -Test UI is written in C#. +displays a list of test failures. Clicking on one shows failure text. GoogleTest +UI is written in C#. [GTest TAP Listener](https://github.com/kinow/gtest-tap-listener) is an event listener for GoogleTest that implements the @@ -121,11 +122,11 @@ result output. If your test runner understands TAP, you may find it useful. runs tests from your binary in parallel to provide significant speed-up. [GoogleTest Adapter](https://marketplace.visualstudio.com/items?itemName=DavidSchuldenfrei.gtest-adapter) -is a VS Code extension allowing to view GoogleTest in a tree view, and run/debug +is a VS Code extension allowing to view GoogleTest in a tree view and run/debug your tests. [C++ TestMate](https://github.com/matepek/vscode-catch2-test-adapter) is a VS -Code extension allowing to view GoogleTest in a tree view, and run/debug your +Code extension allowing to view GoogleTest in a tree view and run/debug your tests. [Cornichon](https://pypi.org/project/cornichon/) is a small Gherkin DSL parser diff --git a/lib/googletest/WORKSPACE b/lib/googletest/WORKSPACE deleted file mode 100644 index 3b4451733118..000000000000 --- a/lib/googletest/WORKSPACE +++ /dev/null @@ -1,30 +0,0 @@ -workspace(name = "com_google_googletest") - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -http_archive( - name = "com_google_absl", # 2020-10-13T16:49:13Z - sha256 = "00c3707bf9cd5eabd1ec6932cc65b97378c043f22573be3adf7d11bb7af17d06", - strip_prefix = "abseil-cpp-f3f785ab59478dd0312bf1b5df65d380650bf0dc", - urls = [ - "https://github.com/abseil/abseil-cpp/archive/f3f785ab59478dd0312bf1b5df65d380650bf0dc.zip", - ], -) - -http_archive( - name = "rules_cc", # 2020-10-05T06:01:24Z - sha256 = "35ea62c63cd71d4000efe85f9f4f17e8afb23896c37ee9510952db2e9d8fbb70", - strip_prefix = "rules_cc-f055da4ff0cb2b3c73de1fe2f094ebdfb8b3acb9", - urls = [ - "https://github.com/bazelbuild/rules_cc/archive/f055da4ff0cb2b3c73de1fe2f094ebdfb8b3acb9.zip", - ], -) - -http_archive( - name = "rules_python", # 2020-09-30T13:50:21Z - sha256 = "6e49996ad3cf45b2232b8f94ca1e3ead369c28394c51632be8d85fe826383012", - strip_prefix = "rules_python-c064f7008a30f307ea7516cf52358a653011f82b", - urls = [ - "https://github.com/bazelbuild/rules_python/archive/c064f7008a30f307ea7516cf52358a653011f82b.zip", - ], -) diff --git a/lib/googletest/googlemock/CMakeLists.txt b/lib/googletest/googlemock/CMakeLists.txt index e7df8ec53d00..5c1f0dafea8b 100644 --- a/lib/googletest/googlemock/CMakeLists.txt +++ b/lib/googletest/googlemock/CMakeLists.txt @@ -36,13 +36,9 @@ endif() # as ${gmock_SOURCE_DIR} and to the root binary directory as # ${gmock_BINARY_DIR}. # Language "C" is required for find_package(Threads). -if (CMAKE_VERSION VERSION_LESS 3.0) - project(gmock CXX C) -else() - cmake_policy(SET CMP0048 NEW) - project(gmock VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) -endif() -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.5) +cmake_policy(SET CMP0048 NEW) +project(gmock VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C) if (COMMAND set_up_hermetic_build) set_up_hermetic_build() @@ -109,11 +105,12 @@ endif() # to the targets for when we are part of a parent build (ie being pulled # in via add_subdirectory() rather than being a standalone build). if (DEFINED CMAKE_VERSION AND NOT "${CMAKE_VERSION}" VERSION_LESS "2.8.11") + string(REPLACE ";" "$" dirs "${gmock_build_include_dirs}") target_include_directories(gmock SYSTEM INTERFACE - "$" + "$" "$/${CMAKE_INSTALL_INCLUDEDIR}>") target_include_directories(gmock_main SYSTEM INTERFACE - "$" + "$" "$/${CMAKE_INSTALL_INCLUDEDIR}>") endif() @@ -154,7 +151,10 @@ if (gmock_build_tests) cxx_test(gmock_ex_test gmock_main) cxx_test(gmock-function-mocker_test gmock_main) cxx_test(gmock-internal-utils_test gmock_main) - cxx_test(gmock-matchers_test gmock_main) + cxx_test(gmock-matchers-arithmetic_test gmock_main) + cxx_test(gmock-matchers-comparisons_test gmock_main) + cxx_test(gmock-matchers-containers_test gmock_main) + cxx_test(gmock-matchers-misc_test gmock_main) cxx_test(gmock-more-actions_test gmock_main) cxx_test(gmock-nice-strict_test gmock_main) cxx_test(gmock-port_test gmock_main) diff --git a/lib/googletest/googlemock/README.md b/lib/googletest/googlemock/README.md index ead688325d2c..7da60655dba8 100644 --- a/lib/googletest/googlemock/README.md +++ b/lib/googletest/googlemock/README.md @@ -35,10 +35,6 @@ Details and examples can be found here: * [gMock Cookbook](https://google.github.io/googletest/gmock_cook_book.html) * [gMock Cheat Sheet](https://google.github.io/googletest/gmock_cheat_sheet.html) -Please note that code under scripts/generator/ is from the -[cppclean project](http://code.google.com/p/cppclean/) and under the Apache -License, which is different from GoogleMock's license. - GoogleMock is a part of [GoogleTest C++ testing framework](http://github.com/google/googletest/) and a subject to the same requirements. diff --git a/lib/googletest/googlemock/docs/README.md b/lib/googletest/googlemock/docs/README.md deleted file mode 100644 index 1bc57b799cce..000000000000 --- a/lib/googletest/googlemock/docs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Content Moved - -We are working on updates to the GoogleTest documentation, which has moved to -the top-level [docs](../../docs) directory. diff --git a/lib/googletest/googlemock/include/gmock/gmock-actions.h b/lib/googletest/googlemock/include/gmock/gmock-actions.h index f2393bd3afad..c83a99ada2d0 100644 --- a/lib/googletest/googlemock/include/gmock/gmock-actions.h +++ b/lib/googletest/googlemock/include/gmock/gmock-actions.h @@ -27,7 +27,6 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - // Google Mock - a framework for writing C++ mock classes. // // The ACTION* family of macros can be used in a namespace scope to @@ -125,13 +124,14 @@ // To learn more about using these macros, please search for 'ACTION' on // https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md -// GOOGLETEST_CM0002 DO NOT DELETE +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* #ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ #define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ #ifndef _WIN32_WCE -# include +#include #endif #include @@ -147,8 +147,8 @@ #include "gmock/internal/gmock-pp.h" #ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable:4100) +#pragma warning(push) +#pragma warning(disable : 4100) #endif namespace testing { @@ -196,9 +196,7 @@ class BuiltInDefaultValue { public: // This function returns true if and only if type T has a built-in default // value. - static bool Exists() { - return ::std::is_default_constructible::value; - } + static bool Exists() { return ::std::is_default_constructible::value; } static T Get() { return BuiltInDefaultValueGetter< @@ -227,11 +225,11 @@ class BuiltInDefaultValue { // The following specializations define the default values for // specific types we care about. #define GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(type, value) \ - template <> \ - class BuiltInDefaultValue { \ - public: \ - static bool Exists() { return true; } \ - static type Get() { return value; } \ + template <> \ + class BuiltInDefaultValue { \ + public: \ + static bool Exists() { return true; } \ + static type Get() { return value; } \ } GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(void, ); // NOLINT @@ -255,18 +253,253 @@ GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned short, 0U); // NOLINT GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed short, 0); // NOLINT GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned int, 0U); GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed int, 0); -GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL); // NOLINT -GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L); // NOLINT GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long long, 0); // NOLINT -GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long long, 0); // NOLINT +GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long long, 0); // NOLINT GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(float, 0); GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0); #undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_ -// Simple two-arg form of std::disjunction. -template -using disjunction = typename ::std::conditional::type; +// Partial implementations of metaprogramming types from the standard library +// not available in C++11. + +template +struct negation + // NOLINTNEXTLINE + : std::integral_constant {}; + +// Base case: with zero predicates the answer is always true. +template +struct conjunction : std::true_type {}; + +// With a single predicate, the answer is that predicate. +template +struct conjunction : P1 {}; + +// With multiple predicates the answer is the first predicate if that is false, +// and we recurse otherwise. +template +struct conjunction + : std::conditional, P1>::type {}; + +template +struct disjunction : std::false_type {}; + +template +struct disjunction : P1 {}; + +template +struct disjunction + // NOLINTNEXTLINE + : std::conditional, P1>::type {}; + +template +using void_t = void; + +// Like std::invoke_result_t from C++17, but works only for objects with call +// operators (not e.g. member function pointers, which we don't need specific +// support for in OnceAction because std::function deals with them). +template +using call_result_t = decltype(std::declval()(std::declval()...)); + +template +struct is_callable_r_impl : std::false_type {}; + +// Specialize the struct for those template arguments where call_result_t is +// well-formed. When it's not, the generic template above is chosen, resulting +// in std::false_type. +template +struct is_callable_r_impl>, R, F, Args...> + : std::conditional< + std::is_same::value, // + std::true_type, // + std::is_convertible, R>>::type {}; + +// Like std::is_invocable_r from C++17, but works only for objects with call +// operators. See the note on call_result_t. +template +using is_callable_r = is_callable_r_impl; + +// Specialized for function types below. +template +class OnceAction; + +// An action that can only be used once. +// +// This is what is accepted by WillOnce, which doesn't require the underlying +// action to be copy-constructible (only move-constructible), and promises to +// invoke it as an rvalue reference. This allows the action to work with +// move-only types like std::move_only_function in a type-safe manner. +// +// For example: +// +// // Assume we have some API that needs to accept a unique pointer to some +// // non-copyable object Foo. +// void AcceptUniquePointer(std::unique_ptr foo); +// +// // We can define an action that provides a Foo to that API. Because It +// // has to give away its unique pointer, it must not be called more than +// // once, so its call operator is &&-qualified. +// struct ProvideFoo { +// std::unique_ptr foo; +// +// void operator()() && { +// AcceptUniquePointer(std::move(Foo)); +// } +// }; +// +// // This action can be used with WillOnce. +// EXPECT_CALL(mock, Call) +// .WillOnce(ProvideFoo{std::make_unique(...)}); +// +// // But a call to WillRepeatedly will fail to compile. This is correct, +// // since the action cannot correctly be used repeatedly. +// EXPECT_CALL(mock, Call) +// .WillRepeatedly(ProvideFoo{std::make_unique(...)}); +// +// A less-contrived example would be an action that returns an arbitrary type, +// whose &&-qualified call operator is capable of dealing with move-only types. +template +class OnceAction final { + private: + // True iff we can use the given callable type (or lvalue reference) directly + // via StdFunctionAdaptor. + template + using IsDirectlyCompatible = internal::conjunction< + // It must be possible to capture the callable in StdFunctionAdaptor. + std::is_constructible::type, Callable>, + // The callable must be compatible with our signature. + internal::is_callable_r::type, + Args...>>; + + // True iff we can use the given callable type via StdFunctionAdaptor once we + // ignore incoming arguments. + template + using IsCompatibleAfterIgnoringArguments = internal::conjunction< + // It must be possible to capture the callable in a lambda. + std::is_constructible::type, Callable>, + // The callable must be invocable with zero arguments, returning something + // convertible to Result. + internal::is_callable_r::type>>; + + public: + // Construct from a callable that is directly compatible with our mocked + // signature: it accepts our function type's arguments and returns something + // convertible to our result type. + template ::type>>, + IsDirectlyCompatible> // + ::value, + int>::type = 0> + OnceAction(Callable&& callable) // NOLINT + : function_(StdFunctionAdaptor::type>( + {}, std::forward(callable))) {} + + // As above, but for a callable that ignores the mocked function's arguments. + template ::type>>, + // Exclude callables for which the overload above works. + // We'd rather provide the arguments if possible. + internal::negation>, + IsCompatibleAfterIgnoringArguments>::value, + int>::type = 0> + OnceAction(Callable&& callable) // NOLINT + // Call the constructor above with a callable + // that ignores the input arguments. + : OnceAction(IgnoreIncomingArguments::type>{ + std::forward(callable)}) {} + + // We are naturally copyable because we store only an std::function, but + // semantically we should not be copyable. + OnceAction(const OnceAction&) = delete; + OnceAction& operator=(const OnceAction&) = delete; + OnceAction(OnceAction&&) = default; + + // Invoke the underlying action callable with which we were constructed, + // handing it the supplied arguments. + Result Call(Args... args) && { + return function_(std::forward(args)...); + } + + private: + // An adaptor that wraps a callable that is compatible with our signature and + // being invoked as an rvalue reference so that it can be used as an + // StdFunctionAdaptor. This throws away type safety, but that's fine because + // this is only used by WillOnce, which we know calls at most once. + // + // Once we have something like std::move_only_function from C++23, we can do + // away with this. + template + class StdFunctionAdaptor final { + public: + // A tag indicating that the (otherwise universal) constructor is accepting + // the callable itself, instead of e.g. stealing calls for the move + // constructor. + struct CallableTag final {}; + + template + explicit StdFunctionAdaptor(CallableTag, F&& callable) + : callable_(std::make_shared(std::forward(callable))) {} + + // Rather than explicitly returning Result, we return whatever the wrapped + // callable returns. This allows for compatibility with existing uses like + // the following, when the mocked function returns void: + // + // EXPECT_CALL(mock_fn_, Call) + // .WillOnce([&] { + // [...] + // return 0; + // }); + // + // Such a callable can be turned into std::function. If we use an + // explicit return type of Result here then it *doesn't* work with + // std::function, because we'll get a "void function should not return a + // value" error. + // + // We need not worry about incompatible result types because the SFINAE on + // OnceAction already checks this for us. std::is_invocable_r_v itself makes + // the same allowance for void result types. + template + internal::call_result_t operator()( + ArgRefs&&... args) const { + return std::move(*callable_)(std::forward(args)...); + } + + private: + // We must put the callable on the heap so that we are copyable, which + // std::function needs. + std::shared_ptr callable_; + }; + + // An adaptor that makes a callable that accepts zero arguments callable with + // our mocked arguments. + template + struct IgnoreIncomingArguments { + internal::call_result_t operator()(Args&&...) { + return std::move(callable)(); + } + + Callable callable; + }; + + std::function function_; +}; } // namespace internal @@ -339,7 +572,8 @@ class DefaultValue { private: const T value_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(FixedValueProducer); + FixedValueProducer(const FixedValueProducer&) = delete; + FixedValueProducer& operator=(const FixedValueProducer&) = delete; }; class FactoryValueProducer : public ValueProducer { @@ -350,7 +584,8 @@ class DefaultValue { private: const FactoryFunction factory_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(FactoryValueProducer); + FactoryValueProducer(const FactoryValueProducer&) = delete; + FactoryValueProducer& operator=(const FactoryValueProducer&) = delete; }; static ValueProducer* producer_; @@ -424,28 +659,34 @@ class ActionInterface { virtual Result Perform(const ArgumentTuple& args) = 0; private: - GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionInterface); + ActionInterface(const ActionInterface&) = delete; + ActionInterface& operator=(const ActionInterface&) = delete; }; -// An Action is a copyable and IMMUTABLE (except by assignment) -// object that represents an action to be taken when a mock function -// of type F is called. The implementation of Action is just a -// std::shared_ptr to const ActionInterface. Don't inherit from Action! -// You can view an object implementing ActionInterface as a -// concrete action (including its current state), and an Action -// object as a handle to it. template -class Action { +class Action; + +// An Action is a copyable and IMMUTABLE (except by assignment) +// object that represents an action to be taken when a mock function of type +// R(Args...) is called. The implementation of Action is just a +// std::shared_ptr to const ActionInterface. Don't inherit from Action! You +// can view an object implementing ActionInterface as a concrete action +// (including its current state), and an Action object as a handle to it. +template +class Action { + private: + using F = R(Args...); + // Adapter class to allow constructing Action from a legacy ActionInterface. // New code should create Actions from functors instead. struct ActionAdapter { // Adapter must be copyable to satisfy std::function requirements. ::std::shared_ptr> impl_; - template - typename internal::Function::Result operator()(Args&&... args) { + template + typename internal::Function::Result operator()(InArgs&&... args) { return impl_->Perform( - ::std::forward_as_tuple(::std::forward(args)...)); + ::std::forward_as_tuple(::std::forward(args)...)); } }; @@ -480,7 +721,8 @@ class Action { // Action, as long as F's arguments can be implicitly converted // to Func's and Func's return type can be implicitly converted to F's. template - explicit Action(const Action& action) : fun_(action.fun_) {} + Action(const Action& action) // NOLINT + : fun_(action.fun_) {} // Returns true if and only if this is the DoDefault() action. bool IsDoDefault() const { return fun_ == nullptr; } @@ -498,6 +740,24 @@ class Action { return internal::Apply(fun_, ::std::move(args)); } + // An action can be used as a OnceAction, since it's obviously safe to call it + // once. + operator internal::OnceAction() const { // NOLINT + // Return a OnceAction-compatible callable that calls Perform with the + // arguments it is provided. We could instead just return fun_, but then + // we'd need to handle the IsDoDefault() case separately. + struct OA { + Action action; + + R operator()(Args... args) && { + return action.Perform( + std::forward_as_tuple(std::forward(args)...)); + } + }; + + return OA{*this}; + } + private: template friend class Action; @@ -514,8 +774,8 @@ class Action { template struct IgnoreArgs { - template - Result operator()(const Args&...) const { + template + Result operator()(const InArgs&...) const { return function_impl(); } @@ -634,12 +894,12 @@ struct ByMoveWrapper { // of gtl::Container() is passed into Return. // template -class ReturnAction { +class ReturnAction final { public: // Constructs a ReturnAction object from the value to be returned. // 'value' is passed by value instead of by const reference in order // to allow Return("string literal") to compile. - explicit ReturnAction(R value) : value_(new R(std::move(value))) {} + explicit ReturnAction(R value) : value_(std::move(value)) {} // This template type conversion operator allows Return(x) to be // used in ANY function that returns x's type. @@ -654,70 +914,80 @@ class ReturnAction { // and put the typedef both here (for use in assert statement) and // in the Impl class. But both definitions must be the same. typedef typename Function::Result Result; - GTEST_COMPILE_ASSERT_( - !std::is_reference::value, - use_ReturnRef_instead_of_Return_to_return_a_reference); + static_assert(!std::is_reference::value, + "use ReturnRef instead of Return to return a reference"); static_assert(!std::is_void::value, "Can't use Return() on an action expected to return `void`."); - return Action(new Impl(value_)); + return Action(new Impl(value_)); } private: // Implements the Return(x) action for a particular function type F. - template + template class Impl : public ActionInterface { public: typedef typename Function::Result Result; typedef typename Function::ArgumentTuple ArgumentTuple; - // The implicit cast is necessary when Result has more than one - // single-argument constructor (e.g. Result is std::vector) and R - // has a type conversion operator template. In that case, value_(value) - // won't compile as the compiler doesn't known which constructor of - // Result to call. ImplicitCast_ forces the compiler to convert R to - // Result without considering explicit constructors, thus resolving the - // ambiguity. value_ is then initialized using its copy constructor. - explicit Impl(const std::shared_ptr& value) - : value_before_cast_(*value), + explicit Impl(const R& value) + : value_before_cast_(value), + // Make an implicit conversion to Result before initializing the + // Result object we store, avoiding calling any explicit constructor + // of Result from R. + // + // This simulates the language rules: a function with return type + // Result that does `return R()` requires R to be implicitly + // convertible to Result, and uses that path for the conversion, even + // if Result has an explicit constructor from R. value_(ImplicitCast_(value_before_cast_)) {} Result Perform(const ArgumentTuple&) override { return value_; } private: - GTEST_COMPILE_ASSERT_(!std::is_reference::value, - Result_cannot_be_a_reference_type); + static_assert(!std::is_reference::value, + "Result cannot be a reference type"); // We save the value before casting just in case it is being cast to a // wrapper type. R value_before_cast_; Result value_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(Impl); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; }; - // Partially specialize for ByMoveWrapper. This version of ReturnAction will - // move its contents instead. - template - class Impl, F> : public ActionInterface { - public: - typedef typename Function::Result Result; - typedef typename Function::ArgumentTuple ArgumentTuple; + R value_; +}; - explicit Impl(const std::shared_ptr& wrapper) - : performed_(false), wrapper_(wrapper) {} +// A specialization of ReturnAction when R is ByMoveWrapper for some T. +// +// This version applies the type system-defeating hack of moving from T even in +// the const call operator, checking at runtime that it isn't called more than +// once, since the user has declared their intent to do so by using ByMove. +template +class ReturnAction> final { + public: + explicit ReturnAction(ByMoveWrapper wrapper) + : state_(new State(std::move(wrapper.payload))) {} - Result Perform(const ArgumentTuple&) override { - GTEST_CHECK_(!performed_) - << "A ByMove() action should only be performed once."; - performed_ = true; - return std::move(wrapper_->payload); - } + T operator()() const { + GTEST_CHECK_(!state_->called) + << "A ByMove() action must be performed at most once."; - private: - bool performed_; - const std::shared_ptr wrapper_; + state_->called = true; + return std::move(state_->value); + } + + private: + // We store our state on the heap so that we are copyable as required by + // Action, despite the fact that we are stateful and T may not be copyable. + struct State { + explicit State(T&& value_in) : value(std::move(value_in)) {} + + T value; + bool called = false; }; - const std::shared_ptr value_; + const std::shared_ptr state_; }; // Implements the ReturnNull() action. @@ -759,8 +1029,8 @@ class ReturnRefAction { // Asserts that the function return type is a reference. This // catches the user error of using ReturnRef(x) when Return(x) // should be used, and generates some helpful error message. - GTEST_COMPILE_ASSERT_(std::is_reference::value, - use_Return_instead_of_ReturnRef_to_return_a_value); + static_assert(std::is_reference::value, + "use Return instead of ReturnRef to return a value"); return Action(new Impl(ref_)); } @@ -801,9 +1071,8 @@ class ReturnRefOfCopyAction { // Asserts that the function return type is a reference. This // catches the user error of using ReturnRefOfCopy(x) when Return(x) // should be used, and generates some helpful error message. - GTEST_COMPILE_ASSERT_( - std::is_reference::value, - use_Return_instead_of_ReturnRefOfCopy_to_return_a_value); + static_assert(std::is_reference::value, + "use Return instead of ReturnRefOfCopy to return a value"); return Action(new Impl(value_)); } @@ -839,7 +1108,7 @@ class ReturnRoundRobinAction { template T operator()(Args&&...) const { - return state_->Next(); + return state_->Next(); } private: @@ -862,7 +1131,9 @@ class DoDefaultAction { // This template type conversion operator allows DoDefault() to be // used in any function. template - operator Action() const { return Action(); } // NOLINT + operator Action() const { + return Action(); + } // NOLINT }; // Implements the Assign action to set a given pointer referent to a @@ -890,8 +1161,7 @@ template class SetErrnoAndReturnAction { public: SetErrnoAndReturnAction(int errno_value, T result) - : errno_(errno_value), - result_(result) {} + : errno_(errno_value), result_(result) {} template Result Perform(const ArgumentTuple& /* args */) const { errno = errno_; @@ -1002,8 +1272,8 @@ class IgnoreResultAction { private: // Type OriginalFunction is the same as F except that its return // type is IgnoredValue. - typedef typename internal::Function::MakeResultIgnoredValue - OriginalFunction; + typedef + typename internal::Function::MakeResultIgnoredValue OriginalFunction; const Action action_; }; @@ -1013,55 +1283,239 @@ class IgnoreResultAction { template struct WithArgsAction { - InnerAction action; + InnerAction inner_action; - // The inner action could be anything convertible to Action. - // We use the conversion operator to detect the signature of the inner Action. + // The signature of the function as seen by the inner action, given an out + // action with the given result and argument types. template + using InnerSignature = + R(typename std::tuple_element>::type...); + + // Rather than a call operator, we must define conversion operators to + // particular action types. This is necessary for embedded actions like + // DoDefault(), which rely on an action conversion operators rather than + // providing a call operator because even with a particular set of arguments + // they don't have a fixed return type. + + template >::type...)>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + struct OA { + OnceAction> inner_action; + + R operator()(Args&&... args) && { + return std::move(inner_action) + .Call(std::get( + std::forward_as_tuple(std::forward(args)...))...); + } + }; + + return OA{std::move(inner_action)}; + } + + template >::type...)>>::value, + int>::type = 0> operator Action() const { // NOLINT - using TupleType = std::tuple; - Action::type...)> - converted(action); + Action> converted(inner_action); - return [converted](Args... args) -> R { + return [converted](Args&&... args) -> R { return converted.Perform(std::forward_as_tuple( - std::get(std::forward_as_tuple(std::forward(args)...))...)); + std::get(std::forward_as_tuple(std::forward(args)...))...)); }; } }; template -struct DoAllAction { - private: +class DoAllAction; + +// Base case: only a single action. +template +class DoAllAction { + public: + struct UserConstructorTag {}; + template - using NonFinalType = - typename std::conditional::value, T, const T&>::type; + explicit DoAllAction(UserConstructorTag, T&& action) + : final_action_(std::forward(action)) {} + + // Rather than a call operator, we must define conversion operators to + // particular action types. This is necessary for embedded actions like + // DoDefault(), which rely on an action conversion operators rather than + // providing a call operator because even with a particular set of arguments + // they don't have a fixed return type. + + template >::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + return std::move(final_action_); + } - template - std::vector Convert(IndexSequence) const { - return {ActionT(std::get(actions))...}; + template < + typename R, typename... Args, + typename std::enable_if< + std::is_convertible>::value, + int>::type = 0> + operator Action() const { // NOLINT + return final_action_; } + private: + FinalAction final_action_; +}; + +// Recursive case: support N actions by calling the initial action and then +// calling through to the base class containing N-1 actions. +template +class DoAllAction + : private DoAllAction { + private: + using Base = DoAllAction; + + // The type of reference that should be provided to an initial action for a + // mocked function parameter of type T. + // + // There are two quirks here: + // + // * Unlike most forwarding functions, we pass scalars through by value. + // This isn't strictly necessary because an lvalue reference would work + // fine too and be consistent with other non-reference types, but it's + // perhaps less surprising. + // + // For example if the mocked function has signature void(int), then it + // might seem surprising for the user's initial action to need to be + // convertible to Action. This is perhaps less + // surprising for a non-scalar type where there may be a performance + // impact, or it might even be impossible, to pass by value. + // + // * More surprisingly, `const T&` is often not a const reference type. + // By the reference collapsing rules in C++17 [dcl.ref]/6, if T refers to + // U& or U&& for some non-scalar type U, then InitialActionArgType is + // U&. In other words, we may hand over a non-const reference. + // + // So for example, given some non-scalar type Obj we have the following + // mappings: + // + // T InitialActionArgType + // ------- ----------------------- + // Obj const Obj& + // Obj& Obj& + // Obj&& Obj& + // const Obj const Obj& + // const Obj& const Obj& + // const Obj&& const Obj& + // + // In other words, the initial actions get a mutable view of an non-scalar + // argument if and only if the mock function itself accepts a non-const + // reference type. They are never given an rvalue reference to an + // non-scalar type. + // + // This situation makes sense if you imagine use with a matcher that is + // designed to write through a reference. For example, if the caller wants + // to fill in a reference argument and then return a canned value: + // + // EXPECT_CALL(mock, Call) + // .WillOnce(DoAll(SetArgReferee<0>(17), Return(19))); + // + template + using InitialActionArgType = + typename std::conditional::value, T, const T&>::type; + public: - std::tuple actions; + struct UserConstructorTag {}; + + template + explicit DoAllAction(UserConstructorTag, T&& initial_action, + U&&... other_actions) + : Base({}, std::forward(other_actions)...), + initial_action_(std::forward(initial_action)) {} + + template ...)>>, + std::is_convertible>>::value, + int>::type = 0> + operator OnceAction() && { // NOLINT + // Return an action that first calls the initial action with arguments + // filtered through InitialActionArgType, then forwards arguments directly + // to the base class to deal with the remaining actions. + struct OA { + OnceAction...)> initial_action; + OnceAction remaining_actions; + + R operator()(Args... args) && { + std::move(initial_action) + .Call(static_cast>(args)...); + + return std::move(remaining_actions).Call(std::forward(args)...); + } + }; - template + return OA{ + std::move(initial_action_), + std::move(static_cast(*this)), + }; + } + + template < + typename R, typename... Args, + typename std::enable_if< + conjunction< + // Both the initial action and the rest must support conversion to + // Action. + std::is_convertible...)>>, + std::is_convertible>>::value, + int>::type = 0> operator Action() const { // NOLINT - struct Op { - std::vector...)>> converted; - Action last; + // Return an action that first calls the initial action with arguments + // filtered through InitialActionArgType, then forwards arguments directly + // to the base class to deal with the remaining actions. + struct OA { + Action...)> initial_action; + Action remaining_actions; + R operator()(Args... args) const { - auto tuple_args = std::forward_as_tuple(std::forward(args)...); - for (auto& a : converted) { - a.Perform(tuple_args); - } - return last.Perform(std::move(tuple_args)); + initial_action.Perform(std::forward_as_tuple( + static_cast>(args)...)); + + return remaining_actions.Perform( + std::forward_as_tuple(std::forward(args)...)); } }; - return Op{Convert...)>>( - MakeIndexSequence()), - std::get(actions)}; + + return OA{ + initial_action_, + static_cast(*this), + }; } + + private: + InitialAction initial_action_; }; template @@ -1078,10 +1532,11 @@ struct ReturnNewAction { template struct ReturnArgAction { - template - auto operator()(const Args&... args) const -> - typename std::tuple_element>::type { - return std::get(std::tie(args...)); + template ::type> + auto operator()(Args&&... args) const -> decltype(std::get( + std::forward_as_tuple(std::forward(args)...))) { + return std::get(std::forward_as_tuple(std::forward(args)...)); } }; @@ -1203,7 +1658,8 @@ typedef internal::IgnoredValue Unused; template internal::DoAllAction::type...> DoAll( Action&&... action) { - return {std::forward_as_tuple(std::forward(action)...)}; + return internal::DoAllAction::type...>( + {}, std::forward(action)...); } // WithArg(an_action) creates an action that passes the k-th @@ -1212,8 +1668,8 @@ internal::DoAllAction::type...> DoAll( // multiple arguments. For convenience, we also provide // WithArgs(an_action) (defined below) as a synonym. template -internal::WithArgsAction::type, k> -WithArg(InnerAction&& action) { +internal::WithArgsAction::type, k> WithArg( + InnerAction&& action) { return {std::forward(action)}; } @@ -1232,8 +1688,8 @@ WithArgs(InnerAction&& action) { // argument. In other words, it adapts an action accepting no // argument to one that accepts (and ignores) arguments. template -internal::WithArgsAction::type> -WithoutArgs(InnerAction&& action) { +internal::WithArgsAction::type> WithoutArgs( + InnerAction&& action) { return {std::forward(action)}; } @@ -1319,7 +1775,7 @@ internal::SetArgumentPointeeAction SetArgumentPointee(T value) { // Creates an action that sets a pointer referent to a given value. template -PolymorphicAction > Assign(T1* ptr, T2 val) { +PolymorphicAction> Assign(T1* ptr, T2 val) { return MakePolymorphicAction(internal::AssignAction(ptr, val)); } @@ -1327,8 +1783,8 @@ PolymorphicAction > Assign(T1* ptr, T2 val) { // Creates an action that sets errno and returns the appropriate error. template -PolymorphicAction > -SetErrnoAndReturn(int errval, T result) { +PolymorphicAction> SetErrnoAndReturn( + int errval, T result) { return MakePolymorphicAction( internal::SetErrnoAndReturnAction(errval, result)); } @@ -1482,7 +1938,8 @@ struct ExcessiveArg {}; // Builds an implementation of an Action<> for some particular signature, using // a class defined by an ACTION* macro. -template struct ActionImpl; +template +struct ActionImpl; template struct ImplBase { @@ -1502,7 +1959,7 @@ struct ActionImpl : ImplBase::type { using args_type = std::tuple; ActionImpl() = default; // Only defined if appropriate for Base. - explicit ActionImpl(std::shared_ptr impl) : Base{std::move(impl)} { } + explicit ActionImpl(std::shared_ptr impl) : Base{std::move(impl)} {} R operator()(Args&&... arg) const { static constexpr size_t kMaxArgs = @@ -1521,12 +1978,14 @@ struct ActionImpl : ImplBase::type { // args_type get passed, followed by a dummy of unspecified type for the // remainder up to 10 explicit args. static constexpr ExcessiveArg kExcessArg{}; - return static_cast(*this).template gmock_PerformImpl< - /*function_type=*/function_type, /*return_type=*/R, - /*args_type=*/args_type, - /*argN_type=*/typename std::tuple_element::type...>( - /*args=*/args, std::get(args)..., - ((void)excess_id, kExcessArg)...); + return static_cast(*this) + .template gmock_PerformImpl< + /*function_type=*/function_type, /*return_type=*/R, + /*args_type=*/args_type, + /*argN_type=*/ + typename std::tuple_element::type...>( + /*args=*/args, std::get(args)..., + ((void)excess_id, kExcessArg)...); } }; @@ -1545,7 +2004,7 @@ ::testing::Action MakeAction(std::shared_ptr impl) { #define GMOCK_INTERNAL_ARG_UNUSED(i, data, el) \ , const arg##i##_type& arg##i GTEST_ATTRIBUTE_UNUSED_ -#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_ \ +#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_ \ const args_type& args GTEST_ATTRIBUTE_UNUSED_ GMOCK_PP_REPEAT( \ GMOCK_INTERNAL_ARG_UNUSED, , 10) @@ -1584,42 +2043,47 @@ ::testing::Action MakeAction(std::shared_ptr impl) { #define GMOCK_ACTION_FIELD_PARAMS_(params) \ GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_FIELD_PARAM, , params) -#define GMOCK_INTERNAL_ACTION(name, full_name, params) \ - template \ - class full_name { \ - public: \ - explicit full_name(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ - : impl_(std::make_shared( \ - GMOCK_ACTION_GVALUE_PARAMS_(params))) { } \ - full_name(const full_name&) = default; \ - full_name(full_name&&) noexcept = default; \ - template \ - operator ::testing::Action() const { \ - return ::testing::internal::MakeAction(impl_); \ - } \ - private: \ - class gmock_Impl { \ - public: \ - explicit gmock_Impl(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ - : GMOCK_ACTION_INIT_PARAMS_(params) {} \ - template \ - return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ - GMOCK_ACTION_FIELD_PARAMS_(params) \ - }; \ - std::shared_ptr impl_; \ - }; \ - template \ - inline full_name name( \ - GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) { \ - return full_name( \ - GMOCK_ACTION_GVALUE_PARAMS_(params)); \ - } \ - template \ - template \ - return_type full_name::gmock_Impl:: \ - gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const +#define GMOCK_INTERNAL_ACTION(name, full_name, params) \ + template \ + class full_name { \ + public: \ + explicit full_name(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : impl_(std::make_shared( \ + GMOCK_ACTION_GVALUE_PARAMS_(params))) {} \ + full_name(const full_name&) = default; \ + full_name(full_name&&) noexcept = default; \ + template \ + operator ::testing::Action() const { \ + return ::testing::internal::MakeAction(impl_); \ + } \ + \ + private: \ + class gmock_Impl { \ + public: \ + explicit gmock_Impl(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) \ + : GMOCK_ACTION_INIT_PARAMS_(params) {} \ + template \ + return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \ + GMOCK_ACTION_FIELD_PARAMS_(params) \ + }; \ + std::shared_ptr impl_; \ + }; \ + template \ + inline full_name name( \ + GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) GTEST_MUST_USE_RESULT_; \ + template \ + inline full_name name( \ + GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) { \ + return full_name( \ + GMOCK_ACTION_GVALUE_PARAMS_(params)); \ + } \ + template \ + template \ + return_type \ + full_name::gmock_Impl::gmock_PerformImpl( \ + GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const } // namespace internal @@ -1627,12 +2091,13 @@ ::testing::Action MakeAction(std::shared_ptr impl) { #define ACTION(name) \ class name##Action { \ public: \ - explicit name##Action() noexcept {} \ - name##Action(const name##Action&) noexcept {} \ + explicit name##Action() noexcept {} \ + name##Action(const name##Action&) noexcept {} \ template \ operator ::testing::Action() const { \ return ::testing::internal::MakeAction(); \ } \ + \ private: \ class gmock_Impl { \ public: \ @@ -1681,7 +2146,7 @@ ::testing::Action MakeAction(std::shared_ptr impl) { } // namespace testing #ifdef _MSC_VER -# pragma warning(pop) +#pragma warning(pop) #endif #endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_ diff --git a/lib/googletest/googlemock/include/gmock/gmock-cardinalities.h b/lib/googletest/googlemock/include/gmock/gmock-cardinalities.h index fc7f803a7a65..b6ab648e50a6 100644 --- a/lib/googletest/googlemock/include/gmock/gmock-cardinalities.h +++ b/lib/googletest/googlemock/include/gmock/gmock-cardinalities.h @@ -27,21 +27,23 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - // Google Mock - a framework for writing C++ mock classes. // // This file implements some commonly used cardinalities. More // cardinalities can be defined by the user implementing the // CardinalityInterface interface if necessary. -// GOOGLETEST_CM0002 DO NOT DELETE +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* #ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ #define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_ #include + #include #include // NOLINT + #include "gmock/internal/gmock-port.h" #include "gtest/gtest.h" @@ -116,7 +118,7 @@ class GTEST_API_ Cardinality { // cardinality, i.e. exceed the maximum number of allowed calls. bool IsOverSaturatedByCallCount(int call_count) const { return impl_->IsSaturatedByCallCount(call_count) && - !impl_->IsSatisfiedByCallCount(call_count); + !impl_->IsSatisfiedByCallCount(call_count); } // Describes self to an ostream diff --git a/lib/googletest/googlemock/include/gmock/gmock-function-mocker.h b/lib/googletest/googlemock/include/gmock/gmock-function-mocker.h index 0fc6f6f3f13c..f565d980c565 100644 --- a/lib/googletest/googlemock/include/gmock/gmock-function-mocker.h +++ b/lib/googletest/googlemock/include/gmock/gmock-function-mocker.h @@ -31,7 +31,8 @@ // // This file implements MOCK_METHOD. -// GOOGLETEST_CM0002 DO NOT DELETE +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* #ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_ // NOLINT #define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_ // NOLINT @@ -64,6 +65,39 @@ struct ThisRefAdjuster { } }; +constexpr bool PrefixOf(const char* a, const char* b) { + return *a == 0 || (*a == *b && internal::PrefixOf(a + 1, b + 1)); +} + +template +constexpr bool StartsWith(const char (&prefix)[N], const char (&str)[M]) { + return N <= M && internal::PrefixOf(prefix, str); +} + +template +constexpr bool EndsWith(const char (&suffix)[N], const char (&str)[M]) { + return N <= M && internal::PrefixOf(suffix, str + M - N); +} + +template +constexpr bool Equals(const char (&a)[N], const char (&b)[M]) { + return N == M && internal::PrefixOf(a, b); +} + +template +constexpr bool ValidateSpec(const char (&spec)[N]) { + return internal::Equals("const", spec) || + internal::Equals("override", spec) || + internal::Equals("final", spec) || + internal::Equals("noexcept", spec) || + (internal::StartsWith("noexcept(", spec) && + internal::EndsWith(")", spec)) || + internal::Equals("ref(&)", spec) || + internal::Equals("ref(&&)", spec) || + (internal::StartsWith("Calltype(", spec) && + internal::EndsWith(")", spec)); +} + } // namespace internal // The style guide prohibits "using" statements in a namespace scope @@ -86,17 +120,18 @@ using internal::FunctionMocker; #define GMOCK_INTERNAL_MOCK_METHOD_ARG_3(_Ret, _MethodName, _Args) \ GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, ()) -#define GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, _Spec) \ - GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args); \ - GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Spec); \ - GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ - GMOCK_PP_NARG0 _Args, GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)); \ - GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ - GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ - GMOCK_PP_NARG0 _Args, _MethodName, GMOCK_INTERNAL_HAS_CONST(_Spec), \ - GMOCK_INTERNAL_HAS_OVERRIDE(_Spec), GMOCK_INTERNAL_HAS_FINAL(_Spec), \ - GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Spec), \ - GMOCK_INTERNAL_GET_CALLTYPE(_Spec), GMOCK_INTERNAL_GET_REF_SPEC(_Spec), \ +#define GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, _Spec) \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args); \ + GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Spec); \ + GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \ + GMOCK_PP_NARG0 _Args, GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)); \ + GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \ + GMOCK_INTERNAL_MOCK_METHOD_IMPL( \ + GMOCK_PP_NARG0 _Args, _MethodName, GMOCK_INTERNAL_HAS_CONST(_Spec), \ + GMOCK_INTERNAL_HAS_OVERRIDE(_Spec), GMOCK_INTERNAL_HAS_FINAL(_Spec), \ + GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Spec), \ + GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Spec), \ + GMOCK_INTERNAL_GET_REF_SPEC(_Spec), \ (GMOCK_INTERNAL_SIGNATURE(_Ret, _Args))) #define GMOCK_INTERNAL_MOCK_METHOD_ARG_5(...) \ @@ -166,11 +201,11 @@ using internal::FunctionMocker; GMOCK_INTERNAL_A_MATCHER_ARGUMENT, _Signature, _N)); \ } \ mutable ::testing::FunctionMocker \ - GMOCK_MOCKER_(_N, _Constness, _MethodName) + GMOCK_MOCKER_(_N, _Constness, _MethodName) #define GMOCK_INTERNAL_EXPAND(...) __VA_ARGS__ -// Five Valid modifiers. +// Valid modifiers. #define GMOCK_INTERNAL_HAS_CONST(_Tuple) \ GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_CONST, ~, _Tuple)) @@ -189,6 +224,14 @@ using internal::FunctionMocker; GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)), \ _elem, ) +#define GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Tuple) \ + GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE, ~, _Tuple) + +#define GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE(_i, _, _elem) \ + GMOCK_PP_IF( \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem)), \ + GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), ) + #define GMOCK_INTERNAL_GET_REF_SPEC(_Tuple) \ GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_REF_SPEC_IF_REF, ~, _Tuple) @@ -196,19 +239,25 @@ using internal::FunctionMocker; GMOCK_PP_IF(GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)), \ GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), ) -#define GMOCK_INTERNAL_GET_CALLTYPE(_Tuple) \ - GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_CALLTYPE_IMPL, ~, _Tuple) - -#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ - static_assert( \ - (GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem)) + \ - GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem)) + \ - GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem)) + \ - GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)) + \ - GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)) + \ - GMOCK_INTERNAL_IS_CALLTYPE(_elem)) == 1, \ - GMOCK_PP_STRINGIZE( \ +#ifdef GMOCK_INTERNAL_STRICT_SPEC_ASSERT +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ + static_assert( \ + ::testing::internal::ValidateSpec(GMOCK_PP_STRINGIZE(_elem)), \ + "Token \'" GMOCK_PP_STRINGIZE( \ + _elem) "\' cannot be recognized as a valid specification " \ + "modifier. Is a ',' missing?"); +#else +#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \ + static_assert( \ + (GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)) + \ + GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem))) == 1, \ + GMOCK_PP_STRINGIZE( \ _elem) " cannot be recognized as a valid specification modifier."); +#endif // GMOCK_INTERNAL_STRICT_SPEC_ASSERT // Modifiers implementation. #define GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem) \ @@ -238,26 +287,12 @@ using internal::FunctionMocker; #define GMOCK_INTERNAL_UNPACK_ref(x) x -#define GMOCK_INTERNAL_GET_CALLTYPE_IMPL(_i, _, _elem) \ - GMOCK_PP_IF(GMOCK_INTERNAL_IS_CALLTYPE(_elem), \ - GMOCK_INTERNAL_GET_VALUE_CALLTYPE, GMOCK_PP_EMPTY) \ - (_elem) +#define GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem) \ + GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CALLTYPE_I_, _elem) + +#define GMOCK_INTERNAL_DETECT_CALLTYPE_I_Calltype , -// TODO(iserna): GMOCK_INTERNAL_IS_CALLTYPE and -// GMOCK_INTERNAL_GET_VALUE_CALLTYPE needed more expansions to work on windows -// maybe they can be simplified somehow. -#define GMOCK_INTERNAL_IS_CALLTYPE(_arg) \ - GMOCK_INTERNAL_IS_CALLTYPE_I( \ - GMOCK_PP_CAT(GMOCK_INTERNAL_IS_CALLTYPE_HELPER_, _arg)) -#define GMOCK_INTERNAL_IS_CALLTYPE_I(_arg) GMOCK_PP_IS_ENCLOSED_PARENS(_arg) - -#define GMOCK_INTERNAL_GET_VALUE_CALLTYPE(_arg) \ - GMOCK_INTERNAL_GET_VALUE_CALLTYPE_I( \ - GMOCK_PP_CAT(GMOCK_INTERNAL_IS_CALLTYPE_HELPER_, _arg)) -#define GMOCK_INTERNAL_GET_VALUE_CALLTYPE_I(_arg) \ - GMOCK_PP_IDENTITY _arg - -#define GMOCK_INTERNAL_IS_CALLTYPE_HELPER_Calltype +#define GMOCK_INTERNAL_UNPACK_Calltype(...) __VA_ARGS__ // Note: The use of `identity_t` here allows _Ret to represent return types that // would normally need to be specified in a different way. For example, a method diff --git a/lib/googletest/googlemock/include/gmock/gmock-matchers.h b/lib/googletest/googlemock/include/gmock/gmock-matchers.h index 86be9c176ebc..6282901145aa 100644 --- a/lib/googletest/googlemock/include/gmock/gmock-matchers.h +++ b/lib/googletest/googlemock/include/gmock/gmock-matchers.h @@ -27,7 +27,6 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - // Google Mock - a framework for writing C++ mock classes. // // The MATCHER* family of macros can be used in a namespace scope to @@ -250,7 +249,8 @@ // See googletest/include/gtest/gtest-matchers.h for the definition of class // Matcher, class MatcherInterface, and others. -// GOOGLETEST_CM0002 DO NOT DELETE +// IWYU pragma: private, include "gmock/gmock.h" +// IWYU pragma: friend gmock/.* #ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ #define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_ @@ -313,7 +313,9 @@ class StringMatchResultListener : public MatchResultListener { private: ::std::stringstream ss_; - GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); + StringMatchResultListener(const StringMatchResultListener&) = delete; + StringMatchResultListener& operator=(const StringMatchResultListener&) = + delete; }; // Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION @@ -396,7 +398,7 @@ class MatcherCastImpl { // is already a Matcher. This only compiles when type T can be // statically converted to type U. template -class MatcherCastImpl > { +class MatcherCastImpl> { public: static Matcher Cast(const Matcher& source_matcher) { return Matcher(new Impl(source_matcher)); @@ -450,7 +452,7 @@ class MatcherCastImpl > { // This even more specialized version is used for efficiently casting // a matcher to its own type. template -class MatcherCastImpl > { +class MatcherCastImpl> { public: static Matcher Cast(const Matcher& matcher) { return matcher; } }; @@ -533,19 +535,18 @@ inline Matcher SafeMatcherCast(const Matcher& matcher) { "T must be implicitly convertible to U"); // Enforce that we are not converting a non-reference type T to a reference // type U. - GTEST_COMPILE_ASSERT_( - std::is_reference::value || !std::is_reference::value, - cannot_convert_non_reference_arg_to_reference); + static_assert(std::is_reference::value || !std::is_reference::value, + "cannot convert non reference arg to reference"); // In case both T and U are arithmetic types, enforce that the // conversion is not lossy. typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT; typedef GTEST_REMOVE_REFERENCE_AND_CONST_(U) RawU; constexpr bool kTIsOther = GMOCK_KIND_OF_(RawT) == internal::kOther; constexpr bool kUIsOther = GMOCK_KIND_OF_(RawU) == internal::kOther; - GTEST_COMPILE_ASSERT_( + static_assert( kTIsOther || kUIsOther || - (internal::LosslessArithmeticConvertible::value), - conversion_of_arithmetic_types_must_be_lossless); + (internal::LosslessArithmeticConvertible::value), + "conversion of arithmetic types must be lossless"); return MatcherCast(matcher); } @@ -678,9 +679,9 @@ bool TupleMatches(const MatcherTuple& matcher_tuple, const ValueTuple& value_tuple) { // Makes sure that matcher_tuple and value_tuple have the same // number of fields. - GTEST_COMPILE_ASSERT_(std::tuple_size::value == - std::tuple_size::value, - matcher_and_value_have_different_numbers_of_fields); + static_assert(std::tuple_size::value == + std::tuple_size::value, + "matcher and value have different numbers of fields"); return TuplePrefix::value>::Matches(matcher_tuple, value_tuple); } @@ -689,8 +690,7 @@ bool TupleMatches(const MatcherTuple& matcher_tuple, // is no failure, nothing will be streamed to os. template void ExplainMatchFailureTupleTo(const MatcherTuple& matchers, - const ValueTuple& values, - ::std::ostream* os) { + const ValueTuple& values, ::std::ostream* os) { TuplePrefix::value>::ExplainMatchFailuresTo( matchers, values, os); } @@ -714,14 +714,14 @@ class TransformTupleValuesHelper { private: template struct IterateOverTuple { - OutIter operator() (Func f, const Tup& t, OutIter out) const { + OutIter operator()(Func f, const Tup& t, OutIter out) const { *out++ = f(::std::get(t)); return IterateOverTuple()(f, t, out); } }; template struct IterateOverTuple { - OutIter operator() (Func /* f */, const Tup& /* t */, OutIter out) const { + OutIter operator()(Func /* f */, const Tup& /* t */, OutIter out) const { return out; } }; @@ -767,9 +767,7 @@ class IsNullMatcher { } void DescribeTo(::std::ostream* os) const { *os << "is NULL"; } - void DescribeNegationTo(::std::ostream* os) const { - *os << "isn't NULL"; - } + void DescribeNegationTo(::std::ostream* os) const { *os << "isn't NULL"; } }; // Implements the polymorphic NotNull() matcher, which matches any raw or smart @@ -783,9 +781,7 @@ class NotNullMatcher { } void DescribeTo(::std::ostream* os) const { *os << "isn't NULL"; } - void DescribeNegationTo(::std::ostream* os) const { - *os << "is NULL"; - } + void DescribeNegationTo(::std::ostream* os) const { *os << "is NULL"; } }; // Ref(variable) matches any argument that is a reference to @@ -871,8 +867,7 @@ inline bool CaseInsensitiveCStringEquals(const wchar_t* lhs, // String comparison for narrow or wide strings that can have embedded NUL // characters. template -bool CaseInsensitiveStringEquals(const StringType& s1, - const StringType& s2) { +bool CaseInsensitiveStringEquals(const StringType& s1, const StringType& s2) { // Are the heads equal? if (!CaseInsensitiveCStringEquals(s1.c_str(), s2.c_str())) { return false; @@ -933,8 +928,8 @@ class StrEqualityMatcher { bool MatchAndExplain(const MatcheeStringType& s, MatchResultListener* /* listener */) const { const StringType s2(s); - const bool eq = case_sensitive_ ? s2 == string_ : - CaseInsensitiveStringEquals(s2, string_); + const bool eq = case_sensitive_ ? s2 == string_ + : CaseInsensitiveStringEquals(s2, string_); return expect_eq_ == eq; } @@ -1021,8 +1016,7 @@ class HasSubstrMatcher { template class StartsWithMatcher { public: - explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) { - } + explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) {} #if GTEST_INTERNAL_HAS_STRING_VIEW bool MatchAndExplain(const internal::StringView& s, @@ -1053,7 +1047,7 @@ class StartsWithMatcher { MatchResultListener* /* listener */) const { const StringType& s2(s); return s2.length() >= prefix_.length() && - s2.substr(0, prefix_.length()) == prefix_; + s2.substr(0, prefix_.length()) == prefix_; } void DescribeTo(::std::ostream* os) const { @@ -1107,7 +1101,7 @@ class EndsWithMatcher { MatchResultListener* /* listener */) const { const StringType& s2(s); return s2.length() >= suffix_.length() && - s2.substr(s2.length() - suffix_.length()) == suffix_; + s2.substr(s2.length() - suffix_.length()) == suffix_; } void DescribeTo(::std::ostream* os) const { @@ -1124,6 +1118,45 @@ class EndsWithMatcher { const StringType suffix_; }; +// Implements the polymorphic WhenBase64Unescaped(matcher) matcher, which can be +// used as a Matcher as long as T can be converted to a string. +class WhenBase64UnescapedMatcher { + public: + using is_gtest_matcher = void; + + explicit WhenBase64UnescapedMatcher( + const Matcher& internal_matcher) + : internal_matcher_(internal_matcher) {} + + // Matches anything that can convert to std::string. + template + bool MatchAndExplain(const MatcheeStringType& s, + MatchResultListener* listener) const { + const std::string s2(s); // NOLINT (needed for working with string_view). + std::string unescaped; + if (!internal::Base64Unescape(s2, &unescaped)) { + if (listener != nullptr) { + *listener << "is not a valid base64 escaped string"; + } + return false; + } + return MatchPrintAndExplain(unescaped, internal_matcher_, listener); + } + + void DescribeTo(::std::ostream* os) const { + *os << "matches after Base64Unescape "; + internal_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const { + *os << "does not match after Base64Unescape "; + internal_matcher_.DescribeTo(os); + } + + private: + const Matcher internal_matcher_; +}; + // Implements a matcher that compares the two fields of a 2-tuple // using one of the ==, <=, <, etc, operators. The two fields being // compared don't have to have the same type. @@ -1197,8 +1230,7 @@ class Ge2Matcher : public PairMatchBase { template class NotMatcherImpl : public MatcherInterface { public: - explicit NotMatcherImpl(const Matcher& matcher) - : matcher_(matcher) {} + explicit NotMatcherImpl(const Matcher& matcher) : matcher_(matcher) {} bool MatchAndExplain(const T& x, MatchResultListener* listener) const override { @@ -1242,7 +1274,7 @@ class NotMatcher { template class AllOfMatcherImpl : public MatcherInterface { public: - explicit AllOfMatcherImpl(std::vector > matchers) + explicit AllOfMatcherImpl(std::vector> matchers) : matchers_(std::move(matchers)) {} void DescribeTo(::std::ostream* os) const override { @@ -1293,7 +1325,7 @@ class AllOfMatcherImpl : public MatcherInterface { } private: - const std::vector > matchers_; + const std::vector> matchers_; }; // VariadicMatcher is used for the variadic implementation of @@ -1316,14 +1348,14 @@ class VariadicMatcher { // all of the provided matchers (Matcher1, Matcher2, ...) can match. template operator Matcher() const { - std::vector > values; + std::vector> values; CreateVariadicMatcher(&values, std::integral_constant()); return Matcher(new CombiningMatcher(std::move(values))); } private: template - void CreateVariadicMatcher(std::vector >* values, + void CreateVariadicMatcher(std::vector>* values, std::integral_constant) const { values->push_back(SafeMatcherCast(std::get(matchers_))); CreateVariadicMatcher(values, std::integral_constant()); @@ -1331,7 +1363,7 @@ class VariadicMatcher { template void CreateVariadicMatcher( - std::vector >*, + std::vector>*, std::integral_constant) const {} std::tuple matchers_; @@ -1347,7 +1379,7 @@ using AllOfMatcher = VariadicMatcher; template class AnyOfMatcherImpl : public MatcherInterface { public: - explicit AnyOfMatcherImpl(std::vector > matchers) + explicit AnyOfMatcherImpl(std::vector> matchers) : matchers_(std::move(matchers)) {} void DescribeTo(::std::ostream* os) const override { @@ -1398,13 +1430,35 @@ class AnyOfMatcherImpl : public MatcherInterface { } private: - const std::vector > matchers_; + const std::vector> matchers_; }; // AnyOfMatcher is used for the variadic implementation of AnyOf(m_1, m_2, ...). template using AnyOfMatcher = VariadicMatcher; +// ConditionalMatcher is the implementation of Conditional(cond, m1, m2) +template +class ConditionalMatcher { + public: + ConditionalMatcher(bool condition, MatcherTrue matcher_true, + MatcherFalse matcher_false) + : condition_(condition), + matcher_true_(std::move(matcher_true)), + matcher_false_(std::move(matcher_false)) {} + + template + operator Matcher() const { // NOLINT(runtime/explicit) + return condition_ ? SafeMatcherCast(matcher_true_) + : SafeMatcherCast(matcher_false_); + } + + private: + bool condition_; + MatcherTrue matcher_true_; + MatcherFalse matcher_false_; +}; + // Wrapper for implementation of Any/AllOfArray(). template