1 /** 2 * @file controllerengine.h 3 * @author Sean M. Pappalardo spappalardo@mixxx.org 4 * @date Sat Apr 30 2011 5 * @brief The script engine for use by a Controller. 6 */ 7 8 #ifndef CONTROLLERENGINE_H 9 #define CONTROLLERENGINE_H 10 11 #include <QTimerEvent> 12 #include <QFileSystemWatcher> 13 #include <QMessageBox> 14 #include <QtScript> 15 16 #include "bytearrayclass.h" 17 #include "preferences/usersettings.h" 18 #include "controllers/controllerpreset.h" 19 #include "controllers/softtakeover.h" 20 #include "util/alphabetafilter.h" 21 #include "util/duration.h" 22 #include "util/memory.h" 23 24 // Forward declaration(s) 25 class Controller; 26 class ControlObjectScript; 27 class ControllerEngine; 28 29 // ScriptConnection represents a connection between 30 // a ControlObject and a script callback function that gets executed when 31 // the value of the ControlObject changes. 32 class ScriptConnection { 33 public: 34 ConfigKey key; 35 QUuid id; 36 QScriptValue callback; 37 ControllerEngine *controllerEngine; 38 QScriptValue context; 39 40 void executeCallback(double value) const; 41 42 // Required for various QList methods and iteration to work. 43 inline bool operator==(const ScriptConnection& other) const { 44 return id == other.id; 45 } 46 inline bool operator!=(const ScriptConnection& other) const { 47 return !(*this == other); 48 } 49 }; 50 51 // ScriptConnectionInvokableWrapper is a class providing scripts 52 // with an interface to ScriptConnection. 53 class ScriptConnectionInvokableWrapper : public QObject { 54 Q_OBJECT Q_PROPERTY(QString id READ readId)55 Q_PROPERTY(QString id READ readId) 56 // We cannot expose ConfigKey directly since it's not a 57 // QObject 58 //Q_PROPERTY(ConfigKey key READ key) 59 // There's little use in exposing the function... 60 //Q_PROPERTY(QScriptValue function READ function) 61 Q_PROPERTY(bool isConnected READ readIsConnected) 62 public: 63 ScriptConnectionInvokableWrapper(const ScriptConnection& conn) { 64 m_scriptConnection = conn; 65 m_idString = conn.id.toString(); 66 m_isConnected = true; 67 } readId()68 const QString& readId() const { return m_idString; } readIsConnected()69 bool readIsConnected() const { return m_isConnected; } 70 Q_INVOKABLE bool disconnect(); 71 Q_INVOKABLE void trigger(); 72 73 private: 74 ScriptConnection m_scriptConnection; 75 QString m_idString; 76 bool m_isConnected; 77 }; 78 79 class ControllerEngine : public QObject { 80 Q_OBJECT 81 public: 82 ControllerEngine(Controller* controller, UserSettingsPointer pConfig); 83 virtual ~ControllerEngine(); 84 85 bool isReady(); 86 87 // Check whether a source file that was evaluated()'d has errors. 88 bool hasErrors(const QString& filename); 89 setPopups(bool bPopups)90 void setPopups(bool bPopups) { 91 m_bPopups = bPopups; 92 } 93 94 // Wrap a snippet of JS code in an anonymous function 95 QScriptValue wrapFunctionCode(const QString& codeSnippet, int numberOfArgs); 96 QScriptValue getThisObjectInFunctionCall(); 97 98 // Look up registered script function prefixes getScriptFunctionPrefixes()99 const QList<QString>& getScriptFunctionPrefixes() { return m_scriptFunctionPrefixes; }; 100 101 // Disconnect a ScriptConnection 102 bool removeScriptConnection(const ScriptConnection& conn); 103 void triggerScriptConnection(const ScriptConnection& conn); 104 105 protected: 106 Q_INVOKABLE double getValue(const QString& group, const QString& name); 107 Q_INVOKABLE void setValue(const QString& group, const QString& name, double newValue); 108 Q_INVOKABLE double getParameter(const QString& group, const QString& name); 109 Q_INVOKABLE void setParameter(const QString& group, const QString& name, double newValue); 110 Q_INVOKABLE double getParameterForValue( 111 const QString& group, const QString& name, double value); 112 Q_INVOKABLE void reset(const QString& group, const QString& name); 113 Q_INVOKABLE double getDefaultValue(const QString& group, const QString& name); 114 Q_INVOKABLE double getDefaultParameter(const QString& group, const QString& name); 115 Q_INVOKABLE QScriptValue makeConnection(const QString& group, 116 const QString& name, 117 const QScriptValue& callback); 118 // DEPRECATED: Use makeConnection instead. 119 Q_INVOKABLE QScriptValue connectControl(const QString& group, 120 const QString& name, 121 const QScriptValue& passedCallback, 122 bool disconnect = false); 123 // Called indirectly by the objects returned by connectControl 124 Q_INVOKABLE void trigger(const QString& group, const QString& name); 125 Q_INVOKABLE void log(const QString& message); 126 Q_INVOKABLE int beginTimer(int interval, const QScriptValue& scriptCode, bool oneShot = false); 127 Q_INVOKABLE void stopTimer(int timerId); 128 Q_INVOKABLE void scratchEnable(int deck, int intervalsPerRev, double rpm, 129 double alpha, double beta, bool ramp = true); 130 Q_INVOKABLE void scratchTick(int deck, int interval); 131 Q_INVOKABLE void scratchDisable(int deck, bool ramp = true); 132 Q_INVOKABLE bool isScratching(int deck); 133 Q_INVOKABLE void softTakeover(const QString& group, const QString& name, bool set); 134 Q_INVOKABLE void softTakeoverIgnoreNextValue(const QString& group, const QString& name); 135 Q_INVOKABLE void brake(int deck, bool activate, double factor=1.0, double rate=1.0); 136 Q_INVOKABLE void spinback(int deck, bool activate, double factor=1.8, double rate=-10.0); 137 Q_INVOKABLE void softStart(int deck, bool activate, double factor=1.0); 138 139 // Handler for timers that scripts set. 140 virtual void timerEvent(QTimerEvent *event); 141 142 public slots: 143 // Evaluate a script file 144 bool evaluate(const QString& filepath); 145 146 // Execute a basic MIDI message callback. 147 bool execute(const QScriptValue& function, 148 unsigned char channel, 149 unsigned char control, 150 unsigned char value, 151 unsigned char status, 152 const QString& group, 153 mixxx::Duration timestamp); 154 155 // Execute a byte array callback. 156 bool execute(const QScriptValue& function, const QByteArray& data, mixxx::Duration timestamp); 157 158 // Evaluates all provided script files and returns true if no script errors 159 // occurred while evaluating them. 160 bool loadScriptFiles(const QList<ControllerPreset::ScriptFileInfo>& scripts); 161 void initializeScripts(const QList<ControllerPreset::ScriptFileInfo>& scripts); 162 void gracefulShutdown(); 163 void scriptHasChanged(const QString&); 164 165 signals: 166 void resetController(); 167 168 private slots: 169 void errorDialogButton(const QString& key, QMessageBox::StandardButton button); 170 171 private: 172 bool syntaxIsValid(const QString& scriptCode, const QString& filename = QString()); 173 bool evaluate(const QFileInfo& scriptFile); 174 bool internalExecute(const QScriptValue& thisObject, const QString& scriptCode); 175 bool internalExecute(const QScriptValue& thisObject, 176 QScriptValue functionObject, 177 const QScriptValueList& arguments); 178 void initializeScriptEngine(); 179 void uninitializeScriptEngine(); 180 181 void scriptErrorDialog(const QString& detailedError, const QString& key, bool bFatal = false); 182 void generateScriptFunctions(const QString& code); 183 // Stops and removes all timers (for shutdown). 184 void stopAllTimers(); 185 186 void callFunctionOnObjects(const QList<QString>&, 187 const QString&, 188 const QScriptValueList& args = QScriptValueList()); 189 bool checkException(bool bFatal = false); 190 QScriptEngine *m_pEngine; 191 192 ControlObjectScript* getControlObjectScript(const QString& group, const QString& name); 193 194 // Scratching functions & variables 195 void scratchProcess(int timerId); 196 197 bool isDeckPlaying(const QString& group); 198 double getDeckRate(const QString& group); 199 200 Controller* m_pController; 201 const UserSettingsPointer m_pConfig; 202 bool m_bPopups; 203 QList<QString> m_scriptFunctionPrefixes; 204 QMap<QString, QStringList> m_scriptErrors; 205 QHash<ConfigKey, ControlObjectScript*> m_controlCache; 206 struct TimerInfo { 207 QScriptValue callback; 208 QScriptValue context; 209 bool oneShot; 210 }; 211 QHash<int, TimerInfo> m_timers; 212 SoftTakeoverCtrl m_st; 213 ByteArrayClass* m_pBaClass; 214 // 256 (default) available virtual decks is enough I would think. 215 // If more are needed at run-time, these will move to the heap automatically 216 QVarLengthArray<int> m_intervalAccumulator; 217 QVarLengthArray<mixxx::Duration> m_lastMovement; 218 QVarLengthArray<double> m_dx, m_rampTo, m_rampFactor; 219 QVarLengthArray<bool> m_ramp, m_brakeActive, m_softStartActive; 220 QVarLengthArray<AlphaBetaFilter*> m_scratchFilters; 221 QHash<int, int> m_scratchTimers; 222 QHash<QString, QScriptValue> m_scriptWrappedFunctionCache; 223 // Filesystem watcher for script auto-reload 224 QFileSystemWatcher m_scriptWatcher; 225 QList<ControllerPreset::ScriptFileInfo> m_lastScriptFiles; 226 227 friend class ControllerEngineTest; 228 }; 229 230 #endif 231