![]() |
Mixxx
|
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 }