Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/midi/midiscriptengine.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002                           midiscriptengine.cpp  -  description
00003                           -------------------
00004     begin                : Fri Dec 12 2008
00005     copyright            : (C) 2008-2010 by Sean M. Pappalardo
00006                                        "Holy crap, I wrote new code!"
00007     email                : spappalardo@mixxx.org
00008  ***************************************************************************/
00009 
00010 /***************************************************************************
00011  *                                                                         *
00012  *   This program is free software; you can redistribute it and/or modify  *
00013  *   it under the terms of the GNU General Public License as published by  *
00014  *   the Free Software Foundation; either version 2 of the License, or     *
00015  *   (at your option) any later version.                                   *
00016  *                                                                         *
00017  ***************************************************************************/
00018 
00019 #include "controlobject.h"
00020 #include "controlobjectthreadmain.h"
00021 #include "mididevice.h"
00022 #include "midiscriptengine.h"
00023 #include "errordialoghandler.h"
00024 
00025 // #include <QScriptSyntaxCheckResult>
00026 
00027 #ifdef _MSC_VER
00028     #include <float.h>  // for _isnan() on VC++
00029     #define isnan(x) _isnan(x)  // VC++ uses _isnan() instead of isnan()
00030 #else
00031     #include <math.h>  // for isnan() everywhere else
00032 #endif
00033 
00034 
00035 MidiScriptEngine::MidiScriptEngine(MidiDevice* midiDevice) :
00036     m_pMidiDevice(midiDevice),
00037     m_midiDebug(false),
00038     m_pEngine(NULL),
00039     m_midiPopups(false) {
00040 
00041     // Handle error dialog buttons
00042     qRegisterMetaType<QMessageBox::StandardButton>("QMessageBox::StandardButton");
00043 
00044     // Pre-allocate arrays for average number of virtual decks
00045     int decks = 16;
00046     m_intervalAccumulator.resize(decks);
00047     m_dx.resize(decks);
00048     m_rampTo.resize(decks);
00049     m_ramp.resize(decks);
00050     m_pitchFilter.resize(decks);
00051 
00052     // Initialize arrays used for testing and pointers
00053     for (int i=0; i < decks; i++) {
00054         m_dx[i] = 0;
00055         m_pitchFilter[i] = new PitchFilter(); // allocate RAM at startup
00056         m_ramp[i] = false;
00057     }
00058 }
00059 
00060 MidiScriptEngine::~MidiScriptEngine() {
00061     // Clean up
00062     int decks = 16; // Must match value above
00063     for (int i=0; i < decks; i++) {
00064         delete m_pitchFilter[i];
00065         m_pitchFilter[i] = NULL;
00066     }
00067 
00068     // Delete the script engine, first clearing the pointer so that
00069     // other threads will not get the dead pointer after we delete it.
00070     if(m_pEngine != NULL) {
00071         QScriptEngine *engine = m_pEngine;
00072         m_pEngine = NULL;
00073         engine->deleteLater();
00074     }
00075 
00076 }
00077 
00078 /* -------- ------------------------------------------------------
00079 Purpose: Shuts down MIDI scripts in an orderly fashion
00080             (stops timers then executes shutdown functions)
00081 Input:   -
00082 Output:  -
00083 -------- ------------------------------------------------------ */
00084 void MidiScriptEngine::gracefulShutdown(QList<QString> scriptFunctionPrefixes) {
00085     qDebug() << "MidiScriptEngine shutting down...";
00086 
00087     m_scriptEngineLock.lock();
00088     // Clear the m_connectedControls hash so we stop responding
00089     // to signals.
00090     m_connectedControls.clear();
00091 
00092     // Disconnect the function call signal
00093     if (m_pMidiDevice)
00094         disconnect(m_pMidiDevice, SIGNAL(callMidiScriptFunction(QString, char, char,
00095                                                                 char, MidiStatusByte, QString)),
00096                    this, SLOT(execute(QString, char, char, char, MidiStatusByte, QString)));
00097 
00098     // Stop all timers
00099     stopAllTimers();
00100 
00101     // Call each script's shutdown function if it exists
00102     QListIterator<QString> prefixIt(scriptFunctionPrefixes);
00103     while (prefixIt.hasNext()) {
00104         QString shutName = prefixIt.next();
00105         if (shutName!="") {
00106             shutName.append(".shutdown");
00107             if (m_midiDebug) qDebug() << "MidiScriptEngine: Executing" << shutName;
00108             if (!internalExecute(QScriptValue(), shutName))
00109                 qWarning() << "MidiScriptEngine: No" << shutName << "function in script";
00110         }
00111     }
00112 
00113     // Prevents leaving decks in an unstable state
00114     //  if the controller is shut down while scratching
00115     QHashIterator<int, int> i(m_scratchTimers);
00116     while (i.hasNext()) {
00117         i.next();
00118         qDebug() << "Aborting scratching on deck" << i.value();
00119         // Clear scratch2_enable
00120         QString group = QString("[Channel%1]").arg(i.value());
00121         ControlObjectThread *cot = getControlObjectThread(group, "scratch2_enable");
00122         if(cot != NULL) cot->slotSet(0);
00123     }
00124 
00125     // Free all the control object threads
00126     QList<ConfigKey> keys = m_controlCache.keys();
00127     QList<ConfigKey>::iterator it = keys.begin();
00128     QList<ConfigKey>::iterator end = keys.end();
00129     while(it != end) {
00130         ConfigKey key = *it;
00131         ControlObjectThread *cot = m_controlCache.take(key);
00132         delete cot;
00133         it++;
00134     }
00135 
00136     m_scriptEngineLock.unlock();
00137 
00138     // Stop processing the event loop and terminate the thread.
00139     quit();
00140 }
00141 
00142 bool MidiScriptEngine::isReady() {
00143     m_scriptEngineLock.lock();
00144     bool ret = m_pEngine != NULL;
00145     m_scriptEngineLock.unlock();
00146     return ret;
00147 }
00148 
00149 /*
00150   WARNING: must hold the lock to call this
00151  */
00152 void MidiScriptEngine::initializeScriptEngine() {
00153     // Create the MidiScriptEngine
00154     m_pEngine = new QScriptEngine(this);
00155 
00156     //qDebug() << "MidiScriptEngine::run() m_pEngine->parent() is " << m_pEngine->parent();
00157     //qDebug() << "MidiScriptEngine::run() m_pEngine->thread() is " << m_pEngine->thread();
00158 
00159     // Make this MidiScriptEngine instance available to scripts as
00160     // 'engine'.
00161     QScriptValue engineGlobalObject = m_pEngine->globalObject();
00162     engineGlobalObject.setProperty("engine", m_pEngine->newQObject(this));
00163 
00164     if (m_pMidiDevice) {
00165         qDebug() << "MIDI Device in script engine is:" << m_pMidiDevice->getName();
00166 
00167         // Make the MidiDevice instance available to scripts as 'midi'.
00168         engineGlobalObject.setProperty("midi", m_pEngine->newQObject(m_pMidiDevice));
00169 
00170         // Allow the MidiDevice to signal script function calls
00171         connect(m_pMidiDevice, SIGNAL(callMidiScriptFunction(QString, char, char,
00172                                                              char, MidiStatusByte, QString)),
00173                 this, SLOT(execute(QString, char, char, char, MidiStatusByte, QString)));
00174     }
00175 }
00176 
00177 /* -------- ------------------------------------------------------
00178    Purpose: Load all script files given in the shared list
00179    Input:   -
00180    Output:  -
00181    -------- ------------------------------------------------------ */
00182 void MidiScriptEngine::loadScriptFiles(QList<QString> scriptFileNames) {
00183 
00184     // Set the Midi Debug flag
00185     if (m_pMidiDevice)
00186         m_midiDebug = m_pMidiDevice->midiDebugging();
00187 
00188     qDebug() << "MidiScriptEngine: Loading & evaluating all MIDI script code";
00189 
00190     // scriptPaths holds the paths to search in when we're looking for scripts
00191     QList<QString> scriptPaths;
00192     scriptPaths.append(QDir::homePath().append("/").append(SETTINGS_PATH).append("presets/"));
00193 
00194     ConfigObject<ConfigValue> *config = new ConfigObject<ConfigValue>(QDir::homePath().append("/").append(SETTINGS_PATH).append(SETTINGS_FILE));
00195     scriptPaths.append(config->getConfigPath().append("midi/"));
00196     delete config;
00197 
00198     QListIterator<QString> it(scriptFileNames);
00199     m_scriptEngineLock.lock();
00200     while (it.hasNext()) {
00201         QString curScriptFileName = it.next();
00202         safeEvaluate(curScriptFileName, scriptPaths);
00203 
00204         if(m_scriptErrors.contains(curScriptFileName)) {
00205             qDebug() << "Errors occured while loading " << curScriptFileName;
00206         }
00207     }
00208 
00209     m_scriptEngineLock.unlock();
00210     emit(initialized());
00211 }
00212 
00213 /* -------- ------------------------------------------------------
00214    Purpose: Run the initialization function for each loaded script
00215                 if it exists
00216    Input:   -
00217    Output:  -
00218    -------- ------------------------------------------------------ */
00219 void MidiScriptEngine::initializeScripts(QList<QString> scriptFunctionPrefixes) {
00220     m_scriptEngineLock.lock();
00221 
00222     QListIterator<QString> prefixIt(scriptFunctionPrefixes);
00223     while (prefixIt.hasNext()) {
00224         QString initName = prefixIt.next();
00225             if (initName!="") {
00226                 initName.append(".init");
00227             if (m_midiDebug) qDebug() << "MidiScriptEngine: Executing" << initName;
00228             if (!safeExecute(initName, m_pMidiDevice->getName()))
00229                 qWarning() << "MidiScriptEngine: No" << initName << "function in script";
00230         }
00231     }
00232     m_scriptEngineLock.unlock();
00233     emit(initialized());
00234 }
00235 
00236 /* -------- ------------------------------------------------------
00237    Purpose: Create the MidiScriptEngine object (so it is owned in this
00238    thread, and start the Qt event loop for this thread via exec().
00239    Input: -
00240    Output: -
00241    -------- ------------------------------------------------------ */
00242 void MidiScriptEngine::run() {
00243     unsigned static id = 0; //the id of this thread, for debugging purposes //XXX copypasta (should factor this out somehow), -kousu 2/2009
00244     QThread::currentThread()->setObjectName(QString("MidiScriptEngine %1").arg(++id));
00245 
00246     // Prevent the script engine from strangling other parts of Mixxx
00247     //  incase of a misbehaving script
00248     //  - Should we perhaps not do this when running with --midiDebug so it's more
00249     //      obvious if a script is taking too much CPU time? - Sean 4/19/10
00250     QThread::currentThread()->setPriority(QThread::LowPriority);
00251 
00252     m_scriptEngineLock.lock();
00253     initializeScriptEngine();
00254     m_scriptEngineLock.unlock();
00255     emit(initialized());
00256 
00257     // Run the Qt event loop indefinitely
00258     exec();
00259 }
00260 
00261 /* -------- ------------------------------------------------------
00262    Purpose: Validate script syntax, then evaluate() it so the
00263             functions are registered & available for use.
00264    Input:   -
00265    Output:  -
00266    -------- ------------------------------------------------------ */
00267 bool MidiScriptEngine::evaluate(QString filepath) {
00268     m_scriptEngineLock.lock();
00269     QList<QString> dummy;
00270     bool ret = safeEvaluate(filepath, dummy);
00271     m_scriptEngineLock.unlock();
00272     return ret;
00273 }
00274 
00275 /* -------- ------------------------------------------------------
00276    Purpose: Evaluate & call a script function
00277    Input:   Function name
00278    Output:  false if an invalid function or an exception
00279    -------- ------------------------------------------------------ */
00280 bool MidiScriptEngine::execute(QString function) {
00281     m_scriptEngineLock.lock();
00282     bool ret = safeExecute(function);
00283     if (!ret) qWarning() << "MidiScriptEngine: Invalid script function" << function;
00284     m_scriptEngineLock.unlock();
00285     return ret;
00286 }
00287 
00288 /* -------- ------------------------------------------------------
00289    Purpose: Evaluate & call a script function
00290    Input:   Function name, data string (e.g. device ID)
00291    Output:  false if an invalid function or an exception
00292    -------- ------------------------------------------------------ */
00293 bool MidiScriptEngine::execute(QString function, QString data) {
00294     m_scriptEngineLock.lock();
00295     bool ret = safeExecute(function, data);
00296     if (!ret) qWarning() << "MidiScriptEngine: Invalid script function" << function;
00297     m_scriptEngineLock.unlock();
00298     return ret;
00299 }
00300 
00301 /* -------- ------------------------------------------------------
00302    Purpose: Evaluate & call a script function
00303    Input:   Function name, pointer to data buffer, length of buffer
00304    Output:  false if an invalid function or an exception
00305    -------- ------------------------------------------------------ */
00306 bool MidiScriptEngine::execute(QString function, const unsigned char data[],
00307                                unsigned int length) {
00308     m_scriptEngineLock.lock();
00309     bool ret = safeExecute(function, data, length);
00310     m_scriptEngineLock.unlock();
00311     return ret;
00312 }
00313 
00314 /* -------- ------------------------------------------------------
00315    Purpose: Evaluate & call a script function
00316    Input:   Function name, channel #, control #, value, status
00317                 MixxxControl group
00318    Output:  false if an invalid function or an exception
00319    -------- ------------------------------------------------------ */
00320 bool MidiScriptEngine::execute(QString function, char channel,
00321                                char control, char value,
00322                                MidiStatusByte status,
00323                                QString group) {
00324     m_scriptEngineLock.lock();
00325     bool ret = safeExecute(function, channel, control, value, status, group);
00326     if (!ret) qWarning() << "MidiScriptEngine: Invalid script function" << function;
00327     m_scriptEngineLock.unlock();
00328     return ret;
00329 }
00330 
00331 /* -------- ------------------------------------------------------
00332    Purpose: Evaluate & call a script function
00333    Input:   Function name
00334    Output:  false if an invalid function or an exception
00335    -------- ------------------------------------------------------ */
00336 bool MidiScriptEngine::safeExecute(QString function) {
00337     //qDebug() << QString("MidiScriptEngine: Exec1 Thread ID=%1").arg(QThread::currentThreadId(),0,16);
00338 
00339     if (m_pEngine == NULL)
00340         return false;
00341 
00342     QScriptValue scriptFunction = m_pEngine->evaluate(function);
00343 
00344     if (checkException())
00345         return false;
00346 
00347     if (!scriptFunction.isFunction())
00348         return false;
00349 
00350     scriptFunction.call(QScriptValue());
00351     if (checkException())
00352         return false;
00353 
00354     return true;
00355 }
00356 
00357 
00358 /* -------- ------------------------------------------------------
00359 Purpose: Evaluate & run script code
00360 Input:   'this' object if applicable, Code string
00361 Output:  false if an exception
00362 -------- ------------------------------------------------------ */
00363 bool MidiScriptEngine::internalExecute(QScriptValue thisObject,
00364                                        QString scriptCode) {
00365     // A special version of safeExecute since we're evaluating strings, not actual functions
00366     //  (execute() would print an error that it's not a function every time a timer fires.)
00367     if(m_pEngine == NULL)
00368         return false;
00369 
00370     // Check syntax
00371     QScriptSyntaxCheckResult result = m_pEngine->checkSyntax(scriptCode);
00372     QString error="";
00373     switch (result.state()) {
00374         case (QScriptSyntaxCheckResult::Valid): break;
00375         case (QScriptSyntaxCheckResult::Intermediate):
00376             error = "Incomplete code";
00377             break;
00378         case (QScriptSyntaxCheckResult::Error):
00379             error = "Syntax error";
00380             break;
00381     }
00382     if (error != "") {
00383         error = QString("%1: %2 at line %3, column %4 of script code:\n%5\n")
00384         .arg(error)
00385         .arg(result.errorMessage())
00386         .arg(result.errorLineNumber())
00387         .arg(result.errorColumnNumber())
00388         .arg(scriptCode);
00389 
00390         if (m_midiDebug) qCritical() << "MidiScriptEngine:" << error;
00391         else scriptErrorDialog(error);
00392         return false;
00393     }
00394 
00395     QScriptValue scriptFunction = m_pEngine->evaluate(scriptCode);
00396 
00397     if (checkException())
00398         return false;
00399 
00400     // If it's not a function, we're done.
00401     if (!scriptFunction.isFunction())
00402         return true;
00403 
00404     // If it does happen to be a function, call it.
00405     scriptFunction.call(thisObject);
00406     if (checkException())
00407         return false;
00408 
00409     return true;
00410 }
00411 
00412 /* -------- ------------------------------------------------------
00413    Purpose: Evaluate & call a script function
00414    Input:   Function name, data string (e.g. device ID)
00415    Output:  false if an invalid function or an exception
00416    -------- ------------------------------------------------------ */
00417 bool MidiScriptEngine::safeExecute(QString function, QString data) {
00418     //qDebug() << QString("MidiScriptEngine: Exec2 Thread ID=%1").arg(QThread::currentThreadId(),0,16);
00419 
00420     if(m_pEngine == NULL) {
00421         return false;
00422     }
00423 
00424     QScriptValue scriptFunction = m_pEngine->evaluate(function);
00425 
00426     if (checkException())
00427         return false;
00428     if (!scriptFunction.isFunction())
00429         return false;
00430 
00431     QScriptValueList args;
00432     args << QScriptValue(data);
00433 
00434     scriptFunction.call(QScriptValue(), args);
00435     if (checkException())
00436         return false;
00437     return true;
00438 }
00439 
00440 /* -------- ------------------------------------------------------
00441    Purpose: Evaluate & call a script function
00442    Input:   Function name, ponter to data buffer, length of buffer
00443    Output:  false if an invalid function or an exception
00444    -------- ------------------------------------------------------ */
00445 bool MidiScriptEngine::safeExecute(QString function, const unsigned char data[],
00446                                    unsigned int length) {
00447 
00448     if(m_pEngine == NULL) {
00449         return false;
00450     }
00451 
00452     if (!m_pEngine->canEvaluate(function)) {
00453         qCritical() << "MidiScriptEngine: ?Syntax error in function " << function;
00454         return false;
00455     }
00456 
00457     QScriptValue scriptFunction = m_pEngine->evaluate(function);
00458 
00459     if (checkException())
00460         return false;
00461     if (!scriptFunction.isFunction())
00462         return false;
00463 
00464     // These funky conversions are required in order to
00465     //  get the byte array into ECMAScript complete and unharmed.
00466     //  Don't change this or I will hurt you -- Sean
00467     QVector<QChar> temp(length);
00468     for (unsigned int i=0; i < length; i++) {
00469         temp[i]=data[i];
00470     }
00471     QString buffer = QString(temp.constData(),length);
00472     QScriptValueList args;
00473     args << QScriptValue(buffer);
00474     args << QScriptValue(length);
00475 
00476     scriptFunction.call(QScriptValue(), args);
00477     if (checkException())
00478         return false;
00479     return true;
00480 }
00481 
00482 /* -------- ------------------------------------------------------
00483    Purpose: Evaluate & call a script function
00484    Input:   Function name, channel #, control #, value, status
00485    Output:  false if an invalid function or an exception
00486    -------- ------------------------------------------------------ */
00487 bool MidiScriptEngine::safeExecute(QString function, char channel,
00488                                    char control, char value,
00489                                    MidiStatusByte status,
00490                                    QString group) {
00491     //qDebug() << QString("MidiScriptEngine: Exec2 Thread ID=%1").arg(QThread::currentThreadId(),0,16);
00492 
00493     if(m_pEngine == NULL) {
00494         return false;
00495     }
00496 
00497     QScriptValue scriptFunction = m_pEngine->evaluate(function);
00498 
00499     if (checkException())
00500         return false;
00501     if (!scriptFunction.isFunction())
00502         return false;
00503 
00504     QScriptValueList args;
00505     args << QScriptValue(channel);
00506     args << QScriptValue(control);
00507     args << QScriptValue(value);
00508     args << QScriptValue(status);
00509     args << QScriptValue(group);
00510 
00511     scriptFunction.call(QScriptValue(), args);
00512     if (checkException())
00513         return false;
00514     return true;
00515 }
00516 
00517 /* -------- ------------------------------------------------------
00518    Purpose: Evaluate & call a script function
00519    Input:   This Object (context), Function name
00520    Output:  false if an invalid function or an exception
00521    Notes:   used for closure calls using native functions (ie: timers)
00522    -------- ------------------------------------------------------ */
00523 bool MidiScriptEngine::safeExecute(QScriptValue thisObject,
00524                                    QScriptValue functionObject) {
00525     if (m_pEngine == NULL) {
00526         return false;
00527     }
00528 
00529     QScriptValueList args;
00530 
00531     functionObject.call(thisObject, args);
00532     if (checkException()) {
00533         return false;
00534     }
00535     return true;
00536 }
00537 
00538 /* -------- ------------------------------------------------------
00539    Purpose: Check to see if a script threw an exception
00540    Input:   QScriptValue returned from call(scriptFunctionName)
00541    Output:  true if there was an exception
00542    -------- ------------------------------------------------------ */
00543 bool MidiScriptEngine::checkException() {
00544     if(m_pEngine == NULL) {
00545         return false;
00546     }
00547 
00548     if (m_pEngine->hasUncaughtException()) {
00549         QScriptValue exception = m_pEngine->uncaughtException();
00550         QString errorMessage = exception.toString();
00551         int line = m_pEngine->uncaughtExceptionLineNumber();
00552         QStringList backtrace = m_pEngine->uncaughtExceptionBacktrace();
00553         QString filename = exception.property("fileName").toString();
00554 
00555         QStringList error;
00556         error << (filename.isEmpty() ? "" : filename) << errorMessage << QString(line);
00557         m_scriptErrors.insert((filename.isEmpty() ? "passed code" : filename), error);
00558 
00559         QString errorText = QString(tr("Uncaught exception at line %1 in file %2: %3"))
00560                             .arg(line)
00561                             .arg((filename.isEmpty() ? "" : filename))
00562                             .arg(errorMessage);
00563 
00564         if (filename.isEmpty())
00565             errorText = QString(tr("Uncaught exception at line %1 in passed code: %2"))
00566                         .arg(line)
00567                         .arg(errorMessage);
00568 
00569         if (m_midiDebug)
00570             qCritical() << "MidiScriptEngine:" << errorText
00571                         << "\nBacktrace:\n"
00572                         << backtrace;
00573         else scriptErrorDialog(errorText);
00574         return true;
00575     }
00576     return false;
00577 }
00578 
00579 /* -------- ------------------------------------------------------
00580 Purpose: Common error dialog creation code for run-time exceptions
00581             Allows users to ignore the error or reload the mappings
00582 Input:   Detailed error string
00583 Output:  -
00584 -------- ------------------------------------------------------ */
00585 void MidiScriptEngine::scriptErrorDialog(QString detailedError) {
00586     qWarning() << "MidiScriptEngine:" << detailedError;
00587     ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
00588     props->setType(DLG_WARNING);
00589     props->setTitle(tr("MIDI script error"));
00590     props->setText(tr("A MIDI control you just used is not working properly."));
00591     props->setInfoText(tr("<html>(The MIDI script code needs to be fixed.)"
00592         "<br>For now, you can:<ul><li>Ignore this error for this session but you may experience erratic behavior</li>"
00593         "<li>Try to recover by resetting your controller</li></ul></html>"));
00594     props->setDetails(detailedError);
00595     props->setKey(detailedError);   // To prevent multiple windows for the same error
00596 
00597     // Allow user to suppress further notifications about this particular error
00598     props->addButton(QMessageBox::Ignore);
00599 
00600     props->addButton(QMessageBox::Retry);
00601     props->addButton(QMessageBox::Close);
00602     props->setDefaultButton(QMessageBox::Close);
00603     props->setEscapeButton(QMessageBox::Close);
00604     props->setModal(false);
00605 
00606     if (ErrorDialogHandler::instance()->requestErrorDialog(props)) {
00607         // Enable custom handling of the dialog buttons
00608         connect(ErrorDialogHandler::instance(), SIGNAL(stdButtonClicked(QString, QMessageBox::StandardButton)),
00609                 this, SLOT(errorDialogButton(QString, QMessageBox::StandardButton)));
00610     }
00611 }
00612 
00613 /* -------- ------------------------------------------------------
00614 Purpose: Slot to handle custom button clicks in error dialogs
00615 Input:   Key of dialog, StandardButton that was clicked
00616 Output:  -
00617 -------- ------------------------------------------------------ */
00618 void MidiScriptEngine::errorDialogButton(QString key, QMessageBox::StandardButton button) {
00619 
00620     // Something was clicked, so disable this signal now
00621     disconnect(ErrorDialogHandler::instance(), SIGNAL(stdButtonClicked(QString, QMessageBox::StandardButton)),
00622         this, SLOT(errorDialogButton(QString, QMessageBox::StandardButton)));
00623 
00624     if (button == QMessageBox::Retry) emit(resetController());
00625 }
00626 
00627 /* -------- ------------------------------------------------------
00628    Purpose: Returns a list of functions available in the QtScript
00629             code
00630    Input:   -
00631    Output:  functionList QStringList
00632    -------- ------------------------------------------------------ */
00633 QStringList MidiScriptEngine::getScriptFunctions() {
00634     m_scriptEngineLock.lock();
00635     QStringList ret = m_scriptFunctions;
00636     m_scriptEngineLock.unlock();
00637     return ret;
00638 }
00639 
00640 void MidiScriptEngine::generateScriptFunctions(QString scriptCode) {
00641 
00642 //     QStringList functionList;
00643     QStringList codeLines = scriptCode.split("\n");
00644 
00645 //     qDebug() << "MidiScriptEngine: m_scriptCode=" << m_scriptCode;
00646 
00647     if (m_midiDebug)
00648         qDebug() << "MidiScriptEngine:" << codeLines.count() << "lines of code being searched for functions";
00649 
00650     // grep 'function' midi/midi-mappings-scripts.js|grep -i '(msg)'|sed -e 's/function \(.*\)(msg).*/\1/i' -e 's/[= ]//g'
00651     QRegExp rx("*.*function*(*)*");    // Find all lines with function names in them
00652     rx.setPatternSyntax(QRegExp::Wildcard);
00653 
00654     int position = codeLines.indexOf(rx);
00655 
00656     while (position != -1) {    // While there are more matches
00657 
00658         QString line = codeLines.takeAt(position);    // Pull & remove the current match from the list.
00659 
00660         if (line.indexOf('#') != 0 && line.indexOf("//") != 0) {    // ignore commented out lines
00661             QStringList field = line.split(" ");
00662             if (m_midiDebug) qDebug() << "MidiScriptEngine: Found function:" << field[0]
00663                                       << "at line" << position;
00664             m_scriptFunctions.append(field[0]);
00665         }
00666         position = codeLines.indexOf(rx);
00667     }
00668 
00669 }
00670 
00671 ControlObjectThread* MidiScriptEngine::getControlObjectThread(QString group, QString name) {
00672 
00673     ConfigKey key = ConfigKey(group, name);
00674 
00675     ControlObjectThread *cot = NULL;
00676     if(!m_controlCache.contains(key)) {
00677         ControlObject *co = ControlObject::getControl(key);
00678         if(co != NULL) {
00679             cot = new ControlObjectThreadMain(co);
00680             m_controlCache.insert(key, cot);
00681         }
00682     } else {
00683         cot = m_controlCache.value(key);
00684     }
00685 
00686     return cot;
00687 
00688 }
00689 
00690 /* -------- ------------------------------------------------------
00691    Purpose: Returns the current value of a Mixxx control (for scripts)
00692    Input:   Control group (e.g. [Channel1]), Key name (e.g. [filterHigh])
00693    Output:  The value
00694    -------- ------------------------------------------------------ */
00695 double MidiScriptEngine::getValue(QString group, QString name) {
00696 
00697 
00698     // When this function runs, assert that somebody is holding the script
00699     // engine lock.
00700     bool lock = m_scriptEngineLock.tryLock();
00701     Q_ASSERT(!lock);
00702     if(lock) {
00703         m_scriptEngineLock.unlock();
00704     }
00705 
00706     //qDebug() << QString("----------------------------------MidiScriptEngine: GetValue Thread ID=%1").arg(QThread::currentThreadId(),0,16);
00707 
00708     ControlObjectThread *cot = getControlObjectThread(group, name);
00709     if (cot == NULL) {
00710         qWarning() << "MidiScriptEngine: Unknown control" << group << name;
00711         return 0.0;
00712     }
00713 
00714     return cot->get();
00715 }
00716 
00717 /* -------- ------------------------------------------------------
00718    Purpose: Sets new value of a Mixxx control (for scripts)
00719    Input:   Control group, Key name, new value
00720    Output:  -
00721    -------- ------------------------------------------------------ */
00722 void MidiScriptEngine::setValue(QString group, QString name, double newValue) {
00723 
00724     // When this function runs, assert that somebody is holding the script
00725     // engine lock.
00726     bool lock = m_scriptEngineLock.tryLock();
00727     Q_ASSERT(!lock);
00728     if(lock) {
00729         m_scriptEngineLock.unlock();
00730     }
00731 
00732     if(isnan(newValue)) {
00733         qWarning() << "MidiScriptEngine: script setting [" << group << "," << name
00734                  << "] to NotANumber, ignoring.";
00735         return;
00736     }
00737 
00738     //qDebug() << QString("----------------------------------MidiScriptEngine: SetValue Thread ID=%1").arg(QThread::currentThreadId(),0,16);
00739 
00740     ControlObjectThread *cot = getControlObjectThread(group, name);
00741 
00742     if(cot != NULL && !m_st.ignore(group,name,newValue)) {
00743         cot->slotSet(newValue);
00744         // We call emitValueChanged so that script functions connected to this
00745         // control will get updates.
00746         cot->emitValueChanged();
00747     }
00748 
00749 }
00750 
00751 /* -------- ------------------------------------------------------
00752    Purpose: qDebugs script output so it ends up in mixxx.log
00753    Input:   String to log
00754    Output:  -
00755    -------- ------------------------------------------------------ */
00756 void MidiScriptEngine::log(QString message) {
00757 
00758     qDebug()<<message;
00759 }
00760 
00761 /* -------- ------------------------------------------------------
00762    Purpose: Calls script function(s) linked to a particular ControlObject
00763             so controller outputs update
00764    Input:   ControlObject Group and Name strings
00765    Output:  -
00766    -------- ------------------------------------------------------ */
00767 void MidiScriptEngine::trigger(QString group, QString name) {
00768     // When this function runs, assert that somebody is holding the script
00769     // engine lock.
00770     bool lock = m_scriptEngineLock.tryLock();
00771     Q_ASSERT(!lock);
00772     if(lock) {
00773         m_scriptEngineLock.unlock();
00774     }
00775 
00776     ControlObjectThread *cot = getControlObjectThread(group, name);
00777 
00778     if (cot == NULL) {
00779         return;
00780     }
00781 
00782     // ControlObject doesn't emit ValueChanged when set to the same value,
00783     //  and ControlObjectThread::emitValueChanged also has no effect
00784     //  so we have to call the function(s) manually with the current value
00785     ConfigKey key = ConfigKey(group,name);
00786     if(m_connectedControls.contains(key)) {
00787         QMultiHash<ConfigKey, QString>::iterator i = m_connectedControls.find(key);
00788         while (i != m_connectedControls.end() && i.key() == key) {
00789             QString function = i.value();
00790 
00791             QScriptValue function_value = m_pEngine->evaluate(function);
00792             QScriptValueList args;
00793             double value;
00794 
00795             args << QScriptValue(cot->get());
00796             args << QScriptValue(key.group);
00797             args << QScriptValue(key.item);
00798             QScriptValue result = function_value.call(QScriptValue(), args);
00799             if (result.isError()) {
00800                 qWarning()<< "MidiScriptEngine: Call to" << function
00801                           << "resulted in an error:" << result.toString();
00802             }
00803             ++i;
00804         }
00805     }
00806 }
00807 
00808 /* -------- ------------------------------------------------------
00809    Purpose: (Dis)connects a ControlObject valueChanged() signal to/from a script function
00810    Input:   Control group (e.g. [Channel1]), Key name (e.g. [filterHigh]),
00811                 script function name, true if you want to disconnect
00812    Output:  true if successful
00813    -------- ------------------------------------------------------ */
00814 bool MidiScriptEngine::connectControl(QString group, QString name, QString function, bool disconnect) {
00815     ControlObjectThread* cobj = getControlObjectThread(group, name);
00816     ConfigKey key(group, name);
00817 
00818     if (cobj == NULL) {
00819         qWarning() << "MidiScriptEngine: script connecting [" << group << "," << name
00820                    << "], which is non-existent. ignoring.";
00821         return false;
00822     }
00823 
00824 
00825     // Don't add duplicates
00826     if (!disconnect && m_connectedControls.contains(key, function)) return true;
00827 
00828     // When this function runs, assert that somebody is holding the script
00829     // engine lock.
00830     bool lock = m_scriptEngineLock.tryLock();
00831     Q_ASSERT(!lock);
00832     if(lock) {
00833         m_scriptEngineLock.unlock();
00834     }
00835 
00836     //qDebug() << QString("MidiScriptEngine: Connect Thread ID=%1").arg(QThread::currentThreadId(),0,16);
00837 
00838 
00839     if(m_pEngine == NULL) {
00840         return false;
00841     }
00842 
00843     QScriptValue slot = m_pEngine->evaluate(function);
00844 
00845     if(!checkException() && slot.isFunction()) {
00846         if(disconnect) {
00847 //             qDebug() << "MidiScriptEngine::connectControl disconnected " << group << name << " from " << function;
00848             m_connectedControls.remove(key, function);
00849             // Only disconnect the signal if there are no other instances of this control using it
00850             if (!m_connectedControls.contains(key)) {
00851                 this->disconnect(cobj, SIGNAL(valueChanged(double)),
00852                                  this, SLOT(slotValueChanged(double)));
00853             }
00854         } else {
00855 //             qDebug() << "MidiScriptEngine::connectControl connected " << group << name << " to " << function;
00856             connect(cobj, SIGNAL(valueChanged(double)),
00857                     this, SLOT(slotValueChanged(double)),
00858                     Qt::QueuedConnection);
00859             m_connectedControls.insert(key, function);
00860         }
00861         return true;
00862     }
00863 
00864     return false;
00865 }
00866 
00867 /* -------- ------------------------------------------------------
00868    Purpose: Receives valueChanged() slots from ControlObjects, and
00869    fires off the appropriate script function.
00870    -------- ------------------------------------------------------ */
00871 void MidiScriptEngine::slotValueChanged(double value) {
00872     m_scriptEngineLock.lock();
00873 
00874     ControlObjectThread* sender = dynamic_cast<ControlObjectThread*>(this->sender());
00875     if(sender == NULL) {
00876         qWarning() << "MidiScriptEngine::slotValueChanged() Shouldn't happen -- sender == NULL";
00877         m_scriptEngineLock.unlock();
00878         return;
00879     }
00880     ControlObject* pSenderCO = sender->getControlObject();
00881     if (pSenderCO == NULL) {
00882         return;
00883     }
00884     ConfigKey key = pSenderCO->getKey();
00885 
00886     //qDebug() << QString("MidiScriptEngine: slotValueChanged Thread ID=%1").arg(QThread::currentThreadId(),0,16);
00887 
00888     if(m_connectedControls.contains(key)) {
00889         QMultiHash<ConfigKey, QString>::iterator i = m_connectedControls.find(key);
00890         while (i != m_connectedControls.end() && i.key() == key) {
00891             QString function = i.value();
00892 
00893 //             qDebug() << "MidiScriptEngine::slotValueChanged() received signal from " << key.group << key.item << " ... firing : " << function;
00894 
00895             // Could branch to safeExecute from here, but for now do it this way.
00896             QScriptValue function_value = m_pEngine->evaluate(function);
00897             QScriptValueList args;
00898             args << QScriptValue(value);
00899             args << QScriptValue(key.group); // Added by Math`
00900             args << QScriptValue(key.item);  // Added by Math`
00901             QScriptValue result = function_value.call(QScriptValue(), args);
00902             if (result.isError()) {
00903                 qWarning()<< "MidiScriptEngine: Call to " << function << " resulted in an error:  " << result.toString();
00904             }
00905             ++i;
00906         }
00907     } else {
00908         qWarning() << "MidiScriptEngine::slotValueChanged() Received signal from ControlObject that is not connected to a script function.";
00909     }
00910 
00911     m_scriptEngineLock.unlock();
00912 }
00913 
00914 /* -------- ------------------------------------------------------
00915    Purpose: Evaluate a script file
00916    Input:   Script filename
00917    Output:  false if the script file has errors or doesn't exist
00918    -------- ------------------------------------------------------ */
00919 bool MidiScriptEngine::safeEvaluate(QString scriptName, QList<QString> scriptPaths) {
00920     if(m_pEngine == NULL) {
00921         return false;
00922     }
00923 
00924     QString filename = "";
00925     QFile input;
00926 
00927     if (scriptPaths.length() == 0) {
00928         // If we aren't given any paths to search, assume that scriptName
00929         // contains the full file name
00930         filename = scriptName;
00931         input.setFileName(filename);
00932     } else {
00933         QListIterator<QString> it(scriptPaths);
00934         do {
00935             filename = it.next()+scriptName;
00936             input.setFileName(filename);
00937         } while (it.hasNext() && !input.exists());
00938     }
00939 
00940     qDebug() << "MidiScriptEngine: Loading" << filename;
00941 
00942     // Read in the script file
00943     if (!input.open(QIODevice::ReadOnly)) {
00944         QString errorLog =
00945             QString("MidiScriptEngine: Problem opening the script file: %1, error # %2, %3")
00946                 .arg(filename)
00947                 .arg(input.error())
00948                 .arg(input.errorString());
00949 
00950         // GUI actions do not belong in the MSE. They should be passed to
00951         // the above layers, along with input.errorString(), and that layer
00952         // can take care of notifying the user. The script engine should do
00953         // one thign and one thign alone -- run the scripts.
00954         if (m_midiDebug) {
00955             qCritical() << errorLog;
00956         } else {
00957             qWarning() << errorLog;
00958             if (m_midiPopups) {
00959                 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
00960                 props->setType(DLG_WARNING);
00961                 props->setTitle("MIDI script file problem");
00962                 props->setText(QString("There was a problem opening the MIDI script file %1.").arg(filename));
00963                 props->setInfoText(input.errorString());
00964 
00965                 ErrorDialogHandler::instance()->requestErrorDialog(props);
00966             }
00967         }
00968         return false;
00969     }
00970 
00971     QString scriptCode = "";
00972     scriptCode.append(input.readAll());
00973     scriptCode.append('\n');
00974     input.close();
00975 
00976     // Check syntax
00977     QScriptSyntaxCheckResult result = m_pEngine->checkSyntax(scriptCode);
00978     QString error="";
00979     switch (result.state()) {
00980         case (QScriptSyntaxCheckResult::Valid): break;
00981         case (QScriptSyntaxCheckResult::Intermediate):
00982             error = "Incomplete code";
00983             break;
00984         case (QScriptSyntaxCheckResult::Error):
00985             error = "Syntax error";
00986             break;
00987     }
00988     if (error!="") {
00989         error = QString("%1 at line %2, column %3 in file %4: %5")
00990                         .arg(error)
00991                         .arg(result.errorLineNumber())
00992                         .arg(result.errorColumnNumber())
00993                         .arg(filename)
00994                         .arg(result.errorMessage());
00995 
00996         if (m_midiDebug) qCritical() << "MidiScriptEngine:" << error;
00997         else {
00998             qWarning() << "MidiScriptEngine:" << error;
00999             if (m_midiPopups) {
01000                 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
01001                 props->setType(DLG_WARNING);
01002                 props->setTitle("MIDI script file error");
01003                 props->setText(QString("There was an error in the MIDI script file %1.").arg(filename));
01004                 props->setInfoText("The functionality provided by this script file will be disabled.");
01005                 props->setDetails(error);
01006 
01007                 ErrorDialogHandler::instance()->requestErrorDialog(props);
01008             }
01009         }
01010         return false;
01011     }
01012 
01013     // Evaluate the code
01014     QScriptValue scriptFunction = m_pEngine->evaluate(scriptCode, filename);
01015 
01016     // Record errors
01017     if (checkException())
01018         return false;
01019 
01020     // Add the code we evaluated to our index
01021     generateScriptFunctions(scriptCode);
01022 
01023     return true;
01024 }
01025 
01026 /*
01027  * Check whether a source file that was evaluated()'d has errors.
01028  */
01029 bool MidiScriptEngine::hasErrors(QString filename) {
01030     m_scriptEngineLock.lock();
01031     bool ret = m_scriptErrors.contains(filename);
01032     m_scriptEngineLock.unlock();
01033     return ret;
01034 }
01035 
01036 /*
01037  * Get the errors for a source file that was evaluated()'d
01038  */
01039 const QStringList MidiScriptEngine::getErrors(QString filename) {
01040     QStringList ret;
01041     m_scriptEngineLock.lock();
01042     if(m_scriptErrors.contains(filename))
01043         ret = m_scriptErrors.value(filename);
01044     m_scriptEngineLock.unlock();
01045     return ret;
01046 }
01047 
01048 
01049 /* -------- ------------------------------------------------------
01050    Purpose: Creates & starts a timer that runs some script code
01051                 on timeout
01052    Input:   Number of milliseconds, script function to call,
01053                 whether it should fire just once
01054    Output:  The timer's ID, 0 if starting it failed
01055    -------- ------------------------------------------------------ */
01056 int MidiScriptEngine::beginTimer(int interval, QScriptValue timerCallback,
01057                                  bool oneShot) {
01058     // When this function runs, assert that somebody is holding the script
01059     // engine lock.
01060     bool lock = m_scriptEngineLock.tryLock();
01061     Q_ASSERT(!lock);
01062     if (lock) {
01063         m_scriptEngineLock.unlock();
01064     }
01065 
01066     if (!timerCallback.isFunction() && !timerCallback.isString()) {
01067         qWarning() << "Invalid timer callback provided to beginTimer."
01068                    << "Valid callbacks are strings and functions.";
01069         return 0;
01070     }
01071 
01072     if (interval < 20) {
01073         qWarning() << "Timer request for" << interval
01074                    << "ms is too short. Setting to the minimum of 20ms.";
01075         interval = 20;
01076     }
01077     // This makes use of every QObject's internal timer mechanism. Nice, clean,
01078     // and simple. See http://doc.trolltech.com/4.6/qobject.html#startTimer for
01079     // details
01080     int timerId = startTimer(interval);
01081     TimerInfo info;
01082     info.callback = timerCallback;
01083     QScriptContext *ctxt = m_pEngine->currentContext();
01084     info.context = ctxt ? ctxt->thisObject() : QScriptValue();
01085     info.oneShot = oneShot;
01086     m_timers[timerId] = info;
01087     if (timerId == 0) {
01088         qWarning() << "MIDI Script timer could not be created";
01089     } else if (m_midiDebug) {
01090         if (oneShot)
01091             qDebug() << "Starting one-shot timer:" << timerId;
01092         else
01093             qDebug() << "Starting timer:" << timerId;
01094     }
01095     return timerId;
01096 }
01097 
01098 /* -------- ------------------------------------------------------
01099    Purpose: Stops & removes a timer
01100    Input:   ID of timer to stop
01101    Output:  -
01102    -------- ------------------------------------------------------ */
01103 void MidiScriptEngine::stopTimer(int timerId) {
01104     // When this function runs, assert that somebody is holding the script
01105     // engine lock.
01106     bool lock = m_scriptEngineLock.tryLock();
01107     Q_ASSERT(!lock);
01108     if(lock) m_scriptEngineLock.unlock();
01109 
01110     if (!m_timers.contains(timerId)) {
01111         qWarning() << "Killing timer" << timerId << ": That timer does not exist!";
01112         return;
01113     }
01114     if (m_midiDebug) qDebug() << "Killing timer:" << timerId;
01115 
01116     killTimer(timerId);
01117     m_timers.remove(timerId);
01118 }
01119 
01120 /* -------- ------------------------------------------------------
01121    Purpose: Stops & removes all timers (for shutdown)
01122    Input:   -
01123    Output:  -
01124    -------- ------------------------------------------------------ */
01125 void MidiScriptEngine::stopAllTimers() {
01126     // When this function runs, assert that somebody is holding the script
01127     // engine lock.
01128     bool lock = m_scriptEngineLock.tryLock();
01129     Q_ASSERT(!lock);
01130     if(lock) m_scriptEngineLock.unlock();
01131 
01132     QMutableHashIterator<int, TimerInfo> i(m_timers);
01133     while (i.hasNext()) {
01134         i.next();
01135         stopTimer(i.key());
01136     }
01137 }
01138 
01139 /* -------- ------------------------------------------------------
01140    Purpose: Runs the appropriate script code on timer events
01141    Input:   -
01142    Output:  -
01143    -------- ------------------------------------------------------ */
01144 void MidiScriptEngine::timerEvent(QTimerEvent *event) {
01145     int timerId = event->timerId();
01146 
01147     m_scriptEngineLock.lock();
01148 
01149     // See if this is a scratching timer
01150     if (m_scratchTimers.contains(timerId)) {
01151         m_scriptEngineLock.unlock();
01152         scratchProcess(timerId);
01153         return;
01154     }
01155 
01156     if (!m_timers.contains(timerId)) {
01157         qWarning() << "Timer" << timerId << "fired but there's no function mapped to it!";
01158         m_scriptEngineLock.unlock();
01159         return;
01160     }
01161 
01162     TimerInfo timerTarget = m_timers[timerId];
01163     if (timerTarget.oneShot) stopTimer(timerId);
01164 
01165     if (timerTarget.callback.isString()) {
01166         internalExecute(timerTarget.context, timerTarget.callback.toString());
01167     } else if (timerTarget.callback.isFunction()) {
01168         safeExecute(timerTarget.context, timerTarget.callback);
01169     }
01170     m_scriptEngineLock.unlock();
01171 }
01172 
01173 /* -------- ------------------------------------------------------
01174     Purpose: Enables scratching for relative controls
01175     Input:   Virtual deck to scratch,
01176              Number of intervals per revolution of the controller wheel,
01177              RPM for the track at normal speed (usually 33+1/3),
01178              (optional) alpha value for the filter,
01179              (optional) beta value for the filter
01180     Output:  -
01181     -------- ------------------------------------------------------ */
01182 void MidiScriptEngine::scratchEnable(int deck, int intervalsPerRev, float rpm, float alpha, float beta) {
01183     // If we're already scratching this deck, override that with this request
01184     if (m_dx[deck]) {
01185 //         qDebug() << "Already scratching deck" << deck << ". Overriding.";
01186         int timerId = m_scratchTimers.key(deck);
01187         killTimer(timerId);
01188         m_scratchTimers.remove(timerId);
01189     }
01190 
01191     // Controller resolution in intervals per second at normal speed (rev/min * ints/rev * mins/sec)
01192     float intervalsPerSecond = (rpm * intervalsPerRev)/60;
01193 
01194     m_dx[deck] = 1/intervalsPerSecond;
01195     m_intervalAccumulator[deck] = 0;
01196     m_ramp[deck] = false;
01197 
01198     QString group = QString("[Channel%1]").arg(deck);
01199 
01200     // Ramp
01201     float initVelocity = 0.0;   // Default to stopped
01202 
01203     // See if the deck is already being scratched
01204     ControlObjectThread *cot = getControlObjectThread(group, "scratch2_enable");
01205     if (cot != NULL && cot->get() == 1) {
01206         // If so, set the filter's initial velocity to the scratch speed
01207         cot = getControlObjectThread(group, "scratch2");
01208         if (cot != NULL) initVelocity=cot->get();
01209     } else {
01210         // See if deck is playing
01211         cot = getControlObjectThread(group, "play");
01212         if (cot != NULL && cot->get() == 1) {
01213             // If so, set the filter's initial velocity to the playback speed
01214             float rate=0;
01215             cot = getControlObjectThread(group, "rate");
01216             if (cot != NULL) rate = cot->get();
01217             cot = getControlObjectThread(group, "rateRange");
01218             if (cot != NULL) rate = rate * cot->get();
01219             // Add 1 since the deck is playing
01220             rate++;
01221             // See if we're in reverse play
01222             cot = getControlObjectThread(group, "reverse");
01223             if (cot != NULL && cot->get() == 1) rate = -rate;
01224 
01225             initVelocity = rate;
01226         }
01227     }
01228 
01229     // Initialize pitch filter (0.001s = 1ms)
01230     //  (We're assuming the OS actually gives us a 1ms timer below)
01231     if (alpha && beta) m_pitchFilter[deck]->init(0.001, initVelocity, alpha, beta);
01232     else m_pitchFilter[deck]->init(0.001, initVelocity); // Use filter's defaults if not specified
01233 
01234     int timerId = startTimer(1);    // 1ms is shortest possible, OS dependent
01235     // Associate this virtual deck with this timer for later processing
01236     m_scratchTimers[timerId] = deck;
01237 
01238     // Set scratch2_enable
01239     cot = getControlObjectThread(group, "scratch2_enable");
01240     if(cot != NULL) cot->slotSet(1);
01241 }
01242 
01243 /* -------- ------------------------------------------------------
01244     Purpose: Accumulates "ticks" of the controller wheel
01245     Input:   Virtual deck to scratch, interval value (usually +1 or -1)
01246     Output:  -
01247     -------- ------------------------------------------------------ */
01248 void MidiScriptEngine::scratchTick(int deck, int interval) {
01249     m_intervalAccumulator[deck] += interval;
01250 }
01251 
01252 /* -------- ------------------------------------------------------
01253     Purpose: Applies the accumulated movement to the track speed
01254     Input:   ID of timer for this deck
01255     Output:  -
01256     -------- ------------------------------------------------------ */
01257 void MidiScriptEngine::scratchProcess(int timerId) {
01258     int deck = m_scratchTimers[timerId];
01259     PitchFilter* filter = m_pitchFilter[deck];
01260     QString group = QString("[Channel%1]").arg(deck);
01261 
01262     if (!filter) {
01263         qWarning() << "Scratch filter pointer is null on deck" << deck;
01264         return;
01265     }
01266 
01267     // Give the filter a data point:
01268 
01269     // If we're ramping to end scratching, feed fixed data
01270     if (m_ramp[deck]) filter->observation(m_rampTo[deck]*0.001);
01271     //  This will (and should) be 0 if no net ticks have been accumulated (i.e. the wheel is stopped)
01272     else filter->observation(m_dx[deck] * m_intervalAccumulator[deck]);
01273 
01274     // Actually do the scratching
01275     ControlObjectThread *cot = getControlObjectThread(group, "scratch2");
01276     if(cot != NULL) cot->slotSet(filter->currentPitch());
01277 
01278     // Reset accumulator
01279     m_intervalAccumulator[deck] = 0;
01280 
01281     // If we're ramping and the current pitch is really close to the rampTo value,
01282     //  end scratching
01283 //     if (m_ramp[deck]) qDebug() << "Ramping to" << m_rampTo[deck] << " Currently at:" << filter->currentPitch();
01284     if (m_ramp[deck] && fabs(m_rampTo[deck]-filter->currentPitch()) <= 0.00001) {
01285 
01286         m_ramp[deck] = false;   // Not ramping no mo'
01287 
01288         // Clear scratch2_enable
01289         cot = getControlObjectThread(group, "scratch2_enable");
01290         if(cot != NULL) cot->slotSet(0);
01291 
01292         // Remove timer
01293         killTimer(timerId);
01294         m_scratchTimers.remove(timerId);
01295 
01296         m_dx[deck] = 0;
01297     }
01298 }
01299 
01300 /* -------- ------------------------------------------------------
01301     Purpose: Stops scratching the specified virtual deck
01302     Input:   Virtual deck to stop scratching
01303     Output:  -
01304     -------- ------------------------------------------------------ */
01305 void MidiScriptEngine::scratchDisable(int deck) {
01306     QString group = QString("[Channel%1]").arg(deck);
01307 
01308     // See if deck is playing
01309     ControlObjectThread *cot = getControlObjectThread(group, "play");
01310     if (cot != NULL && cot->get() == 1) {
01311         // If so, set the target velocity to the playback speed
01312         float rate = 0;
01313         // Get the pitch slider value
01314         cot = getControlObjectThread(group, "rate");
01315         if (cot != NULL) rate = cot->get();
01316         // Multiply by the pitch range
01317         cot = getControlObjectThread(group, "rateRange");
01318         if (cot != NULL) rate = rate * cot->get();
01319         // Add 1 since the deck is playing
01320         rate++;
01321         // See if we're in reverse play
01322         cot = getControlObjectThread(group, "reverse");
01323         if (cot != NULL && cot->get() == 1) rate = -rate;
01324 
01325         m_rampTo[deck] = rate;
01326     } else {
01327         m_rampTo[deck] = 0.0;
01328     }
01329 
01330     m_ramp[deck] = true;    // Activate the ramping in scratchProcess()
01331 }
01332 
01333 /*  -------- ------------------------------------------------------
01334     Purpose: [En/dis]ables soft-takeover status for a particular MixxxControl
01335     Input:   MixxxControl group and key values,
01336                 whether to set the soft-takeover status or not
01337     Output:  -
01338     -------- ------------------------------------------------------ */
01339 void MidiScriptEngine::softTakeover(QString group, QString name, bool set) {
01340     MixxxControl mc = MixxxControl(group, name);
01341     if (set) {
01342         m_st.enable(mc);
01343     } else {
01344         m_st.disable(mc);
01345     }
01346 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines