1
/***************************************************************************
2
midimapping.cpp - "Wow, I wrote more new code!"
5
begin : Sat Jan 17 2009
6
copyright : (C) 2009 Sean M. Pappalardo
7
(C) 2009 Albert Santoni
8
email : pegasus@c64.org
10
***************************************************************************/
12
/***************************************************************************
14
* This program is free software; you can redistribute it and/or modify *
15
* it under the terms of the GNU General Public License as published by *
16
* the Free Software Foundation; either version 2 of the License, or *
17
* (at your option) any later version. *
19
***************************************************************************/
21
#include <qapplication.h>
22
#include "defs_version.h"
23
#include "widget/wwidget.h" // FIXME: This should be xmlparse.h
24
#include "mixxxcontrol.h"
25
#include "midimessage.h"
27
#include "midiinputmappingtablemodel.h"
28
#include "midioutputmappingtablemodel.h"
29
#include "midimapping.h"
30
#include "mididevicedummy.h"
31
#include "midiledhandler.h"
32
#include "configobject.h"
33
#include "errordialoghandler.h"
35
#define REQUIRED_SCRIPT_FILE "midi-mappings-scripts.js"
36
#define XML_SCHEMA_VERSION "1"
37
#define DEFAULT_DEVICE_PRESET BINDINGS_PATH.append(m_deviceName.right(m_deviceName.size()-m_deviceName.indexOf(" ")-1).replace(" ", "_") + MIDI_MAPPING_EXTENSION)
39
// static QString toHex(QString numberStr) {
40
// return "0x" + QString("0" + QString::number(numberStr.toUShort(), 16).toUpper()).right(2);
43
MidiMapping::MidiMapping(MidiDevice* outputMidiDevice)
45
m_mappingLock(QMutex::Recursive) {
46
// If BINDINGS_PATH doesn't exist, create it
47
if (!QDir(BINDINGS_PATH).exists()) {
48
qDebug() << "Creating new MIDI mapping directory" << BINDINGS_PATH;
49
QDir().mkpath(BINDINGS_PATH);
51
// Likewise, if LPRESETS_PATH doesn't exist, create that too
52
if (!QDir(LPRESETS_PATH).exists()) {
53
qDebug() << "Creating new MIDI presets directory" <<LPRESETS_PATH;
54
QDir().mkpath(LPRESETS_PATH);
56
// So we can signal the MidiScriptEngine and pass a QList
57
qRegisterMetaType<QList<QString> >("QList<QString>");
59
//Q_ASSERT(outputMidiDevice);
62
//Start the scripting engine.
63
m_pScriptEngine = NULL;
64
m_pOutputMidiDevice = outputMidiDevice;
65
if (m_pOutputMidiDevice)
66
m_deviceName = m_pOutputMidiDevice->getName(); //Name of the device to look for the <controller> block for in the XML.
68
startupScriptEngine();
70
m_pMidiInputMappingTableModel = new MidiInputMappingTableModel(this);
71
m_pMidiOutputMappingTableModel = new MidiOutputMappingTableModel(this);
74
MidiMapping::~MidiMapping() {
75
delete m_pMidiInputMappingTableModel;
76
delete m_pMidiOutputMappingTableModel;
80
void MidiMapping::startupScriptEngine() {
81
QMutexLocker Locker(&m_mappingLock);
83
if(m_pScriptEngine) return;
85
//XXX FIXME: Deadly hack attack:
86
if (m_pOutputMidiDevice == NULL) {
87
m_pOutputMidiDevice = new MidiDeviceDummy(this); //Just make some dummy device :(
88
/* Why can't this be the same as the input MIDI device? Most of the time,
89
the script engine thinks of it as the input device. Scripting is useful
90
even if the MIDI device has no outputs - Sean 4/19/10 */
94
qDebug () << "Starting script engine with output device" << m_pOutputMidiDevice->getName();
96
m_pScriptEngine = new MidiScriptEngine(m_pOutputMidiDevice);
98
m_pScriptEngine->moveToThread(m_pScriptEngine);
100
// Let the script engine tell us when it's done with each task
101
connect(m_pScriptEngine, SIGNAL(initialized()),
102
this, SLOT(slotScriptEngineReady()),
103
Qt::DirectConnection);
104
m_scriptEngineInitializedMutex.lock();
105
m_pScriptEngine->start();
106
// Wait until the script engine is initialized
107
m_scriptEngineInitializedCondition.wait(&m_scriptEngineInitializedMutex);
108
m_scriptEngineInitializedMutex.unlock();
110
// Allow this object to signal the MidiScriptEngine to load the list of script files
111
connect(this, SIGNAL(loadMidiScriptFiles(QList<QString>)), m_pScriptEngine, SLOT(loadScriptFiles(QList<QString>)));
112
// Allow this object to signal the MidiScriptEngine to run the initialization
113
// functions in the loaded scripts
114
connect(this, SIGNAL(initMidiScripts(QList<QString>)), m_pScriptEngine, SLOT(initializeScripts(QList<QString>)));
115
// Allow this object to signal the MidiScriptEngine to run its shutdown routines
116
connect(this, SIGNAL(shutdownMidiScriptEngine(QList<QString>)), m_pScriptEngine, SLOT(gracefulShutdown(QList<QString>)));
118
// Allow this object to signal the MidiScriptEngine to call functions
119
connect(this, SIGNAL(callMidiScriptFunction(QString)),
120
m_pScriptEngine, SLOT(execute(QString)));
121
connect(this, SIGNAL(callMidiScriptFunction(QString, QString)),
122
m_pScriptEngine, SLOT(execute(QString, QString)));
124
// Allow the MidiScriptEngine to tell us if it needs to reset the controller (on errors)
125
connect(m_pScriptEngine, SIGNAL(resetController()), this, SLOT(reset()));
128
void MidiMapping::loadScriptCode() {
129
QMutexLocker Locker(&m_mappingLock);
130
if(m_pScriptEngine) {
131
m_scriptEngineInitializedMutex.lock();
132
// Tell the script engine to run the init function in all loaded scripts
133
emit(loadMidiScriptFiles(m_scriptFileNames));
134
// Wait until it's done
135
m_scriptEngineInitializedCondition.wait(&m_scriptEngineInitializedMutex);
136
m_scriptEngineInitializedMutex.unlock();
140
void MidiMapping::initializeScripts() {
141
QMutexLocker Locker(&m_mappingLock);
142
if(m_pScriptEngine) {
143
m_scriptEngineInitializedMutex.lock();
144
// Tell the script engine to run the init function in all loaded scripts
145
emit(initMidiScripts(m_scriptFunctionPrefixes));
146
// Wait until it's done
147
m_scriptEngineInitializedCondition.wait(&m_scriptEngineInitializedMutex);
148
m_scriptEngineInitializedMutex.unlock();
152
void MidiMapping::shutdownScriptEngine() {
153
QMutexLocker Locker(&m_mappingLock);
154
if(m_pScriptEngine) {
155
// Tell the script engine to do its shutdown sequence
156
emit(shutdownMidiScriptEngine(m_scriptFunctionPrefixes));
157
// ...and wait for it to finish
158
m_pScriptEngine->wait();
160
MidiScriptEngine *engine = m_pScriptEngine;
161
m_pScriptEngine = NULL;
167
void MidiMapping::setOutputMidiDevice(MidiDevice* outputMidiDevice)
169
m_mappingLock.lock();
170
m_pOutputMidiDevice = outputMidiDevice;
171
m_mappingLock.unlock();
172
#ifdef __MIDISCRIPT__
173
//Restart the script engine so it gets its pointer to the output MIDI device updated.
174
restartScriptEngine();
178
/* ============================== MIDI Input Mapping Modifiers
179
(Part of QT MVC wrapper)
183
* Return the total number of current input mappings.
185
int MidiMapping::numInputMidiMessages() {
186
m_mappingLock.lock();
187
int value = internalNumInputMidiMessages();
188
m_mappingLock.unlock();
191
int MidiMapping::internalNumInputMidiMessages() {
192
return m_inputMapping.size();;
196
* Return true if the index corresponds to an input mapping key.
198
bool MidiMapping::isInputIndexValid(int index) {
199
if(index < 0 || index >= numInputMidiMessages()) {
205
bool MidiMapping::internalIsInputIndexValid(int index) {
206
if(index < 0 || index >= internalNumInputMidiMessages()) {
212
bool MidiMapping::isMidiMessageMapped(MidiMessage command) {
213
m_mappingLock.lock();
214
bool value = m_inputMapping.contains(command);
215
m_mappingLock.unlock();
220
* Lookup the MidiMessage corresponding to a given index.
222
MidiMessage MidiMapping::getInputMidiMessage(int index) {
223
m_mappingLock.lock();
225
if (internalIsInputIndexValid(index)) {
226
message = m_inputMapping.keys().at(index);
228
m_mappingLock.unlock();
233
* Lookup the MixxxControl mapped to a given MidiMessage (by index).
235
MixxxControl MidiMapping::getInputMixxxControl(int index) {
236
m_mappingLock.lock();
237
MixxxControl control;
238
if (internalIsInputIndexValid(index)) {
239
MidiMessage key = m_inputMapping.keys().at(index);
240
control = m_inputMapping.value(key);
242
m_mappingLock.unlock();
247
* Lookup the MixxxControl mapped to a given MidiMessage.
249
MixxxControl MidiMapping::getInputMixxxControl(MidiMessage command) {
250
m_mappingLock.lock();
251
MixxxControl control;
252
if (m_inputMapping.contains(command)) {
253
control = m_inputMapping.value(command);
255
m_mappingLock.unlock();
260
* Set a MidiMessage -> MixxxControl mapping, replacing an existing one
263
void MidiMapping::setInputMidiMapping(MidiMessage command, MixxxControl control) {
264
m_mappingLock.lock();
265
internalSetInputMidiMapping(command, control, false);
266
m_mappingLock.unlock();
267
emit(inputMappingChanged());
270
void MidiMapping::internalSetInputMidiMapping(MidiMessage command, MixxxControl control, bool shouldEmit) {
271
// If the command is already in the mapping, it will be replaced
272
m_inputMapping.insert(command,control);
274
emit(inputMappingChanged());
279
* Clear a specific mapping for a MidiMessage by index.
281
void MidiMapping::clearInputMidiMapping(int index) {
283
m_mappingLock.lock();
284
if (internalIsInputIndexValid(index)) {
285
MidiMessage key = m_inputMapping.keys().at(index);
286
m_inputMapping.remove(key);
289
m_mappingLock.unlock();
292
emit(inputMappingChanged());
296
* Clear a specific mapping for a MidiMessage.
298
void MidiMapping::clearInputMidiMapping(MidiMessage command) {
299
m_mappingLock.lock();
300
int changed = m_inputMapping.remove(command);
301
m_mappingLock.unlock();
304
emit(inputMappingChanged());
308
* Clears a range of input mappings. (This really only exists so that
309
* a caller can atomically remove a range of rows)
312
void MidiMapping::clearInputMidiMapping(int index, int count) {
313
m_mappingLock.lock();
314
QList<MidiMessage> keys = m_inputMapping.keys();
316
for(int i=index; i < index+count; i++) {
317
MidiMessage command = keys.at(i);
318
changed += m_inputMapping.remove(command);
320
m_mappingLock.unlock();
322
emit(inputMappingChanged());
325
/*==== End of MIDI input mapping modifiers (part of QT MVC wrapper) */
329
/* ============================== MIDI ***Output*** Mapping Modifiers
330
(Part of QT MVC wrapper)
334
* Return the total number of current output mappings.
336
int MidiMapping::numOutputMixxxControls() {
337
m_mappingLock.lock();
338
int value = internalNumOutputMixxxControls();
339
m_mappingLock.unlock();
343
int MidiMapping::internalNumOutputMixxxControls() {
344
return m_outputMapping.size();
349
* Return true if the index corresponds to an input mapping key.
351
bool MidiMapping::isOutputIndexValid(int index) {
352
m_mappingLock.lock();
353
bool result = internalIsOutputIndexValid(index);
354
m_mappingLock.unlock();
358
bool MidiMapping::internalIsOutputIndexValid(int index) {
359
if(index < 0 || index >= internalNumOutputMixxxControls()) {
366
bool MidiMapping::isMixxxControlMapped(MixxxControl control) {
367
m_mappingLock.lock();
368
bool result = m_outputMapping.contains(control);
369
m_mappingLock.unlock();
374
* Lookup the MidiMessage corresponding to a given index.
376
MixxxControl MidiMapping::getOutputMixxxControl(int index) {
377
m_mappingLock.lock();
378
MixxxControl control;
379
if (!internalIsOutputIndexValid(index)) {
380
control = m_outputMapping.keys().at(index);
382
m_mappingLock.unlock();
387
* Lookup the MixxxControl mapped to a given MidiMessage (by index).
389
MidiMessage MidiMapping::getOutputMidiMessage(int index) {
390
qDebug() << "getOutputMidiMessage" << index;
391
m_mappingLock.lock();
393
if (internalIsOutputIndexValid(index)) {
394
MixxxControl key = m_outputMapping.keys().at(index);
395
message = m_outputMapping.value(key);
397
m_mappingLock.unlock();
402
* Lookup the MixxxControl mapped to a given MidiMessage.
404
MidiMessage MidiMapping::getOutputMidiMessage(MixxxControl control) {
405
m_mappingLock.lock();
407
if (m_outputMapping.contains(control)) {
408
message = m_outputMapping.value(control);
410
m_mappingLock.unlock();
415
* Set a MidiMessage -> MixxxControl mapping, replacing an existing one
418
void MidiMapping::setOutputMidiMapping(MixxxControl control, MidiMessage command) {
419
m_mappingLock.lock();
420
internalSetOutputMidiMapping(control, command, false);
421
m_mappingLock.unlock();
422
emit(outputMappingChanged());
425
void MidiMapping::internalSetOutputMidiMapping(MixxxControl control,
428
// If the command is already in the mapping, it will be replaced
429
m_outputMapping.insert(control, command);
432
emit(outputMappingChanged());
436
* Clear a specific mapping for a MidiMessage by index.
438
void MidiMapping::clearOutputMidiMapping(int index) {
439
m_mappingLock.lock();
440
bool changed = false;
441
if (internalIsOutputIndexValid(index)) {
442
qDebug() << m_outputMapping.size();
443
qDebug() << "MidiMapping: removing" << index;
444
MixxxControl key = m_outputMapping.keys().at(index);
445
m_outputMapping.remove(key);
446
qDebug() << m_outputMapping.size();
449
m_mappingLock.unlock();
452
emit(outputMappingChanged());
456
* Clear a specific mapping for a MidiMessage.
458
void MidiMapping::clearOutputMidiMapping(MixxxControl control) {
459
m_mappingLock.lock();
460
int changed = m_outputMapping.remove(control);
461
m_mappingLock.unlock();
464
emit(outputMappingChanged());
468
* Clears a range of input mappings. (This really only exists so that
469
* a caller can atomically remove a range of rows)
472
void MidiMapping::clearOutputMidiMapping(int index, int count) {
473
m_mappingLock.lock();
474
QList<MixxxControl> keys = m_outputMapping.keys();
476
for(int i=index; i < index+count; i++) {
477
MixxxControl control = keys.at(i);
478
changed += m_outputMapping.remove(control);
480
m_mappingLock.unlock();
483
emit(outputMappingChanged());
486
/*==== End of MIDI output mapping modifiers (part of QT MVC wrapper) */
489
#ifdef __MIDISCRIPT__
490
/* -------- ------------------------------------------------------
491
Purpose: Adds an entry to the list of script file names
492
& associated list of function prefixes
493
Input: QString file name, QString function prefix
495
-------- ------------------------------------------------------ */
496
void MidiMapping::addScriptFile(QString filename, QString functionprefix) {
497
QMutexLocker Locker(&m_mappingLock);
498
m_scriptFileNames.append(filename);
499
m_scriptFunctionPrefixes.append(functionprefix);
504
* Sets the controller name this mapping corresponds to
505
* @param name The controller name this mapping is hooked to
507
void MidiMapping::setName(QString name) {
508
QMutexLocker Locker(&m_mappingLock);
513
* Overloaded function for convenience, uses the default device path
514
* @param forceLoad Forces the MIDI mapping to be loaded, regardless of whether or not the controller id
515
* specified in the mapping matches the device this MidiMapping object is hooked up to.
517
void MidiMapping::loadPreset(bool forceLoad) {
518
loadPreset(DEFAULT_DEVICE_PRESET, forceLoad);
521
/* loadPreset(QString)
522
* Overloaded function for convenience
523
* @param path The path to a MIDI mapping XML file.
524
* @param forceLoad Forces the MIDI mapping to be loaded, regardless of whether or not the controller id
525
* specified in the mapping matches the device this MidiMapping object is hooked up to.
527
void MidiMapping::loadPreset(QString path, bool forceLoad) {
528
qDebug() << "MidiMapping: Loading MIDI preset from" << path;
529
loadPreset(WWidget::openXMLFile(path, "controller"), forceLoad);
532
/* loadPreset(QDomElement)
533
* Loads a set of MIDI bindings from a QDomElement structure.
534
* @param root The root node of the XML document for the MIDI mapping.
535
* @param forceLoad Forces the MIDI mapping to be loaded, regardless of whether or not the controller id
536
* specified in the mapping matches the device this MidiMapping object is hooked up to.
538
void MidiMapping::loadPreset(QDomElement root, bool forceLoad) {
539
//qDebug() << QString("MidiMapping: loadPreset() called in thread ID=%1").arg(this->thread()->currentThreadId(),0,16);
541
if (root.isNull()) return;
543
m_pMidiInputMappingTableModel->removeRows(0, m_pMidiInputMappingTableModel->rowCount());
544
m_pMidiOutputMappingTableModel->removeRows(0, m_pMidiOutputMappingTableModel->rowCount());
546
// Note, the lock comes after these two lines. We mustn't touch the
547
// *MappingTableModel's after we are locked because they have pointers to us
548
// so when we make a call to them they might in turn call us, causing a
550
m_mappingLock.lock();
552
#ifdef __MIDISCRIPT__
553
m_scriptFileNames.clear();
554
m_scriptFunctionPrefixes.clear();
557
// For each controller in the DOM
559
QDomElement controller = m_Bindings.firstChildElement("controller");
561
// For each controller in the MIDI mapping XML...
562
//(Only parse the <controller> block if it's id matches our device name, otherwise
563
//keep looking at the next controller blocks....)
565
while (!controller.isNull()) {
567
device = controller.attribute("id","");
568
if (device != m_deviceName && !forceLoad) {
569
controller = controller.nextSiblingElement("controller");
575
if (!controller.isNull()) {
577
qDebug() << device << " settings found";
578
#ifdef __MIDISCRIPT__
579
// Build a list of MIDI script files to load
581
QDomElement scriptFile = controller.firstChildElement("scriptfiles").firstChildElement("file");
583
// Default currently required file
584
addScriptFile(REQUIRED_SCRIPT_FILE,"");
586
// Look for additional ones
587
while (!scriptFile.isNull()) {
588
QString functionPrefix = scriptFile.attribute("functionprefix","");
589
QString filename = scriptFile.attribute("filename","");
590
addScriptFile(filename, functionPrefix);
592
scriptFile = scriptFile.nextSiblingElement("file");
595
loadScriptCode(); // Actually load code from the list built above
597
QStringList scriptFunctions;
598
if (m_pScriptEngine != NULL) {
599
scriptFunctions = m_pScriptEngine->getScriptFunctions();
604
QDomElement control = controller.firstChildElement("controls").firstChildElement("control");
606
//Iterate through each <control> block in the XML
607
while (!control.isNull()) {
609
//Unserialize these objects from the XML
610
MidiMessage midiMessage(control);
611
MixxxControl mixxxControl(control);
612
#ifdef __MIDISCRIPT__
613
// Verify script functions are loaded
614
if (mixxxControl.getMidiOption()==MIDI_OPT_SCRIPT &&
615
scriptFunctions.indexOf(mixxxControl.getControlObjectValue())==-1) {
617
QString status = QString("%1").arg(midiMessage.getMidiStatusByte(), 0, 16).toUpper();
618
status = "0x"+status;
619
QString byte2 = QString("%1").arg(midiMessage.getMidiNo(), 0, 16).toUpper();
622
// If status is MIDI pitch, the 2nd byte is part of the payload so don't display it
623
if (midiMessage.getMidiStatusByte() == 0xE0) byte2 = "";
625
QString errorLog = QString("MIDI script function \"%1\" not found. "
626
"(Mapped to MIDI message %2 %3)")
627
.arg(mixxxControl.getControlObjectValue(), status, byte2);
629
if (m_pOutputMidiDevice != NULL
630
&& m_pOutputMidiDevice->midiDebugging()) {
631
qCritical() << errorLog;
634
qWarning() << errorLog;
635
ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
636
props->setType(DLG_WARNING);
637
props->setTitle(tr("MIDI script function not found"));
638
props->setText(QString(tr("The MIDI script function '%1' was not "
639
"found in loaded scripts."))
640
.arg(mixxxControl.getControlObjectValue()));
641
props->setInfoText(QString(tr("The MIDI message %1 %2 will not be bound."
642
"\n(Click Show Details for hints.)"))
643
.arg(status, byte2));
644
QString detailsText = QString(tr("* Check to see that the "
645
"function name is spelled correctly in the mapping "
646
"file (.xml) and script file (.js)\n"));
647
detailsText += QString(tr("* Check to see that the script "
648
"file name (.js) is spelled correctly in the mapping "
650
props->setDetails(detailsText);
652
ErrorDialogHandler::instance()->requestErrorDialog(props);
656
//Add to the input mapping.
657
internalSetInputMidiMapping(midiMessage, mixxxControl, true);
658
/*Old code: m_inputMapping.insert(midiMessage, mixxxControl);
659
Reason why this is bad: Don't want to access this directly because the
660
model doesn't get notified about the update */
661
#ifdef __MIDISCRIPT__
664
control = control.nextSiblingElement("control");
667
qDebug() << "MidiMapping: Input parsed!";
669
QDomElement output = controller.firstChildElement("outputs").firstChildElement("output");
671
//Iterate through each <control> block in the XML
672
while (!output.isNull()) {
673
//Unserialize these objects from the XML
674
MidiMessage midiMessage(output);
675
MixxxControl mixxxControl(output, true);
677
//Add to the output mapping.
678
internalSetOutputMidiMapping(mixxxControl, midiMessage, true);
679
/*Old code: m_outputMapping.insert(mixxxControl, midiMessage);
680
Reason why this is bad: Don't want to access this directly because the
681
model doesn't get notified about the update */
683
output = output.nextSiblingElement("output");
686
qDebug() << "MidiMapping: Output parsed!";
687
//controller = controller.nextSiblingElement("controller"); //FIXME: Remove this line of code permanently - Albert
690
m_mappingLock.unlock();
692
} // END loadPreset(QDomElement)
695
* Saves the current table of bindings to the default device XML file.
697
void MidiMapping::savePreset() {
698
savePreset(DEFAULT_DEVICE_PRESET);
701
/* savePreset(QString)
702
* Given a path, saves the current table of bindings to an XML file.
704
void MidiMapping::savePreset(QString path) {
705
qDebug() << "Writing MIDI preset file" << path;
706
QMutexLocker locker(&m_mappingLock);
708
if (!output.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;
709
QTextStream outputstream(&output);
710
// Construct the DOM from the table
711
QDomDocument docBindings = buildDomElement();
712
// Save the DOM to the XML file
713
docBindings.save(outputstream, 4);
718
* Load the current bindings set into the MIDI handler, and the outputs info into
721
void MidiMapping::applyPreset() {
722
qDebug() << "MidiMapping::applyPreset()";
723
QMutexLocker locker(&m_mappingLock);
725
#ifdef __MIDISCRIPT__
726
// Since this can be called after re-enabling a device without reloading the XML preset,
727
// the script engine must have its code loaded here as well
728
QStringList scriptFunctions;
729
if (m_pScriptEngine != NULL) {
730
scriptFunctions = m_pScriptEngine->getScriptFunctions();
732
if (scriptFunctions.isEmpty()) loadScriptCode();
737
if (m_pOutputMidiDevice != NULL) {
738
//^^^ Only execute this code if we have an output device hooked up
739
// to this MidiMapping...
741
QDomElement controller = m_Bindings.firstChildElement("controller");
743
while (!controller.isNull()) {
744
// Device Outputs - LEDs
745
QString deviceId = controller.attribute("id","");
747
qDebug() << "MidiMapping: Processing MIDI Output Bindings for" << deviceId;
748
MidiLedHandler::createHandlers(controller.namedItem("outputs").firstChild(),
749
*m_pOutputMidiDevice);
752
controller = controller.nextSiblingElement("controller");
754
MidiLedHandler::updateAll();
759
* Creates a blank bindings preset.
761
void MidiMapping::clearPreset() {
762
// Assumes the lock is held.
763
// Create a new blank DomNode
764
QString blank = "<MixxxMIDIPreset schemaVersion=\"" + QString(XML_SCHEMA_VERSION) + "\" mixxxVersion=\"" + QString(VERSION) + "+\">\n"
765
"</MixxxMIDIPreset>\n";
766
QDomDocument doc("Bindings");
767
doc.setContent(blank);
768
m_Bindings = doc.documentElement();
772
* Updates the DOM with what is currently in the table
774
QDomDocument MidiMapping::buildDomElement() {
775
// We should hold the mapping lock. The lock is recursive so if we already
776
// hold it it will relock.
777
QMutexLocker locker(&m_mappingLock);
779
clearPreset(); // Create blank document
781
QDomDocument doc("Bindings");
782
QString blank = "<MixxxMIDIPreset schemaVersion=\"" + QString(XML_SCHEMA_VERSION) + "\" mixxxVersion=\"" + QString(VERSION) + "+\">\n"
783
"</MixxxMIDIPreset>\n";
785
doc.setContent(blank);
787
QDomElement rootNode = doc.documentElement();
788
QDomElement controller = doc.createElement("controller");
789
controller.setAttribute("id", m_deviceName.right(m_deviceName.size()-m_deviceName.indexOf(" ")-1));
790
rootNode.appendChild(controller);
792
#ifdef __MIDISCRIPT__
793
//This sucks, put this code inside MidiScriptEngine instead of here,
794
// and just ask MidiScriptEngine to spit it out for us.
795
// qDebug() << "MidiMapping: Writing script block!";
797
QDomElement scriptFiles = doc.createElement("scriptfiles");
798
controller.appendChild(scriptFiles);
801
for (int i = 0; i < m_scriptFileNames.count(); i++) {
802
// qDebug() << "MidiMapping: writing script block for" << m_scriptFileNames[i];
803
QString filename = m_scriptFileNames[i];
806
//Don't need to write anything for the required mapping file.
807
if (filename != REQUIRED_SCRIPT_FILE) {
808
qDebug() << "MidiMapping: writing script block for" << filename;
809
QString functionPrefix = m_scriptFunctionPrefixes[i];
810
QDomElement scriptFile = doc.createElement("file");
813
scriptFile.setAttribute("filename", filename);
814
scriptFile.setAttribute("functionprefix", functionPrefix);
816
scriptFiles.appendChild(scriptFile);
821
QDomElement controls = doc.createElement("controls");
822
controller.appendChild(controls);
825
//Iterate over all of the command/control pairs in the input mapping
826
QHashIterator<MidiMessage, MixxxControl> it(m_inputMapping);
827
while (it.hasNext()) {
830
QDomElement controlNode = doc.createElement("control");
832
//Save the MidiMessage and MixxxControl objects as XML
833
it.key().serializeToXML(controlNode);
834
it.value().serializeToXML(controlNode);
836
//Add the control node we just created to the XML document in the proper spot
837
controls.appendChild(controlNode);
841
QDomElement outputs = doc.createElement("outputs");
842
controller.appendChild(outputs);
844
//Iterate over all of the control/command pairs in the OUTPUT mapping
845
QHashIterator<MixxxControl, MidiMessage> outIt(m_outputMapping);
846
while (outIt.hasNext()) {
849
QDomElement outputNode = doc.createElement("output");
852
//Save the MidiMessage and MixxxControl objects as XML
853
outIt.key().serializeToXML(outputNode, true);
854
outIt.value().serializeToXML(outputNode, true);
856
//Add the control node we just created to the XML document in the proper spot
857
outputs.appendChild(outputNode);
860
m_Bindings = doc.documentElement();
864
/* -------- ------------------------------------------------------
865
Purpose: Adds an input MIDI mapping block to the XML.
866
Input: QDomElement control, QString device
868
-------- ------------------------------------------------------ */
869
void MidiMapping::addControl(QDomElement &control, QString device) {
870
QDomDocument nodeMaker;
871
//Add control to correct device tag - find the correct tag
872
QDomElement controller = m_Bindings.firstChildElement("controller");
873
while (controller.attribute("id","") != device && !controller.isNull()) {
874
controller = controller.nextSiblingElement("controller");
876
if (controller.isNull()) {
877
// No tag was found - create it
878
controller = nodeMaker.createElement("controller");
879
controller.setAttribute("id", device);
880
m_Bindings.appendChild(controller);
882
// Check for controls tag
883
QDomElement controls = controller.firstChildElement("controls");
884
if (controls.isNull()) {
885
controls = nodeMaker.createElement("controls");
886
controller.appendChild(controls);
888
controls.appendChild(control);
891
/* -------- ------------------------------------------------------
892
Purpose: This code sucks, temporary hack
893
Input: QDomElement control, QString device
895
-------- ------------------------------------------------------ */
896
void MidiMapping::addMidiScriptInfo(QDomElement &scriptFile, QString device) {
897
QDomDocument nodeMaker;
898
//Add control to correct device tag - find the correct tag
899
QDomElement controller = m_Bindings.firstChildElement("controller");
900
while (controller.attribute("id","") != device && !controller.isNull()) {
901
controller = controller.nextSiblingElement("controller");
903
if (controller.isNull()) {
904
// No tag was found - create it
905
controller = nodeMaker.createElement("controller");
906
controller.setAttribute("id", device);
907
m_Bindings.appendChild(controller);
909
// Check for controls tag
910
QDomElement scriptfiles = controller.firstChildElement("scriptfiles");
911
if (scriptfiles.isNull()) {
912
scriptfiles = nodeMaker.createElement("scriptfiles");
913
controller.appendChild(scriptfiles);
915
scriptfiles.appendChild(scriptFile);
918
/* -------- ------------------------------------------------------
919
Purpose: Adds an output (LED, etc.) MIDI mapping block to the XML.
920
Input: QDomElement output, QString device
922
-------- ------------------------------------------------------ */
923
void MidiMapping::addOutput(QDomElement &output, QString device) {
924
QDomDocument nodeMaker;
925
// Find the controller to attach the XML to...
926
QDomElement controller = m_Bindings.firstChildElement("controller");
927
while (controller.attribute("id","") != device && !controller.isNull()) {
928
controller = controller.nextSiblingElement("controller");
930
if (controller.isNull()) {
931
// No tag was found - create it
932
controller = nodeMaker.createElement("controller");
933
controller.setAttribute("id", device);
934
m_Bindings.appendChild(controller);
937
// Find the outputs block
938
QDomElement outputs = controller.firstChildElement("outputs");
939
if (outputs.isNull()) {
940
outputs = nodeMaker.createElement("outputs");
941
controller.appendChild(outputs);
943
// attach the output to the outputs block
944
outputs.appendChild(output);
947
bool MidiMapping::addInputControl(MidiStatusByte midiStatus, int midiNo, int midiChannel,
948
QString controlObjectGroup, QString controlObjectKey,
949
QString controlObjectDescription, MidiOption midiOption)
951
return addInputControl(MidiMessage(midiStatus, midiNo, midiChannel),
952
MixxxControl(controlObjectGroup, controlObjectKey,
953
controlObjectDescription, midiOption));
956
bool MidiMapping::addInputControl(MidiMessage message, MixxxControl control)
958
//TODO: Check if mapping already exists for this MidiMessage.
960
//Add to the input mapping.
961
m_inputMapping.insert(message, control);
962
return true; //XXX is this right? should this be returning whether the add happened successfully?
966
void MidiMapping::removeInputMapping(MidiStatusByte midiStatus, int midiNo, int midiChannel)
968
m_inputMapping.remove(MidiMessage(midiStatus, midiNo, midiChannel));
971
MidiInputMappingTableModel* MidiMapping::getMidiInputMappingTableModel()
973
return m_pMidiInputMappingTableModel;
976
MidiOutputMappingTableModel* MidiMapping::getMidiOutputMappingTableModel()
978
return m_pMidiOutputMappingTableModel;
981
//Used by MidiObject to query what control matches a given MIDI command.
982
/*MixxxControl* MidiMapping::getInputMixxxControl(MidiMessage command)
984
if (!m_inputMapping.contains(command)) {
985
qWarning() << "Unbound MIDI command";
986
qDebug() << "Midi Status:" << command.getMidiStatusByte();
987
qDebug() << "Midi No:" << command.getMidiNo();
988
qDebug() << "Midi Channel:" << command.getMidiChannel();
992
MixxxControl* control = &(m_inputMapping[command]);
996
// BJW: Note: _prevmidivalue is not the previous MIDI value. It's the
997
// current controller value, scaled to 0-127 but only in the case of pots.
998
// (See Control*::GetMidiValue())
1001
double MidiMapping::ComputeValue(MidiOption midioption, double _prevmidivalue, double _newmidivalue)
1003
double tempval = 0.;
1006
// qDebug() << "ComputeValue: option " << midioption << ", MIDI value " << _newmidivalue << ", current control value " << _prevmidivalue;
1007
if (midioption == MIDI_OPT_NORMAL) {
1008
return _newmidivalue;
1010
else if (midioption == MIDI_OPT_INVERT)
1012
return 127. - _newmidivalue;
1014
else if (midioption == MIDI_OPT_ROT64 || midioption == MIDI_OPT_ROT64_INV)
1016
tempval = _prevmidivalue;
1017
diff = _newmidivalue - 64.;
1018
if (diff == -1 || diff == 1)
1021
diff += (diff > 0 ? -1 : +1);
1022
if (midioption == MIDI_OPT_ROT64)
1026
return (tempval < 0. ? 0. : (tempval > 127. ? 127.0 : tempval));
1028
else if (midioption == MIDI_OPT_ROT64_FAST)
1030
tempval = _prevmidivalue;
1031
diff = _newmidivalue - 64.;
1034
return (tempval < 0. ? 0. : (tempval > 127. ? 127.0 : tempval));
1036
else if (midioption == MIDI_OPT_DIFF)
1038
//Interpret 7-bit signed value using two's compliment.
1039
if (_newmidivalue >= 64.)
1040
_newmidivalue = _newmidivalue - 128.;
1041
//Apply sensitivity to signed value. FIXME
1042
// if(sensitivity > 0)
1043
// _newmidivalue = _newmidivalue * ((double)sensitivity / 50.);
1044
//Apply new value to current value.
1045
_newmidivalue = _prevmidivalue + _newmidivalue;
1047
else if (midioption == MIDI_OPT_SELECTKNOB)
1049
//Interpret 7-bit signed value using two's compliment.
1050
if (_newmidivalue >= 64.)
1051
_newmidivalue = _newmidivalue - 128.;
1052
//Apply sensitivity to signed value. FIXME
1053
//if(sensitivity > 0)
1054
// _newmidivalue = _newmidivalue * ((double)sensitivity / 50.);
1055
//Since this is a selection knob, we do not want to inherit previous values.
1057
else if (midioption == MIDI_OPT_BUTTON) { _newmidivalue = (_newmidivalue != 0); }
1058
else if (midioption == MIDI_OPT_SWITCH) { _newmidivalue = 1; }
1059
else if (midioption == MIDI_OPT_SPREAD64)
1062
//qDebug() << "MIDI_OPT_SPREAD64";
1063
// BJW: Spread64: Distance away from centre point (aka "relative CC")
1064
// Uses a similar non-linear scaling formula as ControlTTRotary::getValueFromWidget()
1065
// but with added sensitivity adjustment. This formula is still experimental.
1067
_newmidivalue = _newmidivalue - 64.;
1069
//double distance = _newmidivalue - 64.;
1070
// _newmidivalue = distance * distance * sensitivity / 50000.;
1071
//if (distance < 0.)
1072
// _newmidivalue = -_newmidivalue;
1074
//qDebug() << "Spread64: in " << distance << " out " << _newmidivalue;
1076
else if (midioption == MIDI_OPT_HERC_JOG)
1078
if (_newmidivalue > 64.) { _newmidivalue -= 128.; }
1079
_newmidivalue += _prevmidivalue;
1080
//if (_prevmidivalue != 0.0) { qDebug() << "AAAAAAAAAAAA" << _prevmidivalue; }
1084
qWarning("Unknown MIDI option %d", midioption);
1087
return _newmidivalue;
1090
void MidiMapping::finishMidiLearn(MidiMessage message)
1092
bool shouldEmit = false;
1093
m_mappingLock.lock();
1094
//We've received a MidiMessage that should be mapped onto some control. When
1095
//beginMidiLearn() was called, we were given the control that should be
1096
//mapped, so let's connect the message to the saved control and thus
1097
//"create" the mapping between the two.
1098
if (!m_controlToLearn.isNull()) { //Ensure we've actually been told to learn
1099
//a control. Note the ! out front.
1100
addInputControl(message, m_controlToLearn);
1102
//If we caught a NOTE_ON message, add a binding for NOTE_OFF as well.
1103
if (message.getMidiStatusByte() == MIDI_STATUS_NOTE_ON) {
1104
MidiMessage noteOffMessage(message);
1105
noteOffMessage.setMidiStatusByte(MIDI_STATUS_NOTE_OFF);
1106
addInputControl(noteOffMessage, m_controlToLearn);
1109
//Reset the saved control.
1110
m_controlToLearn = MixxxControl();
1112
qDebug() << "MidiMapping: Learning finished!";
1116
m_mappingLock.unlock();
1119
//Notify the prefs dialog that we've finished doing a MIDI learn.
1120
emit(midiLearningFinished(message));
1121
emit(midiLearningFinished()); //Tells MidiObject to stop feeding us messages.
1122
emit(inputMappingChanged());
1126
void MidiMapping::beginMidiLearn(MixxxControl control)
1128
m_mappingLock.lock();
1129
//Save the internal control we're supposed to map/remap. After this we have
1130
//to wait for the user to push a button on their controller, which should
1131
//give us a MidiMessage via finishMidiLearn().
1132
m_controlToLearn = control;
1134
qDebug() << "MidiMapping: Learning started!";
1136
m_mappingLock.unlock();
1138
//Notify the MIDI device class that it should feed us the next MIDI message...
1139
emit(midiLearningStarted());
1142
void MidiMapping::cancelMidiLearn()
1144
m_mappingLock.lock();
1145
m_controlToLearn = MixxxControl();
1146
m_mappingLock.unlock();
1149
#ifdef __MIDISCRIPT__
1150
void MidiMapping::restartScriptEngine()
1152
//Note: Locking occurs inside the functions below.
1153
shutdownScriptEngine();
1154
startupScriptEngine();
1158
// Reset the MIDI controller
1159
void MidiMapping::reset() {
1160
#ifdef __MIDISCRIPT__ // Can't ifdef slots in the .h file, so we just do the body.
1161
restartScriptEngine();
1163
MidiLedHandler::destroyHandlers(m_pOutputMidiDevice);
1168
void MidiMapping::slotScriptEngineReady() {
1169
#ifdef __MIDISCRIPT__ // Can't ifdef slots in the .h file, so we just do the body.
1171
// The lock prevents us from waking before the main thread is waiting on the
1173
m_scriptEngineInitializedMutex.lock();
1174
m_scriptEngineInitializedCondition.wakeAll();
1175
m_scriptEngineInitializedMutex.unlock();