![]() |
Mixxx
|
00001 /*************************************************************************** 00002 mididevice.cpp 00003 MIDI Device Class 00004 ------------------- 00005 begin : Thu Dec 18 2008 00006 copyright : (C) 2008 Albert Santoni 00007 email : alberts@mixxx.org 00008 00009 ***************************************************************************/ 00010 00011 /*************************************************************************** 00012 * * 00013 * This program is free software; you can redistribute it and/or modify * 00014 * it under the terms of the GNU General Public License as published by * 00015 * the Free Software Foundation; either version 2 of the License, or * 00016 * (at your option) any later version. * 00017 * * 00018 ***************************************************************************/ 00019 00020 #include <qapplication.h> // For command line arguments 00021 #include "mididevice.h" 00022 #include "midimapping.h" 00023 #include "midimessage.h" 00024 #include "mixxxcontrol.h" 00025 #include "controlobject.h" 00026 #include "midiledhandler.h" 00027 00028 #ifdef __MIDISCRIPT__ 00029 #include "midiscriptengine.h" 00030 #endif 00031 00032 static QString toHex(QString numberStr) { 00033 return "0x" + QString("0" + QString::number(numberStr.toUShort(), 16).toUpper()).right(2); 00034 } 00035 00036 MidiDevice::MidiDevice(MidiMapping* mapping) : QThread() 00037 { 00038 00039 m_bIsOutputDevice = false; 00040 m_bIsInputDevice = false; 00041 m_pMidiMapping = mapping; 00042 m_pCorrespondingOutputDevice = NULL; 00043 m_bIsOpen = false; 00044 m_bMidiLearn = false; 00045 m_bReceiveInhibit = false; 00046 00047 if (m_pMidiMapping == NULL) { 00048 m_pMidiMapping = new MidiMapping(this); 00049 } 00050 00051 // TODO: Should this be in MidiDeviceManager instead? 00052 // Get --midiDebug command line option 00053 QStringList commandLineArgs = QApplication::arguments(); 00054 m_midiDebug = commandLineArgs.contains("--midiDebug", Qt::CaseInsensitive); 00055 00056 connect(m_pMidiMapping, SIGNAL(midiLearningStarted()), this, SLOT(enableMidiLearn())); 00057 connect(m_pMidiMapping, SIGNAL(midiLearningFinished()), this, SLOT(disableMidiLearn())); 00058 } 00059 00060 MidiDevice::~MidiDevice() 00061 { 00062 QMutexLocker locker(&m_mutex); 00063 00064 qDebug() << "MidiDevice: Deleting MidiMapping..."; 00065 m_mappingPtrMutex.lock(); 00066 delete m_pMidiMapping; 00067 m_mappingPtrMutex.unlock(); 00068 } 00069 00070 void MidiDevice::startup() 00071 { 00072 QMutexLocker locker(&m_mappingPtrMutex); 00073 #ifdef __MIDISCRIPT__ 00074 setReceiveInhibit(true); 00075 m_pMidiMapping->startupScriptEngine(); 00076 setReceiveInhibit(false); 00077 #endif 00078 } 00079 00080 void MidiDevice::shutdown() 00081 { 00082 QMutexLocker locker(&m_mappingPtrMutex); 00083 //Stop us from processing any MIDI messages that are 00084 //received while we're trying to shut down the scripting engine. 00085 //This prevents a deadlock that can happen because we've locked 00086 //the MIDI mapping pointer mutex (in the line above). If a 00087 //MIDI message is received while this is locked, the script 00088 //engine can end up waiting for the MIDI message to be 00089 //mapped and we end up with a deadlock. 00090 //Similarly, if a MIDI message is sent from the scripting 00091 //engine while we've held this lock, we'll end up with 00092 //a similar deadlock. (Note that shutdownScriptEngine() 00093 //waits for the scripting engine thread to terminate, 00094 //which will never happen if it's stuck waiting for 00095 //m_mappingPtrMutex to unlock. 00096 setReceiveInhibit(true); 00097 #ifdef __MIDISCRIPT__ 00098 m_pMidiMapping->shutdownScriptEngine(); 00099 #endif 00100 MidiLedHandler::destroyHandlers(this); 00101 setReceiveInhibit(false); 00102 } 00103 00104 void MidiDevice::setMidiMapping(MidiMapping* mapping) 00105 { 00106 m_mutex.lock(); 00107 m_mappingPtrMutex.lock(); 00108 m_pMidiMapping = mapping; 00109 00110 if (m_pCorrespondingOutputDevice) 00111 { 00112 m_pCorrespondingOutputDevice->setMidiMapping(m_pMidiMapping); 00113 } 00114 m_mappingPtrMutex.unlock(); 00115 m_mutex.unlock(); 00116 } 00117 00118 void MidiDevice::sendShortMsg(unsigned char status, unsigned char byte1, unsigned char byte2) { 00119 unsigned int word = (((unsigned int)byte2) << 16) | 00120 (((unsigned int)byte1) << 8) | status; 00121 sendShortMsg(word); 00122 } 00123 00124 void MidiDevice::sendShortMsg(unsigned int word) { 00125 m_mutex.lock(); 00126 qDebug() << "MIDI short message sending not yet implemented for this API or platform"; 00127 m_mutex.unlock(); 00128 } 00129 00130 void MidiDevice::sendSysexMsg(QList<int> data, unsigned int length) { 00131 m_mutex.lock(); 00132 00133 unsigned char * sysexMsg; 00134 sysexMsg = new unsigned char [length]; 00135 00136 for (unsigned int i=0; i<length; i++) { 00137 sysexMsg[i] = data.at(i); 00138 // qDebug() << "sysexMsg" << i << "=" << sysexMsg[i] << ", data=" << data.at(i); 00139 } 00140 00141 sendSysexMsg(sysexMsg,length); 00142 delete[] sysexMsg; 00143 m_mutex.unlock(); 00144 } 00145 00146 void MidiDevice::sendSysexMsg(unsigned char data[], unsigned int length) { 00147 qDebug() << "MIDI system exclusive message sending not yet implemented for this API or platform"; 00148 } 00149 00150 bool MidiDevice::getMidiLearnStatus() { 00151 m_mutex.lock(); 00152 bool learn = m_bMidiLearn; 00153 m_mutex.unlock(); 00154 return learn; 00155 } 00156 00157 void MidiDevice::enableMidiLearn() { 00158 m_mutex.lock(); 00159 m_bMidiLearn = true; 00160 m_mutex.unlock(); 00161 00162 m_mappingPtrMutex.lock(); 00163 connect(this, SIGNAL(midiEvent(MidiMessage)), m_pMidiMapping, SLOT(finishMidiLearn(MidiMessage))); 00164 m_mappingPtrMutex.unlock(); 00165 } 00166 00167 void MidiDevice::disableMidiLearn() { 00168 m_mutex.lock(); 00169 m_bMidiLearn = false; 00170 m_mutex.unlock(); 00171 00172 m_mappingPtrMutex.lock(); 00173 disconnect(this, SIGNAL(midiEvent(MidiMessage)), m_pMidiMapping, SLOT(finishMidiLearn(MidiMessage))); 00174 m_mappingPtrMutex.unlock(); 00175 } 00176 00177 void MidiDevice::receive(MidiStatusByte status, char channel, char control, char value) 00178 { 00179 if (midiDebugging()) qDebug() << QString("MIDI status 0x%1 (ch %2, opcode 0x%3), ctrl 0x%4, val 0x%5") 00180 .arg(QString::number(status, 16).toUpper()) 00181 .arg(QString::number(channel+1, 10)) 00182 .arg(QString::number((status & 255)>>4, 16).toUpper()) 00183 .arg(QString::number(control, 16).toUpper().rightJustified(2,'0')) 00184 .arg(QString::number(value, 16).toUpper().rightJustified(2,'0')); 00185 00186 // some status bytes can have the channel encoded in them. Take out the 00187 // channel when necessary. We do this because later bits of this 00188 // function (and perhaps its callchain) assume the channel nibble to be 00189 // zero in its comparisons -- bkgood 00190 switch (status & 0xF0) { 00191 case MIDI_STATUS_NOTE_OFF: 00192 case MIDI_STATUS_NOTE_ON: 00193 case MIDI_STATUS_AFTERTOUCH: 00194 case MIDI_STATUS_CC: 00195 case MIDI_STATUS_PROGRAM_CH: 00196 case MIDI_STATUS_CH_AFTERTOUCH: 00197 case MIDI_STATUS_PITCH_BEND: 00198 status = (MidiStatusByte) (status & 0xF0); 00199 } 00200 QMutexLocker locker(&m_mutex); //Lots of returns in this function. Keeps things simple. 00201 00202 MidiMessage inputCommand(status, control, channel); 00203 00204 //If the receive inhibit flag is true, then we don't process any midi messages 00205 //that are received from the device. This is done in order to prevent a race 00206 //condition where the MidiMapping is accessed via isMidiMessageMapped() below 00207 //but it is already locked because it is being modified by the GUI thread. 00208 //(This happens when you hit apply in the preferences and then quickly push 00209 // a button on your controller.) 00210 // This also avoids deadlocks when the device is sending data while it's being 00211 // initialized or shut down (more of a problem with scripted devices.) 00212 if (m_bReceiveInhibit) 00213 return; 00214 00215 if (m_bMidiLearn) { 00216 emit(midiEvent(inputCommand)); 00217 return; // Don't process midi messages further when MIDI learning 00218 } 00219 00220 QMutexLocker mappingLocker(&m_mappingPtrMutex); 00221 00222 // Only check for a mapping if the status byte is one we know how to handle 00223 if (status == MIDI_STATUS_NOTE_ON 00224 || status == MIDI_STATUS_NOTE_OFF 00225 || status == MIDI_STATUS_PITCH_BEND 00226 || status == MIDI_STATUS_CC) { 00227 // If there was no control bound to that MIDI command, return; 00228 if (!m_pMidiMapping->isMidiMessageMapped(inputCommand)) { 00229 return; 00230 } 00231 } 00232 00233 MixxxControl mixxxControl = m_pMidiMapping->getInputMixxxControl(inputCommand); 00234 //qDebug() << "MidiDevice: " << mixxxControl.getControlObjectGroup() << mixxxControl.getControlObjectValue(); 00235 00236 ConfigKey configKey(mixxxControl.getControlObjectGroup(), mixxxControl.getControlObjectValue()); 00237 00238 MidiOption currMidiOption = mixxxControl.getMidiOption(); 00239 00240 #ifdef __MIDISCRIPT__ 00241 // Custom MixxxScript (QtScript) handler 00242 00243 if (currMidiOption == MIDI_OPT_SCRIPT) { 00244 // qDebug() << "MidiDevice: Calling script function" << configKey.item << "with" 00245 // << (int)channel << (int)control << (int)value << (int)status; 00246 00247 //Unlock the mutex here to prevent a deadlock if a script needs to send a MIDI message 00248 //to the device. (sendShortMessage() would try to lock m_mutex...) 00249 locker.unlock(); 00250 00251 // This needs to be a signal because the MIDI Script Engine thread must execute 00252 // script functions, not this MidiDevice one 00253 emit(callMidiScriptFunction(configKey.item, channel, control, value, status, 00254 mixxxControl.getControlObjectGroup())); 00255 return; 00256 } 00257 #endif 00258 00259 ControlObject * p = ControlObject::getControl(configKey); 00260 00261 if (p) //Only pass values on to valid ControlObjects. 00262 { 00263 double currMixxxControlValue = p->GetMidiValue(); 00264 00265 double newValue = value; 00266 00267 // compute LSB and MSB for pitch bend messages 00268 if (status == MIDI_STATUS_PITCH_BEND) { 00269 unsigned int ivalue; 00270 ivalue = (value << 7) + control; 00271 00272 newValue = m_pMidiMapping->ComputeValue(currMidiOption, currMixxxControlValue, ivalue); 00273 00274 // normalize our value to 0-127 00275 newValue = (newValue / 0x3FFF) * 0x7F; 00276 } else if (currMidiOption != MIDI_OPT_SOFT_TAKEOVER) { 00277 newValue = m_pMidiMapping->ComputeValue(currMidiOption, currMixxxControlValue, value); 00278 } 00279 00280 // ControlPushButton ControlObjects only accept NOTE_ON, so if the midi 00281 // mapping is <button> we override the Midi 'status' appropriately. 00282 switch (currMidiOption) { 00283 case MIDI_OPT_BUTTON: 00284 case MIDI_OPT_SWITCH: status = MIDI_STATUS_NOTE_ON; break; // Buttons and Switches are 00285 // treated the same, except 00286 // that their values are 00287 // computed differently. 00288 default: break; 00289 } 00290 00291 // Soft-takeover is processed in addition to any other options 00292 if (currMidiOption == MIDI_OPT_SOFT_TAKEOVER) { 00293 m_st.enable(mixxxControl); // This is the only place to enable it if it isn't already. 00294 if (m_st.ignore(mixxxControl,newValue,true)) return; 00295 } 00296 00297 ControlObject::sync(); 00298 00299 //Super dangerous cast here... Should be fine once MidiCategory is replaced with MidiStatusByte permanently. 00300 p->queueFromMidi((MidiCategory)status, newValue); 00301 } 00302 00303 return; 00304 } 00305 00306 #ifdef __MIDISCRIPT__ 00307 // SysEx reception requires scripting 00308 void MidiDevice::receive(const unsigned char data[], unsigned int length) { 00309 QMutexLocker locker(&m_mutex); //Lots of returns in this function. Keeps things simple. 00310 00311 QString message = m_strDeviceName+": ["; 00312 for(uint i=0; i<length; i++) { 00313 message += QString("%1%2") 00314 .arg(data[i], 2, 16, QChar('0')).toUpper() 00315 .arg((i<(length-1))?' ':']'); 00316 } 00317 00318 if (midiDebugging()) qDebug()<< message; 00319 00320 MidiMessage inputCommand((MidiStatusByte)data[0]); 00321 00322 //If the receive inhibit flag is true, then we don't process any midi messages 00323 //that are received from the device. This is done in order to prevent a race 00324 //condition where the MidiMapping is accessed via isMidiMessageMapped() below 00325 //but it is already locked because it is being modified by the GUI thread. 00326 //(This happens when you hit apply in the preferences and then quickly push 00327 // a button on your controller.) 00328 if (m_bReceiveInhibit) 00329 return; 00330 00331 if (m_bMidiLearn) 00332 return; // Don't process custom midi messages when MIDI learning 00333 00334 QMutexLocker mappingLocker(&m_mappingPtrMutex); 00335 00336 MixxxControl mixxxControl = m_pMidiMapping->getInputMixxxControl(inputCommand); 00337 //qDebug() << "MidiDevice: " << mixxxControl.getControlObjectGroup() << mixxxControl.getControlObjectValue(); 00338 00339 ConfigKey configKey(mixxxControl.getControlObjectGroup(), mixxxControl.getControlObjectValue()); 00340 00341 // Custom MixxxScript (QtScript) handler 00342 00343 if (mixxxControl.getMidiOption() == MIDI_OPT_SCRIPT) { 00344 // qDebug() << "MidiDevice: Calling script function" << configKey.item << "with" 00345 // << (int)channel << (int)control << (int)value << (int)status; 00346 00347 //Unlock the mutex here to prevent a deadlock if a script needs to send a MIDI message 00348 //to the device. (sendShortMessage() would try to lock m_mutex...) 00349 locker.unlock(); 00350 00351 if (!m_pMidiMapping->getMidiScriptEngine()->execute(configKey.item, data, length)) { 00352 qDebug() << "MidiDevice: Invalid script function" << configKey.item; 00353 } 00354 return; 00355 } 00356 qWarning() << "MidiDevice: No MIDI Script function found for" << message; 00357 return; 00358 } 00359 #endif 00360 00361 bool MidiDevice::midiDebugging() { 00362 // Assumes a lock is already held. :/ 00363 return m_midiDebug; 00364 } 00365 00366 void MidiDevice::setReceiveInhibit(bool inhibit) 00367 { 00368 //See comments for m_bReceiveInhibit. 00369 QMutexLocker locker(&m_mutex); 00370 m_bReceiveInhibit = inhibit; 00371 }