surface_master 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +13 -0
  5. data/LICENSE +21 -0
  6. data/README.md +48 -0
  7. data/Rakefile +10 -0
  8. data/debug_tools/Numark_Orbit_Pad_Coloring.mmon +98 -0
  9. data/debug_tools/OrbitLightingExample.json +662 -0
  10. data/debug_tools/Orbit_Color_Test.json +662 -0
  11. data/debug_tools/Orbit_Colors_And_Reset.1.raw +0 -0
  12. data/debug_tools/Orbit_Colors_And_Reset.1.txt +82 -0
  13. data/debug_tools/Orbit_Colors_And_Reset.2.raw +0 -0
  14. data/debug_tools/Orbit_Colors_And_Reset.2.txt +2 -0
  15. data/debug_tools/Orbit_Colors_And_Reset.3.raw +0 -0
  16. data/debug_tools/Orbit_Colors_And_Reset.3.txt +82 -0
  17. data/debug_tools/Orbit_Colors_And_Reset.mmon +93 -0
  18. data/debug_tools/Orbit_Preset.1.raw +0 -0
  19. data/debug_tools/Orbit_Preset.1.txt +82 -0
  20. data/debug_tools/Orbit_Preset.2.raw +0 -0
  21. data/debug_tools/Orbit_Preset.2.txt +2 -0
  22. data/debug_tools/Orbit_Preset.mmon +72 -0
  23. data/debug_tools/compare.sh +12 -0
  24. data/debug_tools/decode.rb +14 -0
  25. data/debug_tools/extract_midi_monitor_sample.sh +33 -0
  26. data/docs/Numark_Orbit_QuickRef.md +50 -0
  27. data/examples/launchpad_testbed.rb +141 -0
  28. data/examples/monitor.rb +61 -0
  29. data/examples/orbit_testbed.rb +62 -0
  30. data/lib/control_center.rb +26 -0
  31. data/lib/surface_master/device.rb +90 -0
  32. data/lib/surface_master/errors.rb +27 -0
  33. data/lib/surface_master/interaction.rb +133 -0
  34. data/lib/surface_master/launchpad/device.rb +159 -0
  35. data/lib/surface_master/launchpad/errors.rb +11 -0
  36. data/lib/surface_master/launchpad/interaction.rb +86 -0
  37. data/lib/surface_master/launchpad/midi_codes.rb +51 -0
  38. data/lib/surface_master/logging.rb +15 -0
  39. data/lib/surface_master/orbit/device.rb +160 -0
  40. data/lib/surface_master/orbit/interaction.rb +29 -0
  41. data/lib/surface_master/orbit/midi_codes.rb +31 -0
  42. data/lib/surface_master/version.rb +3 -0
  43. data/mappings/Orbit_Preset.json +662 -0
  44. data/surface_master.gemspec +26 -0
  45. data/test/helper.rb +44 -0
  46. data/test/test_device.rb +530 -0
  47. data/test/test_interaction.rb +456 -0
  48. metadata +121 -0
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "surface_master/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "surface_master"
7
+ s.version = SurfaceMaster::VERSION
8
+ s.authors = ["Jon Frisby"]
9
+ s.email = ["jfrisby@mrjoy.com"]
10
+ s.homepage = "https://github.com/MrJoy/surface_master"
11
+ s.summary = %q{A gem for accessing various MIDI control surfaces programmatically.}
12
+ s.description = %q{This gem provides an interface to access Novation's LaunchPad Mark 2, and Numark's Orbit programmatically. LEDs can be lit and button presses can be read.}
13
+ # TODO: Update docs to give credit to Thomas Jachmann (self@thomasjachmann.com) for his `launchpad` gem.
14
+
15
+ s.required_ruby_version = ">= 2.2.0"
16
+
17
+ s.add_dependency "portmidi", ">= 0.0.6"
18
+ s.add_dependency "ffi"
19
+
20
+ # s.has_rdoc = true
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+
4
+ begin
5
+ require 'minitest/reporters'
6
+ MiniTest::Reporters.use!
7
+ rescue LoadError
8
+ # ignore when it's not there - must be ruby 1.8
9
+ end
10
+
11
+ require 'mocha/setup'
12
+
13
+ require 'launchpad'
14
+
15
+ # mock Portmidi for tests
16
+ module Portmidi
17
+
18
+ class Input
19
+ attr_accessor :device_id
20
+ def initialize(device_id)
21
+ self.device_id = device_id
22
+ end
23
+ def read(*args); nil; end
24
+ def close; nil; end
25
+ end
26
+
27
+ class Output
28
+ attr_accessor :device_id
29
+ def initialize(device_id)
30
+ self.device_id = device_id
31
+ end
32
+ def write(*args); nil; end
33
+ def close; nil; end
34
+ end
35
+
36
+ def self.input_devices; mock_devices; end
37
+ def self.output_devices; mock_devices; end
38
+ def self.start; end
39
+
40
+ end
41
+
42
+ def mock_devices(opts = {})
43
+ [Portmidi::Device.new(opts[:id] || 1, 0, 0, opts[:name] || 'Launchpad MK2')]
44
+ end
@@ -0,0 +1,530 @@
1
+ require "helper"
2
+
3
+ describe SurfaceMaster::Launchpad::Device do
4
+
5
+ CONTROL_BUTTONS = {
6
+ :up => 0x68,
7
+ :down => 0x69,
8
+ :left => 0x6A,
9
+ :right => 0x6B,
10
+ :session => 0x6C,
11
+ :user1 => 0x6D,
12
+ :user2 => 0x6E,
13
+ :mixer => 0x6F
14
+ }
15
+ SCENE_BUTTONS = {
16
+ :scene1 => 0x08,
17
+ :scene2 => 0x18,
18
+ :scene3 => 0x28,
19
+ :scene4 => 0x38,
20
+ :scene5 => 0x48,
21
+ :scene6 => 0x58,
22
+ :scene7 => 0x68,
23
+ :scene8 => 0x78
24
+ }
25
+ COLORS = {
26
+ nil => 0, 0 => 0, :off => 0,
27
+ 1 => 1, :lo => 1, :low => 1,
28
+ 2 => 2, :med => 2, :medium => 2,
29
+ 3 => 3, :hi => 3, :high => 3
30
+ }
31
+ STATES = {
32
+ :down => 127,
33
+ :up => 0
34
+ }
35
+
36
+ def expects_output(device, *args)
37
+ args = [args] unless args.first.is_a?(Array)
38
+ messages = args.collect {|data| {:message => data, :timestamp => 0}}
39
+ device.instance_variable_get('@output').expects(:write).with(messages)
40
+ end
41
+
42
+ def stub_input(device, *args)
43
+ device.instance_variable_get('@input').stubs(:read).returns(args)
44
+ end
45
+
46
+ describe '#initialize' do
47
+
48
+ it 'tries to initialize both input and output when not specified' do
49
+ Portmidi.expects(:input_devices).returns(mock_devices)
50
+ Portmidi.expects(:output_devices).returns(mock_devices)
51
+ d = Launchpad::Device.new
52
+ refute_nil d.instance_variable_get('@input')
53
+ refute_nil d.instance_variable_get('@output')
54
+ end
55
+
56
+ it 'does not try to initialize input when set to false' do
57
+ Portmidi.expects(:input_devices).never
58
+ d = Launchpad::Device.new(:input => false)
59
+ assert_nil d.instance_variable_get('@input')
60
+ refute_nil d.instance_variable_get('@output')
61
+ end
62
+
63
+ it 'does not try to initialize output when set to false' do
64
+ Portmidi.expects(:output_devices).never
65
+ d = Launchpad::Device.new(:output => false)
66
+ refute_nil d.instance_variable_get('@input')
67
+ assert_nil d.instance_variable_get('@output')
68
+ end
69
+
70
+ it 'does not try to initialize any of both when set to false' do
71
+ Portmidi.expects(:input_devices).never
72
+ Portmidi.expects(:output_devices).never
73
+ d = Launchpad::Device.new(:input => false, :output => false)
74
+ assert_nil d.instance_variable_get('@input')
75
+ assert_nil d.instance_variable_get('@output')
76
+ end
77
+
78
+ it 'initializes the correct input output devices when specified by name' do
79
+ Portmidi.stubs(:input_devices).returns(mock_devices(:id => 4, :name => 'Launchpad Name'))
80
+ Portmidi.stubs(:output_devices).returns(mock_devices(:id => 5, :name => 'Launchpad Name'))
81
+ d = Launchpad::Device.new(:device_name => 'Launchpad Name')
82
+ assert_equal Portmidi::Input, (input = d.instance_variable_get('@input')).class
83
+ assert_equal 4, input.device_id
84
+ assert_equal Portmidi::Output, (output = d.instance_variable_get('@output')).class
85
+ assert_equal 5, output.device_id
86
+ end
87
+
88
+ it 'initializes the correct input output devices when specified by id' do
89
+ Portmidi.stubs(:input_devices).returns(mock_devices(:id => 4))
90
+ Portmidi.stubs(:output_devices).returns(mock_devices(:id => 5))
91
+ d = Launchpad::Device.new(:input_device_id => 4, :output_device_id => 5, :device_name => 'nonexistant')
92
+ assert_equal Portmidi::Input, (input = d.instance_variable_get('@input')).class
93
+ assert_equal 4, input.device_id
94
+ assert_equal Portmidi::Output, (output = d.instance_variable_get('@output')).class
95
+ assert_equal 5, output.device_id
96
+ end
97
+
98
+ it 'raises NoSuchDeviceError when requested input device does not exist' do
99
+ assert_raises Launchpad::NoSuchDeviceError do
100
+ Portmidi.stubs(:input_devices).returns(mock_devices(:name => 'Launchpad Input'))
101
+ Launchpad::Device.new
102
+ end
103
+ end
104
+
105
+ it 'raises NoSuchDeviceError when requested output device does not exist' do
106
+ assert_raises Launchpad::NoSuchDeviceError do
107
+ Portmidi.stubs(:output_devices).returns(mock_devices(:name => 'Launchpad Output'))
108
+ Launchpad::Device.new
109
+ end
110
+ end
111
+
112
+ it 'raises DeviceBusyError when requested input device is busy' do
113
+ assert_raises Launchpad::DeviceBusyError do
114
+ Portmidi::Input.stubs(:new).raises(RuntimeError)
115
+ Launchpad::Device.new
116
+ end
117
+ end
118
+
119
+ it 'raises DeviceBusyError when requested output device is busy' do
120
+ assert_raises Launchpad::DeviceBusyError do
121
+ Portmidi::Output.stubs(:new).raises(RuntimeError)
122
+ Launchpad::Device.new
123
+ end
124
+ end
125
+
126
+ it 'stores the logger given' do
127
+ logger = Logger.new(nil)
128
+ device = Launchpad::Device.new(:logger => logger)
129
+ assert_same logger, device.logger
130
+ end
131
+
132
+ end
133
+
134
+ describe '#close' do
135
+
136
+ it 'does not fail when neither input nor output are there' do
137
+ Launchpad::Device.new(:input => false, :output => false).close
138
+ end
139
+
140
+ describe 'with input and output devices' do
141
+
142
+ before do
143
+ Portmidi::Input.stubs(:new).returns(@input = mock('input'))
144
+ Portmidi::Output.stubs(:new).returns(@output = mock('output', :write => nil))
145
+ @device = Launchpad::Device.new
146
+ end
147
+
148
+ it 'closes input/output and raise NoInputAllowedError/NoOutputAllowedError on subsequent read/write accesses' do
149
+ @input.expects(:close)
150
+ @output.expects(:close)
151
+ @device.close
152
+ assert_raises Launchpad::NoInputAllowedError do
153
+ @device.read_pending_actions
154
+ end
155
+ assert_raises Launchpad::NoOutputAllowedError do
156
+ @device.change(:session)
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+
164
+ describe '#closed?' do
165
+
166
+ it 'returns true when neither input nor output are there' do
167
+ assert Launchpad::Device.new(:input => false, :output => false).closed?
168
+ end
169
+
170
+ it 'returns false when initialized with input' do
171
+ assert !Launchpad::Device.new(:input => true, :output => false).closed?
172
+ end
173
+
174
+ it 'returns false when initialized with output' do
175
+ assert !Launchpad::Device.new(:input => false, :output => true).closed?
176
+ end
177
+
178
+ it 'returns false when initialized with both but true after calling close' do
179
+ d = Launchpad::Device.new
180
+ assert !d.closed?
181
+ d.close
182
+ assert d.closed?
183
+ end
184
+
185
+ end
186
+
187
+ {
188
+ :reset => [0xB0, 0x00, 0x00],
189
+ :flashing_on => [0xB0, 0x00, 0x20],
190
+ :flashing_off => [0xB0, 0x00, 0x21],
191
+ :flashing_auto => [0xB0, 0x00, 0x28]
192
+ }.each do |method, codes|
193
+ describe "##{method}" do
194
+
195
+ it 'raises NoOutputAllowedError when not initialized with output' do
196
+ assert_raises Launchpad::NoOutputAllowedError do
197
+ Launchpad::Device.new(:output => false).send(method)
198
+ end
199
+ end
200
+
201
+ it "sends #{codes.inspect}" do
202
+ d = Launchpad::Device.new
203
+ expects_output(d, *codes)
204
+ d.send(method)
205
+ end
206
+
207
+ end
208
+ end
209
+
210
+ describe '#test_leds' do
211
+
212
+ it 'raises NoOutputAllowedError when not initialized with output' do
213
+ assert_raises Launchpad::NoOutputAllowedError do
214
+ Launchpad::Device.new(:output => false).test_leds
215
+ end
216
+ end
217
+
218
+ describe 'initialized with output' do
219
+
220
+ before do
221
+ @device = Launchpad::Device.new(:input => false)
222
+ end
223
+
224
+ it 'returns nil' do
225
+ assert_nil @device.test_leds
226
+ end
227
+
228
+ COLORS.merge(nil => 3).each do |name, value|
229
+ if value == 0
230
+ it "sends 0xB0, 0x00, 0x00 when given #{name}" do
231
+ expects_output(@device, 0xB0, 0x00, 0x00)
232
+ @device.test_leds(value)
233
+ end
234
+ else
235
+ it "sends 0xB0, 0x00, 0x7C + #{value} when given #{name}" do
236
+ d = Launchpad::Device.new
237
+ expects_output(@device, 0xB0, 0x00, 0x7C + value)
238
+ value.nil? ? @device.test_leds : @device.test_leds(value)
239
+ end
240
+ end
241
+ end
242
+
243
+ end
244
+
245
+ end
246
+
247
+ describe '#change' do
248
+
249
+ it 'raises NoOutputAllowedError when not initialized with output' do
250
+ assert_raises Launchpad::NoOutputAllowedError do
251
+ Launchpad::Device.new(:output => false).change(:up)
252
+ end
253
+ end
254
+
255
+ describe 'initialized with output' do
256
+
257
+ before do
258
+ @device = Launchpad::Device.new(:input => false)
259
+ end
260
+
261
+ it 'returns nil' do
262
+ assert_nil @device.change(:up)
263
+ end
264
+
265
+ describe 'control buttons' do
266
+ CONTROL_BUTTONS.each do |type, value|
267
+ it "sends 0xB0, #{value}, 12 when given #{type}" do
268
+ expects_output(@device, 0xB0, value, 12)
269
+ @device.change(type)
270
+ end
271
+ end
272
+ end
273
+
274
+ describe 'scene buttons' do
275
+ SCENE_BUTTONS.each do |type, value|
276
+ it "sends 0x90, #{value}, 12 when given #{type}" do
277
+ expects_output(@device, 0x90, value, 12)
278
+ @device.change(type)
279
+ end
280
+ end
281
+ end
282
+
283
+ describe 'grid buttons' do
284
+ 8.times do |x|
285
+ 8.times do |y|
286
+ it "sends 0x90, #{16 * y + x}, 12 when given :grid, :x => #{x}, :y => #{y}" do
287
+ expects_output(@device, 0x90, 16 * y + x, 12)
288
+ @device.change(:grid, :x => x, :y => y)
289
+ end
290
+ end
291
+ end
292
+
293
+ it 'raises NoValidGridCoordinatesError if x is not specified' do
294
+ assert_raises Launchpad::NoValidGridCoordinatesError do
295
+ @device.change(:grid, :y => 1)
296
+ end
297
+ end
298
+
299
+ it 'raises NoValidGridCoordinatesError if x is below 0' do
300
+ assert_raises Launchpad::NoValidGridCoordinatesError do
301
+ @device.change(:grid, :x => -1, :y => 1)
302
+ end
303
+ end
304
+
305
+ it 'raises NoValidGridCoordinatesError if x is above 7' do
306
+ assert_raises Launchpad::NoValidGridCoordinatesError do
307
+ @device.change(:grid, :x => 8, :y => 1)
308
+ end
309
+ end
310
+
311
+ it 'raises NoValidGridCoordinatesError if y is not specified' do
312
+ assert_raises Launchpad::NoValidGridCoordinatesError do
313
+ @device.change(:grid, :x => 1)
314
+ end
315
+ end
316
+
317
+ it 'raises NoValidGridCoordinatesError if y is below 0' do
318
+ assert_raises Launchpad::NoValidGridCoordinatesError do
319
+ @device.change(:grid, :x => 1, :y => -1)
320
+ end
321
+ end
322
+
323
+ it 'raises NoValidGridCoordinatesError if y is above 7' do
324
+ assert_raises Launchpad::NoValidGridCoordinatesError do
325
+ @device.change(:grid, :x => 1, :y => 8)
326
+ end
327
+ end
328
+
329
+ end
330
+
331
+ describe 'colors' do
332
+ COLORS.each do |red_key, red_value|
333
+ COLORS.each do |green_key, green_value|
334
+ it "sends 0x90, 0, #{16 * green_value + red_value + 12} when given :red => #{red_key}, :green => #{green_key}" do
335
+ expects_output(@device, 0x90, 0, 16 * green_value + red_value + 12)
336
+ @device.change(:grid, :x => 0, :y => 0, :red => red_key, :green => green_key)
337
+ end
338
+ end
339
+ end
340
+
341
+ it 'raises NoValidBrightnessError if red is below 0' do
342
+ assert_raises Launchpad::NoValidBrightnessError do
343
+ @device.change(:grid, :x => 0, :y => 0, :red => -1)
344
+ end
345
+ end
346
+
347
+ it 'raises NoValidBrightnessError if red is above 3' do
348
+ assert_raises Launchpad::NoValidBrightnessError do
349
+ @device.change(:grid, :x => 0, :y => 0, :red => 4)
350
+ end
351
+ end
352
+
353
+ it 'raises NoValidBrightnessError if red is an unknown symbol' do
354
+ assert_raises Launchpad::NoValidBrightnessError do
355
+ @device.change(:grid, :x => 0, :y => 0, :red => :unknown)
356
+ end
357
+ end
358
+
359
+ it 'raises NoValidBrightnessError if green is below 0' do
360
+ assert_raises Launchpad::NoValidBrightnessError do
361
+ @device.change(:grid, :x => 0, :y => 0, :green => -1)
362
+ end
363
+ end
364
+
365
+ it 'raises NoValidBrightnessError if green is above 3' do
366
+ assert_raises Launchpad::NoValidBrightnessError do
367
+ @device.change(:grid, :x => 0, :y => 0, :green => 4)
368
+ end
369
+ end
370
+
371
+ it 'raises NoValidBrightnessError if green is an unknown symbol' do
372
+ assert_raises Launchpad::NoValidBrightnessError do
373
+ @device.change(:grid, :x => 0, :y => 0, :green => :unknown)
374
+ end
375
+ end
376
+
377
+ end
378
+
379
+ describe 'mode' do
380
+
381
+ it 'sends color + 12 when nothing given' do
382
+ expects_output(@device, 0x90, 0, 12)
383
+ @device.change(:grid, :x => 0, :y => 0, :red => 0, :green => 0)
384
+ end
385
+
386
+ it 'sends color + 12 when given :normal' do
387
+ expects_output(@device, 0x90, 0, 12)
388
+ @device.change(:grid, :x => 0, :y => 0, :red => 0, :green => 0, :mode => :normal)
389
+ end
390
+
391
+ it 'sends color + 8 when given :flashing' do
392
+ expects_output(@device, 0x90, 0, 8)
393
+ @device.change(:grid, :x => 0, :y => 0, :red => 0, :green => 0, :mode => :flashing)
394
+ end
395
+
396
+ it 'sends color when given :buffering' do
397
+ expects_output(@device, 0x90, 0, 0)
398
+ @device.change(:grid, :x => 0, :y => 0, :red => 0, :green => 0, :mode => :buffering)
399
+ end
400
+
401
+ end
402
+
403
+ end
404
+
405
+ end
406
+
407
+ describe '#change_all' do
408
+
409
+ it 'raises NoOutputAllowedError when not initialized with output' do
410
+ assert_raises Launchpad::NoOutputAllowedError do
411
+ Launchpad::Device.new(:output => false).change_all
412
+ end
413
+ end
414
+
415
+ describe 'initialized with output' do
416
+
417
+ before do
418
+ @device = Launchpad::Device.new(:input => false)
419
+ end
420
+
421
+ it 'returns nil' do
422
+ assert_nil @device.change_all([0])
423
+ end
424
+
425
+ it 'fills colors with 0, set grid layout to XY and flush colors' do
426
+ expects_output(@device, 0xB0, 0, 0x01)
427
+ expects_output(@device, *([[0x92, 17, 17]] * 20 + [[0x92, 12, 12]] * 20))
428
+ @device.change_all([5] * 40)
429
+ end
430
+
431
+ it 'cuts off exceeding colors, set grid layout to XY and flush colors' do
432
+ expects_output(@device, 0xB0, 0, 0x01)
433
+ expects_output(@device, *([[0x92, 17, 17]] * 40))
434
+ @device.change_all([5] * 100)
435
+ end
436
+
437
+ end
438
+
439
+ end
440
+
441
+ describe '#buffering_mode' do
442
+
443
+ it 'raises NoOutputAllowedError when not initialized with output' do
444
+ assert_raises Launchpad::NoOutputAllowedError do
445
+ Launchpad::Device.new(:output => false).buffering_mode
446
+ end
447
+ end
448
+
449
+ {
450
+ nil => [0xB0, 0x00, 0x20],
451
+ {} => [0xB0, 0x00, 0x20],
452
+ {:display_buffer => 1} => [0xB0, 0x00, 0x21],
453
+ {:update_buffer => 1} => [0xB0, 0x00, 0x24],
454
+ {:copy => true} => [0xB0, 0x00, 0x30],
455
+ {:flashing => true} => [0xB0, 0x00, 0x28],
456
+ {
457
+ :display_buffer => 1,
458
+ :update_buffer => 1,
459
+ :copy => true,
460
+ :flashing => true
461
+ } => [0xB0, 0x00, 0x3D]
462
+ }.each do |opts, codes|
463
+ it "sends #{codes.inspect} when called with #{opts.inspect}" do
464
+ d = Launchpad::Device.new
465
+ expects_output(d, *codes)
466
+ d.buffering_mode(opts)
467
+ end
468
+ end
469
+
470
+ end
471
+
472
+ describe '#read_pending_actions' do
473
+
474
+ it 'raises NoInputAllowedError when not initialized with input' do
475
+ assert_raises Launchpad::NoInputAllowedError do
476
+ Launchpad::Device.new(:input => false).read_pending_actions
477
+ end
478
+ end
479
+
480
+ describe 'initialized with input' do
481
+
482
+ before do
483
+ @device = Launchpad::Device.new(:output => false)
484
+ end
485
+
486
+ describe 'control buttons' do
487
+ CONTROL_BUTTONS.each do |type, value|
488
+ STATES.each do |state, velocity|
489
+ it "builds proper action for control button #{type}, #{state}" do
490
+ stub_input(@device, {:timestamp => 0, :message => [0xB0, value, velocity]})
491
+ assert_equal [{:timestamp => 0, :state => state, :type => type}], @device.read_pending_actions
492
+ end
493
+ end
494
+ end
495
+ end
496
+
497
+ describe 'scene buttons' do
498
+ SCENE_BUTTONS.each do |type, value|
499
+ STATES.each do |state, velocity|
500
+ it "builds proper action for scene button #{type}, #{state}" do
501
+ stub_input(@device, {:timestamp => 0, :message => [0x90, value, velocity]})
502
+ assert_equal [{:timestamp => 0, :state => state, :type => type}], @device.read_pending_actions
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+ describe '#grid buttons' do
509
+ 8.times do |x|
510
+ 8.times do |y|
511
+ STATES.each do |state, velocity|
512
+ it "builds proper action for grid button #{x},#{y}, #{state}" do
513
+ stub_input(@device, {:timestamp => 0, :message => [0x90, 16 * y + x, velocity]})
514
+ assert_equal [{:timestamp => 0, :state => state, :type => :grid, :x => x, :y => y}], @device.read_pending_actions
515
+ end
516
+ end
517
+ end
518
+ end
519
+ end
520
+
521
+ it 'builds proper actions for multiple pending actions' do
522
+ stub_input(@device, {:timestamp => 1, :message => [0x90, 0, 127]}, {:timestamp => 2, :message => [0xB0, 0x68, 0]})
523
+ assert_equal [{:timestamp => 1, :state => :down, :type => :grid, :x => 0, :y => 0}, {:timestamp => 2, :state => :up, :type => :up}], @device.read_pending_actions
524
+ end
525
+
526
+ end
527
+
528
+ end
529
+
530
+ end