1 #include <stdint.h>
2
3 #include "control/controlobject.h"
4 #include "controllers/learningutils.h"
5 #include "test/mixxxtest.h"
6 #include "util/memory.h"
7
operator <<(std::ostream & stream,const MidiInputMapping & mapping)8 std::ostream& operator<<(std::ostream& stream, const MidiInputMapping& mapping) {
9 stream << mapping.key.key << mapping.options.all;
10 return stream;
11 }
12
13 class LearningUtilsTest : public MixxxTest {
14 protected:
addMessage(unsigned char status,unsigned char control,unsigned char value)15 void addMessage(unsigned char status, unsigned char control, unsigned char value) {
16 m_messages.append(qMakePair(MidiKey(status, control), value));
17 }
18
19 /// Check if mapping in present in mapping list.
20 /// Similar to MidiInputMappings::contains(const MidiInputMapping&), but
21 /// does not compare the description.
containsMapping(const MidiInputMappings & haystack,const MidiInputMapping & needle)22 bool containsMapping(const MidiInputMappings& haystack, const MidiInputMapping& needle) {
23 for (const MidiInputMapping& mapping : haystack) {
24 if (mapping.key == needle.key &&
25 mapping.options == needle.options &&
26 mapping.control == needle.control) {
27 return true;
28 }
29 }
30 return false;
31 }
32
33 QList<QPair<MidiKey, unsigned char> > m_messages;
34 };
35
TEST_F(LearningUtilsTest,NoteOnButton)36 TEST_F(LearningUtilsTest, NoteOnButton) {
37 // Status: 0x91, Control: 0x10
38 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x7F);
39 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x00);
40
41 ConfigKey control("[Test]", "SomeControl");
42 MidiInputMappings mappings =
43 LearningUtils::guessMidiInputMappings(control, m_messages);
44
45 ASSERT_EQ(1, mappings.size());
46 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10),
47 MidiOptions(),
48 control,
49 mappings.first().description),
50 mappings.first());
51 }
52
TEST_F(LearningUtilsTest,NoteOnNoteOffButton)53 TEST_F(LearningUtilsTest, NoteOnNoteOffButton) {
54 // Status: 0x91, Control: 0x10
55 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x7F);
56 addMessage(MIDI_NOTE_OFF | 0x01, 0x10, 0x00);
57
58 ConfigKey control("[Test]", "SomeControl");
59 MidiInputMappings mappings =
60 LearningUtils::guessMidiInputMappings(control, m_messages);
61
62 ASSERT_EQ(2, mappings.size());
63 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10),
64 MidiOptions(),
65 control,
66 mappings.at(0).description),
67 mappings.at(0));
68 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | 0x01, 0x10),
69 MidiOptions(),
70 control,
71 mappings.at(1).description),
72 mappings.at(1));
73 }
74
TEST_F(LearningUtilsTest,CC7BitKnob)75 TEST_F(LearningUtilsTest, CC7BitKnob) {
76 // Standard CC 7-bit knobs show up as a MIDI_CC message, single channel,
77 // single control and a variety of values in the range of 0x00 to 0x7F.
78 // Status: 0x81 Control: 0x01
79 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
80 addMessage(MIDI_CC | 0x01, 0x10, 0x70);
81 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
82 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
83 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
84 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
85 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
86
87 ConfigKey control("[Test]", "SomeControl");
88 MidiInputMappings mappings =
89 LearningUtils::guessMidiInputMappings(control, m_messages);
90
91 ASSERT_EQ(1, mappings.size());
92 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
93 MidiOptions(),
94 control,
95 mappings.first().description),
96 mappings.first());
97 }
98
TEST_F(LearningUtilsTest,CC7BitKnob_CenterPointButton_NoteOn)99 TEST_F(LearningUtilsTest, CC7BitKnob_CenterPointButton_NoteOn) {
100 // This style of 7-bit CC knob is found on controllers like the
101 // VCI-100. They do not emit a center-point CC message with value 0x40 and
102 // instead emit a NOTE_ON (0x7F, 0x00) pair for the center point. This
103 // causes Mixxx to confuse these knobs for buttons and also cause them to
104 // never truly reach their center point. We map the button to the
105 // _set_default control if it exists.
106
107 // Starting outside the center point.
108 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
109 addMessage(MIDI_CC | 0x01, 0x10, 0x70);
110 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
111 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
112 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
113 addMessage(MIDI_NOTE_ON | 0x01, 0xE0, 0x7F);
114 addMessage(MIDI_NOTE_ON | 0x01, 0xE0, 0x00);
115 addMessage(MIDI_CC | 0x01, 0x10, 0x3F);
116 addMessage(MIDI_CC | 0x01, 0x10, 0x30);
117 addMessage(MIDI_CC | 0x01, 0x10, 0x20);
118 addMessage(MIDI_CC | 0x01, 0x10, 0x10);
119 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
120
121 ConfigKey control("[Test]", "SomeControl");
122 // This message recognizer checks for the existence of a _reset control.
123 ConfigKey resetControl("[Test]", "SomeControl_set_default");
124 auto pResetControl = std::make_unique<ControlObject>(resetControl);
125 MidiInputMappings mappings =
126 LearningUtils::guessMidiInputMappings(control, m_messages);
127
128 ASSERT_EQ(2, mappings.size());
129 EXPECT_TRUE(containsMapping(mappings,
130 MidiInputMapping(
131 MidiKey(MIDI_CC | 0x01, 0x10), MidiOptions(), control)));
132 EXPECT_TRUE(containsMapping(mappings,
133 MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0xE0),
134 MidiOptions(),
135 resetControl)));
136
137 m_messages.clear();
138
139 // Starting on the center point the VCI-100 only sends a NOTE_ON with value
140 // 0x00 to indicate we started moving but are leaving the center point.
141 addMessage(MIDI_NOTE_ON | 0x01, 0xE0, 0x00);
142 addMessage(MIDI_CC | 0x01, 0x10, 0x3F);
143 addMessage(MIDI_CC | 0x01, 0x10, 0x35);
144 addMessage(MIDI_CC | 0x01, 0x10, 0x30);
145 addMessage(MIDI_CC | 0x01, 0x10, 0x25);
146 addMessage(MIDI_CC | 0x01, 0x10, 0x20);
147 addMessage(MIDI_CC | 0x01, 0x10, 0x15);
148 addMessage(MIDI_CC | 0x01, 0x10, 0x10);
149 addMessage(MIDI_CC | 0x01, 0x10, 0x05);
150 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
151
152 ASSERT_EQ(2, mappings.size());
153 EXPECT_TRUE(containsMapping(mappings,
154 MidiInputMapping(
155 MidiKey(MIDI_CC | 0x01, 0x10), MidiOptions(), control)));
156 EXPECT_TRUE(containsMapping(mappings,
157 MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0xE0),
158 MidiOptions(),
159 resetControl)));
160 }
161
TEST_F(LearningUtilsTest,CC14BitKnob_MSBFirst)162 TEST_F(LearningUtilsTest, CC14BitKnob_MSBFirst) {
163 // A CC 14-bit knob with the MSB as the first message. We treat streams of
164 // single-channel, CC opcode, 2 control, equal size message streams as
165 // 14-bit CC knobs. The order of the LSB and MSB does not matter to us.
166
167 addMessage(MIDI_CC | 0x01, 0x11, 0x00);
168 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
169 addMessage(MIDI_CC | 0x01, 0x11, 0x00);
170 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
171 addMessage(MIDI_CC | 0x01, 0x11, 0x00);
172 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
173 addMessage(MIDI_CC | 0x01, 0x11, 0x01);
174 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
175 addMessage(MIDI_CC | 0x01, 0x11, 0x01);
176 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
177 addMessage(MIDI_CC | 0x01, 0x11, 0x01);
178 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
179 addMessage(MIDI_CC | 0x01, 0x11, 0x02);
180 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
181 addMessage(MIDI_CC | 0x01, 0x11, 0x02);
182 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
183 addMessage(MIDI_CC | 0x01, 0x11, 0x02);
184 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
185 addMessage(MIDI_CC | 0x01, 0x11, 0x03);
186 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
187 addMessage(MIDI_CC | 0x01, 0x11, 0x03);
188 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
189 addMessage(MIDI_CC | 0x01, 0x11, 0x03);
190 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
191
192 ConfigKey control("[Test]", "SomeControl");
193 MidiInputMappings mappings =
194 LearningUtils::guessMidiInputMappings(control, m_messages);
195
196 ASSERT_EQ(2, mappings.size());
197 MidiOptions lsb_option;
198 lsb_option.fourteen_bit_lsb = true;
199 EXPECT_TRUE(containsMapping(mappings,
200 MidiInputMapping(
201 MidiKey(MIDI_CC | 0x01, 0x10), lsb_option, control)));
202 MidiOptions msb_option;
203 msb_option.fourteen_bit_msb = true;
204 EXPECT_TRUE(containsMapping(mappings,
205 MidiInputMapping(
206 MidiKey(MIDI_CC | 0x01, 0x11), msb_option, control)));
207 }
208
TEST_F(LearningUtilsTest,CC14BitKnob_LSBFirst)209 TEST_F(LearningUtilsTest, CC14BitKnob_LSBFirst) {
210 // A CC 14-bit knob with the LSB as the first message. We treat streams of
211 // single-channel, CC opcode, 2 control, equal size message streams as
212 // 14-bit CC knobs. The order of the LSB and MSB does not matter to us.
213
214 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
215 addMessage(MIDI_CC | 0x01, 0x11, 0x00);
216 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
217 addMessage(MIDI_CC | 0x01, 0x11, 0x00);
218 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
219 addMessage(MIDI_CC | 0x01, 0x11, 0x00);
220 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
221 addMessage(MIDI_CC | 0x01, 0x11, 0x01);
222 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
223 addMessage(MIDI_CC | 0x01, 0x11, 0x01);
224 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
225 addMessage(MIDI_CC | 0x01, 0x11, 0x01);
226 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
227 addMessage(MIDI_CC | 0x01, 0x11, 0x02);
228 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
229 addMessage(MIDI_CC | 0x01, 0x11, 0x02);
230 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
231 addMessage(MIDI_CC | 0x01, 0x11, 0x02);
232 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
233 addMessage(MIDI_CC | 0x01, 0x11, 0x03);
234 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
235 addMessage(MIDI_CC | 0x01, 0x11, 0x03);
236 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
237 addMessage(MIDI_CC | 0x01, 0x11, 0x03);
238
239 ConfigKey control("[Test]", "SomeControl");
240 MidiInputMappings mappings =
241 LearningUtils::guessMidiInputMappings(control, m_messages);
242
243 ASSERT_EQ(2, mappings.size());
244 MidiOptions lsb_option;
245 lsb_option.fourteen_bit_lsb = true;
246 EXPECT_TRUE(containsMapping(mappings,
247 MidiInputMapping(
248 MidiKey(MIDI_CC | 0x01, 0x10), lsb_option, control)));
249 MidiOptions msb_option;
250 msb_option.fourteen_bit_msb = true;
251 EXPECT_TRUE(containsMapping(mappings,
252 MidiInputMapping(
253 MidiKey(MIDI_CC | 0x01, 0x11), msb_option, control)));
254 }
255
TEST_F(LearningUtilsTest,CC7BitKnob_ConfusableForCC7BitTicker_Zeroes)256 TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker_Zeroes) {
257 // Make sure that 0x00 is a tell that we are not a 2's complement select
258 // knob.
259 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
260 addMessage(MIDI_CC | 0x01, 0x10, 0x02);
261 addMessage(MIDI_CC | 0x01, 0x10, 0x03);
262 addMessage(MIDI_CC | 0x01, 0x10, 0x02);
263 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
264
265 ConfigKey control("[Test]", "SomeControl");
266 MidiInputMappings mappings =
267 LearningUtils::guessMidiInputMappings(control, m_messages);
268
269 MidiOptions options;
270 options.selectknob = true;
271 ASSERT_EQ(1, mappings.size());
272 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
273 options,
274 control,
275 mappings.first().description),
276 mappings.first());
277
278 m_messages.clear();
279
280 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
281 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
282 addMessage(MIDI_CC | 0x01, 0x10, 0x02);
283 addMessage(MIDI_CC | 0x01, 0x10, 0x03);
284 addMessage(MIDI_CC | 0x01, 0x10, 0x02);
285 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
286
287 mappings = LearningUtils::guessMidiInputMappings(control, m_messages);
288
289 ASSERT_EQ(1, mappings.size());
290 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
291 MidiOptions(),
292 control,
293 mappings.first().description),
294 mappings.first());
295 }
296
TEST_F(LearningUtilsTest,CC7BitKnob_ConfusableForCC7BitTicker_ZeroIncluded)297 TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker_ZeroIncluded) {
298 // Moving a CC knob through its range multiple times is confusable for
299 // select knobs in some cases. More than 8 distinct values tell us this is
300 // not a select knob.
301 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
302 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
303 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
304 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
305 addMessage(MIDI_CC | 0x01, 0x10, 0x30);
306 addMessage(MIDI_CC | 0x01, 0x10, 0x20);
307 addMessage(MIDI_CC | 0x01, 0x10, 0x10);
308 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
309 // Zero tells us this is not a select knob.
310 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
311 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
312 addMessage(MIDI_CC | 0x01, 0x10, 0x10);
313 addMessage(MIDI_CC | 0x01, 0x10, 0x20);
314 addMessage(MIDI_CC | 0x01, 0x10, 0x30);
315 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
316 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
317 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
318 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
319
320 ConfigKey control("[Test]", "SomeControl");
321 MidiInputMappings mappings =
322 LearningUtils::guessMidiInputMappings(control, m_messages);
323
324 ASSERT_EQ(1, mappings.size());
325 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
326 MidiOptions(),
327 control,
328 mappings.first().description),
329 mappings.first());
330 }
331
TEST_F(LearningUtilsTest,CC7BitKnob_ConfusableForCC7BitTicker)332 TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForCC7BitTicker) {
333 // Moving a CC knob through its range multiple times is confusable for
334 // select knobs when a 0x7F is repeated. More than 8 distinct values tells
335 // us this is not a select knob.
336 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
337 addMessage(MIDI_CC | 0x01, 0x10, 0x75);
338 addMessage(MIDI_CC | 0x01, 0x10, 0x70);
339 addMessage(MIDI_CC | 0x01, 0x10, 0x65);
340 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
341 addMessage(MIDI_CC | 0x01, 0x10, 0x55);
342 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
343 addMessage(MIDI_CC | 0x01, 0x10, 0x45);
344 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
345 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
346 addMessage(MIDI_CC | 0x01, 0x10, 0x75);
347 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
348
349 ConfigKey control("[Test]", "SomeControl");
350 MidiInputMappings mappings =
351 LearningUtils::guessMidiInputMappings(control, m_messages);
352
353 ASSERT_EQ(1, mappings.size());
354 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
355 MidiOptions(),
356 control,
357 mappings.first().description),
358 mappings.first());
359 }
360
TEST_F(LearningUtilsTest,CC7BitKnob_ConfusableForSpread64Ticker_StartAndStopOn41)361 TEST_F(LearningUtilsTest,
362 CC7BitKnob_ConfusableForSpread64Ticker_StartAndStopOn41) {
363 // Moving a CC knob through its range multiple times is confusable for
364 // Spread64 select knobs when a 0x41 or 0x3F is repeated. If we start and
365 // stop on 0x41 (and don't pass through 0x40) then this can set off Spread64
366 // detection. We require <8 distinct values for Spread64 detection to
367 // prevent this.
368 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
369 addMessage(MIDI_CC | 0x01, 0x10, 0x45);
370 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
371 addMessage(MIDI_CC | 0x01, 0x10, 0x55);
372 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
373 addMessage(MIDI_CC | 0x01, 0x10, 0x65);
374 addMessage(MIDI_CC | 0x01, 0x10, 0x70);
375 addMessage(MIDI_CC | 0x01, 0x10, 0x75);
376 addMessage(MIDI_CC | 0x01, 0x10, 0x62);
377 addMessage(MIDI_CC | 0x01, 0x10, 0x52);
378 addMessage(MIDI_CC | 0x01, 0x10, 0x48);
379 addMessage(MIDI_CC | 0x01, 0x10, 0x46);
380 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
381
382 ConfigKey control("[Test]", "SomeControl");
383 MidiInputMappings mappings =
384 LearningUtils::guessMidiInputMappings(control, m_messages);
385
386 ASSERT_EQ(1, mappings.size());
387 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
388 MidiOptions(),
389 control,
390 mappings.first().description),
391 mappings.first());
392 }
393
TEST_F(LearningUtilsTest,CC7BitKnob_ConfusableForSpread64Ticker_0x40Included)394 TEST_F(LearningUtilsTest, CC7BitKnob_ConfusableForSpread64Ticker_0x40Included) {
395 // Moving a CC knob through its range multiple times is confusable for
396 // Spread64 select knobs when a 0x41 or 0x3F is repeated.
397 addMessage(MIDI_CC | 0x01, 0x10, 0x70);
398 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
399 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
400 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
401 addMessage(MIDI_CC | 0x01, 0x10, 0x3F);
402 addMessage(MIDI_CC | 0x01, 0x10, 0x30);
403 addMessage(MIDI_CC | 0x01, 0x10, 0x20);
404 addMessage(MIDI_CC | 0x01, 0x10, 0x10);
405 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
406 addMessage(MIDI_CC | 0x01, 0x10, 0x10);
407 addMessage(MIDI_CC | 0x01, 0x10, 0x20);
408 addMessage(MIDI_CC | 0x01, 0x10, 0x30);
409 addMessage(MIDI_CC | 0x01, 0x10, 0x3F);
410 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
411 // 0x40 tells us this is not a Spread64 select knob.
412 addMessage(MIDI_CC | 0x01, 0x10, 0x40);
413 addMessage(MIDI_CC | 0x01, 0x10, 0x50);
414 addMessage(MIDI_CC | 0x01, 0x10, 0x60);
415 addMessage(MIDI_CC | 0x01, 0x10, 0x70);
416
417 ConfigKey control("[Test]", "SomeControl");
418 MidiInputMappings mappings =
419 LearningUtils::guessMidiInputMappings(control, m_messages);
420
421 ASSERT_EQ(1, mappings.size());
422 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
423 MidiOptions(),
424 control,
425 mappings.first().description),
426 mappings.first());
427 }
428
TEST_F(LearningUtilsTest,CC7BitTicker)429 TEST_F(LearningUtilsTest, CC7BitTicker) {
430 // A CC 7-bit ticker (select knob, jog wheel, etc.) shows up as a MIDI_CC
431 // message, single channel, single control and a variety of values in two's
432 // complement. We detect this by looking at jumps across the boundary of
433 // 0x7F to 0x00 so the user has to go forward and backward.
434 // Status: 0x81 Control: 0x10
435 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
436 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
437 addMessage(MIDI_CC | 0x01, 0x10, 0x02);
438 addMessage(MIDI_CC | 0x01, 0x10, 0x05);
439 addMessage(MIDI_CC | 0x01, 0x10, 0x7E);
440 addMessage(MIDI_CC | 0x01, 0x10, 0x7C);
441 addMessage(MIDI_CC | 0x01, 0x10, 0x70);
442
443 ConfigKey control("[Test]", "SomeControl");
444 MidiInputMappings mappings =
445 LearningUtils::guessMidiInputMappings(control, m_messages);
446
447 ASSERT_EQ(1, mappings.size());
448 MidiOptions options;
449 options.selectknob = true;
450 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
451 options,
452 control,
453 mappings.first().description),
454 mappings.first());
455 }
456
TEST_F(LearningUtilsTest,Spread64Ticker)457 TEST_F(LearningUtilsTest, Spread64Ticker) {
458 // A Spread64 ticker (select knob, jog wheel, etc.) shows up as a MIDI_CC
459 // message, single channel, single control and a variety of values centered
460 // around 0x40 but never including that value (since 0x40 means "not moving")
461 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
462 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
463 addMessage(MIDI_CC | 0x01, 0x10, 0x42);
464 addMessage(MIDI_CC | 0x01, 0x10, 0x41);
465 addMessage(MIDI_CC | 0x01, 0x10, 0x3F);
466 addMessage(MIDI_CC | 0x01, 0x10, 0x3F);
467 addMessage(MIDI_CC | 0x01, 0x10, 0x3F);
468
469 ConfigKey control("[Test]", "SomeControl");
470 MidiInputMappings mappings =
471 LearningUtils::guessMidiInputMappings(control, m_messages);
472
473 ASSERT_EQ(1, mappings.size());
474 MidiOptions options;
475 options.spread64 = true;
476 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
477 options,
478 control,
479 mappings.first().description),
480 mappings.first());
481 }
482
TEST_F(LearningUtilsTest,CC7BitTicker_SingleDirection)483 TEST_F(LearningUtilsTest, CC7BitTicker_SingleDirection) {
484 // A CC 7-bit ticker (select knob, jog wheel, etc.) shows up as a MIDI_CC
485 // message, single channel, single control and a variety of values in two's
486 // complement. It's harder to detect when the user does not change direction
487 // because we do not see 0x00 boundary crossings that are tell-tale signs of
488 // two's complement. If we see repeat 0x01 or 0x7F values then we interpret
489 // those as being a ticker as well.
490
491 // Status: 0x81 Control: 0x10
492 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
493 addMessage(MIDI_CC | 0x01, 0x10, 0x02);
494 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
495 addMessage(MIDI_CC | 0x01, 0x10, 0x03);
496 addMessage(MIDI_CC | 0x01, 0x10, 0x01);
497
498 MidiOptions options;
499 options.selectknob = true;
500
501 ConfigKey control("[Test]", "SomeControl");
502 MidiInputMappings mappings =
503 LearningUtils::guessMidiInputMappings(control, m_messages);
504 ASSERT_EQ(1, mappings.size());
505 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
506 options,
507 control,
508 mappings.first().description),
509 mappings.first());
510
511 m_messages.clear();
512
513 // Status: 0x81 Control: 0x10
514 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
515 addMessage(MIDI_CC | 0x01, 0x10, 0x7E);
516 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
517 addMessage(MIDI_CC | 0x01, 0x10, 0x7C);
518
519 mappings = LearningUtils::guessMidiInputMappings(control, m_messages);
520 ASSERT_EQ(1, mappings.size());
521 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
522 options,
523 control,
524 mappings.first().description),
525 mappings.first());
526 }
527
TEST_F(LearningUtilsTest,SingleMessageSwitchMode_NoteOn)528 TEST_F(LearningUtilsTest, SingleMessageSwitchMode_NoteOn) {
529 // If we only get one NOTE_ON message that is either 0x7F or 0x00 then we
530 // assume this is a binary toggle switch.
531
532 // Status: 0x91 Control: 0x10
533 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x7F);
534
535 ConfigKey control("[Test]", "SomeControl");
536 MidiInputMappings mappings =
537 LearningUtils::guessMidiInputMappings(control, m_messages);
538
539 ASSERT_EQ(1, mappings.size());
540 MidiOptions options;
541 options.sw = true;
542 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10),
543 options,
544 control,
545 mappings.first().description),
546 mappings.first());
547
548 m_messages.clear();
549
550 // Status: 0x91 Control: 0x10
551 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x00);
552
553 mappings = LearningUtils::guessMidiInputMappings(control, m_messages);
554
555 ASSERT_EQ(1, mappings.size());
556 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10),
557 options,
558 control,
559 mappings.first().description),
560 mappings.first());
561 }
562
TEST_F(LearningUtilsTest,SingleMessageSwitchMode_CC)563 TEST_F(LearningUtilsTest, SingleMessageSwitchMode_CC) {
564 // If we only get one CC message that is either 0x7F or 0x00 then we
565 // assume this is a binary toggle switch.
566
567 // Status: 0xB1 Control: 0x10
568 addMessage(MIDI_CC | 0x01, 0x10, 0x7F);
569
570 ConfigKey control("[Test]", "SomeControl");
571 MidiInputMappings mappings =
572 LearningUtils::guessMidiInputMappings(control, m_messages);
573
574 ASSERT_EQ(1, mappings.size());
575 MidiOptions options;
576 options.sw = true;
577 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
578 options,
579 control,
580 mappings.first().description),
581 mappings.first());
582
583 m_messages.clear();
584
585 // Status: 0x91 Control: 0x10
586 addMessage(MIDI_CC | 0x01, 0x10, 0x00);
587
588 mappings = LearningUtils::guessMidiInputMappings(control, m_messages);
589
590 ASSERT_EQ(1, mappings.size());
591 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_CC | 0x01, 0x10),
592 options,
593 control,
594 mappings.first().description),
595 mappings.first());
596 }
597
TEST_F(LearningUtilsTest,MultipleControlsUnrecognized_BindsFirst)598 TEST_F(LearningUtilsTest, MultipleControlsUnrecognized_BindsFirst) {
599 // Status 0x91, Control 0x10
600 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x7F);
601 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x00);
602
603 // Status 0x91, Control 0x11
604 addMessage(MIDI_NOTE_ON | 0x01, 0x11, 0x7F);
605 addMessage(MIDI_NOTE_ON | 0x01, 0x11, 0x00);
606
607 ConfigKey control("[Test]", "SomeControl");
608 MidiInputMappings mappings =
609 LearningUtils::guessMidiInputMappings(control, m_messages);
610 ASSERT_EQ(1, mappings.size());
611 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10),
612 MidiOptions(),
613 control,
614 mappings.first().description),
615 mappings.first());
616 }
617
TEST_F(LearningUtilsTest,MultipleChannelsUnrecognized_BindsFirst)618 TEST_F(LearningUtilsTest, MultipleChannelsUnrecognized_BindsFirst) {
619 // Status 0x91, Control 0x10
620 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x7F);
621 addMessage(MIDI_NOTE_ON | 0x01, 0x10, 0x00);
622
623 // Status 0x91, Control 0x11
624 addMessage(MIDI_NOTE_ON | 0x02, 0x10, 0x7F);
625 addMessage(MIDI_NOTE_ON | 0x02, 0x10, 0x00);
626
627 ConfigKey control("[Test]", "SomeControl");
628 MidiInputMappings mappings =
629 LearningUtils::guessMidiInputMappings(control, m_messages);
630 ASSERT_EQ(1, mappings.size());
631 EXPECT_EQ(MidiInputMapping(MidiKey(MIDI_NOTE_ON | 0x01, 0x10),
632 MidiOptions(),
633 control,
634 mappings.first().description),
635 mappings.first());
636 }
637