2
* @file midicontroller.cpp
3
* @author Sean Pappalardo spappalardo@mixxx.org
5
* @brief MIDI Controller base class
9
#include "controllers/midi/midicontroller.h"
11
#include "controllers/defs_controllers.h"
12
#include "controlobject.h"
13
#include "errordialoghandler.h"
15
MidiController::MidiController() : Controller() {
18
MidiController::~MidiController() {
19
destroyOutputHandlers();
20
// Don't close the device here. Sub-classes should close the device in their
24
QString MidiController::defaultPreset() {
25
QString name = getName();
26
return USER_PRESETS_PATH.append(name.right(name.size()
27
-name.indexOf(" ")-1).replace(" ", "_")
31
QString MidiController::presetExtension() {
32
return MIDI_MAPPING_EXTENSION;
35
void MidiController::visit(const MidiControllerPreset* preset) {
37
emit(presetLoaded(getPreset()));
40
void MidiController::clearInputMappings() {
41
m_preset.mappings.clear();
44
void MidiController::clearOutputMappings() {
45
m_preset.outputMappings.clear();
48
int MidiController::close() {
49
destroyOutputHandlers();
53
void MidiController::visit(const HidControllerPreset* preset) {
55
qWarning() << "ERROR: Attempting to load an HidControllerPreset to a MidiController!";
56
// TODO(XXX): throw a hissy fit.
59
bool MidiController::savePreset(const QString fileName) const {
60
MidiControllerPresetFileHandler handler;
61
return handler.save(m_preset, getName(), fileName);
64
void MidiController::applyPreset(QString configPath) {
66
Controller::applyPreset(configPath);
68
// Only execute this code if this is an output device
69
if (isOutputDevice()) {
70
if (m_outputs.count() > 0) {
71
destroyOutputHandlers();
73
createOutputHandlers();
78
void MidiController::createOutputHandlers() {
79
if (m_preset.outputMappings.isEmpty()) {
83
QHashIterator<MixxxControl, MidiOutput> outIt(m_preset.outputMappings);
84
while (outIt.hasNext()) {
87
MidiOutput outputPack = outIt.value();
88
QString group = outIt.key().group();
89
QString key = outIt.key().item();
91
unsigned char status = outputPack.status;
92
unsigned char control = outputPack.control;
93
unsigned char on = outputPack.on;
94
unsigned char off = outputPack.off;
95
float min = outputPack.min;
96
float max = outputPack.max;
100
"Creating output handler for %1,%2 between %3 and %4 to MIDI out: 0x%5 0x%6, on: 0x%7 off: 0x%8")
102
QString::number(min), QString::number(max),
103
QString::number(status, 16).toUpper(),
104
QString::number(control, 16).toUpper().rightJustified(2,'0'),
105
QString::number(on, 16).toUpper().rightJustified(2,'0'),
106
QString::number(off, 16).toUpper().rightJustified(2,'0'));
109
MidiOutputHandler* moh = new MidiOutputHandler(group, key, this,
110
min, max, status, control,
112
if (!moh->validate()) {
113
QString errorLog = QString("Invalid MixxxControl: %1, %2")
114
.arg(group, key).toUtf8();
116
qCritical() << errorLog;
118
qWarning() << errorLog;
119
ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
120
props->setType(DLG_WARNING);
121
props->setTitle(tr("MixxxControl not found"));
122
props->setText(QString(tr("The MixxxControl '%1, %2' specified in the "
123
"loaded mapping is invalid."))
125
props->setInfoText(QString(tr("The MIDI output message 0x%1 0x%2 will not be bound."
126
"\n(Click Show Details for hints.)"))
127
.arg(QString::number(status, 16).toUpper(),
128
QString::number(control, 16).toUpper().rightJustified(2,'0')));
129
QString detailsText = QString(tr("* Check to see that the "
130
"MixxxControl name is spelled correctly in the mapping "
132
detailsText += QString(tr("* Make sure the MixxxControl you're trying to use actually exists."
133
" Visit this wiki page for a complete list:"));
134
detailsText += QString("\nhttp://mixxx.org/wiki/doku.php/midi_controller_mapping_file_format#ui_midi_controls_and_names");
135
props->setDetails(detailsText);
136
ErrorDialogHandler::instance()->requestErrorDialog(props);
141
m_outputs.append(moh);
145
void MidiController::updateAllOutputs() {
146
foreach (MidiOutputHandler* pOutput, m_outputs) {
151
void MidiController::destroyOutputHandlers() {
152
while (m_outputs.size() > 0) {
153
delete m_outputs.takeLast();
157
void MidiController::receive(unsigned char status, unsigned char control,
158
unsigned char value) {
159
unsigned char channel = status & 0x0F;
160
unsigned char opCode = status & 0xF0;
161
if (opCode >= 0xF0) {
166
bool twoBytes = true;
169
case MIDI_PITCH_BEND:
171
message = QString("MIDI status 0x%1: pitch bend ch %2, value 0x%3")
172
.arg(QString::number(status, 16).toUpper(),
173
QString::number(channel+1, 10),
174
QString::number((value << 7) | control, 16).toUpper().rightJustified(4,'0'));
178
message = QString("MIDI status 0x%1: song position 0x%2")
179
.arg(QString::number(status, 16).toUpper(),
180
QString::number((value << 7) | control, 16).toUpper().rightJustified(4,'0'));
182
case MIDI_PROGRAM_CH:
183
case MIDI_CH_AFTERTOUCH:
185
message = QString("MIDI status 0x%1 (ch %2, opcode 0x%3), value 0x%4")
186
.arg(QString::number(status, 16).toUpper(),
187
QString::number(channel+1, 10),
188
QString::number((status & 255)>>4, 16).toUpper(),
189
QString::number(control, 16).toUpper().rightJustified(2,'0'));
192
message = QString("MIDI status 0x%1: select song #%2")
193
.arg(QString::number(status, 16).toUpper(),
194
QString::number(control+1, 10));
198
case MIDI_AFTERTOUCH:
200
message = QString("MIDI status 0x%1 (ch %2, opcode 0x%3), ctrl 0x%4, val 0x%5")
201
.arg(QString::number(status, 16).toUpper(),
202
QString::number(channel+1, 10),
203
QString::number((status & 255)>>4, 16).toUpper(),
204
QString::number(control, 16).toUpper().rightJustified(2,'0'),
205
QString::number(value, 16).toUpper().rightJustified(2,'0'));
209
message = QString("MIDI status 0x%1")
210
.arg(QString::number(status, 16).toUpper());
218
//if (m_bReceiveInhibit) return;
221
mappingKey.status = status;
223
// When it's part of the message, include it
225
mappingKey.control = control;
227
// Signifies that the second byte is part of the payload, default
228
mappingKey.control = 0xFF;
233
MixxxControl control = controlToLearn();
234
if (!control.isNull()) {
238
QPair<MixxxControl, MidiOptions> target;
239
target.first = control;
240
target.second = options;
242
// TODO: store these in a temporary hash to be applied on learning
243
// success, or thrown away on cancel.
244
m_preset.mappings.insert(mappingKey.key,target);
246
//If we caught a NOTE_ON message, add a binding for NOTE_OFF as well.
247
if (status == MIDI_NOTE_ON) {
248
MidiKey mappingKeyTemp;
249
mappingKeyTemp.status = MIDI_NOTE_OFF;
250
mappingKeyTemp.control = mappingKey.control;
251
m_preset.mappings.insert(mappingKeyTemp.key,target);
254
//Reset the saved control.
255
setControlToLearn(MixxxControl());
257
QString message = "error";
259
message = QString("0x%1 0x%2")
260
.arg(QString::number(mappingKey.status, 16).toUpper(),
261
QString::number(mappingKey.control, 16).toUpper()
262
.rightJustified(2,'0'));
265
message = QString("0x%1")
266
.arg(QString::number(mappingKey.status, 16).toUpper());
268
emit(learnedMessage(message));
272
// If no control is bound to this MIDI message, return
273
if (!m_preset.mappings.contains(mappingKey.key)) {
277
QPair<MixxxControl, MidiOptions> controlOptions = m_preset.mappings.value(mappingKey.key);
278
MixxxControl mc = controlOptions.first;
279
MidiOptions options = controlOptions.second;
281
if (options.script) {
282
ControllerEngine* pEngine = getEngine();
283
if (pEngine == NULL) {
287
QScriptValueList args;
288
args << QScriptValue(status & 0x0F);
289
args << QScriptValue(control);
290
args << QScriptValue(value);
291
args << QScriptValue(status);
292
args << QScriptValue(mc.group());
294
pEngine->execute(mc.item(), args);
298
// Only pass values on to valid ControlObjects.
299
ControlObject* p = mc.getControlObject();
304
double currMixxxControlValue = p->GetMidiValue();
306
double newValue = value;
308
//qDebug() << "MIDI Options" << QString::number(options.all, 2).rightJustified(16,'0');
310
// compute 14-bit number for pitch bend messages
311
if (opCode == MIDI_PITCH_BEND) {
313
ivalue = (value << 7) | control;
315
currMixxxControlValue = p->get();
317
// Range is 0x0000..0x3FFF center @ 0x2000, i.e. 0..16383 center @ 8192
318
newValue = (ivalue-8192)/8191;
319
// computeValue not done on pitch messages because it all assumes 7-bit numbers
321
newValue = computeValue(options, currMixxxControlValue, value);
324
if (options.soft_takeover) {
325
// This is the only place to enable it if it isn't already.
327
if (m_st.ignore(p, newValue, true)) {
332
ControlObject::sync();
333
p->queueFromMidi(static_cast<MidiOpCode>(opCode), newValue);
336
double MidiController::computeValue(MidiOptions options, double _prevmidivalue, double _newmidivalue) {
340
if (options.all == 0) {
341
return _newmidivalue;
344
if (options.invert) {
345
return 127. - _newmidivalue;
348
if (options.rot64 || options.rot64_inv) {
349
tempval = _prevmidivalue;
350
diff = _newmidivalue - 64.;
351
if (diff == -1 || diff == 1)
354
diff += (diff > 0 ? -1 : +1);
359
return (tempval < 0. ? 0. : (tempval > 127. ? 127.0 : tempval));
362
if (options.rot64_fast) {
363
tempval = _prevmidivalue;
364
diff = _newmidivalue - 64.;
367
return (tempval < 0. ? 0. : (tempval > 127. ? 127.0 : tempval));
371
//Interpret 7-bit signed value using two's compliment.
372
if (_newmidivalue >= 64.)
373
_newmidivalue = _newmidivalue - 128.;
374
//Apply sensitivity to signed value. FIXME
375
// if(sensitivity > 0)
376
// _newmidivalue = _newmidivalue * ((double)sensitivity / 50.);
377
//Apply new value to current value.
378
_newmidivalue = _prevmidivalue + _newmidivalue;
381
if (options.selectknob) {
382
//Interpret 7-bit signed value using two's compliment.
383
if (_newmidivalue >= 64.)
384
_newmidivalue = _newmidivalue - 128.;
385
//Apply sensitivity to signed value. FIXME
386
//if(sensitivity > 0)
387
// _newmidivalue = _newmidivalue * ((double)sensitivity / 50.);
388
//Since this is a selection knob, we do not want to inherit previous values.
391
if (options.button) {
392
_newmidivalue = _newmidivalue != 0;
399
if (options.spread64) {
400
//qDebug() << "MIDI_OPT_SPREAD64";
401
// BJW: Spread64: Distance away from centre point (aka "relative CC")
402
// Uses a similar non-linear scaling formula as ControlTTRotary::getValueFromWidget()
403
// but with added sensitivity adjustment. This formula is still experimental.
405
_newmidivalue = _newmidivalue - 64.;
407
//double distance = _newmidivalue - 64.;
408
// _newmidivalue = distance * distance * sensitivity / 50000.;
410
// _newmidivalue = -_newmidivalue;
412
//qDebug() << "Spread64: in " << distance << " out " << _newmidivalue;
415
if (options.herc_jog) {
416
if (_newmidivalue > 64.) {
417
_newmidivalue -= 128.;
419
_newmidivalue += _prevmidivalue;
420
//if (_prevmidivalue != 0.0) { qDebug() << "AAAAAAAAAAAA" << _prevmidivalue; }
423
return _newmidivalue;
426
void MidiController::receive(QByteArray data) {
427
int length = data.size();
428
QString message = getName() + ": [";
429
for (int i = 0; i < length; ++i) {
430
message += QString("%1%2").arg(
431
QString("%1").arg((unsigned char)(data.at(i)), 2, 16, QChar('0')).toUpper(),
432
QString("%1").arg((i < (length-1)) ? ' ' : ']'));
438
//if (m_bReceiveInhibit) return;
441
mappingKey.status = data.at(0);
442
// Signifies that the second byte is part of the payload, default
443
mappingKey.control = 0xFF;
447
MixxxControl control = controlToLearn();
448
if (!control.isNull()) {
452
QPair<MixxxControl, MidiOptions> target;
453
target.first = control;
454
target.second = options;
456
// TODO: store these in a temporary hash to be applied on learning
457
// success, or thrown away on cancel.
458
m_preset.mappings.insert(mappingKey.key,target);
460
//Reset the saved control.
461
setControlToLearn(MixxxControl());
463
QString message = QString("0x%1")
464
.arg(QString::number(mappingKey.status, 16).toUpper());
465
emit(learnedMessage(message));
467
// Don't process MIDI messages when learning
471
// If no control is bound to this MIDI status, return
472
if (!m_preset.mappings.contains(mappingKey.key)) {
476
QPair<MixxxControl, MidiOptions> control = m_preset.mappings.value(mappingKey.key);
478
MixxxControl mc = control.first;
479
MidiOptions options = control.second;
481
// Custom script handler
482
if (options.script) {
483
ControllerEngine* pEngine = getEngine();
484
if (pEngine == NULL) {
487
if (!pEngine->execute(mc.item(), data)) {
488
qDebug() << "MidiController: Invalid script function" << mc.item();
492
qWarning() << "MidiController: No script function specified for" << message;
495
void MidiController::sendShortMsg(unsigned char status, unsigned char byte1, unsigned char byte2) {
496
unsigned int word = (((unsigned int)byte2) << 16) |
497
(((unsigned int)byte1) << 8) | status;