diff --git a/plugins/midi/src/common/midiprotocol.cpp b/plugins/midi/src/common/midiprotocol.cpp index 47b7ab218d..62683ca034 100644 --- a/plugins/midi/src/common/midiprotocol.cpp +++ b/plugins/midi/src/common/midiprotocol.cpp @@ -41,7 +41,7 @@ bool QLCMIDIProtocol::midiToInput(uchar cmd, uchar data1, uchar data2, uchar midi_ch = MIDI_CH(cmd); /* Check that the command came on the correct MIDI channel */ - if (midiChannel <= 0xF && midi_ch != midiChannel) + if (midiChannel < MAX_MIDI_CHANNELS && midi_ch != midiChannel) return false; switch(MIDI_CMD(cmd)) @@ -67,8 +67,8 @@ bool QLCMIDIProtocol::midiToInput(uchar cmd, uchar data1, uchar data2, break; case MIDI_PROGRAM_CHANGE: - *channel = CHANNEL_OFFSET_PROGRAM_CHANGE + quint32(data1); - *value = MIDI2DMX(data2); + *channel = CHANNEL_OFFSET_PROGRAM_CHANGE; + *value = MIDI2DMX(data1); break; case MIDI_CHANNEL_AFTERTOUCH: @@ -157,8 +157,7 @@ bool QLCMIDIProtocol::feedbackToMidi(quint32 channel, uchar value, *data1 = static_cast (channel - CHANNEL_OFFSET_NOTE_AFTERTOUCH); *data2 = DMX2MIDI(value); } - else if (channel >= CHANNEL_OFFSET_PROGRAM_CHANGE && - channel <= CHANNEL_OFFSET_PROGRAM_CHANGE_MAX) + else if (channel == CHANNEL_OFFSET_PROGRAM_CHANGE) { *cmd = MIDI_PROGRAM_CHANGE | midiChannel; *data1 = DMX2MIDI(value); diff --git a/plugins/midi/src/common/midiprotocol.h b/plugins/midi/src/common/midiprotocol.h index beed0efa8b..4c1ea309cc 100644 --- a/plugins/midi/src/common/midiprotocol.h +++ b/plugins/midi/src/common/midiprotocol.h @@ -101,6 +101,8 @@ namespace QLCMIDIProtocol /**************************************************************************** * MIDI commands with a MIDI channel (0-16) + * + * See https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message ****************************************************************************/ #define MIDI_NOTE_OFF 0x80 #define MIDI_NOTE_ON 0x90 @@ -137,8 +139,6 @@ namespace QLCMIDIProtocol #define CHANNEL_OFFSET_NOTE_AFTERTOUCH_MAX 383 #define CHANNEL_OFFSET_PROGRAM_CHANGE 384 -#define CHANNEL_OFFSET_PROGRAM_CHANGE_MAX 511 - #define CHANNEL_OFFSET_CHANNEL_AFTERTOUCH 512 #define CHANNEL_OFFSET_PITCH_WHEEL 513 diff --git a/plugins/midi/test/midi_test.cpp b/plugins/midi/test/midi_test.cpp index 87972481f1..9a4321d354 100644 --- a/plugins/midi/test/midi_test.cpp +++ b/plugins/midi/test/midi_test.cpp @@ -19,30 +19,285 @@ #include -#define private public #include "midi_test.h" #include "midiprotocol.h" -#undef private - /**************************************************************************** * MIDI tests ****************************************************************************/ +void Midi_Test::midiToInput_data() +{ + QTest::addColumn("cmd"); + QTest::addColumn("data1"); + QTest::addColumn("data2"); + QTest::addColumn("channel_expected"); + QTest::addColumn("value_expected"); + + uchar cmd; + quint32 channel; + + // Note OFF 0x80 + // Channel = data1 + // Value = 0, whatever is the velocity (data2) + cmd = MIDI_NOTE_OFF; + channel = CHANNEL_OFFSET_NOTE; + for( uchar i = 0; i < 128; ++i ) + QTest::addRow("Note OFF key=%d vel=%d", i, i) << cmd << i << i << channel + i << (uchar) 0; + + // Note ON 0x90 + // Channel = data1 + // Value = data2 * 2 (and 255 for data2=127) + cmd = MIDI_NOTE_ON; + channel = CHANNEL_OFFSET_NOTE; + for( uchar i = 0; i < 128; ++i ) { + uchar tmp2 = 127 - i; + QTest::addRow("Note ON key=%d vel=%d", i, tmp2) << cmd << i << tmp2 << channel + i << midi7b_to_dmx(tmp2); + } + + // Note Aftertouch 0xA0 + // Channel = data1 + // Value = data2 * 2 (and 255 for data2=127) + cmd = MIDI_NOTE_AFTERTOUCH; + channel = CHANNEL_OFFSET_NOTE_AFTERTOUCH; + for( uchar i = 0; i < 128; ++i ) + QTest::addRow("Note Aftertouch key=%d vel=%d", i, i) << cmd << i << i << channel + i << midi7b_to_dmx(i); + + // Control Change 0xB0 + // Channel = data1 + // Value = data2 * 2 (and 255 for data2=127) + cmd = MIDI_CONTROL_CHANGE; + channel = CHANNEL_OFFSET_CONTROL_CHANGE; + for( uchar i = 0; i < 128; ++i ) { + uchar tmp2 = 127 - i; + QTest::addRow("CC ctrl=%d val=%d", i, tmp2) << cmd << i << tmp2 << channel + i << midi7b_to_dmx(tmp2); + } + + // Program Change 0xC0 + // value = data1 * 2 (and 255 for data2=127) + cmd = MIDI_PROGRAM_CHANGE; + channel = CHANNEL_OFFSET_PROGRAM_CHANGE; + for( uchar i = 0; i < 128; ++i ) { + uchar tmp2 = 127 - i; + QTest::addRow("Program Change prg=%d", i) << cmd << i << tmp2 << channel << midi7b_to_dmx(i); + } + + // Channel Aftertouch 0xD0 + // value = data1 * 2 (and 255 for data2=127) + cmd = MIDI_CHANNEL_AFTERTOUCH; + channel = CHANNEL_OFFSET_CHANNEL_AFTERTOUCH; + for( uchar i = 0; i < 128; ++i ) { + uchar tmp2 = 127 - i; + QTest::addRow("Channel Aftertouch val=%d", i) << cmd << i << tmp2 << channel << midi7b_to_dmx(i); + } + + // Pitch Bend 0xE0 + // value = data1 for LSB & data2 for MSB + cmd = MIDI_PITCH_WHEEL; + channel = CHANNEL_OFFSET_PITCH_WHEEL; + for( int i = 0; i < 0x4000; i+=100 ) { + uchar data1 = i & 0x7F; + uchar data2 = i >> 7; + QTest::addRow("Pitch Wheel val=%d", i) << cmd << data1 << data2 << channel << midi14b_to_dmx(i); + } + +} + void Midi_Test::midiToInput() { - quint32 channel = 0; - uchar value = 0; + quint32 channel_result; + uchar value_result; + + QFETCH(uchar, cmd); + QFETCH(uchar, data1); + QFETCH(uchar, data2); + QFETCH(quint32, channel_expected); + QFETCH(uchar, value_expected); + + QVERIFY2( + QLCMIDIProtocol::midiToInput( + cmd, data1, data2, 0 /* midiChannel */, &channel_result, &value_result), + "Expecting valid answer from midiToInput"); + + QCOMPARE(channel_result, channel_expected); + QCOMPARE(value_result, value_expected); +} + + + +void Midi_Test::feedbackToMidi_data() +{ + QTest::addColumn("qlcChannel"); + QTest::addColumn("qlcValue"); + QTest::addColumn("midiChannel"); + QTest::addColumn("sendNoteOff"); + QTest::addColumn("cmd_expected"); + QTest::addColumn("data1_expected"); + QTest::addColumn("data2_expected"); // 255 => data2 byte not used by MIDI command + + for(quint32 qlcChannel = 0; qlcChannel < MAX_MIDI_CHANNELS; qlcChannel +=6) + { + for(uchar midiChannel = 0; midiChannel <= MAX_MIDI_CHANNELS; midiChannel += 4) + { + uchar qlcValue = 7 + 3 * qlcChannel + 3 * midiChannel; + + for(uchar key = qlcChannel * 3 + midiChannel * 5; key < 128; key += 27) + { + QTest::addRow("Note OFF key=%d qlc=%d midi=%d", key, qlcChannel, midiChannel) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_NOTE + key << uchar(0) << midiChannel << true + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_NOTE_OFF) + << uchar(key) << uchar(0); + + QTest::addRow("Note ON key=%d qlc=%d midi=%d val=0", key, qlcChannel, midiChannel) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_NOTE + key << uchar(0) << midiChannel << false + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_NOTE_ON) + << uchar(key) << uchar(0); + + QTest::addRow("Note ON key=%d qlc=%d midi=%d val=%d", key, qlcChannel, midiChannel, qlcValue) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_NOTE + key << qlcValue << midiChannel << true + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_NOTE_ON) + << uchar(key) << dmx_to_midi7b(qlcValue); + + QTest::addRow("Note Aftertouch key=%d qlc=%d midi=%d val=%d", key, qlcChannel, midiChannel, qlcValue) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_NOTE_AFTERTOUCH + key << qlcValue << midiChannel << false + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_NOTE_AFTERTOUCH) + << uchar(key) << dmx_to_midi7b(qlcValue); + + QTest::addRow("Control Change ctrl=%d qlc=%d midi=%d val=%d", key, qlcChannel, midiChannel, qlcValue) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_CONTROL_CHANGE + key << qlcValue << midiChannel << true + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_CONTROL_CHANGE) + << uchar(key) << dmx_to_midi7b(qlcValue); + } + + QTest::addRow("Program Change prg=%d qlc=%d midi=%d", qlcValue, qlcChannel, midiChannel) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_PROGRAM_CHANGE << qlcValue << midiChannel << false + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_PROGRAM_CHANGE) + << dmx_to_midi7b(qlcValue) << uchar(255); + + QTest::addRow("Channel Aftertouch val=%d qlc=%d midi=%d", qlcValue, qlcChannel, midiChannel) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_CHANNEL_AFTERTOUCH << qlcValue << midiChannel << true + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_CHANNEL_AFTERTOUCH) + << dmx_to_midi7b(qlcValue) << uchar(255); + + QTest::addRow("Pitch Bend val=%d qlc=%d midi=%d", qlcValue, qlcChannel, midiChannel) + << qlcChannel * OmniChannelOffset + CHANNEL_OFFSET_PITCH_WHEEL << qlcValue << midiChannel << false + << qlc_to_midiCmd(qlcChannel, midiChannel, MIDI_PITCH_WHEEL) + << uchar((qlcValue & 0x01) << 6) << uchar(qlcValue >> 1); + + ++qlcValue; + } + } + +} + +void Midi_Test::feedbackToMidi() +{ + uchar cmd_result, data1_result, data2_result; + + QFETCH(quint32, qlcChannel); + QFETCH(uchar, qlcValue); + QFETCH(uchar, midiChannel); + QFETCH(bool, sendNoteOff); + QFETCH(uchar, cmd_expected); + QFETCH(uchar, data1_expected); + QFETCH(uchar, data2_expected); + if(data2_expected == 255) // data2 byte not used by MIDI command + data2_result = data2_expected; + + QVERIFY2( + QLCMIDIProtocol::feedbackToMidi( + qlcChannel, qlcValue, midiChannel, sendNoteOff, + &cmd_result, &data1_result, &data2_result), + "Expecting valid answer from feedbackToMidi"); + + QCOMPARE(cmd_result, cmd_expected); + QCOMPARE(data1_result, data1_expected); + QCOMPARE(data2_result, data2_expected); +} + + + +void Midi_Test::midiToInput_midiChannels_data() +{ + QTest::addColumn("cmd"); + QTest::addColumn("data1"); + QTest::addColumn("data2"); + QTest::addColumn("midiChannel"); + QTest::addColumn("qlc_channel_expected"); + QTest::addColumn("qlc_value_expected"); + + for(uchar midiChannel = 0; midiChannel < 16; ++midiChannel) + { + uchar data1 = midiChannel * 7; + uchar data2 = midiChannel * 3; + + for(uchar qlcMidiChannel = 2; qlcMidiChannel <= 16; qlcMidiChannel+=7) + { + QTest::addRow("Note OFF midi=%d qlc=%d", midiChannel, qlcMidiChannel) + << uchar(MIDI_NOTE_OFF + midiChannel) << data1 << data2 << qlcMidiChannel + << midiChannel_to_qlcChannel( midiChannel, qlcMidiChannel, CHANNEL_OFFSET_NOTE + data1++ ) + << midiChannel_to_qlcValue( midiChannel, qlcMidiChannel, 0 ); + + QTest::addRow("Note ON midi=%d qlc=%d", midiChannel, qlcMidiChannel) + << uchar(MIDI_NOTE_ON + midiChannel) << data1 << data2 << qlcMidiChannel + << midiChannel_to_qlcChannel( midiChannel, qlcMidiChannel, CHANNEL_OFFSET_NOTE + data1++ ) + << midiChannel_to_qlcValue( midiChannel, qlcMidiChannel, midi7b_to_dmx(data2++) ); + + QTest::addRow("Note Aftertouch midi=%d qlc=%d", midiChannel, qlcMidiChannel) + << uchar(MIDI_NOTE_AFTERTOUCH + midiChannel) << data1 << data2 << qlcMidiChannel + << midiChannel_to_qlcChannel( midiChannel, qlcMidiChannel, CHANNEL_OFFSET_NOTE_AFTERTOUCH + data1++ ) + << midiChannel_to_qlcValue( midiChannel, qlcMidiChannel, midi7b_to_dmx(data2++) ); + + QTest::addRow("CC midi=%d qlc=%d", midiChannel, qlcMidiChannel) + << uchar(MIDI_CONTROL_CHANGE + midiChannel) << data1 << data2 << qlcMidiChannel + << midiChannel_to_qlcChannel( midiChannel, qlcMidiChannel, CHANNEL_OFFSET_CONTROL_CHANGE + data1++ ) + << midiChannel_to_qlcValue( midiChannel, qlcMidiChannel, midi7b_to_dmx(data2++) ); + + QTest::addRow("Program Change midi=%d qlc=%d", midiChannel, qlcMidiChannel) + << uchar(MIDI_PROGRAM_CHANGE + midiChannel) << data1 << data2++ << qlcMidiChannel + << midiChannel_to_qlcChannel( midiChannel, qlcMidiChannel, CHANNEL_OFFSET_PROGRAM_CHANGE ) + << midiChannel_to_qlcValue( midiChannel, qlcMidiChannel, midi7b_to_dmx(data1++) ); + + QTest::addRow("Channel Aftertouch midi=%d qlc=%d", midiChannel, qlcMidiChannel) + << uchar(MIDI_CHANNEL_AFTERTOUCH + midiChannel) << data1 << data2++ << qlcMidiChannel + << midiChannel_to_qlcChannel( midiChannel, qlcMidiChannel, CHANNEL_OFFSET_CHANNEL_AFTERTOUCH ) + << midiChannel_to_qlcValue( midiChannel, qlcMidiChannel, midi7b_to_dmx(data1++) ); + + int val = data2 << 7 | data1; + QTest::addRow("Pitch Wheel midi=%d qlc=%d", midiChannel, qlcMidiChannel) + << uchar(MIDI_PITCH_WHEEL + midiChannel) << data1 << data2 << qlcMidiChannel + << midiChannel_to_qlcChannel( midiChannel, qlcMidiChannel, CHANNEL_OFFSET_PITCH_WHEEL ) + << midiChannel_to_qlcValue( midiChannel, qlcMidiChannel, midi14b_to_dmx(val) ); + } + } +} + +void Midi_Test::midiToInput_midiChannels() +{ + quint32 channel_result; + uchar value_result; - uchar midiChannel = 7; - uchar cmd = MIDI_NOTE_ON | midiChannel; - uchar data1 = 10; - uchar data2 = 127; + QFETCH(uchar, cmd); + QFETCH(uchar, data1); + QFETCH(uchar, data2); + QFETCH(uchar, midiChannel); + QFETCH(quint32, qlc_channel_expected); + QFETCH(int, qlc_value_expected); - QLCMIDIProtocol::midiToInput(cmd, data1, data2, midiChannel, &channel, &value); + bool result = QLCMIDIProtocol::midiToInput( + cmd, data1, data2, midiChannel, + &channel_result, &value_result); - QCOMPARE(channel, 138U); - QCOMPARE(value, uchar(255U)); + if(qlc_value_expected >= 0) + { + QVERIFY2(result, "Expecting valid answer from midiToInput"); + QCOMPARE(channel_result, qlc_channel_expected); + QCOMPARE(value_result, qlc_value_expected); + } + else + { + QVERIFY2(!result, "Expecting midiToInput to ignore the call"); + } } QTEST_MAIN(Midi_Test) diff --git a/plugins/midi/test/midi_test.h b/plugins/midi/test/midi_test.h index 50fb75ad82..42a08e9171 100644 --- a/plugins/midi/test/midi_test.h +++ b/plugins/midi/test/midi_test.h @@ -24,10 +24,51 @@ class Midi_Test : public QObject { - Q_OBJECT + Q_OBJECT + +public: + + static inline + uchar midi7b_to_dmx( int in ) + { return in >= 127 ? 255 : in * 2; } + static inline + uchar midi14b_to_dmx( int in ) + { return in >> 6; } + + static inline + uchar dmx_to_midi7b( int in ) + { return in / 2; } + + + static const quint32 OmniChannelOffset = 1 << 12; + + inline static + quint32 midiChannel_to_qlcChannel(uchar midi, uchar qlc, quint32 offset) + { return offset + (qlc == 16 ? midi * OmniChannelOffset : 0); } + + inline static + int midiChannel_to_qlcValue(uchar midi, uchar qlc, uchar value) + { return (qlc == 16 || qlc == midi) ? value : -1; } + + inline static + uchar qlc_to_midiCmd(quint32 qlcChannel, uchar midiChannel, uchar cmd) + { return cmd + (midiChannel == 16 ? qlcChannel : midiChannel); } private slots: - void midiToInput(); + + // Tests of data bytes conversion + void midiToInput_data(); + void midiToInput(); + + void feedbackToMidi_data(); + void feedbackToMidi(); + + // Tests of MIDI Channel filtering + void midiToInput_midiChannels_data(); + void midiToInput_midiChannels(); + + // TODO Tests of System Common Messages + }; #endif