Mixxx

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

Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines