Mixxx

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

Go to the documentation of this file.
00001 /***************************************************************************
00002                              midimapping.cpp - "Wow, I wrote more new code!"
00003                            MIDI Mapping Class
00004                            -------------------
00005     begin                : Sat Jan 17 2009
00006     copyright            : (C) 2009 Sean M. Pappalardo
00007                            (C) 2009 Albert Santoni
00008     email                : pegasus@c64.org
00009 
00010 ***************************************************************************/
00011 
00012 /***************************************************************************
00013 *                                                                         *
00014 *   This program is free software; you can redistribute it and/or modify  *
00015 *   it under the terms of the GNU General Public License as published by  *
00016 *   the Free Software Foundation; either version 2 of the License, or     *
00017 *   (at your option) any later version.                                   *
00018 *                                                                         *
00019 ***************************************************************************/
00020 
00021 #include <qapplication.h>
00022 #include "defs_version.h"
00023 #include "widget/wwidget.h"    // FIXME: This should be xmlparse.h
00024 #include "mixxxcontrol.h"
00025 #include "midimessage.h"
00026 #include "defs.h"
00027 #include "midiinputmappingtablemodel.h"
00028 #include "midioutputmappingtablemodel.h"
00029 #include "midimapping.h"
00030 #include "mididevicedummy.h"
00031 #include "midiledhandler.h"
00032 #include "configobject.h"
00033 #include "errordialoghandler.h"
00034 
00035 #define REQUIRED_SCRIPT_FILE "midi-mappings-scripts.js"
00036 #define XML_SCHEMA_VERSION "1"
00037 #define DEFAULT_DEVICE_PRESET BINDINGS_PATH.append(m_deviceName.right(m_deviceName.size()-m_deviceName.indexOf(" ")-1).replace(" ", "_") + MIDI_MAPPING_EXTENSION)
00038 
00039 // static QString toHex(QString numberStr) {
00040 //     return "0x" + QString("0" + QString::number(numberStr.toUShort(), 16).toUpper()).right(2);
00041 // }
00042 
00043 MidiMapping::MidiMapping(MidiDevice* outputMidiDevice)
00044         : QObject(),
00045           m_mappingLock(QMutex::Recursive) {
00046     // If BINDINGS_PATH doesn't exist, create it
00047     if (!QDir(BINDINGS_PATH).exists()) {
00048         qDebug() << "Creating new MIDI mapping directory" << BINDINGS_PATH;
00049         QDir().mkpath(BINDINGS_PATH);
00050     }
00051     // Likewise, if LPRESETS_PATH doesn't exist, create that too
00052     if (!QDir(LPRESETS_PATH).exists()) {
00053         qDebug() << "Creating new MIDI presets directory" <<LPRESETS_PATH;
00054         QDir().mkpath(LPRESETS_PATH);
00055     }
00056     // So we can signal the MidiScriptEngine and pass a QList
00057     qRegisterMetaType<QList<QString> >("QList<QString>");
00058 
00059     //Q_ASSERT(outputMidiDevice);
00060 
00061 #ifdef __MIDISCRIPT__
00062     //Start the scripting engine.
00063     m_pScriptEngine = NULL;
00064     m_pOutputMidiDevice = outputMidiDevice;
00065     if (m_pOutputMidiDevice)
00066         m_deviceName = m_pOutputMidiDevice->getName(); //Name of the device to look for the <controller> block for in the XML.
00067 
00068     startupScriptEngine();
00069 #endif
00070     m_pMidiInputMappingTableModel = new MidiInputMappingTableModel(this);
00071     m_pMidiOutputMappingTableModel = new MidiOutputMappingTableModel(this);
00072 }
00073 
00074 MidiMapping::~MidiMapping() {
00075     delete m_pMidiInputMappingTableModel;
00076     delete m_pMidiOutputMappingTableModel;
00077 }
00078 
00079 #ifdef __MIDISCRIPT__
00080 void MidiMapping::startupScriptEngine() {
00081     QMutexLocker Locker(&m_mappingLock);
00082 
00083     if(m_pScriptEngine) return;
00084 
00085     //XXX FIXME: Deadly hack attack:
00086     if (m_pOutputMidiDevice == NULL) {
00087         m_pOutputMidiDevice = new MidiDeviceDummy(this); //Just make some dummy device :(
00088         /* Why can't this be the same as the input MIDI device? Most of the time,
00089             the script engine thinks of it as the input device. Scripting is useful
00090             even if the MIDI device has no outputs - Sean 4/19/10   */
00091     }
00092     //XXX Memory leak :(
00093 
00094     qDebug () << "Starting script engine with output device" << m_pOutputMidiDevice->getName();
00095 
00096     m_pScriptEngine = new MidiScriptEngine(m_pOutputMidiDevice);
00097 
00098     m_pScriptEngine->moveToThread(m_pScriptEngine);
00099 
00100     // Let the script engine tell us when it's done with each task
00101     connect(m_pScriptEngine, SIGNAL(initialized()),
00102             this, SLOT(slotScriptEngineReady()),
00103             Qt::DirectConnection);
00104     m_scriptEngineInitializedMutex.lock();
00105     m_pScriptEngine->start();
00106     // Wait until the script engine is initialized
00107     m_scriptEngineInitializedCondition.wait(&m_scriptEngineInitializedMutex);
00108     m_scriptEngineInitializedMutex.unlock();
00109 
00110     // Allow this object to signal the MidiScriptEngine to load the list of script files
00111     connect(this, SIGNAL(loadMidiScriptFiles(QList<QString>)), m_pScriptEngine, SLOT(loadScriptFiles(QList<QString>)));
00112     // Allow this object to signal the MidiScriptEngine to run the initialization
00113     //  functions in the loaded scripts
00114     connect(this, SIGNAL(initMidiScripts(QList<QString>)), m_pScriptEngine, SLOT(initializeScripts(QList<QString>)));
00115     // Allow this object to signal the MidiScriptEngine to run its shutdown routines
00116     connect(this, SIGNAL(shutdownMidiScriptEngine(QList<QString>)), m_pScriptEngine, SLOT(gracefulShutdown(QList<QString>)));
00117 
00118     // Allow this object to signal the MidiScriptEngine to call functions
00119     connect(this, SIGNAL(callMidiScriptFunction(QString)),
00120             m_pScriptEngine, SLOT(execute(QString)));
00121     connect(this, SIGNAL(callMidiScriptFunction(QString, QString)),
00122             m_pScriptEngine, SLOT(execute(QString, QString)));
00123 
00124     // Allow the MidiScriptEngine to tell us if it needs to reset the controller (on errors)
00125     connect(m_pScriptEngine, SIGNAL(resetController()), this, SLOT(reset()));
00126 }
00127 
00128 void MidiMapping::loadScriptCode() {
00129     QMutexLocker Locker(&m_mappingLock);
00130     if(m_pScriptEngine) {
00131         m_scriptEngineInitializedMutex.lock();
00132         // Tell the script engine to run the init function in all loaded scripts
00133         emit(loadMidiScriptFiles(m_scriptFileNames));
00134         // Wait until it's done
00135         m_scriptEngineInitializedCondition.wait(&m_scriptEngineInitializedMutex);
00136         m_scriptEngineInitializedMutex.unlock();
00137     }
00138 }
00139 
00140 void MidiMapping::initializeScripts() {
00141     QMutexLocker Locker(&m_mappingLock);
00142     if(m_pScriptEngine) {
00143         m_scriptEngineInitializedMutex.lock();
00144         // Tell the script engine to run the init function in all loaded scripts
00145         emit(initMidiScripts(m_scriptFunctionPrefixes));
00146         // Wait until it's done
00147         m_scriptEngineInitializedCondition.wait(&m_scriptEngineInitializedMutex);
00148         m_scriptEngineInitializedMutex.unlock();
00149     }
00150 }
00151 
00152 void MidiMapping::shutdownScriptEngine() {
00153     QMutexLocker Locker(&m_mappingLock);
00154     if(m_pScriptEngine) {
00155         // Tell the script engine to do its shutdown sequence
00156         emit(shutdownMidiScriptEngine(m_scriptFunctionPrefixes));
00157         // ...and wait for it to finish
00158         m_pScriptEngine->wait();
00159 
00160         MidiScriptEngine *engine = m_pScriptEngine;
00161         m_pScriptEngine = NULL;
00162         delete engine;
00163     }
00164 }
00165 #endif
00166 
00167 void MidiMapping::setOutputMidiDevice(MidiDevice* outputMidiDevice)
00168 {
00169     m_mappingLock.lock();
00170     m_pOutputMidiDevice = outputMidiDevice;
00171     m_mappingLock.unlock();
00172 #ifdef __MIDISCRIPT__
00173     //Restart the script engine so it gets its pointer to the output MIDI device updated.
00174     restartScriptEngine();
00175 #endif
00176 }
00177 
00178 /* ============================== MIDI Input Mapping Modifiers
00179                                     (Part of QT MVC wrapper)
00180 */
00181 
00182 /*
00183  * Return the total number of current input mappings.
00184  */
00185 int MidiMapping::numInputMidiMessages() {
00186     m_mappingLock.lock();
00187     int value = internalNumInputMidiMessages();
00188     m_mappingLock.unlock();
00189     return value;
00190 }
00191 int MidiMapping::internalNumInputMidiMessages() {
00192     return m_inputMapping.size();;
00193 }
00194 
00195 /*
00196  * Return true if the index corresponds to an input mapping key.
00197  */
00198 bool MidiMapping::isInputIndexValid(int index) {
00199     if(index < 0 || index >= numInputMidiMessages()) {
00200         return false;
00201     }
00202     return true;
00203 }
00204 
00205 bool MidiMapping::internalIsInputIndexValid(int index) {
00206     if(index < 0 || index >= internalNumInputMidiMessages()) {
00207         return false;
00208     }
00209     return true;
00210 }
00211 
00212 bool MidiMapping::isMidiMessageMapped(MidiMessage command) {
00213     m_mappingLock.lock();
00214     bool value = m_inputMapping.contains(command);
00215     m_mappingLock.unlock();
00216     return value;
00217 }
00218 
00219 /*
00220  * Lookup the MidiMessage corresponding to a given index.
00221  */
00222 MidiMessage MidiMapping::getInputMidiMessage(int index) {
00223     m_mappingLock.lock();
00224     MidiMessage message;
00225     if (internalIsInputIndexValid(index)) {
00226         message = m_inputMapping.keys().at(index);
00227     }
00228     m_mappingLock.unlock();
00229     return message;
00230 }
00231 
00232 /*
00233  * Lookup the MixxxControl mapped to a given MidiMessage (by index).
00234  */
00235 MixxxControl MidiMapping::getInputMixxxControl(int index) {
00236     m_mappingLock.lock();
00237     MixxxControl control;
00238     if (internalIsInputIndexValid(index)) {
00239         MidiMessage key = m_inputMapping.keys().at(index);
00240         control = m_inputMapping.value(key);
00241     }
00242     m_mappingLock.unlock();
00243     return control;
00244 }
00245 
00246 /*
00247  * Lookup the MixxxControl mapped to a given MidiMessage.
00248  */
00249 MixxxControl MidiMapping::getInputMixxxControl(MidiMessage command) {
00250     m_mappingLock.lock();
00251     MixxxControl control;
00252     if (m_inputMapping.contains(command)) {
00253         control = m_inputMapping.value(command);
00254     }
00255     m_mappingLock.unlock();
00256     return control;
00257 }
00258 
00259 /*
00260  * Set a MidiMessage -> MixxxControl mapping, replacing an existing one
00261  * if necessary.
00262  */
00263 void MidiMapping::setInputMidiMapping(MidiMessage command, MixxxControl control) {
00264     m_mappingLock.lock();
00265     internalSetInputMidiMapping(command, control, false);
00266     m_mappingLock.unlock();
00267     emit(inputMappingChanged());
00268 }
00269 
00270 void MidiMapping::internalSetInputMidiMapping(MidiMessage command, MixxxControl control, bool shouldEmit) {
00271     // If the command is already in the mapping, it will be replaced
00272     m_inputMapping.insert(command,control);
00273     if (shouldEmit)
00274         emit(inputMappingChanged());
00275 }
00276 
00277 
00278 /*
00279  * Clear a specific mapping for a MidiMessage by index.
00280  */
00281 void MidiMapping::clearInputMidiMapping(int index) {
00282     bool valid = false;
00283     m_mappingLock.lock();
00284     if (internalIsInputIndexValid(index)) {
00285         MidiMessage key = m_inputMapping.keys().at(index);
00286         m_inputMapping.remove(key);
00287         valid = true;
00288     }
00289     m_mappingLock.unlock();
00290 
00291     if (valid)
00292         emit(inputMappingChanged());
00293 }
00294 
00295 /*
00296  * Clear a specific mapping for a MidiMessage.
00297  */
00298 void MidiMapping::clearInputMidiMapping(MidiMessage command) {
00299     m_mappingLock.lock();
00300     int changed = m_inputMapping.remove(command);
00301     m_mappingLock.unlock();
00302 
00303     if(changed > 0)
00304         emit(inputMappingChanged());
00305 }
00306 
00307 /*
00308  * Clears a range of input mappings. (This really only exists so that
00309  * a caller can atomically remove a range of rows)
00310  *
00311  */
00312 void MidiMapping::clearInputMidiMapping(int index, int count) {
00313     m_mappingLock.lock();
00314     QList<MidiMessage> keys = m_inputMapping.keys();
00315     int changed = 0;
00316     for(int i=index; i < index+count; i++) {
00317         MidiMessage command = keys.at(i);
00318         changed += m_inputMapping.remove(command);
00319     }
00320     m_mappingLock.unlock();
00321     if(changed > 0)
00322         emit(inputMappingChanged());
00323 }
00324 
00325 /*==== End of MIDI input mapping modifiers (part of QT MVC wrapper) */
00326 
00327 
00328 
00329 /* ============================== MIDI ***Output*** Mapping Modifiers
00330                                     (Part of QT MVC wrapper)
00331 */
00332 
00333 /*
00334  * Return the total number of current output mappings.
00335  */
00336 int MidiMapping::numOutputMixxxControls() {
00337     m_mappingLock.lock();
00338     int value = internalNumOutputMixxxControls();
00339     m_mappingLock.unlock();
00340     return value;
00341 }
00342 
00343 int MidiMapping::internalNumOutputMixxxControls() {
00344     return m_outputMapping.size();
00345 }
00346 
00347 
00348 /*
00349  * Return true if the index corresponds to an input mapping key.
00350  */
00351 bool MidiMapping::isOutputIndexValid(int index) {
00352     m_mappingLock.lock();
00353     bool result = internalIsOutputIndexValid(index);
00354     m_mappingLock.unlock();
00355     return result;
00356 }
00357 
00358 bool MidiMapping::internalIsOutputIndexValid(int index) {
00359     if(index < 0 || index >= internalNumOutputMixxxControls()) {
00360         return false;
00361     }
00362     return true;
00363 }
00364 
00365 
00366 bool MidiMapping::isMixxxControlMapped(MixxxControl control) {
00367     m_mappingLock.lock();
00368     bool result = m_outputMapping.contains(control);
00369     m_mappingLock.unlock();
00370     return result;
00371 }
00372 
00373 /*
00374  * Lookup the MidiMessage corresponding to a given index.
00375  */
00376 MixxxControl MidiMapping::getOutputMixxxControl(int index) {
00377     m_mappingLock.lock();
00378     MixxxControl control;
00379     if (!internalIsOutputIndexValid(index)) {
00380         control = m_outputMapping.keys().at(index);
00381     }
00382     m_mappingLock.unlock();
00383     return control;
00384 }
00385 
00386 /*
00387  * Lookup the MixxxControl mapped to a given MidiMessage (by index).
00388  */
00389 MidiMessage MidiMapping::getOutputMidiMessage(int index) {
00390     qDebug() << "getOutputMidiMessage" << index;
00391     m_mappingLock.lock();
00392     MidiMessage message;
00393     if (internalIsOutputIndexValid(index)) {
00394         MixxxControl key = m_outputMapping.keys().at(index);
00395         message = m_outputMapping.value(key);
00396     }
00397     m_mappingLock.unlock();
00398     return message;
00399 }
00400 
00401 /*
00402  * Lookup the MixxxControl mapped to a given MidiMessage.
00403  */
00404 MidiMessage MidiMapping::getOutputMidiMessage(MixxxControl control) {
00405     m_mappingLock.lock();
00406     MidiMessage message;
00407     if (m_outputMapping.contains(control)) {
00408         message = m_outputMapping.value(control);
00409     }
00410     m_mappingLock.unlock();
00411     return message;
00412 }
00413 
00414 /*
00415  * Set a MidiMessage -> MixxxControl mapping, replacing an existing one
00416  * if necessary.
00417  */
00418 void MidiMapping::setOutputMidiMapping(MixxxControl control, MidiMessage command) {
00419     m_mappingLock.lock();
00420     internalSetOutputMidiMapping(control, command, false);
00421     m_mappingLock.unlock();
00422     emit(outputMappingChanged());
00423 }
00424 
00425 void MidiMapping::internalSetOutputMidiMapping(MixxxControl control,
00426                                                MidiMessage command,
00427                                                bool shouldEmit) {
00428     // If the command is already in the mapping, it will be replaced
00429     m_outputMapping.insert(control, command);
00430 
00431     if (shouldEmit)
00432         emit(outputMappingChanged());
00433 }
00434 
00435 /*
00436  * Clear a specific mapping for a MidiMessage by index.
00437  */
00438 void MidiMapping::clearOutputMidiMapping(int index) {
00439     m_mappingLock.lock();
00440     bool changed = false;
00441     if (internalIsOutputIndexValid(index)) {
00442         qDebug() << m_outputMapping.size();
00443         qDebug() << "MidiMapping: removing" << index;
00444         MixxxControl key = m_outputMapping.keys().at(index);
00445         m_outputMapping.remove(key);
00446         qDebug() << m_outputMapping.size();
00447         changed = true;
00448     }
00449     m_mappingLock.unlock();
00450 
00451     if (changed)
00452         emit(outputMappingChanged());
00453 }
00454 
00455 /*
00456  * Clear a specific mapping for a MidiMessage.
00457  */
00458 void MidiMapping::clearOutputMidiMapping(MixxxControl control) {
00459     m_mappingLock.lock();
00460     int changed = m_outputMapping.remove(control);
00461     m_mappingLock.unlock();
00462 
00463     if(changed > 0)
00464         emit(outputMappingChanged());
00465 }
00466 
00467 /*
00468  * Clears a range of input mappings. (This really only exists so that
00469  * a caller can atomically remove a range of rows)
00470  *
00471  */
00472 void MidiMapping::clearOutputMidiMapping(int index, int count) {
00473     m_mappingLock.lock();
00474     QList<MixxxControl> keys = m_outputMapping.keys();
00475     int changed = 0;
00476     for(int i=index; i < index+count; i++) {
00477         MixxxControl control = keys.at(i);
00478         changed += m_outputMapping.remove(control);
00479     }
00480     m_mappingLock.unlock();
00481 
00482     if(changed > 0)
00483         emit(outputMappingChanged());
00484 }
00485 
00486 /*==== End of MIDI output mapping modifiers (part of QT MVC wrapper) */
00487 
00488 
00489 #ifdef __MIDISCRIPT__
00490 /* -------- ------------------------------------------------------
00491    Purpose: Adds an entry to the list of script file names
00492             & associated list of function prefixes
00493    Input:   QString file name, QString function prefix
00494    Output:  -
00495    -------- ------------------------------------------------------ */
00496 void MidiMapping::addScriptFile(QString filename, QString functionprefix) {
00497     QMutexLocker Locker(&m_mappingLock);
00498     m_scriptFileNames.append(filename);
00499     m_scriptFunctionPrefixes.append(functionprefix);
00500 }
00501 #endif
00502 
00503 /* setName(QString)
00504  * Sets the controller name this mapping corresponds to
00505  * @param name The controller name this mapping is hooked to
00506  */
00507 void MidiMapping::setName(QString name) {
00508     QMutexLocker Locker(&m_mappingLock);
00509     m_deviceName = name;
00510 }
00511 
00512 /* loadPreset()
00513  * Overloaded function for convenience, uses the default device path
00514  * @param forceLoad Forces the MIDI mapping to be loaded, regardless of whether or not the controller id
00515  *        specified in the mapping matches the device this MidiMapping object is hooked up to.
00516  */
00517 void MidiMapping::loadPreset(bool forceLoad) {
00518     loadPreset(DEFAULT_DEVICE_PRESET, forceLoad);
00519 }
00520 
00521 /* loadPreset(QString)
00522  * Overloaded function for convenience
00523  * @param path The path to a MIDI mapping XML file.
00524  * @param forceLoad Forces the MIDI mapping to be loaded, regardless of whether or not the controller id
00525  *        specified in the mapping matches the device this MidiMapping object is hooked up to.
00526  */
00527 void MidiMapping::loadPreset(QString path, bool forceLoad) {
00528     qDebug() << "MidiMapping: Loading MIDI preset from" << path;
00529     loadPreset(WWidget::openXMLFile(path, "controller"), forceLoad);
00530 }
00531 
00532 /* loadPreset(QDomElement)
00533  * Loads a set of MIDI bindings from a QDomElement structure.
00534  * @param root The root node of the XML document for the MIDI mapping.
00535  * @param forceLoad Forces the MIDI mapping to be loaded, regardless of whether or not the controller id
00536  *        specified in the mapping matches the device this MidiMapping object is hooked up to.
00537  */
00538 void MidiMapping::loadPreset(QDomElement root, bool forceLoad) {
00539     //qDebug() << QString("MidiMapping: loadPreset() called in thread ID=%1").arg(this->thread()->currentThreadId(),0,16);
00540 
00541     if (root.isNull()) return;
00542 
00543     m_pMidiInputMappingTableModel->removeRows(0, m_pMidiInputMappingTableModel->rowCount());
00544     m_pMidiOutputMappingTableModel->removeRows(0, m_pMidiOutputMappingTableModel->rowCount());
00545 
00546     // Note, the lock comes after these two lines. We mustn't touch the
00547     // *MappingTableModel's after we are locked because they have pointers to us
00548     // so when we make a call to them they might in turn call us, causing a
00549     // deadlock.
00550     m_mappingLock.lock();
00551 
00552 #ifdef __MIDISCRIPT__
00553     m_scriptFileNames.clear();
00554     m_scriptFunctionPrefixes.clear();
00555 #endif
00556 
00557     // For each controller in the DOM
00558     m_Bindings = root;
00559     QDomElement controller = m_Bindings.firstChildElement("controller");
00560 
00561     // For each controller in the MIDI mapping XML...
00562     //(Only parse the <controller> block if it's id matches our device name, otherwise
00563     //keep looking at the next controller blocks....)
00564     QString device;
00565     while (!controller.isNull()) {
00566         // Get deviceid
00567         device = controller.attribute("id","");
00568         if (device != m_deviceName && !forceLoad) {
00569             controller = controller.nextSiblingElement("controller");
00570         }
00571         else
00572             break;
00573     }
00574 
00575     if (!controller.isNull()) {
00576 
00577         qDebug() << device << " settings found";
00578 #ifdef __MIDISCRIPT__
00579         // Build a list of MIDI script files to load
00580 
00581         QDomElement scriptFile = controller.firstChildElement("scriptfiles").firstChildElement("file");
00582 
00583         // Default currently required file
00584         addScriptFile(REQUIRED_SCRIPT_FILE,"");
00585 
00586         // Look for additional ones
00587         while (!scriptFile.isNull()) {
00588             QString functionPrefix = scriptFile.attribute("functionprefix","");
00589             QString filename = scriptFile.attribute("filename","");
00590             addScriptFile(filename, functionPrefix);
00591 
00592             scriptFile = scriptFile.nextSiblingElement("file");
00593         }
00594 
00595         loadScriptCode();   // Actually load code from the list built above
00596 
00597         QStringList scriptFunctions;
00598         if (m_pScriptEngine != NULL) {
00599             scriptFunctions = m_pScriptEngine->getScriptFunctions();
00600         }
00601 
00602 #endif
00603 
00604         QDomElement control = controller.firstChildElement("controls").firstChildElement("control");
00605 
00606         //Iterate through each <control> block in the XML
00607         while (!control.isNull()) {
00608 
00609             //Unserialize these objects from the XML
00610             MidiMessage midiMessage(control);
00611             MixxxControl mixxxControl(control);
00612 #ifdef __MIDISCRIPT__
00613             // Verify script functions are loaded
00614             if (mixxxControl.getMidiOption()==MIDI_OPT_SCRIPT &&
00615                 scriptFunctions.indexOf(mixxxControl.getControlObjectValue())==-1) {
00616 
00617                 QString status = QString("%1").arg(midiMessage.getMidiStatusByte(), 0, 16).toUpper();
00618                 status = "0x"+status;
00619                 QString byte2 = QString("%1").arg(midiMessage.getMidiNo(), 0, 16).toUpper();
00620                 byte2 = "0x"+byte2;
00621 
00622                 // If status is MIDI pitch, the 2nd byte is part of the payload so don't display it
00623                 if (midiMessage.getMidiStatusByte() == 0xE0) byte2 = "";
00624 
00625                 QString errorLog = QString("MIDI script function \"%1\" not found. "
00626                                     "(Mapped to MIDI message %2 %3)")
00627                                     .arg(mixxxControl.getControlObjectValue())
00628                                     .arg(status)
00629                                     .arg(byte2);
00630 
00631                 if (m_pOutputMidiDevice != NULL
00632                     && m_pOutputMidiDevice->midiDebugging()) {
00633                         qCritical() << errorLog;
00634                 }
00635                 else {
00636                     qWarning() << errorLog;
00637                     ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
00638                     props->setType(DLG_WARNING);
00639                     props->setTitle(tr("MIDI script function not found"));
00640                     props->setText(QString(tr("The MIDI script function '%1' was not "
00641                                     "found in loaded scripts."))
00642                                     .arg(mixxxControl.getControlObjectValue()));
00643                     props->setInfoText(QString(tr("The MIDI message %1 %2 will not be bound."
00644                                     "\n(Click Show Details for hints.)"))
00645                                     .arg(status)
00646                                     .arg(byte2));
00647                     QString detailsText = QString(tr("* Check to see that the "
00648                         "function name is spelled correctly in the mapping "
00649                         "file (.xml) and script file (.js)\n"));
00650                     detailsText += QString(tr("* Check to see that the script "
00651                         "file name (.js) is spelled correctly in the mapping "
00652                         "file (.xml)"));
00653                     props->setDetails(detailsText);
00654 
00655                     ErrorDialogHandler::instance()->requestErrorDialog(props);
00656                 }
00657             } else {
00658 #endif
00659                 //Add to the input mapping.
00660                 internalSetInputMidiMapping(midiMessage, mixxxControl, true);
00661                 /*Old code: m_inputMapping.insert(midiMessage, mixxxControl);
00662                   Reason why this is bad: Don't want to access this directly because the
00663                   model doesn't get notified about the update */
00664 #ifdef __MIDISCRIPT__
00665             }
00666 #endif
00667             control = control.nextSiblingElement("control");
00668         }
00669 
00670         qDebug() << "MidiMapping: Input parsed!";
00671 
00672         QDomElement output = controller.firstChildElement("outputs").firstChildElement("output");
00673 
00674         //Iterate through each <control> block in the XML
00675         while (!output.isNull()) {
00676             //Unserialize these objects from the XML
00677             MidiMessage midiMessage(output);
00678             MixxxControl mixxxControl(output, true);
00679 
00680             //Add to the output mapping.
00681             internalSetOutputMidiMapping(mixxxControl, midiMessage, true);
00682             /*Old code: m_outputMapping.insert(mixxxControl, midiMessage);
00683               Reason why this is bad: Don't want to access this directly because the
00684                                       model doesn't get notified about the update */
00685 
00686             output = output.nextSiblingElement("output");
00687         }
00688 
00689         qDebug() << "MidiMapping: Output parsed!";
00690         //controller = controller.nextSiblingElement("controller"); //FIXME: Remove this line of code permanently - Albert
00691     }
00692 
00693     m_mappingLock.unlock();
00694 
00695 }   // END loadPreset(QDomElement)
00696 
00697 /* savePreset()
00698  * Saves the current table of bindings to the default device XML file.
00699  */
00700 void MidiMapping::savePreset() {
00701     savePreset(DEFAULT_DEVICE_PRESET);
00702 }
00703 
00704 /* savePreset(QString)
00705  * Given a path, saves the current table of bindings to an XML file.
00706  */
00707 void MidiMapping::savePreset(QString path) {
00708     qDebug() << "Writing MIDI preset file" << path;
00709     QMutexLocker locker(&m_mappingLock);
00710     QFile output(path);
00711     if (!output.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;
00712     QTextStream outputstream(&output);
00713     // Construct the DOM from the table
00714     QDomDocument docBindings = buildDomElement();
00715     // Save the DOM to the XML file
00716     docBindings.save(outputstream, 4);
00717     output.close();
00718 }
00719 
00720 /* applyPreset()
00721  * Load the current bindings set into the MIDI handler, and the outputs info into
00722  * the LED handler.
00723  */
00724 void MidiMapping::applyPreset() {
00725     qDebug() << "MidiMapping::applyPreset()";
00726     QMutexLocker locker(&m_mappingLock);
00727 
00728 #ifdef __MIDISCRIPT__
00729     // Since this can be called after re-enabling a device without reloading the XML preset,
00730     // the script engine must have its code loaded here as well
00731     QStringList scriptFunctions;
00732     if (m_pScriptEngine != NULL) {
00733         scriptFunctions = m_pScriptEngine->getScriptFunctions();
00734     }
00735     if (scriptFunctions.isEmpty()) loadScriptCode();
00736     
00737     initializeScripts();
00738 #endif
00739 
00740     if (m_pOutputMidiDevice != NULL) {
00741         //^^^ Only execute this code if we have an output device hooked up
00742         //    to this MidiMapping...
00743 
00744         QDomElement controller = m_Bindings.firstChildElement("controller");
00745         // For each device
00746         while (!controller.isNull()) {
00747             // Device Outputs - LEDs
00748             QString deviceId = controller.attribute("id","");
00749 
00750             qDebug() << "MidiMapping: Processing MIDI Output Bindings for" << deviceId;
00751             MidiLedHandler::createHandlers(controller.namedItem("outputs").firstChild(),
00752                                            *m_pOutputMidiDevice);
00753 
00754             // Next device
00755             controller = controller.nextSiblingElement("controller");
00756         }
00757         MidiLedHandler::updateAll();
00758     }
00759 }
00760 
00761 /* clearPreset()
00762  * Creates a blank bindings preset.
00763  */
00764 void MidiMapping::clearPreset() {
00765     // Assumes the lock is held.
00766     // Create a new blank DomNode
00767     QString blank = "<MixxxMIDIPreset schemaVersion=\"" + QString(XML_SCHEMA_VERSION) + "\" mixxxVersion=\"" + QString(VERSION) + "+\">\n"
00768     "</MixxxMIDIPreset>\n";
00769     QDomDocument doc("Bindings");
00770     doc.setContent(blank);
00771     m_Bindings = doc.documentElement();
00772 }
00773 
00774 /* buildDomElement()
00775  * Updates the DOM with what is currently in the table
00776  */
00777  QDomDocument MidiMapping::buildDomElement() {
00778      // We should hold the mapping lock. The lock is recursive so if we already
00779      // hold it it will relock.
00780      QMutexLocker locker(&m_mappingLock);
00781 
00782     clearPreset(); // Create blank document
00783 
00784     QDomDocument doc("Bindings");
00785     QString blank = "<MixxxMIDIPreset schemaVersion=\"" + QString(XML_SCHEMA_VERSION) + "\" mixxxVersion=\"" + QString(VERSION) + "+\">\n"
00786                     "</MixxxMIDIPreset>\n";
00787 
00788     doc.setContent(blank);
00789 
00790     QDomElement rootNode = doc.documentElement();
00791     QDomElement controller = doc.createElement("controller");
00792     controller.setAttribute("id", m_deviceName.right(m_deviceName.size()-m_deviceName.indexOf(" ")-1));
00793     rootNode.appendChild(controller);
00794 
00795 #ifdef __MIDISCRIPT__
00796     //This sucks, put this code inside MidiScriptEngine instead of here,
00797     // and just ask MidiScriptEngine to spit it out for us.
00798 //     qDebug() << "MidiMapping: Writing script block!";
00799 
00800     QDomElement scriptFiles = doc.createElement("scriptfiles");
00801     controller.appendChild(scriptFiles);
00802 
00803 
00804     for (int i = 0; i < m_scriptFileNames.count(); i++) {
00805 //         qDebug() << "MidiMapping: writing script block for" << m_scriptFileNames[i];
00806         QString filename = m_scriptFileNames[i];
00807 
00808 
00809         //Don't need to write anything for the required mapping file.
00810         if (filename != REQUIRED_SCRIPT_FILE) {
00811             qDebug() << "MidiMapping: writing script block for" << filename;
00812             QString functionPrefix = m_scriptFunctionPrefixes[i];
00813             QDomElement scriptFile = doc.createElement("file");
00814 
00815 
00816             scriptFile.setAttribute("filename", filename);
00817             scriptFile.setAttribute("functionprefix", functionPrefix);
00818 
00819             scriptFiles.appendChild(scriptFile);
00820         }
00821     }
00822 #endif
00823 
00824     QDomElement controls = doc.createElement("controls");
00825     controller.appendChild(controls);
00826 
00827 
00828     //Iterate over all of the command/control pairs in the input mapping
00829     QHashIterator<MidiMessage, MixxxControl> it(m_inputMapping);
00830     while (it.hasNext()) {
00831         it.next();
00832 
00833         QDomElement controlNode = doc.createElement("control");
00834 
00835         //Save the MidiMessage and MixxxControl objects as XML
00836         it.key().serializeToXML(controlNode);
00837         it.value().serializeToXML(controlNode);
00838 
00839         //Add the control node we just created to the XML document in the proper spot
00840         controls.appendChild(controlNode);
00841     }
00842 
00843 
00844     QDomElement outputs = doc.createElement("outputs");
00845     controller.appendChild(outputs);
00846 
00847     //Iterate over all of the control/command pairs in the OUTPUT mapping
00848     QHashIterator<MixxxControl, MidiMessage> outIt(m_outputMapping);
00849     while (outIt.hasNext()) {
00850         outIt.next();
00851 
00852         QDomElement outputNode = doc.createElement("output");
00853 
00854 
00855         //Save the MidiMessage and MixxxControl objects as XML
00856         outIt.key().serializeToXML(outputNode, true);
00857         outIt.value().serializeToXML(outputNode, true);
00858 
00859         //Add the control node we just created to the XML document in the proper spot
00860         outputs.appendChild(outputNode);
00861     }
00862 
00863     m_Bindings = doc.documentElement();
00864     return doc;
00865 }
00866 
00867 /* -------- ------------------------------------------------------
00868    Purpose: Adds an input MIDI mapping block to the XML.
00869    Input:   QDomElement control, QString device
00870    Output:  -
00871    -------- ------------------------------------------------------ */
00872 void MidiMapping::addControl(QDomElement &control, QString device) {
00873     QDomDocument nodeMaker;
00874     //Add control to correct device tag - find the correct tag
00875     QDomElement controller = m_Bindings.firstChildElement("controller");
00876     while (controller.attribute("id","") != device && !controller.isNull()) {
00877         controller = controller.nextSiblingElement("controller");
00878     }
00879     if (controller.isNull()) {
00880         // No tag was found - create it
00881         controller = nodeMaker.createElement("controller");
00882         controller.setAttribute("id", device);
00883         m_Bindings.appendChild(controller);
00884     }
00885     // Check for controls tag
00886     QDomElement controls = controller.firstChildElement("controls");
00887     if (controls.isNull()) {
00888         controls = nodeMaker.createElement("controls");
00889         controller.appendChild(controls);
00890     }
00891     controls.appendChild(control);
00892 }
00893 
00894 /* -------- ------------------------------------------------------
00895    Purpose: This code sucks, temporary hack
00896    Input:   QDomElement control, QString device
00897    Output:  -
00898    -------- ------------------------------------------------------ */
00899 void MidiMapping::addMidiScriptInfo(QDomElement &scriptFile, QString device) {
00900     QDomDocument nodeMaker;
00901     //Add control to correct device tag - find the correct tag
00902     QDomElement controller = m_Bindings.firstChildElement("controller");
00903     while (controller.attribute("id","") != device && !controller.isNull()) {
00904         controller = controller.nextSiblingElement("controller");
00905     }
00906     if (controller.isNull()) {
00907         // No tag was found - create it
00908         controller = nodeMaker.createElement("controller");
00909         controller.setAttribute("id", device);
00910         m_Bindings.appendChild(controller);
00911     }
00912     // Check for controls tag
00913     QDomElement scriptfiles = controller.firstChildElement("scriptfiles");
00914     if (scriptfiles.isNull()) {
00915         scriptfiles = nodeMaker.createElement("scriptfiles");
00916         controller.appendChild(scriptfiles);
00917     }
00918     scriptfiles.appendChild(scriptFile);
00919 }
00920 
00921 /* -------- ------------------------------------------------------
00922    Purpose: Adds an output (LED, etc.) MIDI mapping block to the XML.
00923    Input:   QDomElement output, QString device
00924    Output:  -
00925    -------- ------------------------------------------------------ */
00926 void MidiMapping::addOutput(QDomElement &output, QString device) {
00927     QDomDocument nodeMaker;
00928     // Find the controller to attach the XML to...
00929     QDomElement controller = m_Bindings.firstChildElement("controller");
00930     while (controller.attribute("id","") != device && !controller.isNull()) {
00931         controller = controller.nextSiblingElement("controller");
00932     }
00933     if (controller.isNull()) {
00934         // No tag was found - create it
00935         controller = nodeMaker.createElement("controller");
00936         controller.setAttribute("id", device);
00937         m_Bindings.appendChild(controller);
00938     }
00939 
00940     // Find the outputs block
00941     QDomElement outputs = controller.firstChildElement("outputs");
00942     if (outputs.isNull()) {
00943         outputs = nodeMaker.createElement("outputs");
00944         controller.appendChild(outputs);
00945     }
00946     // attach the output to the outputs block
00947     outputs.appendChild(output);
00948 }
00949 
00950 bool MidiMapping::addInputControl(MidiStatusByte midiStatus, int midiNo, int midiChannel,
00951                                   QString controlObjectGroup, QString controlObjectKey,
00952                                   QString controlObjectDescription, MidiOption midiOption)
00953 {
00954     return addInputControl(MidiMessage(midiStatus, midiNo, midiChannel),
00955                            MixxxControl(controlObjectGroup, controlObjectKey,
00956                                         controlObjectDescription, midiOption));
00957 }
00958 
00959 bool MidiMapping::addInputControl(MidiMessage message, MixxxControl control)
00960 {
00961     //TODO: Check if mapping already exists for this MidiMessage.
00962 
00963     //Add to the input mapping.
00964     m_inputMapping.insert(message, control);
00965     return true; //XXX is this right? should this be returning whether the add happened successfully?
00966 
00967 }
00968 
00969 void MidiMapping::removeInputMapping(MidiStatusByte midiStatus, int midiNo, int midiChannel)
00970 {
00971     m_inputMapping.remove(MidiMessage(midiStatus, midiNo, midiChannel));
00972 }
00973 
00974 MidiInputMappingTableModel* MidiMapping::getMidiInputMappingTableModel()
00975 {
00976     return m_pMidiInputMappingTableModel;
00977 }
00978 
00979 MidiOutputMappingTableModel* MidiMapping::getMidiOutputMappingTableModel()
00980 {
00981     return m_pMidiOutputMappingTableModel;
00982 }
00983 
00984 //Used by MidiObject to query what control matches a given MIDI command.
00985 /*MixxxControl* MidiMapping::getInputMixxxControl(MidiMessage command)
00986 {
00987     if (!m_inputMapping.contains(command)) {
00988         qWarning() << "Unbound MIDI command";
00989         qDebug() << "Midi Status:" << command.getMidiStatusByte();
00990         qDebug() << "Midi No:" << command.getMidiNo();
00991         qDebug() << "Midi Channel:" << command.getMidiChannel();
00992         return NULL;
00993     }
00994 
00995     MixxxControl* control = &(m_inputMapping[command]);
00996     return control;
00997     }*/
00998 
00999 // BJW: Note: _prevmidivalue is not the previous MIDI value. It's the
01000 // current controller value, scaled to 0-127 but only in the case of pots.
01001 // (See Control*::GetMidiValue())
01002 
01003  // static
01004 double MidiMapping::ComputeValue(MidiOption midioption, double _prevmidivalue, double _newmidivalue)
01005 {
01006     double tempval = 0.;
01007     double diff = 0.;
01008 
01009     // qDebug() << "ComputeValue: option " << midioption << ", MIDI value " << _newmidivalue << ", current control value " << _prevmidivalue;
01010     if (midioption == MIDI_OPT_NORMAL) {
01011         return _newmidivalue;
01012     }
01013     else if (midioption == MIDI_OPT_INVERT)
01014     {
01015         return 127. - _newmidivalue;
01016     }
01017     else if (midioption == MIDI_OPT_ROT64 || midioption == MIDI_OPT_ROT64_INV)
01018     {
01019         tempval = _prevmidivalue;
01020         diff = _newmidivalue - 64.;
01021         if (diff == -1 || diff == 1)
01022             diff /= 16;
01023         else
01024             diff += (diff > 0 ? -1 : +1);
01025         if (midioption == MIDI_OPT_ROT64)
01026             tempval += diff;
01027         else
01028             tempval -= diff;
01029         return (tempval < 0. ? 0. : (tempval > 127. ? 127.0 : tempval));
01030     }
01031     else if (midioption == MIDI_OPT_ROT64_FAST)
01032     {
01033         tempval = _prevmidivalue;
01034         diff = _newmidivalue - 64.;
01035         diff *= 1.5;
01036         tempval += diff;
01037         return (tempval < 0. ? 0. : (tempval > 127. ? 127.0 : tempval));
01038     }
01039     else if (midioption == MIDI_OPT_DIFF)
01040     {
01041         //Interpret 7-bit signed value using two's compliment.
01042         if (_newmidivalue >= 64.)
01043             _newmidivalue = _newmidivalue - 128.;
01044         //Apply sensitivity to signed value. FIXME
01045        // if(sensitivity > 0)
01046         //    _newmidivalue = _newmidivalue * ((double)sensitivity / 50.);
01047         //Apply new value to current value.
01048         _newmidivalue = _prevmidivalue + _newmidivalue;
01049     }
01050     else if (midioption == MIDI_OPT_SELECTKNOB)
01051     {
01052         //Interpret 7-bit signed value using two's compliment.
01053         if (_newmidivalue >= 64.)
01054             _newmidivalue = _newmidivalue - 128.;
01055         //Apply sensitivity to signed value. FIXME
01056         //if(sensitivity > 0)
01057         //    _newmidivalue = _newmidivalue * ((double)sensitivity / 50.);
01058         //Since this is a selection knob, we do not want to inherit previous values.
01059     }
01060     else if (midioption == MIDI_OPT_BUTTON) { _newmidivalue = (_newmidivalue != 0); }
01061     else if (midioption == MIDI_OPT_SWITCH) { _newmidivalue = 1; }
01062     else if (midioption == MIDI_OPT_SPREAD64)
01063     {
01064 
01065         //qDebug() << "MIDI_OPT_SPREAD64";
01066         // BJW: Spread64: Distance away from centre point (aka "relative CC")
01067         // Uses a similar non-linear scaling formula as ControlTTRotary::getValueFromWidget()
01068         // but with added sensitivity adjustment. This formula is still experimental.
01069 
01070         _newmidivalue = _newmidivalue - 64.;
01071         //FIXME
01072         //double distance = _newmidivalue - 64.;
01073         // _newmidivalue = distance * distance * sensitivity / 50000.;
01074         //if (distance < 0.)
01075         //    _newmidivalue = -_newmidivalue;
01076 
01077          //qDebug() << "Spread64: in " << distance << "  out " << _newmidivalue;
01078     }
01079     else if (midioption == MIDI_OPT_HERC_JOG)
01080     {
01081         if (_newmidivalue > 64.) { _newmidivalue -= 128.; }
01082         _newmidivalue += _prevmidivalue;
01083         //if (_prevmidivalue != 0.0) { qDebug() << "AAAAAAAAAAAA" << _prevmidivalue; }
01084     }
01085     else
01086     {
01087         qWarning("Unknown MIDI option %d", midioption);
01088     }
01089 
01090     return _newmidivalue;
01091 }
01092 
01093 void MidiMapping::finishMidiLearn(MidiMessage message)
01094 {
01095     bool shouldEmit = false;
01096     m_mappingLock.lock();
01097     //We've received a MidiMessage that should be mapped onto some control. When
01098     //beginMidiLearn() was called, we were given the control that should be
01099     //mapped, so let's connect the message to the saved control and thus
01100     //"create" the mapping between the two.
01101     if (!m_controlToLearn.isNull()) { //Ensure we've actually been told to learn
01102                                       //a control.  Note the ! out front.
01103         addInputControl(message, m_controlToLearn);
01104 
01105         //If we caught a NOTE_ON message, add a binding for NOTE_OFF as well.
01106         if (message.getMidiStatusByte() == MIDI_STATUS_NOTE_ON) {
01107             MidiMessage noteOffMessage(message);
01108             noteOffMessage.setMidiStatusByte(MIDI_STATUS_NOTE_OFF);
01109             addInputControl(noteOffMessage, m_controlToLearn);
01110         }
01111 
01112         //Reset the saved control.
01113         m_controlToLearn = MixxxControl();
01114 
01115         qDebug() << "MidiMapping: Learning finished!";
01116 
01117         shouldEmit = true;
01118     }
01119     m_mappingLock.unlock();
01120 
01121     if (shouldEmit) {
01122         //Notify the prefs dialog that we've finished doing a MIDI learn.
01123         emit(midiLearningFinished(message));
01124         emit(midiLearningFinished()); //Tells MidiObject to stop feeding us messages.
01125         emit(inputMappingChanged());
01126     }
01127 }
01128 
01129 void MidiMapping::beginMidiLearn(MixxxControl control)
01130 {
01131     m_mappingLock.lock();
01132     //Save the internal control we're supposed to map/remap. After this we have
01133     //to wait for the user to push a button on their controller, which should
01134     //give us a MidiMessage via finishMidiLearn().
01135     m_controlToLearn = control;
01136 
01137     qDebug() << "MidiMapping: Learning started!";
01138 
01139     m_mappingLock.unlock();
01140 
01141     //Notify the MIDI device class that it should feed us the next MIDI message...
01142     emit(midiLearningStarted());
01143 }
01144 
01145 void MidiMapping::cancelMidiLearn()
01146 {
01147     m_mappingLock.lock();
01148     m_controlToLearn = MixxxControl();
01149     m_mappingLock.unlock();
01150 }
01151 
01152 #ifdef __MIDISCRIPT__
01153 void MidiMapping::restartScriptEngine()
01154 {
01155     //Note: Locking occurs inside the functions below.
01156     shutdownScriptEngine();
01157     startupScriptEngine();
01158 }
01159 #endif
01160 
01161 // Reset the MIDI controller
01162 void MidiMapping::reset() {
01163 #ifdef __MIDISCRIPT__   // Can't ifdef slots in the .h file, so we just do the body.
01164     restartScriptEngine();
01165 #endif
01166     MidiLedHandler::destroyHandlers(m_pOutputMidiDevice);
01167 
01168     applyPreset();
01169 }
01170 
01171 void MidiMapping::slotScriptEngineReady() {
01172 #ifdef __MIDISCRIPT__   // Can't ifdef slots in the .h file, so we just do the body.
01173 
01174     // The lock prevents us from waking before the main thread is waiting on the
01175     // condition.
01176     m_scriptEngineInitializedMutex.lock();
01177     m_scriptEngineInitializedCondition.wakeAll();
01178     m_scriptEngineInitializedMutex.unlock();
01179 
01180 #endif
01181 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines