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