1 #include <QScopedPointer>
2
3 #include <gmock/gmock.h>
4
5 #include "test/mixxxtest.h"
6 #include "controllers/midi/midicontroller.h"
7 #include "controllers/midi/midicontrollerpreset.h"
8 #include "controllers/midi/midimessage.h"
9 #include "control/controlpushbutton.h"
10 #include "control/controlpotmeter.h"
11 #include "util/time.h"
12
13 class MockMidiController : public MidiController {
14 public:
MockMidiController(UserSettingsPointer pConfig)15 explicit MockMidiController(UserSettingsPointer pConfig)
16 : MidiController(pConfig) {
17 }
~MockMidiController()18 ~MockMidiController() override { }
19
20 MOCK_METHOD0(open, int());
21 MOCK_METHOD0(close, int());
22 MOCK_METHOD3(sendShortMsg, void(unsigned char status,
23 unsigned char byte1,
24 unsigned char byte2));
25 MOCK_METHOD1(send, void(const QByteArray& data));
26 MOCK_CONST_METHOD0(isPolling, bool());
27 };
28
29 class MidiControllerTest : public MixxxTest {
30 protected:
SetUp()31 void SetUp() override {
32 m_pController.reset(new MockMidiController(config()));
33 }
34
addMapping(const MidiInputMapping & mapping)35 void addMapping(const MidiInputMapping& mapping) {
36 m_preset.addInputMapping(mapping.key.key, mapping);
37 }
38
loadPreset(const MidiControllerPreset & preset)39 void loadPreset(const MidiControllerPreset& preset) {
40 m_pController->visit(&preset);
41 }
42
receive(unsigned char status,unsigned char control,unsigned char value)43 void receive(unsigned char status, unsigned char control,
44 unsigned char value) {
45 // TODO(rryan): This test doesn't care about timestamps.
46 m_pController->receive(status, control, value, mixxx::Time::elapsed());
47 }
48
49 MidiControllerPreset m_preset;
50 QScopedPointer<MockMidiController> m_pController;
51 };
52
TEST_F(MidiControllerTest,ReceiveMessage_PushButtonCO_PushOnOff)53 TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushOnOff) {
54 // Most MIDI controller send push-buttons as (NOTE_ON, 0x7F) for press and
55 // (NOTE_OFF, 0x00) for release.
56 ConfigKey key("[Channel1]", "hotcue_1_activate");
57 ControlPushButton cpb(key);
58
59 unsigned char channel = 0x01;
60 unsigned char control = 0x10;
61
62 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
63 MidiOptions(), key));
64 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | channel, control),
65 MidiOptions(), key));
66 loadPreset(m_preset);
67
68 // Receive an on/off, sets the control on/off with each press.
69 receive(MIDI_NOTE_ON | channel, control, 0x7F);
70 EXPECT_LT(0.0, cpb.get());
71 receive(MIDI_NOTE_OFF | channel, control, 0x00);
72 EXPECT_DOUBLE_EQ(0.0, cpb.get());
73
74 // Receive an on/off, sets the control on/off with each press.
75 receive(MIDI_NOTE_ON | channel, control, 0x7F);
76 EXPECT_LT(0.0, cpb.get());
77 receive(MIDI_NOTE_OFF | channel, control, 0x00);
78 EXPECT_DOUBLE_EQ(0.0, cpb.get());
79 }
80
TEST_F(MidiControllerTest,ReceiveMessage_PushButtonCO_PushOnOn)81 TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushOnOn) {
82 // Some MIDI controllers send push-buttons as (NOTE_ON, 0x7f) for press and
83 // (NOTE_ON, 0x00) for release.
84 ConfigKey key("[Channel1]", "hotcue_1_activate");
85 ControlPushButton cpb(key);
86
87 unsigned char channel = 0x01;
88 unsigned char control = 0x10;
89
90 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
91 MidiOptions(), key));
92 loadPreset(m_preset);
93
94 // Receive an on/off, sets the control on/off with each press.
95 receive(MIDI_NOTE_ON | channel, control, 0x7F);
96 EXPECT_LT(0.0, cpb.get());
97 receive(MIDI_NOTE_ON | channel, control, 0x00);
98 EXPECT_DOUBLE_EQ(0.0, cpb.get());
99
100 // Receive an on/off, sets the control on/off with each press.
101 receive(MIDI_NOTE_ON | channel, control, 0x7F);
102 EXPECT_LT(0.0, cpb.get());
103 receive(MIDI_NOTE_ON | channel, control, 0x00);
104 EXPECT_DOUBLE_EQ(0.0, cpb.get());
105 }
106
TEST_F(MidiControllerTest,ReceiveMessage_PushButtonCO_ToggleOnOff_ButtonMidiOption)107 TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_ToggleOnOff_ButtonMidiOption) {
108 // Using the button MIDI option allows you to use a MIDI toggle button as a
109 // push button.
110 ConfigKey key("[Channel1]", "hotcue_1_activate");
111 ControlPushButton cpb(key);
112
113 unsigned char channel = 0x01;
114 unsigned char control = 0x10;
115
116 MidiOptions options;
117 options.button = true;
118
119 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
120 options, key));
121 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | channel, control),
122 options, key));
123 loadPreset(m_preset);
124
125 // NOTE(rryan): This behavior is broken!
126
127 // Toggle the switch on, sets the push button on.
128 receive(MIDI_NOTE_ON | channel, control, 0x7F);
129 EXPECT_LT(0.0, cpb.get());
130
131 // The push button is stuck down here!
132
133 // Toggle the switch off, sets the push button off.
134 receive(MIDI_NOTE_OFF | channel, control, 0x00);
135 EXPECT_DOUBLE_EQ(0.0, cpb.get());
136 }
137
TEST_F(MidiControllerTest,ReceiveMessage_PushButtonCO_ToggleOnOff_SwitchMidiOption)138 TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_ToggleOnOff_SwitchMidiOption) {
139 // Using the switch MIDI option interprets a MIDI toggle button as a toggle
140 // button rather than a momentary push button.
141 ConfigKey key("[Channel1]", "hotcue_1_activate");
142 ControlPushButton cpb(key);
143
144 unsigned char channel = 0x01;
145 unsigned char control = 0x10;
146
147 MidiOptions options;
148 options.sw = true;
149
150 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
151 options, key));
152 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | channel, control),
153 options, key));
154 loadPreset(m_preset);
155
156 // NOTE(rryan): This behavior is broken!
157
158 // Toggle the switch on, sets the push button on.
159 receive(MIDI_NOTE_ON | channel, control, 0x7F);
160 EXPECT_LT(0.0, cpb.get());
161
162 // The push button is stuck down here!
163
164 // Toggle the switch off, sets the push button on again.
165 receive(MIDI_NOTE_OFF | channel, control, 0x00);
166 EXPECT_LT(0.0, cpb.get());
167
168 // NOTE(rryan): What is supposed to happen in this case? It's an open
169 // question I think. I think if you want to connect a switch MIDI control to
170 // a push button CO then the switch should directly set the CO. After all,
171 // the preset author asked for the switch to be interpreted as a switch. If
172 // they want the switch to act like a push button, they should use the
173 // button MIDI option.
174 //
175 // Most of our push buttons trigger behavior on press and do nothing on
176 // release, and most don't care about being "stuck down" except for hotcue
177 // and cue controls that have preview behavior.
178
179 // "reverse" is an example of a push button that is a push button because we
180 // want the default behavior to be momentary press and not toggle. If I
181 // mapped a switch to it, I would expect the switch to enable it (set it to
182 // 1) when the switch was enabled and set it to 0 when the switch was
183 // disabled. So I think we should change the switch option to behave like
184 // this.
185 }
186
TEST_F(MidiControllerTest,ReceiveMessage_PushButtonCO_PushCC)187 TEST_F(MidiControllerTest, ReceiveMessage_PushButtonCO_PushCC) {
188 // Some MIDI controllers (e.g. Korg nanoKONTROL) send momentary push-buttons
189 // as (CC, 0x7f) for press and (CC, 0x00) for release.
190 ConfigKey key("[Channel1]", "hotcue_1_activate");
191 ControlPushButton cpb(key);
192
193 unsigned char channel = 0x01;
194 unsigned char control = 0x10;
195
196 addMapping(MidiInputMapping(MidiKey(MIDI_CC | channel, control),
197 MidiOptions(), key));
198 loadPreset(m_preset);
199
200 // Receive an on/off, sets the control on/off with each press.
201 receive(MIDI_CC | channel, control, 0x7F);
202 EXPECT_LT(0.0, cpb.get());
203 receive(MIDI_CC | channel, control, 0x00);
204 EXPECT_DOUBLE_EQ(0.0, cpb.get());
205
206 // Receive an on/off, sets the control on/off with each press.
207 receive(MIDI_CC | channel, control, 0x7F);
208 EXPECT_LT(0.0, cpb.get());
209 receive(MIDI_CC | channel, control, 0x00);
210 EXPECT_DOUBLE_EQ(0.0, cpb.get());
211 }
212
TEST_F(MidiControllerTest,ReceiveMessage_ToggleCO_PushOnOff)213 TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushOnOff) {
214 // Most MIDI controller send push-buttons as (NOTE_ON, 0x7F) for press and
215 // (NOTE_OFF, 0x00) for release.
216 ConfigKey key("[Channel1]", "keylock");
217 ControlPushButton cpb(key);
218 cpb.setButtonMode(ControlPushButton::TOGGLE);
219
220 unsigned char channel = 0x01;
221 unsigned char control = 0x10;
222
223 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
224 MidiOptions(), key));
225 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | channel, control),
226 MidiOptions(), key));
227 loadPreset(m_preset);
228
229 // Receive an on/off, toggles the control.
230 receive(MIDI_NOTE_ON | channel, control, 0x7F);
231 receive(MIDI_NOTE_OFF | channel, control, 0x00);
232
233 EXPECT_LT(0.0, cpb.get());
234
235 // Receive an on/off, toggles the control.
236 receive(MIDI_NOTE_ON | channel, control, 0x7F);
237 receive(MIDI_NOTE_OFF | channel, control, 0x00);
238
239 EXPECT_DOUBLE_EQ(0.0, cpb.get());
240 }
241
TEST_F(MidiControllerTest,ReceiveMessage_ToggleCO_PushOnOn)242 TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushOnOn) {
243 // Some MIDI controllers send push-buttons as (NOTE_ON, 0x7f) for press and
244 // (NOTE_ON, 0x00) for release.
245 ConfigKey key("[Channel1]", "keylock");
246 ControlPushButton cpb(key);
247 cpb.setButtonMode(ControlPushButton::TOGGLE);
248
249 unsigned char channel = 0x01;
250 unsigned char control = 0x10;
251
252 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
253 MidiOptions(), key));
254 loadPreset(m_preset);
255
256 // Receive an on/off, toggles the control.
257 receive(MIDI_NOTE_ON | channel, control, 0x7F);
258 receive(MIDI_NOTE_ON | channel, control, 0x00);
259
260 EXPECT_LT(0.0, cpb.get());
261
262 // Receive an on/off, toggles the control.
263 receive(MIDI_NOTE_ON | channel, control, 0x7F);
264 receive(MIDI_NOTE_ON | channel, control, 0x00);
265
266 EXPECT_DOUBLE_EQ(0.0, cpb.get());
267 }
268
TEST_F(MidiControllerTest,ReceiveMessage_ToggleCO_ToggleOnOff_ButtonMidiOption)269 TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_ButtonMidiOption) {
270 // Using the button MIDI option allows you to use a MIDI toggle button as a
271 // push button.
272 ConfigKey key("[Channel1]", "keylock");
273 ControlPushButton cpb(key);
274 cpb.setButtonMode(ControlPushButton::TOGGLE);
275
276 unsigned char channel = 0x01;
277 unsigned char control = 0x10;
278
279 MidiOptions options;
280 options.button = true;
281
282 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
283 options, key));
284 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | channel, control),
285 options, key));
286 loadPreset(m_preset);
287
288 // NOTE(rryan): If the intended behavior of the button MIDI option is to
289 // make a toggle MIDI button act like a push button then this isn't
290 // working. The toggle on toggles the CO but the toggle off does nothing.
291
292 // Toggle the switch on, since it is interpreted as a button press it
293 // toggles the button on.
294 receive(MIDI_NOTE_ON | channel, control, 0x7F);
295 EXPECT_LT(0.0, cpb.get());
296
297 // Toggle the switch off, since it is interpreted as a button release it
298 // does nothing to the toggle button.
299 receive(MIDI_NOTE_OFF | channel, control, 0x00);
300 EXPECT_LT(0.0, cpb.get());
301 }
302
TEST_F(MidiControllerTest,ReceiveMessage_ToggleCO_ToggleOnOff_SwitchMidiOption)303 TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_ToggleOnOff_SwitchMidiOption) {
304 // Using the switch MIDI option interprets a MIDI toggle button as a toggle
305 // button rather than a momentary push button.
306 ConfigKey key("[Channel1]", "keylock");
307 ControlPushButton cpb(key);
308 cpb.setButtonMode(ControlPushButton::TOGGLE);
309
310 unsigned char channel = 0x01;
311 unsigned char control = 0x10;
312
313 MidiOptions options;
314 options.sw = true;
315
316 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_ON | channel, control),
317 options, key));
318 addMapping(MidiInputMapping(MidiKey(MIDI_NOTE_OFF | channel, control),
319 options, key));
320 loadPreset(m_preset);
321
322 // NOTE(rryan): If the intended behavior of switch MIDI option is to make a
323 // toggle MIDI button act like a toggle button then this isn't working. The
324 // toggle on presses the CO and the toggle off presses the CO. This toggles
325 // the control but allows it to get out of sync.
326
327 // Toggle the switch on, since it is interpreted as a button press it
328 // toggles the control on.
329 receive(MIDI_NOTE_ON | channel, control, 0x7F);
330 EXPECT_LT(0.0, cpb.get());
331
332 // Toggle the switch off, since it is interpreted as a button press it
333 // toggles the control off.
334 receive(MIDI_NOTE_OFF | channel, control, 0x00);
335 EXPECT_DOUBLE_EQ(0.0, cpb.get());
336
337 // Meanwhile, the GUI toggles the control on again.
338 // NOTE(rryan): Now the MIDI toggle button is out of sync with the toggle
339 // CO.
340 cpb.set(1.0);
341
342 // Toggle the switch on, since it is interpreted as a button press it
343 // toggles the control off (since it was on).
344 receive(MIDI_NOTE_ON | channel, control, 0x7F);
345 EXPECT_DOUBLE_EQ(0.0, cpb.get());
346
347 // Toggle the switch off, since it is interpreted as a button press it
348 // toggles the control on (since it was off).
349 receive(MIDI_NOTE_OFF | channel, control, 0x00);
350 EXPECT_LT(0.0, cpb.get());
351 }
352
TEST_F(MidiControllerTest,ReceiveMessage_ToggleCO_PushCC)353 TEST_F(MidiControllerTest, ReceiveMessage_ToggleCO_PushCC) {
354 // Some MIDI controllers (e.g. Korg nanoKONTROL) send momentary push-buttons
355 // as (CC, 0x7f) for press and (CC, 0x00) for release.
356 ConfigKey key("[Channel1]", "keylock");
357 ControlPushButton cpb(key);
358 cpb.setButtonMode(ControlPushButton::TOGGLE);
359
360 unsigned char channel = 0x01;
361 unsigned char control = 0x10;
362
363 addMapping(MidiInputMapping(MidiKey(MIDI_CC | channel, control),
364 MidiOptions(), key));
365 loadPreset(m_preset);
366
367 // Receive an on/off, toggles the control.
368 receive(MIDI_CC | channel, control, 0x7F);
369 receive(MIDI_CC | channel, control, 0x00);
370
371 EXPECT_LT(0.0, cpb.get());
372
373 // Receive an on/off, toggles the control.
374 receive(MIDI_CC | channel, control, 0x7F);
375 receive(MIDI_CC | channel, control, 0x00);
376
377 EXPECT_DOUBLE_EQ(0.0, cpb.get());
378 }
379
TEST_F(MidiControllerTest,ReceiveMessage_PotMeterCO_7BitCC)380 TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_7BitCC) {
381 ConfigKey key("[Channel1]", "playposition");
382
383 const double kMinValue = -1234.5;
384 const double kMaxValue = 678.9;
385 const double kMiddleValue = (kMinValue + kMaxValue) * 0.5;
386 ControlPotmeter potmeter(key, kMinValue, kMaxValue);
387
388 unsigned char channel = 0x01;
389 unsigned char control = 0x10;
390
391 addMapping(MidiInputMapping(MidiKey(MIDI_CC | channel, control),
392 MidiOptions(), key));
393 loadPreset(m_preset);
394
395 // Receive a 0, MIDI parameter should map to the min value.
396 receive(MIDI_CC | channel, control, 0x00);
397 EXPECT_DOUBLE_EQ(kMinValue, potmeter.get());
398
399 // Receive a 0x7F, MIDI parameter should map to the potmeter max value.
400 receive(MIDI_CC | channel, control, 0x7F);
401 EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get());
402
403 // Receive a 0x40, MIDI parameter should map to the potmeter middle value.
404 receive(MIDI_CC | channel, control, 0x40);
405 EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get());
406 }
407
TEST_F(MidiControllerTest,ReceiveMessage_PotMeterCO_14BitCC)408 TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitCC) {
409 ConfigKey key("[Channel1]", "playposition");
410
411 const double kMinValue = -1234.5;
412 const double kMaxValue = 678.9;
413 const double kMiddleValue = (kMinValue + kMaxValue) * 0.5;
414 ControlPotmeter potmeter(key, kMinValue, kMaxValue);
415 potmeter.set(0);
416
417 unsigned char channel = 0x01;
418 unsigned char lsb_control = 0x10;
419 unsigned char msb_control = 0x11;
420
421 MidiOptions lsb;
422 lsb.fourteen_bit_lsb = true;
423
424 MidiOptions msb;
425 msb.fourteen_bit_msb = true;
426
427 addMapping(MidiInputMapping(MidiKey(MIDI_CC | channel, lsb_control),
428 lsb, key));
429 addMapping(MidiInputMapping(MidiKey(MIDI_CC | channel, msb_control),
430 msb, key));
431 loadPreset(m_preset);
432
433 // If kMinValue or kMaxValue are such that the middle value is 0 then the
434 // set(0) commands below allow us to hide failures.
435 ASSERT_NE(0.0, kMiddleValue);
436
437 // Receive a 0x0000 (lsb-first), MIDI parameter should map to the min value.
438 potmeter.set(0);
439 receive(MIDI_CC | channel, lsb_control, 0x00);
440 receive(MIDI_CC | channel, msb_control, 0x00);
441 EXPECT_DOUBLE_EQ(kMinValue, potmeter.get());
442
443 // Receive a 0x0000 (msb-first), MIDI parameter should map to the min value.
444 potmeter.set(0);
445 receive(MIDI_CC | channel, msb_control, 0x00);
446 receive(MIDI_CC | channel, lsb_control, 0x00);
447 EXPECT_DOUBLE_EQ(kMinValue, potmeter.get());
448
449 // Receive a 0x3FFF (lsb-first), MIDI parameter should map to the max value.
450 potmeter.set(0);
451 receive(MIDI_CC | channel, lsb_control, 0x7F);
452 receive(MIDI_CC | channel, msb_control, 0x7F);
453 EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get());
454
455 // Receive a 0x3FFF (msb-first), MIDI parameter should map to the max value.
456 potmeter.set(0);
457 receive(MIDI_CC | channel, msb_control, 0x7F);
458 receive(MIDI_CC | channel, lsb_control, 0x7F);
459 EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get());
460
461 // Receive a 0x2000 (lsb-first), MIDI parameter should map to the middle
462 // value.
463 potmeter.set(0);
464 receive(MIDI_CC | channel, lsb_control, 0x00);
465 receive(MIDI_CC | channel, msb_control, 0x40);
466 EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get());
467
468 // Receive a 0x2000 (msb-first), MIDI parameter should map to the middle
469 // value.
470 potmeter.set(0);
471 receive(MIDI_CC | channel, msb_control, 0x40);
472 receive(MIDI_CC | channel, lsb_control, 0x00);
473 EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get());
474
475 // Check the 14-bit resolution is actually present. Receive a 0x2001
476 // (msb-first), MIDI parameter should map to the middle value plus a tiny
477 // amount. Scaling is not quite linear for MIDI parameters so just check
478 // that incrementing the LSB by 1 is greater than the middle value.
479 potmeter.set(0);
480 receive(MIDI_CC | channel, msb_control, 0x40);
481 receive(MIDI_CC | channel, lsb_control, 0x01);
482 EXPECT_LT(kMiddleValue, potmeter.get());
483
484 // Check the 14-bit resolution is actually present. Receive a 0x2001
485 // (lsb-first), MIDI parameter should map to the middle value plus a tiny
486 // amount. Scaling is not quite linear for MIDI parameters so just check
487 // that incrementing the LSB by 1 is greater than the middle value.
488 potmeter.set(0);
489 receive(MIDI_CC | channel, lsb_control, 0x01);
490 receive(MIDI_CC | channel, msb_control, 0x40);
491 EXPECT_LT(kMiddleValue, potmeter.get());
492 }
493
TEST_F(MidiControllerTest,ReceiveMessage_PotMeterCO_14BitPitchBend)494 TEST_F(MidiControllerTest, ReceiveMessage_PotMeterCO_14BitPitchBend) {
495 ConfigKey key("[Channel1]", "rate");
496
497 const double kMinValue = -1234.5;
498 const double kMaxValue = 678.9;
499 const double kMiddleValue = (kMinValue + kMaxValue) * 0.5;
500 ControlPotmeter potmeter(key, kMinValue, kMaxValue);
501 unsigned char channel = 0x01;
502
503 // The control is ignored in mappings for messages where the control is part
504 // of the payload.
505 addMapping(MidiInputMapping(MidiKey(MIDI_PITCH_BEND | channel, 0xFF),
506 MidiOptions(), key));
507 loadPreset(m_preset);
508
509 // Receive a 0x0000, MIDI parameter should map to the min value.
510 receive(MIDI_PITCH_BEND | channel, 0x00, 0x00);
511 EXPECT_DOUBLE_EQ(kMinValue, potmeter.get());
512
513 // Receive a 0x3FFF, MIDI parameter should map to the potmeter max value.
514 receive(MIDI_PITCH_BEND | channel, 0x7F, 0x7F);
515 EXPECT_DOUBLE_EQ(kMaxValue, potmeter.get());
516
517 // Receive a 0x2000, MIDI parameter should map to the potmeter middle value.
518 receive(MIDI_PITCH_BEND | channel, 0x00, 0x40);
519 EXPECT_DOUBLE_EQ(kMiddleValue, potmeter.get());
520
521 // Check the 14-bit resolution is actually present. Receive a 0x2001, MIDI
522 // parameter should map to the middle value plus a tiny amount. Scaling is
523 // not quite linear for MIDI parameters so just check that incrementing the
524 // LSB by 1 is greater than the middle value.
525 receive(MIDI_PITCH_BEND | channel, 0x01, 0x40);
526 EXPECT_LT(kMiddleValue, potmeter.get());
527 }
528