From fed2e1b59ab498fcd3901acd43619a0a89df1aff Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 6 Jul 2020 01:24:43 -0500 Subject: [PATCH 01/61] split ControllerEngine functionality into ControllerEngineJSProxy --- src/controllers/engine/controllerengine.cpp | 820 ----------------- src/controllers/engine/controllerengine.h | 93 +- .../engine/controllerenginejsproxy.cpp | 849 +++++++++++++++++- .../engine/controllerenginejsproxy.h | 40 +- src/controllers/engine/scriptconnection.h | 2 + .../engine/scriptconnectionjsproxy.cpp | 6 +- 6 files changed, 858 insertions(+), 952 deletions(-) diff --git a/src/controllers/engine/controllerengine.cpp b/src/controllers/engine/controllerengine.cpp index ff9e4415f90..ca4d110315d 100644 --- a/src/controllers/engine/controllerengine.cpp +++ b/src/controllers/engine/controllerengine.cpp @@ -1,24 +1,12 @@ #include "controllers/engine/controllerengine.h" #include "control/controlobject.h" -#include "control/controlobjectscript.h" #include "controllers/controller.h" #include "controllers/controllerdebug.h" #include "controllers/engine/colormapperjsproxy.h" #include "controllers/engine/controllerenginejsproxy.h" -#include "controllers/engine/scriptconnectionjsproxy.h" #include "errordialoghandler.h" #include "mixer/playermanager.h" -// to tell the msvs compiler about `isnan` -#include "util/math.h" -#include "util/time.h" - -const int kDecks = 16; - -// Use 1ms for the Alpha-Beta dt. We're assuming the OS actually gives us a 1ms -// timer. -const int kScratchTimerMs = 1; -const double kAlphaBetaDt = kScratchTimerMs / 1000.0; ControllerEngine::ControllerEngine(Controller* controller) : m_bDisplayingExceptionDialog(false), @@ -28,33 +16,10 @@ ControllerEngine::ControllerEngine(Controller* controller) // Handle error dialog buttons qRegisterMetaType("QMessageBox::StandardButton"); - // Pre-allocate arrays for average number of virtual decks - m_intervalAccumulator.resize(kDecks); - m_lastMovement.resize(kDecks); - m_dx.resize(kDecks); - m_rampTo.resize(kDecks); - m_ramp.resize(kDecks); - m_scratchFilters.resize(kDecks); - m_rampFactor.resize(kDecks); - m_brakeActive.resize(kDecks); - m_softStartActive.resize(kDecks); - // Initialize arrays used for testing and pointers - for (int i = 0; i < kDecks; ++i) { - m_dx[i] = 0.0; - m_scratchFilters[i] = new AlphaBetaFilter(); - m_ramp[i] = false; - } - initializeScriptEngine(); } ControllerEngine::~ControllerEngine() { - // Clean up - for (int i = 0; i < kDecks; ++i) { - delete m_scratchFilters[i]; - m_scratchFilters[i] = nullptr; - } - uninitializeScriptEngine(); } @@ -143,9 +108,6 @@ void ControllerEngine::gracefulShutdown() { qDebug() << "ControllerEngine shutting down..."; - // Stop all timers - stopAllTimers(); - qDebug() << "Invoking shutdown() hook in scripts"; callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); @@ -153,37 +115,8 @@ void ControllerEngine::gracefulShutdown() { executeFunction(m_shutdownFunction, QJSValueList{}); } - // Prevents leaving decks in an unstable state - // if the controller is shut down while scratching - QHashIterator i(m_scratchTimers); - while (i.hasNext()) { - i.next(); - qDebug() << "Aborting scratching on deck" << i.value(); - // Clear scratch2_enable. PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(i.value() - 1); - ControlObjectScript* pScratch2Enable = - getControlObjectScript(group, "scratch2_enable"); - if (pScratch2Enable != nullptr) { - pScratch2Enable->set(0); - } - } - qDebug() << "Clearing function wrapper cache"; m_scriptWrappedFunctionCache.clear(); - - // Free all the ControlObjectScripts - { - auto it = m_controlCache.begin(); - while (it != m_controlCache.end()) { - qDebug() - << "Deleting ControlObjectScript" - << it.key().group - << it.key().item; - delete it.value(); - // Advance iterator - it = m_controlCache.erase(it); - } - } } void ControllerEngine::initializeScriptEngine() { @@ -514,305 +447,6 @@ void ControllerEngine::errorDialogButton( } } -ControlObjectScript* ControllerEngine::getControlObjectScript(const QString& group, const QString& name) { - ConfigKey key = ConfigKey(group, name); - ControlObjectScript* coScript = m_controlCache.value(key, nullptr); - if (coScript == nullptr) { - // create COT - coScript = new ControlObjectScript(key, this); - if (coScript->valid()) { - m_controlCache.insert(key, coScript); - } else { - delete coScript; - coScript = nullptr; - } - } - return coScript; -} - -double ControllerEngine::getValue(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - return coScript->get(); -} - -void ControllerEngine::setValue(QString group, QString name, double newValue) { - if (isnan(newValue)) { - qWarning() << "ControllerEngine: script setting [" << group << "," << name - << "] to NotANumber, ignoring."; - return; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript != nullptr) { - ControlObject* pControl = ControlObject::getControl( - coScript->getKey(), ControlFlag::NoAssertIfMissing); - if (pControl && !m_st.ignore(pControl, coScript->getParameterForValue(newValue))) { - coScript->slotSet(newValue); - } - } -} - -double ControllerEngine::getParameter(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - return coScript->getParameter(); -} - -void ControllerEngine::setParameter(QString group, QString name, double newParameter) { - if (isnan(newParameter)) { - qWarning() << "ControllerEngine: script setting [" << group << "," << name - << "] to NotANumber, ignoring."; - return; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript != nullptr) { - ControlObject* pControl = ControlObject::getControl( - coScript->getKey(), ControlFlag::NoAssertIfMissing); - if (pControl && !m_st.ignore(pControl, newParameter)) { - coScript->setParameter(newParameter); - } - } -} - -double ControllerEngine::getParameterForValue(QString group, QString name, double value) { - if (isnan(value)) { - qWarning() << "ControllerEngine: script setting [" << group << "," << name - << "] to NotANumber, ignoring."; - return 0.0; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - - return coScript->getParameterForValue(value); -} - -void ControllerEngine::reset(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript != nullptr) { - coScript->reset(); - } -} - -double ControllerEngine::getDefaultValue(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - - return coScript->getDefault(); -} - -double ControllerEngine::getDefaultParameter(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - - return coScript->getParameterForValue(coScript->getDefault()); -} - -void ControllerEngine::log(QString message) { - controllerDebug(message); -} - -QJSValue ControllerEngine::makeConnection(QString group, QString name, const QJSValue callback) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine != nullptr) { - return QJSValue(); - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript == nullptr) { - // The test setups do not run all of Mixxx, so ControlObjects not - // existing during tests is okay. - if (!m_bTesting) { - throwJSError("ControllerEngine: script tried to connect to ControlObject (" + - group + ", " + name + - ") which is non-existent."); - } - return QJSValue(); - } - - if (!callback.isCallable()) { - throwJSError("Tried to connect (" + group + ", " + name + ")" + " to an invalid callback. Make sure that your code contains no syntax errors."); - return QJSValue(); - } - - ScriptConnection connection; - connection.key = ConfigKey(group, name); - connection.controllerEngine = this; - connection.callback = callback; - connection.id = QUuid::createUuid(); - - if (coScript->addScriptConnection(connection)) { - return m_pScriptEngine->newQObject(new ScriptConnectionJSProxy(connection)); - } - - return QJSValue(); -} - -bool ControllerEngine::removeScriptConnection(const ScriptConnection connection) { - ControlObjectScript* coScript = getControlObjectScript(connection.key.group, - connection.key.item); - - if (m_pScriptEngine == nullptr || coScript == nullptr) { - return false; - } - - return coScript->removeScriptConnection(connection); -} - -void ControllerEngine::triggerScriptConnection(const ScriptConnection connection) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return; - } - - ControlObjectScript* coScript = getControlObjectScript(connection.key.group, - connection.key.item); - if (coScript == nullptr) { - return; - } - - connection.executeCallback(coScript->get()); -} - -// This function is a legacy version of makeConnection with several alternate -// ways of invoking it. The callback function can be passed either as a string of -// JavaScript code that evaluates to a function or an actual JavaScript function. -// If "true" is passed as a 4th parameter, all connections to the ControlObject -// are removed. If a ScriptConnectionInvokableWrapper is passed instead of a callback, -// it is disconnected. -// WARNING: These behaviors are quirky and confusing, so if you change this function, -// be sure to run the ControllerEngineTest suite to make sure you do not break old scripts. -QJSValue ControllerEngine::connectControl( - QString group, QString name, QJSValue passedCallback, bool disconnect) { - // The passedCallback may or may not actually be a function, so when - // the actual callback function is found, store it in this variable. - QJSValue actualCallbackFunction; - - if (passedCallback.isCallable()) { - if (!disconnect) { - // skip all the checks below and just make the connection - return makeConnection(group, name, passedCallback); - } - actualCallbackFunction = passedCallback; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - // This check is redundant with makeConnection, but the - // ControlObjectScript is also needed here to check for duplicate connections. - if (coScript == nullptr) { - // The test setups do not run all of Mixxx, so ControlObjects not - // existing during tests is okay. - if (!m_bTesting) { - if (disconnect) { - throwJSError("ControllerEngine: script tried to disconnect from ControlObject (" + - group + ", " + name + ") which is non-existent."); - } else { - throwJSError("ControllerEngine: script tried to connect to ControlObject (" + - group + ", " + name + ") which is non-existent."); - } - } - // This is inconsistent with other failures, which return false. - // QJSValue() with no arguments is undefined in JavaScript. - return QJSValue(); - } - - if (passedCallback.isString()) { - // This check is redundant with makeConnection, but it must be done here - // before evaluating the code string. - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine != nullptr) { - return QJSValue(false); - } - - actualCallbackFunction = evaluateCodeString(passedCallback.toString()); - - if (!actualCallbackFunction.isCallable()) { - QString sErrorMessage("Invalid connection callback provided to engine.connectControl."); - if (actualCallbackFunction.isError()) { - sErrorMessage.append("\n" + actualCallbackFunction.toString()); - } - throwJSError(sErrorMessage); - return QJSValue(false); - } - - if (coScript->countConnections() > 0 && !disconnect) { - // This is inconsistent with the behavior when passing the callback as - // a function, but keep the old behavior to make sure old scripts do - // not break. - ScriptConnection connection = coScript->firstConnection(); - - qWarning() << "Tried to make duplicate connection between (" + - group + ", " + name + ") and " + passedCallback.toString() + - " but this is not allowed when passing a callback as a string. " + - "If you actually want to create duplicate connections, " + - "use engine.makeConnection. Returning reference to connection " + - connection.id.toString(); - - return m_pScriptEngine->newQObject(new ScriptConnectionJSProxy(connection)); - } - } else if (passedCallback.isQObject()) { - // Assume a ScriptConnection and assume that the script author - // wants to disconnect it, regardless of the disconnect parameter - // and regardless of whether it is connected to the same ControlObject - // specified by the first two parameters to this function. - QObject* qobject = passedCallback.toQObject(); - const QMetaObject* qmeta = qobject->metaObject(); - - qWarning() << "QObject passed to engine.connectControl. Assuming it is" - << "a connection object to disconnect and returning false."; - if (!strcmp(qmeta->className(), - "ScriptConnectionJSProxy")) { - ScriptConnectionJSProxy* proxy = - (ScriptConnectionJSProxy*)qobject; - proxy->disconnect(); - } - return QJSValue(false); - } - - // Support removing connections by passing "true" as the last parameter - // to this function, regardless of whether the callback is provided - // as a function or a string. - if (disconnect) { - // There is no way to determine which - // ScriptConnection to disconnect unless the script calls - // ScriptConnectionInvokableWrapper::disconnect(), so - // disconnect all ScriptConnections connected to the - // callback function, even though there may be multiple connections. - coScript->disconnectAllConnectionsToFunction(actualCallbackFunction); - return QJSValue(true); - } - - // If execution gets this far without returning, make - // a new connection to actualCallbackFunction. - return makeConnection(group, name, actualCallbackFunction); -} - -void ControllerEngine::trigger(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript != nullptr) { - coScript->emitValueChanged(); - } -} - bool ControllerEngine::evaluateScriptFile(const QFileInfo& scriptFile) { VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { return false; @@ -869,457 +503,3 @@ bool ControllerEngine::evaluateScriptFile(const QFileInfo& scriptFile) { return true; } - -int ControllerEngine::beginTimer(int intervalMillis, QJSValue timerCallback, bool oneShot) { - if (timerCallback.isString()) { - timerCallback = evaluateCodeString(timerCallback.toString()); - } else if (!timerCallback.isCallable()) { - QString sErrorMessage( - "Invalid timer callback provided to engine.beginTimer. Valid callbacks are strings and functions. " - "Make sure that your code contains no syntax errors."); - if (timerCallback.isError()) { - sErrorMessage.append("\n" + timerCallback.toString()); - } - throwJSError(sErrorMessage); - return 0; - } - - if (intervalMillis < 20) { - qWarning() << "Timer request for" << intervalMillis - << "ms is too short. Setting to the minimum of 20ms."; - intervalMillis = 20; - } - - // This makes use of every QObject's internal timer mechanism. Nice, clean, - // and simple. See http://doc.trolltech.com/4.6/qobject.html#startTimer for - // details - int timerId = startTimer(intervalMillis); - TimerInfo info; - info.callback = timerCallback; - info.oneShot = oneShot; - m_timers[timerId] = info; - if (timerId == 0) { - qWarning() << "Script timer could not be created"; - } else if (oneShot) { - controllerDebug("Starting one-shot timer:" << timerId); - } else { - controllerDebug("Starting timer:" << timerId); - } - return timerId; -} - -void ControllerEngine::stopTimer(int timerId) { - if (!m_timers.contains(timerId)) { - qWarning() << "Killing timer" << timerId << ": That timer does not exist!"; - return; - } - controllerDebug("Killing timer:" << timerId); - killTimer(timerId); - m_timers.remove(timerId); -} - -void ControllerEngine::stopAllTimers() { - QMutableHashIterator i(m_timers); - while (i.hasNext()) { - i.next(); - stopTimer(i.key()); - } -} - -void ControllerEngine::timerEvent(QTimerEvent* event) { - int timerId = event->timerId(); - - // See if this is a scratching timer - if (m_scratchTimers.contains(timerId)) { - scratchProcess(timerId); - return; - } - - auto it = m_timers.constFind(timerId); - if (it == m_timers.constEnd()) { - qWarning() << "Timer" << timerId << "fired but there's no function mapped to it!"; - return; - } - - // NOTE(rryan): Do not assign by reference -- make a copy. I have no idea - // why but this causes segfaults in ~QScriptValue while scratching if we - // don't copy here -- even though internalExecute passes the QScriptValues - // by value. *boggle* - const TimerInfo timerTarget = it.value(); - if (timerTarget.oneShot) { - stopTimer(timerId); - } - - executeFunction(timerTarget.callback, QJSValueList()); -} - -void ControllerEngine::softTakeover(QString group, QString name, bool set) { - ControlObject* pControl = ControlObject::getControl( - ConfigKey(group, name), ControlFlag::NoAssertIfMissing); - if (!pControl) { - return; - } - if (set) { - m_st.enable(pControl); - } else { - m_st.disable(pControl); - } -} - -void ControllerEngine::softTakeoverIgnoreNextValue(QString group, const QString name) { - ControlObject* pControl = ControlObject::getControl( - ConfigKey(group, name), ControlFlag::NoAssertIfMissing); - if (!pControl) { - return; - } - - m_st.ignoreNext(pControl); -} - -double ControllerEngine::getDeckRate(const QString& group) { - double rate = 0.0; - ControlObjectScript* pRateRatio = getControlObjectScript(group, "rate_ratio"); - if (pRateRatio != nullptr) { - rate = pRateRatio->get(); - } - - // See if we're in reverse play - ControlObjectScript* pReverse = getControlObjectScript(group, "reverse"); - if (pReverse != nullptr && pReverse->get() == 1) { - rate = -rate; - } - return rate; -} - -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; - } - - return pPlay->get() > 0.0; -} - -void ControllerEngine::scratchEnable( - int deck, - int intervalsPerRev, - double rpm, - double alpha, - double beta, - bool ramp) { - // If we're already scratching this deck, override that with this request - if (m_dx[deck]) { - //qDebug() << "Already scratching deck" << deck << ". Overriding."; - int timerId = m_scratchTimers.key(deck); - killTimer(timerId); - m_scratchTimers.remove(timerId); - } - - // Controller resolution in intervals per second at normal speed. - // (rev/min * ints/rev * mins/sec) - double intervalsPerSecond = (rpm * intervalsPerRev) / 60.0; - - if (intervalsPerSecond == 0.0) { - qWarning() << "Invalid rpm or intervalsPerRev supplied to scratchEnable. Ignoring request."; - return; - } - - m_dx[deck] = 1.0 / intervalsPerSecond; - m_intervalAccumulator[deck] = 0.0; - m_ramp[deck] = false; - m_rampFactor[deck] = 0.001; - m_brakeActive[deck] = false; - - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - - // Ramp velocity, default to stopped. - double initVelocity = 0.0; - - ControlObjectScript* pScratch2Enable = - getControlObjectScript(group, "scratch2_enable"); - - // If ramping is desired, figure out the deck's current speed - if (ramp) { - // See if the deck is already being scratched - if (pScratch2Enable != nullptr && pScratch2Enable->get() == 1) { - // If so, set the filter's initial velocity to the scratch speed - ControlObjectScript* pScratch2 = - getControlObjectScript(group, "scratch2"); - if (pScratch2 != nullptr) { - initVelocity = pScratch2->get(); - } - } else if (isDeckPlaying(group)) { - // If the deck is playing, set the filter's initial velocity to the - // playback speed - initVelocity = getDeckRate(group); - } - } - - // Initialize scratch filter - if (alpha && beta) { - m_scratchFilters[deck]->init(kAlphaBetaDt, initVelocity, alpha, beta); - } else { - // Use filter's defaults if not specified - m_scratchFilters[deck]->init(kAlphaBetaDt, initVelocity); - } - - // 1ms is shortest possible, OS dependent - int timerId = startTimer(kScratchTimerMs); - - // Associate this virtual deck with this timer for later processing - m_scratchTimers[timerId] = deck; - - // Set scratch2_enable - if (pScratch2Enable != nullptr) { - pScratch2Enable->slotSet(1); - } -} - -void ControllerEngine::scratchTick(int deck, int interval) { - m_lastMovement[deck] = mixxx::Time::elapsed(); - m_intervalAccumulator[deck] += interval; -} - -void ControllerEngine::scratchProcess(int timerId) { - int deck = m_scratchTimers[timerId]; - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (!filter) { - qWarning() << "Scratch filter pointer is null on deck" << deck; - return; - } - - const double oldRate = filter->predictedVelocity(); - - // Give the filter a data point: - - // If we're ramping to end scratching and the wheel hasn't been turned very - // recently (spinback after lift-off,) feed fixed data - if (m_ramp[deck] && !m_softStartActive[deck] && - ((mixxx::Time::elapsed() - m_lastMovement[deck]) >= mixxx::Duration::fromMillis(1))) { - 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]) { - // pretend we have moved by (desired rate*default distance) - filter->observation(m_rampTo[deck] * kAlphaBetaDt); - } 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]); - } - - const double newRate = filter->predictedVelocity(); - - // Actually do the scratching - ControlObjectScript* pScratch2 = getControlObjectScript(group, "scratch2"); - if (pScratch2 == nullptr) { - return; // abort and maybe it'll work on the next pass - } - pScratch2->set(newRate); - - // Reset accumulator - m_intervalAccumulator[deck] = 0; - - // 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)))) { - // 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. - pScratch2->slotSet(0.0); - ControlObjectScript* pPlay = getControlObjectScript(group, "play"); - if (pPlay != nullptr) { - pPlay->slotSet(0.0); - } - } - - // Clear scratch2_enable to end scratching. - ControlObjectScript* pScratch2Enable = - getControlObjectScript(group, "scratch2_enable"); - if (pScratch2Enable == nullptr) { - return; // abort and maybe it'll work on the next pass - } - pScratch2Enable->slotSet(0); - - // Remove timer - killTimer(timerId); - m_scratchTimers.remove(timerId); - - m_dx[deck] = 0.0; - m_brakeActive[deck] = false; - m_softStartActive[deck] = false; - } -} - -void ControllerEngine::scratchDisable(int deck, bool ramp) { - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - - m_rampTo[deck] = 0.0; - - // If no ramping is desired, disable scratching immediately - if (!ramp) { - // Clear scratch2_enable - ControlObjectScript* pScratch2Enable = getControlObjectScript(group, "scratch2_enable"); - if (pScratch2Enable != nullptr) { - pScratch2Enable->slotSet(0); - } - // Can't return here because we need scratchProcess to stop the timer. - // So it's still actually ramping, we just won't hear or see it. - } else if (isDeckPlaying(group)) { - // If so, set the target velocity to the playback speed - m_rampTo[deck] = getDeckRate(group); - } - - m_lastMovement[deck] = mixxx::Time::elapsed(); - m_ramp[deck] = true; // Activate the ramping in scratchProcess() -} - -bool ControllerEngine::isScratching(int deck) { - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - return getValue(group, "scratch2_enable") > 0; -} - -void ControllerEngine::spinback(int deck, bool activate, double factor, double rate) { - // defaults for args set in header file - brake(deck, activate, factor, rate); -} - -void ControllerEngine::brake(int deck, bool activate, double factor, double rate) { - // 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; - 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); - } - - // 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; - } -} - -void ControllerEngine::softStart(int deck, bool activate, double factor) { - // 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_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; - - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (filter != nullptr) { - initRate = filter->predictedVelocity(); - } - } - - // setup timer, start playing and set scratch2 - timerId = startTimer(kScratchTimerMs); - m_scratchTimers[timerId] = deck; - - ControlObjectScript* pPlay = getControlObjectScript(group, "play"); - if (pPlay != nullptr) { - pPlay->slotSet(1.0); - } - - 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; - } -} diff --git a/src/controllers/engine/controllerengine.h b/src/controllers/engine/controllerengine.h index bd58e1fe69e..9e0362cf351 100644 --- a/src/controllers/engine/controllerengine.h +++ b/src/controllers/engine/controllerengine.h @@ -14,7 +14,6 @@ #include "util/duration.h" class Controller; -class ControlObjectScript; class ControllerEngine; class ControllerEngineJSProxy; class EvaluationException; @@ -44,65 +43,19 @@ class ControllerEngine : public QObject { /// Shows a UI dialog notifying of a script evaluation error. /// Precondition: QJSValue.isError() == true void showScriptExceptionDialog(QJSValue evaluationResult, bool bFatal = false); - - bool removeScriptConnection(const ScriptConnection conn); - /// Execute a ScriptConnection's JS callback - void triggerScriptConnection(const ScriptConnection conn); + void throwJSError(const QString& message); inline void setTesting(bool testing) { m_bTesting = testing; }; - protected: - double getValue(QString group, QString name); - void setValue(QString group, QString name, double newValue); - double getParameter(QString group, QString name); - void setParameter(QString group, QString name, double newValue); - double getParameterForValue(QString group, QString name, double value); - void reset(QString group, QString name); - double getDefaultValue(QString group, QString name); - double getDefaultParameter(QString group, QString name); - /// Connect a ControlObject's valueChanged() signal to a script callback function - /// Returns to the script a ScriptConnectionJSProxy - QJSValue makeConnection(QString group, QString name, const QJSValue callback); - /// DEPRECATED: Use makeConnection instead. - QJSValue connectControl(QString group, - QString name, - QJSValue passedCallback, - bool disconnect = false); - /// Execute callbacks for all ScriptConnections connected to a ControlObject - /// DEPRECATED: Use ScriptConnectionJSProxy::trigger instead. - void trigger(QString group, QString name); - void log(QString message); - /// Returns a timer ID to the script - int beginTimer(int intervalMillis, QJSValue scriptCode, bool oneShot = false); - void stopTimer(int timerId); - - /// [En/dis]able soft-takeover status for a particular ControlObject - void softTakeover(QString group, QString name, bool set); - /// Ignores the next value for the given ControlObject. This should be called - /// before or after an absolute physical control (slider or knob with hard limits) - /// is changed to operate on a different ControlObject, allowing it to sync up to the - /// soft-takeover state without an abrupt jump. - void softTakeoverIgnoreNextValue(QString group, QString name); - - void scratchEnable( - int deck, - int intervalsPerRev, - double rpm, - double alpha, - double beta, - bool ramp = true); - /// Accumulates ticks of the controller wheel - void scratchTick(int deck, int interval); - void scratchDisable(int deck, bool ramp = true); - bool isScratching(int deck); - void brake(int deck, bool activate, double factor = 1.0, double rate = 1.0); - void spinback(int deck, bool activate, double factor = 1.8, double rate = -10.0); - void softStart(int deck, bool activate, double factor = 1.0); - - /// Handler for timers that scripts set. - virtual void timerEvent(QTimerEvent* event); + bool isTesting() { + return m_bTesting; + } + + QJSEngine* scriptEngine() { + return m_pScriptEngine; + } public slots: void loadModule(QFileInfo moduleFileInfo); @@ -122,8 +75,6 @@ class ControllerEngine : public QObject { void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); void generateScriptFunctions(const QString& code); - /// Stops and removes all timers (for shutdown). - void stopAllTimers(); bool callFunctionOnObjects(QList, const QString&, @@ -133,40 +84,14 @@ class ControllerEngine : public QObject { QJSValue byteArrayToScriptValue(const QByteArray& byteArray); QJSValue evaluateCodeString(const QString& program, const QString& fileName = QString(), int lineNumber = 1); - void throwJSError(const QString& message); - bool m_bDisplayingExceptionDialog; QJSEngine* m_pScriptEngine; - ControlObjectScript* getControlObjectScript(const QString& group, const QString& name); - - // Scratching functions & variables - - /// Applies the accumulated movement to the track speed - void scratchProcess(int timerId); - - bool isDeckPlaying(const QString& group); - double getDeckRate(const QString& group); - Controller* m_pController; QJSValue m_handleInputFunction; QJSValue m_shutdownFunction; QList m_scriptFunctionPrefixes; - QHash m_controlCache; - struct TimerInfo { - QJSValue callback; - bool oneShot; - }; - QHash m_timers; - SoftTakeoverCtrl m_st; - // 256 (default) available virtual decks is enough I would think. - // If more are needed at run-time, these will move to the heap automatically - QVarLengthArray m_intervalAccumulator; - QVarLengthArray m_lastMovement; - QVarLengthArray m_dx, m_rampTo, m_rampFactor; - QVarLengthArray m_ramp, m_brakeActive, m_softStartActive; - QVarLengthArray m_scratchFilters; - QHash m_scratchTimers; + QHash m_scriptWrappedFunctionCache; QJSValue m_byteArrayToScriptValueJSFunction; // Filesystem watcher for script auto-reload diff --git a/src/controllers/engine/controllerenginejsproxy.cpp b/src/controllers/engine/controllerenginejsproxy.cpp index 2e2177c851b..f821e3ad9f2 100644 --- a/src/controllers/engine/controllerenginejsproxy.cpp +++ b/src/controllers/engine/controllerenginejsproxy.cpp @@ -1,105 +1,872 @@ #include "controllerenginejsproxy.h" + +#include "control/controlobject.h" +#include "control/controlobjectscript.h" +#include "controllers/controllerdebug.h" #include "controllers/engine/controllerengine.h" +#include "controllers/engine/scriptconnectionjsproxy.h" +#include "mixer/playermanager.h" +#include "util/math.h" +#include "util/time.h" + +namespace { +const int kDecks = 16; + +// Use 1ms for the Alpha-Beta dt. We're assuming the OS actually gives us a 1ms +// timer. +const int kScratchTimerMs = 1; +const double kAlphaBetaDt = kScratchTimerMs / 1000.0; +} // anonymous namespace ControllerEngineJSProxy::ControllerEngineJSProxy(ControllerEngine* m_pEngine) : m_pEngine(m_pEngine) { + // Pre-allocate arrays for average number of virtual decks + m_intervalAccumulator.resize(kDecks); + m_lastMovement.resize(kDecks); + m_dx.resize(kDecks); + m_rampTo.resize(kDecks); + m_ramp.resize(kDecks); + m_scratchFilters.resize(kDecks); + m_rampFactor.resize(kDecks); + m_brakeActive.resize(kDecks); + m_softStartActive.resize(kDecks); + // Initialize arrays used for testing and pointers + for (int i = 0; i < kDecks; ++i) { + m_dx[i] = 0.0; + m_scratchFilters[i] = new AlphaBetaFilter(); + m_ramp[i] = false; + } } ControllerEngineJSProxy::~ControllerEngineJSProxy() { + // Stop all timers + QMutableHashIterator i(m_timers); + while (i.hasNext()) { + i.next(); + stopTimer(i.key()); + } + + // Prevents leaving decks in an unstable state + // if the controller is shut down while scratching + QHashIterator it(m_scratchTimers); + while (it.hasNext()) { + it.next(); + qDebug() << "Aborting scratching on deck" << it.value(); + // Clear scratch2_enable. PlayerManager::groupForDeck is 0-indexed. + QString group = PlayerManager::groupForDeck(it.value() - 1); + ControlObjectScript* pScratch2Enable = + getControlObjectScript(group, "scratch2_enable"); + if (pScratch2Enable != nullptr) { + pScratch2Enable->set(0); + } + } + + for (int i = 0; i < kDecks; ++i) { + delete m_scratchFilters[i]; + m_scratchFilters[i] = nullptr; + } + + // Free all the ControlObjectScripts + { + auto it = m_controlCache.begin(); + while (it != m_controlCache.end()) { + qDebug() + << "Deleting ControlObjectScript" + << it.key().group + << it.key().item; + delete it.value(); + // Advance iterator + it = m_controlCache.erase(it); + } + } +} + +ControlObjectScript* ControllerEngineJSProxy::getControlObjectScript( + const QString& group, const QString& name) { + ConfigKey key = ConfigKey(group, name); + ControlObjectScript* coScript = m_controlCache.value(key, nullptr); + if (coScript == nullptr) { + // create COT + coScript = new ControlObjectScript(key, this); + if (coScript->valid()) { + m_controlCache.insert(key, coScript); + } else { + delete coScript; + coScript = nullptr; + } + } + return coScript; } double ControllerEngineJSProxy::getValue(QString group, QString name) { - return m_pEngine->getValue(group, name); + ControlObjectScript* coScript = getControlObjectScript(group, name); + if (coScript == nullptr) { + qWarning() << "ControllerEngine: Unknown control" << group << name + << ", returning 0.0"; + return 0.0; + } + return coScript->get(); } -void ControllerEngineJSProxy::setValue(QString group, QString name, double newValue) { - m_pEngine->setValue(group, name, newValue); +void ControllerEngineJSProxy::setValue( + QString group, QString name, double newValue) { + if (isnan(newValue)) { + qWarning() << "ControllerEngine: script setting [" << group << "," + << name << "] to NotANumber, ignoring."; + return; + } + + ControlObjectScript* coScript = getControlObjectScript(group, name); + + if (coScript != nullptr) { + ControlObject* pControl = ControlObject::getControl( + coScript->getKey(), ControlFlag::NoAssertIfMissing); + if (pControl && + !m_st.ignore( + pControl, coScript->getParameterForValue(newValue))) { + coScript->slotSet(newValue); + } + } } double ControllerEngineJSProxy::getParameter(QString group, QString name) { - return m_pEngine->getParameter(group, name); + ControlObjectScript* coScript = getControlObjectScript(group, name); + if (coScript == nullptr) { + qWarning() << "ControllerEngine: Unknown control" << group << name + << ", returning 0.0"; + return 0.0; + } + return coScript->getParameter(); } -void ControllerEngineJSProxy::setParameter(QString group, QString name, double newValue) { - m_pEngine->setParameter(group, name, newValue); +void ControllerEngineJSProxy::setParameter( + QString group, QString name, double newParameter) { + if (isnan(newParameter)) { + qWarning() << "ControllerEngine: script setting [" << group << "," + << name << "] to NotANumber, ignoring."; + return; + } + + ControlObjectScript* coScript = getControlObjectScript(group, name); + + if (coScript != nullptr) { + ControlObject* pControl = ControlObject::getControl( + coScript->getKey(), ControlFlag::NoAssertIfMissing); + if (pControl && !m_st.ignore(pControl, newParameter)) { + coScript->setParameter(newParameter); + } + } } -double ControllerEngineJSProxy::getParameterForValue(QString group, - QString name, - double value) { - return m_pEngine->getParameterForValue(group, name, value); +double ControllerEngineJSProxy::getParameterForValue( + QString group, QString name, double value) { + if (isnan(value)) { + qWarning() << "ControllerEngine: script setting [" << group << "," + << name << "] to NotANumber, ignoring."; + return 0.0; + } + + ControlObjectScript* coScript = getControlObjectScript(group, name); + + if (coScript == nullptr) { + qWarning() << "ControllerEngine: Unknown control" << group << name + << ", returning 0.0"; + return 0.0; + } + + return coScript->getParameterForValue(value); } void ControllerEngineJSProxy::reset(QString group, QString name) { - m_pEngine->reset(group, name); + ControlObjectScript* coScript = getControlObjectScript(group, name); + if (coScript != nullptr) { + coScript->reset(); + } } double ControllerEngineJSProxy::getDefaultValue(QString group, QString name) { - return m_pEngine->getDefaultValue(group, name); + ControlObjectScript* coScript = getControlObjectScript(group, name); + + if (coScript == nullptr) { + qWarning() << "ControllerEngine: Unknown control" << group << name + << ", returning 0.0"; + return 0.0; + } + + return coScript->getDefault(); } -double ControllerEngineJSProxy::getDefaultParameter(QString group, - QString name) { - return m_pEngine->getDefaultParameter(group, name); +double ControllerEngineJSProxy::getDefaultParameter( + QString group, QString name) { + ControlObjectScript* coScript = getControlObjectScript(group, name); + + if (coScript == nullptr) { + qWarning() << "ControllerEngine: Unknown control" << group << name + << ", returning 0.0"; + return 0.0; + } + + return coScript->getParameterForValue(coScript->getDefault()); } -QJSValue ControllerEngineJSProxy::makeConnection(QString group, QString name, const QJSValue callback) { - return m_pEngine->makeConnection(group, name, callback); +QJSValue ControllerEngineJSProxy::makeConnection( + QString group, QString name, const QJSValue callback) { + QJSEngine* pScriptEngine = m_pEngine->scriptEngine(); + VERIFY_OR_DEBUG_ASSERT(pScriptEngine) { + return QJSValue(); + } + + ControlObjectScript* coScript = getControlObjectScript(group, name); + if (coScript == nullptr) { + // The test setups do not run all of Mixxx, so ControlObjects not + // existing during tests is okay. + if (!m_pEngine->isTesting()) { + m_pEngine->throwJSError( + "ControllerEngine: script tried to connect to " + "ControlObject (" + + group + ", " + name + ") which is non-existent."); + } + return QJSValue(); + } + + if (!callback.isCallable()) { + m_pEngine->throwJSError("Tried to connect (" + group + ", " + name + + ")" + + " to an invalid callback. Make sure that your code contains no " + "syntax errors."); + return QJSValue(); + } + + ScriptConnection connection; + connection.key = ConfigKey(group, name); + connection.engineJSProxy = this; + connection.controllerEngine = m_pEngine; + connection.callback = callback; + connection.id = QUuid::createUuid(); + + if (coScript->addScriptConnection(connection)) { + return pScriptEngine->newQObject( + new ScriptConnectionJSProxy(connection)); + } + + return QJSValue(); } -QJSValue ControllerEngineJSProxy::connectControl(QString group, QString name, const QJSValue passedCallback, bool disconnect) { - return m_pEngine->connectControl(group, name, passedCallback, disconnect); +bool ControllerEngineJSProxy::removeScriptConnection( + const ScriptConnection connection) { + ControlObjectScript* coScript = + getControlObjectScript(connection.key.group, connection.key.item); + + if (m_pEngine->scriptEngine() == nullptr || coScript == nullptr) { + return false; + } + + return coScript->removeScriptConnection(connection); +} + +void ControllerEngineJSProxy::triggerScriptConnection( + const ScriptConnection connection) { + VERIFY_OR_DEBUG_ASSERT(m_pEngine->scriptEngine()) { + return; + } + + ControlObjectScript* coScript = + getControlObjectScript(connection.key.group, connection.key.item); + if (coScript == nullptr) { + return; + } + + connection.executeCallback(coScript->get()); +} + +// This function is a legacy version of makeConnection with several alternate +// ways of invoking it. The callback function can be passed either as a string of +// JavaScript code that evaluates to a function or an actual JavaScript function. +// If "true" is passed as a 4th parameter, all connections to the ControlObject +// are removed. If a ScriptConnectionInvokableWrapper is passed instead of a callback, +// it is disconnected. +// WARNING: These behaviors are quirky and confusing, so if you change this function, +// be sure to run the ControllerEngineTest suite to make sure you do not break old scripts. +QJSValue ControllerEngineJSProxy::connectControl( + QString group, QString name, QJSValue passedCallback, bool disconnect) { + // The passedCallback may or may not actually be a function, so when + // the actual callback function is found, store it in this variable. + QJSValue actualCallbackFunction; + + if (passedCallback.isCallable()) { + if (!disconnect) { + // skip all the checks below and just make the connection + return makeConnection(group, name, passedCallback); + } + actualCallbackFunction = passedCallback; + } + + QJSEngine* pScriptEngine = m_pEngine->scriptEngine(); + + ControlObjectScript* coScript = getControlObjectScript(group, name); + // This check is redundant with makeConnection, but the + // ControlObjectScript is also needed here to check for duplicate connections. + if (coScript == nullptr) { + // The test setups do not run all of Mixxx, so ControlObjects not + // existing during tests is okay. + if (!m_pEngine->isTesting()) { + if (disconnect) { + m_pEngine->throwJSError( + "ControllerEngine: script tried to disconnect from " + "ControlObject (" + + group + ", " + name + ") which is non-existent."); + } else { + m_pEngine->throwJSError( + "ControllerEngine: script tried to connect to " + "ControlObject (" + + group + ", " + name + ") which is non-existent."); + } + } + // This is inconsistent with other failures, which return false. + // QJSValue() with no arguments is undefined in JavaScript. + return QJSValue(); + } + + if (passedCallback.isString()) { + // This check is redundant with makeConnection, but it must be done here + // before evaluating the code string. + VERIFY_OR_DEBUG_ASSERT(pScriptEngine != nullptr) { + return QJSValue(false); + } + + actualCallbackFunction = + m_pEngine->evaluateCodeString(passedCallback.toString()); + + if (!actualCallbackFunction.isCallable()) { + QString sErrorMessage( + "Invalid connection callback provided to " + "engine.connectControl."); + if (actualCallbackFunction.isError()) { + sErrorMessage.append("\n" + actualCallbackFunction.toString()); + } + m_pEngine->throwJSError(sErrorMessage); + return QJSValue(false); + } + + if (coScript->countConnections() > 0 && !disconnect) { + // This is inconsistent with the behavior when passing the callback as + // a function, but keep the old behavior to make sure old scripts do + // not break. + ScriptConnection connection = coScript->firstConnection(); + + qWarning() << "Tried to make duplicate connection between (" + + group + ", " + name + ") and " + + passedCallback.toString() + + " but this is not allowed when passing a callback " + "as a string. " + + "If you actually want to create duplicate " + "connections, " + + "use engine.makeConnection. Returning reference to " + "connection " + + connection.id.toString(); + + return pScriptEngine->newQObject( + new ScriptConnectionJSProxy(connection)); + } + } else if (passedCallback.isQObject()) { + // Assume a ScriptConnection and assume that the script author + // wants to disconnect it, regardless of the disconnect parameter + // and regardless of whether it is connected to the same ControlObject + // specified by the first two parameters to this function. + QObject* qobject = passedCallback.toQObject(); + const QMetaObject* qmeta = qobject->metaObject(); + + qWarning() << "QObject passed to engine.connectControl. Assuming it is" + << "a connection object to disconnect and returning false."; + if (!strcmp(qmeta->className(), "ScriptConnectionJSProxy")) { + ScriptConnectionJSProxy* proxy = (ScriptConnectionJSProxy*)qobject; + proxy->disconnect(); + } + return QJSValue(false); + } + + // Support removing connections by passing "true" as the last parameter + // to this function, regardless of whether the callback is provided + // as a function or a string. + if (disconnect) { + // There is no way to determine which + // ScriptConnection to disconnect unless the script calls + // ScriptConnectionInvokableWrapper::disconnect(), so + // disconnect all ScriptConnections connected to the + // callback function, even though there may be multiple connections. + coScript->disconnectAllConnectionsToFunction(actualCallbackFunction); + return QJSValue(true); + } + + // If execution gets this far without returning, make + // a new connection to actualCallbackFunction. + return makeConnection(group, name, actualCallbackFunction); } void ControllerEngineJSProxy::trigger(QString group, QString name) { - m_pEngine->trigger(group, name); + ControlObjectScript* coScript = getControlObjectScript(group, name); + if (coScript != nullptr) { + coScript->emitValueChanged(); + } } void ControllerEngineJSProxy::log(QString message) { - m_pEngine->log(message); + controllerDebug(message); } +int ControllerEngineJSProxy::beginTimer( + int intervalMillis, QJSValue timerCallback, bool oneShot) { + if (timerCallback.isString()) { + timerCallback = m_pEngine->evaluateCodeString(timerCallback.toString()); + } else if (!timerCallback.isCallable()) { + QString sErrorMessage( + "Invalid timer callback provided to engine.beginTimer. Valid " + "callbacks are strings and functions. " + "Make sure that your code contains no syntax errors."); + if (timerCallback.isError()) { + sErrorMessage.append("\n" + timerCallback.toString()); + } + m_pEngine->throwJSError(sErrorMessage); + return 0; + } + + if (intervalMillis < 20) { + qWarning() << "Timer request for" << intervalMillis + << "ms is too short. Setting to the minimum of 20ms."; + intervalMillis = 20; + } -int ControllerEngineJSProxy::beginTimer(int interval, QJSValue scriptCode, bool oneShot) { - return m_pEngine->beginTimer(interval, scriptCode, oneShot); + // This makes use of every QObject's internal timer mechanism. Nice, clean, + // and simple. See http://doc.trolltech.com/4.6/qobject.html#startTimer for + // details + int timerId = startTimer(intervalMillis); + TimerInfo info; + info.callback = timerCallback; + info.oneShot = oneShot; + m_timers[timerId] = info; + if (timerId == 0) { + qWarning() << "Script timer could not be created"; + } else if (oneShot) { + controllerDebug("Starting one-shot timer:" << timerId); + } else { + controllerDebug("Starting timer:" << timerId); + } + return timerId; } void ControllerEngineJSProxy::stopTimer(int timerId) { - m_pEngine->stopTimer(timerId); + if (!m_timers.contains(timerId)) { + qWarning() << "Killing timer" << timerId + << ": That timer does not exist!"; + return; + } + controllerDebug("Killing timer:" << timerId); + killTimer(timerId); + m_timers.remove(timerId); } -void ControllerEngineJSProxy::scratchEnable(int deck, int intervalsPerRev, double rpm, double alpha, double beta, bool ramp) { - m_pEngine->scratchEnable(deck, intervalsPerRev, rpm, alpha, beta, ramp); +void ControllerEngineJSProxy::timerEvent(QTimerEvent* event) { + int timerId = event->timerId(); + + // See if this is a scratching timer + if (m_scratchTimers.contains(timerId)) { + scratchProcess(timerId); + return; + } + + auto it = m_timers.constFind(timerId); + if (it == m_timers.constEnd()) { + qWarning() << "Timer" << timerId + << "fired but there's no function mapped to it!"; + return; + } + + // NOTE(rryan): Do not assign by reference -- make a copy. I have no idea + // why but this causes segfaults in ~QScriptValue while scratching if we + // don't copy here -- even though internalExecute passes the QScriptValues + // by value. *boggle* + const TimerInfo timerTarget = it.value(); + if (timerTarget.oneShot) { + stopTimer(timerId); + } + + m_pEngine->executeFunction(timerTarget.callback, QJSValueList()); } -void ControllerEngineJSProxy::scratchTick(int deck, int interval) { - m_pEngine->scratchTick(deck, interval); +void ControllerEngineJSProxy::softTakeover( + QString group, QString name, bool set) { + ControlObject* pControl = ControlObject::getControl( + ConfigKey(group, name), ControlFlag::NoAssertIfMissing); + if (!pControl) { + return; + } + if (set) { + m_st.enable(pControl); + } else { + m_st.disable(pControl); + } } -void ControllerEngineJSProxy::scratchDisable(int deck, bool ramp) { - m_pEngine->scratchDisable(deck, ramp); +void ControllerEngineJSProxy::softTakeoverIgnoreNextValue( + QString group, const QString name) { + ControlObject* pControl = ControlObject::getControl( + ConfigKey(group, name), ControlFlag::NoAssertIfMissing); + if (!pControl) { + return; + } + + m_st.ignoreNext(pControl); } -bool ControllerEngineJSProxy::isScratching(int deck) { - return m_pEngine->isScratching(deck); +double ControllerEngineJSProxy::getDeckRate(const QString& group) { + double rate = 0.0; + ControlObjectScript* pRateRatio = + getControlObjectScript(group, "rate_ratio"); + if (pRateRatio != nullptr) { + rate = pRateRatio->get(); + } + + // See if we're in reverse play + ControlObjectScript* pReverse = getControlObjectScript(group, "reverse"); + if (pReverse != nullptr && pReverse->get() == 1) { + rate = -rate; + } + return rate; } -void ControllerEngineJSProxy::softTakeover(QString group, QString name, bool set) { - m_pEngine->softTakeover(group, name, set); +bool ControllerEngineJSProxy::isDeckPlaying(const QString& group) { + ControlObjectScript* pPlay = getControlObjectScript(group, "play"); + + if (pPlay == nullptr) { + QString error = QString("Could not getControlObjectScript()"); + m_pEngine->scriptErrorDialog(error, error); + return false; + } + + return pPlay->get() > 0.0; } -void ControllerEngineJSProxy::softTakeoverIgnoreNextValue(QString group, - QString name) { - m_pEngine->softTakeoverIgnoreNextValue(group, name); +void ControllerEngineJSProxy::scratchEnable(int deck, + int intervalsPerRev, + double rpm, + double alpha, + double beta, + bool ramp) { + // If we're already scratching this deck, override that with this request + if (m_dx[deck]) { + //qDebug() << "Already scratching deck" << deck << ". Overriding."; + int timerId = m_scratchTimers.key(deck); + killTimer(timerId); + m_scratchTimers.remove(timerId); + } + + // Controller resolution in intervals per second at normal speed. + // (rev/min * ints/rev * mins/sec) + double intervalsPerSecond = (rpm * intervalsPerRev) / 60.0; + + if (intervalsPerSecond == 0.0) { + qWarning() << "Invalid rpm or intervalsPerRev supplied to " + "scratchEnable. Ignoring request."; + return; + } + + m_dx[deck] = 1.0 / intervalsPerSecond; + m_intervalAccumulator[deck] = 0.0; + m_ramp[deck] = false; + m_rampFactor[deck] = 0.001; + m_brakeActive[deck] = false; + + // PlayerManager::groupForDeck is 0-indexed. + QString group = PlayerManager::groupForDeck(deck - 1); + + // Ramp velocity, default to stopped. + double initVelocity = 0.0; + + ControlObjectScript* pScratch2Enable = + getControlObjectScript(group, "scratch2_enable"); + + // If ramping is desired, figure out the deck's current speed + if (ramp) { + // See if the deck is already being scratched + if (pScratch2Enable != nullptr && pScratch2Enable->get() == 1) { + // If so, set the filter's initial velocity to the scratch speed + ControlObjectScript* pScratch2 = + getControlObjectScript(group, "scratch2"); + if (pScratch2 != nullptr) { + initVelocity = pScratch2->get(); + } + } else if (isDeckPlaying(group)) { + // If the deck is playing, set the filter's initial velocity to the + // playback speed + initVelocity = getDeckRate(group); + } + } + + // Initialize scratch filter + if (alpha && beta) { + m_scratchFilters[deck]->init(kAlphaBetaDt, initVelocity, alpha, beta); + } else { + // Use filter's defaults if not specified + m_scratchFilters[deck]->init(kAlphaBetaDt, initVelocity); + } + + // 1ms is shortest possible, OS dependent + int timerId = startTimer(kScratchTimerMs); + + // Associate this virtual deck with this timer for later processing + m_scratchTimers[timerId] = deck; + + // Set scratch2_enable + if (pScratch2Enable != nullptr) { + pScratch2Enable->slotSet(1); + } } -void ControllerEngineJSProxy::brake(int deck, bool activate, double factor, double rate) { - m_pEngine->brake(deck, activate, factor, rate); +void ControllerEngineJSProxy::scratchTick(int deck, int interval) { + m_lastMovement[deck] = mixxx::Time::elapsed(); + m_intervalAccumulator[deck] += interval; +} + +void ControllerEngineJSProxy::scratchProcess(int timerId) { + int deck = m_scratchTimers[timerId]; + // PlayerManager::groupForDeck is 0-indexed. + QString group = PlayerManager::groupForDeck(deck - 1); + AlphaBetaFilter* filter = m_scratchFilters[deck]; + if (!filter) { + qWarning() << "Scratch filter pointer is null on deck" << deck; + return; + } + + const double oldRate = filter->predictedVelocity(); + + // Give the filter a data point: + + // If we're ramping to end scratching and the wheel hasn't been turned very + // recently (spinback after lift-off,) feed fixed data + if (m_ramp[deck] && !m_softStartActive[deck] && + ((mixxx::Time::elapsed() - m_lastMovement[deck]) >= + mixxx::Duration::fromMillis(1))) { + 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]) { + // pretend we have moved by (desired rate*default distance) + filter->observation(m_rampTo[deck] * kAlphaBetaDt); + } 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]); + } + + const double newRate = filter->predictedVelocity(); + + // Actually do the scratching + ControlObjectScript* pScratch2 = getControlObjectScript(group, "scratch2"); + if (pScratch2 == nullptr) { + return; // abort and maybe it'll work on the next pass + } + pScratch2->set(newRate); + + // Reset accumulator + m_intervalAccumulator[deck] = 0; + + // 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)))) { + // 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. + pScratch2->slotSet(0.0); + ControlObjectScript* pPlay = getControlObjectScript(group, "play"); + if (pPlay != nullptr) { + pPlay->slotSet(0.0); + } + } + + // Clear scratch2_enable to end scratching. + ControlObjectScript* pScratch2Enable = + getControlObjectScript(group, "scratch2_enable"); + if (pScratch2Enable == nullptr) { + return; // abort and maybe it'll work on the next pass + } + pScratch2Enable->slotSet(0); + + // Remove timer + killTimer(timerId); + m_scratchTimers.remove(timerId); + + m_dx[deck] = 0.0; + m_brakeActive[deck] = false; + m_softStartActive[deck] = false; + } +} + +void ControllerEngineJSProxy::scratchDisable(int deck, bool ramp) { + // PlayerManager::groupForDeck is 0-indexed. + QString group = PlayerManager::groupForDeck(deck - 1); + + m_rampTo[deck] = 0.0; + + // If no ramping is desired, disable scratching immediately + if (!ramp) { + // Clear scratch2_enable + ControlObjectScript* pScratch2Enable = getControlObjectScript(group, "scratch2_enable"); + if (pScratch2Enable != nullptr) { + pScratch2Enable->slotSet(0); + } + // Can't return here because we need scratchProcess to stop the timer. + // So it's still actually ramping, we just won't hear or see it. + } else if (isDeckPlaying(group)) { + // If so, set the target velocity to the playback speed + m_rampTo[deck] = getDeckRate(group); + } + + m_lastMovement[deck] = mixxx::Time::elapsed(); + m_ramp[deck] = true; // Activate the ramping in scratchProcess() +} + +bool ControllerEngineJSProxy::isScratching(int deck) { + // PlayerManager::groupForDeck is 0-indexed. + QString group = PlayerManager::groupForDeck(deck - 1); + return getValue(group, "scratch2_enable") > 0; } void ControllerEngineJSProxy::spinback(int deck, bool activate, double factor, double rate) { - m_pEngine->spinback(deck, activate, factor, rate); + // defaults for args set in header file + brake(deck, activate, factor, rate); +} + +void ControllerEngineJSProxy::brake(int deck, bool activate, double factor, double rate) { + // 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; + 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); + } + + // 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; + } } void ControllerEngineJSProxy::softStart(int deck, bool activate, double factor) { - m_pEngine->softStart(deck, activate, factor); + // 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_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; + + AlphaBetaFilter* filter = m_scratchFilters[deck]; + if (filter != nullptr) { + initRate = filter->predictedVelocity(); + } + } + + // setup timer, start playing and set scratch2 + timerId = startTimer(kScratchTimerMs); + m_scratchTimers[timerId] = deck; + + ControlObjectScript* pPlay = getControlObjectScript(group, "play"); + if (pPlay != nullptr) { + pPlay->slotSet(1.0); + } + + 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; + } } diff --git a/src/controllers/engine/controllerenginejsproxy.h b/src/controllers/engine/controllerenginejsproxy.h index 8a06c86aa34..e6135057f80 100644 --- a/src/controllers/engine/controllerenginejsproxy.h +++ b/src/controllers/engine/controllerenginejsproxy.h @@ -1,10 +1,15 @@ -#ifndef CONTROLLERENGINEJSPROXY_H -#define CONTROLLERENGINEJSPROXY_H +#pragma once #include #include +#include "controllers/softtakeover.h" +#include "util/alphabetafilter.h" + class ControllerEngine; +class ControlObjectScript; +class ScriptConnection; +class ConfigKey; // An object of this class gets exposed to the JS engine, so the methods of this class // constitute the api that is provided to scripts under "engine" object. @@ -47,8 +52,35 @@ class ControllerEngineJSProxy : public QObject { 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); + bool removeScriptConnection(const ScriptConnection conn); + /// Execute a ScriptConnection's JS callback + void triggerScriptConnection(const ScriptConnection conn); + + /// Handler for timers that scripts set. + virtual void timerEvent(QTimerEvent* event); + private: + QHash m_controlCache; + ControlObjectScript* getControlObjectScript(const QString& group, const QString& name); + + SoftTakeoverCtrl m_st; + + struct TimerInfo { + QJSValue callback; + bool oneShot; + }; + QHash m_timers; + + QVarLengthArray m_intervalAccumulator; + QVarLengthArray m_lastMovement; + QVarLengthArray m_dx, m_rampTo, m_rampFactor; + QVarLengthArray m_ramp, m_brakeActive, m_softStartActive; + QVarLengthArray m_scratchFilters; + QHash m_scratchTimers; + /// Applies the accumulated movement to the track speed + void scratchProcess(int timerId); + bool isDeckPlaying(const QString& group); + double getDeckRate(const QString& group); + ControllerEngine* m_pEngine; }; - -#endif // CONTROLLERENGINEJSPROXY_H diff --git a/src/controllers/engine/scriptconnection.h b/src/controllers/engine/scriptconnection.h index 153a4b83f86..e665fdecfe7 100644 --- a/src/controllers/engine/scriptconnection.h +++ b/src/controllers/engine/scriptconnection.h @@ -6,6 +6,7 @@ #include "preferences/configobject.h" class ControllerEngine; +class ControllerEngineJSProxy; /// ScriptConnection is a connection between a ControlObject and a /// script callback function that gets executed when the value @@ -15,6 +16,7 @@ class ScriptConnection { ConfigKey key; QUuid id; QJSValue callback; + ControllerEngineJSProxy* engineJSProxy; ControllerEngine* controllerEngine; void executeCallback(double value) const; diff --git a/src/controllers/engine/scriptconnectionjsproxy.cpp b/src/controllers/engine/scriptconnectionjsproxy.cpp index 408971a2e86..5efd01c16f5 100644 --- a/src/controllers/engine/scriptconnectionjsproxy.cpp +++ b/src/controllers/engine/scriptconnectionjsproxy.cpp @@ -1,14 +1,14 @@ #include "controllers/engine/scriptconnectionjsproxy.h" -#include "controllers/engine/controllerengine.h" +#include "controllers/engine/controllerenginejsproxy.h" bool ScriptConnectionJSProxy::disconnect() { // if the removeScriptConnection succeeded, the connection has been successfully disconnected - bool success = m_scriptConnection.controllerEngine->removeScriptConnection(m_scriptConnection); + bool success = m_scriptConnection.engineJSProxy->removeScriptConnection(m_scriptConnection); m_isConnected = !success; return success; } void ScriptConnectionJSProxy::trigger() { - m_scriptConnection.controllerEngine->triggerScriptConnection(m_scriptConnection); + m_scriptConnection.engineJSProxy->triggerScriptConnection(m_scriptConnection); } From 9c04291b2496384c944939f80be589089da57850 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 6 Jul 2020 02:18:20 -0500 Subject: [PATCH 02/61] reorganize controller scripting code * Move src/controllers/engine to src/controllers/scripting * Create src/controllers/scripting/legacy subfolder * Rename ControllerEngine to ControllerScriptHandler * Rename ControllerEngineJSProxy to ControllerScriptInterface --- CMakeLists.txt | 12 +- build/depends.py | 12 +- src/control/controlobjectscript.h | 2 +- src/controllers/controller.cpp | 33 ++-- src/controllers/controller.h | 8 +- src/controllers/midi/midicontroller.cpp | 8 +- .../{engine => scripting}/colormapper.cpp | 2 +- .../{engine => scripting}/colormapper.h | 0 .../colormapperjsproxy.cpp | 2 +- .../colormapperjsproxy.h | 2 +- .../controllerscripthandler.cpp} | 156 +++++++++++------- .../controllerscripthandler.h} | 13 +- .../legacy/controllerscriptinterface.cpp} | 94 +++++------ .../legacy/controllerscriptinterface.h} | 20 +-- .../legacy}/scriptconnection.cpp | 4 +- .../legacy}/scriptconnection.h | 8 +- .../legacy}/scriptconnectionjsproxy.cpp | 4 +- .../legacy}/scriptconnectionjsproxy.h | 2 +- src/test/colormapperjsproxy_test.cpp | 2 +- .../controller_preset_validation_test.cpp | 2 +- src/test/controllerengine_test.cpp | 6 +- 21 files changed, 210 insertions(+), 182 deletions(-) rename src/controllers/{engine => scripting}/colormapper.cpp (98%) rename src/controllers/{engine => scripting}/colormapper.h (100%) rename src/controllers/{engine => scripting}/colormapperjsproxy.cpp (97%) rename src/controllers/{engine => scripting}/colormapperjsproxy.h (96%) rename src/controllers/{engine/controllerengine.cpp => scripting/controllerscripthandler.cpp} (72%) rename src/controllers/{engine/controllerengine.h => scripting/controllerscripthandler.h} (91%) rename src/controllers/{engine/controllerenginejsproxy.cpp => scripting/legacy/controllerscriptinterface.cpp} (89%) rename src/controllers/{engine/controllerenginejsproxy.h => scripting/legacy/controllerscriptinterface.h} (79%) rename src/controllers/{engine => scripting/legacy}/scriptconnection.cpp (84%) rename src/controllers/{engine => scripting/legacy}/scriptconnection.h (81%) rename src/controllers/{engine => scripting/legacy}/scriptconnectionjsproxy.cpp (75%) rename src/controllers/{engine => scripting/legacy}/scriptconnectionjsproxy.h (92%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1404d67d35c..6e803b15911 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -374,12 +374,12 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/dlgprefcontrollerdlg.ui src/controllers/dlgprefcontrollers.cpp src/controllers/dlgprefcontrollersdlg.ui - src/controllers/engine/controllerengine.cpp - src/controllers/engine/controllerenginejsproxy.cpp - src/controllers/engine/colormapper.cpp - src/controllers/engine/colormapperjsproxy.cpp - src/controllers/engine/scriptconnection.cpp - src/controllers/engine/scriptconnectionjsproxy.cpp + src/controllers/scripting/controllerscripthandler.cpp + src/controllers/scripting/colormapper.cpp + src/controllers/scripting/colormapperjsproxy.cpp + src/controllers/scripting/legacy/controllerscriptinterface.cpp + src/controllers/scripting/legacy/scriptconnection.cpp + src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp src/controllers/keyboard/keyboardeventfilter.cpp src/controllers/learningutils.cpp src/controllers/midi/midicontroller.cpp diff --git a/build/depends.py b/build/depends.py index 8fe2422d0d6..a9c3029f967 100644 --- a/build/depends.py +++ b/build/depends.py @@ -922,12 +922,12 @@ def sources(self, build): "src/controllers/delegates/midibytedelegate.cpp", "src/controllers/delegates/midioptionsdelegate.cpp", "src/controllers/learningutils.cpp", - "src/controllers/engine/controllerengine.cpp", - "src/controllers/engine/controllerenginejsproxy.cpp", - "src/controllers/engine/colormapper.cpp", - "src/controllers/engine/colormapperjsproxy.cpp", - "src/controllers/engine/scriptconnection.cpp", - "src/controllers/engine/scriptconnectionjsproxy.cpp", + "src/controllers/scripting/controllerscripthandler.cpp", + "src/controllers/scripting/colormapper.cpp", + "src/controllers/scripting/colormapperjsproxy.cpp", + "src/controllers/scripting/legacy/controllerscriptinterface.cpp", + "src/controllers/scripting/legacy/scriptconnection.cpp", + "src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp", "src/controllers/midi/midimessage.cpp", "src/controllers/midi/midiutils.cpp", "src/controllers/midi/midicontroller.cpp", diff --git a/src/control/controlobjectscript.h b/src/control/controlobjectscript.h index acc6d9ceba3..9c046efed18 100644 --- a/src/control/controlobjectscript.h +++ b/src/control/controlobjectscript.h @@ -5,7 +5,7 @@ #include "control/controlproxy.h" #include "controllers/controllerdebug.h" -#include "controllers/engine/scriptconnection.h" +#include "controllers/scripting/legacy/scriptconnection.h" // this is used for communicate with controller scripts class ControlObjectScript : public ControlProxy { diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index ec41338ec06..02e6e1e778c 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -14,7 +14,7 @@ #include "util/screensaver.h" Controller::Controller() - : m_pEngine(nullptr), + : m_pScriptHandler(nullptr), m_bIsOutputDevice(false), m_bIsInputDevice(false), m_bIsOpen(false), @@ -34,22 +34,22 @@ ControllerJSProxy* Controller::jsProxy() { void Controller::startEngine() { controllerDebug(" Starting engine"); - if (m_pEngine != NULL) { + if (m_pScriptHandler != NULL) { qWarning() << "Controller: Engine already exists! Restarting:"; stopEngine(); } - m_pEngine = new ControllerEngine(this); + m_pScriptHandler = new ControllerScriptHandler(this); } void Controller::stopEngine() { controllerDebug(" Shutting down engine"); - if (m_pEngine == NULL) { + if (m_pScriptHandler == NULL) { qWarning() << "Controller::stopEngine(): No engine exists!"; return; } - m_pEngine->gracefulShutdown(); - delete m_pEngine; - m_pEngine = NULL; + m_pScriptHandler->gracefulShutdown(); + delete m_pScriptHandler; + m_pScriptHandler = NULL; } bool Controller::applyPreset(bool initializeScripts) { @@ -58,7 +58,7 @@ bool Controller::applyPreset(bool initializeScripts) { const ControllerPreset* pPreset = preset(); // Load the script code into the engine - if (m_pEngine == NULL) { + if (m_pScriptHandler == NULL) { qWarning() << "Controller::applyPreset(): No engine exists!"; return false; } @@ -69,13 +69,13 @@ bool Controller::applyPreset(bool initializeScripts) { return true; } - bool success = m_pEngine->loadScriptFiles(scriptFiles); + bool success = m_pScriptHandler->loadScriptFiles(scriptFiles); if (success && initializeScripts) { - m_pEngine->initializeScripts(scriptFiles); + m_pScriptHandler->initializeScripts(scriptFiles); } if (initializeScripts) { - m_pEngine->loadModule(pPreset->moduleFileInfo()); + m_pScriptHandler->loadModule(pPreset->moduleFileInfo()); } return success; } @@ -114,8 +114,7 @@ void Controller::triggerActivity() } } void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { - - if (m_pEngine == NULL) { + if (m_pScriptHandler == NULL) { //qWarning() << "Controller::receive called with no active engine!"; // Don't complain, since this will always show after closing a device as // queued signals flush out @@ -139,14 +138,14 @@ void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { controllerDebug(message); } - foreach (QString function, m_pEngine->getScriptFunctionPrefixes()) { + foreach (QString function, m_pScriptHandler->getScriptFunctionPrefixes()) { if (function == "") { continue; } function.append(".incomingData"); - QJSValue incomingDataFunction = m_pEngine->wrapFunctionCode(function, 2); - m_pEngine->executeFunction(incomingDataFunction, data); + QJSValue incomingDataFunction = m_pScriptHandler->wrapFunctionCode(function, 2); + m_pScriptHandler->executeFunction(incomingDataFunction, data); } - m_pEngine->handleInput(data, timestamp); + m_pScriptHandler->handleInput(data, timestamp); } diff --git a/src/controllers/controller.h b/src/controllers/controller.h index e9ef45f6510..ebb2bbf2c01 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -17,7 +17,7 @@ #include "controllers/controllerpresetinfo.h" #include "controllers/controllerpresetvisitor.h" #include "controllers/controllervisitor.h" -#include "controllers/engine/controllerengine.h" +#include "controllers/scripting/controllerscripthandler.h" #include "util/duration.h" class ControllerJSProxy; @@ -122,8 +122,8 @@ class Controller : public QObject, ConstControllerPresetVisitor { // To be called when receiving events void triggerActivity(); - inline ControllerEngine* getEngine() const { - return m_pEngine; + inline ControllerScriptHandler* getScriptHandler() const { + return m_pScriptHandler; } inline void setDeviceName(QString deviceName) { m_sDeviceName = deviceName; @@ -160,7 +160,7 @@ class Controller : public QObject, ConstControllerPresetVisitor { // Returns a pointer to the currently loaded controller preset. For internal // use only. virtual ControllerPreset* preset() = 0; - ControllerEngine* m_pEngine; + ControllerScriptHandler* m_pScriptHandler; // Verbose and unique device name suitable for display. QString m_sDeviceName; diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index d3371f037c6..9485eb5a34b 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -207,7 +207,7 @@ void MidiController::receive(unsigned char status, unsigned char control, byteArray.append(control); byteArray.append(value); - ControllerEngine* pEngine = getEngine(); + ControllerScriptHandler* pEngine = getScriptHandler(); // pEngine is nullptr in tests. if (pEngine) { pEngine->handleInput(byteArray, timestamp); @@ -261,7 +261,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, unsigned char opCode = MidiUtils::opCodeFromStatus(status); if (mapping.options.script) { - ControllerEngine* pEngine = getEngine(); + ControllerScriptHandler* pEngine = getScriptHandler(); if (pEngine == NULL) { return; } @@ -482,7 +482,7 @@ double MidiController::computeValue( void MidiController::receive(QByteArray data, mixxx::Duration timestamp) { controllerDebug(MidiUtils::formatSysexMessage(getName(), data, timestamp)); - getEngine()->handleInput(data, timestamp); + getScriptHandler()->handleInput(data, timestamp); // legacy stuff below MidiKey mappingKey(data.at(0), 0xFF); @@ -514,7 +514,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, mixxx::Duration timestamp) { // Custom script handler if (mapping.options.script) { - ControllerEngine* pEngine = getEngine(); + ControllerScriptHandler* pEngine = getScriptHandler(); if (pEngine == NULL) { return; } diff --git a/src/controllers/engine/colormapper.cpp b/src/controllers/scripting/colormapper.cpp similarity index 98% rename from src/controllers/engine/colormapper.cpp rename to src/controllers/scripting/colormapper.cpp index 5e6514ae411..2600b94dcf1 100644 --- a/src/controllers/engine/colormapper.cpp +++ b/src/controllers/scripting/colormapper.cpp @@ -1,4 +1,4 @@ -#include "controllers/engine/colormapper.h" +#include "controllers/scripting/colormapper.h" #include #include diff --git a/src/controllers/engine/colormapper.h b/src/controllers/scripting/colormapper.h similarity index 100% rename from src/controllers/engine/colormapper.h rename to src/controllers/scripting/colormapper.h diff --git a/src/controllers/engine/colormapperjsproxy.cpp b/src/controllers/scripting/colormapperjsproxy.cpp similarity index 97% rename from src/controllers/engine/colormapperjsproxy.cpp rename to src/controllers/scripting/colormapperjsproxy.cpp index b1891311f02..86bb5340865 100644 --- a/src/controllers/engine/colormapperjsproxy.cpp +++ b/src/controllers/scripting/colormapperjsproxy.cpp @@ -1,4 +1,4 @@ -#include "controllers/engine/colormapperjsproxy.h" +#include "controllers/scripting/colormapperjsproxy.h" ColorMapperJSProxy::ColorMapperJSProxy(QVariantMap availableColors) : m_pColorMapper(nullptr) { diff --git a/src/controllers/engine/colormapperjsproxy.h b/src/controllers/scripting/colormapperjsproxy.h similarity index 96% rename from src/controllers/engine/colormapperjsproxy.h rename to src/controllers/scripting/colormapperjsproxy.h index 3b014796109..b64e8c6337a 100644 --- a/src/controllers/engine/colormapperjsproxy.h +++ b/src/controllers/scripting/colormapperjsproxy.h @@ -2,7 +2,7 @@ #include #include -#include "controllers/engine/colormapper.h" +#include "controllers/scripting/colormapper.h" /// ColorMapperJSProxy is a wrapper class that exposes ColorMapper via the /// QJSEngine and makes it possible to create and use ColorMapper object from diff --git a/src/controllers/engine/controllerengine.cpp b/src/controllers/scripting/controllerscripthandler.cpp similarity index 72% rename from src/controllers/engine/controllerengine.cpp rename to src/controllers/scripting/controllerscripthandler.cpp index ca4d110315d..54bb52ee15d 100644 --- a/src/controllers/engine/controllerengine.cpp +++ b/src/controllers/scripting/controllerscripthandler.cpp @@ -1,14 +1,14 @@ -#include "controllers/engine/controllerengine.h" +#include "controllers/scripting/controllerscripthandler.h" #include "control/controlobject.h" #include "controllers/controller.h" #include "controllers/controllerdebug.h" -#include "controllers/engine/colormapperjsproxy.h" -#include "controllers/engine/controllerenginejsproxy.h" +#include "controllers/scripting/colormapperjsproxy.h" +#include "controllers/scripting/legacy/controllerscriptinterface.h" #include "errordialoghandler.h" #include "mixer/playermanager.h" -ControllerEngine::ControllerEngine(Controller* controller) +ControllerScriptHandler::ControllerScriptHandler(Controller* controller) : m_bDisplayingExceptionDialog(false), m_pScriptEngine(nullptr), m_pController(controller), @@ -19,11 +19,11 @@ ControllerEngine::ControllerEngine(Controller* controller) initializeScriptEngine(); } -ControllerEngine::~ControllerEngine() { +ControllerScriptHandler::~ControllerScriptHandler() { uninitializeScriptEngine(); } -bool ControllerEngine::callFunctionOnObjects(QList scriptFunctionPrefixes, +bool ControllerScriptHandler::callFunctionOnObjects(QList scriptFunctionPrefixes, const QString& function, QJSValueList args, bool bFatalError) { @@ -37,16 +37,18 @@ bool ControllerEngine::callFunctionOnObjects(QList scriptFunctionPrefix for (const QString& prefixName : scriptFunctionPrefixes) { QJSValue prefix = global.property(prefixName); if (!prefix.isObject()) { - qWarning() << "ControllerEngine: No" << prefixName << "object in script"; + qWarning() << "ControllerScriptHandler: No" << prefixName << "object in script"; continue; } QJSValue init = prefix.property(function); if (!init.isCallable()) { - qWarning() << "ControllerEngine:" << prefixName << "has no" << function << " method"; + qWarning() << "ControllerScriptHandler:" << prefixName << "has no" + << function << " method"; continue; } - controllerDebug("ControllerEngine: Executing" << prefixName << "." << function); + controllerDebug("ControllerScriptHandler: Executing" + << prefixName << "." << function); QJSValue result = init.callWithInstance(prefix, args); if (result.isError()) { showScriptExceptionDialog(result, bFatalError); @@ -56,21 +58,22 @@ bool ControllerEngine::callFunctionOnObjects(QList scriptFunctionPrefix return success; } -QJSValue ControllerEngine::byteArrayToScriptValue(const QByteArray& byteArray) { +QJSValue ControllerScriptHandler::byteArrayToScriptValue( + const QByteArray& byteArray) { // The QJSEngine converts the QByteArray to an ArrayBuffer object. QJSValue arrayBuffer = m_pScriptEngine->toScriptValue(byteArray); // Convert the ArrayBuffer to a Uint8 typed array so scripts can access its bytes // with the [] operator. - QJSValue result = m_byteArrayToScriptValueJSFunction.call( - QJSValueList{arrayBuffer}); + QJSValue result = + m_byteArrayToScriptValueJSFunction.call(QJSValueList{arrayBuffer}); if (result.isError()) { showScriptExceptionDialog(result); } return result; } -QJSValue ControllerEngine::wrapFunctionCode(const QString& codeSnippet, - int numberOfArgs) { +QJSValue ControllerScriptHandler::wrapFunctionCode( + const QString& codeSnippet, int numberOfArgs) { // This function is called from outside the controller engine, so we can't // use VERIFY_OR_DEBUG_ASSERT here if (m_pScriptEngine == nullptr) { @@ -101,12 +104,12 @@ QJSValue ControllerEngine::wrapFunctionCode(const QString& codeSnippet, return wrappedFunction; } -void ControllerEngine::gracefulShutdown() { +void ControllerScriptHandler::gracefulShutdown() { if (m_pScriptEngine == nullptr) { return; } - qDebug() << "ControllerEngine shutting down..."; + qDebug() << "ControllerScriptHandler shutting down..."; qDebug() << "Invoking shutdown() hook in scripts"; callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); @@ -119,7 +122,7 @@ void ControllerEngine::gracefulShutdown() { m_scriptWrappedFunctionCache.clear(); } -void ControllerEngine::initializeScriptEngine() { +void ControllerScriptHandler::initializeScriptEngine() { VERIFY_OR_DEBUG_ASSERT(!m_pScriptEngine) { return; } @@ -129,30 +132,37 @@ void ControllerEngine::initializeScriptEngine() { m_pScriptEngine->installExtensions(QJSEngine::ConsoleExtension); - // Make this ControllerEngine instance available to scripts as 'engine'. + // Make this ControllerScriptHandler instance available to scripts as 'engine'. QJSValue engineGlobalObject = m_pScriptEngine->globalObject(); - ControllerEngineJSProxy* proxy = new ControllerEngineJSProxy(this); - engineGlobalObject.setProperty("engine", m_pScriptEngine->newQObject(proxy)); + ControllerScriptInterface* legacyScriptInterface = + new ControllerScriptInterface(this); + engineGlobalObject.setProperty( + "engine", m_pScriptEngine->newQObject(legacyScriptInterface)); - QJSValue mapper = m_pScriptEngine->newQMetaObject(&ColorMapperJSProxy::staticMetaObject); + QJSValue mapper = m_pScriptEngine->newQMetaObject( + &ColorMapperJSProxy::staticMetaObject); engineGlobalObject.setProperty("ColorMapper", mapper); if (m_pController) { - qDebug() << "Controller in script engine is:" << m_pController->getName(); + qDebug() << "Controller in script engine is:" + << m_pController->getName(); ControllerJSProxy* controllerProxy = m_pController->jsProxy(); // Make the Controller instance available to scripts - engineGlobalObject.setProperty("controller", m_pScriptEngine->newQObject(controllerProxy)); + engineGlobalObject.setProperty( + "controller", m_pScriptEngine->newQObject(controllerProxy)); // ...under the legacy name as well - engineGlobalObject.setProperty("midi", m_pScriptEngine->newQObject(controllerProxy)); + engineGlobalObject.setProperty( + "midi", m_pScriptEngine->newQObject(controllerProxy)); } - m_byteArrayToScriptValueJSFunction = evaluateCodeString("(function(arg1) { return new Uint8Array(arg1) })"); + m_byteArrayToScriptValueJSFunction = evaluateCodeString( + "(function(arg1) { return new Uint8Array(arg1) })"); } -void ControllerEngine::uninitializeScriptEngine() { +void ControllerScriptHandler::uninitializeScriptEngine() { // Delete the script engine, first clearing the pointer so that // other threads will not get the dead pointer after we delete it. if (m_pScriptEngine != nullptr) { @@ -162,11 +172,12 @@ void ControllerEngine::uninitializeScriptEngine() { } } -void ControllerEngine::loadModule(QFileInfo moduleFileInfo) { +void ControllerScriptHandler::loadModule(QFileInfo moduleFileInfo) { #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) m_moduleFileInfo = moduleFileInfo; - QJSValue mod = m_pScriptEngine->importModule(moduleFileInfo.absoluteFilePath()); + QJSValue mod = + m_pScriptEngine->importModule(moduleFileInfo.absoluteFilePath()); if (mod.isError()) { showScriptExceptionDialog(mod); return; @@ -175,7 +186,7 @@ void ControllerEngine::loadModule(QFileInfo moduleFileInfo) { connect(&m_scriptWatcher, &QFileSystemWatcher::fileChanged, this, - &ControllerEngine::scriptHasChanged); + &ControllerScriptHandler::scriptHasChanged); m_scriptWatcher.addPath(moduleFileInfo.absoluteFilePath()); QJSValue initFunction = mod.property("init"); @@ -202,7 +213,8 @@ void ControllerEngine::loadModule(QFileInfo moduleFileInfo) { #endif } -void ControllerEngine::handleInput(QByteArray data, mixxx::Duration timestamp) { +void ControllerScriptHandler::handleInput( + QByteArray data, mixxx::Duration timestamp) { if (m_handleInputFunction.isCallable()) { QJSValueList args; args << byteArrayToScriptValue(data); @@ -211,7 +223,8 @@ void ControllerEngine::handleInput(QByteArray data, mixxx::Duration timestamp) { } } -bool ControllerEngine::loadScriptFiles(const QList& scripts) { +bool ControllerScriptHandler::loadScriptFiles( + const QList& scripts) { bool scriptsEvaluatedCorrectly = true; for (const auto& script : scripts) { if (!evaluateScriptFile(script.file)) { @@ -221,7 +234,10 @@ bool ControllerEngine::loadScriptFiles(const QListgetPreset(); gracefulShutdown(); @@ -254,7 +273,8 @@ void ControllerEngine::reloadScripts() { loadModule(m_moduleFileInfo); } -void ControllerEngine::initializeScripts(const QList& scripts) { +void ControllerScriptHandler::initializeScripts( + const QList& scripts) { m_scriptFunctionPrefixes.clear(); for (const ControllerPreset::ScriptFileInfo& script : scripts) { // Skip empty prefixes. @@ -268,7 +288,8 @@ void ControllerEngine::initializeScripts(const QListevaluate(program, fileName, lineNumber); } -void ControllerEngine::throwJSError(const QString& message) { +void ControllerScriptHandler::throwJSError(const QString& message) { #if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) QString errorText = tr("Uncaught exception: %1").arg(message); qWarning() << "ControllerEngine:" << errorText; @@ -339,7 +362,8 @@ void ControllerEngine::throwJSError(const QString& message) { #endif } -void ControllerEngine::showScriptExceptionDialog(QJSValue evaluationResult, bool bFatalError) { +void ControllerScriptHandler::showScriptExceptionDialog( + QJSValue evaluationResult, bool bFatalError) { VERIFY_OR_DEBUG_ASSERT(evaluationResult.isError()) { return; } @@ -351,9 +375,11 @@ void ControllerEngine::showScriptExceptionDialog(QJSValue evaluationResult, bool QString errorText; if (filename.isEmpty()) { - errorText = QString("Uncaught exception at line %1 in passed code.").arg(line); + errorText = QString("Uncaught exception at line %1 in passed code.") + .arg(line); } else { - errorText = QString("Uncaught exception at line %1 in file %2.").arg(line, filename); + errorText = QString("Uncaught exception at line %1 in file %2.") + .arg(line, filename); } errorText += QStringLiteral("\n\nException:\n ") + errorMessage; @@ -362,7 +388,7 @@ void ControllerEngine::showScriptExceptionDialog(QJSValue evaluationResult, bool // slider values that will differ most of the time. This would break // the "Ignore" feature of the error dialog. QString key = errorText; - qWarning() << "ControllerEngine:" << errorText; + qWarning() << "ControllerScriptHandler:" << errorText; // Add backtrace to the error details errorText += QStringLiteral("\n\nBacktrace:\n") + backtrace; @@ -372,7 +398,7 @@ void ControllerEngine::showScriptExceptionDialog(QJSValue evaluationResult, bool } } -void ControllerEngine::scriptErrorDialog( +void ControllerScriptHandler::scriptErrorDialog( const QString& detailedError, const QString& key, bool bFatalError) { if (m_bTesting) { return; @@ -426,46 +452,54 @@ void ControllerEngine::scriptErrorDialog( if (ErrorDialogHandler::instance()->requestErrorDialog(props)) { m_bDisplayingExceptionDialog = true; // Enable custom handling of the dialog buttons - connect(ErrorDialogHandler::instance(), &ErrorDialogHandler::stdButtonClicked, this, &ControllerEngine::errorDialogButton); + connect(ErrorDialogHandler::instance(), + &ErrorDialogHandler::stdButtonClicked, + this, + &ControllerScriptHandler::errorDialogButton); } } -void ControllerEngine::errorDialogButton( +void ControllerScriptHandler::errorDialogButton( const QString& key, QMessageBox::StandardButton clickedButton) { Q_UNUSED(key); m_bDisplayingExceptionDialog = false; // Something was clicked, so disable this signal now - disconnect( - ErrorDialogHandler::instance(), + disconnect(ErrorDialogHandler::instance(), &ErrorDialogHandler::stdButtonClicked, this, - &ControllerEngine::errorDialogButton); + &ControllerScriptHandler::errorDialogButton); if (clickedButton == QMessageBox::Retry) { reloadScripts(); } } -bool ControllerEngine::evaluateScriptFile(const QFileInfo& scriptFile) { +bool ControllerScriptHandler::evaluateScriptFile(const QFileInfo& scriptFile) { VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { return false; } if (!scriptFile.exists()) { - qWarning() << "ControllerEngine: File does not exist:" << scriptFile.absoluteFilePath(); + qWarning() << "ControllerScriptHandler: File does not exist:" + << scriptFile.absoluteFilePath(); return false; } m_scriptWatcher.addPath(scriptFile.absoluteFilePath()); - qDebug() << "ControllerEngine: Loading" << scriptFile.absoluteFilePath(); + qDebug() << "ControllerScriptHandler: Loading" + << scriptFile.absoluteFilePath(); // Read in the script file QString filename = scriptFile.absoluteFilePath(); QFile input(filename); if (!input.open(QIODevice::ReadOnly)) { - qWarning() << QString("ControllerEngine: Problem opening the script file: %1, error # %2, %3") - .arg(filename, QString::number(input.error()), input.errorString()); + qWarning() << QString( + "ControllerScriptHandler: Problem opening the script file: %1, " + "error # %2, %3") + .arg(filename, + QString::number(input.error()), + input.errorString()); // Set up error dialog ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); props->setType(DLG_WARNING); diff --git a/src/controllers/engine/controllerengine.h b/src/controllers/scripting/controllerscripthandler.h similarity index 91% rename from src/controllers/engine/controllerengine.h rename to src/controllers/scripting/controllerscripthandler.h index 9e0362cf351..fc245be49c2 100644 --- a/src/controllers/engine/controllerengine.h +++ b/src/controllers/scripting/controllerscripthandler.h @@ -14,16 +14,17 @@ #include "util/duration.h" class Controller; -class ControllerEngine; -class ControllerEngineJSProxy; +class ControllerScriptHandler; +class ControllerScriptInterface; class EvaluationException; class ScriptConnection; -class ControllerEngine : public QObject { +/// ControllerScriptHandler loads and executes script files for controller mappings +class ControllerScriptHandler : public QObject { Q_OBJECT public: - ControllerEngine(Controller* controller); - virtual ~ControllerEngine(); + ControllerScriptHandler(Controller* controller); + virtual ~ControllerScriptHandler(); void handleInput(QByteArray data, mixxx::Duration timestamp); @@ -102,7 +103,7 @@ class ControllerEngine : public QObject { bool m_bTesting; friend class ScriptConnection; - friend class ControllerEngineJSProxy; + friend class ControllerScriptInterface; friend class ColorJSProxy; friend class ColorMapperJSProxy; friend class ControllerEngineTest; diff --git a/src/controllers/engine/controllerenginejsproxy.cpp b/src/controllers/scripting/legacy/controllerscriptinterface.cpp similarity index 89% rename from src/controllers/engine/controllerenginejsproxy.cpp rename to src/controllers/scripting/legacy/controllerscriptinterface.cpp index f821e3ad9f2..d29cd89991f 100644 --- a/src/controllers/engine/controllerenginejsproxy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterface.cpp @@ -1,10 +1,10 @@ -#include "controllerenginejsproxy.h" +#include "controllerscriptinterface.h" #include "control/controlobject.h" #include "control/controlobjectscript.h" #include "controllers/controllerdebug.h" -#include "controllers/engine/controllerengine.h" -#include "controllers/engine/scriptconnectionjsproxy.h" +#include "controllers/scripting/controllerscripthandler.h" +#include "controllers/scripting/legacy/scriptconnectionjsproxy.h" #include "mixer/playermanager.h" #include "util/math.h" #include "util/time.h" @@ -18,7 +18,7 @@ const int kScratchTimerMs = 1; const double kAlphaBetaDt = kScratchTimerMs / 1000.0; } // anonymous namespace -ControllerEngineJSProxy::ControllerEngineJSProxy(ControllerEngine* m_pEngine) +ControllerScriptInterface::ControllerScriptInterface(ControllerScriptHandler* m_pEngine) : m_pEngine(m_pEngine) { // Pre-allocate arrays for average number of virtual decks m_intervalAccumulator.resize(kDecks); @@ -38,7 +38,7 @@ ControllerEngineJSProxy::ControllerEngineJSProxy(ControllerEngine* m_pEngine) } } -ControllerEngineJSProxy::~ControllerEngineJSProxy() { +ControllerScriptInterface::~ControllerScriptInterface() { // Stop all timers QMutableHashIterator i(m_timers); while (i.hasNext()) { @@ -81,7 +81,7 @@ ControllerEngineJSProxy::~ControllerEngineJSProxy() { } } -ControlObjectScript* ControllerEngineJSProxy::getControlObjectScript( +ControlObjectScript* ControllerScriptInterface::getControlObjectScript( const QString& group, const QString& name) { ConfigKey key = ConfigKey(group, name); ControlObjectScript* coScript = m_controlCache.value(key, nullptr); @@ -98,20 +98,20 @@ ControlObjectScript* ControllerEngineJSProxy::getControlObjectScript( return coScript; } -double ControllerEngineJSProxy::getValue(QString group, QString name) { +double ControllerScriptInterface::getValue(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name + qWarning() << "ControllerScriptInterface: Unknown control" << group << name << ", returning 0.0"; return 0.0; } return coScript->get(); } -void ControllerEngineJSProxy::setValue( +void ControllerScriptInterface::setValue( QString group, QString name, double newValue) { if (isnan(newValue)) { - qWarning() << "ControllerEngine: script setting [" << group << "," + qWarning() << "ControllerScriptInterface: script setting [" << group << "," << name << "] to NotANumber, ignoring."; return; } @@ -129,20 +129,20 @@ void ControllerEngineJSProxy::setValue( } } -double ControllerEngineJSProxy::getParameter(QString group, QString name) { +double ControllerScriptInterface::getParameter(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name + qWarning() << "ControllerScriptInterface: Unknown control" << group << name << ", returning 0.0"; return 0.0; } return coScript->getParameter(); } -void ControllerEngineJSProxy::setParameter( +void ControllerScriptInterface::setParameter( QString group, QString name, double newParameter) { if (isnan(newParameter)) { - qWarning() << "ControllerEngine: script setting [" << group << "," + qWarning() << "ControllerScriptInterface: script setting [" << group << "," << name << "] to NotANumber, ignoring."; return; } @@ -158,10 +158,10 @@ void ControllerEngineJSProxy::setParameter( } } -double ControllerEngineJSProxy::getParameterForValue( +double ControllerScriptInterface::getParameterForValue( QString group, QString name, double value) { if (isnan(value)) { - qWarning() << "ControllerEngine: script setting [" << group << "," + qWarning() << "ControllerScriptInterface: script setting [" << group << "," << name << "] to NotANumber, ignoring."; return 0.0; } @@ -169,7 +169,7 @@ double ControllerEngineJSProxy::getParameterForValue( ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name + qWarning() << "ControllerScriptInterface: Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -177,18 +177,18 @@ double ControllerEngineJSProxy::getParameterForValue( return coScript->getParameterForValue(value); } -void ControllerEngineJSProxy::reset(QString group, QString name) { +void ControllerScriptInterface::reset(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript != nullptr) { coScript->reset(); } } -double ControllerEngineJSProxy::getDefaultValue(QString group, QString name) { +double ControllerScriptInterface::getDefaultValue(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name + qWarning() << "ControllerScriptInterface: Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -196,12 +196,12 @@ double ControllerEngineJSProxy::getDefaultValue(QString group, QString name) { return coScript->getDefault(); } -double ControllerEngineJSProxy::getDefaultParameter( +double ControllerScriptInterface::getDefaultParameter( QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name + qWarning() << "ControllerScriptInterface: Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -209,7 +209,7 @@ double ControllerEngineJSProxy::getDefaultParameter( return coScript->getParameterForValue(coScript->getDefault()); } -QJSValue ControllerEngineJSProxy::makeConnection( +QJSValue ControllerScriptInterface::makeConnection( QString group, QString name, const QJSValue callback) { QJSEngine* pScriptEngine = m_pEngine->scriptEngine(); VERIFY_OR_DEBUG_ASSERT(pScriptEngine) { @@ -222,7 +222,7 @@ QJSValue ControllerEngineJSProxy::makeConnection( // existing during tests is okay. if (!m_pEngine->isTesting()) { m_pEngine->throwJSError( - "ControllerEngine: script tried to connect to " + "ControllerScriptInterface: script tried to connect to " "ControlObject (" + group + ", " + name + ") which is non-existent."); } @@ -252,7 +252,7 @@ QJSValue ControllerEngineJSProxy::makeConnection( return QJSValue(); } -bool ControllerEngineJSProxy::removeScriptConnection( +bool ControllerScriptInterface::removeScriptConnection( const ScriptConnection connection) { ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); @@ -264,7 +264,7 @@ bool ControllerEngineJSProxy::removeScriptConnection( return coScript->removeScriptConnection(connection); } -void ControllerEngineJSProxy::triggerScriptConnection( +void ControllerScriptInterface::triggerScriptConnection( const ScriptConnection connection) { VERIFY_OR_DEBUG_ASSERT(m_pEngine->scriptEngine()) { return; @@ -286,8 +286,8 @@ void ControllerEngineJSProxy::triggerScriptConnection( // are removed. If a ScriptConnectionInvokableWrapper is passed instead of a callback, // it is disconnected. // WARNING: These behaviors are quirky and confusing, so if you change this function, -// be sure to run the ControllerEngineTest suite to make sure you do not break old scripts. -QJSValue ControllerEngineJSProxy::connectControl( +// be sure to run the ControllerScriptInterfaceTest suite to make sure you do not break old scripts. +QJSValue ControllerScriptInterface::connectControl( QString group, QString name, QJSValue passedCallback, bool disconnect) { // The passedCallback may or may not actually be a function, so when // the actual callback function is found, store it in this variable. @@ -312,12 +312,12 @@ QJSValue ControllerEngineJSProxy::connectControl( if (!m_pEngine->isTesting()) { if (disconnect) { m_pEngine->throwJSError( - "ControllerEngine: script tried to disconnect from " + "ControllerScriptInterface: script tried to disconnect from " "ControlObject (" + group + ", " + name + ") which is non-existent."); } else { m_pEngine->throwJSError( - "ControllerEngine: script tried to connect to " + "ControllerScriptInterface: script tried to connect to " "ControlObject (" + group + ", " + name + ") which is non-existent."); } @@ -403,17 +403,17 @@ QJSValue ControllerEngineJSProxy::connectControl( return makeConnection(group, name, actualCallbackFunction); } -void ControllerEngineJSProxy::trigger(QString group, QString name) { +void ControllerScriptInterface::trigger(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript != nullptr) { coScript->emitValueChanged(); } } -void ControllerEngineJSProxy::log(QString message) { +void ControllerScriptInterface::log(QString message) { controllerDebug(message); } -int ControllerEngineJSProxy::beginTimer( +int ControllerScriptInterface::beginTimer( int intervalMillis, QJSValue timerCallback, bool oneShot) { if (timerCallback.isString()) { timerCallback = m_pEngine->evaluateCodeString(timerCallback.toString()); @@ -453,7 +453,7 @@ int ControllerEngineJSProxy::beginTimer( return timerId; } -void ControllerEngineJSProxy::stopTimer(int timerId) { +void ControllerScriptInterface::stopTimer(int timerId) { if (!m_timers.contains(timerId)) { qWarning() << "Killing timer" << timerId << ": That timer does not exist!"; @@ -464,7 +464,7 @@ void ControllerEngineJSProxy::stopTimer(int timerId) { m_timers.remove(timerId); } -void ControllerEngineJSProxy::timerEvent(QTimerEvent* event) { +void ControllerScriptInterface::timerEvent(QTimerEvent* event) { int timerId = event->timerId(); // See if this is a scratching timer @@ -492,7 +492,7 @@ void ControllerEngineJSProxy::timerEvent(QTimerEvent* event) { m_pEngine->executeFunction(timerTarget.callback, QJSValueList()); } -void ControllerEngineJSProxy::softTakeover( +void ControllerScriptInterface::softTakeover( QString group, QString name, bool set) { ControlObject* pControl = ControlObject::getControl( ConfigKey(group, name), ControlFlag::NoAssertIfMissing); @@ -506,7 +506,7 @@ void ControllerEngineJSProxy::softTakeover( } } -void ControllerEngineJSProxy::softTakeoverIgnoreNextValue( +void ControllerScriptInterface::softTakeoverIgnoreNextValue( QString group, const QString name) { ControlObject* pControl = ControlObject::getControl( ConfigKey(group, name), ControlFlag::NoAssertIfMissing); @@ -517,7 +517,7 @@ void ControllerEngineJSProxy::softTakeoverIgnoreNextValue( m_st.ignoreNext(pControl); } -double ControllerEngineJSProxy::getDeckRate(const QString& group) { +double ControllerScriptInterface::getDeckRate(const QString& group) { double rate = 0.0; ControlObjectScript* pRateRatio = getControlObjectScript(group, "rate_ratio"); @@ -533,7 +533,7 @@ double ControllerEngineJSProxy::getDeckRate(const QString& group) { return rate; } -bool ControllerEngineJSProxy::isDeckPlaying(const QString& group) { +bool ControllerScriptInterface::isDeckPlaying(const QString& group) { ControlObjectScript* pPlay = getControlObjectScript(group, "play"); if (pPlay == nullptr) { @@ -545,7 +545,7 @@ bool ControllerEngineJSProxy::isDeckPlaying(const QString& group) { return pPlay->get() > 0.0; } -void ControllerEngineJSProxy::scratchEnable(int deck, +void ControllerScriptInterface::scratchEnable(int deck, int intervalsPerRev, double rpm, double alpha, @@ -621,12 +621,12 @@ void ControllerEngineJSProxy::scratchEnable(int deck, } } -void ControllerEngineJSProxy::scratchTick(int deck, int interval) { +void ControllerScriptInterface::scratchTick(int deck, int interval) { m_lastMovement[deck] = mixxx::Time::elapsed(); m_intervalAccumulator[deck] += interval; } -void ControllerEngineJSProxy::scratchProcess(int timerId) { +void ControllerScriptInterface::scratchProcess(int timerId) { int deck = m_scratchTimers[timerId]; // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -709,7 +709,7 @@ void ControllerEngineJSProxy::scratchProcess(int timerId) { } } -void ControllerEngineJSProxy::scratchDisable(int deck, bool ramp) { +void ControllerScriptInterface::scratchDisable(int deck, bool ramp) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -733,18 +733,18 @@ void ControllerEngineJSProxy::scratchDisable(int deck, bool ramp) { m_ramp[deck] = true; // Activate the ramping in scratchProcess() } -bool ControllerEngineJSProxy::isScratching(int deck) { +bool ControllerScriptInterface::isScratching(int deck) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); return getValue(group, "scratch2_enable") > 0; } -void ControllerEngineJSProxy::spinback(int deck, bool activate, double factor, double rate) { +void ControllerScriptInterface::spinback(int deck, bool activate, double factor, double rate) { // defaults for args set in header file brake(deck, activate, factor, rate); } -void ControllerEngineJSProxy::brake(int deck, bool activate, double factor, double rate) { +void ControllerScriptInterface::brake(int deck, bool activate, double factor, double rate) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -807,7 +807,7 @@ void ControllerEngineJSProxy::brake(int deck, bool activate, double factor, doub } } -void ControllerEngineJSProxy::softStart(int deck, bool activate, double factor) { +void ControllerScriptInterface::softStart(int deck, bool activate, double factor) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); diff --git a/src/controllers/engine/controllerenginejsproxy.h b/src/controllers/scripting/legacy/controllerscriptinterface.h similarity index 79% rename from src/controllers/engine/controllerenginejsproxy.h rename to src/controllers/scripting/legacy/controllerscriptinterface.h index e6135057f80..964761b96b8 100644 --- a/src/controllers/engine/controllerenginejsproxy.h +++ b/src/controllers/scripting/legacy/controllerscriptinterface.h @@ -6,25 +6,19 @@ #include "controllers/softtakeover.h" #include "util/alphabetafilter.h" -class ControllerEngine; +class ControllerScriptHandler; class ControlObjectScript; class ScriptConnection; class ConfigKey; -// An object of this class gets exposed to the JS engine, so the methods of this class -// constitute the api that is provided to scripts under "engine" object. -// -// The implementation simply forwards its method calls to the ControllerEngine. -// We cannot expose ControllerEngine directly to the JS engine because the JS engine would take -// ownership of ControllerEngine. This is problematic when we reload a script file, because we -// destroy the existing JS engine to create a new one. Then, since the JS engine owns ControllerEngine -// it will try to delete it. See this Qt bug: https://bugreports.qt.io/browse/QTBUG-41171 -class ControllerEngineJSProxy : public QObject { +/// ControllerScriptInterface is the legacy API for controller scripts to interact +/// with Mixxx. It is inserted into the JS environment as the "engine" object. +class ControllerScriptInterface : public QObject { Q_OBJECT public: - ControllerEngineJSProxy(ControllerEngine* m_pEngine); + ControllerScriptInterface(ControllerScriptHandler* m_pEngine); - virtual ~ControllerEngineJSProxy(); + virtual ~ControllerScriptInterface(); Q_INVOKABLE double getValue(QString group, QString name); Q_INVOKABLE void setValue(QString group, QString name, double newValue); @@ -82,5 +76,5 @@ class ControllerEngineJSProxy : public QObject { bool isDeckPlaying(const QString& group); double getDeckRate(const QString& group); - ControllerEngine* m_pEngine; + ControllerScriptHandler* m_pEngine; }; diff --git a/src/controllers/engine/scriptconnection.cpp b/src/controllers/scripting/legacy/scriptconnection.cpp similarity index 84% rename from src/controllers/engine/scriptconnection.cpp rename to src/controllers/scripting/legacy/scriptconnection.cpp index f059d127e9a..b9970c8e32c 100644 --- a/src/controllers/engine/scriptconnection.cpp +++ b/src/controllers/scripting/legacy/scriptconnection.cpp @@ -1,6 +1,6 @@ -#include "controllers/engine/scriptconnection.h" +#include "controllers/scripting/legacy/scriptconnection.h" -#include "controllers/engine/controllerengine.h" +#include "controllers/scripting/controllerscripthandler.h" void ScriptConnection::executeCallback(double value) const { QJSValueList args; diff --git a/src/controllers/engine/scriptconnection.h b/src/controllers/scripting/legacy/scriptconnection.h similarity index 81% rename from src/controllers/engine/scriptconnection.h rename to src/controllers/scripting/legacy/scriptconnection.h index e665fdecfe7..a072dfcb7db 100644 --- a/src/controllers/engine/scriptconnection.h +++ b/src/controllers/scripting/legacy/scriptconnection.h @@ -5,8 +5,8 @@ #include "preferences/configobject.h" -class ControllerEngine; -class ControllerEngineJSProxy; +class ControllerScriptHandler; +class ControllerScriptInterface; /// ScriptConnection is a connection between a ControlObject and a /// script callback function that gets executed when the value @@ -16,8 +16,8 @@ class ScriptConnection { ConfigKey key; QUuid id; QJSValue callback; - ControllerEngineJSProxy* engineJSProxy; - ControllerEngine* controllerEngine; + ControllerScriptInterface* engineJSProxy; + ControllerScriptHandler* controllerEngine; void executeCallback(double value) const; diff --git a/src/controllers/engine/scriptconnectionjsproxy.cpp b/src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp similarity index 75% rename from src/controllers/engine/scriptconnectionjsproxy.cpp rename to src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp index 5efd01c16f5..9103c917c3f 100644 --- a/src/controllers/engine/scriptconnectionjsproxy.cpp +++ b/src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp @@ -1,6 +1,6 @@ -#include "controllers/engine/scriptconnectionjsproxy.h" +#include "controllers/scripting/legacy/scriptconnectionjsproxy.h" -#include "controllers/engine/controllerenginejsproxy.h" +#include "controllers/scripting/legacy/controllerscriptinterface.h" bool ScriptConnectionJSProxy::disconnect() { // if the removeScriptConnection succeeded, the connection has been successfully disconnected diff --git a/src/controllers/engine/scriptconnectionjsproxy.h b/src/controllers/scripting/legacy/scriptconnectionjsproxy.h similarity index 92% rename from src/controllers/engine/scriptconnectionjsproxy.h rename to src/controllers/scripting/legacy/scriptconnectionjsproxy.h index 4c3877ef836..d492c92ec84 100644 --- a/src/controllers/engine/scriptconnectionjsproxy.h +++ b/src/controllers/scripting/legacy/scriptconnectionjsproxy.h @@ -2,7 +2,7 @@ #include -#include "controllers/engine/scriptconnection.h" +#include "controllers/scripting/legacy/scriptconnection.h" /// ScriptConnectionJSProxy provides scripts with an interface to ScriptConnection. class ScriptConnectionJSProxy : public QObject { diff --git a/src/test/colormapperjsproxy_test.cpp b/src/test/colormapperjsproxy_test.cpp index f6a9e29bf5f..753f6fe79c5 100644 --- a/src/test/colormapperjsproxy_test.cpp +++ b/src/test/colormapperjsproxy_test.cpp @@ -1,4 +1,4 @@ -#include "controllers/engine/colormapperjsproxy.h" +#include "controllers/scripting/colormapperjsproxy.h" #include diff --git a/src/test/controller_preset_validation_test.cpp b/src/test/controller_preset_validation_test.cpp index 8ce2be2bce6..7e09e6756b4 100644 --- a/src/test/controller_preset_validation_test.cpp +++ b/src/test/controller_preset_validation_test.cpp @@ -28,7 +28,7 @@ FakeController::FakeController() : m_bMidiPreset(false), m_bHidPreset(false) { startEngine(); - getEngine()->setTesting(true); + getScriptHandler()->setTesting(true); } FakeController::~FakeController() { diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index 3e87d5af1a4..964827754eb 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -4,7 +4,7 @@ #include "control/controlobject.h" #include "control/controlpotmeter.h" #include "controllers/controllerdebug.h" -#include "controllers/engine/controllerengine.h" +#include "controllers/scripting/controllerscripthandler.h" #include "controllers/softtakeover.h" #include "preferences/usersettings.h" #include "test/mixxxtest.h" @@ -18,7 +18,7 @@ class ControllerEngineTest : public MixxxTest { mixxx::Time::setTestMode(true); mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); QThread::currentThread()->setObjectName("Main"); - cEngine = new ControllerEngine(nullptr); + cEngine = new ControllerScriptHandler(nullptr); ControllerDebug::enable(); } @@ -40,7 +40,7 @@ class ControllerEngineTest : public MixxxTest { return !cEngine->evaluateCodeString(code).isError(); } - ControllerEngine *cEngine; + ControllerScriptHandler* cEngine; }; TEST_F(ControllerEngineTest, commonScriptHasNoErrors) { From 24408abfb991251b29c0d9e680eb403e2f6b06c1 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 6 Jul 2020 02:33:56 -0500 Subject: [PATCH 03/61] controllers: clean up some #includes --- src/controllers/bulk/bulkcontroller.h | 13 ++--------- src/controllers/controller.h | 22 ++++++------------- src/controllers/controllermanager.cpp | 16 +++++--------- src/controllers/controllermanager.h | 15 +++---------- .../scripting/controllerscripthandler.h | 6 ----- .../controller_preset_validation_test.cpp | 2 ++ src/util/translations.h | 9 +++----- 7 files changed, 22 insertions(+), 61 deletions(-) diff --git a/src/controllers/bulk/bulkcontroller.h b/src/controllers/bulk/bulkcontroller.h index 24e02a598d9..b51b8c29c32 100644 --- a/src/controllers/bulk/bulkcontroller.h +++ b/src/controllers/bulk/bulkcontroller.h @@ -1,14 +1,7 @@ -/** - * @file bulkcontroller.h - * @author Neale Picket neale@woozle.org - * @date Thu Jun 28 2012 - * @brief USB Bulk controller backend - */ - -#ifndef BULKCONTROLLER_H -#define BULKCONTROLLER_H +#pragma once #include +#include #include "controllers/controller.h" #include "controllers/hid/hidcontrollerpreset.h" @@ -107,5 +100,3 @@ class BulkController : public Controller { BulkReader* m_pReader; HidControllerPreset m_preset; }; - -#endif diff --git a/src/controllers/controller.h b/src/controllers/controller.h index ebb2bbf2c01..d232af6637b 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -1,16 +1,7 @@ -/** -* @file controller.h -* @author Sean Pappalardo spappalardo@mixxx.org -* @date Sat Apr 30 2011 -* @brief Base class representing a physical (or software) controller. -* -* This is a base class representing a physical (or software) controller. It -* must be inherited by a class that implements it on some API. Note that the -* subclass' destructor should call close() at a minimum. -*/ - -#ifndef CONTROLLER_H -#define CONTROLLER_H +#pragma once + +#include +#include #include "controllers/controllerpreset.h" #include "controllers/controllerpresetfilehandler.h" @@ -22,6 +13,9 @@ class ControllerJSProxy; +/// This is a base class representing a physical (or software) controller. It +/// must be inherited by a class that implements it on some API. Note that the +/// subclass' destructor should call close() at a minimum. class Controller : public QObject, ConstControllerPresetVisitor { Q_OBJECT public: @@ -203,5 +197,3 @@ class ControllerJSProxy : public QObject { private: Controller* const m_pController; }; - -#endif diff --git a/src/controllers/controllermanager.cpp b/src/controllers/controllermanager.cpp index cc2d475b786..0290f5e6fab 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -1,20 +1,14 @@ -/** - * @file controllermanager.cpp - * @author Sean Pappalardo spappalardo@mixxx.org - * @date Sat Apr 30 2011 - * @brief Manages creation/enumeration/deletion of hardware controllers. - */ +#include "controllers/controllermanager.h" #include +#include -#include "util/trace.h" -#include "controllers/controllermanager.h" -#include "controllers/defs_controllers.h" #include "controllers/controllerlearningeventfilter.h" +#include "controllers/defs_controllers.h" +#include "controllers/midi/portmidienumerator.h" #include "util/cmdlineargs.h" #include "util/time.h" - -#include "controllers/midi/portmidienumerator.h" +#include "util/trace.h" #ifdef __HSS1394__ #include "controllers/midi/hss1394enumerator.h" #endif diff --git a/src/controllers/controllermanager.h b/src/controllers/controllermanager.h index 8837d903580..897b9947fdc 100644 --- a/src/controllers/controllermanager.h +++ b/src/controllers/controllermanager.h @@ -1,14 +1,7 @@ -/** - * @file controllermanager.h - * @author Sean Pappalardo spappalardo@mixxx.org - * @date Sat Apr 30 2011 - * @brief Manages creation/enumeration/deletion of hardware controllers. - */ - -#ifndef CONTROLLERMANAGER_H -#define CONTROLLERMANAGER_H +#pragma once #include +#include #include "controllers/controllerenumerator.h" #include "controllers/controllerpreset.h" @@ -23,7 +16,7 @@ class ControllerLearningEventFilter; // Function to sort controllers by name bool controllerCompare(Controller *a, Controller *b); -/** Manages enumeration/operation/deletion of hardware controllers. */ +/// Manages enumeration/operation/deletion of hardware controllers. class ControllerManager : public QObject { Q_OBJECT public: @@ -86,5 +79,3 @@ class ControllerManager : public QObject { QSharedPointer m_pMainThreadSystemPresetEnumerator; bool m_skipPoll; }; - -#endif // CONTROLLERMANAGER_H diff --git a/src/controllers/scripting/controllerscripthandler.h b/src/controllers/scripting/controllerscripthandler.h index fc245be49c2..0d117d1623d 100644 --- a/src/controllers/scripting/controllerscripthandler.h +++ b/src/controllers/scripting/controllerscripthandler.h @@ -4,17 +4,11 @@ #include #include #include -#include -#include #include "controllers/controllerpreset.h" -#include "controllers/softtakeover.h" -#include "preferences/usersettings.h" -#include "util/alphabetafilter.h" #include "util/duration.h" class Controller; -class ControllerScriptHandler; class ControllerScriptInterface; class EvaluationException; class ScriptConnection; diff --git a/src/test/controller_preset_validation_test.cpp b/src/test/controller_preset_validation_test.cpp index 7e09e6756b4..501624efac4 100644 --- a/src/test/controller_preset_validation_test.cpp +++ b/src/test/controller_preset_validation_test.cpp @@ -1,5 +1,7 @@ #include "test/controller_preset_validation_test.h" +#include + #include "controllers/defs_controllers.h" FakeControllerJSProxy::FakeControllerJSProxy() diff --git a/src/util/translations.h b/src/util/translations.h index 7597e0e675f..ece8ce2aac0 100644 --- a/src/util/translations.h +++ b/src/util/translations.h @@ -1,10 +1,10 @@ -#ifndef MIXXX_UTIL_TRANSLATIONS_H -#define MIXXX_UTIL_TRANSLATIONS_H +#pragma once #include +#include #include -#include #include +#include #include #include "preferences/usersettings.h" @@ -100,6 +100,3 @@ class Translations { }; } // namespace mixxx - - -#endif /* MIXXX_UTIL_TRANSLATIONS_H */ From 979a243aa0934a3c9de320b65f08db79dcb07250 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 6 Jul 2020 14:15:22 -0500 Subject: [PATCH 04/61] split ControllerScriptHandler into base and subclasses This allows for a clean separation between new and legacy code. --- CMakeLists.txt | 4 +- build/depends.py | 4 +- src/controllers/controller.cpp | 34 +- src/controllers/controller.h | 8 +- src/controllers/midi/midicontroller.cpp | 20 +- .../scripting/controllerscriptenginebase.cpp | 251 ++++++++ .../scripting/controllerscriptenginebase.h | 68 +++ .../scripting/controllerscripthandler.cpp | 539 ------------------ .../scripting/controllerscripthandler.h | 104 ---- .../controllerscriptmoduleengine.cpp | 56 ++ .../scripting/controllerscriptmoduleengine.h | 26 + .../legacy/controllerscriptenginelegacy.cpp | 198 +++++++ .../legacy/controllerscriptenginelegacy.h | 69 +++ .../legacy/controllerscriptinterface.cpp | 44 +- .../legacy/controllerscriptinterface.h | 6 +- .../scripting/legacy/scriptconnection.cpp | 2 +- .../scripting/legacy/scriptconnection.h | 4 +- .../controller_preset_validation_test.cpp | 2 +- src/test/controllerengine_test.cpp | 12 +- 19 files changed, 731 insertions(+), 720 deletions(-) create mode 100644 src/controllers/scripting/controllerscriptenginebase.cpp create mode 100644 src/controllers/scripting/controllerscriptenginebase.h delete mode 100644 src/controllers/scripting/controllerscripthandler.cpp delete mode 100644 src/controllers/scripting/controllerscripthandler.h create mode 100644 src/controllers/scripting/controllerscriptmoduleengine.cpp create mode 100644 src/controllers/scripting/controllerscriptmoduleengine.h create mode 100644 src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp create mode 100644 src/controllers/scripting/legacy/controllerscriptenginelegacy.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e803b15911..a4a87341157 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -374,9 +374,11 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/dlgprefcontrollerdlg.ui src/controllers/dlgprefcontrollers.cpp src/controllers/dlgprefcontrollersdlg.ui - src/controllers/scripting/controllerscripthandler.cpp + src/controllers/scripting/controllerscriptenginebase.cpp + src/controllers/scripting/controllerscriptmoduleengine.cpp src/controllers/scripting/colormapper.cpp src/controllers/scripting/colormapperjsproxy.cpp + src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp src/controllers/scripting/legacy/controllerscriptinterface.cpp src/controllers/scripting/legacy/scriptconnection.cpp src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp diff --git a/build/depends.py b/build/depends.py index a9c3029f967..efae7d1297a 100644 --- a/build/depends.py +++ b/build/depends.py @@ -922,9 +922,11 @@ def sources(self, build): "src/controllers/delegates/midibytedelegate.cpp", "src/controllers/delegates/midioptionsdelegate.cpp", "src/controllers/learningutils.cpp", - "src/controllers/scripting/controllerscripthandler.cpp", + "src/controllers/scripting/controllerscriptenginebase.cpp", + "src/controllers/scripting/controllerscriptmoduleengine.cpp", "src/controllers/scripting/colormapper.cpp", "src/controllers/scripting/colormapperjsproxy.cpp", + "src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp", "src/controllers/scripting/legacy/controllerscriptinterface.cpp", "src/controllers/scripting/legacy/scriptconnection.cpp", "src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp", diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 02e6e1e778c..f43400a5ffc 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -14,7 +14,7 @@ #include "util/screensaver.h" Controller::Controller() - : m_pScriptHandler(nullptr), + : m_pScriptEngineLegacy(nullptr), m_bIsOutputDevice(false), m_bIsInputDevice(false), m_bIsOpen(false), @@ -34,22 +34,21 @@ ControllerJSProxy* Controller::jsProxy() { void Controller::startEngine() { controllerDebug(" Starting engine"); - if (m_pScriptHandler != NULL) { + if (m_pScriptEngineLegacy != NULL) { qWarning() << "Controller: Engine already exists! Restarting:"; stopEngine(); } - m_pScriptHandler = new ControllerScriptHandler(this); + m_pScriptEngineLegacy = new ControllerScriptEngineLegacy(this); } void Controller::stopEngine() { controllerDebug(" Shutting down engine"); - if (m_pScriptHandler == NULL) { + if (m_pScriptEngineLegacy == nullptr) { qWarning() << "Controller::stopEngine(): No engine exists!"; return; } - m_pScriptHandler->gracefulShutdown(); - delete m_pScriptHandler; - m_pScriptHandler = NULL; + delete m_pScriptEngineLegacy; + m_pScriptEngineLegacy = nullptr; } bool Controller::applyPreset(bool initializeScripts) { @@ -58,7 +57,7 @@ bool Controller::applyPreset(bool initializeScripts) { const ControllerPreset* pPreset = preset(); // Load the script code into the engine - if (m_pScriptHandler == NULL) { + if (m_pScriptEngineLegacy == NULL) { qWarning() << "Controller::applyPreset(): No engine exists!"; return false; } @@ -69,14 +68,13 @@ bool Controller::applyPreset(bool initializeScripts) { return true; } - bool success = m_pScriptHandler->loadScriptFiles(scriptFiles); - if (success && initializeScripts) { - m_pScriptHandler->initializeScripts(scriptFiles); - } + bool success = true; + m_pScriptEngineLegacy->setScriptFiles(scriptFiles); if (initializeScripts) { - m_pScriptHandler->loadModule(pPreset->moduleFileInfo()); + success = m_pScriptEngineLegacy->initialize(); } + return success; } @@ -114,7 +112,7 @@ void Controller::triggerActivity() } } void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { - if (m_pScriptHandler == NULL) { + if (m_pScriptEngineLegacy == NULL) { //qWarning() << "Controller::receive called with no active engine!"; // Don't complain, since this will always show after closing a device as // queued signals flush out @@ -138,14 +136,12 @@ void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { controllerDebug(message); } - foreach (QString function, m_pScriptHandler->getScriptFunctionPrefixes()) { + foreach (QString function, m_pScriptEngineLegacy->getScriptFunctionPrefixes()) { if (function == "") { continue; } function.append(".incomingData"); - QJSValue incomingDataFunction = m_pScriptHandler->wrapFunctionCode(function, 2); - m_pScriptHandler->executeFunction(incomingDataFunction, data); + QJSValue incomingDataFunction = m_pScriptEngineLegacy->wrapFunctionCode(function, 2); + m_pScriptEngineLegacy->executeIncomingDataFunction(incomingDataFunction, data); } - - m_pScriptHandler->handleInput(data, timestamp); } diff --git a/src/controllers/controller.h b/src/controllers/controller.h index d232af6637b..6000a4b07f7 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -8,7 +8,7 @@ #include "controllers/controllerpresetinfo.h" #include "controllers/controllerpresetvisitor.h" #include "controllers/controllervisitor.h" -#include "controllers/scripting/controllerscripthandler.h" +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "util/duration.h" class ControllerJSProxy; @@ -116,8 +116,8 @@ class Controller : public QObject, ConstControllerPresetVisitor { // To be called when receiving events void triggerActivity(); - inline ControllerScriptHandler* getScriptHandler() const { - return m_pScriptHandler; + inline ControllerScriptEngineLegacy* getScriptEngine() const { + return m_pScriptEngineLegacy; } inline void setDeviceName(QString deviceName) { m_sDeviceName = deviceName; @@ -154,7 +154,7 @@ class Controller : public QObject, ConstControllerPresetVisitor { // Returns a pointer to the currently loaded controller preset. For internal // use only. virtual ControllerPreset* preset() = 0; - ControllerScriptHandler* m_pScriptHandler; + ControllerScriptEngineLegacy* m_pScriptEngineLegacy; // Verbose and unique device name suitable for display. QString m_sDeviceName; diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index 9485eb5a34b..c893dd9f227 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -202,18 +202,6 @@ void MidiController::commitTemporaryInputMappings() { void MidiController::receive(unsigned char status, unsigned char control, unsigned char value, mixxx::Duration timestamp) { - QByteArray byteArray; - byteArray.append(status); - byteArray.append(control); - byteArray.append(value); - - ControllerScriptHandler* pEngine = getScriptHandler(); - // pEngine is nullptr in tests. - if (pEngine) { - pEngine->handleInput(byteArray, timestamp); - } - // legacy stuff below - // The rest of this function is for legacy mappings unsigned char channel = MidiUtils::channelFromStatus(status); unsigned char opCode = MidiUtils::opCodeFromStatus(status); @@ -261,7 +249,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, unsigned char opCode = MidiUtils::opCodeFromStatus(status); if (mapping.options.script) { - ControllerScriptHandler* pEngine = getScriptHandler(); + ControllerScriptEngineLegacy* pEngine = getScriptEngine(); if (pEngine == NULL) { return; } @@ -482,8 +470,6 @@ double MidiController::computeValue( void MidiController::receive(QByteArray data, mixxx::Duration timestamp) { controllerDebug(MidiUtils::formatSysexMessage(getName(), data, timestamp)); - getScriptHandler()->handleInput(data, timestamp); - // legacy stuff below MidiKey mappingKey(data.at(0), 0xFF); @@ -514,12 +500,12 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, mixxx::Duration timestamp) { // Custom script handler if (mapping.options.script) { - ControllerScriptHandler* pEngine = getScriptHandler(); + ControllerScriptEngineLegacy* pEngine = getScriptEngine(); if (pEngine == NULL) { return; } QJSValue function = pEngine->wrapFunctionCode(mapping.control.item, 2); - if (!pEngine->executeFunction(function, data)) { + if (!pEngine->executeIncomingDataFunction(function, data)) { qDebug() << "MidiController: Invalid script function" << mapping.control.item; } diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp new file mode 100644 index 00000000000..8cc1f90a8d4 --- /dev/null +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -0,0 +1,251 @@ +#include "controllers/scripting/controllerscriptenginebase.h" + +#include "control/controlobject.h" +#include "controllers/controller.h" +#include "controllers/controllerdebug.h" +#include "controllers/scripting/colormapperjsproxy.h" +#include "controllers/scripting/legacy/controllerscriptinterface.h" +#include "errordialoghandler.h" +#include "mixer/playermanager.h" + +ControllerScriptEngineBase::ControllerScriptEngineBase(Controller* controller) + : m_bDisplayingExceptionDialog(false), + m_pJSEngine(nullptr), + m_pController(controller), + m_bTesting(false) { + // Handle error dialog buttons + qRegisterMetaType("QMessageBox::StandardButton"); + + // Subclasses are responsible for adding paths to watch to m_scriptWatcher + // in ther initializeScriptEngine method. + connect(&m_scriptWatcher, + &QFileSystemWatcher::fileChanged, + this, + &ControllerScriptEngineBase::reload); +} + +ControllerScriptEngineBase::~ControllerScriptEngineBase() { + shutdown(); +} + +bool ControllerScriptEngineBase::initialize() { + VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine) { + return false; + } + + // Create the Script Engine + m_pJSEngine = new QJSEngine(this); + + // Make this ControllerScriptHandlerBase instance available to scripts as 'engine'. + QJSValue engineGlobalObject = m_pJSEngine->globalObject(); + + QJSValue mapper = m_pJSEngine->newQMetaObject( + &ColorMapperJSProxy::staticMetaObject); + engineGlobalObject.setProperty("ColorMapper", mapper); + + if (m_pController) { + qDebug() << "Controller in script engine is:" + << m_pController->getName(); + + ControllerJSProxy* controllerProxy = m_pController->jsProxy(); + + // Make the Controller instance available to scripts + engineGlobalObject.setProperty( + "controller", m_pJSEngine->newQObject(controllerProxy)); + + // ...under the legacy name as well + engineGlobalObject.setProperty( + "midi", m_pJSEngine->newQObject(controllerProxy)); + } + + m_byteArrayToScriptValueJSFunction = m_pJSEngine->evaluate( + "(function(arg1) { return new Uint8Array(arg1) })"); + + return true; +} + +void ControllerScriptEngineBase::shutdown() { + m_scriptWatcher.removePaths(m_scriptWatcher.directories()); + + // Delete the script engine, first clearing the pointer so that + // other threads will not get the dead pointer after we delete it. + if (m_pJSEngine != nullptr) { + QJSEngine* engine = m_pJSEngine; + m_pJSEngine = nullptr; + engine->deleteLater(); + } +} + +void ControllerScriptEngineBase::reload() { + shutdown(); + initialize(); +} + +bool ControllerScriptEngineBase::executeFunction( + QJSValue functionObject, QJSValueList args) { + // This function is called from outside the controller engine, so we can't + // use VERIFY_OR_DEBUG_ASSERT here + if (!m_pJSEngine) { + return false; + } + + if (functionObject.isError()) { + qDebug() << "ControllerScriptHandlerBase::executeFunction:" + << functionObject.toString(); + return false; + } + + // If it's not a function, we're done. + if (!functionObject.isCallable()) { + qDebug() << "ControllerScriptHandlerBase::executeFunction:" + << functionObject.toVariant() << "Not a function"; + return false; + } + + // If it does happen to be a function, call it. + QJSValue returnValue = functionObject.call(args); + if (returnValue.isError()) { + showScriptExceptionDialog(returnValue); + return false; + } + return true; +} + +void ControllerScriptEngineBase::showScriptExceptionDialog( + QJSValue evaluationResult, bool bFatalError) { + VERIFY_OR_DEBUG_ASSERT(evaluationResult.isError()) { + return; + } + + QString errorMessage = evaluationResult.toString(); + QString line = evaluationResult.property("lineNumber").toString(); + QString backtrace = evaluationResult.property("stack").toString(); + QString filename = evaluationResult.property("fileName").toString(); + + QString errorText; + if (filename.isEmpty()) { + errorText = QString("Uncaught exception at line %1 in passed code.") + .arg(line); + } else { + errorText = QString("Uncaught exception at line %1 in file %2.") + .arg(line, filename); + } + + errorText += QStringLiteral("\n\nException:\n ") + errorMessage; + + // Do not include backtrace in dialog key because it might contain midi + // slider values that will differ most of the time. This would break + // the "Ignore" feature of the error dialog. + QString key = errorText; + qWarning() << "ControllerScriptHandlerBase:" << errorText; + + // Add backtrace to the error details + errorText += QStringLiteral("\n\nBacktrace:\n") + backtrace; + + if (!m_bDisplayingExceptionDialog) { + scriptErrorDialog(errorText, key, bFatalError); + } +} + +void ControllerScriptEngineBase::scriptErrorDialog( + const QString& detailedError, const QString& key, bool bFatalError) { + if (m_bTesting) { + return; + } + + ErrorDialogProperties* props = + ErrorDialogHandler::instance()->newDialogProperties(); + + QString additionalErrorText; + if (bFatalError) { + additionalErrorText = + tr("The functionality provided by this controller mapping will " + "be disabled until the issue has been resolved."); + } else { + additionalErrorText = + tr("You can ignore this error for this session but " + "you may experience erratic behavior.") + + QString("
") + + tr("Try to recover by resetting your controller."); + } + + props->setType(DLG_WARNING); + props->setTitle(tr("Controller Preset Error")); + props->setText(QString(tr("The preset for your controller \"%1\" is not " + "working properly.")) + .arg(m_pController->getName())); + props->setInfoText(QStringLiteral("") + + tr("The script code needs to be fixed.") + QStringLiteral("

") + + additionalErrorText + QStringLiteral("

")); + + // Add "Details" text and set monospace font since they may contain + // backtraces and code. + props->setDetails(detailedError, true); + + // To prevent multiple windows for the same error + props->setKey(key); + + // Allow user to suppress further notifications about this particular error + if (!bFatalError) { + props->addButton(QMessageBox::Ignore); + props->addButton(QMessageBox::Retry); + props->setDefaultButton(QMessageBox::Ignore); + props->setEscapeButton(QMessageBox::Ignore); + } else { + props->addButton(QMessageBox::Close); + props->setDefaultButton(QMessageBox::Close); + props->setEscapeButton(QMessageBox::Close); + } + props->setModal(false); + + if (ErrorDialogHandler::instance()->requestErrorDialog(props)) { + m_bDisplayingExceptionDialog = true; + // Enable custom handling of the dialog buttons + connect(ErrorDialogHandler::instance(), + &ErrorDialogHandler::stdButtonClicked, + this, + &ControllerScriptEngineBase::errorDialogButton); + } +} + +void ControllerScriptEngineBase::errorDialogButton( + const QString& key, QMessageBox::StandardButton clickedButton) { + Q_UNUSED(key); + + m_bDisplayingExceptionDialog = false; + // Something was clicked, so disable this signal now + disconnect(ErrorDialogHandler::instance(), + &ErrorDialogHandler::stdButtonClicked, + this, + &ControllerScriptEngineBase::errorDialogButton); + + if (clickedButton == QMessageBox::Retry) { + reload(); + } +} + +void ControllerScriptEngineBase::throwJSError(const QString& message) { +#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) + QString errorText = tr("Uncaught exception: %1").arg(message); + qWarning() << "ControllerEngine:" << errorText; + if (!m_bDisplayingExceptionDialog) { + scriptErrorDialog(errorText, errorText); + } +#else + m_pJSEngine->throwError(message); +#endif +} + +QJSValue ControllerScriptEngineBase::byteArrayToScriptValue( + const QByteArray& byteArray) { + // The QJSEngine converts the QByteArray to an ArrayBuffer object. + QJSValue arrayBuffer = m_pJSEngine->toScriptValue(byteArray); + // Convert the ArrayBuffer to a Uint8 typed array so scripts can access its bytes + // with the [] operator. + QJSValue result = + m_byteArrayToScriptValueJSFunction.call(QJSValueList{arrayBuffer}); + if (result.isError()) { + showScriptExceptionDialog(result); + } + return result; +} diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h new file mode 100644 index 00000000000..11b5882ae63 --- /dev/null +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include + +#include "controllers/controllerpreset.h" +#include "util/duration.h" + +class Controller; +class EvaluationException; + +/// ControllerScriptEngineBase manages the JavaScript engine for controller scripts. +/// ControllerScriptModuleEngine implements the current system using JS modules. +/// ControllerScriptEngineLegacy implements the legacy hybrid JS/XML system. +class ControllerScriptEngineBase : public QObject { + Q_OBJECT + public: + ControllerScriptEngineBase(Controller* controller); + virtual ~ControllerScriptEngineBase(); + + virtual bool initialize(); + + bool executeFunction(QJSValue functionObject, QJSValueList arguments); + + /// Shows a UI dialog notifying of a script evaluation error. + /// Precondition: QJSValue.isError() == true + void showScriptExceptionDialog(QJSValue evaluationResult, bool bFatal = false); + void throwJSError(const QString& message); + + inline void setTesting(bool testing) { + m_bTesting = testing; + }; + + bool isTesting() { + return m_bTesting; + } + + protected: + virtual void shutdown(); + + void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); + + QJSValue byteArrayToScriptValue(const QByteArray& byteArray); + + bool m_bDisplayingExceptionDialog; + QJSEngine* m_pJSEngine; + + Controller* m_pController; + + // Filesystem watcher for script auto-reload + QFileSystemWatcher m_scriptWatcher; + + bool m_bTesting; + + protected slots: + void reload(); + + private: + QJSValue m_byteArrayToScriptValueJSFunction; + + private slots: + void errorDialogButton(const QString& key, QMessageBox::StandardButton button); + + friend class ColorMapperJSProxy; + friend class ControllerEngineTest; +}; diff --git a/src/controllers/scripting/controllerscripthandler.cpp b/src/controllers/scripting/controllerscripthandler.cpp deleted file mode 100644 index 54bb52ee15d..00000000000 --- a/src/controllers/scripting/controllerscripthandler.cpp +++ /dev/null @@ -1,539 +0,0 @@ -#include "controllers/scripting/controllerscripthandler.h" - -#include "control/controlobject.h" -#include "controllers/controller.h" -#include "controllers/controllerdebug.h" -#include "controllers/scripting/colormapperjsproxy.h" -#include "controllers/scripting/legacy/controllerscriptinterface.h" -#include "errordialoghandler.h" -#include "mixer/playermanager.h" - -ControllerScriptHandler::ControllerScriptHandler(Controller* controller) - : m_bDisplayingExceptionDialog(false), - m_pScriptEngine(nullptr), - m_pController(controller), - m_bTesting(false) { - // Handle error dialog buttons - qRegisterMetaType("QMessageBox::StandardButton"); - - initializeScriptEngine(); -} - -ControllerScriptHandler::~ControllerScriptHandler() { - uninitializeScriptEngine(); -} - -bool ControllerScriptHandler::callFunctionOnObjects(QList scriptFunctionPrefixes, - const QString& function, - QJSValueList args, - bool bFatalError) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return false; - } - - const QJSValue global = m_pScriptEngine->globalObject(); - - bool success = true; - for (const QString& prefixName : scriptFunctionPrefixes) { - QJSValue prefix = global.property(prefixName); - if (!prefix.isObject()) { - qWarning() << "ControllerScriptHandler: No" << prefixName << "object in script"; - continue; - } - - QJSValue init = prefix.property(function); - if (!init.isCallable()) { - qWarning() << "ControllerScriptHandler:" << prefixName << "has no" - << function << " method"; - continue; - } - controllerDebug("ControllerScriptHandler: Executing" - << prefixName << "." << function); - QJSValue result = init.callWithInstance(prefix, args); - if (result.isError()) { - showScriptExceptionDialog(result, bFatalError); - success = false; - } - } - return success; -} - -QJSValue ControllerScriptHandler::byteArrayToScriptValue( - const QByteArray& byteArray) { - // The QJSEngine converts the QByteArray to an ArrayBuffer object. - QJSValue arrayBuffer = m_pScriptEngine->toScriptValue(byteArray); - // Convert the ArrayBuffer to a Uint8 typed array so scripts can access its bytes - // with the [] operator. - QJSValue result = - m_byteArrayToScriptValueJSFunction.call(QJSValueList{arrayBuffer}); - if (result.isError()) { - showScriptExceptionDialog(result); - } - return result; -} - -QJSValue ControllerScriptHandler::wrapFunctionCode( - const QString& codeSnippet, int numberOfArgs) { - // This function is called from outside the controller engine, so we can't - // use VERIFY_OR_DEBUG_ASSERT here - if (m_pScriptEngine == nullptr) { - return QJSValue(); - } - - QJSValue wrappedFunction; - - auto i = m_scriptWrappedFunctionCache.constFind(codeSnippet); - if (i != m_scriptWrappedFunctionCache.constEnd()) { - wrappedFunction = i.value(); - } else { - QStringList wrapperArgList; - for (int i = 1; i <= numberOfArgs; i++) { - wrapperArgList << QString("arg%1").arg(i); - } - QString wrapperArgs = wrapperArgList.join(","); - QString wrappedCode = QStringLiteral("(function (") + wrapperArgs + - QStringLiteral(") { (") + codeSnippet + QStringLiteral(")(") + - wrapperArgs + QStringLiteral("); })"); - - wrappedFunction = evaluateCodeString(wrappedCode); - if (wrappedFunction.isError()) { - showScriptExceptionDialog(wrappedFunction); - } - m_scriptWrappedFunctionCache[codeSnippet] = wrappedFunction; - } - return wrappedFunction; -} - -void ControllerScriptHandler::gracefulShutdown() { - if (m_pScriptEngine == nullptr) { - return; - } - - qDebug() << "ControllerScriptHandler shutting down..."; - - qDebug() << "Invoking shutdown() hook in scripts"; - callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); - - if (m_shutdownFunction.isCallable()) { - executeFunction(m_shutdownFunction, QJSValueList{}); - } - - qDebug() << "Clearing function wrapper cache"; - m_scriptWrappedFunctionCache.clear(); -} - -void ControllerScriptHandler::initializeScriptEngine() { - VERIFY_OR_DEBUG_ASSERT(!m_pScriptEngine) { - return; - } - - // Create the Script Engine - m_pScriptEngine = new QJSEngine(this); - - m_pScriptEngine->installExtensions(QJSEngine::ConsoleExtension); - - // Make this ControllerScriptHandler instance available to scripts as 'engine'. - QJSValue engineGlobalObject = m_pScriptEngine->globalObject(); - ControllerScriptInterface* legacyScriptInterface = - new ControllerScriptInterface(this); - engineGlobalObject.setProperty( - "engine", m_pScriptEngine->newQObject(legacyScriptInterface)); - - QJSValue mapper = m_pScriptEngine->newQMetaObject( - &ColorMapperJSProxy::staticMetaObject); - engineGlobalObject.setProperty("ColorMapper", mapper); - - if (m_pController) { - qDebug() << "Controller in script engine is:" - << m_pController->getName(); - - ControllerJSProxy* controllerProxy = m_pController->jsProxy(); - - // Make the Controller instance available to scripts - engineGlobalObject.setProperty( - "controller", m_pScriptEngine->newQObject(controllerProxy)); - - // ...under the legacy name as well - engineGlobalObject.setProperty( - "midi", m_pScriptEngine->newQObject(controllerProxy)); - } - - m_byteArrayToScriptValueJSFunction = evaluateCodeString( - "(function(arg1) { return new Uint8Array(arg1) })"); -} - -void ControllerScriptHandler::uninitializeScriptEngine() { - // Delete the script engine, first clearing the pointer so that - // other threads will not get the dead pointer after we delete it. - if (m_pScriptEngine != nullptr) { - QJSEngine* engine = m_pScriptEngine; - m_pScriptEngine = nullptr; - engine->deleteLater(); - } -} - -void ControllerScriptHandler::loadModule(QFileInfo moduleFileInfo) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) - m_moduleFileInfo = moduleFileInfo; - - QJSValue mod = - m_pScriptEngine->importModule(moduleFileInfo.absoluteFilePath()); - if (mod.isError()) { - showScriptExceptionDialog(mod); - return; - } - - connect(&m_scriptWatcher, - &QFileSystemWatcher::fileChanged, - this, - &ControllerScriptHandler::scriptHasChanged); - m_scriptWatcher.addPath(moduleFileInfo.absoluteFilePath()); - - QJSValue initFunction = mod.property("init"); - executeFunction(initFunction, QJSValueList{}); - - QJSValue handleInputFunction = mod.property("handleInput"); - if (handleInputFunction.isCallable()) { - m_handleInputFunction = handleInputFunction; - } else { - scriptErrorDialog( - "Controller JavaScript module exports no handleInput function.", - QStringLiteral("handleInput"), - true); - } - - QJSValue shutdownFunction = mod.property("shutdown"); - if (shutdownFunction.isCallable()) { - m_shutdownFunction = shutdownFunction; - } else { - qDebug() << "Module exports no shutdown function."; - } -#else - Q_UNUSED(moduleFileInfo); -#endif -} - -void ControllerScriptHandler::handleInput( - QByteArray data, mixxx::Duration timestamp) { - if (m_handleInputFunction.isCallable()) { - QJSValueList args; - args << byteArrayToScriptValue(data); - args << timestamp.toDoubleMillis(); - executeFunction(m_handleInputFunction, args); - } -} - -bool ControllerScriptHandler::loadScriptFiles( - const QList& scripts) { - bool scriptsEvaluatedCorrectly = true; - for (const auto& script : scripts) { - if (!evaluateScriptFile(script.file)) { - scriptsEvaluatedCorrectly = false; - } - } - - m_lastScriptFiles = scripts; - - connect(&m_scriptWatcher, - &QFileSystemWatcher::fileChanged, - this, - &ControllerScriptHandler::scriptHasChanged); - - if (!scriptsEvaluatedCorrectly) { - gracefulShutdown(); - uninitializeScriptEngine(); - } - - return scriptsEvaluatedCorrectly; -} - -void ControllerScriptHandler::scriptHasChanged(const QString& scriptFilename) { - Q_UNUSED(scriptFilename); - disconnect(&m_scriptWatcher, - &QFileSystemWatcher::fileChanged, - this, - &ControllerScriptHandler::scriptHasChanged); - reloadScripts(); -} - -void ControllerScriptHandler::reloadScripts() { - qDebug() << "ControllerScriptHandler: Reloading Scripts"; - ControllerPresetPointer pPreset = m_pController->getPreset(); - - gracefulShutdown(); - uninitializeScriptEngine(); - - initializeScriptEngine(); - if (!loadScriptFiles(m_lastScriptFiles)) { - return; - } - - qDebug() << "Re-initializing scripts"; - initializeScripts(m_lastScriptFiles); - loadModule(m_moduleFileInfo); -} - -void ControllerScriptHandler::initializeScripts( - const QList& scripts) { - m_scriptFunctionPrefixes.clear(); - for (const ControllerPreset::ScriptFileInfo& script : scripts) { - // Skip empty prefixes. - if (!script.functionPrefix.isEmpty()) { - m_scriptFunctionPrefixes.append(script.functionPrefix); - } - } - - QJSValueList args; - args << QJSValue(m_pController->getName()); - args << QJSValue(ControllerDebug::enabled()); - - // Call the init method for all the prefixes. - bool success = - callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true); - - // We failed to initialize the controller scripts, shutdown the script - // engine to avoid error popups on every button press or slider move - if (!success) { - gracefulShutdown(); - uninitializeScriptEngine(); - } -} - -bool ControllerScriptHandler::executeFunction( - QJSValue functionObject, QJSValueList args) { - // This function is called from outside the controller engine, so we can't - // use VERIFY_OR_DEBUG_ASSERT here - if (!m_pScriptEngine) { - return false; - } - - if (functionObject.isError()) { - qDebug() << "ControllerScriptHandler::executeFunction:" - << functionObject.toString(); - return false; - } - - // If it's not a function, we're done. - if (!functionObject.isCallable()) { - qDebug() << "ControllerScriptHandler::executeFunction:" - << functionObject.toVariant() << "Not a function"; - return false; - } - - // If it does happen to be a function, call it. - QJSValue returnValue = functionObject.call(args); - if (returnValue.isError()) { - showScriptExceptionDialog(returnValue); - return false; - } - return true; -} - -bool ControllerScriptHandler::executeFunction( - QJSValue functionObject, const QByteArray& data) { - // This function is called from outside the controller engine, so we can't - // use VERIFY_OR_DEBUG_ASSERT here - if (!m_pScriptEngine) { - return false; - } - QJSValueList args; - args << byteArrayToScriptValue(data); - args << QJSValue(data.size()); - return executeFunction(functionObject, args); -} - -QJSValue ControllerScriptHandler::evaluateCodeString( - const QString& program, const QString& fileName, int lineNumber) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return QJSValue::UndefinedValue; - } - return m_pScriptEngine->evaluate(program, fileName, lineNumber); -} - -void ControllerScriptHandler::throwJSError(const QString& message) { -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - QString errorText = tr("Uncaught exception: %1").arg(message); - qWarning() << "ControllerEngine:" << errorText; - if (!m_bDisplayingExceptionDialog) { - scriptErrorDialog(errorText, errorText); - } -#else - m_pScriptEngine->throwError(message); -#endif -} - -void ControllerScriptHandler::showScriptExceptionDialog( - QJSValue evaluationResult, bool bFatalError) { - VERIFY_OR_DEBUG_ASSERT(evaluationResult.isError()) { - return; - } - - QString errorMessage = evaluationResult.toString(); - QString line = evaluationResult.property("lineNumber").toString(); - QString backtrace = evaluationResult.property("stack").toString(); - QString filename = evaluationResult.property("fileName").toString(); - - QString errorText; - if (filename.isEmpty()) { - errorText = QString("Uncaught exception at line %1 in passed code.") - .arg(line); - } else { - errorText = QString("Uncaught exception at line %1 in file %2.") - .arg(line, filename); - } - - errorText += QStringLiteral("\n\nException:\n ") + errorMessage; - - // Do not include backtrace in dialog key because it might contain midi - // slider values that will differ most of the time. This would break - // the "Ignore" feature of the error dialog. - QString key = errorText; - qWarning() << "ControllerScriptHandler:" << errorText; - - // Add backtrace to the error details - errorText += QStringLiteral("\n\nBacktrace:\n") + backtrace; - - if (!m_bDisplayingExceptionDialog) { - scriptErrorDialog(errorText, key, bFatalError); - } -} - -void ControllerScriptHandler::scriptErrorDialog( - const QString& detailedError, const QString& key, bool bFatalError) { - if (m_bTesting) { - return; - } - - ErrorDialogProperties* props = - ErrorDialogHandler::instance()->newDialogProperties(); - - QString additionalErrorText; - if (bFatalError) { - additionalErrorText = - tr("The functionality provided by this controller mapping will " - "be disabled until the issue has been resolved."); - } else { - additionalErrorText = - tr("You can ignore this error for this session but " - "you may experience erratic behavior.") + - QString("
") + - tr("Try to recover by resetting your controller."); - } - - props->setType(DLG_WARNING); - props->setTitle(tr("Controller Preset Error")); - props->setText(QString(tr("The preset for your controller \"%1\" is not " - "working properly.")) - .arg(m_pController->getName())); - props->setInfoText(QStringLiteral("") + - tr("The script code needs to be fixed.") + QStringLiteral("

") + - additionalErrorText + QStringLiteral("

")); - - // Add "Details" text and set monospace font since they may contain - // backtraces and code. - props->setDetails(detailedError, true); - - // To prevent multiple windows for the same error - props->setKey(key); - - // Allow user to suppress further notifications about this particular error - if (!bFatalError) { - props->addButton(QMessageBox::Ignore); - props->addButton(QMessageBox::Retry); - props->setDefaultButton(QMessageBox::Ignore); - props->setEscapeButton(QMessageBox::Ignore); - } else { - props->addButton(QMessageBox::Close); - props->setDefaultButton(QMessageBox::Close); - props->setEscapeButton(QMessageBox::Close); - } - props->setModal(false); - - if (ErrorDialogHandler::instance()->requestErrorDialog(props)) { - m_bDisplayingExceptionDialog = true; - // Enable custom handling of the dialog buttons - connect(ErrorDialogHandler::instance(), - &ErrorDialogHandler::stdButtonClicked, - this, - &ControllerScriptHandler::errorDialogButton); - } -} - -void ControllerScriptHandler::errorDialogButton( - const QString& key, QMessageBox::StandardButton clickedButton) { - Q_UNUSED(key); - - m_bDisplayingExceptionDialog = false; - // Something was clicked, so disable this signal now - disconnect(ErrorDialogHandler::instance(), - &ErrorDialogHandler::stdButtonClicked, - this, - &ControllerScriptHandler::errorDialogButton); - - if (clickedButton == QMessageBox::Retry) { - reloadScripts(); - } -} - -bool ControllerScriptHandler::evaluateScriptFile(const QFileInfo& scriptFile) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return false; - } - - if (!scriptFile.exists()) { - qWarning() << "ControllerScriptHandler: File does not exist:" - << scriptFile.absoluteFilePath(); - return false; - } - m_scriptWatcher.addPath(scriptFile.absoluteFilePath()); - - qDebug() << "ControllerScriptHandler: Loading" - << scriptFile.absoluteFilePath(); - - // Read in the script file - QString filename = scriptFile.absoluteFilePath(); - QFile input(filename); - if (!input.open(QIODevice::ReadOnly)) { - qWarning() << QString( - "ControllerScriptHandler: Problem opening the script file: %1, " - "error # %2, %3") - .arg(filename, - QString::number(input.error()), - input.errorString()); - // Set up error dialog - ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); - props->setType(DLG_WARNING); - props->setTitle(tr("Controller Mapping File Problem")); - props->setText(tr("The mapping for controller \"%1\" cannot be opened.") - .arg(m_pController->getName())); - props->setInfoText( - tr("The functionality provided by this controller mapping will " - "be disabled until the issue has been resolved.")); - - // We usually don't translate the details field, but the cause of - // this problem lies in the user's system (e.g. a permission - // issue). Translating this will help users to fix the issue even - // when they don't speak english. - props->setDetails(tr("File:") + QStringLiteral(" ") + filename + - QStringLiteral("\n") + tr("Error:") + QStringLiteral(" ") + - input.errorString()); - - // Ask above layer to display the dialog & handle user response - ErrorDialogHandler::instance()->requestErrorDialog(props); - return false; - } - - QString scriptCode = ""; - scriptCode.append(input.readAll()); - scriptCode.append('\n'); - input.close(); - - // Evaluate the code - QJSValue scriptFunction = evaluateCodeString(scriptCode, filename); - if (scriptFunction.isError()) { - showScriptExceptionDialog(scriptFunction, true); - return false; - } - - return true; -} diff --git a/src/controllers/scripting/controllerscripthandler.h b/src/controllers/scripting/controllerscripthandler.h deleted file mode 100644 index 0d117d1623d..00000000000 --- a/src/controllers/scripting/controllerscripthandler.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "controllers/controllerpreset.h" -#include "util/duration.h" - -class Controller; -class ControllerScriptInterface; -class EvaluationException; -class ScriptConnection; - -/// ControllerScriptHandler loads and executes script files for controller mappings -class ControllerScriptHandler : public QObject { - Q_OBJECT - public: - ControllerScriptHandler(Controller* controller); - virtual ~ControllerScriptHandler(); - - void handleInput(QByteArray data, mixxx::Duration timestamp); - - bool executeFunction(QJSValue functionObject, QJSValueList arguments); - bool executeFunction(QJSValue functionObject, const QByteArray& data); - - /// Wrap a string of JS code in an anonymous function. This allows any JS - /// string that evaluates to a function to be used in MIDI mapping XML files - /// and ensures the function is executed with the correct 'this' object. - QJSValue wrapFunctionCode(const QString& codeSnippet, int numberOfArgs); - - /// Look up registered script function prefixes - const QList& getScriptFunctionPrefixes() { - return m_scriptFunctionPrefixes; - }; - - /// Shows a UI dialog notifying of a script evaluation error. - /// Precondition: QJSValue.isError() == true - void showScriptExceptionDialog(QJSValue evaluationResult, bool bFatal = false); - void throwJSError(const QString& message); - - inline void setTesting(bool testing) { - m_bTesting = testing; - }; - - bool isTesting() { - return m_bTesting; - } - - QJSEngine* scriptEngine() { - return m_pScriptEngine; - } - - public slots: - void loadModule(QFileInfo moduleFileInfo); - bool loadScriptFiles(const QList& scripts); - void initializeScripts(const QList& scripts); - void gracefulShutdown(); - void scriptHasChanged(const QString&); - - private slots: - void errorDialogButton(const QString& key, QMessageBox::StandardButton button); - - private: - bool evaluateScriptFile(const QFileInfo& scriptFile); - void initializeScriptEngine(); - void uninitializeScriptEngine(); - void reloadScripts(); - - void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); - void generateScriptFunctions(const QString& code); - - bool callFunctionOnObjects(QList, - const QString&, - QJSValueList args = QJSValueList(), - bool bFatalError = false); - /// Convert a byteArray to a JS typed array over an ArrayBuffer - QJSValue byteArrayToScriptValue(const QByteArray& byteArray); - QJSValue evaluateCodeString(const QString& program, const QString& fileName = QString(), int lineNumber = 1); - - bool m_bDisplayingExceptionDialog; - QJSEngine* m_pScriptEngine; - - Controller* m_pController; - QJSValue m_handleInputFunction; - QJSValue m_shutdownFunction; - QList m_scriptFunctionPrefixes; - - QHash m_scriptWrappedFunctionCache; - QJSValue m_byteArrayToScriptValueJSFunction; - // Filesystem watcher for script auto-reload - QFileSystemWatcher m_scriptWatcher; - QFileInfo m_moduleFileInfo; - QList m_lastScriptFiles; - - bool m_bTesting; - - friend class ScriptConnection; - friend class ControllerScriptInterface; - friend class ColorJSProxy; - friend class ColorMapperJSProxy; - friend class ControllerEngineTest; -}; diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp new file mode 100644 index 00000000000..58a9cb8e83a --- /dev/null +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -0,0 +1,56 @@ +#include "controllers/scripting/controllerscriptmoduleengine.h" + +bool ControllerScriptModuleEngine::initialize() { + ControllerScriptEngineBase::initialize(); + m_pJSEngine->installExtensions(QJSEngine::ConsoleExtension); + // TODO: Add new ControlObject JS API to scripting environment. + + QJSValue mod = + m_pJSEngine->importModule(m_moduleFileInfo.absoluteFilePath()); + if (mod.isError()) { + showScriptExceptionDialog(mod); + shutdown(); + return false; + } + + m_scriptWatcher.addPath(m_moduleFileInfo.absoluteFilePath()); + + QJSValue initFunction = mod.property("init"); + if (!executeFunction(initFunction, QJSValueList{})) { + shutdown(); + return false; + } + + QJSValue handleInputFunction = mod.property("handleInput"); + if (handleInputFunction.isCallable()) { + m_handleInputFunction = handleInputFunction; + } else { + scriptErrorDialog( + "Controller JavaScript module exports no handleInput function.", + QStringLiteral("handleInput"), + true); + shutdown(); + return false; + } + + QJSValue shutdownFunction = mod.property("shutdown"); + if (shutdownFunction.isCallable()) { + m_shutdownFunction = shutdownFunction; + } else { + qDebug() << "Module exports no shutdown function."; + } + return true; +} + +void ControllerScriptModuleEngine::shutdown() { + executeFunction(m_shutdownFunction, QJSValueList()); + ControllerScriptEngineBase::shutdown(); +} + +void ControllerScriptModuleEngine::handleInput( + QByteArray data, mixxx::Duration timestamp) { + QJSValueList args; + args << byteArrayToScriptValue(data); + args << timestamp.toDoubleMillis(); + executeFunction(m_handleInputFunction, args); +} diff --git a/src/controllers/scripting/controllerscriptmoduleengine.h b/src/controllers/scripting/controllerscriptmoduleengine.h new file mode 100644 index 00000000000..3fc44a1c843 --- /dev/null +++ b/src/controllers/scripting/controllerscriptmoduleengine.h @@ -0,0 +1,26 @@ +#pragma once + +#include "controllers/scripting/controllerscriptenginebase.h" + +/// ControllerScriptModuleEngine loads and executes script module files for controller mappings. +class ControllerScriptModuleEngine : public ControllerScriptEngineBase { + Q_OBJECT + public: + ControllerScriptModuleEngine(Controller* controller); + + bool initialize() override; + + void setModuleFileInfo(QFileInfo moduleFileInfo) { + m_moduleFileInfo = moduleFileInfo; + } + + void handleInput(QByteArray data, mixxx::Duration timestamp); + + private: + void shutdown() override; + + QJSValue m_handleInputFunction; + QJSValue m_shutdownFunction; + + QFileInfo m_moduleFileInfo; +}; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp new file mode 100644 index 00000000000..9f7b4aac976 --- /dev/null +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -0,0 +1,198 @@ +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" + +#include "control/controlobject.h" +#include "controllers/controller.h" +#include "controllers/controllerdebug.h" +#include "controllers/scripting/colormapperjsproxy.h" +#include "controllers/scripting/legacy/controllerscriptinterface.h" +#include "errordialoghandler.h" +#include "mixer/playermanager.h" + +ControllerScriptEngineLegacy::ControllerScriptEngineLegacy(Controller* controller) + : ControllerScriptEngineBase(controller) { +} + +bool ControllerScriptEngineLegacy::callFunctionOnObjects(QList scriptFunctionPrefixes, + const QString& function, + QJSValueList args, + bool bFatalError) { + VERIFY_OR_DEBUG_ASSERT(m_pJSEngine) { + return false; + } + + const QJSValue global = m_pJSEngine->globalObject(); + + bool success = true; + for (const QString& prefixName : scriptFunctionPrefixes) { + QJSValue prefix = global.property(prefixName); + if (!prefix.isObject()) { + qWarning() << "ControllerScriptHandlerLegacy: No" << prefixName << "object in script"; + continue; + } + + QJSValue init = prefix.property(function); + if (!init.isCallable()) { + qWarning() << "ControllerScriptHandlerLegacy:" << prefixName << "has no" + << function << " method"; + continue; + } + controllerDebug("ControllerScriptHandlerLegacy: Executing" + << prefixName << "." << function); + QJSValue result = init.callWithInstance(prefix, args); + if (result.isError()) { + showScriptExceptionDialog(result, bFatalError); + success = false; + } + } + return success; +} + +QJSValue ControllerScriptEngineLegacy::wrapFunctionCode( + const QString& codeSnippet, int numberOfArgs) { + // This function is called from outside the controller engine, so we can't + // use VERIFY_OR_DEBUG_ASSERT here + if (m_pJSEngine == nullptr) { + return QJSValue(); + } + + QJSValue wrappedFunction; + + auto i = m_scriptWrappedFunctionCache.constFind(codeSnippet); + if (i != m_scriptWrappedFunctionCache.constEnd()) { + wrappedFunction = i.value(); + } else { + QStringList wrapperArgList; + for (int i = 1; i <= numberOfArgs; i++) { + wrapperArgList << QString("arg%1").arg(i); + } + QString wrapperArgs = wrapperArgList.join(","); + QString wrappedCode = QStringLiteral("(function (") + wrapperArgs + + QStringLiteral(") { (") + codeSnippet + QStringLiteral(")(") + + wrapperArgs + QStringLiteral("); })"); + + wrappedFunction = m_pJSEngine->evaluate(wrappedCode); + if (wrappedFunction.isError()) { + showScriptExceptionDialog(wrappedFunction); + } + m_scriptWrappedFunctionCache[codeSnippet] = wrappedFunction; + } + return wrappedFunction; +} + +bool ControllerScriptEngineLegacy::initialize() { + ControllerScriptEngineBase::initialize(); + + // Make this ControllerScriptHandler instance available to scripts as 'engine'. + QJSValue engineGlobalObject = m_pJSEngine->globalObject(); + ControllerScriptInterface* legacyScriptInterface = + new ControllerScriptInterface(this); + engineGlobalObject.setProperty( + "engine", m_pJSEngine->newQObject(legacyScriptInterface)); + + for (const ControllerPreset::ScriptFileInfo& script : m_scriptFiles) { + if (!evaluateScriptFile(script.file)) { + shutdown(); + return false; + } + if (!script.functionPrefix.isEmpty()) { + m_scriptFunctionPrefixes.append(script.functionPrefix); + } + m_scriptWatcher.addPath(script.file.absoluteFilePath()); + } + + QJSValueList args; + if (m_pController) { + args << QJSValue(m_pController->getName()); + } else { // m_pController is nullptr in tests. + args << QJSValue(); + } + args << QJSValue(ControllerDebug::enabled()); + if (!callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true)) { + shutdown(); + return false; + } + + return true; +} + +void ControllerScriptEngineLegacy::shutdown() { + callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); + m_scriptWrappedFunctionCache.clear(); + m_scriptFunctionPrefixes.clear(); + ControllerScriptEngineBase::shutdown(); +} + +bool ControllerScriptEngineLegacy::executeIncomingDataFunction( + QJSValue functionObject, const QByteArray& data) { + // This function is called from outside the controller engine, so we can't + // use VERIFY_OR_DEBUG_ASSERT here + if (!m_pJSEngine) { + return false; + } + QJSValueList args; + args << byteArrayToScriptValue(data); + args << QJSValue(data.size()); + return ControllerScriptEngineBase::executeFunction(functionObject, args); +} + +bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFile) { + VERIFY_OR_DEBUG_ASSERT(m_pJSEngine) { + return false; + } + + if (!scriptFile.exists()) { + qWarning() << "ControllerScriptHandlerLegacy: File does not exist:" + << scriptFile.absoluteFilePath(); + return false; + } + m_scriptWatcher.addPath(scriptFile.absoluteFilePath()); + + qDebug() << "ControllerScriptHandlerLegacy: Loading" + << scriptFile.absoluteFilePath(); + + // Read in the script file + QString filename = scriptFile.absoluteFilePath(); + QFile input(filename); + if (!input.open(QIODevice::ReadOnly)) { + qWarning() << QString( + "ControllerScriptHandlerLegacy: Problem opening the script file: %1, " + "error # %2, %3") + .arg(filename, + QString::number(input.error()), + input.errorString()); + // Set up error dialog + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); + props->setType(DLG_WARNING); + props->setTitle(tr("Controller Mapping File Problem")); + props->setText(tr("The mapping for controller \"%1\" cannot be opened.") + .arg(m_pController->getName())); + props->setInfoText( + tr("The functionality provided by this controller mapping will " + "be disabled until the issue has been resolved.")); + + // We usually don't translate the details field, but the cause of + // this problem lies in the user's system (e.g. a permission + // issue). Translating this will help users to fix the issue even + // when they don't speak english. + props->setDetails(tr("File:") + QStringLiteral(" ") + filename + + QStringLiteral("\n") + tr("Error:") + QStringLiteral(" ") + + input.errorString()); + + // Ask above layer to display the dialog & handle user response + ErrorDialogHandler::instance()->requestErrorDialog(props); + return false; + } + + QString scriptCode = ""; + scriptCode.append(input.readAll()); + scriptCode.append('\n'); + input.close(); + + QJSValue scriptFunction = m_pJSEngine->evaluate(scriptCode, filename); + if (scriptFunction.isError()) { + showScriptExceptionDialog(scriptFunction, true); + return false; + } + + return true; +} diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h new file mode 100644 index 00000000000..22684ade887 --- /dev/null +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include + +#include "controllers/controllerpreset.h" +#include "controllers/scripting/controllerscriptenginebase.h" +#include "util/duration.h" + +class Controller; +class ControllerScriptInterface; +class EvaluationException; +class ScriptConnection; + +/// ControllerScriptEngineLegacy loads and executes controller scripts for the legacy +/// JS/XML hybrid controller mapping system. +class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { + Q_OBJECT + public: + ControllerScriptEngineLegacy(Controller* controller); + + bool initialize() override; + + bool executeIncomingDataFunction(QJSValue functionObject, const QByteArray& data); + + /// Wrap a string of JS code in an anonymous function. This allows any JS + /// string that evaluates to a function to be used in MIDI mapping XML files + /// and ensures the function is executed with the correct 'this' object. + QJSValue wrapFunctionCode(const QString& codeSnippet, int numberOfArgs); + + /// Look up registered script function prefixes + const QList& getScriptFunctionPrefixes() { + return m_scriptFunctionPrefixes; + }; + + // There is lots of tight coupling between ControllerScriptEngineLegacy + // and ControllerScriptInterface. This is probably not worth improving in legacy code. + QJSEngine* jsEngine() { + return m_pJSEngine; + } + + public slots: + void setScriptFiles(const QList& scripts) { + m_scriptFiles = scripts; + } + + private: + bool evaluateScriptFile(const QFileInfo& scriptFile); + void shutdown() override; + + void generateScriptFunctions(const QString& code); + + bool callFunctionOnObjects(QList, + const QString&, + QJSValueList args = QJSValueList(), + bool bFatalError = false); + + QList m_scriptFunctionPrefixes; + QHash m_scriptWrappedFunctionCache; + QList m_scriptFiles; + + friend class ScriptConnection; + friend class ControllerScriptInterface; + friend class ColorJSProxy; + friend class ColorMapperJSProxy; + friend class ControllerEngineTest; +}; diff --git a/src/controllers/scripting/legacy/controllerscriptinterface.cpp b/src/controllers/scripting/legacy/controllerscriptinterface.cpp index d29cd89991f..ba2fd2d9d5d 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterface.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterface.cpp @@ -3,7 +3,7 @@ #include "control/controlobject.h" #include "control/controlobjectscript.h" #include "controllers/controllerdebug.h" -#include "controllers/scripting/controllerscripthandler.h" +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "controllers/scripting/legacy/scriptconnectionjsproxy.h" #include "mixer/playermanager.h" #include "util/math.h" @@ -18,8 +18,8 @@ const int kScratchTimerMs = 1; const double kAlphaBetaDt = kScratchTimerMs / 1000.0; } // anonymous namespace -ControllerScriptInterface::ControllerScriptInterface(ControllerScriptHandler* m_pEngine) - : m_pEngine(m_pEngine) { +ControllerScriptInterface::ControllerScriptInterface(ControllerScriptEngineLegacy* m_pEngine) + : m_pScriptEngineLegacy(m_pEngine) { // Pre-allocate arrays for average number of virtual decks m_intervalAccumulator.resize(kDecks); m_lastMovement.resize(kDecks); @@ -211,8 +211,8 @@ double ControllerScriptInterface::getDefaultParameter( QJSValue ControllerScriptInterface::makeConnection( QString group, QString name, const QJSValue callback) { - QJSEngine* pScriptEngine = m_pEngine->scriptEngine(); - VERIFY_OR_DEBUG_ASSERT(pScriptEngine) { + QJSEngine* jsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(jsEngine) { return QJSValue(); } @@ -220,8 +220,8 @@ QJSValue ControllerScriptInterface::makeConnection( if (coScript == nullptr) { // The test setups do not run all of Mixxx, so ControlObjects not // existing during tests is okay. - if (!m_pEngine->isTesting()) { - m_pEngine->throwJSError( + if (!m_pScriptEngineLegacy->isTesting()) { + m_pScriptEngineLegacy->throwJSError( "ControllerScriptInterface: script tried to connect to " "ControlObject (" + group + ", " + name + ") which is non-existent."); @@ -230,7 +230,7 @@ QJSValue ControllerScriptInterface::makeConnection( } if (!callback.isCallable()) { - m_pEngine->throwJSError("Tried to connect (" + group + ", " + name + + m_pScriptEngineLegacy->throwJSError("Tried to connect (" + group + ", " + name + ")" + " to an invalid callback. Make sure that your code contains no " "syntax errors."); @@ -240,12 +240,12 @@ QJSValue ControllerScriptInterface::makeConnection( ScriptConnection connection; connection.key = ConfigKey(group, name); connection.engineJSProxy = this; - connection.controllerEngine = m_pEngine; + connection.controllerEngine = m_pScriptEngineLegacy; connection.callback = callback; connection.id = QUuid::createUuid(); if (coScript->addScriptConnection(connection)) { - return pScriptEngine->newQObject( + return jsEngine->newQObject( new ScriptConnectionJSProxy(connection)); } @@ -257,7 +257,7 @@ bool ControllerScriptInterface::removeScriptConnection( ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); - if (m_pEngine->scriptEngine() == nullptr || coScript == nullptr) { + if (m_pScriptEngineLegacy->jsEngine() == nullptr || coScript == nullptr) { return false; } @@ -266,7 +266,7 @@ bool ControllerScriptInterface::removeScriptConnection( void ControllerScriptInterface::triggerScriptConnection( const ScriptConnection connection) { - VERIFY_OR_DEBUG_ASSERT(m_pEngine->scriptEngine()) { + VERIFY_OR_DEBUG_ASSERT(m_pScriptEngineLegacy->jsEngine()) { return; } @@ -301,7 +301,7 @@ QJSValue ControllerScriptInterface::connectControl( actualCallbackFunction = passedCallback; } - QJSEngine* pScriptEngine = m_pEngine->scriptEngine(); + QJSEngine* pScriptEngine = m_pScriptEngineLegacy->jsEngine(); ControlObjectScript* coScript = getControlObjectScript(group, name); // This check is redundant with makeConnection, but the @@ -309,14 +309,14 @@ QJSValue ControllerScriptInterface::connectControl( if (coScript == nullptr) { // The test setups do not run all of Mixxx, so ControlObjects not // existing during tests is okay. - if (!m_pEngine->isTesting()) { + if (!m_pScriptEngineLegacy->isTesting()) { if (disconnect) { - m_pEngine->throwJSError( + m_pScriptEngineLegacy->throwJSError( "ControllerScriptInterface: script tried to disconnect from " "ControlObject (" + group + ", " + name + ") which is non-existent."); } else { - m_pEngine->throwJSError( + m_pScriptEngineLegacy->throwJSError( "ControllerScriptInterface: script tried to connect to " "ControlObject (" + group + ", " + name + ") which is non-existent."); @@ -335,7 +335,7 @@ QJSValue ControllerScriptInterface::connectControl( } actualCallbackFunction = - m_pEngine->evaluateCodeString(passedCallback.toString()); + pScriptEngine->evaluate(passedCallback.toString()); if (!actualCallbackFunction.isCallable()) { QString sErrorMessage( @@ -344,7 +344,7 @@ QJSValue ControllerScriptInterface::connectControl( if (actualCallbackFunction.isError()) { sErrorMessage.append("\n" + actualCallbackFunction.toString()); } - m_pEngine->throwJSError(sErrorMessage); + m_pScriptEngineLegacy->throwJSError(sErrorMessage); return QJSValue(false); } @@ -416,7 +416,7 @@ void ControllerScriptInterface::log(QString message) { int ControllerScriptInterface::beginTimer( int intervalMillis, QJSValue timerCallback, bool oneShot) { if (timerCallback.isString()) { - timerCallback = m_pEngine->evaluateCodeString(timerCallback.toString()); + timerCallback = m_pScriptEngineLegacy->jsEngine()->evaluate(timerCallback.toString()); } else if (!timerCallback.isCallable()) { QString sErrorMessage( "Invalid timer callback provided to engine.beginTimer. Valid " @@ -425,7 +425,7 @@ int ControllerScriptInterface::beginTimer( if (timerCallback.isError()) { sErrorMessage.append("\n" + timerCallback.toString()); } - m_pEngine->throwJSError(sErrorMessage); + m_pScriptEngineLegacy->throwJSError(sErrorMessage); return 0; } @@ -489,7 +489,7 @@ void ControllerScriptInterface::timerEvent(QTimerEvent* event) { stopTimer(timerId); } - m_pEngine->executeFunction(timerTarget.callback, QJSValueList()); + m_pScriptEngineLegacy->executeFunction(timerTarget.callback, QJSValueList()); } void ControllerScriptInterface::softTakeover( @@ -538,7 +538,7 @@ bool ControllerScriptInterface::isDeckPlaying(const QString& group) { if (pPlay == nullptr) { QString error = QString("Could not getControlObjectScript()"); - m_pEngine->scriptErrorDialog(error, error); + m_pScriptEngineLegacy->scriptErrorDialog(error, error); return false; } diff --git a/src/controllers/scripting/legacy/controllerscriptinterface.h b/src/controllers/scripting/legacy/controllerscriptinterface.h index 964761b96b8..97da98e48d2 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterface.h +++ b/src/controllers/scripting/legacy/controllerscriptinterface.h @@ -6,7 +6,7 @@ #include "controllers/softtakeover.h" #include "util/alphabetafilter.h" -class ControllerScriptHandler; +class ControllerScriptEngineLegacy; class ControlObjectScript; class ScriptConnection; class ConfigKey; @@ -16,7 +16,7 @@ class ConfigKey; class ControllerScriptInterface : public QObject { Q_OBJECT public: - ControllerScriptInterface(ControllerScriptHandler* m_pEngine); + ControllerScriptInterface(ControllerScriptEngineLegacy* m_pEngine); virtual ~ControllerScriptInterface(); @@ -76,5 +76,5 @@ class ControllerScriptInterface : public QObject { bool isDeckPlaying(const QString& group); double getDeckRate(const QString& group); - ControllerScriptHandler* m_pEngine; + ControllerScriptEngineLegacy* m_pScriptEngineLegacy; }; diff --git a/src/controllers/scripting/legacy/scriptconnection.cpp b/src/controllers/scripting/legacy/scriptconnection.cpp index b9970c8e32c..2f66346db3f 100644 --- a/src/controllers/scripting/legacy/scriptconnection.cpp +++ b/src/controllers/scripting/legacy/scriptconnection.cpp @@ -1,6 +1,6 @@ #include "controllers/scripting/legacy/scriptconnection.h" -#include "controllers/scripting/controllerscripthandler.h" +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" void ScriptConnection::executeCallback(double value) const { QJSValueList args; diff --git a/src/controllers/scripting/legacy/scriptconnection.h b/src/controllers/scripting/legacy/scriptconnection.h index a072dfcb7db..e7174429429 100644 --- a/src/controllers/scripting/legacy/scriptconnection.h +++ b/src/controllers/scripting/legacy/scriptconnection.h @@ -5,7 +5,7 @@ #include "preferences/configobject.h" -class ControllerScriptHandler; +class ControllerScriptEngineLegacy; class ControllerScriptInterface; /// ScriptConnection is a connection between a ControlObject and a @@ -17,7 +17,7 @@ class ScriptConnection { QUuid id; QJSValue callback; ControllerScriptInterface* engineJSProxy; - ControllerScriptHandler* controllerEngine; + ControllerScriptEngineLegacy* controllerEngine; void executeCallback(double value) const; diff --git a/src/test/controller_preset_validation_test.cpp b/src/test/controller_preset_validation_test.cpp index 501624efac4..ac51bb14033 100644 --- a/src/test/controller_preset_validation_test.cpp +++ b/src/test/controller_preset_validation_test.cpp @@ -30,7 +30,7 @@ FakeController::FakeController() : m_bMidiPreset(false), m_bHidPreset(false) { startEngine(); - getScriptHandler()->setTesting(true); + getScriptEngine()->setTesting(true); } FakeController::~FakeController() { diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index 964827754eb..4c3f3f8488e 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -4,7 +4,7 @@ #include "control/controlobject.h" #include "control/controlpotmeter.h" #include "controllers/controllerdebug.h" -#include "controllers/scripting/controllerscripthandler.h" +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "controllers/softtakeover.h" #include "preferences/usersettings.h" #include "test/mixxxtest.h" @@ -18,12 +18,12 @@ class ControllerEngineTest : public MixxxTest { mixxx::Time::setTestMode(true); mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); QThread::currentThread()->setObjectName("Main"); - cEngine = new ControllerScriptHandler(nullptr); + cEngine = new ControllerScriptEngineLegacy(nullptr); + cEngine->initialize(); ControllerDebug::enable(); } void TearDown() override { - cEngine->gracefulShutdown(); delete cEngine; mixxx::Time::setTestMode(false); } @@ -33,14 +33,14 @@ class ControllerEngineTest : public MixxxTest { } QJSValue evaluate(const QString& code) { - return cEngine->evaluateCodeString(code); + return cEngine->jsEngine()->evaluate(code); } bool evaluateAndAssert(const QString& code) { - return !cEngine->evaluateCodeString(code).isError(); + return !evaluate(code).isError(); } - ControllerScriptHandler* cEngine; + ControllerScriptEngineLegacy* cEngine; }; TEST_F(ControllerEngineTest, commonScriptHasNoErrors) { From b3d6ff804c17358f8d262f38be3055883b158719 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 6 Jul 2020 15:45:00 -0500 Subject: [PATCH 05/61] remove hacks for JS modules in legacy controller system --- src/controllers/controllerpreset.h | 9 --------- src/controllers/controllerpresetfilehandler.cpp | 3 --- 2 files changed, 12 deletions(-) diff --git a/src/controllers/controllerpreset.h b/src/controllers/controllerpreset.h index f129e99b7c4..120d7b007e7 100644 --- a/src/controllers/controllerpreset.h +++ b/src/controllers/controllerpreset.h @@ -57,14 +57,6 @@ class ControllerPreset { return m_scripts; } - void setModuleFileInfo(QFileInfo fileInfo) { - m_moduleFileInfo = fileInfo; - } - - QFileInfo moduleFileInfo() const { - return m_moduleFileInfo; - } - inline void setDirty(bool bDirty) { m_bDirty = bDirty; } @@ -186,7 +178,6 @@ class ControllerPreset { QString m_mixxxVersion; QList m_scripts; - QFileInfo m_moduleFileInfo; }; typedef QSharedPointer ControllerPresetPointer; diff --git a/src/controllers/controllerpresetfilehandler.cpp b/src/controllers/controllerpresetfilehandler.cpp index f181c9081fc..6d147a6e885 100644 --- a/src/controllers/controllerpresetfilehandler.cpp +++ b/src/controllers/controllerpresetfilehandler.cpp @@ -144,9 +144,6 @@ void ControllerPresetFileHandler::addScriptFilesToPreset( preset->addScriptFile(filename, functionPrefix, file); scriptFile = scriptFile.nextSiblingElement("file"); } - - QString moduleFileName = controller.firstChildElement("module").text(); - preset->setModuleFileInfo(preset->dirPath().absoluteFilePath(moduleFileName)); } bool ControllerPresetFileHandler::writeDocument( From 72e849f76ee6eff461d4cd41586b1093a332b85f Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 12 Jul 2020 15:07:48 -0500 Subject: [PATCH 06/61] ControllerScriptEngineBase: remove outdated comment --- src/controllers/scripting/controllerscriptenginebase.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 8cc1f90a8d4..95890fa80b2 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -36,7 +36,6 @@ bool ControllerScriptEngineBase::initialize() { // Create the Script Engine m_pJSEngine = new QJSEngine(this); - // Make this ControllerScriptHandlerBase instance available to scripts as 'engine'. QJSValue engineGlobalObject = m_pJSEngine->globalObject(); QJSValue mapper = m_pJSEngine->newQMetaObject( From 0968a9ddd9790e49ac153e899b26b6f5d4d6791b Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 12 Jul 2020 17:16:09 -0500 Subject: [PATCH 07/61] Controller: replace legacy NULL with nullptr --- src/controllers/controller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index f43400a5ffc..6147fdfa90e 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -34,7 +34,7 @@ ControllerJSProxy* Controller::jsProxy() { void Controller::startEngine() { controllerDebug(" Starting engine"); - if (m_pScriptEngineLegacy != NULL) { + if (m_pScriptEngineLegacy != nullptr) { qWarning() << "Controller: Engine already exists! Restarting:"; stopEngine(); } @@ -57,7 +57,7 @@ bool Controller::applyPreset(bool initializeScripts) { const ControllerPreset* pPreset = preset(); // Load the script code into the engine - if (m_pScriptEngineLegacy == NULL) { + if (m_pScriptEngineLegacy == nullptr) { qWarning() << "Controller::applyPreset(): No engine exists!"; return false; } @@ -112,7 +112,7 @@ void Controller::triggerActivity() } } void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { - if (m_pScriptEngineLegacy == NULL) { + if (m_pScriptEngineLegacy == nullptr) { //qWarning() << "Controller::receive called with no active engine!"; // Don't complain, since this will always show after closing a device as // queued signals flush out From 77ca43f89ae9190d8a7bf8db8e5a1a0e9835f70f Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 12 Jul 2020 17:20:29 -0500 Subject: [PATCH 08/61] ControllerScriptModuleEngine: keep build working with Qt < 5.12 --- src/controllers/scripting/controllerscriptmoduleengine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index 58a9cb8e83a..5d9cb3efdf0 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -2,6 +2,7 @@ bool ControllerScriptModuleEngine::initialize() { ControllerScriptEngineBase::initialize(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) m_pJSEngine->installExtensions(QJSEngine::ConsoleExtension); // TODO: Add new ControlObject JS API to scripting environment. @@ -39,6 +40,7 @@ bool ControllerScriptModuleEngine::initialize() { } else { qDebug() << "Module exports no shutdown function."; } +#endif return true; } From 8153b3fd3fc2d04d16bf0a657bf5dddfbbe5d83c Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 12 Jul 2020 18:12:56 -0500 Subject: [PATCH 09/61] cleanup handling of incoming HID/USB Bulk/MIDI sysex data --- src/controllers/controller.cpp | 9 +-------- src/controllers/midi/midicontroller.cpp | 6 +----- .../legacy/controllerscriptenginelegacy.cpp | 20 ++++++++++++++++--- .../legacy/controllerscriptenginelegacy.h | 8 ++------ 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 6147fdfa90e..f335d33eeaf 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -136,12 +136,5 @@ void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { controllerDebug(message); } - foreach (QString function, m_pScriptEngineLegacy->getScriptFunctionPrefixes()) { - if (function == "") { - continue; - } - function.append(".incomingData"); - QJSValue incomingDataFunction = m_pScriptEngineLegacy->wrapFunctionCode(function, 2); - m_pScriptEngineLegacy->executeIncomingDataFunction(incomingDataFunction, data); - } + m_pScriptEngineLegacy->handleIncomingData(data); } diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index c893dd9f227..78273fcf5bb 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -504,11 +504,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, if (pEngine == NULL) { return; } - QJSValue function = pEngine->wrapFunctionCode(mapping.control.item, 2); - if (!pEngine->executeIncomingDataFunction(function, data)) { - qDebug() << "MidiController: Invalid script function" - << mapping.control.item; - } + pEngine->handleIncomingData(data); return; } qWarning() << "MidiController: No script function specified for" diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 9f7b4aac976..c11551944de 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -100,6 +100,14 @@ bool ControllerScriptEngineLegacy::initialize() { m_scriptWatcher.addPath(script.file.absoluteFilePath()); } + for (QString functionName : m_scriptFunctionPrefixes) { + if (functionName.isEmpty()) { + continue; + } + functionName.append(QStringLiteral(".incomingData")); + m_incomingDataFunctions.append(wrapFunctionCode(functionName, 2)); + } + QJSValueList args; if (m_pController) { args << QJSValue(m_pController->getName()); @@ -118,21 +126,27 @@ bool ControllerScriptEngineLegacy::initialize() { void ControllerScriptEngineLegacy::shutdown() { callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); m_scriptWrappedFunctionCache.clear(); + m_incomingDataFunctions.clear(); m_scriptFunctionPrefixes.clear(); ControllerScriptEngineBase::shutdown(); } -bool ControllerScriptEngineLegacy::executeIncomingDataFunction( - QJSValue functionObject, const QByteArray& data) { +bool ControllerScriptEngineLegacy::handleIncomingData(const QByteArray& data) { // This function is called from outside the controller engine, so we can't // use VERIFY_OR_DEBUG_ASSERT here if (!m_pJSEngine) { return false; } + QJSValueList args; args << byteArrayToScriptValue(data); args << QJSValue(data.size()); - return ControllerScriptEngineBase::executeFunction(functionObject, args); + + for (const QJSValue& function : m_incomingDataFunctions) { + ControllerScriptEngineBase::executeFunction(function, args); + } + + return true; } bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFile) { diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 22684ade887..9ab40d12ffb 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -23,18 +23,13 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { bool initialize() override; - bool executeIncomingDataFunction(QJSValue functionObject, const QByteArray& data); + bool handleIncomingData(const QByteArray& data); /// Wrap a string of JS code in an anonymous function. This allows any JS /// string that evaluates to a function to be used in MIDI mapping XML files /// and ensures the function is executed with the correct 'this' object. QJSValue wrapFunctionCode(const QString& codeSnippet, int numberOfArgs); - /// Look up registered script function prefixes - const QList& getScriptFunctionPrefixes() { - return m_scriptFunctionPrefixes; - }; - // There is lots of tight coupling between ControllerScriptEngineLegacy // and ControllerScriptInterface. This is probably not worth improving in legacy code. QJSEngine* jsEngine() { @@ -58,6 +53,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { bool bFatalError = false); QList m_scriptFunctionPrefixes; + QList m_incomingDataFunctions; QHash m_scriptWrappedFunctionCache; QList m_scriptFiles; From 9363b497e13e39191655441a32b458f0af1727d8 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 12 Jul 2020 18:22:28 -0500 Subject: [PATCH 10/61] MidiController: rename confusingly overloaded "receive" method --- src/controllers/midi/hss1394controller.cpp | 6 +- src/controllers/midi/midicontroller.cpp | 6 +- src/controllers/midi/midicontroller.h | 6 +- src/controllers/midi/portmidicontroller.cpp | 4 +- src/test/midicontrollertest.cpp | 119 ++++++++++---------- src/test/portmidicontroller_test.cpp | 14 +-- 6 files changed, 80 insertions(+), 75 deletions(-) diff --git a/src/controllers/midi/hss1394controller.cpp b/src/controllers/midi/hss1394controller.cpp index 4e58b966f6a..4d119051c16 100644 --- a/src/controllers/midi/hss1394controller.cpp +++ b/src/controllers/midi/hss1394controller.cpp @@ -112,8 +112,10 @@ int Hss1394Controller::open() { m_pChannelListener = new DeviceChannelListener(this, getName()); connect(m_pChannelListener, SIGNAL(incomingData(QByteArray, mixxx::Duration)), this, SLOT(receive(QByteArray, mixxx::Duration))); - connect(m_pChannelListener, SIGNAL(incomingData(unsigned char, unsigned char, unsigned char, mixxx::Duration)), - this, SLOT(receive(unsigned char, unsigned char, unsigned char, mixxx::Duration))); + connect(m_pChannelListener, + &DeviceChannelListener::incomingData, + this, + &Hss1394Controller::receiveShortMessage); if (!m_pChannel->InstallChannelListener(m_pChannelListener)) { qDebug() << "HSS1394 channel listener could not be installed for device" << getName(); diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index 78273fcf5bb..f60958cfcb0 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -200,8 +200,10 @@ void MidiController::commitTemporaryInputMappings() { m_temporaryInputMappings.clear(); } -void MidiController::receive(unsigned char status, unsigned char control, - unsigned char value, mixxx::Duration timestamp) { +void MidiController::receiveShortMessage(unsigned char status, + unsigned char control, + unsigned char value, + mixxx::Duration timestamp) { // The rest of this function is for legacy mappings unsigned char channel = MidiUtils::channelFromStatus(status); unsigned char opCode = MidiUtils::opCodeFromStatus(status); diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index 0090c12c24e..1cb4f74f457 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -69,8 +69,10 @@ class MidiController : public Controller { } protected slots: - virtual void receive(unsigned char status, unsigned char control, - unsigned char value, mixxx::Duration timestamp); + virtual void receiveShortMessage(unsigned char status, + unsigned char control, + unsigned char value, + mixxx::Duration timestamp); // For receiving System Exclusive messages void receive(const QByteArray data, mixxx::Duration timestamp) override; int close() override; diff --git a/src/controllers/midi/portmidicontroller.cpp b/src/controllers/midi/portmidicontroller.cpp index bf30f0feda7..10213d9b3da 100644 --- a/src/controllers/midi/portmidicontroller.cpp +++ b/src/controllers/midi/portmidicontroller.cpp @@ -149,7 +149,7 @@ bool PortMidiController::poll() { if ((status & 0xF8) == 0xF8) { // Handle real-time MIDI messages at any time - receive(status, 0, 0, timestamp); + receiveShortMessage(status, 0, 0, timestamp); continue; } @@ -163,7 +163,7 @@ bool PortMidiController::poll() { //unsigned char channel = status & 0x0F; unsigned char note = Pm_MessageData1(m_midiBuffer[i].message); unsigned char velocity = Pm_MessageData2(m_midiBuffer[i].message); - receive(status, note, velocity, timestamp); + receiveShortMessage(status, note, velocity, timestamp); } } diff --git a/src/test/midicontrollertest.cpp b/src/test/midicontrollertest.cpp index 4568b6c0691..f1405b65456 100644 --- a/src/test/midicontrollertest.cpp +++ b/src/test/midicontrollertest.cpp @@ -38,10 +38,9 @@ class MidiControllerTest : public MixxxTest { m_pController->visit(&preset); } - void receive(unsigned char status, unsigned char control, - unsigned char value) { + void receiveShortMessage(unsigned char status, unsigned char control, unsigned char value) { // TODO(rryan): This test doesn't care about timestamps. - m_pController->receive(status, control, value, mixxx::Time::elapsed()); + m_pController->receiveShortMessage(status, control, value, mixxx::Time::elapsed()); } MidiControllerPreset m_preset; @@ -64,15 +63,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushOnOff) { loadPreset(m_preset); // Receive an on/off, sets the control on/off with each press. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Receive an on/off, sets the control on/off with each press. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -90,15 +89,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushOnOn) { loadPreset(m_preset); // Receive an on/off, sets the control on/off with each press. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receive(MIDI_NOTE_ON | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Receive an on/off, sets the control on/off with each press. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receive(MIDI_NOTE_ON | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -123,13 +122,13 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_ToggleOnOff_ButtonMidiOpt // NOTE(rryan): This behavior is broken! // Toggle the switch on, sets the push button on. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // The push button is stuck down here! // Toggle the switch off, sets the push button off. - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -154,13 +153,13 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_ToggleOnOff_SwitchMidiOpt // NOTE(rryan): This behavior is broken! // Toggle the switch on, sets the push button on. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // The push button is stuck down here! // Toggle the switch off, sets the push button on again. - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // NOTE(rryan): What is supposed to happen in this case? It's an open @@ -196,15 +195,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushCC) { loadPreset(m_preset); // Receive an on/off, sets the control on/off with each press. - receive(MIDI_CC | channel, control, 0x7F); + receiveShortMessage(MIDI_CC | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receive(MIDI_CC | channel, control, 0x00); + receiveShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Receive an on/off, sets the control on/off with each press. - receive(MIDI_CC | channel, control, 0x7F); + receiveShortMessage(MIDI_CC | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receive(MIDI_CC | channel, control, 0x00); + receiveShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -225,14 +224,14 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushOnOff) { loadPreset(m_preset); // Receive an on/off, toggles the control. - receive(MIDI_NOTE_ON | channel, control, 0x7F); - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // Receive an on/off, toggles the control. - receive(MIDI_NOTE_ON | channel, control, 0x7F); - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -252,14 +251,14 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushOnOn) { loadPreset(m_preset); // Receive an on/off, toggles the control. - receive(MIDI_NOTE_ON | channel, control, 0x7F); - receive(MIDI_NOTE_ON | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // Receive an on/off, toggles the control. - receive(MIDI_NOTE_ON | channel, control, 0x7F); - receive(MIDI_NOTE_ON | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -289,12 +288,12 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_ButtonMidiOption) // Toggle the switch on, since it is interpreted as a button press it // toggles the button on. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // Toggle the switch off, since it is interpreted as a button release it // does nothing to the toggle button. - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); } @@ -324,12 +323,12 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_SwitchMidiOption) // Toggle the switch on, since it is interpreted as a button press it // toggles the control on. - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // Toggle the switch off, since it is interpreted as a button press it // toggles the control off. - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Meanwhile, the GUI toggles the control on again. @@ -339,12 +338,12 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_SwitchMidiOption) // Toggle the switch on, since it is interpreted as a button press it // toggles the control off (since it was on). - receive(MIDI_NOTE_ON | channel, control, 0x7F); + receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Toggle the switch off, since it is interpreted as a button press it // toggles the control on (since it was off). - receive(MIDI_NOTE_OFF | channel, control, 0x00); + receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); } @@ -363,14 +362,14 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushCC) { loadPreset(m_preset); // Receive an on/off, toggles the control. - receive(MIDI_CC | channel, control, 0x7F); - receive(MIDI_CC | channel, control, 0x00); + receiveShortMessage(MIDI_CC | channel, control, 0x7F); + receiveShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // Receive an on/off, toggles the control. - receive(MIDI_CC | channel, control, 0x7F); - receive(MIDI_CC | channel, control, 0x00); + receiveShortMessage(MIDI_CC | channel, control, 0x7F); + receiveShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -391,15 +390,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_7BitCC) { loadPreset(m_preset); // Receive a 0, MIDI parameter should map to the min value. - receive(MIDI_CC | channel, control, 0x00); + receiveShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x7F, MIDI parameter should map to the potmeter max value. - receive(MIDI_CC | channel, control, 0x7F); + receiveShortMessage(MIDI_CC | channel, control, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x40, MIDI parameter should map to the potmeter middle value. - receive(MIDI_CC | channel, control, 0x40); + receiveShortMessage(MIDI_CC | channel, control, 0x40); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); } @@ -434,40 +433,40 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitCC) { // Receive a 0x0000 (lsb-first), MIDI parameter should map to the min value. potmeter.set(0); - receive(MIDI_CC | channel, lsb_control, 0x00); - receive(MIDI_CC | channel, msb_control, 0x00); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x0000 (msb-first), MIDI parameter should map to the min value. potmeter.set(0); - receive(MIDI_CC | channel, msb_control, 0x00); - receive(MIDI_CC | channel, lsb_control, 0x00); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x00); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x3FFF (lsb-first), MIDI parameter should map to the max value. potmeter.set(0); - receive(MIDI_CC | channel, lsb_control, 0x7F); - receive(MIDI_CC | channel, msb_control, 0x7F); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x7F); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x3FFF (msb-first), MIDI parameter should map to the max value. potmeter.set(0); - receive(MIDI_CC | channel, msb_control, 0x7F); - receive(MIDI_CC | channel, lsb_control, 0x7F); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x7F); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x2000 (lsb-first), MIDI parameter should map to the middle // value. potmeter.set(0); - receive(MIDI_CC | channel, lsb_control, 0x00); - receive(MIDI_CC | channel, msb_control, 0x40); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); // Receive a 0x2000 (msb-first), MIDI parameter should map to the middle // value. potmeter.set(0); - receive(MIDI_CC | channel, msb_control, 0x40); - receive(MIDI_CC | channel, lsb_control, 0x00); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); // Check the 14-bit resolution is actually present. Receive a 0x2001 @@ -475,8 +474,8 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitCC) { // amount. Scaling is not quite linear for MIDI parameters so just check // that incrementing the LSB by 1 is greater than the middle value. potmeter.set(0); - receive(MIDI_CC | channel, msb_control, 0x40); - receive(MIDI_CC | channel, lsb_control, 0x01); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x01); EXPECT_LT(kMiddleValue, potmeter.get()); // Check the 14-bit resolution is actually present. Receive a 0x2001 @@ -484,8 +483,8 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitCC) { // amount. Scaling is not quite linear for MIDI parameters so just check // that incrementing the LSB by 1 is greater than the middle value. potmeter.set(0); - receive(MIDI_CC | channel, lsb_control, 0x01); - receive(MIDI_CC | channel, msb_control, 0x40); + receiveShortMessage(MIDI_CC | channel, lsb_control, 0x01); + receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); EXPECT_LT(kMiddleValue, potmeter.get()); } @@ -505,21 +504,21 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitPitchBend) { loadPreset(m_preset); // Receive a 0x0000, MIDI parameter should map to the min value. - receive(MIDI_PITCH_BEND | channel, 0x00, 0x00); + receiveShortMessage(MIDI_PITCH_BEND | channel, 0x00, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x3FFF, MIDI parameter should map to the potmeter max value. - receive(MIDI_PITCH_BEND | channel, 0x7F, 0x7F); + receiveShortMessage(MIDI_PITCH_BEND | channel, 0x7F, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x2000, MIDI parameter should map to the potmeter middle value. - receive(MIDI_PITCH_BEND | channel, 0x00, 0x40); + receiveShortMessage(MIDI_PITCH_BEND | channel, 0x00, 0x40); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); // Check the 14-bit resolution is actually present. Receive a 0x2001, MIDI // parameter should map to the middle value plus a tiny amount. Scaling is // not quite linear for MIDI parameters so just check that incrementing the // LSB by 1 is greater than the middle value. - receive(MIDI_PITCH_BEND | channel, 0x01, 0x40); + receiveShortMessage(MIDI_PITCH_BEND | channel, 0x01, 0x40); EXPECT_LT(kMiddleValue, potmeter.get()); } diff --git a/src/test/portmidicontroller_test.cpp b/src/test/portmidicontroller_test.cpp index 097deb41f42..48d6c5c4855 100644 --- a/src/test/portmidicontroller_test.cpp +++ b/src/test/portmidicontroller_test.cpp @@ -35,8 +35,8 @@ class MockPortMidiController : public PortMidiController { PortMidiController::sendSysexMsg(data, length); } - MOCK_METHOD4(receive, void(unsigned char, unsigned char, unsigned char, - mixxx::Duration)); + MOCK_METHOD4(receiveShortMessage, + void(unsigned char, unsigned char, unsigned char, mixxx::Duration)); MOCK_METHOD2(receive, void(const QByteArray, mixxx::Duration)); }; @@ -228,9 +228,9 @@ TEST_F(PortMidiControllerTest, Poll_Read_Basic) { .WillOnce(DoAll(SetArrayArgument<0>(messages.begin(), messages.end()), Return(messages.size()))); - EXPECT_CALL(*m_pController, receive(0x90, 0x3C, 0x40, _)) + EXPECT_CALL(*m_pController, receiveShortMessage(0x90, 0x3C, 0x40, _)) .InSequence(read); - EXPECT_CALL(*m_pController, receive(0x80, 0x3C, 0x40, _)) + EXPECT_CALL(*m_pController, receiveShortMessage(0x80, 0x3C, 0x40, _)) .InSequence(read); pollDevice(); @@ -265,9 +265,9 @@ TEST_F(PortMidiControllerTest, Poll_Read_SysExWithRealtime) { .InSequence(read) .WillOnce(DoAll(SetArrayArgument<0>(messages.begin(), messages.end()), Return(messages.size()))); - EXPECT_CALL(*m_pController, receive(0xF8, 0x00, 0x00, _)) + EXPECT_CALL(*m_pController, receiveShortMessage(0xF8, 0x00, 0x00, _)) .InSequence(read); - EXPECT_CALL(*m_pController, receive(0xFA, 0x00, 0x00, _)) + EXPECT_CALL(*m_pController, receiveShortMessage(0xFA, 0x00, 0x00, _)) .InSequence(read); EXPECT_CALL(*m_pController, receive(sysex, _)) .InSequence(read); @@ -363,7 +363,7 @@ TEST_F(PortMidiControllerTest, Poll_Read_SysExInterrupted_FollowedByNormalMessag .InSequence(read) .WillOnce(DoAll(SetArrayArgument<0>(messages.begin(), messages.end()), Return(messages.size()))); - EXPECT_CALL(*m_pController, receive(0x90, 0x3C, 0x40, _)) + EXPECT_CALL(*m_pController, receiveShortMessage(0x90, 0x3C, 0x40, _)) .InSequence(read); pollDevice(); From e25bf12ab4bd910745d4359a9b3520b8c7f673c9 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 13 Jul 2020 14:50:23 -0500 Subject: [PATCH 11/61] ControllerScriptEngine: generate wrappers for input callbacks This avoids calling two separate JS functions (one to convert the ArrayBuffer to a Uint8Array then the callback) every time controller input is received. --- .../scripting/controllerscriptenginebase.cpp | 30 ++++++++++--------- .../scripting/controllerscriptenginebase.h | 4 +-- .../controllerscriptmoduleengine.cpp | 4 +-- .../legacy/controllerscriptenginelegacy.cpp | 6 ++-- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 95890fa80b2..858cbfa28bc 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -57,8 +57,20 @@ bool ControllerScriptEngineBase::initialize() { "midi", m_pJSEngine->newQObject(controllerProxy)); } - m_byteArrayToScriptValueJSFunction = m_pJSEngine->evaluate( - "(function(arg1) { return new Uint8Array(arg1) })"); + // Binary data is passed from the Controller as a QByteArray, which + // QJSEngine::toScriptValue converts to an ArrayBuffer in JavaScript. + // ArrayBuffer cannot be accessed with the [] operator in JS; it needs + // to be converted to a typed array (Uint8Array in this case) first. + // This function generates a wrapper function from a JS callback to do + // that conversion automatically. + m_makeArrayBufferWrapperFunction = m_pJSEngine->evaluate(QStringLiteral( + // arg2 is the timestamp for ControllerScriptModuleEngine. + // In ControllerScriptEngineLegacy it is the length of the array. + "(function(callback) {" + " return function(arrayBuffer, arg2) {" + " callback(new Uint8Array(arrayBuffer), arg2);" + " };" + "})")); return true; } @@ -235,16 +247,6 @@ void ControllerScriptEngineBase::throwJSError(const QString& message) { #endif } -QJSValue ControllerScriptEngineBase::byteArrayToScriptValue( - const QByteArray& byteArray) { - // The QJSEngine converts the QByteArray to an ArrayBuffer object. - QJSValue arrayBuffer = m_pJSEngine->toScriptValue(byteArray); - // Convert the ArrayBuffer to a Uint8 typed array so scripts can access its bytes - // with the [] operator. - QJSValue result = - m_byteArrayToScriptValueJSFunction.call(QJSValueList{arrayBuffer}); - if (result.isError()) { - showScriptExceptionDialog(result); - } - return result; +QJSValue ControllerScriptEngineBase::wrapArrayBufferCallback(const QJSValue& callback) { + return m_makeArrayBufferWrapperFunction.call(QJSValueList{callback}); } diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 11b5882ae63..dd50766f8ca 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -42,7 +42,7 @@ class ControllerScriptEngineBase : public QObject { void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); - QJSValue byteArrayToScriptValue(const QByteArray& byteArray); + QJSValue wrapArrayBufferCallback(const QJSValue& callback); bool m_bDisplayingExceptionDialog; QJSEngine* m_pJSEngine; @@ -58,7 +58,7 @@ class ControllerScriptEngineBase : public QObject { void reload(); private: - QJSValue m_byteArrayToScriptValueJSFunction; + QJSValue m_makeArrayBufferWrapperFunction; private slots: void errorDialogButton(const QString& key, QMessageBox::StandardButton button); diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index 5d9cb3efdf0..3d9886de979 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -24,7 +24,7 @@ bool ControllerScriptModuleEngine::initialize() { QJSValue handleInputFunction = mod.property("handleInput"); if (handleInputFunction.isCallable()) { - m_handleInputFunction = handleInputFunction; + m_handleInputFunction = wrapArrayBufferCallback(handleInputFunction); } else { scriptErrorDialog( "Controller JavaScript module exports no handleInput function.", @@ -52,7 +52,7 @@ void ControllerScriptModuleEngine::shutdown() { void ControllerScriptModuleEngine::handleInput( QByteArray data, mixxx::Duration timestamp) { QJSValueList args; - args << byteArrayToScriptValue(data); + args << m_pJSEngine->toScriptValue(data); args << timestamp.toDoubleMillis(); executeFunction(m_handleInputFunction, args); } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index c11551944de..e79c34f6919 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -105,7 +105,9 @@ bool ControllerScriptEngineLegacy::initialize() { continue; } functionName.append(QStringLiteral(".incomingData")); - m_incomingDataFunctions.append(wrapFunctionCode(functionName, 2)); + m_incomingDataFunctions.append( + wrapArrayBufferCallback( + wrapFunctionCode(functionName, 2))); } QJSValueList args; @@ -139,7 +141,7 @@ bool ControllerScriptEngineLegacy::handleIncomingData(const QByteArray& data) { } QJSValueList args; - args << byteArrayToScriptValue(data); + args << m_pJSEngine->toScriptValue(data); args << QJSValue(data.size()); for (const QJSValue& function : m_incomingDataFunctions) { From e8e24879deb47db57c5a139c1ee0b3ba919a8abd Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 13 Jul 2020 15:08:35 -0500 Subject: [PATCH 12/61] HIDController: avoid deep copying input data --- src/controllers/hid/hidcontroller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index f1cbe30ea62..a44934eafa4 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -253,8 +253,8 @@ bool HidController::poll() { return false; } else if (result > 0) { Trace process("HidController process packet"); - QByteArray outData(reinterpret_cast(m_pPollData), result); - receive(outData, mixxx::Time::elapsed()); + QByteArray data = QByteArray::fromRawData(reinterpret_cast(m_pPollData), result); + receive(data, mixxx::Time::elapsed()); } return true; From 26b51ec3ffcf7d465fe3db8e53b9f4f5accedbd3 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 13 Jul 2020 15:14:46 -0500 Subject: [PATCH 13/61] Controller: pass received input QByteArray by reference --- src/controllers/controller.cpp | 2 +- src/controllers/controller.h | 2 +- src/controllers/midi/midicontroller.cpp | 2 +- src/controllers/midi/midicontroller.h | 2 +- src/test/portmidicontroller_test.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index f335d33eeaf..d453f553414 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -111,7 +111,7 @@ void Controller::triggerActivity() m_userActivityInhibitTimer.start(); } } -void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { +void Controller::receive(const QByteArray& data, mixxx::Duration timestamp) { if (m_pScriptEngineLegacy == nullptr) { //qWarning() << "Controller::receive called with no active engine!"; // Don't complain, since this will always show after closing a device as diff --git a/src/controllers/controller.h b/src/controllers/controller.h index 6000a4b07f7..1a04706ac9d 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -82,7 +82,7 @@ class Controller : public QObject, ConstControllerPresetVisitor { // Handles packets of raw bytes and passes them to an ".incomingData" script // function that is assumed to exist. (Sub-classes may want to reimplement // this if they have an alternate way of handling such data.) - virtual void receive(const QByteArray data, mixxx::Duration timestamp); + virtual void receive(const QByteArray& data, mixxx::Duration timestamp); /// Apply the preset to the controller. /// @brief Initializes both controller engine and static output mappings. diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index f60958cfcb0..440abdc5c97 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -470,7 +470,7 @@ double MidiController::computeValue( return newmidivalue; } -void MidiController::receive(QByteArray data, mixxx::Duration timestamp) { +void MidiController::receive(const QByteArray& data, mixxx::Duration timestamp) { controllerDebug(MidiUtils::formatSysexMessage(getName(), data, timestamp)); MidiKey mappingKey(data.at(0), 0xFF); diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index 1cb4f74f457..36a4f68aa43 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -74,7 +74,7 @@ class MidiController : public Controller { unsigned char value, mixxx::Duration timestamp); // For receiving System Exclusive messages - void receive(const QByteArray data, mixxx::Duration timestamp) override; + void receive(const QByteArray& data, mixxx::Duration timestamp) override; int close() override; private slots: diff --git a/src/test/portmidicontroller_test.cpp b/src/test/portmidicontroller_test.cpp index 48d6c5c4855..d090de7c2b6 100644 --- a/src/test/portmidicontroller_test.cpp +++ b/src/test/portmidicontroller_test.cpp @@ -37,7 +37,7 @@ class MockPortMidiController : public PortMidiController { MOCK_METHOD4(receiveShortMessage, void(unsigned char, unsigned char, unsigned char, mixxx::Duration)); - MOCK_METHOD2(receive, void(const QByteArray, mixxx::Duration)); + MOCK_METHOD2(receive, void(const QByteArray&, mixxx::Duration)); }; class MockPortMidiDevice : public PortMidiDevice { From e99cef375015010a1ee0c0cf32be57744fda37a1 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 13 Jul 2020 15:26:55 -0500 Subject: [PATCH 14/61] BulkController: avoid deep copying input data --- src/controllers/bulk/bulkcontroller.cpp | 5 +++-- src/controllers/bulk/bulkcontroller.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/controllers/bulk/bulkcontroller.cpp b/src/controllers/bulk/bulkcontroller.cpp index 271c3b100b7..684a083f364 100644 --- a/src/controllers/bulk/bulkcontroller.cpp +++ b/src/controllers/bulk/bulkcontroller.cpp @@ -52,8 +52,9 @@ void BulkReader::run() { if (result >= 0) { Trace process("BulkReader process packet"); //qDebug() << "Read" << result << "bytes, pointer:" << data; - QByteArray outData((char*)data, transferred); - emit incomingData(outData, mixxx::Time::elapsed()); + QByteArray byteArray = QByteArray::fromRawData( + reinterpret_cast(data), transferred); + emit incomingData(byteArray, mixxx::Time::elapsed()); } } qDebug() << "Stopped Reader"; diff --git a/src/controllers/bulk/bulkcontroller.h b/src/controllers/bulk/bulkcontroller.h index b51b8c29c32..fa55b379e5f 100644 --- a/src/controllers/bulk/bulkcontroller.h +++ b/src/controllers/bulk/bulkcontroller.h @@ -21,7 +21,7 @@ class BulkReader : public QThread { void stop(); signals: - void incomingData(QByteArray data, mixxx::Duration timestamp); + void incomingData(const QByteArray& data, mixxx::Duration timestamp); protected: void run(); From f6f7050296af350b62c01d149599e17e15b69509 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 13 Jul 2020 18:07:12 -0500 Subject: [PATCH 15/61] ControllerScriptModuleEngine: pass raw ArrayBuffer to input callback ArrayBuffer is more useful for HID and perhaps other use cases. For backwards compatibility, continue to convert the ArrayBuffer to a Uint8Array in ControllerScriptEngineLegacy following discussion on https://github.com/mixxxdj/mixxx/pull/2920/files/9363b497e13e39191655441a32b458f0af1727d8..e25bf12ab4bd910745d4359a9b3520b8c7f673c9#diff-69d807d0e5894e9f5bf1bd447ca878ab --- src/controllers/scripting/controllerscriptmoduleengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index 3d9886de979..e5b64477c0b 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -24,7 +24,7 @@ bool ControllerScriptModuleEngine::initialize() { QJSValue handleInputFunction = mod.property("handleInput"); if (handleInputFunction.isCallable()) { - m_handleInputFunction = wrapArrayBufferCallback(handleInputFunction); + m_handleInputFunction = handleInputFunction; } else { scriptErrorDialog( "Controller JavaScript module exports no handleInput function.", From b4d2d213b387493a581cda8744506d4cf8cbe46e Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 14 Jul 2020 09:11:00 -0500 Subject: [PATCH 16/61] Hss1394Controller: fix build --- src/controllers/midi/hss1394controller.cpp | 26 +++++++++++++--------- src/controllers/midi/hss1394controller.h | 9 +++++--- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/controllers/midi/hss1394controller.cpp b/src/controllers/midi/hss1394controller.cpp index 4d119051c16..ebede71d641 100644 --- a/src/controllers/midi/hss1394controller.cpp +++ b/src/controllers/midi/hss1394controller.cpp @@ -40,7 +40,7 @@ void DeviceChannelListener::Process(const hss1394::uint8 *pBuffer, hss1394::uint if (i + 2 < uBufferSize) { note = pBuffer[i+1]; velocity = pBuffer[i+2]; - emit incomingData(status, note, velocity, timestamp); + emit receiveShortMessage(status, note, velocity, timestamp); } else { qWarning() << "Buffer underflow in DeviceChannelListener::Process()"; } @@ -48,8 +48,8 @@ void DeviceChannelListener::Process(const hss1394::uint8 *pBuffer, hss1394::uint break; default: // Handle platter messages and any others that are not 3 bytes - QByteArray outArray((char*)pBuffer,uBufferSize); - emit incomingData(outArray, timestamp); + QByteArray outArray = QByteArray::fromRawData((char*)pBuffer, uBufferSize); + emit receiveSysex(outArray, timestamp); i = uBufferSize; break; } @@ -110,12 +110,14 @@ int Hss1394Controller::open() { } m_pChannelListener = new DeviceChannelListener(this, getName()); - connect(m_pChannelListener, SIGNAL(incomingData(QByteArray, mixxx::Duration)), - this, SLOT(receive(QByteArray, mixxx::Duration))); connect(m_pChannelListener, - &DeviceChannelListener::incomingData, + &DeviceChannelListener::receiveShortMessage, this, &Hss1394Controller::receiveShortMessage); + connect(m_pChannelListener, + &DeviceChannelListener::receiveSysex, + this, + &Hss1394Controller::receive); if (!m_pChannel->InstallChannelListener(m_pChannelListener)) { qDebug() << "HSS1394 channel listener could not be installed for device" << getName(); @@ -150,10 +152,14 @@ int Hss1394Controller::close() { return -1; } - disconnect(m_pChannelListener, SIGNAL(incomingData(QByteArray, mixxx::Duration)), - this, SLOT(receive(QByteArray, mixxx::Duration))); - disconnect(m_pChannelListener, SIGNAL(incomingData(unsigned char, unsigned char, unsigned char, mixxx::Duration)), - this, SLOT(receive(unsigned char, unsigned char, unsigned char, mixxx::Duration))); + disconnect(m_pChannelListener, + &DeviceChannelListener::receiveShortMessage, + this, + &Hss1394Controller::receiveShortMessage); + disconnect(m_pChannelListener, + &DeviceChannelListener::receiveSysex, + this, + &Hss1394Controller::receive); stopEngine(); MidiController::close(); diff --git a/src/controllers/midi/hss1394controller.h b/src/controllers/midi/hss1394controller.h index c4a0045c59f..a7b0fca4160 100644 --- a/src/controllers/midi/hss1394controller.h +++ b/src/controllers/midi/hss1394controller.h @@ -33,9 +33,12 @@ class DeviceChannelListener : public QObject, public hss1394::ChannelListener { void Disconnected(); void Reconnected(); signals: - void incomingData(unsigned char status, unsigned char control, unsigned char value, - mixxx::Duration timestamp); - void incomingData(QByteArray data, mixxx::Duration timestamp); + void receiveShortMessage(unsigned char status, + unsigned char control, + unsigned char value, + mixxx::Duration timestamp); + void receiveSysex(const QByteArray& data, mixxx::Duration timestamp); + private: QString m_sName; }; From 2046add570e061a2fc4b793a20809a2996a95471 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:00:58 -0500 Subject: [PATCH 17/61] change explicit comparisons to nullptr to implicit --- src/controllers/controller.cpp | 8 ++++---- src/controllers/scripting/controllerscriptenginebase.cpp | 2 +- .../scripting/legacy/controllerscriptenginelegacy.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 4d635e244d7..6acaddb37a4 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -34,7 +34,7 @@ ControllerJSProxy* Controller::jsProxy() { void Controller::startEngine() { controllerDebug(" Starting engine"); - if (m_pScriptEngineLegacy != nullptr) { + if (m_pScriptEngineLegacy) { qWarning() << "Controller: Engine already exists! Restarting:"; stopEngine(); } @@ -43,7 +43,7 @@ void Controller::startEngine() void Controller::stopEngine() { controllerDebug(" Shutting down engine"); - if (m_pScriptEngineLegacy == nullptr) { + if (!m_pScriptEngineLegacy) { qWarning() << "Controller::stopEngine(): No engine exists!"; return; } @@ -57,7 +57,7 @@ bool Controller::applyPreset(bool initializeScripts) { const ControllerPreset* pPreset = preset(); // Load the script code into the engine - if (m_pScriptEngineLegacy == nullptr) { + if (!m_pScriptEngineLegacy) { qWarning() << "Controller::applyPreset(): No engine exists!"; return false; } @@ -112,7 +112,7 @@ void Controller::triggerActivity() } } void Controller::receive(const QByteArray& data, mixxx::Duration timestamp) { - if (m_pScriptEngineLegacy == nullptr) { + if (!m_pScriptEngineLegacy) { //qWarning() << "Controller::receive called with no active engine!"; // Don't complain, since this will always show after closing a device as // queued signals flush out diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 858cbfa28bc..100a5c9f0a8 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -80,7 +80,7 @@ void ControllerScriptEngineBase::shutdown() { // Delete the script engine, first clearing the pointer so that // other threads will not get the dead pointer after we delete it. - if (m_pJSEngine != nullptr) { + if (m_pJSEngine) { QJSEngine* engine = m_pJSEngine; m_pJSEngine = nullptr; engine->deleteLater(); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index e79c34f6919..d01464a6e3d 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -51,7 +51,7 @@ QJSValue ControllerScriptEngineLegacy::wrapFunctionCode( const QString& codeSnippet, int numberOfArgs) { // This function is called from outside the controller engine, so we can't // use VERIFY_OR_DEBUG_ASSERT here - if (m_pJSEngine == nullptr) { + if (!m_pJSEngine) { return QJSValue(); } From a691d32630bf75ec6ae8ef201ca6cebf1e900ca9 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:01:37 -0500 Subject: [PATCH 18/61] HSS1394Controller: replace C cast with reinterpret_cast --- src/controllers/midi/hss1394controller.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/midi/hss1394controller.cpp b/src/controllers/midi/hss1394controller.cpp index 2b6cb9ac0c4..95f30864c51 100644 --- a/src/controllers/midi/hss1394controller.cpp +++ b/src/controllers/midi/hss1394controller.cpp @@ -48,7 +48,8 @@ void DeviceChannelListener::Process(const hss1394::uint8 *pBuffer, hss1394::uint break; default: // Handle platter messages and any others that are not 3 bytes - QByteArray outArray = QByteArray::fromRawData((char*)pBuffer, uBufferSize); + QByteArray outArray = QByteArray::fromRawData( + reinterpret_cast(pBuffer), uBufferSize); emit receiveSysex(outArray, timestamp); i = uBufferSize; break; From 487f8f9abcca9883a8206b76ff2c1eda40a07333 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:02:01 -0500 Subject: [PATCH 19/61] ControllerScriptEngineLegacy: make iterator const --- .../scripting/legacy/controllerscriptenginelegacy.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index d01464a6e3d..9e0aa981085 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -57,9 +57,9 @@ QJSValue ControllerScriptEngineLegacy::wrapFunctionCode( QJSValue wrappedFunction; - auto i = m_scriptWrappedFunctionCache.constFind(codeSnippet); - if (i != m_scriptWrappedFunctionCache.constEnd()) { - wrappedFunction = i.value(); + const auto it = m_scriptWrappedFunctionCache.constFind(codeSnippet); + if (it != m_scriptWrappedFunctionCache.constEnd()) { + wrappedFunction = it.value(); } else { QStringList wrapperArgList; for (int i = 1; i <= numberOfArgs; i++) { From 2b6eb9fc0ad73273e22f8cd0ddd8937e6f282a85 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:19:29 -0500 Subject: [PATCH 20/61] rename ControllerScriptInterface to ControllerScriptInterfaceLegacy --- CMakeLists.txt | 2 +- build/depends.py | 2 +- .../scripting/controllerscriptenginebase.cpp | 1 - .../legacy/controllerscriptenginelegacy.cpp | 6 +- .../legacy/controllerscriptenginelegacy.h | 4 +- ...pp => controllerscriptinterfacelegacy.cpp} | 66 +++++++++---------- ...ce.h => controllerscriptinterfacelegacy.h} | 8 +-- .../scripting/legacy/scriptconnection.h | 4 +- .../legacy/scriptconnectionjsproxy.cpp | 2 +- 9 files changed, 47 insertions(+), 48 deletions(-) rename src/controllers/scripting/legacy/{controllerscriptinterface.cpp => controllerscriptinterfacelegacy.cpp} (92%) rename src/controllers/scripting/legacy/{controllerscriptinterface.h => controllerscriptinterfacelegacy.h} (92%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1ed910e166..2f301064d82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/controllers/scripting/colormapper.cpp src/controllers/scripting/colormapperjsproxy.cpp src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp - src/controllers/scripting/legacy/controllerscriptinterface.cpp + src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp src/controllers/scripting/legacy/scriptconnection.cpp src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp src/controllers/keyboard/keyboardeventfilter.cpp diff --git a/build/depends.py b/build/depends.py index 875dc4af490..dce956016f0 100644 --- a/build/depends.py +++ b/build/depends.py @@ -927,7 +927,7 @@ def sources(self, build): "src/controllers/scripting/colormapper.cpp", "src/controllers/scripting/colormapperjsproxy.cpp", "src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp", - "src/controllers/scripting/legacy/controllerscriptinterface.cpp", + "src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp", "src/controllers/scripting/legacy/scriptconnection.cpp", "src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp", "src/controllers/midi/midimessage.cpp", diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 100a5c9f0a8..efc9f2d3850 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -4,7 +4,6 @@ #include "controllers/controller.h" #include "controllers/controllerdebug.h" #include "controllers/scripting/colormapperjsproxy.h" -#include "controllers/scripting/legacy/controllerscriptinterface.h" #include "errordialoghandler.h" #include "mixer/playermanager.h" diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 9e0aa981085..100917c2a6e 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -4,7 +4,7 @@ #include "controllers/controller.h" #include "controllers/controllerdebug.h" #include "controllers/scripting/colormapperjsproxy.h" -#include "controllers/scripting/legacy/controllerscriptinterface.h" +#include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" #include "errordialoghandler.h" #include "mixer/playermanager.h" @@ -84,8 +84,8 @@ bool ControllerScriptEngineLegacy::initialize() { // Make this ControllerScriptHandler instance available to scripts as 'engine'. QJSValue engineGlobalObject = m_pJSEngine->globalObject(); - ControllerScriptInterface* legacyScriptInterface = - new ControllerScriptInterface(this); + ControllerScriptInterfaceLegacy* legacyScriptInterface = + new ControllerScriptInterfaceLegacy(this); engineGlobalObject.setProperty( "engine", m_pJSEngine->newQObject(legacyScriptInterface)); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 9ab40d12ffb..611bb79b43b 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -10,7 +10,7 @@ #include "util/duration.h" class Controller; -class ControllerScriptInterface; +class ControllerScriptInterfaceLegacy; class EvaluationException; class ScriptConnection; @@ -58,7 +58,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QList m_scriptFiles; friend class ScriptConnection; - friend class ControllerScriptInterface; + friend class ControllerScriptInterfaceLegacy; friend class ColorJSProxy; friend class ColorMapperJSProxy; friend class ControllerEngineTest; diff --git a/src/controllers/scripting/legacy/controllerscriptinterface.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp similarity index 92% rename from src/controllers/scripting/legacy/controllerscriptinterface.cpp rename to src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 041e9751aba..dac64558c0b 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterface.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -1,4 +1,4 @@ -#include "controllerscriptinterface.h" +#include "controllerscriptinterfacelegacy.h" #include "control/controlobject.h" #include "control/controlobjectscript.h" @@ -18,7 +18,7 @@ const int kScratchTimerMs = 1; const double kAlphaBetaDt = kScratchTimerMs / 1000.0; } // anonymous namespace -ControllerScriptInterface::ControllerScriptInterface(ControllerScriptEngineLegacy* m_pEngine) +ControllerScriptInterfaceLegacy::ControllerScriptInterfaceLegacy(ControllerScriptEngineLegacy* m_pEngine) : m_pScriptEngineLegacy(m_pEngine) { // Pre-allocate arrays for average number of virtual decks m_intervalAccumulator.resize(kDecks); @@ -38,7 +38,7 @@ ControllerScriptInterface::ControllerScriptInterface(ControllerScriptEngineLegac } } -ControllerScriptInterface::~ControllerScriptInterface() { +ControllerScriptInterfaceLegacy::~ControllerScriptInterfaceLegacy() { // Stop all timers QMutableHashIterator i(m_timers); while (i.hasNext()) { @@ -81,7 +81,7 @@ ControllerScriptInterface::~ControllerScriptInterface() { } } -ControlObjectScript* ControllerScriptInterface::getControlObjectScript( +ControlObjectScript* ControllerScriptInterfaceLegacy::getControlObjectScript( const QString& group, const QString& name) { ConfigKey key = ConfigKey(group, name); ControlObjectScript* coScript = m_controlCache.value(key, nullptr); @@ -98,7 +98,7 @@ ControlObjectScript* ControllerScriptInterface::getControlObjectScript( return coScript; } -double ControllerScriptInterface::getValue(QString group, QString name) { +double ControllerScriptInterfaceLegacy::getValue(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { qWarning() << "ControllerScriptInterface: Unknown control" << group << name @@ -108,7 +108,7 @@ double ControllerScriptInterface::getValue(QString group, QString name) { return coScript->get(); } -void ControllerScriptInterface::setValue( +void ControllerScriptInterfaceLegacy::setValue( QString group, QString name, double newValue) { if (isnan(newValue)) { qWarning() << "ControllerScriptInterface: script setting [" << group << "," @@ -129,7 +129,7 @@ void ControllerScriptInterface::setValue( } } -double ControllerScriptInterface::getParameter(QString group, QString name) { +double ControllerScriptInterfaceLegacy::getParameter(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { qWarning() << "ControllerScriptInterface: Unknown control" << group << name @@ -139,7 +139,7 @@ double ControllerScriptInterface::getParameter(QString group, QString name) { return coScript->getParameter(); } -void ControllerScriptInterface::setParameter( +void ControllerScriptInterfaceLegacy::setParameter( QString group, QString name, double newParameter) { if (isnan(newParameter)) { qWarning() << "ControllerScriptInterface: script setting [" << group << "," @@ -158,7 +158,7 @@ void ControllerScriptInterface::setParameter( } } -double ControllerScriptInterface::getParameterForValue( +double ControllerScriptInterfaceLegacy::getParameterForValue( QString group, QString name, double value) { if (isnan(value)) { qWarning() << "ControllerScriptInterface: script setting [" << group << "," @@ -177,14 +177,14 @@ double ControllerScriptInterface::getParameterForValue( return coScript->getParameterForValue(value); } -void ControllerScriptInterface::reset(QString group, QString name) { +void ControllerScriptInterfaceLegacy::reset(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript != nullptr) { coScript->reset(); } } -double ControllerScriptInterface::getDefaultValue(QString group, QString name) { +double ControllerScriptInterfaceLegacy::getDefaultValue(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { @@ -196,7 +196,7 @@ double ControllerScriptInterface::getDefaultValue(QString group, QString name) { return coScript->getDefault(); } -double ControllerScriptInterface::getDefaultParameter( +double ControllerScriptInterfaceLegacy::getDefaultParameter( QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); @@ -209,7 +209,7 @@ double ControllerScriptInterface::getDefaultParameter( return coScript->getParameterForValue(coScript->getDefault()); } -QJSValue ControllerScriptInterface::makeConnection( +QJSValue ControllerScriptInterfaceLegacy::makeConnection( QString group, QString name, const QJSValue callback) { QJSEngine* jsEngine = m_pScriptEngineLegacy->jsEngine(); VERIFY_OR_DEBUG_ASSERT(jsEngine) { @@ -252,7 +252,7 @@ QJSValue ControllerScriptInterface::makeConnection( return QJSValue(); } -bool ControllerScriptInterface::removeScriptConnection( +bool ControllerScriptInterfaceLegacy::removeScriptConnection( const ScriptConnection connection) { ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); @@ -264,7 +264,7 @@ bool ControllerScriptInterface::removeScriptConnection( return coScript->removeScriptConnection(connection); } -void ControllerScriptInterface::triggerScriptConnection( +void ControllerScriptInterfaceLegacy::triggerScriptConnection( const ScriptConnection connection) { VERIFY_OR_DEBUG_ASSERT(m_pScriptEngineLegacy->jsEngine()) { return; @@ -287,7 +287,7 @@ void ControllerScriptInterface::triggerScriptConnection( // it is disconnected. // WARNING: These behaviors are quirky and confusing, so if you change this function, // be sure to run the ControllerScriptInterfaceTest suite to make sure you do not break old scripts. -QJSValue ControllerScriptInterface::connectControl( +QJSValue ControllerScriptInterfaceLegacy::connectControl( QString group, QString name, QJSValue passedCallback, bool disconnect) { // The passedCallback may or may not actually be a function, so when // the actual callback function is found, store it in this variable. @@ -403,17 +403,17 @@ QJSValue ControllerScriptInterface::connectControl( return makeConnection(group, name, actualCallbackFunction); } -void ControllerScriptInterface::trigger(QString group, QString name) { +void ControllerScriptInterfaceLegacy::trigger(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript != nullptr) { coScript->emitValueChanged(); } } -void ControllerScriptInterface::log(QString message) { +void ControllerScriptInterfaceLegacy::log(QString message) { controllerDebug(message); } -int ControllerScriptInterface::beginTimer( +int ControllerScriptInterfaceLegacy::beginTimer( int intervalMillis, QJSValue timerCallback, bool oneShot) { if (timerCallback.isString()) { timerCallback = m_pScriptEngineLegacy->jsEngine()->evaluate(timerCallback.toString()); @@ -453,7 +453,7 @@ int ControllerScriptInterface::beginTimer( return timerId; } -void ControllerScriptInterface::stopTimer(int timerId) { +void ControllerScriptInterfaceLegacy::stopTimer(int timerId) { if (!m_timers.contains(timerId)) { qWarning() << "Killing timer" << timerId << ": That timer does not exist!"; @@ -464,7 +464,7 @@ void ControllerScriptInterface::stopTimer(int timerId) { m_timers.remove(timerId); } -void ControllerScriptInterface::timerEvent(QTimerEvent* event) { +void ControllerScriptInterfaceLegacy::timerEvent(QTimerEvent* event) { int timerId = event->timerId(); // See if this is a scratching timer @@ -492,7 +492,7 @@ void ControllerScriptInterface::timerEvent(QTimerEvent* event) { m_pScriptEngineLegacy->executeFunction(timerTarget.callback, QJSValueList()); } -void ControllerScriptInterface::softTakeover( +void ControllerScriptInterfaceLegacy::softTakeover( QString group, QString name, bool set) { ControlObject* pControl = ControlObject::getControl( ConfigKey(group, name), ControlFlag::AllowMissingOrInvalid); @@ -506,7 +506,7 @@ void ControllerScriptInterface::softTakeover( } } -void ControllerScriptInterface::softTakeoverIgnoreNextValue( +void ControllerScriptInterfaceLegacy::softTakeoverIgnoreNextValue( QString group, const QString name) { ControlObject* pControl = ControlObject::getControl( ConfigKey(group, name), ControlFlag::AllowMissingOrInvalid); @@ -517,7 +517,7 @@ void ControllerScriptInterface::softTakeoverIgnoreNextValue( m_st.ignoreNext(pControl); } -double ControllerScriptInterface::getDeckRate(const QString& group) { +double ControllerScriptInterfaceLegacy::getDeckRate(const QString& group) { double rate = 0.0; ControlObjectScript* pRateRatio = getControlObjectScript(group, "rate_ratio"); @@ -533,7 +533,7 @@ double ControllerScriptInterface::getDeckRate(const QString& group) { return rate; } -bool ControllerScriptInterface::isDeckPlaying(const QString& group) { +bool ControllerScriptInterfaceLegacy::isDeckPlaying(const QString& group) { ControlObjectScript* pPlay = getControlObjectScript(group, "play"); if (pPlay == nullptr) { @@ -545,7 +545,7 @@ bool ControllerScriptInterface::isDeckPlaying(const QString& group) { return pPlay->get() > 0.0; } -void ControllerScriptInterface::scratchEnable(int deck, +void ControllerScriptInterfaceLegacy::scratchEnable(int deck, int intervalsPerRev, double rpm, double alpha, @@ -621,12 +621,12 @@ void ControllerScriptInterface::scratchEnable(int deck, } } -void ControllerScriptInterface::scratchTick(int deck, int interval) { +void ControllerScriptInterfaceLegacy::scratchTick(int deck, int interval) { m_lastMovement[deck] = mixxx::Time::elapsed(); m_intervalAccumulator[deck] += interval; } -void ControllerScriptInterface::scratchProcess(int timerId) { +void ControllerScriptInterfaceLegacy::scratchProcess(int timerId) { int deck = m_scratchTimers[timerId]; // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -709,7 +709,7 @@ void ControllerScriptInterface::scratchProcess(int timerId) { } } -void ControllerScriptInterface::scratchDisable(int deck, bool ramp) { +void ControllerScriptInterfaceLegacy::scratchDisable(int deck, bool ramp) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -733,18 +733,18 @@ void ControllerScriptInterface::scratchDisable(int deck, bool ramp) { m_ramp[deck] = true; // Activate the ramping in scratchProcess() } -bool ControllerScriptInterface::isScratching(int deck) { +bool ControllerScriptInterfaceLegacy::isScratching(int deck) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); return getValue(group, "scratch2_enable") > 0; } -void ControllerScriptInterface::spinback(int deck, bool activate, double factor, double rate) { +void ControllerScriptInterfaceLegacy::spinback(int deck, bool activate, double factor, double rate) { // defaults for args set in header file brake(deck, activate, factor, rate); } -void ControllerScriptInterface::brake(int deck, bool activate, double factor, double rate) { +void ControllerScriptInterfaceLegacy::brake(int deck, bool activate, double factor, double rate) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); @@ -807,7 +807,7 @@ void ControllerScriptInterface::brake(int deck, bool activate, double factor, do } } -void ControllerScriptInterface::softStart(int deck, bool activate, double factor) { +void ControllerScriptInterfaceLegacy::softStart(int deck, bool activate, double factor) { // PlayerManager::groupForDeck is 0-indexed. QString group = PlayerManager::groupForDeck(deck - 1); diff --git a/src/controllers/scripting/legacy/controllerscriptinterface.h b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h similarity index 92% rename from src/controllers/scripting/legacy/controllerscriptinterface.h rename to src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h index 97da98e48d2..62ebabc3f53 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterface.h +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h @@ -11,14 +11,14 @@ class ControlObjectScript; class ScriptConnection; class ConfigKey; -/// ControllerScriptInterface is the legacy API for controller scripts to interact +/// ControllerScriptInterfaceLegacy is the legacy API for controller scripts to interact /// with Mixxx. It is inserted into the JS environment as the "engine" object. -class ControllerScriptInterface : public QObject { +class ControllerScriptInterfaceLegacy : public QObject { Q_OBJECT public: - ControllerScriptInterface(ControllerScriptEngineLegacy* m_pEngine); + ControllerScriptInterfaceLegacy(ControllerScriptEngineLegacy* m_pEngine); - virtual ~ControllerScriptInterface(); + virtual ~ControllerScriptInterfaceLegacy(); Q_INVOKABLE double getValue(QString group, QString name); Q_INVOKABLE void setValue(QString group, QString name, double newValue); diff --git a/src/controllers/scripting/legacy/scriptconnection.h b/src/controllers/scripting/legacy/scriptconnection.h index e7174429429..f2e6558e64b 100644 --- a/src/controllers/scripting/legacy/scriptconnection.h +++ b/src/controllers/scripting/legacy/scriptconnection.h @@ -6,7 +6,7 @@ #include "preferences/configobject.h" class ControllerScriptEngineLegacy; -class ControllerScriptInterface; +class ControllerScriptInterfaceLegacy; /// ScriptConnection is a connection between a ControlObject and a /// script callback function that gets executed when the value @@ -16,7 +16,7 @@ class ScriptConnection { ConfigKey key; QUuid id; QJSValue callback; - ControllerScriptInterface* engineJSProxy; + ControllerScriptInterfaceLegacy* engineJSProxy; ControllerScriptEngineLegacy* controllerEngine; void executeCallback(double value) const; diff --git a/src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp b/src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp index 9103c917c3f..7ba44b18c4d 100644 --- a/src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp +++ b/src/controllers/scripting/legacy/scriptconnectionjsproxy.cpp @@ -1,6 +1,6 @@ #include "controllers/scripting/legacy/scriptconnectionjsproxy.h" -#include "controllers/scripting/legacy/controllerscriptinterface.h" +#include "controllers/scripting/legacy/controllerscriptinterfacelegacy.h" bool ScriptConnectionJSProxy::disconnect() { // if the removeScriptConnection succeeded, the connection has been successfully disconnected From fa16b574bb286329955c809765a1eb11946e429e Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:28:01 -0500 Subject: [PATCH 21/61] ControllerScriptInterfaceLegacy: clang-format fixes --- .../legacy/controllerscriptinterfacelegacy.cpp | 6 ++++-- .../legacy/controllerscriptinterfacelegacy.h | 12 ++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index dac64558c0b..24c19c86673 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -18,7 +18,8 @@ const int kScratchTimerMs = 1; const double kAlphaBetaDt = kScratchTimerMs / 1000.0; } // anonymous namespace -ControllerScriptInterfaceLegacy::ControllerScriptInterfaceLegacy(ControllerScriptEngineLegacy* m_pEngine) +ControllerScriptInterfaceLegacy::ControllerScriptInterfaceLegacy( + ControllerScriptEngineLegacy* m_pEngine) : m_pScriptEngineLegacy(m_pEngine) { // Pre-allocate arrays for average number of virtual decks m_intervalAccumulator.resize(kDecks); @@ -739,7 +740,8 @@ bool ControllerScriptInterfaceLegacy::isScratching(int deck) { return getValue(group, "scratch2_enable") > 0; } -void ControllerScriptInterfaceLegacy::spinback(int deck, bool activate, double factor, double rate) { +void ControllerScriptInterfaceLegacy::spinback( + int deck, bool activate, double factor, double rate) { // defaults for args set in header file brake(deck, activate, factor, rate); } diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h index 62ebabc3f53..2043b6beadc 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h @@ -30,13 +30,21 @@ class ControllerScriptInterfaceLegacy : public QObject { Q_INVOKABLE double getDefaultParameter(QString group, QString name); Q_INVOKABLE QJSValue makeConnection(QString group, QString name, const QJSValue callback); // DEPRECATED: Use makeConnection instead. - Q_INVOKABLE QJSValue connectControl(QString group, QString name, const QJSValue passedCallback, bool disconnect = false); + Q_INVOKABLE QJSValue connectControl(QString group, + QString name, + const QJSValue passedCallback, + bool disconnect = false); // Called indirectly by the objects returned by connectControl Q_INVOKABLE void trigger(QString group, QString name); Q_INVOKABLE void log(QString message); Q_INVOKABLE int beginTimer(int interval, QJSValue scriptCode, bool oneShot = false); Q_INVOKABLE void stopTimer(int timerId); - Q_INVOKABLE void scratchEnable(int deck, int intervalsPerRev, double rpm, double alpha, double beta, bool ramp = true); + Q_INVOKABLE void scratchEnable(int deck, + int intervalsPerRev, + double rpm, + double alpha, + double beta, + bool ramp = true); Q_INVOKABLE void scratchTick(int deck, int interval); Q_INVOKABLE void scratchDisable(int deck, bool ramp = true); Q_INVOKABLE bool isScratching(int deck); From 427ba5ef310bf2b29ce49a4dcbad6228efe12fbb Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:30:01 -0500 Subject: [PATCH 22/61] assert for missing ControlObjects with --controllerDebug CLI option --- src/control/controlobjectscript.cpp | 6 ++++-- src/control/controlobjectscript.h | 1 - src/controllers/controllerdebug.h | 9 +++++++++ .../scripting/legacy/controllerscriptinterfacelegacy.cpp | 8 ++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/control/controlobjectscript.cpp b/src/control/controlobjectscript.cpp index fb997ad17f4..1765a5453ec 100644 --- a/src/control/controlobjectscript.cpp +++ b/src/control/controlobjectscript.cpp @@ -1,9 +1,11 @@ +#include "control/controlobjectscript.h" + #include -#include "control/controlobjectscript.h" +#include "controllers/controllerdebug.h" ControlObjectScript::ControlObjectScript(const ConfigKey& key, QObject* pParent) - : ControlProxy(key, pParent, ControlFlag::AllowMissingOrInvalid) { + : ControlProxy(key, pParent, ControllerDebug::shouldAssertForInvalidControlObjects()) { } bool ControlObjectScript::addScriptConnection(const ScriptConnection& conn) { diff --git a/src/control/controlobjectscript.h b/src/control/controlobjectscript.h index 9c046efed18..2c2c0a60d67 100644 --- a/src/control/controlobjectscript.h +++ b/src/control/controlobjectscript.h @@ -4,7 +4,6 @@ #include #include "control/controlproxy.h" -#include "controllers/controllerdebug.h" #include "controllers/scripting/legacy/scriptconnection.h" // this is used for communicate with controller scripts diff --git a/src/controllers/controllerdebug.h b/src/controllers/controllerdebug.h index 96bd08ba63c..63e42009dd0 100644 --- a/src/controllers/controllerdebug.h +++ b/src/controllers/controllerdebug.h @@ -3,6 +3,7 @@ #include +#include "control/control.h" // Specifies whether or not we should dump incoming data to the console at // runtime. This is useful for end-user debugging and script-writing. @@ -24,6 +25,14 @@ class ControllerDebug { s_enabled = false; } + static ControlFlags shouldAssertForInvalidControlObjects() { + if (enabled()) { + return ControlFlag::None; + } + + return ControlFlag::AllowMissingOrInvalid; + } + private: ControllerDebug() = delete; diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 24c19c86673..458bf87e762 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -121,7 +121,7 @@ void ControllerScriptInterfaceLegacy::setValue( if (coScript != nullptr) { ControlObject* pControl = ControlObject::getControl( - coScript->getKey(), ControlFlag::AllowMissingOrInvalid); + coScript->getKey(), ControllerDebug::shouldAssertForInvalidControlObjects()); if (pControl && !m_st.ignore( pControl, coScript->getParameterForValue(newValue))) { @@ -152,7 +152,7 @@ void ControllerScriptInterfaceLegacy::setParameter( if (coScript != nullptr) { ControlObject* pControl = ControlObject::getControl( - coScript->getKey(), ControlFlag::AllowMissingOrInvalid); + coScript->getKey(), ControllerDebug::shouldAssertForInvalidControlObjects()); if (pControl && !m_st.ignore(pControl, newParameter)) { coScript->setParameter(newParameter); } @@ -496,7 +496,7 @@ void ControllerScriptInterfaceLegacy::timerEvent(QTimerEvent* event) { void ControllerScriptInterfaceLegacy::softTakeover( QString group, QString name, bool set) { ControlObject* pControl = ControlObject::getControl( - ConfigKey(group, name), ControlFlag::AllowMissingOrInvalid); + ConfigKey(group, name), ControllerDebug::shouldAssertForInvalidControlObjects()); if (!pControl) { return; } @@ -510,7 +510,7 @@ void ControllerScriptInterfaceLegacy::softTakeover( void ControllerScriptInterfaceLegacy::softTakeoverIgnoreNextValue( QString group, const QString name) { ControlObject* pControl = ControlObject::getControl( - ConfigKey(group, name), ControlFlag::AllowMissingOrInvalid); + ConfigKey(group, name), ControllerDebug::shouldAssertForInvalidControlObjects()); if (!pControl) { return; } From caf3d68aa44bb2ba351cd572bc7d25f80d9fc6cf Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:32:05 -0500 Subject: [PATCH 23/61] remove legacy ControllerEngine file brought back from merge --- src/controllers/engine/controllerengine.cpp | 1364 ------------------- 1 file changed, 1364 deletions(-) delete mode 100644 src/controllers/engine/controllerengine.cpp diff --git a/src/controllers/engine/controllerengine.cpp b/src/controllers/engine/controllerengine.cpp deleted file mode 100644 index 4c7513bd36a..00000000000 --- a/src/controllers/engine/controllerengine.cpp +++ /dev/null @@ -1,1364 +0,0 @@ -#include "controllers/engine/controllerengine.h" - -#include "control/controlobject.h" -#include "control/controlobjectscript.h" -#include "controllers/controller.h" -#include "controllers/controllerdebug.h" -#include "controllers/engine/colormapperjsproxy.h" -#include "controllers/engine/controllerenginejsproxy.h" -#include "controllers/engine/scriptconnectionjsproxy.h" -#include "errordialoghandler.h" -#include "mixer/playermanager.h" -// to tell the msvs compiler about `isnan` -#include "util/math.h" -#include "util/time.h" - -namespace { -constexpr int kDecks = 16; - -// Use 1ms for the Alpha-Beta dt. We're assuming the OS actually gives us a 1ms -// timer. -constexpr int kScratchTimerMs = 1; -constexpr double kAlphaBetaDt = kScratchTimerMs / 1000.0; - -inline ControlFlags onlyAssertOnControllerDebug() { - if (ControllerDebug::enabled()) { - return ControlFlag::None; - } - - return ControlFlag::AllowMissingOrInvalid; -} -} // namespace - -ControllerEngine::ControllerEngine(Controller* controller) - : m_bDisplayingExceptionDialog(false), - m_pScriptEngine(nullptr), - m_pController(controller), - m_bTesting(false) { - // Handle error dialog buttons - qRegisterMetaType("QMessageBox::StandardButton"); - - // Pre-allocate arrays for average number of virtual decks - m_intervalAccumulator.resize(kDecks); - m_lastMovement.resize(kDecks); - m_dx.resize(kDecks); - m_rampTo.resize(kDecks); - m_ramp.resize(kDecks); - m_scratchFilters.resize(kDecks); - m_rampFactor.resize(kDecks); - m_brakeActive.resize(kDecks); - m_softStartActive.resize(kDecks); - // Initialize arrays used for testing and pointers - for (int i = 0; i < kDecks; ++i) { - m_dx[i] = 0.0; - m_scratchFilters[i] = new AlphaBetaFilter(); - m_ramp[i] = false; - } - - initializeScriptEngine(); -} - -ControllerEngine::~ControllerEngine() { - // Clean up - for (int i = 0; i < kDecks; ++i) { - delete m_scratchFilters[i]; - m_scratchFilters[i] = nullptr; - } - - uninitializeScriptEngine(); -} - -bool ControllerEngine::callFunctionOnObjects(QList scriptFunctionPrefixes, - const QString& function, - QJSValueList args, - bool bFatalError) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return false; - } - - const QJSValue global = m_pScriptEngine->globalObject(); - - bool success = true; - for (const QString& prefixName : scriptFunctionPrefixes) { - QJSValue prefix = global.property(prefixName); - if (!prefix.isObject()) { - qWarning() << "ControllerEngine: No" << prefixName << "object in script"; - continue; - } - - QJSValue init = prefix.property(function); - if (!init.isCallable()) { - qWarning() << "ControllerEngine:" << prefixName << "has no" << function << " method"; - continue; - } - controllerDebug("ControllerEngine: Executing" << prefixName << "." << function); - QJSValue result = init.callWithInstance(prefix, args); - if (result.isError()) { - showScriptExceptionDialog(result, bFatalError); - success = false; - } - } - return success; -} - -QJSValue ControllerEngine::byteArrayToScriptValue(const QByteArray& byteArray) { - // The QJSEngine converts the QByteArray to an ArrayBuffer object. - QJSValue arrayBuffer = m_pScriptEngine->toScriptValue(byteArray); - // Convert the ArrayBuffer to a Uint8 typed array so scripts can access its bytes - // with the [] operator. - QJSValue result = m_byteArrayToScriptValueJSFunction.call( - QJSValueList{arrayBuffer}); - if (result.isError()) { - showScriptExceptionDialog(result); - } - return result; -} - -QJSValue ControllerEngine::wrapFunctionCode(const QString& codeSnippet, - int numberOfArgs) { - // This function is called from outside the controller engine, so we can't - // use VERIFY_OR_DEBUG_ASSERT here - if (m_pScriptEngine == nullptr) { - return QJSValue(); - } - - QJSValue wrappedFunction; - - auto i = m_scriptWrappedFunctionCache.constFind(codeSnippet); - if (i != m_scriptWrappedFunctionCache.constEnd()) { - wrappedFunction = i.value(); - } else { - QStringList wrapperArgList; - for (int i = 1; i <= numberOfArgs; i++) { - wrapperArgList << QString("arg%1").arg(i); - } - QString wrapperArgs = wrapperArgList.join(","); - QString wrappedCode = QStringLiteral("(function (") + wrapperArgs + - QStringLiteral(") { (") + codeSnippet + QStringLiteral(")(") + - wrapperArgs + QStringLiteral("); })"); - - wrappedFunction = evaluateCodeString(wrappedCode); - if (wrappedFunction.isError()) { - showScriptExceptionDialog(wrappedFunction); - } - m_scriptWrappedFunctionCache[codeSnippet] = wrappedFunction; - } - return wrappedFunction; -} - -void ControllerEngine::gracefulShutdown() { - if (m_pScriptEngine == nullptr) { - return; - } - - qDebug() << "ControllerEngine shutting down..."; - - // Stop all timers - stopAllTimers(); - - qDebug() << "Invoking shutdown() hook in scripts"; - callFunctionOnObjects(m_scriptFunctionPrefixes, "shutdown"); - - if (m_shutdownFunction.isCallable()) { - executeFunction(m_shutdownFunction, QJSValueList{}); - } - - // Prevents leaving decks in an unstable state - // if the controller is shut down while scratching - QHashIterator i(m_scratchTimers); - while (i.hasNext()) { - i.next(); - qDebug() << "Aborting scratching on deck" << i.value(); - // Clear scratch2_enable. PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(i.value() - 1); - ControlObjectScript* pScratch2Enable = - getControlObjectScript(group, "scratch2_enable"); - if (pScratch2Enable != nullptr) { - pScratch2Enable->set(0); - } - } - - qDebug() << "Clearing function wrapper cache"; - m_scriptWrappedFunctionCache.clear(); - - // Free all the ControlObjectScripts - { - auto it = m_controlCache.begin(); - while (it != m_controlCache.end()) { - qDebug() - << "Deleting ControlObjectScript" - << it.key().group - << it.key().item; - delete it.value(); - // Advance iterator - it = m_controlCache.erase(it); - } - } -} - -void ControllerEngine::initializeScriptEngine() { - VERIFY_OR_DEBUG_ASSERT(!m_pScriptEngine) { - return; - } - - // Create the Script Engine - m_pScriptEngine = new QJSEngine(this); - - m_pScriptEngine->installExtensions(QJSEngine::ConsoleExtension); - - // Make this ControllerEngine instance available to scripts as 'engine'. - QJSValue engineGlobalObject = m_pScriptEngine->globalObject(); - ControllerEngineJSProxy* proxy = new ControllerEngineJSProxy(this); - engineGlobalObject.setProperty("engine", m_pScriptEngine->newQObject(proxy)); - - QJSValue mapper = m_pScriptEngine->newQMetaObject(&ColorMapperJSProxy::staticMetaObject); - engineGlobalObject.setProperty("ColorMapper", mapper); - - if (m_pController) { - qDebug() << "Controller in script engine is:" << m_pController->getName(); - - ControllerJSProxy* controllerProxy = m_pController->jsProxy(); - - // Make the Controller instance available to scripts - engineGlobalObject.setProperty("controller", m_pScriptEngine->newQObject(controllerProxy)); - - // ...under the legacy name as well - engineGlobalObject.setProperty("midi", m_pScriptEngine->newQObject(controllerProxy)); - } - - m_byteArrayToScriptValueJSFunction = evaluateCodeString("(function(arg1) { return new Uint8Array(arg1) })"); -} - -void ControllerEngine::uninitializeScriptEngine() { - // Delete the script engine, first clearing the pointer so that - // other threads will not get the dead pointer after we delete it. - if (m_pScriptEngine != nullptr) { - QJSEngine* engine = m_pScriptEngine; - m_pScriptEngine = nullptr; - engine->deleteLater(); - } -} - -void ControllerEngine::loadModule(QFileInfo moduleFileInfo) { - // QFileInfo does not have a isValid/isEmpty/isNull method to check if it - // actually contains a reference, so we check if the filePath is empty as a - // workaround. - // See https://stackoverflow.com/a/45652741/1455128 for details. - VERIFY_OR_DEBUG_ASSERT(!moduleFileInfo.filePath().isEmpty()) { - return; - } - - VERIFY_OR_DEBUG_ASSERT(moduleFileInfo.isFile()) { - return; - } -#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) - m_moduleFileInfo = moduleFileInfo; - - QJSValue mod = m_pScriptEngine->importModule(moduleFileInfo.absoluteFilePath()); - if (mod.isError()) { - showScriptExceptionDialog(mod); - return; - } - - connect(&m_scriptWatcher, - &QFileSystemWatcher::fileChanged, - this, - &ControllerEngine::scriptHasChanged); - m_scriptWatcher.addPath(moduleFileInfo.absoluteFilePath()); - - QJSValue initFunction = mod.property("init"); - executeFunction(initFunction, QJSValueList{}); - - QJSValue handleInputFunction = mod.property("handleInput"); - if (handleInputFunction.isCallable()) { - m_handleInputFunction = handleInputFunction; - } else { - scriptErrorDialog( - "Controller JavaScript module exports no handleInput function.", - QStringLiteral("handleInput"), - true); - } - - QJSValue shutdownFunction = mod.property("shutdown"); - if (shutdownFunction.isCallable()) { - m_shutdownFunction = shutdownFunction; - } else { - qDebug() << "Module exports no shutdown function."; - } -#else - Q_UNUSED(moduleFileInfo); -#endif -} - -void ControllerEngine::handleInput(QByteArray data, mixxx::Duration timestamp) { - if (m_handleInputFunction.isCallable()) { - QJSValueList args; - args << byteArrayToScriptValue(data); - args << timestamp.toDoubleMillis(); - executeFunction(m_handleInputFunction, args); - } -} - -bool ControllerEngine::loadScriptFiles(const QList& scripts) { - bool scriptsEvaluatedCorrectly = true; - for (const auto& script : scripts) { - if (!evaluateScriptFile(script.file)) { - scriptsEvaluatedCorrectly = false; - } - } - - m_lastScriptFiles = scripts; - - connect(&m_scriptWatcher, &QFileSystemWatcher::fileChanged, this, &ControllerEngine::scriptHasChanged); - - if (!scriptsEvaluatedCorrectly) { - gracefulShutdown(); - uninitializeScriptEngine(); - } - - return scriptsEvaluatedCorrectly; -} - -void ControllerEngine::scriptHasChanged(const QString& scriptFilename) { - Q_UNUSED(scriptFilename); - disconnect(&m_scriptWatcher, &QFileSystemWatcher::fileChanged, this, &ControllerEngine::scriptHasChanged); - reloadScripts(); -} - -void ControllerEngine::reloadScripts() { - qDebug() << "ControllerEngine: Reloading Scripts"; - ControllerPresetPointer pPreset = m_pController->getPreset(); - - gracefulShutdown(); - uninitializeScriptEngine(); - - initializeScriptEngine(); - if (!loadScriptFiles(m_lastScriptFiles)) { - return; - } - - qDebug() << "Re-initializing scripts"; - initializeScripts(m_lastScriptFiles); - - // QFileInfo does not have a isValid/isEmpty/isNull method to check if it - // actually contains a reference, so we check if the filePath is empty as a - // workaround. - // See https://stackoverflow.com/a/45652741/1455128 for details. - if (!m_moduleFileInfo.filePath().isEmpty()) { - loadModule(m_moduleFileInfo); - } -} - -void ControllerEngine::initializeScripts(const QList& scripts) { - m_scriptFunctionPrefixes.clear(); - for (const ControllerPreset::ScriptFileInfo& script : scripts) { - // Skip empty prefixes. - if (!script.functionPrefix.isEmpty()) { - m_scriptFunctionPrefixes.append(script.functionPrefix); - } - } - - QJSValueList args; - args << QJSValue(m_pController->getName()); - args << QJSValue(ControllerDebug::enabled()); - - // Call the init method for all the prefixes. - bool success = callFunctionOnObjects(m_scriptFunctionPrefixes, "init", args, true); - - // We failed to initialize the controller scripts, shutdown the script - // engine to avoid error popups on every button press or slider move - if (!success) { - gracefulShutdown(); - uninitializeScriptEngine(); - } -} - -bool ControllerEngine::executeFunction(QJSValue functionObject, QJSValueList args) { - // This function is called from outside the controller engine, so we can't - // use VERIFY_OR_DEBUG_ASSERT here - if (!m_pScriptEngine) { - return false; - } - - if (functionObject.isError()) { - qDebug() << "ControllerEngine::executeFunction:" - << functionObject.toString(); - return false; - } - - // If it's not a function, we're done. - if (!functionObject.isCallable()) { - qDebug() << "ControllerEngine::executeFunction:" - << functionObject.toVariant() - << "Not a function"; - return false; - } - - // If it does happen to be a function, call it. - QJSValue returnValue = functionObject.call(args); - if (returnValue.isError()) { - showScriptExceptionDialog(returnValue); - return false; - } - return true; -} - -bool ControllerEngine::executeFunction(QJSValue functionObject, const QByteArray& data) { - // This function is called from outside the controller engine, so we can't - // use VERIFY_OR_DEBUG_ASSERT here - if (!m_pScriptEngine) { - return false; - } - QJSValueList args; - args << byteArrayToScriptValue(data); - args << QJSValue(data.size()); - return executeFunction(functionObject, args); -} - -QJSValue ControllerEngine::evaluateCodeString(const QString& program, const QString& fileName, int lineNumber) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return QJSValue::UndefinedValue; - } - return m_pScriptEngine->evaluate(program, fileName, lineNumber); -} - -void ControllerEngine::throwJSError(const QString& message) { -#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) - QString errorText = tr("Uncaught exception: %1").arg(message); - qWarning() << "ControllerEngine:" << errorText; - if (!m_bDisplayingExceptionDialog) { - scriptErrorDialog(errorText, errorText); - } -#else - m_pScriptEngine->throwError(message); -#endif -} - -void ControllerEngine::showScriptExceptionDialog(QJSValue evaluationResult, bool bFatalError) { - VERIFY_OR_DEBUG_ASSERT(evaluationResult.isError()) { - return; - } - - QString errorMessage = evaluationResult.toString(); - QString line = evaluationResult.property("lineNumber").toString(); - QString backtrace = evaluationResult.property("stack").toString(); - QString filename = evaluationResult.property("fileName").toString(); - - QString errorText; - if (filename.isEmpty()) { - errorText = QString("Uncaught exception at line %1 in passed code.").arg(line); - } else { - errorText = QString("Uncaught exception at line %1 in file %2.").arg(line, filename); - } - - errorText += QStringLiteral("\n\nException:\n ") + errorMessage; - - // Do not include backtrace in dialog key because it might contain midi - // slider values that will differ most of the time. This would break - // the "Ignore" feature of the error dialog. - QString key = errorText; - qWarning() << "ControllerEngine:" << errorText; - - // Add backtrace to the error details - errorText += QStringLiteral("\n\nBacktrace:\n") + backtrace; - - if (!m_bDisplayingExceptionDialog) { - scriptErrorDialog(errorText, key, bFatalError); - } -} - -void ControllerEngine::scriptErrorDialog( - const QString& detailedError, const QString& key, bool bFatalError) { - if (m_bTesting) { - return; - } - - ErrorDialogProperties* props = - ErrorDialogHandler::instance()->newDialogProperties(); - - QString additionalErrorText; - if (bFatalError) { - additionalErrorText = - tr("The functionality provided by this controller mapping will " - "be disabled until the issue has been resolved."); - } else { - additionalErrorText = - tr("You can ignore this error for this session but " - "you may experience erratic behavior.") + - QString("
") + - tr("Try to recover by resetting your controller."); - } - - props->setType(DLG_WARNING); - props->setTitle(tr("Controller Preset Error")); - props->setText(QString(tr("The preset for your controller \"%1\" is not " - "working properly.")) - .arg(m_pController->getName())); - props->setInfoText(QStringLiteral("") + - tr("The script code needs to be fixed.") + QStringLiteral("

") + - additionalErrorText + QStringLiteral("

")); - - // Add "Details" text and set monospace font since they may contain - // backtraces and code. - props->setDetails(detailedError, true); - - // To prevent multiple windows for the same error - props->setKey(key); - - // Allow user to suppress further notifications about this particular error - if (!bFatalError) { - props->addButton(QMessageBox::Ignore); - props->addButton(QMessageBox::Retry); - props->setDefaultButton(QMessageBox::Ignore); - props->setEscapeButton(QMessageBox::Ignore); - } else { - props->addButton(QMessageBox::Close); - props->setDefaultButton(QMessageBox::Close); - props->setEscapeButton(QMessageBox::Close); - } - props->setModal(false); - - if (ErrorDialogHandler::instance()->requestErrorDialog(props)) { - m_bDisplayingExceptionDialog = true; - // Enable custom handling of the dialog buttons - connect(ErrorDialogHandler::instance(), &ErrorDialogHandler::stdButtonClicked, this, &ControllerEngine::errorDialogButton); - } -} - -void ControllerEngine::errorDialogButton( - const QString& key, QMessageBox::StandardButton clickedButton) { - Q_UNUSED(key); - - m_bDisplayingExceptionDialog = false; - // Something was clicked, so disable this signal now - disconnect( - ErrorDialogHandler::instance(), - &ErrorDialogHandler::stdButtonClicked, - this, - &ControllerEngine::errorDialogButton); - - if (clickedButton == QMessageBox::Retry) { - reloadScripts(); - } -} - -ControlObjectScript* ControllerEngine::getControlObjectScript(const QString& group, const QString& name) { - ConfigKey key = ConfigKey(group, name); - - if (!key.isValid()) { - qWarning() << "ControllerEngine: Requested control with invalid key" << key; - // Throw a debug assertion if controllerDebug is enabled - DEBUG_ASSERT(!ControllerDebug::enabled()); - return nullptr; - } - - ControlObjectScript* coScript = m_controlCache.value(key, nullptr); - if (coScript == nullptr) { - // create COT - coScript = new ControlObjectScript(key, this); - if (coScript->valid()) { - m_controlCache.insert(key, coScript); - } else { - delete coScript; - coScript = nullptr; - } - } - return coScript; -} - -double ControllerEngine::getValue(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - return coScript->get(); -} - -void ControllerEngine::setValue(QString group, QString name, double newValue) { - if (isnan(newValue)) { - qWarning() << "ControllerEngine: script setting [" << group << "," << name - << "] to NotANumber, ignoring."; - return; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript) { - ControlObject* pControl = ControlObject::getControl( - coScript->getKey(), onlyAssertOnControllerDebug()); - if (pControl && !m_st.ignore(pControl, coScript->getParameterForValue(newValue))) { - coScript->slotSet(newValue); - } - } -} - -double ControllerEngine::getParameter(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - return coScript->getParameter(); -} - -void ControllerEngine::setParameter(QString group, QString name, double newParameter) { - if (isnan(newParameter)) { - qWarning() << "ControllerEngine: script setting [" << group << "," << name - << "] to NotANumber, ignoring."; - return; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript) { - ControlObject* pControl = ControlObject::getControl( - coScript->getKey(), onlyAssertOnControllerDebug()); - if (pControl && !m_st.ignore(pControl, newParameter)) { - coScript->setParameter(newParameter); - } - } -} - -double ControllerEngine::getParameterForValue(QString group, QString name, double value) { - if (isnan(value)) { - qWarning() << "ControllerEngine: script setting [" << group << "," << name - << "] to NotANumber, ignoring."; - return 0.0; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - - return coScript->getParameterForValue(value); -} - -void ControllerEngine::reset(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript != nullptr) { - coScript->reset(); - } -} - -double ControllerEngine::getDefaultValue(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - - return coScript->getDefault(); -} - -double ControllerEngine::getDefaultParameter(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - - if (coScript == nullptr) { - qWarning() << "ControllerEngine: Unknown control" << group << name << ", returning 0.0"; - return 0.0; - } - - return coScript->getParameterForValue(coScript->getDefault()); -} - -void ControllerEngine::log(QString message) { - controllerDebug(message); -} - -QJSValue ControllerEngine::makeConnection(QString group, QString name, const QJSValue callback) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine != nullptr) { - return QJSValue(); - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript == nullptr) { - // The test setups do not run all of Mixxx, so ControlObjects not - // existing during tests is okay. - if (!m_bTesting) { - throwJSError("ControllerEngine: script tried to connect to ControlObject (" + - group + ", " + name + - ") which is non-existent."); - } - return QJSValue(); - } - - if (!callback.isCallable()) { - throwJSError("Tried to connect (" + group + ", " + name + ")" + " to an invalid callback. Make sure that your code contains no syntax errors."); - return QJSValue(); - } - - ScriptConnection connection; - connection.key = ConfigKey(group, name); - connection.controllerEngine = this; - connection.callback = callback; - connection.id = QUuid::createUuid(); - - if (coScript->addScriptConnection(connection)) { - return m_pScriptEngine->newQObject(new ScriptConnectionJSProxy(connection)); - } - - return QJSValue(); -} - -bool ControllerEngine::removeScriptConnection(const ScriptConnection connection) { - ControlObjectScript* coScript = getControlObjectScript(connection.key.group, - connection.key.item); - - if (m_pScriptEngine == nullptr || coScript == nullptr) { - return false; - } - - return coScript->removeScriptConnection(connection); -} - -void ControllerEngine::triggerScriptConnection(const ScriptConnection connection) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return; - } - - ControlObjectScript* coScript = getControlObjectScript(connection.key.group, - connection.key.item); - if (coScript == nullptr) { - return; - } - - connection.executeCallback(coScript->get()); -} - -// This function is a legacy version of makeConnection with several alternate -// ways of invoking it. The callback function can be passed either as a string of -// JavaScript code that evaluates to a function or an actual JavaScript function. -// If "true" is passed as a 4th parameter, all connections to the ControlObject -// are removed. If a ScriptConnectionInvokableWrapper is passed instead of a callback, -// it is disconnected. -// WARNING: These behaviors are quirky and confusing, so if you change this function, -// be sure to run the ControllerEngineTest suite to make sure you do not break old scripts. -QJSValue ControllerEngine::connectControl( - QString group, QString name, QJSValue passedCallback, bool disconnect) { - // The passedCallback may or may not actually be a function, so when - // the actual callback function is found, store it in this variable. - QJSValue actualCallbackFunction; - - if (passedCallback.isCallable()) { - if (!disconnect) { - // skip all the checks below and just make the connection - return makeConnection(group, name, passedCallback); - } - actualCallbackFunction = passedCallback; - } - - ControlObjectScript* coScript = getControlObjectScript(group, name); - // This check is redundant with makeConnection, but the - // ControlObjectScript is also needed here to check for duplicate connections. - if (coScript == nullptr) { - // The test setups do not run all of Mixxx, so ControlObjects not - // existing during tests is okay. - if (!m_bTesting) { - if (disconnect) { - throwJSError("ControllerEngine: script tried to disconnect from ControlObject (" + - group + ", " + name + ") which is non-existent."); - } else { - throwJSError("ControllerEngine: script tried to connect to ControlObject (" + - group + ", " + name + ") which is non-existent."); - } - } - // This is inconsistent with other failures, which return false. - // QJSValue() with no arguments is undefined in JavaScript. - return QJSValue(); - } - - if (passedCallback.isString()) { - // This check is redundant with makeConnection, but it must be done here - // before evaluating the code string. - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine != nullptr) { - return QJSValue(false); - } - - actualCallbackFunction = evaluateCodeString(passedCallback.toString()); - - if (!actualCallbackFunction.isCallable()) { - QString sErrorMessage("Invalid connection callback provided to engine.connectControl."); - if (actualCallbackFunction.isError()) { - sErrorMessage.append("\n" + actualCallbackFunction.toString()); - } - throwJSError(sErrorMessage); - return QJSValue(false); - } - - if (coScript->countConnections() > 0 && !disconnect) { - // This is inconsistent with the behavior when passing the callback as - // a function, but keep the old behavior to make sure old scripts do - // not break. - ScriptConnection connection = coScript->firstConnection(); - - qWarning() << "Tried to make duplicate connection between (" + - group + ", " + name + ") and " + passedCallback.toString() + - " but this is not allowed when passing a callback as a string. " + - "If you actually want to create duplicate connections, " + - "use engine.makeConnection. Returning reference to connection " + - connection.id.toString(); - - return m_pScriptEngine->newQObject(new ScriptConnectionJSProxy(connection)); - } - } else if (passedCallback.isQObject()) { - // Assume a ScriptConnection and assume that the script author - // wants to disconnect it, regardless of the disconnect parameter - // and regardless of whether it is connected to the same ControlObject - // specified by the first two parameters to this function. - QObject* qobject = passedCallback.toQObject(); - const QMetaObject* qmeta = qobject->metaObject(); - - qWarning() << "QObject passed to engine.connectControl. Assuming it is" - << "a connection object to disconnect and returning false."; - if (!strcmp(qmeta->className(), - "ScriptConnectionJSProxy")) { - ScriptConnectionJSProxy* proxy = - (ScriptConnectionJSProxy*)qobject; - proxy->disconnect(); - } - return QJSValue(false); - } - - // Support removing connections by passing "true" as the last parameter - // to this function, regardless of whether the callback is provided - // as a function or a string. - if (disconnect) { - // There is no way to determine which - // ScriptConnection to disconnect unless the script calls - // ScriptConnectionInvokableWrapper::disconnect(), so - // disconnect all ScriptConnections connected to the - // callback function, even though there may be multiple connections. - coScript->disconnectAllConnectionsToFunction(actualCallbackFunction); - return QJSValue(true); - } - - // If execution gets this far without returning, make - // a new connection to actualCallbackFunction. - return makeConnection(group, name, actualCallbackFunction); -} - -void ControllerEngine::trigger(QString group, QString name) { - ControlObjectScript* coScript = getControlObjectScript(group, name); - if (coScript != nullptr) { - coScript->emitValueChanged(); - } -} - -bool ControllerEngine::evaluateScriptFile(const QFileInfo& scriptFile) { - VERIFY_OR_DEBUG_ASSERT(m_pScriptEngine) { - return false; - } - - if (!scriptFile.exists()) { - qWarning() << "ControllerEngine: File does not exist:" << scriptFile.absoluteFilePath(); - return false; - } - m_scriptWatcher.addPath(scriptFile.absoluteFilePath()); - - qDebug() << "ControllerEngine: Loading" << scriptFile.absoluteFilePath(); - - // Read in the script file - QString filename = scriptFile.absoluteFilePath(); - QFile input(filename); - if (!input.open(QIODevice::ReadOnly)) { - qWarning() << QString("ControllerEngine: Problem opening the script file: %1, error # %2, %3") - .arg(filename, QString::number(input.error()), input.errorString()); - // Set up error dialog - ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); - props->setType(DLG_WARNING); - props->setTitle(tr("Controller Mapping File Problem")); - props->setText(tr("The mapping for controller \"%1\" cannot be opened.") - .arg(m_pController->getName())); - props->setInfoText( - tr("The functionality provided by this controller mapping will " - "be disabled until the issue has been resolved.")); - - // We usually don't translate the details field, but the cause of - // this problem lies in the user's system (e.g. a permission - // issue). Translating this will help users to fix the issue even - // when they don't speak english. - props->setDetails(tr("File:") + QStringLiteral(" ") + filename + - QStringLiteral("\n") + tr("Error:") + QStringLiteral(" ") + - input.errorString()); - - // Ask above layer to display the dialog & handle user response - ErrorDialogHandler::instance()->requestErrorDialog(props); - return false; - } - - QString scriptCode = ""; - scriptCode.append(input.readAll()); - scriptCode.append('\n'); - input.close(); - - // Evaluate the code - QJSValue scriptFunction = evaluateCodeString(scriptCode, filename); - if (scriptFunction.isError()) { - showScriptExceptionDialog(scriptFunction, true); - return false; - } - - return true; -} - -int ControllerEngine::beginTimer(int intervalMillis, QJSValue timerCallback, bool oneShot) { - if (timerCallback.isString()) { - timerCallback = evaluateCodeString(timerCallback.toString()); - } else if (!timerCallback.isCallable()) { - QString sErrorMessage( - "Invalid timer callback provided to engine.beginTimer. Valid callbacks are strings and functions. " - "Make sure that your code contains no syntax errors."); - if (timerCallback.isError()) { - sErrorMessage.append("\n" + timerCallback.toString()); - } - throwJSError(sErrorMessage); - return 0; - } - - if (intervalMillis < 20) { - qWarning() << "Timer request for" << intervalMillis - << "ms is too short. Setting to the minimum of 20ms."; - intervalMillis = 20; - } - - // This makes use of every QObject's internal timer mechanism. Nice, clean, - // and simple. See http://doc.trolltech.com/4.6/qobject.html#startTimer for - // details - int timerId = startTimer(intervalMillis); - TimerInfo info; - info.callback = timerCallback; - info.oneShot = oneShot; - m_timers[timerId] = info; - if (timerId == 0) { - qWarning() << "Script timer could not be created"; - } else if (oneShot) { - controllerDebug("Starting one-shot timer:" << timerId); - } else { - controllerDebug("Starting timer:" << timerId); - } - return timerId; -} - -void ControllerEngine::stopTimer(int timerId) { - if (!m_timers.contains(timerId)) { - qWarning() << "Killing timer" << timerId << ": That timer does not exist!"; - return; - } - controllerDebug("Killing timer:" << timerId); - killTimer(timerId); - m_timers.remove(timerId); -} - -void ControllerEngine::stopAllTimers() { - QMutableHashIterator i(m_timers); - while (i.hasNext()) { - i.next(); - stopTimer(i.key()); - } -} - -void ControllerEngine::timerEvent(QTimerEvent* event) { - int timerId = event->timerId(); - - // See if this is a scratching timer - if (m_scratchTimers.contains(timerId)) { - scratchProcess(timerId); - return; - } - - auto it = m_timers.constFind(timerId); - if (it == m_timers.constEnd()) { - qWarning() << "Timer" << timerId << "fired but there's no function mapped to it!"; - return; - } - - // NOTE(rryan): Do not assign by reference -- make a copy. I have no idea - // why but this causes segfaults in ~QScriptValue while scratching if we - // don't copy here -- even though internalExecute passes the QScriptValues - // by value. *boggle* - const TimerInfo timerTarget = it.value(); - if (timerTarget.oneShot) { - stopTimer(timerId); - } - - executeFunction(timerTarget.callback, QJSValueList()); -} - -void ControllerEngine::softTakeover(QString group, QString name, bool set) { - ConfigKey key = ConfigKey(group, name); - ControlObject* pControl = ControlObject::getControl(key, onlyAssertOnControllerDebug()); - if (!pControl) { - qWarning() << "Failed to" << (set ? "enable" : "disable") - << "softTakeover for invalid control" << key; - return; - } - if (set) { - m_st.enable(pControl); - } else { - m_st.disable(pControl); - } -} - -void ControllerEngine::softTakeoverIgnoreNextValue(QString group, const QString name) { - ConfigKey key = ConfigKey(group, name); - ControlObject* pControl = ControlObject::getControl(key, onlyAssertOnControllerDebug()); - if (!pControl) { - qWarning() << "Failed to call softTakeoverIgnoreNextValue for invalid control" << key; - return; - } - - m_st.ignoreNext(pControl); -} - -double ControllerEngine::getDeckRate(const QString& group) { - double rate = 0.0; - ControlObjectScript* pRateRatio = getControlObjectScript(group, "rate_ratio"); - if (pRateRatio != nullptr) { - rate = pRateRatio->get(); - } - - // See if we're in reverse play - ControlObjectScript* pReverse = getControlObjectScript(group, "reverse"); - if (pReverse != nullptr && pReverse->get() == 1) { - rate = -rate; - } - return rate; -} - -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; - } - - return pPlay->get() > 0.0; -} - -void ControllerEngine::scratchEnable( - int deck, - int intervalsPerRev, - double rpm, - double alpha, - double beta, - bool ramp) { - // If we're already scratching this deck, override that with this request - if (m_dx[deck]) { - //qDebug() << "Already scratching deck" << deck << ". Overriding."; - int timerId = m_scratchTimers.key(deck); - killTimer(timerId); - m_scratchTimers.remove(timerId); - } - - // Controller resolution in intervals per second at normal speed. - // (rev/min * ints/rev * mins/sec) - double intervalsPerSecond = (rpm * intervalsPerRev) / 60.0; - - if (intervalsPerSecond == 0.0) { - qWarning() << "Invalid rpm or intervalsPerRev supplied to scratchEnable. Ignoring request."; - return; - } - - m_dx[deck] = 1.0 / intervalsPerSecond; - m_intervalAccumulator[deck] = 0.0; - m_ramp[deck] = false; - m_rampFactor[deck] = 0.001; - m_brakeActive[deck] = false; - - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - - // Ramp velocity, default to stopped. - double initVelocity = 0.0; - - ControlObjectScript* pScratch2Enable = - getControlObjectScript(group, "scratch2_enable"); - - // If ramping is desired, figure out the deck's current speed - if (ramp) { - // See if the deck is already being scratched - if (pScratch2Enable != nullptr && pScratch2Enable->get() == 1) { - // If so, set the filter's initial velocity to the scratch speed - ControlObjectScript* pScratch2 = - getControlObjectScript(group, "scratch2"); - if (pScratch2 != nullptr) { - initVelocity = pScratch2->get(); - } - } else if (isDeckPlaying(group)) { - // If the deck is playing, set the filter's initial velocity to the - // playback speed - initVelocity = getDeckRate(group); - } - } - - // Initialize scratch filter - if (alpha && beta) { - m_scratchFilters[deck]->init(kAlphaBetaDt, initVelocity, alpha, beta); - } else { - // Use filter's defaults if not specified - m_scratchFilters[deck]->init(kAlphaBetaDt, initVelocity); - } - - // 1ms is shortest possible, OS dependent - int timerId = startTimer(kScratchTimerMs); - - // Associate this virtual deck with this timer for later processing - m_scratchTimers[timerId] = deck; - - // Set scratch2_enable - if (pScratch2Enable != nullptr) { - pScratch2Enable->slotSet(1); - } -} - -void ControllerEngine::scratchTick(int deck, int interval) { - m_lastMovement[deck] = mixxx::Time::elapsed(); - m_intervalAccumulator[deck] += interval; -} - -void ControllerEngine::scratchProcess(int timerId) { - int deck = m_scratchTimers[timerId]; - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (!filter) { - qWarning() << "Scratch filter pointer is null on deck" << deck; - return; - } - - const double oldRate = filter->predictedVelocity(); - - // Give the filter a data point: - - // If we're ramping to end scratching and the wheel hasn't been turned very - // recently (spinback after lift-off,) feed fixed data - if (m_ramp[deck] && !m_softStartActive[deck] && - ((mixxx::Time::elapsed() - m_lastMovement[deck]) >= mixxx::Duration::fromMillis(1))) { - 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]) { - // pretend we have moved by (desired rate*default distance) - filter->observation(m_rampTo[deck] * kAlphaBetaDt); - } 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]); - } - - const double newRate = filter->predictedVelocity(); - - // Actually do the scratching - ControlObjectScript* pScratch2 = getControlObjectScript(group, "scratch2"); - if (pScratch2 == nullptr) { - return; // abort and maybe it'll work on the next pass - } - pScratch2->set(newRate); - - // Reset accumulator - m_intervalAccumulator[deck] = 0; - - // 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)))) { - // 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. - pScratch2->slotSet(0.0); - ControlObjectScript* pPlay = getControlObjectScript(group, "play"); - if (pPlay != nullptr) { - pPlay->slotSet(0.0); - } - } - - // Clear scratch2_enable to end scratching. - ControlObjectScript* pScratch2Enable = - getControlObjectScript(group, "scratch2_enable"); - if (pScratch2Enable == nullptr) { - return; // abort and maybe it'll work on the next pass - } - pScratch2Enable->slotSet(0); - - // Remove timer - killTimer(timerId); - m_scratchTimers.remove(timerId); - - m_dx[deck] = 0.0; - m_brakeActive[deck] = false; - m_softStartActive[deck] = false; - } -} - -void ControllerEngine::scratchDisable(int deck, bool ramp) { - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - - m_rampTo[deck] = 0.0; - - // If no ramping is desired, disable scratching immediately - if (!ramp) { - // Clear scratch2_enable - ControlObjectScript* pScratch2Enable = getControlObjectScript(group, "scratch2_enable"); - if (pScratch2Enable != nullptr) { - pScratch2Enable->slotSet(0); - } - // Can't return here because we need scratchProcess to stop the timer. - // So it's still actually ramping, we just won't hear or see it. - } else if (isDeckPlaying(group)) { - // If so, set the target velocity to the playback speed - m_rampTo[deck] = getDeckRate(group); - } - - m_lastMovement[deck] = mixxx::Time::elapsed(); - m_ramp[deck] = true; // Activate the ramping in scratchProcess() -} - -bool ControllerEngine::isScratching(int deck) { - // PlayerManager::groupForDeck is 0-indexed. - QString group = PlayerManager::groupForDeck(deck - 1); - return getValue(group, "scratch2_enable") > 0; -} - -void ControllerEngine::spinback(int deck, bool activate, double factor, double rate) { - // defaults for args set in header file - brake(deck, activate, factor, rate); -} - -void ControllerEngine::brake(int deck, bool activate, double factor, double rate) { - // 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; - 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); - } - - // 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; - } -} - -void ControllerEngine::softStart(int deck, bool activate, double factor) { - // 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_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; - - AlphaBetaFilter* filter = m_scratchFilters[deck]; - if (filter != nullptr) { - initRate = filter->predictedVelocity(); - } - } - - // setup timer, start playing and set scratch2 - timerId = startTimer(kScratchTimerMs); - m_scratchTimers[timerId] = deck; - - ControlObjectScript* pPlay = getControlObjectScript(group, "play"); - if (pPlay != nullptr) { - pPlay->slotSet(1.0); - } - - 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 6c1a6b6f1479870aa114811a3f13a55f56889ccf Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 18:34:39 -0500 Subject: [PATCH 24/61] ControllerScriptEngineLegacy: remove QString initialization to "" --- .../scripting/legacy/controllerscriptenginelegacy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 100917c2a6e..18e8a150c4f 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -199,7 +199,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil return false; } - QString scriptCode = ""; + QString scriptCode; scriptCode.append(input.readAll()); scriptCode.append('\n'); input.close(); From df4f3776000d805ce28579d76f8cb4fc22a4467a Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 19:03:49 -0500 Subject: [PATCH 25/61] ControllerScriptEngineBase: fix typo in comment Thanks codespell! --- src/controllers/scripting/controllerscriptenginebase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index efc9f2d3850..95d9d51331e 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -16,7 +16,7 @@ ControllerScriptEngineBase::ControllerScriptEngineBase(Controller* controller) qRegisterMetaType("QMessageBox::StandardButton"); // Subclasses are responsible for adding paths to watch to m_scriptWatcher - // in ther initializeScriptEngine method. + // in their initializeScriptEngine method. connect(&m_scriptWatcher, &QFileSystemWatcher::fileChanged, this, From 8ec7ed8bd17d164b4a4325fba2c6ec16cef0391c Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 1 Sep 2020 21:59:23 -0500 Subject: [PATCH 26/61] fix ControllerEngine tests --- src/controllers/controllerdebug.cpp | 1 + src/controllers/controllerdebug.h | 8 +++++++- src/test/controllerengine_test.cpp | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/controllers/controllerdebug.cpp b/src/controllers/controllerdebug.cpp index ac1c3f39ef4..f201473f341 100644 --- a/src/controllers/controllerdebug.cpp +++ b/src/controllers/controllerdebug.cpp @@ -5,6 +5,7 @@ //static bool ControllerDebug::s_enabled = false; +bool ControllerDebug::s_testing = false; //static bool ControllerDebug::enabled() { diff --git a/src/controllers/controllerdebug.h b/src/controllers/controllerdebug.h index 63e42009dd0..86437fec65d 100644 --- a/src/controllers/controllerdebug.h +++ b/src/controllers/controllerdebug.h @@ -25,8 +25,13 @@ class ControllerDebug { s_enabled = false; } + static void enableTesting() { + s_enabled = true; + s_testing = true; + } + static ControlFlags shouldAssertForInvalidControlObjects() { - if (enabled()) { + if (s_enabled && !s_testing) { return ControlFlag::None; } @@ -37,6 +42,7 @@ class ControllerDebug { ControllerDebug() = delete; static bool s_enabled; + static bool s_testing; }; // Usage: controllerDebug("hello" << "world"); diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index f32a9d622bf..c37a83dd783 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -20,7 +20,7 @@ class ControllerEngineTest : public MixxxTest { QThread::currentThread()->setObjectName("Main"); cEngine = new ControllerScriptEngineLegacy(nullptr); cEngine->initialize(); - ControllerDebug::enable(); + ControllerDebug::enableTesting(); } void TearDown() override { From 0d39501550be05dd174cf9784337bbba86df3ea2 Mon Sep 17 00:00:00 2001 From: Be Date: Wed, 2 Sep 2020 10:31:21 -0500 Subject: [PATCH 27/61] rename ControllerEngineTest to ControllerScriptEngineLegacyTest --- CMakeLists.txt | 2 +- .../legacy/controllerscriptenginelegacy.h | 2 +- ... => controllerscriptenginelegacy_test.cpp} | 62 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) rename src/test/{controllerengine_test.cpp => controllerscriptenginelegacy_test.cpp} (92%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f301064d82..25b7c290f8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1248,7 +1248,7 @@ add_executable(mixxx-test src/test/compatibility_test.cpp src/test/configobject_test.cpp src/test/controller_preset_validation_test.cpp - src/test/controllerengine_test.cpp + src/test/controllerscriptenginelegacy_test.cpp src/test/controlobjecttest.cpp src/test/coverartcache_test.cpp src/test/coverartutils_test.cpp diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 611bb79b43b..5fa58474a48 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -61,5 +61,5 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { friend class ControllerScriptInterfaceLegacy; friend class ColorJSProxy; friend class ColorMapperJSProxy; - friend class ControllerEngineTest; + friend class ControllerScriptEngineLegacyTest; }; diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerscriptenginelegacy_test.cpp similarity index 92% rename from src/test/controllerengine_test.cpp rename to src/test/controllerscriptenginelegacy_test.cpp index c37a83dd783..47d2c930914 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerscriptenginelegacy_test.cpp @@ -12,7 +12,7 @@ #include "util/memory.h" #include "util/time.h" -class ControllerEngineTest : public MixxxTest { +class ControllerScriptEngineLegacyTest : public MixxxTest { protected: void SetUp() override { mixxx::Time::setTestMode(true); @@ -54,18 +54,18 @@ class ControllerEngineTest : public MixxxTest { ControllerScriptEngineLegacy* cEngine; }; -TEST_F(ControllerEngineTest, commonScriptHasNoErrors) { +TEST_F(ControllerScriptEngineLegacyTest, commonScriptHasNoErrors) { QFileInfo commonScript("./res/controllers/common-controller-scripts.js"); EXPECT_TRUE(evaluateScriptFile(commonScript)); } -TEST_F(ControllerEngineTest, setValue) { +TEST_F(ControllerScriptEngineLegacyTest, setValue) { auto co = std::make_unique(ConfigKey("[Test]", "co")); EXPECT_TRUE(evaluateAndAssert("engine.setValue('[Test]', 'co', 1.0);")); EXPECT_DOUBLE_EQ(1.0, co->get()); } -TEST_F(ControllerEngineTest, getValue_InvalidKey) { +TEST_F(ControllerScriptEngineLegacyTest, getValue_InvalidKey) { ControllerDebug::disable(); EXPECT_TRUE(evaluateAndAssert("engine.getValue('', '');")); EXPECT_TRUE(evaluateAndAssert("engine.getValue('', 'invalid');")); @@ -73,28 +73,28 @@ TEST_F(ControllerEngineTest, getValue_InvalidKey) { ControllerDebug::enable(); } -TEST_F(ControllerEngineTest, setValue_InvalidControl) { +TEST_F(ControllerScriptEngineLegacyTest, setValue_InvalidControl) { EXPECT_TRUE(evaluateAndAssert("engine.setValue('[Nothing]', 'nothing', 1.0);")); } -TEST_F(ControllerEngineTest, getValue_InvalidControl) { +TEST_F(ControllerScriptEngineLegacyTest, getValue_InvalidControl) { EXPECT_TRUE(evaluateAndAssert("engine.getValue('[Nothing]', 'nothing');")); } -TEST_F(ControllerEngineTest, setValue_IgnoresNaN) { +TEST_F(ControllerScriptEngineLegacyTest, setValue_IgnoresNaN) { auto co = std::make_unique(ConfigKey("[Test]", "co")); co->set(10.0); EXPECT_TRUE(evaluateAndAssert("engine.setValue('[Test]', 'co', NaN);")); EXPECT_DOUBLE_EQ(10.0, co->get()); } -TEST_F(ControllerEngineTest, getSetValue) { +TEST_F(ControllerScriptEngineLegacyTest, getSetValue) { auto co = std::make_unique(ConfigKey("[Test]", "co")); EXPECT_TRUE(evaluateAndAssert("engine.setValue('[Test]', 'co', engine.getValue('[Test]', 'co') + 1);")); EXPECT_DOUBLE_EQ(1.0, co->get()); } -TEST_F(ControllerEngineTest, setParameter) { +TEST_F(ControllerScriptEngineLegacyTest, setParameter) { auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, 10.0); @@ -106,7 +106,7 @@ TEST_F(ControllerEngineTest, setParameter) { EXPECT_DOUBLE_EQ(0.0, co->get()); } -TEST_F(ControllerEngineTest, setParameter_OutOfRange) { +TEST_F(ControllerScriptEngineLegacyTest, setParameter_OutOfRange) { auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, 10.0); @@ -116,7 +116,7 @@ TEST_F(ControllerEngineTest, setParameter_OutOfRange) { EXPECT_DOUBLE_EQ(-10.0, co->get()); } -TEST_F(ControllerEngineTest, setParameter_NaN) { +TEST_F(ControllerScriptEngineLegacyTest, setParameter_NaN) { // Test that NaNs are ignored. auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, @@ -125,7 +125,7 @@ TEST_F(ControllerEngineTest, setParameter_NaN) { EXPECT_DOUBLE_EQ(0.0, co->get()); } -TEST_F(ControllerEngineTest, getSetParameter) { +TEST_F(ControllerScriptEngineLegacyTest, getSetParameter) { auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, 10.0); @@ -135,7 +135,7 @@ TEST_F(ControllerEngineTest, getSetParameter) { EXPECT_DOUBLE_EQ(2.0, co->get()); } -TEST_F(ControllerEngineTest, softTakeover_setValue) { +TEST_F(ControllerScriptEngineLegacyTest, softTakeover_setValue) { auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, 10.0); @@ -167,7 +167,7 @@ TEST_F(ControllerEngineTest, softTakeover_setValue) { EXPECT_DOUBLE_EQ(0.0, co->get()); } -TEST_F(ControllerEngineTest, softTakeover_setParameter) { +TEST_F(ControllerScriptEngineLegacyTest, softTakeover_setParameter) { auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, 10.0); @@ -199,7 +199,7 @@ TEST_F(ControllerEngineTest, softTakeover_setParameter) { EXPECT_DOUBLE_EQ(0.0, co->get()); } -TEST_F(ControllerEngineTest, softTakeover_ignoreNextValue) { +TEST_F(ControllerScriptEngineLegacyTest, softTakeover_ignoreNextValue) { auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, 10.0); @@ -222,7 +222,7 @@ TEST_F(ControllerEngineTest, softTakeover_ignoreNextValue) { EXPECT_DOUBLE_EQ(0.0, co->get()); } -TEST_F(ControllerEngineTest, reset) { +TEST_F(ControllerScriptEngineLegacyTest, reset) { // Test that NaNs are ignored. auto co = std::make_unique(ConfigKey("[Test]", "co"), -10.0, @@ -232,11 +232,11 @@ TEST_F(ControllerEngineTest, reset) { EXPECT_DOUBLE_EQ(0.0, co->get()); } -TEST_F(ControllerEngineTest, log) { +TEST_F(ControllerScriptEngineLegacyTest, log) { EXPECT_TRUE(evaluateAndAssert("engine.log('Test that logging works.');")); } -TEST_F(ControllerEngineTest, trigger) { +TEST_F(ControllerScriptEngineLegacyTest, trigger) { auto co = std::make_unique(ConfigKey("[Test]", "co")); auto pass = std::make_unique(ConfigKey("[Test]", "passed")); @@ -257,7 +257,7 @@ TEST_F(ControllerEngineTest, trigger) { // depending on how it is invoked, so we need a lot of tests to make sure old scripts // do not break. -TEST_F(ControllerEngineTest, connectControl_ByString) { +TEST_F(ControllerScriptEngineLegacyTest, connectControl_ByString) { // Test that connecting and disconnecting by function name works. auto co = std::make_unique(ConfigKey("[Test]", "co")); auto pass = std::make_unique(ConfigKey("[Test]", "passed")); @@ -280,7 +280,7 @@ TEST_F(ControllerEngineTest, connectControl_ByString) { EXPECT_DOUBLE_EQ(1.0, pass->get()); } -TEST_F(ControllerEngineTest, connectControl_ByStringForbidDuplicateConnections) { +TEST_F(ControllerScriptEngineLegacyTest, connectControl_ByStringForbidDuplicateConnections) { // Test that connecting a control to a callback specified by a string // does not make duplicate connections. This behavior is inconsistent // with the behavior when specifying a callback as a function, but @@ -303,7 +303,7 @@ TEST_F(ControllerEngineTest, connectControl_ByStringForbidDuplicateConnections) EXPECT_DOUBLE_EQ(1.0, pass->get()); } -TEST_F(ControllerEngineTest, +TEST_F(ControllerScriptEngineLegacyTest, connectControl_ByStringRedundantConnectionObjectsAreNotIndependent) { // Test that multiple connections are not allowed when passing // the callback to engine.connectControl as a function name string. @@ -345,7 +345,7 @@ TEST_F(ControllerEngineTest, EXPECT_EQ(1.0, counter->get()); } -TEST_F(ControllerEngineTest, connectControl_ByFunction) { +TEST_F(ControllerScriptEngineLegacyTest, connectControl_ByFunction) { // Test that connecting and disconnecting with a function value works. auto co = std::make_unique(ConfigKey("[Test]", "co")); auto pass = std::make_unique(ConfigKey("[Test]", "passed")); @@ -363,7 +363,7 @@ TEST_F(ControllerEngineTest, connectControl_ByFunction) { EXPECT_DOUBLE_EQ(1.0, pass->get()); } -TEST_F(ControllerEngineTest, connectControl_ByFunctionAllowDuplicateConnections) { +TEST_F(ControllerScriptEngineLegacyTest, connectControl_ByFunctionAllowDuplicateConnections) { // Test that duplicate connections are allowed when passing callbacks as functions. auto co = std::make_unique(ConfigKey("[Test]", "co")); auto pass = std::make_unique(ConfigKey("[Test]", "passed")); @@ -384,7 +384,7 @@ TEST_F(ControllerEngineTest, connectControl_ByFunctionAllowDuplicateConnections) EXPECT_DOUBLE_EQ(2.0, pass->get()); } -TEST_F(ControllerEngineTest, connectControl_toDisconnectRemovesAllConnections) { +TEST_F(ControllerScriptEngineLegacyTest, connectControl_toDisconnectRemovesAllConnections) { // Test that every connection to a ControlObject is disconnected // by calling engine.connectControl(..., true). Individual connections // can only be disconnected by storing the connection object returned by @@ -411,7 +411,7 @@ TEST_F(ControllerEngineTest, connectControl_toDisconnectRemovesAllConnections) { EXPECT_DOUBLE_EQ(2.0, pass->get()); } -TEST_F(ControllerEngineTest, connectControl_ByLambda) { +TEST_F(ControllerScriptEngineLegacyTest, connectControl_ByLambda) { // Test that connecting with an anonymous function works. auto co = std::make_unique(ConfigKey("[Test]", "co")); auto pass = std::make_unique(ConfigKey("[Test]", "passed")); @@ -433,7 +433,7 @@ TEST_F(ControllerEngineTest, connectControl_ByLambda) { EXPECT_DOUBLE_EQ(1.0, pass->get()); } -TEST_F(ControllerEngineTest, connectionObject_Disconnect) { +TEST_F(ControllerScriptEngineLegacyTest, connectionObject_Disconnect) { // Test that disconnecting using the 'disconnect' method on the connection // object returned from connectControl works. auto co = std::make_unique(ConfigKey("[Test]", "co")); @@ -457,7 +457,7 @@ TEST_F(ControllerEngineTest, connectionObject_Disconnect) { EXPECT_DOUBLE_EQ(1.0, pass->get()); } -TEST_F(ControllerEngineTest, connectionObject_reflectDisconnect) { +TEST_F(ControllerScriptEngineLegacyTest, connectionObject_reflectDisconnect) { // Test that checks if disconnecting yields the appropriate feedback auto co = std::make_unique(ConfigKey("[Test]", "co")); auto pass = std::make_unique(ConfigKey("[Test]", "passed")); @@ -480,7 +480,7 @@ TEST_F(ControllerEngineTest, connectionObject_reflectDisconnect) { EXPECT_DOUBLE_EQ(4.0, pass->get()); } -TEST_F(ControllerEngineTest, connectionObject_DisconnectByPassingToConnectControl) { +TEST_F(ControllerScriptEngineLegacyTest, connectionObject_DisconnectByPassingToConnectControl) { // Test that passing a connection object back to engine.connectControl // removes the connection auto co = std::make_unique(ConfigKey("[Test]", "co")); @@ -521,7 +521,7 @@ TEST_F(ControllerEngineTest, connectionObject_DisconnectByPassingToConnectContro EXPECT_DOUBLE_EQ(1.0, pass->get()); } -TEST_F(ControllerEngineTest, connectionObject_MakesIndependentConnection) { +TEST_F(ControllerScriptEngineLegacyTest, connectionObject_MakesIndependentConnection) { // Test that multiple connections can be made to the same CO with // the same callback function and that calling their 'disconnect' method // only disconnects the callback for that object. @@ -560,7 +560,7 @@ TEST_F(ControllerEngineTest, connectionObject_MakesIndependentConnection) { EXPECT_EQ(3.0, counter->get()); } -TEST_F(ControllerEngineTest, connectionObject_trigger) { +TEST_F(ControllerScriptEngineLegacyTest, connectionObject_trigger) { // Test that triggering using the 'trigger' method on the connection // object returned from connectControl works. auto co = std::make_unique(ConfigKey("[Test]", "co")); @@ -581,7 +581,7 @@ TEST_F(ControllerEngineTest, connectionObject_trigger) { EXPECT_DOUBLE_EQ(1.0, counter->get()); } -TEST_F(ControllerEngineTest, connectionExecutesWithCorrectThisObject) { +TEST_F(ControllerScriptEngineLegacyTest, connectionExecutesWithCorrectThisObject) { // Test that callback functions are executed with JavaScript's // 'this' keyword referring to the object in which the connection // was created. From 13f86a5029eafb8708c51c8db06e05907e2bc10f Mon Sep 17 00:00:00 2001 From: Be Date: Wed, 2 Sep 2020 10:32:07 -0500 Subject: [PATCH 28/61] ControllerScriptEngineLegacyTest clang-format fixes --- src/test/controllerscriptenginelegacy_test.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/test/controllerscriptenginelegacy_test.cpp b/src/test/controllerscriptenginelegacy_test.cpp index 47d2c930914..022fd6ad2ef 100644 --- a/src/test/controllerscriptenginelegacy_test.cpp +++ b/src/test/controllerscriptenginelegacy_test.cpp @@ -1,10 +1,11 @@ +#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" + #include #include #include "control/controlobject.h" #include "control/controlpotmeter.h" #include "controllers/controllerdebug.h" -#include "controllers/scripting/legacy/controllerscriptenginelegacy.h" #include "controllers/softtakeover.h" #include "preferences/usersettings.h" #include "test/mixxxtest.h" @@ -90,7 +91,9 @@ TEST_F(ControllerScriptEngineLegacyTest, setValue_IgnoresNaN) { TEST_F(ControllerScriptEngineLegacyTest, getSetValue) { auto co = std::make_unique(ConfigKey("[Test]", "co")); - EXPECT_TRUE(evaluateAndAssert("engine.setValue('[Test]', 'co', engine.getValue('[Test]', 'co') + 1);")); + EXPECT_TRUE( + evaluateAndAssert("engine.setValue('[Test]', 'co', " + "engine.getValue('[Test]', 'co') + 1);")); EXPECT_DOUBLE_EQ(1.0, co->get()); } From bdf497bc753a6668fc944498254c9e2696f914f8 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 15:07:04 -0500 Subject: [PATCH 29/61] ControllerScriptEngineBase: make m_scriptWatcher private --- src/controllers/scripting/controllerscriptenginebase.cpp | 4 ++++ src/controllers/scripting/controllerscriptenginebase.h | 7 ++++--- src/controllers/scripting/controllerscriptmoduleengine.cpp | 2 +- .../scripting/legacy/controllerscriptenginelegacy.cpp | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 95d9d51331e..e237ac75794 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -218,6 +218,10 @@ void ControllerScriptEngineBase::scriptErrorDialog( } } +void ControllerScriptEngineBase::watchScriptFile(const QString& absoluteFilePath) { + m_scriptWatcher.addPath(absoluteFilePath); +} + void ControllerScriptEngineBase::errorDialogButton( const QString& key, QMessageBox::StandardButton clickedButton) { Q_UNUSED(key); diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index dd50766f8ca..f993b4f9147 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -42,6 +42,8 @@ class ControllerScriptEngineBase : public QObject { void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); + void watchScriptFile(const QString& absoluteFilePath); + QJSValue wrapArrayBufferCallback(const QJSValue& callback); bool m_bDisplayingExceptionDialog; @@ -49,9 +51,6 @@ class ControllerScriptEngineBase : public QObject { Controller* m_pController; - // Filesystem watcher for script auto-reload - QFileSystemWatcher m_scriptWatcher; - bool m_bTesting; protected slots: @@ -59,6 +58,8 @@ class ControllerScriptEngineBase : public QObject { private: QJSValue m_makeArrayBufferWrapperFunction; + // Filesystem watcher for script auto-reload + QFileSystemWatcher m_scriptWatcher; private slots: void errorDialogButton(const QString& key, QMessageBox::StandardButton button); diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index e5b64477c0b..eee35ae75c7 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -14,7 +14,7 @@ bool ControllerScriptModuleEngine::initialize() { return false; } - m_scriptWatcher.addPath(m_moduleFileInfo.absoluteFilePath()); + watchScriptFile(m_moduleFileInfo.absoluteFilePath()); QJSValue initFunction = mod.property("init"); if (!executeFunction(initFunction, QJSValueList{})) { diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 18e8a150c4f..778424812c3 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -97,7 +97,7 @@ bool ControllerScriptEngineLegacy::initialize() { if (!script.functionPrefix.isEmpty()) { m_scriptFunctionPrefixes.append(script.functionPrefix); } - m_scriptWatcher.addPath(script.file.absoluteFilePath()); + watchScriptFile(script.file.absoluteFilePath()); } for (QString functionName : m_scriptFunctionPrefixes) { @@ -161,7 +161,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil << scriptFile.absoluteFilePath(); return false; } - m_scriptWatcher.addPath(scriptFile.absoluteFilePath()); + watchScriptFile(scriptFile.absoluteFilePath()); qDebug() << "ControllerScriptHandlerLegacy: Loading" << scriptFile.absoluteFilePath(); From 3383aef72f9bdc5c1fa1353a3949512586c788f9 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 15:12:01 -0500 Subject: [PATCH 30/61] ControllerScriptEngineBase: don't call virtual destructor --- src/controllers/scripting/controllerscriptenginebase.cpp | 4 ---- src/controllers/scripting/controllerscriptenginebase.h | 2 +- src/controllers/scripting/controllerscriptmoduleengine.cpp | 4 ++++ src/controllers/scripting/controllerscriptmoduleengine.h | 1 + .../scripting/legacy/controllerscriptenginelegacy.cpp | 4 ++++ .../scripting/legacy/controllerscriptenginelegacy.h | 1 + 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index e237ac75794..4dc6b90df2e 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -23,10 +23,6 @@ ControllerScriptEngineBase::ControllerScriptEngineBase(Controller* controller) &ControllerScriptEngineBase::reload); } -ControllerScriptEngineBase::~ControllerScriptEngineBase() { - shutdown(); -} - bool ControllerScriptEngineBase::initialize() { VERIFY_OR_DEBUG_ASSERT(!m_pJSEngine) { return false; diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index f993b4f9147..8ac90152b9b 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -18,7 +18,7 @@ class ControllerScriptEngineBase : public QObject { Q_OBJECT public: ControllerScriptEngineBase(Controller* controller); - virtual ~ControllerScriptEngineBase(); + virtual ~ControllerScriptEngineBase() = default; virtual bool initialize(); diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index eee35ae75c7..0e7cd246146 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -1,5 +1,9 @@ #include "controllers/scripting/controllerscriptmoduleengine.h" +ControllerScriptModuleEngine::~ControllerScriptModuleEngine() { + shutdown(); +} + bool ControllerScriptModuleEngine::initialize() { ControllerScriptEngineBase::initialize(); #if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0) diff --git a/src/controllers/scripting/controllerscriptmoduleengine.h b/src/controllers/scripting/controllerscriptmoduleengine.h index 3fc44a1c843..0b0aeb2502f 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.h +++ b/src/controllers/scripting/controllerscriptmoduleengine.h @@ -7,6 +7,7 @@ class ControllerScriptModuleEngine : public ControllerScriptEngineBase { Q_OBJECT public: ControllerScriptModuleEngine(Controller* controller); + ~ControllerScriptModuleEngine(); bool initialize() override; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 778424812c3..fdc3f2ace55 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -12,6 +12,10 @@ ControllerScriptEngineLegacy::ControllerScriptEngineLegacy(Controller* controlle : ControllerScriptEngineBase(controller) { } +ControllerScriptEngineLegacy::~ControllerScriptEngineLegacy() { + shutdown(); +} + bool ControllerScriptEngineLegacy::callFunctionOnObjects(QList scriptFunctionPrefixes, const QString& function, QJSValueList args, diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 5fa58474a48..f7fad0325ad 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -20,6 +20,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { Q_OBJECT public: ControllerScriptEngineLegacy(Controller* controller); + ~ControllerScriptEngineLegacy(); bool initialize() override; From 19b9aaf2ab97063118b26e0c0253730418adfe28 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 15:12:51 -0500 Subject: [PATCH 31/61] ControllerScriptEngineBase: fix removing files from watcher --- src/controllers/scripting/controllerscriptenginebase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 4dc6b90df2e..5ca8baf7888 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -71,7 +71,7 @@ bool ControllerScriptEngineBase::initialize() { } void ControllerScriptEngineBase::shutdown() { - m_scriptWatcher.removePaths(m_scriptWatcher.directories()); + m_scriptWatcher.removePaths(m_scriptWatcher.files()); // Delete the script engine, first clearing the pointer so that // other threads will not get the dead pointer after we delete it. From 4505c86d30622d164be881ba28debc76763f6d07 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 16:38:03 -0500 Subject: [PATCH 32/61] ControllerScriptEngineBase: pass QFileInfo& to watchScriptFile --- src/controllers/scripting/controllerscriptenginebase.cpp | 4 ++-- src/controllers/scripting/controllerscriptenginebase.h | 2 +- src/controllers/scripting/controllerscriptmoduleengine.cpp | 2 +- .../scripting/legacy/controllerscriptenginelegacy.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 5ca8baf7888..2a794f78d7b 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -214,8 +214,8 @@ void ControllerScriptEngineBase::scriptErrorDialog( } } -void ControllerScriptEngineBase::watchScriptFile(const QString& absoluteFilePath) { - m_scriptWatcher.addPath(absoluteFilePath); +void ControllerScriptEngineBase::watchScriptFile(const QFileInfo& fileInfo) { + m_scriptWatcher.addPath(fileInfo.absoluteFilePath()); } void ControllerScriptEngineBase::errorDialogButton( diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 8ac90152b9b..3d6219d005f 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -42,7 +42,7 @@ class ControllerScriptEngineBase : public QObject { void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); - void watchScriptFile(const QString& absoluteFilePath); + void watchScriptFile(const QFileInfo& fileInfo); QJSValue wrapArrayBufferCallback(const QJSValue& callback); diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index 0e7cd246146..51db9d92bb4 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -18,7 +18,7 @@ bool ControllerScriptModuleEngine::initialize() { return false; } - watchScriptFile(m_moduleFileInfo.absoluteFilePath()); + watchScriptFile(m_moduleFileInfo); QJSValue initFunction = mod.property("init"); if (!executeFunction(initFunction, QJSValueList{})) { diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index fdc3f2ace55..118b7a5c725 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -101,7 +101,7 @@ bool ControllerScriptEngineLegacy::initialize() { if (!script.functionPrefix.isEmpty()) { m_scriptFunctionPrefixes.append(script.functionPrefix); } - watchScriptFile(script.file.absoluteFilePath()); + watchScriptFile(script.file); } for (QString functionName : m_scriptFunctionPrefixes) { @@ -165,7 +165,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil << scriptFile.absoluteFilePath(); return false; } - watchScriptFile(scriptFile.absoluteFilePath()); + watchScriptFile(scriptFile); qDebug() << "ControllerScriptHandlerLegacy: Loading" << scriptFile.absoluteFilePath(); From 95b83eaf35e2c61b18a2c058f63b00b67cb70061 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 16:49:48 -0500 Subject: [PATCH 33/61] make ControllerScriptEngine classes' constructors explicit --- src/controllers/scripting/controllerscriptenginebase.h | 2 +- src/controllers/scripting/controllerscriptmoduleengine.h | 2 +- src/controllers/scripting/legacy/controllerscriptenginelegacy.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 3d6219d005f..fcfb551d9c5 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -17,7 +17,7 @@ class EvaluationException; class ControllerScriptEngineBase : public QObject { Q_OBJECT public: - ControllerScriptEngineBase(Controller* controller); + explicit ControllerScriptEngineBase(Controller* controller); virtual ~ControllerScriptEngineBase() = default; virtual bool initialize(); diff --git a/src/controllers/scripting/controllerscriptmoduleengine.h b/src/controllers/scripting/controllerscriptmoduleengine.h index 0b0aeb2502f..a11f8d702fa 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.h +++ b/src/controllers/scripting/controllerscriptmoduleengine.h @@ -6,7 +6,7 @@ class ControllerScriptModuleEngine : public ControllerScriptEngineBase { Q_OBJECT public: - ControllerScriptModuleEngine(Controller* controller); + explicit ControllerScriptModuleEngine(Controller* controller); ~ControllerScriptModuleEngine(); bool initialize() override; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index f7fad0325ad..d2a12ee45c9 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -19,7 +19,7 @@ class ScriptConnection; class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { Q_OBJECT public: - ControllerScriptEngineLegacy(Controller* controller); + explicit ControllerScriptEngineLegacy(Controller* controller); ~ControllerScriptEngineLegacy(); bool initialize() override; From e4bdfddaf355426df7180d6390252ede161ab7f0 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 18:10:33 -0500 Subject: [PATCH 34/61] mark child classes' destructors override --- src/controllers/scripting/controllerscriptmoduleengine.h | 2 +- src/controllers/scripting/legacy/controllerscriptenginelegacy.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/scripting/controllerscriptmoduleengine.h b/src/controllers/scripting/controllerscriptmoduleengine.h index a11f8d702fa..78ef825bfb5 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.h +++ b/src/controllers/scripting/controllerscriptmoduleengine.h @@ -7,7 +7,7 @@ class ControllerScriptModuleEngine : public ControllerScriptEngineBase { Q_OBJECT public: explicit ControllerScriptModuleEngine(Controller* controller); - ~ControllerScriptModuleEngine(); + ~ControllerScriptModuleEngine() override; bool initialize() override; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index d2a12ee45c9..fc06ebea776 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -20,7 +20,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { Q_OBJECT public: explicit ControllerScriptEngineLegacy(Controller* controller); - ~ControllerScriptEngineLegacy(); + ~ControllerScriptEngineLegacy() override; bool initialize() override; From 269de0d57188bb55e799d7209655c7e3e7ae867d Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 18:13:33 -0500 Subject: [PATCH 35/61] ControllerScriptEngineBase: log error when unable to watch file --- src/controllers/scripting/controllerscriptenginebase.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 2a794f78d7b..2e48f19cf16 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -215,7 +215,9 @@ void ControllerScriptEngineBase::scriptErrorDialog( } void ControllerScriptEngineBase::watchScriptFile(const QFileInfo& fileInfo) { - m_scriptWatcher.addPath(fileInfo.absoluteFilePath()); + if (!m_scriptWatcher.addPath(fileInfo.absoluteFilePath())) { + controllerDebug("Failed to watch script file " + fileInfo.absoluteFilePath()); + } } void ControllerScriptEngineBase::errorDialogButton( From d72618f65e637d2f578e1f613bcac7079690ffed Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 19:12:50 -0500 Subject: [PATCH 36/61] move script watching from ControllerScriptEngineBase to child classes Removing scripts from the file system watcher should not be done in the shutdown method because this prevents reloading an invalid script that has been fixed. --- .../scripting/controllerscriptenginebase.cpp | 15 --------------- .../scripting/controllerscriptenginebase.h | 4 ---- .../scripting/controllerscriptmoduleengine.cpp | 12 +++++++++++- .../scripting/controllerscriptmoduleengine.h | 1 + .../legacy/controllerscriptenginelegacy.cpp | 13 +++++++++++-- .../legacy/controllerscriptenginelegacy.h | 2 ++ 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 2e48f19cf16..33fbb0998f6 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -14,13 +14,6 @@ ControllerScriptEngineBase::ControllerScriptEngineBase(Controller* controller) m_bTesting(false) { // Handle error dialog buttons qRegisterMetaType("QMessageBox::StandardButton"); - - // Subclasses are responsible for adding paths to watch to m_scriptWatcher - // in their initializeScriptEngine method. - connect(&m_scriptWatcher, - &QFileSystemWatcher::fileChanged, - this, - &ControllerScriptEngineBase::reload); } bool ControllerScriptEngineBase::initialize() { @@ -71,8 +64,6 @@ bool ControllerScriptEngineBase::initialize() { } void ControllerScriptEngineBase::shutdown() { - m_scriptWatcher.removePaths(m_scriptWatcher.files()); - // Delete the script engine, first clearing the pointer so that // other threads will not get the dead pointer after we delete it. if (m_pJSEngine) { @@ -214,12 +205,6 @@ void ControllerScriptEngineBase::scriptErrorDialog( } } -void ControllerScriptEngineBase::watchScriptFile(const QFileInfo& fileInfo) { - if (!m_scriptWatcher.addPath(fileInfo.absoluteFilePath())) { - controllerDebug("Failed to watch script file " + fileInfo.absoluteFilePath()); - } -} - void ControllerScriptEngineBase::errorDialogButton( const QString& key, QMessageBox::StandardButton clickedButton) { Q_UNUSED(key); diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index fcfb551d9c5..25ab4d45aa3 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -42,8 +42,6 @@ class ControllerScriptEngineBase : public QObject { void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); - void watchScriptFile(const QFileInfo& fileInfo); - QJSValue wrapArrayBufferCallback(const QJSValue& callback); bool m_bDisplayingExceptionDialog; @@ -58,8 +56,6 @@ class ControllerScriptEngineBase : public QObject { private: QJSValue m_makeArrayBufferWrapperFunction; - // Filesystem watcher for script auto-reload - QFileSystemWatcher m_scriptWatcher; private slots: void errorDialogButton(const QString& key, QMessageBox::StandardButton button); diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index 51db9d92bb4..527b044f744 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -1,5 +1,13 @@ #include "controllers/scripting/controllerscriptmoduleengine.h" +ControllerScriptModuleEngine::ControllerScriptModuleEngine(Controller* controller) + : ControllerScriptEngineBase(controller) { + connect(&m_fileWatcher, + &QFileSystemWatcher::fileChanged, + this, + &ControllerScriptModuleEngine::reload); +} + ControllerScriptModuleEngine::~ControllerScriptModuleEngine() { shutdown(); } @@ -18,7 +26,9 @@ bool ControllerScriptModuleEngine::initialize() { return false; } - watchScriptFile(m_moduleFileInfo); + if (!m_fileWatcher.addPath(m_moduleFileInfo.absoluteFilePath())) { + qWarning() << "Failed to watch script file" << m_moduleFileInfo.absoluteFilePath(); + } QJSValue initFunction = mod.property("init"); if (!executeFunction(initFunction, QJSValueList{})) { diff --git a/src/controllers/scripting/controllerscriptmoduleengine.h b/src/controllers/scripting/controllerscriptmoduleengine.h index 78ef825bfb5..68a877f83be 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.h +++ b/src/controllers/scripting/controllerscriptmoduleengine.h @@ -24,4 +24,5 @@ class ControllerScriptModuleEngine : public ControllerScriptEngineBase { QJSValue m_shutdownFunction; QFileInfo m_moduleFileInfo; + QFileSystemWatcher m_fileWatcher; }; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 118b7a5c725..25cd7229927 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -10,6 +10,10 @@ ControllerScriptEngineLegacy::ControllerScriptEngineLegacy(Controller* controller) : ControllerScriptEngineBase(controller) { + connect(&m_fileWatcher, + &QFileSystemWatcher::fileChanged, + this, + &ControllerScriptEngineLegacy::reload); } ControllerScriptEngineLegacy::~ControllerScriptEngineLegacy() { @@ -101,7 +105,6 @@ bool ControllerScriptEngineLegacy::initialize() { if (!script.functionPrefix.isEmpty()) { m_scriptFunctionPrefixes.append(script.functionPrefix); } - watchScriptFile(script.file); } for (QString functionName : m_scriptFunctionPrefixes) { @@ -165,7 +168,13 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil << scriptFile.absoluteFilePath(); return false; } - watchScriptFile(scriptFile); + + // If the script is invalid, it should be watched so the user can fix it + // without having to restart Mixxx. So, add it to the watcher before + // evaluating it. + if (!m_fileWatcher.addPath(scriptFile.absoluteFilePath())) { + qWarning() << "Failed to watch script file" << scriptFile.absoluteFilePath(); + }; qDebug() << "ControllerScriptHandlerLegacy: Loading" << scriptFile.absoluteFilePath(); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index fc06ebea776..9ce7d27e54f 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -58,6 +58,8 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QHash m_scriptWrappedFunctionCache; QList m_scriptFiles; + QFileSystemWatcher m_fileWatcher; + friend class ScriptConnection; friend class ControllerScriptInterfaceLegacy; friend class ColorJSProxy; From d677f4be6e44a3dc5e4fdcd1c013f335ccf37231 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 19:29:30 -0500 Subject: [PATCH 37/61] ControllerScriptEngineLegacy: stop watching old script files --- src/controllers/scripting/legacy/controllerscriptenginelegacy.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 9ce7d27e54f..a921e46f5dd 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -39,6 +39,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { public slots: void setScriptFiles(const QList& scripts) { + m_fileWatcher.removePaths(m_fileWatcher.files()); m_scriptFiles = scripts; } From 5b027152783c3c19a81f98dabf4d222d6980bb34 Mon Sep 17 00:00:00 2001 From: Be Date: Sat, 24 Oct 2020 19:33:19 -0500 Subject: [PATCH 38/61] remove class names from debug messages This is error prone when renaming the classes. Also, the log should be identified by the logging category. --- .../legacy/controllerscriptenginelegacy.cpp | 12 +++++----- .../controllerscriptinterfacelegacy.cpp | 22 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 25cd7229927..4b3f1f65021 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -34,17 +34,17 @@ bool ControllerScriptEngineLegacy::callFunctionOnObjects(QList scriptFu for (const QString& prefixName : scriptFunctionPrefixes) { QJSValue prefix = global.property(prefixName); if (!prefix.isObject()) { - qWarning() << "ControllerScriptHandlerLegacy: No" << prefixName << "object in script"; + qWarning() << "No" << prefixName << "object in script"; continue; } QJSValue init = prefix.property(function); if (!init.isCallable()) { - qWarning() << "ControllerScriptHandlerLegacy:" << prefixName << "has no" + qWarning() << prefixName << "has no" << function << " method"; continue; } - controllerDebug("ControllerScriptHandlerLegacy: Executing" + controllerDebug("Executing" << prefixName << "." << function); QJSValue result = init.callWithInstance(prefix, args); if (result.isError()) { @@ -164,7 +164,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil } if (!scriptFile.exists()) { - qWarning() << "ControllerScriptHandlerLegacy: File does not exist:" + qWarning() << "File does not exist:" << scriptFile.absoluteFilePath(); return false; } @@ -176,7 +176,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil qWarning() << "Failed to watch script file" << scriptFile.absoluteFilePath(); }; - qDebug() << "ControllerScriptHandlerLegacy: Loading" + qDebug() << "Loading" << scriptFile.absoluteFilePath(); // Read in the script file @@ -184,7 +184,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil QFile input(filename); if (!input.open(QIODevice::ReadOnly)) { qWarning() << QString( - "ControllerScriptHandlerLegacy: Problem opening the script file: %1, " + "Problem opening the script file: %1, " "error # %2, %3") .arg(filename, QString::number(input.error()), diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 458bf87e762..584ce6ea096 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -102,7 +102,7 @@ ControlObjectScript* ControllerScriptInterfaceLegacy::getControlObjectScript( double ControllerScriptInterfaceLegacy::getValue(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerScriptInterface: Unknown control" << group << name + qWarning() << "Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -112,7 +112,7 @@ double ControllerScriptInterfaceLegacy::getValue(QString group, QString name) { void ControllerScriptInterfaceLegacy::setValue( QString group, QString name, double newValue) { if (isnan(newValue)) { - qWarning() << "ControllerScriptInterface: script setting [" << group << "," + qWarning() << "script setting [" << group << "," << name << "] to NotANumber, ignoring."; return; } @@ -133,7 +133,7 @@ void ControllerScriptInterfaceLegacy::setValue( double ControllerScriptInterfaceLegacy::getParameter(QString group, QString name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerScriptInterface: Unknown control" << group << name + qWarning() << "Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -143,7 +143,7 @@ double ControllerScriptInterfaceLegacy::getParameter(QString group, QString name void ControllerScriptInterfaceLegacy::setParameter( QString group, QString name, double newParameter) { if (isnan(newParameter)) { - qWarning() << "ControllerScriptInterface: script setting [" << group << "," + qWarning() << "script setting [" << group << "," << name << "] to NotANumber, ignoring."; return; } @@ -162,7 +162,7 @@ void ControllerScriptInterfaceLegacy::setParameter( double ControllerScriptInterfaceLegacy::getParameterForValue( QString group, QString name, double value) { if (isnan(value)) { - qWarning() << "ControllerScriptInterface: script setting [" << group << "," + qWarning() << "script setting [" << group << "," << name << "] to NotANumber, ignoring."; return 0.0; } @@ -170,7 +170,7 @@ double ControllerScriptInterfaceLegacy::getParameterForValue( ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerScriptInterface: Unknown control" << group << name + qWarning() << "Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -189,7 +189,7 @@ double ControllerScriptInterfaceLegacy::getDefaultValue(QString group, QString n ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerScriptInterface: Unknown control" << group << name + qWarning() << "Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -202,7 +202,7 @@ double ControllerScriptInterfaceLegacy::getDefaultParameter( ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { - qWarning() << "ControllerScriptInterface: Unknown control" << group << name + qWarning() << "Unknown control" << group << name << ", returning 0.0"; return 0.0; } @@ -223,7 +223,7 @@ QJSValue ControllerScriptInterfaceLegacy::makeConnection( // existing during tests is okay. if (!m_pScriptEngineLegacy->isTesting()) { m_pScriptEngineLegacy->throwJSError( - "ControllerScriptInterface: script tried to connect to " + "script tried to connect to " "ControlObject (" + group + ", " + name + ") which is non-existent."); } @@ -313,12 +313,12 @@ QJSValue ControllerScriptInterfaceLegacy::connectControl( if (!m_pScriptEngineLegacy->isTesting()) { if (disconnect) { m_pScriptEngineLegacy->throwJSError( - "ControllerScriptInterface: script tried to disconnect from " + "script tried to disconnect from " "ControlObject (" + group + ", " + name + ") which is non-existent."); } else { m_pScriptEngineLegacy->throwJSError( - "ControllerScriptInterface: script tried to connect to " + "script tried to connect to " "ControlObject (" + group + ", " + name + ") which is non-existent."); } From 424c94b5179d6339a6196dc247243252ce33bc4e Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:20:03 -0500 Subject: [PATCH 39/61] Controller: remove unnecessary local variable --- src/controllers/controller.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 6acaddb37a4..0f0988c60c6 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -68,14 +68,12 @@ bool Controller::applyPreset(bool initializeScripts) { return true; } - bool success = true; - m_pScriptEngineLegacy->setScriptFiles(scriptFiles); if (initializeScripts) { - success = m_pScriptEngineLegacy->initialize(); + return m_pScriptEngineLegacy->initialize(); } - return success; + return true; } void Controller::startLearning() { From d50b710aeeaf2692a8856bcaf7629e3dfcc43475 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:31:29 -0500 Subject: [PATCH 40/61] HSS1394 & Bulk controllers: don't use QByteArray::fromRawData These Controller subclasses both run their own threads for receiving data fromt the controller, so it is not safe to access the data in another thread without making a copy. --- src/controllers/bulk/bulkcontroller.cpp | 3 +-- src/controllers/midi/hss1394controller.cpp | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/controllers/bulk/bulkcontroller.cpp b/src/controllers/bulk/bulkcontroller.cpp index fbf402abb7d..c17bcda52f6 100644 --- a/src/controllers/bulk/bulkcontroller.cpp +++ b/src/controllers/bulk/bulkcontroller.cpp @@ -52,8 +52,7 @@ void BulkReader::run() { if (result >= 0) { Trace process("BulkReader process packet"); //qDebug() << "Read" << result << "bytes, pointer:" << data; - QByteArray byteArray = QByteArray::fromRawData( - reinterpret_cast(data), transferred); + QByteArray byteArray(reinterpret_cast(data), transferred); emit incomingData(byteArray, mixxx::Time::elapsed()); } } diff --git a/src/controllers/midi/hss1394controller.cpp b/src/controllers/midi/hss1394controller.cpp index 95f30864c51..7c09f28c4c1 100644 --- a/src/controllers/midi/hss1394controller.cpp +++ b/src/controllers/midi/hss1394controller.cpp @@ -48,9 +48,8 @@ void DeviceChannelListener::Process(const hss1394::uint8 *pBuffer, hss1394::uint break; default: // Handle platter messages and any others that are not 3 bytes - QByteArray outArray = QByteArray::fromRawData( - reinterpret_cast(pBuffer), uBufferSize); - emit receiveSysex(outArray, timestamp); + QByteArray byteArray(reinterpret_cast(pBuffer), uBufferSize); + emit receiveSysex(byteArray, timestamp); i = uBufferSize; break; } From fa8771fa59ae9d9f541230bda9ad437dc904bc74 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:31:51 -0500 Subject: [PATCH 41/61] MidiController: formatting --- src/controllers/midi/midicontroller.h | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index 36a4f68aa43..48855646fc8 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -69,7 +69,8 @@ class MidiController : public Controller { } protected slots: - virtual void receiveShortMessage(unsigned char status, + virtual void receiveShortMessage( + unsigned char status, unsigned char control, unsigned char value, mixxx::Duration timestamp); @@ -91,14 +92,16 @@ class MidiController : public Controller { void commitTemporaryInputMappings(); private: - void processInputMapping(const MidiInputMapping& mapping, - unsigned char status, - unsigned char control, - unsigned char value, - mixxx::Duration timestamp); - void processInputMapping(const MidiInputMapping& mapping, - const QByteArray& data, - mixxx::Duration timestamp); + void processInputMapping( + const MidiInputMapping& mapping, + unsigned char status, + unsigned char control, + unsigned char value, + mixxx::Duration timestamp); + void processInputMapping( + const MidiInputMapping& mapping, + const QByteArray& data, + mixxx::Duration timestamp); double computeValue(MidiOptions options, double _prevmidivalue, double _newmidivalue); void createOutputHandlers(); From c96a2c2ecb0cf6dad25badf9150c3e86e9a3606c Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:32:14 -0500 Subject: [PATCH 42/61] ControllerScriptEngineBase: remove superfluous QString() wrapper --- src/controllers/scripting/controllerscriptenginebase.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 33fbb0998f6..f19d0373570 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -168,8 +168,7 @@ void ControllerScriptEngineBase::scriptErrorDialog( props->setType(DLG_WARNING); props->setTitle(tr("Controller Preset Error")); - props->setText(QString(tr("The preset for your controller \"%1\" is not " - "working properly.")) + props->setText(tr("The preset for your controller \"%1\" is not working properly.") .arg(m_pController->getName())); props->setInfoText(QStringLiteral("") + tr("The script code needs to be fixed.") + QStringLiteral("

") + From cb41ffd63b0e1b50d188d87f5f33388251dd4165 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:33:09 -0500 Subject: [PATCH 43/61] ControllerScriptEngineBase: make isTesting const --- src/controllers/scripting/controllerscriptenginebase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 25ab4d45aa3..5fec4c465ca 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -33,7 +33,7 @@ class ControllerScriptEngineBase : public QObject { m_bTesting = testing; }; - bool isTesting() { + bool isTesting() const { return m_bTesting; } From a80ca2ccf2673455b77ffae5852478ff7056f818 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:45:14 -0500 Subject: [PATCH 44/61] ControllerScriptEngineBase: remove obsolete friend declaration --- src/controllers/scripting/controllerscriptenginebase.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 5fec4c465ca..8c3401086bc 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -61,5 +61,4 @@ class ControllerScriptEngineBase : public QObject { void errorDialogButton(const QString& key, QMessageBox::StandardButton button); friend class ColorMapperJSProxy; - friend class ControllerEngineTest; }; From f7b0ddc718b6ea71b4095177c1405252203b6fbd Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:45:36 -0500 Subject: [PATCH 45/61] ControllerScriptEngineLegacy: use std::as_const for looped container --- .../scripting/legacy/controllerscriptenginelegacy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 4b3f1f65021..f3326a74fba 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -107,7 +107,7 @@ bool ControllerScriptEngineLegacy::initialize() { } } - for (QString functionName : m_scriptFunctionPrefixes) { + for (QString functionName : std::as_const(m_scriptFunctionPrefixes)) { if (functionName.isEmpty()) { continue; } From 01125544743eef9bf7513910914e664546042da0 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:51:25 -0500 Subject: [PATCH 46/61] ControllerScriptEngineLegacy: reserve known list size --- .../scripting/legacy/controllerscriptenginelegacy.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index f3326a74fba..b4a81ba9a3e 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -70,6 +70,7 @@ QJSValue ControllerScriptEngineLegacy::wrapFunctionCode( wrappedFunction = it.value(); } else { QStringList wrapperArgList; + wrapperArgList.reserve(numberOfArgs); for (int i = 1; i <= numberOfArgs; i++) { wrapperArgList << QString("arg%1").arg(i); } From 65de8c521af3903d0eae0969775a91d337f6a25b Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 14:54:04 -0500 Subject: [PATCH 47/61] ControllerScriptEngineLegacy: remove undefined declaration --- src/controllers/scripting/legacy/controllerscriptenginelegacy.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index a921e46f5dd..3a4f69a2ec8 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -47,8 +47,6 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { bool evaluateScriptFile(const QFileInfo& scriptFile); void shutdown() override; - void generateScriptFunctions(const QString& code); - bool callFunctionOnObjects(QList, const QString&, QJSValueList args = QJSValueList(), From 94d94236e25b04089f204b1c83214ff1ac8ba1b0 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 15:09:33 -0500 Subject: [PATCH 48/61] ControllerScriptEngineLegacy: remove obsolete friend class declarations --- .../scripting/legacy/controllerscriptenginelegacy.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 3a4f69a2ec8..ff4812f2496 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -59,9 +59,6 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QFileSystemWatcher m_fileWatcher; - friend class ScriptConnection; friend class ControllerScriptInterfaceLegacy; - friend class ColorJSProxy; - friend class ColorMapperJSProxy; friend class ControllerScriptEngineLegacyTest; }; From 388b4e31d010298e75073ee4159b3d2a60cb93b6 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 15:14:23 -0500 Subject: [PATCH 49/61] ControllerScriptEngineLegacy: make jsEngine() hack private and const --- .../scripting/legacy/controllerscriptenginelegacy.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index ff4812f2496..95f81eb9377 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -31,12 +31,6 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { /// and ensures the function is executed with the correct 'this' object. QJSValue wrapFunctionCode(const QString& codeSnippet, int numberOfArgs); - // There is lots of tight coupling between ControllerScriptEngineLegacy - // and ControllerScriptInterface. This is probably not worth improving in legacy code. - QJSEngine* jsEngine() { - return m_pJSEngine; - } - public slots: void setScriptFiles(const QList& scripts) { m_fileWatcher.removePaths(m_fileWatcher.files()); @@ -59,6 +53,12 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { QFileSystemWatcher m_fileWatcher; + // There is lots of tight coupling between ControllerScriptEngineLegacy + // and ControllerScriptInterface. This is probably not worth improving in legacy code. friend class ControllerScriptInterfaceLegacy; + QJSEngine* jsEngine() const { + return m_pJSEngine; + } + friend class ControllerScriptEngineLegacyTest; }; From 57c0219b21753f566b47ae13338e7f215ea98edd Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 15:17:08 -0500 Subject: [PATCH 50/61] ControllerScriptEngineLegacy: remove obsolete forward declarations --- .../scripting/legacy/controllerscriptenginelegacy.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 95f81eb9377..fec2181c724 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -9,11 +9,6 @@ #include "controllers/scripting/controllerscriptenginebase.h" #include "util/duration.h" -class Controller; -class ControllerScriptInterfaceLegacy; -class EvaluationException; -class ScriptConnection; - /// ControllerScriptEngineLegacy loads and executes controller scripts for the legacy /// JS/XML hybrid controller mapping system. class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { From e504678082dd7f740ee770b766963904c6303e71 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 15:20:13 -0500 Subject: [PATCH 51/61] ControllerScriptEngineLegacy: remove unneeded #include --- src/controllers/scripting/legacy/controllerscriptenginelegacy.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index fec2181c724..a0b2be1b4c5 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -7,7 +7,6 @@ #include "controllers/controllerpreset.h" #include "controllers/scripting/controllerscriptenginebase.h" -#include "util/duration.h" /// ControllerScriptEngineLegacy loads and executes controller scripts for the legacy /// JS/XML hybrid controller mapping system. From e35e90ff03be125178c8feef082e19013f8ad33b Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 25 Oct 2020 15:26:05 -0500 Subject: [PATCH 52/61] ControllerScriptEngineLegacy: use QStringBuilder --- .../scripting/legacy/controllerscriptenginelegacy.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index b4a81ba9a3e..864da51418d 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -213,9 +213,7 @@ bool ControllerScriptEngineLegacy::evaluateScriptFile(const QFileInfo& scriptFil return false; } - QString scriptCode; - scriptCode.append(input.readAll()); - scriptCode.append('\n'); + QString scriptCode = QString(input.readAll()) + QStringLiteral("\n"); input.close(); QJSValue scriptFunction = m_pJSEngine->evaluate(scriptCode, filename); From 5b81d285f48c862f7bddbe10b72738f55be47c71 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 26 Oct 2020 16:10:06 -0500 Subject: [PATCH 53/61] fix legacy controller preset validation tests --- src/controllers/controller.cpp | 8 ++------ src/controllers/controller.h | 8 +------- src/controllers/midi/midicontroller.cpp | 4 ++-- src/controllers/midi/midicontroller.h | 8 +------- .../scripting/legacy/controllerscriptenginelegacy.cpp | 6 ++++++ src/test/controller_preset_validation_test.cpp | 3 +-- 6 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 0f0988c60c6..4d6de005e7c 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -51,7 +51,7 @@ void Controller::stopEngine() { m_pScriptEngineLegacy = nullptr; } -bool Controller::applyPreset(bool initializeScripts) { +bool Controller::applyPreset() { qDebug() << "Applying controller preset..."; const ControllerPreset* pPreset = preset(); @@ -69,11 +69,7 @@ bool Controller::applyPreset(bool initializeScripts) { } m_pScriptEngineLegacy->setScriptFiles(scriptFiles); - if (initializeScripts) { - return m_pScriptEngineLegacy->initialize(); - } - - return true; + return m_pScriptEngineLegacy->initialize(); } void Controller::startLearning() { diff --git a/src/controllers/controller.h b/src/controllers/controller.h index 6622270fdf3..a635339e63f 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -84,13 +84,7 @@ class Controller : public QObject, ConstControllerPresetVisitor { // this if they have an alternate way of handling such data.) virtual void receive(const QByteArray& data, mixxx::Duration timestamp); - /// Apply the preset to the controller. - /// @brief Initializes both controller engine and static output mappings. - /// - /// @param initializeScripts Can be set to false to skip script - /// initialization for unit tests. - /// @return Returns whether it was successful. - virtual bool applyPreset(bool initializeScripts = true); + virtual bool applyPreset(); // Puts the controller in and out of learning mode. void startLearning(); diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index 440abdc5c97..c5484b8c8f0 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -58,9 +58,9 @@ bool MidiController::matchPreset(const PresetInfo& preset) { return false; } -bool MidiController::applyPreset(bool initializeScripts) { +bool MidiController::applyPreset() { // Handles the engine - bool result = Controller::applyPreset(initializeScripts); + bool result = Controller::applyPreset(); // Only execute this code if this is an output device if (isOutputDevice()) { diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index 48855646fc8..615708f6a51 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -79,13 +79,7 @@ class MidiController : public Controller { int close() override; private slots: - /// Apply the preset to the controller. - /// @brief Initializes both controller engine and static output mappings. - /// - /// @param initializeScripts Can be set to false to skip script - /// initialization for unit tests. - /// @return Returns whether it was successful. - bool applyPreset(bool initializeScripts = false) override; + bool applyPreset() override; void learnTemporaryInputMappings(const MidiInputMappings& mappings); void clearTemporaryInputMappings(); diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index 864da51418d..f0789145823 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -108,6 +108,12 @@ bool ControllerScriptEngineLegacy::initialize() { } } + // For testing, do not actually initialize the scripts, just check for + // syntax errors above. + if (m_bTesting) { + return true; + } + for (QString functionName : std::as_const(m_scriptFunctionPrefixes)) { if (functionName.isEmpty()) { continue; diff --git a/src/test/controller_preset_validation_test.cpp b/src/test/controller_preset_validation_test.cpp index ac51bb14033..8a2399dfe59 100644 --- a/src/test/controller_preset_validation_test.cpp +++ b/src/test/controller_preset_validation_test.cpp @@ -82,8 +82,7 @@ bool ControllerPresetValidationTest::testLoadPreset(const PresetInfo& preset) { FakeController controller; controller.setDeviceName("Test Controller"); controller.setPreset(*pPreset); - // Do not initialize the scripts. - bool result = controller.applyPreset(false); + bool result = controller.applyPreset(); controller.stopEngine(); return result; } From 98d9e377f7e133a1674e9c0865f3ca124adbd7a2 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 26 Oct 2020 17:43:48 -0500 Subject: [PATCH 54/61] fix PortMidiController tests --- src/controllers/controller.h | 4 ++-- src/test/portmidicontroller_test.cpp | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/controllers/controller.h b/src/controllers/controller.h index a635339e63f..b624c19a5b2 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -101,11 +101,11 @@ class Controller : public QObject, ConstControllerPresetVisitor { // To be called in sub-class' open() functions after opening the device but // before starting any input polling/processing. - void startEngine(); + virtual void startEngine(); // To be called in sub-class' close() functions after stopping any input // polling/processing but before closing the device. - void stopEngine(); + virtual void stopEngine(); // To be called when receiving events void triggerActivity(); diff --git a/src/test/portmidicontroller_test.cpp b/src/test/portmidicontroller_test.cpp index d090de7c2b6..c5e61ccf86e 100644 --- a/src/test/portmidicontroller_test.cpp +++ b/src/test/portmidicontroller_test.cpp @@ -38,6 +38,10 @@ class MockPortMidiController : public PortMidiController { MOCK_METHOD4(receiveShortMessage, void(unsigned char, unsigned char, unsigned char, mixxx::Duration)); MOCK_METHOD2(receive, void(const QByteArray&, mixxx::Duration)); + + // These tests are unrelated to scripting. + MOCK_METHOD0(startEngine, void()); + MOCK_METHOD0(stopEngine, void()); }; class MockPortMidiDevice : public PortMidiDevice { From 0e6119c9a911ab053735ea5e29c84814ca1f2789 Mon Sep 17 00:00:00 2001 From: Be Date: Fri, 30 Oct 2020 10:04:26 -0500 Subject: [PATCH 55/61] ControllerScriptEngineBase: add missing override --- src/controllers/scripting/controllerscriptenginebase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 8c3401086bc..650733ca068 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -18,7 +18,7 @@ class ControllerScriptEngineBase : public QObject { Q_OBJECT public: explicit ControllerScriptEngineBase(Controller* controller); - virtual ~ControllerScriptEngineBase() = default; + virtual ~ControllerScriptEngineBase() override = default; virtual bool initialize(); From 145cf8e1dbd5e3a19c23a85ead9a3065096d2671 Mon Sep 17 00:00:00 2001 From: Be Date: Fri, 30 Oct 2020 14:03:16 -0500 Subject: [PATCH 56/61] rename receiveShortMessage/Sysex -> receivedShortMessage/Sysex --- src/controllers/midi/hss1394controller.cpp | 16 +-- src/controllers/midi/hss1394controller.h | 4 +- src/controllers/midi/midicontroller.cpp | 2 +- src/controllers/midi/midicontroller.h | 2 +- src/controllers/midi/portmidicontroller.cpp | 4 +- src/test/midicontrollertest.cpp | 118 ++++++++++---------- src/test/portmidicontroller_test.cpp | 12 +- 7 files changed, 79 insertions(+), 79 deletions(-) diff --git a/src/controllers/midi/hss1394controller.cpp b/src/controllers/midi/hss1394controller.cpp index 7c09f28c4c1..ec59cecf569 100644 --- a/src/controllers/midi/hss1394controller.cpp +++ b/src/controllers/midi/hss1394controller.cpp @@ -40,7 +40,7 @@ void DeviceChannelListener::Process(const hss1394::uint8 *pBuffer, hss1394::uint if (i + 2 < uBufferSize) { note = pBuffer[i+1]; velocity = pBuffer[i+2]; - emit receiveShortMessage(status, note, velocity, timestamp); + emit receivedShortMessage(status, note, velocity, timestamp); } else { qWarning() << "Buffer underflow in DeviceChannelListener::Process()"; } @@ -49,7 +49,7 @@ void DeviceChannelListener::Process(const hss1394::uint8 *pBuffer, hss1394::uint default: // Handle platter messages and any others that are not 3 bytes QByteArray byteArray(reinterpret_cast(pBuffer), uBufferSize); - emit receiveSysex(byteArray, timestamp); + emit receivedSysex(byteArray, timestamp); i = uBufferSize; break; } @@ -111,11 +111,11 @@ int Hss1394Controller::open() { m_pChannelListener = new DeviceChannelListener(this, getName()); connect(m_pChannelListener, - &DeviceChannelListener::receiveShortMessage, + &DeviceChannelListener::receivedShortMessage, this, - &Hss1394Controller::receiveShortMessage); + &Hss1394Controller::receivedShortMessage); connect(m_pChannelListener, - &DeviceChannelListener::receiveSysex, + &DeviceChannelListener::receivedSysex, this, &Hss1394Controller::receive); @@ -153,11 +153,11 @@ int Hss1394Controller::close() { } disconnect(m_pChannelListener, - &DeviceChannelListener::receiveShortMessage, + &DeviceChannelListener::receivedShortMessage, this, - &Hss1394Controller::receiveShortMessage); + &Hss1394Controller::receivedShortMessage); disconnect(m_pChannelListener, - &DeviceChannelListener::receiveSysex, + &DeviceChannelListener::receivedSysex, this, &Hss1394Controller::receive); diff --git a/src/controllers/midi/hss1394controller.h b/src/controllers/midi/hss1394controller.h index bc63c6fc0e3..7c08525b23f 100644 --- a/src/controllers/midi/hss1394controller.h +++ b/src/controllers/midi/hss1394controller.h @@ -33,11 +33,11 @@ class DeviceChannelListener : public QObject, public hss1394::ChannelListener { void Disconnected(); void Reconnected(); signals: - void receiveShortMessage(unsigned char status, + void receivedShortMessage(unsigned char status, unsigned char control, unsigned char value, mixxx::Duration timestamp); - void receiveSysex(const QByteArray& data, mixxx::Duration timestamp); + void receivedSysex(const QByteArray& data, mixxx::Duration timestamp); private: QString m_sName; diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index c5484b8c8f0..4e5e6d48bab 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -200,7 +200,7 @@ void MidiController::commitTemporaryInputMappings() { m_temporaryInputMappings.clear(); } -void MidiController::receiveShortMessage(unsigned char status, +void MidiController::receivedShortMessage(unsigned char status, unsigned char control, unsigned char value, mixxx::Duration timestamp) { diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index 615708f6a51..9d1147fdccc 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -69,7 +69,7 @@ class MidiController : public Controller { } protected slots: - virtual void receiveShortMessage( + virtual void receivedShortMessage( unsigned char status, unsigned char control, unsigned char value, diff --git a/src/controllers/midi/portmidicontroller.cpp b/src/controllers/midi/portmidicontroller.cpp index e32d37d28aa..b3932938163 100644 --- a/src/controllers/midi/portmidicontroller.cpp +++ b/src/controllers/midi/portmidicontroller.cpp @@ -149,7 +149,7 @@ bool PortMidiController::poll() { if ((status & 0xF8) == 0xF8) { // Handle real-time MIDI messages at any time - receiveShortMessage(status, 0, 0, timestamp); + receivedShortMessage(status, 0, 0, timestamp); continue; } @@ -163,7 +163,7 @@ bool PortMidiController::poll() { //unsigned char channel = status & 0x0F; unsigned char note = Pm_MessageData1(m_midiBuffer[i].message); unsigned char velocity = Pm_MessageData2(m_midiBuffer[i].message); - receiveShortMessage(status, note, velocity, timestamp); + receivedShortMessage(status, note, velocity, timestamp); } } diff --git a/src/test/midicontrollertest.cpp b/src/test/midicontrollertest.cpp index 56b942ae5b5..94a34cf51aa 100644 --- a/src/test/midicontrollertest.cpp +++ b/src/test/midicontrollertest.cpp @@ -38,9 +38,9 @@ class MidiControllerTest : public MixxxTest { m_pController->visit(&preset); } - void receiveShortMessage(unsigned char status, unsigned char control, unsigned char value) { + void receivedShortMessage(unsigned char status, unsigned char control, unsigned char value) { // TODO(rryan): This test doesn't care about timestamps. - m_pController->receiveShortMessage(status, control, value, mixxx::Time::elapsed()); + m_pController->receivedShortMessage(status, control, value, mixxx::Time::elapsed()); } MidiControllerPreset m_preset; @@ -63,15 +63,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushOnOff) { loadPreset(m_preset); // Receive an on/off, sets the control on/off with each press. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Receive an on/off, sets the control on/off with each press. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -89,15 +89,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushOnOn) { loadPreset(m_preset); // Receive an on/off, sets the control on/off with each press. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Receive an on/off, sets the control on/off with each press. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -122,13 +122,13 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_ToggleOnOff_ButtonMidiOpt // NOTE(rryan): This behavior is broken! // Toggle the switch on, sets the push button on. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // The push button is stuck down here! // Toggle the switch off, sets the push button off. - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -153,13 +153,13 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_ToggleOnOff_SwitchMidiOpt // NOTE(rryan): This behavior is broken! // Toggle the switch on, sets the push button on. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // The push button is stuck down here! // Toggle the switch off, sets the push button on again. - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // NOTE(rryan): What is supposed to happen in this case? It's an open @@ -195,15 +195,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushCC) { loadPreset(m_preset); // Receive an on/off, sets the control on/off with each press. - receiveShortMessage(MIDI_CC | channel, control, 0x7F); + receivedShortMessage(MIDI_CC | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receiveShortMessage(MIDI_CC | channel, control, 0x00); + receivedShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Receive an on/off, sets the control on/off with each press. - receiveShortMessage(MIDI_CC | channel, control, 0x7F); + receivedShortMessage(MIDI_CC | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); - receiveShortMessage(MIDI_CC | channel, control, 0x00); + receivedShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -224,14 +224,14 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushOnOff) { loadPreset(m_preset); // Receive an on/off, toggles the control. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // Receive an on/off, toggles the control. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -251,14 +251,14 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushOnOn) { loadPreset(m_preset); // Receive an on/off, toggles the control. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // Receive an on/off, toggles the control. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -288,12 +288,12 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_ButtonMidiOption) // Toggle the switch on, since it is interpreted as a button press it // toggles the button on. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // Toggle the switch off, since it is interpreted as a button release it // does nothing to the toggle button. - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); } @@ -323,12 +323,12 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_SwitchMidiOption) // Toggle the switch on, since it is interpreted as a button press it // toggles the control on. - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_LT(0.0, cpb.get()); // Toggle the switch off, since it is interpreted as a button press it // toggles the control off. - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Meanwhile, the GUI toggles the control on again. @@ -338,12 +338,12 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_SwitchMidiOption) // Toggle the switch on, since it is interpreted as a button press it // toggles the control off (since it was on). - receiveShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); + receivedShortMessage(MIDI_NOTE_ON | channel, control, 0x7F); EXPECT_DOUBLE_EQ(0.0, cpb.get()); // Toggle the switch off, since it is interpreted as a button press it // toggles the control on (since it was off). - receiveShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); + receivedShortMessage(MIDI_NOTE_OFF | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); } @@ -362,14 +362,14 @@ TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushCC) { loadPreset(m_preset); // Receive an on/off, toggles the control. - receiveShortMessage(MIDI_CC | channel, control, 0x7F); - receiveShortMessage(MIDI_CC | channel, control, 0x00); + receivedShortMessage(MIDI_CC | channel, control, 0x7F); + receivedShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_LT(0.0, cpb.get()); // Receive an on/off, toggles the control. - receiveShortMessage(MIDI_CC | channel, control, 0x7F); - receiveShortMessage(MIDI_CC | channel, control, 0x00); + receivedShortMessage(MIDI_CC | channel, control, 0x7F); + receivedShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(0.0, cpb.get()); } @@ -390,15 +390,15 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_7BitCC) { loadPreset(m_preset); // Receive a 0, MIDI parameter should map to the min value. - receiveShortMessage(MIDI_CC | channel, control, 0x00); + receivedShortMessage(MIDI_CC | channel, control, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x7F, MIDI parameter should map to the potmeter max value. - receiveShortMessage(MIDI_CC | channel, control, 0x7F); + receivedShortMessage(MIDI_CC | channel, control, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x40, MIDI parameter should map to the potmeter middle value. - receiveShortMessage(MIDI_CC | channel, control, 0x40); + receivedShortMessage(MIDI_CC | channel, control, 0x40); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); } @@ -433,40 +433,40 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitCC) { // Receive a 0x0000 (lsb-first), MIDI parameter should map to the min value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x00); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x00); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x0000 (msb-first), MIDI parameter should map to the min value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x00); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x00); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x3FFF (lsb-first), MIDI parameter should map to the max value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x7F); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x7F); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x7F); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x3FFF (msb-first), MIDI parameter should map to the max value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x7F); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x7F); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x7F); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x2000 (lsb-first), MIDI parameter should map to the middle // value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x00); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x40); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); // Receive a 0x2000 (msb-first), MIDI parameter should map to the middle // value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x00); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x40); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x00); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); // Check the 14-bit resolution is actually present. Receive a 0x2001 @@ -474,8 +474,8 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitCC) { // amount. Scaling is not quite linear for MIDI parameters so just check // that incrementing the LSB by 1 is greater than the middle value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x01); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x40); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x01); EXPECT_LT(kMiddleValue, potmeter.get()); // Check the 14-bit resolution is actually present. Receive a 0x2001 @@ -483,8 +483,8 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitCC) { // amount. Scaling is not quite linear for MIDI parameters so just check // that incrementing the LSB by 1 is greater than the middle value. potmeter.set(0); - receiveShortMessage(MIDI_CC | channel, lsb_control, 0x01); - receiveShortMessage(MIDI_CC | channel, msb_control, 0x40); + receivedShortMessage(MIDI_CC | channel, lsb_control, 0x01); + receivedShortMessage(MIDI_CC | channel, msb_control, 0x40); EXPECT_LT(kMiddleValue, potmeter.get()); } @@ -504,21 +504,21 @@ TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitPitchBend) { loadPreset(m_preset); // Receive a 0x0000, MIDI parameter should map to the min value. - receiveShortMessage(MIDI_PITCH_BEND | channel, 0x00, 0x00); + receivedShortMessage(MIDI_PITCH_BEND | channel, 0x00, 0x00); EXPECT_DOUBLE_EQ(kMinValue, potmeter.get()); // Receive a 0x3FFF, MIDI parameter should map to the potmeter max value. - receiveShortMessage(MIDI_PITCH_BEND | channel, 0x7F, 0x7F); + receivedShortMessage(MIDI_PITCH_BEND | channel, 0x7F, 0x7F); EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get()); // Receive a 0x2000, MIDI parameter should map to the potmeter middle value. - receiveShortMessage(MIDI_PITCH_BEND | channel, 0x00, 0x40); + receivedShortMessage(MIDI_PITCH_BEND | channel, 0x00, 0x40); EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get()); // Check the 14-bit resolution is actually present. Receive a 0x2001, MIDI // parameter should map to the middle value plus a tiny amount. Scaling is // not quite linear for MIDI parameters so just check that incrementing the // LSB by 1 is greater than the middle value. - receiveShortMessage(MIDI_PITCH_BEND | channel, 0x01, 0x40); + receivedShortMessage(MIDI_PITCH_BEND | channel, 0x01, 0x40); EXPECT_LT(kMiddleValue, potmeter.get()); } diff --git a/src/test/portmidicontroller_test.cpp b/src/test/portmidicontroller_test.cpp index c5e61ccf86e..c93ab36d725 100644 --- a/src/test/portmidicontroller_test.cpp +++ b/src/test/portmidicontroller_test.cpp @@ -35,7 +35,7 @@ class MockPortMidiController : public PortMidiController { PortMidiController::sendSysexMsg(data, length); } - MOCK_METHOD4(receiveShortMessage, + MOCK_METHOD4(receivedShortMessage, void(unsigned char, unsigned char, unsigned char, mixxx::Duration)); MOCK_METHOD2(receive, void(const QByteArray&, mixxx::Duration)); @@ -232,9 +232,9 @@ TEST_F(PortMidiControllerTest, Poll_Read_Basic) { .WillOnce(DoAll(SetArrayArgument<0>(messages.begin(), messages.end()), Return(messages.size()))); - EXPECT_CALL(*m_pController, receiveShortMessage(0x90, 0x3C, 0x40, _)) + EXPECT_CALL(*m_pController, receivedShortMessage(0x90, 0x3C, 0x40, _)) .InSequence(read); - EXPECT_CALL(*m_pController, receiveShortMessage(0x80, 0x3C, 0x40, _)) + EXPECT_CALL(*m_pController, receivedShortMessage(0x80, 0x3C, 0x40, _)) .InSequence(read); pollDevice(); @@ -269,9 +269,9 @@ TEST_F(PortMidiControllerTest, Poll_Read_SysExWithRealtime) { .InSequence(read) .WillOnce(DoAll(SetArrayArgument<0>(messages.begin(), messages.end()), Return(messages.size()))); - EXPECT_CALL(*m_pController, receiveShortMessage(0xF8, 0x00, 0x00, _)) + EXPECT_CALL(*m_pController, receivedShortMessage(0xF8, 0x00, 0x00, _)) .InSequence(read); - EXPECT_CALL(*m_pController, receiveShortMessage(0xFA, 0x00, 0x00, _)) + EXPECT_CALL(*m_pController, receivedShortMessage(0xFA, 0x00, 0x00, _)) .InSequence(read); EXPECT_CALL(*m_pController, receive(sysex, _)) .InSequence(read); @@ -367,7 +367,7 @@ TEST_F(PortMidiControllerTest, Poll_Read_SysExInterrupted_FollowedByNormalMessag .InSequence(read) .WillOnce(DoAll(SetArrayArgument<0>(messages.begin(), messages.end()), Return(messages.size()))); - EXPECT_CALL(*m_pController, receiveShortMessage(0x90, 0x3C, 0x40, _)) + EXPECT_CALL(*m_pController, receivedShortMessage(0x90, 0x3C, 0x40, _)) .InSequence(read); pollDevice(); From 7f2187dcf4edb39a577e0790d2aabab8e91d4bac Mon Sep 17 00:00:00 2001 From: Be Date: Fri, 30 Oct 2020 14:38:37 -0500 Subject: [PATCH 57/61] ControllerScriptModuleEngine: remove input handling This will be reimplemented differently in the future. --- .../controllerscriptmoduleengine.cpp | 20 ------------------- .../scripting/controllerscriptmoduleengine.h | 3 --- 2 files changed, 23 deletions(-) diff --git a/src/controllers/scripting/controllerscriptmoduleengine.cpp b/src/controllers/scripting/controllerscriptmoduleengine.cpp index 527b044f744..540c19c980e 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.cpp +++ b/src/controllers/scripting/controllerscriptmoduleengine.cpp @@ -36,18 +36,6 @@ bool ControllerScriptModuleEngine::initialize() { return false; } - QJSValue handleInputFunction = mod.property("handleInput"); - if (handleInputFunction.isCallable()) { - m_handleInputFunction = handleInputFunction; - } else { - scriptErrorDialog( - "Controller JavaScript module exports no handleInput function.", - QStringLiteral("handleInput"), - true); - shutdown(); - return false; - } - QJSValue shutdownFunction = mod.property("shutdown"); if (shutdownFunction.isCallable()) { m_shutdownFunction = shutdownFunction; @@ -62,11 +50,3 @@ void ControllerScriptModuleEngine::shutdown() { executeFunction(m_shutdownFunction, QJSValueList()); ControllerScriptEngineBase::shutdown(); } - -void ControllerScriptModuleEngine::handleInput( - QByteArray data, mixxx::Duration timestamp) { - QJSValueList args; - args << m_pJSEngine->toScriptValue(data); - args << timestamp.toDoubleMillis(); - executeFunction(m_handleInputFunction, args); -} diff --git a/src/controllers/scripting/controllerscriptmoduleengine.h b/src/controllers/scripting/controllerscriptmoduleengine.h index 68a877f83be..3b6c7009810 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.h +++ b/src/controllers/scripting/controllerscriptmoduleengine.h @@ -15,12 +15,9 @@ class ControllerScriptModuleEngine : public ControllerScriptEngineBase { m_moduleFileInfo = moduleFileInfo; } - void handleInput(QByteArray data, mixxx::Duration timestamp); - private: void shutdown() override; - QJSValue m_handleInputFunction; QJSValue m_shutdownFunction; QFileInfo m_moduleFileInfo; From 30692a13afa31b504922a8f438fdd3a9b8338314 Mon Sep 17 00:00:00 2001 From: Be Date: Sun, 1 Nov 2020 16:25:49 -0600 Subject: [PATCH 58/61] ControllerScriptEngineBase: remove confusing (obsolete?) comment m_pJSEngine is never passed to other threads --- .../scripting/controllerscriptenginebase.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index f19d0373570..03fea4e38f7 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -64,13 +64,11 @@ bool ControllerScriptEngineBase::initialize() { } void ControllerScriptEngineBase::shutdown() { - // Delete the script engine, first clearing the pointer so that - // other threads will not get the dead pointer after we delete it. - if (m_pJSEngine) { - QJSEngine* engine = m_pJSEngine; - m_pJSEngine = nullptr; - engine->deleteLater(); + VERIFY_OR_DEBUG_ASSERT(m_pJSEngine) { + return; } + delete m_pJSEngine; + m_pJSEngine = nullptr; } void ControllerScriptEngineBase::reload() { From 86bebb65c08140298627fa3aa5508643110f8615 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 2 Nov 2020 03:03:04 -0600 Subject: [PATCH 59/61] ControllerScriptEngineBase/Legacy: use shared_ptr for QJSEngine --- mixxx.log.1 | 0 src/controllers/scripting/controllerscriptenginebase.cpp | 9 +++------ src/controllers/scripting/controllerscriptenginebase.h | 3 ++- .../scripting/legacy/controllerscriptenginelegacy.h | 2 +- .../scripting/legacy/controllerscriptinterfacelegacy.cpp | 8 ++++---- 5 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 mixxx.log.1 diff --git a/mixxx.log.1 b/mixxx.log.1 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 03fea4e38f7..74c4eaa4041 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -22,7 +22,7 @@ bool ControllerScriptEngineBase::initialize() { } // Create the Script Engine - m_pJSEngine = new QJSEngine(this); + m_pJSEngine = std::make_shared(this); QJSValue engineGlobalObject = m_pJSEngine->globalObject(); @@ -64,11 +64,8 @@ bool ControllerScriptEngineBase::initialize() { } void ControllerScriptEngineBase::shutdown() { - VERIFY_OR_DEBUG_ASSERT(m_pJSEngine) { - return; - } - delete m_pJSEngine; - m_pJSEngine = nullptr; + DEBUG_ASSERT(m_pJSEngine.use_count() == 1); + m_pJSEngine.reset(); } void ControllerScriptEngineBase::reload() { diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 650733ca068..87ae4fa4019 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "controllers/controllerpreset.h" #include "util/duration.h" @@ -45,7 +46,7 @@ class ControllerScriptEngineBase : public QObject { QJSValue wrapArrayBufferCallback(const QJSValue& callback); bool m_bDisplayingExceptionDialog; - QJSEngine* m_pJSEngine; + std::shared_ptr m_pJSEngine; Controller* m_pController; diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index a0b2be1b4c5..98075376e42 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -50,7 +50,7 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { // There is lots of tight coupling between ControllerScriptEngineLegacy // and ControllerScriptInterface. This is probably not worth improving in legacy code. friend class ControllerScriptInterfaceLegacy; - QJSEngine* jsEngine() const { + std::shared_ptr jsEngine() const { return m_pJSEngine; } diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 584ce6ea096..9b2a6d6320a 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -212,8 +212,8 @@ double ControllerScriptInterfaceLegacy::getDefaultParameter( QJSValue ControllerScriptInterfaceLegacy::makeConnection( QString group, QString name, const QJSValue callback) { - QJSEngine* jsEngine = m_pScriptEngineLegacy->jsEngine(); - VERIFY_OR_DEBUG_ASSERT(jsEngine) { + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); + VERIFY_OR_DEBUG_ASSERT(pJsEngine) { return QJSValue(); } @@ -246,7 +246,7 @@ QJSValue ControllerScriptInterfaceLegacy::makeConnection( connection.id = QUuid::createUuid(); if (coScript->addScriptConnection(connection)) { - return jsEngine->newQObject( + return pJsEngine->newQObject( new ScriptConnectionJSProxy(connection)); } @@ -302,7 +302,7 @@ QJSValue ControllerScriptInterfaceLegacy::connectControl( actualCallbackFunction = passedCallback; } - QJSEngine* pScriptEngine = m_pScriptEngineLegacy->jsEngine(); + auto pScriptEngine = m_pScriptEngineLegacy->jsEngine(); ControlObjectScript* coScript = getControlObjectScript(group, name); // This check is redundant with makeConnection, but the From 4227e22e54b3c5aff62f2ca0b15e21f69d231394 Mon Sep 17 00:00:00 2001 From: Be Date: Tue, 3 Nov 2020 08:42:13 -0600 Subject: [PATCH 60/61] ControllerScriptEngineLegacy: rename local variable --- .../scripting/legacy/controllerscriptinterfacelegacy.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index 9b2a6d6320a..c45231f3d45 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -302,7 +302,7 @@ QJSValue ControllerScriptInterfaceLegacy::connectControl( actualCallbackFunction = passedCallback; } - auto pScriptEngine = m_pScriptEngineLegacy->jsEngine(); + auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); ControlObjectScript* coScript = getControlObjectScript(group, name); // This check is redundant with makeConnection, but the @@ -331,12 +331,12 @@ QJSValue ControllerScriptInterfaceLegacy::connectControl( if (passedCallback.isString()) { // This check is redundant with makeConnection, but it must be done here // before evaluating the code string. - VERIFY_OR_DEBUG_ASSERT(pScriptEngine != nullptr) { + VERIFY_OR_DEBUG_ASSERT(pJsEngine != nullptr) { return QJSValue(false); } actualCallbackFunction = - pScriptEngine->evaluate(passedCallback.toString()); + pJsEngine->evaluate(passedCallback.toString()); if (!actualCallbackFunction.isCallable()) { QString sErrorMessage( @@ -366,7 +366,7 @@ QJSValue ControllerScriptInterfaceLegacy::connectControl( "connection " + connection.id.toString(); - return pScriptEngine->newQObject( + return pJsEngine->newQObject( new ScriptConnectionJSProxy(connection)); } } else if (passedCallback.isQObject()) { From b5cf59d44d8071f51eed0273c6b4a06e482ac2f3 Mon Sep 17 00:00:00 2001 From: Be Date: Mon, 30 Nov 2020 18:30:36 -0600 Subject: [PATCH 61/61] controllers: fix clazy warnings --- .../scripting/controllerscriptenginebase.cpp | 4 +- .../scripting/controllerscriptenginebase.h | 4 +- .../scripting/controllerscriptmoduleengine.h | 2 +- .../legacy/controllerscriptenginelegacy.cpp | 9 +++-- .../legacy/controllerscriptenginelegacy.h | 4 +- .../controllerscriptinterfacelegacy.cpp | 40 ++++++++++--------- .../legacy/controllerscriptinterfacelegacy.h | 38 +++++++++--------- 7 files changed, 53 insertions(+), 48 deletions(-) diff --git a/src/controllers/scripting/controllerscriptenginebase.cpp b/src/controllers/scripting/controllerscriptenginebase.cpp index 74c4eaa4041..14c6fde111b 100644 --- a/src/controllers/scripting/controllerscriptenginebase.cpp +++ b/src/controllers/scripting/controllerscriptenginebase.cpp @@ -74,7 +74,7 @@ void ControllerScriptEngineBase::reload() { } bool ControllerScriptEngineBase::executeFunction( - QJSValue functionObject, QJSValueList args) { + QJSValue functionObject, const QJSValueList& args) { // This function is called from outside the controller engine, so we can't // use VERIFY_OR_DEBUG_ASSERT here if (!m_pJSEngine) { @@ -104,7 +104,7 @@ bool ControllerScriptEngineBase::executeFunction( } void ControllerScriptEngineBase::showScriptExceptionDialog( - QJSValue evaluationResult, bool bFatalError) { + const QJSValue& evaluationResult, bool bFatalError) { VERIFY_OR_DEBUG_ASSERT(evaluationResult.isError()) { return; } diff --git a/src/controllers/scripting/controllerscriptenginebase.h b/src/controllers/scripting/controllerscriptenginebase.h index 87ae4fa4019..49295dbe76b 100644 --- a/src/controllers/scripting/controllerscriptenginebase.h +++ b/src/controllers/scripting/controllerscriptenginebase.h @@ -23,11 +23,11 @@ class ControllerScriptEngineBase : public QObject { virtual bool initialize(); - bool executeFunction(QJSValue functionObject, QJSValueList arguments); + bool executeFunction(QJSValue functionObject, const QJSValueList& arguments); /// Shows a UI dialog notifying of a script evaluation error. /// Precondition: QJSValue.isError() == true - void showScriptExceptionDialog(QJSValue evaluationResult, bool bFatal = false); + void showScriptExceptionDialog(const QJSValue& evaluationResult, bool bFatal = false); void throwJSError(const QString& message); inline void setTesting(bool testing) { diff --git a/src/controllers/scripting/controllerscriptmoduleengine.h b/src/controllers/scripting/controllerscriptmoduleengine.h index 3b6c7009810..76b14ee774c 100644 --- a/src/controllers/scripting/controllerscriptmoduleengine.h +++ b/src/controllers/scripting/controllerscriptmoduleengine.h @@ -11,7 +11,7 @@ class ControllerScriptModuleEngine : public ControllerScriptEngineBase { bool initialize() override; - void setModuleFileInfo(QFileInfo moduleFileInfo) { + void setModuleFileInfo(const QFileInfo& moduleFileInfo) { m_moduleFileInfo = moduleFileInfo; } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp index f0789145823..32d818c9277 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.cpp @@ -20,9 +20,10 @@ ControllerScriptEngineLegacy::~ControllerScriptEngineLegacy() { shutdown(); } -bool ControllerScriptEngineLegacy::callFunctionOnObjects(QList scriptFunctionPrefixes, +bool ControllerScriptEngineLegacy::callFunctionOnObjects( + const QList& scriptFunctionPrefixes, const QString& function, - QJSValueList args, + const QJSValueList& args, bool bFatalError) { VERIFY_OR_DEBUG_ASSERT(m_pJSEngine) { return false; @@ -98,7 +99,7 @@ bool ControllerScriptEngineLegacy::initialize() { engineGlobalObject.setProperty( "engine", m_pJSEngine->newQObject(legacyScriptInterface)); - for (const ControllerPreset::ScriptFileInfo& script : m_scriptFiles) { + for (const ControllerPreset::ScriptFileInfo& script : std::as_const(m_scriptFiles)) { if (!evaluateScriptFile(script.file)) { shutdown(); return false; @@ -158,7 +159,7 @@ bool ControllerScriptEngineLegacy::handleIncomingData(const QByteArray& data) { args << m_pJSEngine->toScriptValue(data); args << QJSValue(data.size()); - for (const QJSValue& function : m_incomingDataFunctions) { + for (const QJSValue& function : std::as_const(m_incomingDataFunctions)) { ControllerScriptEngineBase::executeFunction(function, args); } diff --git a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h index 98075376e42..d8b83bb2cda 100644 --- a/src/controllers/scripting/legacy/controllerscriptenginelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptenginelegacy.h @@ -35,9 +35,9 @@ class ControllerScriptEngineLegacy : public ControllerScriptEngineBase { bool evaluateScriptFile(const QFileInfo& scriptFile); void shutdown() override; - bool callFunctionOnObjects(QList, + bool callFunctionOnObjects(const QList& scriptFunctionPrefixes, const QString&, - QJSValueList args = QJSValueList(), + const QJSValueList& args = QJSValueList(), bool bFatalError = false); QList m_scriptFunctionPrefixes; diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp index c45231f3d45..11718bf0801 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.cpp @@ -99,7 +99,7 @@ ControlObjectScript* ControllerScriptInterfaceLegacy::getControlObjectScript( return coScript; } -double ControllerScriptInterfaceLegacy::getValue(QString group, QString name) { +double ControllerScriptInterfaceLegacy::getValue(const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { qWarning() << "Unknown control" << group << name @@ -110,7 +110,7 @@ double ControllerScriptInterfaceLegacy::getValue(QString group, QString name) { } void ControllerScriptInterfaceLegacy::setValue( - QString group, QString name, double newValue) { + const QString& group, const QString& name, double newValue) { if (isnan(newValue)) { qWarning() << "script setting [" << group << "," << name << "] to NotANumber, ignoring."; @@ -130,7 +130,7 @@ void ControllerScriptInterfaceLegacy::setValue( } } -double ControllerScriptInterfaceLegacy::getParameter(QString group, QString name) { +double ControllerScriptInterfaceLegacy::getParameter(const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { qWarning() << "Unknown control" << group << name @@ -141,7 +141,7 @@ double ControllerScriptInterfaceLegacy::getParameter(QString group, QString name } void ControllerScriptInterfaceLegacy::setParameter( - QString group, QString name, double newParameter) { + const QString& group, const QString& name, double newParameter) { if (isnan(newParameter)) { qWarning() << "script setting [" << group << "," << name << "] to NotANumber, ignoring."; @@ -160,7 +160,7 @@ void ControllerScriptInterfaceLegacy::setParameter( } double ControllerScriptInterfaceLegacy::getParameterForValue( - QString group, QString name, double value) { + const QString& group, const QString& name, double value) { if (isnan(value)) { qWarning() << "script setting [" << group << "," << name << "] to NotANumber, ignoring."; @@ -178,14 +178,14 @@ double ControllerScriptInterfaceLegacy::getParameterForValue( return coScript->getParameterForValue(value); } -void ControllerScriptInterfaceLegacy::reset(QString group, QString name) { +void ControllerScriptInterfaceLegacy::reset(const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript != nullptr) { coScript->reset(); } } -double ControllerScriptInterfaceLegacy::getDefaultValue(QString group, QString name) { +double ControllerScriptInterfaceLegacy::getDefaultValue(const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { @@ -198,7 +198,7 @@ double ControllerScriptInterfaceLegacy::getDefaultValue(QString group, QString n } double ControllerScriptInterfaceLegacy::getDefaultParameter( - QString group, QString name) { + const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript == nullptr) { @@ -211,7 +211,7 @@ double ControllerScriptInterfaceLegacy::getDefaultParameter( } QJSValue ControllerScriptInterfaceLegacy::makeConnection( - QString group, QString name, const QJSValue callback) { + const QString& group, const QString& name, const QJSValue& callback) { auto pJsEngine = m_pScriptEngineLegacy->jsEngine(); VERIFY_OR_DEBUG_ASSERT(pJsEngine) { return QJSValue(); @@ -254,7 +254,7 @@ QJSValue ControllerScriptInterfaceLegacy::makeConnection( } bool ControllerScriptInterfaceLegacy::removeScriptConnection( - const ScriptConnection connection) { + const ScriptConnection& connection) { ControlObjectScript* coScript = getControlObjectScript(connection.key.group, connection.key.item); @@ -266,7 +266,7 @@ bool ControllerScriptInterfaceLegacy::removeScriptConnection( } void ControllerScriptInterfaceLegacy::triggerScriptConnection( - const ScriptConnection connection) { + const ScriptConnection& connection) { VERIFY_OR_DEBUG_ASSERT(m_pScriptEngineLegacy->jsEngine()) { return; } @@ -288,8 +288,10 @@ void ControllerScriptInterfaceLegacy::triggerScriptConnection( // it is disconnected. // WARNING: These behaviors are quirky and confusing, so if you change this function, // be sure to run the ControllerScriptInterfaceTest suite to make sure you do not break old scripts. -QJSValue ControllerScriptInterfaceLegacy::connectControl( - QString group, QString name, QJSValue passedCallback, bool disconnect) { +QJSValue ControllerScriptInterfaceLegacy::connectControl(const QString& group, + const QString& name, + const QJSValue& passedCallback, + bool disconnect) { // The passedCallback may or may not actually be a function, so when // the actual callback function is found, store it in this variable. QJSValue actualCallbackFunction; @@ -404,14 +406,14 @@ QJSValue ControllerScriptInterfaceLegacy::connectControl( return makeConnection(group, name, actualCallbackFunction); } -void ControllerScriptInterfaceLegacy::trigger(QString group, QString name) { +void ControllerScriptInterfaceLegacy::trigger(const QString& group, const QString& name) { ControlObjectScript* coScript = getControlObjectScript(group, name); if (coScript != nullptr) { coScript->emitValueChanged(); } } -void ControllerScriptInterfaceLegacy::log(QString message) { +void ControllerScriptInterfaceLegacy::log(const QString& message) { controllerDebug(message); } int ControllerScriptInterfaceLegacy::beginTimer( @@ -494,7 +496,7 @@ void ControllerScriptInterfaceLegacy::timerEvent(QTimerEvent* event) { } void ControllerScriptInterfaceLegacy::softTakeover( - QString group, QString name, bool set) { + const QString& group, const QString& name, bool set) { ControlObject* pControl = ControlObject::getControl( ConfigKey(group, name), ControllerDebug::shouldAssertForInvalidControlObjects()); if (!pControl) { @@ -508,7 +510,7 @@ void ControllerScriptInterfaceLegacy::softTakeover( } void ControllerScriptInterfaceLegacy::softTakeoverIgnoreNextValue( - QString group, const QString name) { + const QString& group, const QString& name) { ControlObject* pControl = ControlObject::getControl( ConfigKey(group, name), ControllerDebug::shouldAssertForInvalidControlObjects()); if (!pControl) { @@ -553,7 +555,7 @@ void ControllerScriptInterfaceLegacy::scratchEnable(int deck, double beta, bool ramp) { // If we're already scratching this deck, override that with this request - if (m_dx[deck]) { + if (static_cast(m_dx[deck])) { //qDebug() << "Already scratching deck" << deck << ". Overriding."; int timerId = m_scratchTimers.key(deck); killTimer(timerId); @@ -603,7 +605,7 @@ void ControllerScriptInterfaceLegacy::scratchEnable(int deck, } // Initialize scratch filter - if (alpha && beta) { + if (static_cast(alpha) && static_cast(beta)) { m_scratchFilters[deck]->init(kAlphaBetaDt, initVelocity, alpha, beta); } else { // Use filter's defaults if not specified diff --git a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h index 2043b6beadc..11087394eb5 100644 --- a/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h +++ b/src/controllers/scripting/legacy/controllerscriptinterfacelegacy.h @@ -20,23 +20,25 @@ class ControllerScriptInterfaceLegacy : public QObject { virtual ~ControllerScriptInterfaceLegacy(); - Q_INVOKABLE double getValue(QString group, QString name); - Q_INVOKABLE void setValue(QString group, QString name, double newValue); - Q_INVOKABLE double getParameter(QString group, QString name); - Q_INVOKABLE void setParameter(QString group, QString name, double newValue); - Q_INVOKABLE double getParameterForValue(QString group, QString name, double value); - Q_INVOKABLE void reset(QString group, QString name); - Q_INVOKABLE double getDefaultValue(QString group, QString name); - Q_INVOKABLE double getDefaultParameter(QString group, QString name); - Q_INVOKABLE QJSValue makeConnection(QString group, QString name, const QJSValue callback); + Q_INVOKABLE double getValue(const QString& group, const QString& name); + Q_INVOKABLE void setValue(const QString& group, const QString& name, double newValue); + Q_INVOKABLE double getParameter(const QString& group, const QString& name); + Q_INVOKABLE void setParameter(const QString& group, const QString& name, double newValue); + Q_INVOKABLE double getParameterForValue( + const QString& group, const QString& name, double value); + Q_INVOKABLE void reset(const QString& group, const QString& name); + Q_INVOKABLE double getDefaultValue(const QString& group, const QString& name); + Q_INVOKABLE double getDefaultParameter(const QString& group, const QString& name); + Q_INVOKABLE QJSValue makeConnection( + const QString& group, const QString& name, const QJSValue& callback); // DEPRECATED: Use makeConnection instead. - Q_INVOKABLE QJSValue connectControl(QString group, - QString name, - const QJSValue passedCallback, + Q_INVOKABLE QJSValue connectControl(const QString& group, + const QString& name, + const QJSValue& passedCallback, bool disconnect = false); // Called indirectly by the objects returned by connectControl - Q_INVOKABLE void trigger(QString group, QString name); - Q_INVOKABLE void log(QString message); + Q_INVOKABLE void trigger(const QString& group, const QString& name); + Q_INVOKABLE void log(const QString& message); Q_INVOKABLE int beginTimer(int interval, QJSValue scriptCode, bool oneShot = false); Q_INVOKABLE void stopTimer(int timerId); Q_INVOKABLE void scratchEnable(int deck, @@ -48,15 +50,15 @@ class ControllerScriptInterfaceLegacy : public QObject { Q_INVOKABLE void scratchTick(int deck, int interval); Q_INVOKABLE void scratchDisable(int deck, bool ramp = true); Q_INVOKABLE bool isScratching(int deck); - Q_INVOKABLE void softTakeover(QString group, QString name, bool set); - Q_INVOKABLE void softTakeoverIgnoreNextValue(QString group, QString name); + 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); - bool removeScriptConnection(const ScriptConnection conn); + bool removeScriptConnection(const ScriptConnection& conn); /// Execute a ScriptConnection's JS callback - void triggerScriptConnection(const ScriptConnection conn); + void triggerScriptConnection(const ScriptConnection& conn); /// Handler for timers that scripts set. virtual void timerEvent(QTimerEvent* event);