surface_master 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0809d0fe24d9da2f41becb4e27a0ec785019c029
4
- data.tar.gz: 43919aa3d44ec3006953501bebf6f3d5a4010f76
3
+ metadata.gz: f2b72c0db5f531114165b1bf813b7723de8daa62
4
+ data.tar.gz: 816e060bcc90c233e3fe80f994295a0d04222334
5
5
  SHA512:
6
- metadata.gz: 0ab45e7ab7dc4761d108f5dd5dc758516bf35a93b6cc4ea0b5142ed80c2ac5a4c11469756762a57aea5de98a7821f79ed12ace9649bb4ec12ae85532b1bd4493
7
- data.tar.gz: 4ede3f9b33450d69c3bcd250494426b4a66cda5d25c1907ce370fd4bc3a8c7a17cc1131d8d077c832fc4dad342854327c57440e25f522651a9a7593118ec2374
6
+ metadata.gz: 7d7e478d03cb7f4bfbb8dd29c527c374d4ad798054b04a3a9c6326d4ed42e56cd971c2830642715df5e9d851de8631342f6d02f215b4cd6c0394ec83e6af7206
7
+ data.tar.gz: 3252e00acc136cf55dee085e073266f65f7cdb63494f84ddcead0fb6bac150e73598fa9e922fc8aefdc58ec6cb9e5e7a608b6c8a4798e2b0141bfc66b8ca0147
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changes
2
2
 
3
+ ## v0.4.0
4
+
5
+ * Jettison threaded input handling.
6
+ * More Numark Orbit handling (output is still a problem).
7
+
8
+
9
+ ## v0.3.0
10
+
11
+ * __BREAKING CHANGE__: Change Orbit interface to more closely follow conventions established with Novation Launchpad driver.
12
+ * Add preliminary `Interaction` class for Numark Orbit.
13
+ * See `examples/orbit_interaction.rb`.
14
+ * Fix exclusive binding in `Interaction` to not nuke coarser bindings entirely.
15
+ * Rename `examples/orbit_testbed.rb` to `examples/orbit_device.rb`.
16
+ * Rename `examples/monitor.rb` to `examples/system_monitor.rb`.
17
+ * Rename `examples/launchpad_testbed.rb` to `examples/launchpad_playground.rb`.
18
+
19
+
3
20
  ## v0.2.1
4
21
 
5
22
  * Missed a file rename.
@@ -1,5 +1,10 @@
1
1
  # Numark Orbit Quick Reference
2
2
 
3
+ ## Mappings
4
+
5
+ Use the file `mappings/Orbit_Preset.json`, and be sure to hit `Send` several times, as it sometimes does not actually take.
6
+
7
+
3
8
  ## Low Battery Indicator
4
9
 
5
10
  Any blinking `Pad Bank Selectors` indicates the battery is low.
@@ -1,32 +1,52 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # This file provides an example of using the Novation Launchpad Mark 2 using
4
+ # event handlers to respond to buttons, and batch LED updates to update every
5
+ # button on the board quite rapidly.
6
+ #
7
+ # Controls:
8
+ #
9
+ # * Press any grid button and observe that the color changes to white while it's held. Any number
10
+ # of pads may be pressed simultaneously.
11
+ # * The control buttons on the right will each channel-flip a quadrant of the board. They act as
12
+ # toggles, to pressing them again undoes the effect.
13
+ # * The `Mixer` button will terminate the simulation.
14
+ #
15
+ # Effects:
16
+ #
17
+ # * The color of a pad is applied in layers additively, with values clamped to white at the end:
18
+ # 1. The base color for each grid pad is defined by its position with the color getting more
19
+ # green on the X axis, and more blue along the Y axis. See `SCALE` for how steep the change
20
+ # is.
21
+ # 2. Each quadrant adds in a specific color (see `QUADRANTS` below).
22
+ # 3. The red/green/blue channels may be rotated for any given quadrant, if the relevant toggle
23
+ # is active.
24
+ # 4. A sine-wave is applied to one or more channels (see `TIME_SCALE` for the speed of the sine
25
+ # wave).
26
+ # 5. If a particular pad is pressed, the color is set to white.
27
+ #
28
+ # TODO: Input handling seems to interfere with frame rendering, as our FPS seems to "jump" to
29
+ # TODO: insane values when a button is pressed.
30
+ #
2
31
  require "rubygems"
3
32
  require "bundler/setup"
4
33
  Bundler.require(:default, :development)
5
34
 
6
35
  require "surface_master"
7
36
 
8
- # Flash: F0h 00h 20h 29h 02h 18h 23h <LED> <Colour> F7h
9
- # Pulse: F0h 00h 20h 29h 02h 18h 28h <LED> <Colour> F7h
10
- # Set all: F0h 00h 20h 29h 02h 18h 0Eh <Colour> F7h
11
- # Set row: F0h 00h 20h 29h 02h 18h 0Dh <Row> <Colour> F7h
12
- # Set col: F0h 00h 20h 29h 02h 18h 0Ch <Column> <Colour> F7h
13
-
14
- # Configuration
15
- SCALE = 2
37
+ # Animation Configuration
38
+ SCALE = [2, 2]
16
39
  TIME_SCALE = 4.0
40
+ TIME_MASK = { red: 0x3F, green: 0x00, blue: 0x00 }
41
+ QUADRANTS = [[{ red: 0x2F, green: 0x00, blue: 0x00 }, { red: 0x00, green: 0x2F, blue: 0x00 }],
42
+ [{ red: 0x00, green: 0x00, blue: 0x2F }, { red: 0x2F, green: 0x2F, blue: 0x00 }]]
17
43
 
18
44
  # State
19
- QUADRANTS = [
20
- [{ red: 0x2F, green: 0x00, blue: 0x00 }, { red: 0x00, green: 0x2F, blue: 0x00 }],
21
- [{ red: 0x00, green: 0x00, blue: 0x2F }, { red: 0x2F, green: 0x2F, blue: 0x00 }],
22
- ]
23
45
  FLIPPED = [[false, false], [false, false]]
24
46
  PRESSED = (0..7).map { |_x| (0..7).map { |_y| false } }
25
47
  NOW = [Time.now.to_f]
26
48
 
27
49
  # Helpers
28
- CC = %i(up down left right session user1 user2 scene1 scene2 scene3 scene4 scene5 scene6 scene7
29
- scene8)
30
50
  GRID = (0..7).map { |x| (0..7).map { |y| { grid: [x, y] } } }.flatten
31
51
  WHITE = { red: 0x3F, green: 0x3F, blue: 0x3F }
32
52
  BLACK = { red: 0x00, green: 0x00, blue: 0x00 }
@@ -41,15 +61,15 @@ end
41
61
 
42
62
  def positional_color(x, y)
43
63
  { red: 0x00,
44
- green: (x * SCALE),
45
- blue: (y * SCALE) }
64
+ green: (x * SCALE[0]),
65
+ blue: (y * SCALE[1]) }
46
66
  end
47
67
 
48
68
  def temporal_color(_x, _y)
49
69
  s_t = (Math.sin(NOW[0] * TIME_SCALE) * 0.5) + 0.5
50
- { red: (s_t * 0x3F).round,
51
- green: 0x00,
52
- blue: 0x00 }
70
+ { red: (s_t * TIME_MASK[:red]).round,
71
+ green: (s_t * TIME_MASK[:green]).round,
72
+ blue: (s_t * TIME_MASK[:blue]).round }
53
73
  end
54
74
 
55
75
  def clamp_color(color)
@@ -155,18 +175,38 @@ interaction.response_to(:mixer, :down) do |_interaction, _action|
155
175
  interaction.stop
156
176
  end
157
177
  interaction.change(red: 0x03, green: 0x00, blue: 0x00, cc: :mixer)
158
- BTN_COL = { red: 0x03, green: 0x03, blue: 0x03, cc: cc }
178
+ BTN_COL = { red: 0x03, green: 0x03, blue: 0x03 }
159
179
  interaction.changes(%i(scene1 scene2 scene3 scene4).map { |cc| BTN_COL.merge(cc: cc) })
160
180
 
161
181
  init_board(interaction)
162
182
  input_thread = Thread.new do
163
183
  interaction.start
164
184
  end
165
- animation_thread = Thread.new do
185
+ cumulative_time = 0.0
186
+ frame_times = []
187
+ min_frame_time = Float::INFINITY
188
+ max_frame_time = 0.0
189
+ animation_thread = Thread.new do
166
190
  loop do
167
191
  begin
168
192
  NOW[0] = Time.now.to_f
169
193
  init_board(interaction)
194
+ elapsed = Time.now.to_f - NOW[0]
195
+ cumulative_time += elapsed
196
+ min_frame_time = elapsed if elapsed < min_frame_time
197
+ max_frame_time = elapsed if elapsed > max_frame_time
198
+ frame_times.push(elapsed)
199
+ frame_times.shift if frame_times.length > 60
200
+
201
+ # Show avg FPS once per second.
202
+ if cumulative_time >= 1.0
203
+ cumulative_time = 0.0
204
+ avg_frame_time = frame_times.inject(0.0) { |a, e| a + e } / frame_times.length
205
+ avg_rate = (1.0 / avg_frame_time)
206
+ max_rate = (1.0 / min_frame_time)
207
+ min_rate = (1.0 / max_frame_time)
208
+ puts "FPS: Average = %0.1f, Min = %0.1f, Max = %0.1f" % [avg_rate, min_rate, max_rate]
209
+ end
170
210
  rescue StandardError => e
171
211
  puts e.inspect
172
212
  puts e.backtrace.join("\n")
@@ -1,16 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
- # require "bignum"
3
2
  require "rubygems"
4
3
  require "bundler/setup"
5
4
  Bundler.require(:default, :development)
6
5
 
7
6
  require "surface_master"
8
7
 
9
- # Monkey-patching to make debugging easier.
10
- class Fixnum
11
- def to_hex; "%02X" % self; end
12
- end
13
-
14
8
  SurfaceMaster.init!
15
9
  device = SurfaceMaster::Orbit::Device.new
16
10
  loop do
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "bundler/setup"
4
+ Bundler.require(:default, :development)
5
+
6
+ require "surface_master"
7
+
8
+ SurfaceMaster.init!
9
+ interaction = SurfaceMaster::Orbit::Interaction.new
10
+ interaction.response_to(:pad, :down) do |_inter, action|
11
+ puts "PAD DOWN: #{action.inspect}"
12
+ end
13
+ interaction.response_to(:pad, :up) do |_inter, action|
14
+ puts "PAD UP: #{action.inspect}"
15
+ end
16
+ interaction.response_to(:pad, :up, button: 1..4) do |_inter, action|
17
+ puts "LEFT COLUMN PAD UP: #{action.inspect}"
18
+ end
19
+ interaction.response_to(:pad, :up, button: 5..8, bank: 4, exclusive: true) do |_inter, action|
20
+ puts "SECOND-FROM-LEFT COLUMN PAD UP: #{action.inspect}"
21
+ end
22
+
23
+ interaction.response_to(:vknob, :update) do |_inter, action|
24
+ puts "ANY KNOB, ANY BANK (EXCEPT 2) TURNED: #{action.inspect}"
25
+ end
26
+ interaction.response_to(:vknob, :update, vknob: 3) do |_inter, action|
27
+ puts "KNOB 3, ANY BANK (EXCEPT 2) TURNED: #{action.inspect}"
28
+ end
29
+ interaction.response_to(:vknob, :update, bank: 2, exclusive: true) do |_inter, action|
30
+ puts "ANY KNOB, BANK 2 TURNED: #{action.inspect}"
31
+ end
32
+ interaction.response_to(:vknob, :update, bank: 4, vknob: 1) do |_inter, action|
33
+ puts "KNOB 1, BANK 4 TURNED: #{action.inspect}"
34
+ end
35
+
36
+ interaction.response_to(:accelerometer, :tilt) do |_inter, action|
37
+ puts "ANY AXIS TILT: #{action.inspect}"
38
+ end
39
+ interaction.response_to(:accelerometer, :tilt, axis: :x) do |_inter, action|
40
+ puts "X-AXIS TILT: #{action.inspect}"
41
+ end
42
+
43
+ interaction.response_to(:vknobs, :down) do |_inter, action|
44
+ puts "ANY VKNOB SELECTOR DOWN: #{action.inspect}"
45
+ end
46
+ interaction.response_to(:vknobs, :down, button: 2) do |_inter, action|
47
+ puts "VKNOB 2 SELECTOR DOWN: #{action.inspect}"
48
+ end
49
+
50
+ interaction.response_to(:banks, :down) do |_inter, action|
51
+ puts "ANY BANK SELECTOR DOWN: #{action.inspect}"
52
+ end
53
+ interaction.response_to(:banks, :down, button: 3) do |_inter, action|
54
+ puts "BANK 3 SELECTOR DOWN: #{action.inspect}"
55
+ end
56
+
57
+ interaction.response_to(:shoulder, :down) do |_inter, action|
58
+ puts "ANY SHOULDER DOWN: #{action.inspect}"
59
+ end
60
+ interaction.response_to(:shoulder, :up) do |_inter, action|
61
+ puts "ANY SHOULDER UP: #{action.inspect}"
62
+ end
63
+ interaction.response_to(:shoulder, :down, button: :left) do |_inter, action|
64
+ puts "LEFT SHOULDER DOWN: #{action.inspect}"
65
+ end
66
+ interaction.response_to(:shoulder, :up, button: :left) do |_inter, action|
67
+ puts "LEFT SHOULDER UP: #{action.inspect}"
68
+ end
69
+
70
+ puts "Starting input loop..."
71
+ interaction.start
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env ruby
2
+ # Cycle Numark Orbit colors through a wheel.
3
+ #
4
+ # IMPORTANT: Set `MODE` below to `:wired`, or `:wireless`, as appropriate to
5
+ # IMPORTANT: how your Numark Orbit is connected!
6
+ #
7
+ # NOTE: If the lights do not blank out when this starts, your device is in a
8
+ # NOTE: bad state. Push a config to it from the Numark Orbit Editor. If that
9
+ # NOTE: doesn't work, power-cycle it and try again!
10
+ require "rubygems"
11
+ require "bundler/setup"
12
+ Bundler.require(:default, :development)
13
+
14
+ require "surface_master"
15
+
16
+ device = SurfaceMaster::Orbit::Device.new
17
+ # The device seems to be notably less able to accept updates over wireless, and
18
+ # perhaps because CoreMIDI has no backpressure, we can easily wind up hosed.
19
+ # Before settling on new values here, run the process for *several full minutes*
20
+ # and make sure the device continues accepting updates at the end!
21
+ #
22
+ # TODO: More thorough stress testing.
23
+ #
24
+ # TODO: Can we determine if the connection is wired/wireless automatically?
25
+ #
26
+ # TODO: Can we safely get input simultaneously?
27
+ MODE = :wired
28
+
29
+ CONFIGS = { wireless: { delay: 0.75, offset: 0x03, use_read: true, read_delay: 0.1 },
30
+ wired: { delay: 0.1, offset: 0x01, use_read: false, read_delay: 0 } }
31
+ MAPPINGS = [0x03, 0x01, 0x70,
32
+
33
+ 0x00, 0x00, 0x00,
34
+ 0x00, 0x04, 0x04,
35
+ 0x00, 0x08, 0x08,
36
+ 0x00, 0x0C, 0x0C,
37
+ 0x00, 0x01, 0x01,
38
+ 0x00, 0x05, 0x05,
39
+ 0x00, 0x09, 0x09,
40
+ 0x00, 0x0D, 0x0D,
41
+ 0x00, 0x02, 0x02,
42
+ 0x00, 0x06, 0x06,
43
+ 0x00, 0x0A, 0x0A,
44
+ 0x00, 0x0E, 0x0E,
45
+ 0x00, 0x03, 0x03,
46
+ 0x00, 0x07, 0x07,
47
+ 0x00, 0x0B, 0x0B,
48
+ 0x00, 0x0F, 0x0F,
49
+ 0x01, 0x00, 0x10,
50
+ 0x01, 0x04, 0x14,
51
+ 0x01, 0x08, 0x18,
52
+ 0x01, 0x0C, 0x1C,
53
+ 0x01, 0x01, 0x11,
54
+ 0x01, 0x05, 0x15,
55
+ 0x01, 0x09, 0x19,
56
+ 0x01, 0x0D, 0x1D,
57
+ 0x01, 0x02, 0x12,
58
+ 0x01, 0x06, 0x16,
59
+ 0x01, 0x0A, 0x1A,
60
+ 0x01, 0x0E, 0x1E,
61
+ 0x01, 0x03, 0x13,
62
+ 0x01, 0x07, 0x17,
63
+ 0x01, 0x0B, 0x1B,
64
+ 0x01, 0x0F, 0x1F,
65
+ 0x02, 0x00, 0x20,
66
+ 0x02, 0x04, 0x24,
67
+ 0x02, 0x08, 0x28,
68
+ 0x02, 0x0C, 0x2C,
69
+ 0x02, 0x01, 0x21,
70
+ 0x02, 0x05, 0x25,
71
+ 0x02, 0x09, 0x29,
72
+ 0x02, 0x0D, 0x2D,
73
+ 0x02, 0x02, 0x22,
74
+ 0x02, 0x06, 0x26,
75
+ 0x02, 0x0A, 0x2A,
76
+ 0x02, 0x0E, 0x2E,
77
+ 0x02, 0x03, 0x23,
78
+ 0x02, 0x07, 0x27,
79
+ 0x02, 0x0B, 0x2B,
80
+ 0x02, 0x0F, 0x2F,
81
+ 0x03, 0x00, 0x30,
82
+ 0x03, 0x04, 0x34,
83
+ 0x03, 0x08, 0x38,
84
+ 0x03, 0x0C, 0x3C,
85
+ 0x03, 0x01, 0x31,
86
+ 0x03, 0x05, 0x35,
87
+ 0x03, 0x09, 0x39,
88
+ 0x03, 0x0D, 0x3D,
89
+ 0x03, 0x02, 0x32,
90
+ 0x03, 0x06, 0x36,
91
+ 0x03, 0x0A, 0x3A,
92
+ 0x03, 0x0E, 0x3E,
93
+ 0x03, 0x03, 0x33,
94
+ 0x03, 0x07, 0x37,
95
+ 0x03, 0x0B, 0x3B,
96
+ 0x03, 0x0F, 0x3F,
97
+
98
+ 0x00, 0x00, 0x01,
99
+ 0x00, 0x02, 0x00,
100
+ 0x03, 0x00, 0x00,
101
+ 0x01, 0x01, 0x01,
102
+ 0x02, 0x01, 0x03,
103
+ 0x01, 0x00, 0x02,
104
+ 0x01, 0x02, 0x02,
105
+ 0x02, 0x03, 0x02,
106
+ 0x00, 0x03, 0x01,
107
+ 0x03, 0x02, 0x03,
108
+ 0x03, 0x03, 0x0C,
109
+ 0x00, 0x0D, 0x00,
110
+ 0x0C, 0x00, 0x0D,
111
+ 0x00, 0x0C, 0x00,
112
+ 0x0D, 0x00, 0x0C,
113
+ 0x00, 0x0D, 0x00]
114
+ READ_STATE = [0x01, 0x00, 0x00]
115
+
116
+ delay = CONFIGS[MODE][:delay]
117
+ offset = CONFIGS[MODE][:offset]
118
+ use_read = CONFIGS[MODE][:use_read]
119
+ read_delay = CONFIGS[MODE][:read_delay]
120
+
121
+ sleep delay
122
+ puts "Starting..."
123
+ indices = (0..63).map { |n| 5 + (3 * n) }
124
+ loop do
125
+ indices.each do |i|
126
+ MAPPINGS[i] = (MAPPINGS[i] + offset) % 0x3F
127
+ end
128
+ device.sysex!(*MAPPINGS)
129
+ if use_read
130
+ sleep read_delay
131
+ device.sysex!(*READ_STATE)
132
+ end
133
+ printf "."
134
+ sleep delay
135
+ end
@@ -39,14 +39,14 @@ end
39
39
 
40
40
  interaction = SurfaceMaster::Launchpad::Interaction.new
41
41
  cpu_bar = Bar.new(interaction, 0, red: 0x3F, green: 0x00, blue: 0x00)
42
- io_bar = Bar.new(interaction, 0, red: 0x00, green: 0x3F, blue: 0x00)
42
+ io_bar = Bar.new(interaction, 1, red: 0x00, green: 0x3F, blue: 0x00)
43
43
  monitor = Thread.new do
44
44
  loop do
45
45
  fields = `iostat -c 2 disk0`.split(/\n/).last.strip.split(/\s+/)
46
46
  cpu_pct = 100 - fields[-4].to_i
47
47
  cpu_usage = ((cpu_pct / 100.0) * 8.0).round.to_i
48
48
 
49
- disk_pct = (fields[2].to_f / 750.0) * 100.0
49
+ disk_pct = ((fields[2].to_f / 750.0) * 100.0).round.to_i
50
50
  disk_usage = ((disk_pct / 100.0) * 8.0).round.to_i
51
51
 
52
52
  puts "I/O=#{disk_pct}%, CPU=#{cpu_pct}%"
@@ -51,10 +51,13 @@ module SurfaceMaster
51
51
  fail NoOutputAllowedError unless output_enabled?
52
52
  msg = sysex_msg(payload)
53
53
  logger.debug { "#{msg.length}: 0x#{msg.map { |b| '%02X' % b }.join(' ')}" }
54
- @output.write_sysex(msg)
54
+ result = @output.write_sysex(msg)
55
+ if result != 0
56
+ puts "Sysex Error: #{Portmidi::PM_Map.Pm_GetErrorText(result)}"
57
+ end
58
+ result
55
59
  end
56
60
 
57
-
58
61
  def create_input_device(opts)
59
62
  return nil unless opts[:input]
60
63
  create_device(Portmidi.input_devices,
@@ -12,14 +12,11 @@ module SurfaceMaster
12
12
  self.logger = opts[:logger]
13
13
  logger.debug "Initializing #{self.class}##{object_id} with #{opts.inspect}"
14
14
 
15
- @use_threads = opts[:use_threads] || true
16
15
  @device = opts[:device] || @device_class.new(opts.merge(input: true,
17
16
  output: true,
18
17
  logger: opts[:logger]))
19
18
  @latency = (opts[:latency] || 0.001).to_f.abs
20
19
  @active = false
21
-
22
- @action_threads = ThreadGroup.new
23
20
  end
24
21
 
25
22
  def change(opts); @device.change(opts); end
@@ -33,36 +30,29 @@ module SurfaceMaster
33
30
 
34
31
  def closed?; @device.closed?; end
35
32
 
36
- def start(opts = nil)
33
+ def start
37
34
  logger.debug "Starting #{self.class}##{object_id}"
38
35
 
39
- opts = { detached: false }.merge(opts || {})
40
- @active = true
41
- @reader_thread ||= create_reader_thread
42
-
43
- @reader_thread.join unless opts[:detached]
36
+ @active = true
37
+ guard_input_and_reset_at_end! do
38
+ while @active
39
+ @device.read.each { |action| respond_to_action(action) }
40
+ sleep @latency if @latency && @latency > 0.0
41
+ end
42
+ end
44
43
  end
45
44
 
46
45
  def stop
47
46
  logger.debug "Stopping #{self.class}##{object_id}"
48
47
  @active = false
49
- if @reader_thread
50
- # run (resume from sleep) and wait for @reader_thread to end
51
- @reader_thread.run if @reader_thread.alive?
52
- @reader_thread.join
53
- @reader_thread = nil
54
- end
55
- ensure
56
- kill_action_threads!
57
- nil
58
48
  end
59
49
 
60
50
  def response_to(types = :all, state = :both, opts = nil, &block)
61
51
  logger.debug "Setting response to #{types.inspect} for state #{state.inspect} with"\
62
52
  " #{opts.inspect}"
63
- types = Array(types)
64
- opts ||= {}
65
- no_response_to(types, state) if opts[:exclusive] == true
53
+ types = Array(types)
54
+ opts ||= {}
55
+ no_response_to(types, state, opts) if opts[:exclusive] == true
66
56
  expand_states(state).each do |st|
67
57
  add_response_for_state!(types, opts, st, block)
68
58
  end
@@ -71,7 +61,8 @@ module SurfaceMaster
71
61
 
72
62
  def no_response_to(types = nil, state = :both, opts = nil)
73
63
  logger.debug "Removing response to #{types.inspect} for state #{state.inspect}"
74
- types = Array(types)
64
+ types = Array(types)
65
+ opts ||= {}
75
66
  expand_states(state).each do |st|
76
67
  clear_responses_for_state!(types, opts, st)
77
68
  end
@@ -84,15 +75,8 @@ module SurfaceMaster
84
75
 
85
76
  protected
86
77
 
87
- def create_reader_thread
88
- Thread.new do
89
- guard_input_and_reset_at_end! do
90
- while @active
91
- @device.read.each { |action| handle_action(action) }
92
- sleep @latency if @latency && @latency > 0.0
93
- end
94
- end
95
- end
78
+ def expand(list)
79
+ list.map { |ll| ll.respond_to?(:to_a) ? ll.to_a : ll }.flatten
96
80
  end
97
81
 
98
82
  def guard_input_and_reset_at_end!(&block)
@@ -107,17 +91,6 @@ module SurfaceMaster
107
91
  @device.reset!
108
92
  end
109
93
 
110
- def kill_action_threads!
111
- @action_threads.list.each do |thread|
112
- begin
113
- thread.kill
114
- thread.join
115
- rescue StandardException => e # TODO: RuntimeError, Exception, or this?
116
- logger.error "Error when killing action thread: #{e.inspect}"
117
- end
118
- end
119
- end
120
-
121
94
  def add_response_for_state!(types, opts, state, block)
122
95
  response_groups_for(types, opts, state) do |responses|
123
96
  responses << block
@@ -138,17 +111,6 @@ module SurfaceMaster
138
111
 
139
112
  def expand_states(state); Array(state == :both ? %i(down up) : state); end
140
113
 
141
- def handle_action(action)
142
- if @use_threads
143
- action_thread = Thread.new(action) do |act|
144
- respond_to_action(act)
145
- end
146
- @action_threads.add(action_thread)
147
- else
148
- respond_to_action(action)
149
- end
150
- end
151
-
152
114
  def responses
153
115
  # TODO: Generalize for arbitrary actions...
154
116
  @responses ||= Hash.new { |hash, key| hash[key] = { down: [], up: [] } }
@@ -148,7 +148,7 @@ module SurfaceMaster
148
148
  # 0x0D -> Row
149
149
  # 0x0E -> All LEDs
150
150
  [0x0B,
151
- { led: decode_led(opts),
151
+ { led: decode_led(opts)[1],
152
152
  color: [opts[:red] || 0x00, opts[:green] || 0x00, opts[:blue] || 0x00] }]
153
153
  end
154
154
 
@@ -11,11 +11,52 @@ module SurfaceMaster
11
11
  end
12
12
 
13
13
  def reset!
14
- # TODO: This... doesn't appear to work. At all.
15
- if (result = sysex!(*MAPPINGS)) != 0
16
- fail "Expected success (0) setting mappings, got: #{result}"
14
+ # Skip Sysex begin, vendor header, command code, aaaaand sysex end --
15
+ # this will let us compare command vs. response payloads to determine
16
+ # if the state of the device is what we want. Of course, sometimes it
17
+ # lies, but we can't do much about that.
18
+ expected_state = MAPPINGS[6..-2]
19
+ sysex!(MAPPINGS)
20
+ sleep 0.1
21
+ sysex!(READ_STATE)
22
+ current_state = nil
23
+ started_at = Time.now.to_f
24
+ attempts = 1
25
+ loop do
26
+ # TODO: It appears that accessing `buffer` is HIGHLY unsafe! We may
27
+ # TODO: be OK if everyone's waiting on us to come back from this
28
+ # TODO: method before they begin clamoring for input, but that's just
29
+ # TODO: a guess right now.
30
+ if @input.buffer.length == 0
31
+ elapsed = Time.now.to_f - started_at
32
+ if elapsed > 4.0
33
+ logger.warn { "Timeout fetching state of Numark Orbit!" }
34
+ break
35
+ elsif elapsed > (1.0 * attempts)
36
+ logger.warn { "Asking for current state of Numark Orbit again!" }
37
+ attempts += 1
38
+ @output.puts(READ_STATE)
39
+ next
40
+ end
41
+ sleep 0.01
42
+ next
43
+ end
44
+ raw = @input.gets
45
+ current_state = raw.find { |ii| ii[:data][0] == 0xF0 }
46
+ break unless current_state.nil?
47
+ end
48
+
49
+ return unless current_state
50
+
51
+ current_state = current_state[:data][6..-2].dup
52
+ logger.debug { "Got state info from Numark Orbit!" }
53
+ if expected_state != current_state
54
+ logger.error { "UH OH! Numark Orbit state didn't match what we sent!" }
55
+ logger.error { "Expected: #{expected_state.inspect}" }
56
+ logger.error { "Got: #{current_state.inspect}" }
57
+ else
58
+ logger.debug { "Your Numark Orbit should be in the right state now." }
17
59
  end
18
- sysex!(0x01, 0x00, 0x00)
19
60
  end
20
61
 
21
62
  def read
@@ -26,133 +67,140 @@ module SurfaceMaster
26
67
 
27
68
  protected
28
69
 
29
- MAPPINGS = [0x03, 0x01, 0x70,
30
- 0x00, 0x00, 0x00,
31
- 0x00, 0x04, 0x00,
32
- 0x00, 0x08, 0x00,
33
- 0x00, 0x0C, 0x00,
34
- 0x00, 0x01, 0x00,
35
- 0x00, 0x05, 0x00,
36
- 0x00, 0x09, 0x00,
37
- 0x00, 0x0D, 0x00,
38
- 0x00, 0x02, 0x00,
39
- 0x00, 0x06, 0x00,
40
- 0x00, 0x0A, 0x00,
41
- 0x00, 0x0E, 0x00,
42
- 0x00, 0x03, 0x00,
43
- 0x00, 0x07, 0x00,
44
- 0x00, 0x0B, 0x00,
45
- 0x00, 0x0F, 0x00,
46
- 0x01, 0x00, 0x00,
47
- 0x01, 0x04, 0x00,
48
- 0x01, 0x08, 0x00,
49
- 0x01, 0x0C, 0x00,
50
- 0x01, 0x01, 0x00,
51
- 0x01, 0x05, 0x00,
52
- 0x01, 0x09, 0x00,
53
- 0x01, 0x0D, 0x00,
54
- 0x01, 0x02, 0x00,
55
- 0x01, 0x06, 0x00,
56
- 0x01, 0x0A, 0x00,
57
- 0x01, 0x0E, 0x00,
58
- 0x01, 0x03, 0x00,
59
- 0x01, 0x07, 0x00,
60
- 0x01, 0x0B, 0x00,
61
- 0x01, 0x0F, 0x00,
62
- 0x02, 0x00, 0x00,
63
- 0x02, 0x04, 0x00,
64
- 0x02, 0x08, 0x00,
65
- 0x02, 0x0C, 0x00,
66
- 0x02, 0x01, 0x00,
67
- 0x02, 0x05, 0x00,
68
- 0x02, 0x09, 0x00,
69
- 0x02, 0x0D, 0x00,
70
- 0x02, 0x02, 0x00,
71
- 0x02, 0x06, 0x00,
72
- 0x02, 0x0A, 0x00,
73
- 0x02, 0x0E, 0x00,
74
- 0x02, 0x03, 0x00,
75
- 0x02, 0x07, 0x00,
76
- 0x02, 0x0B, 0x00,
77
- 0x02, 0x0F, 0x00,
78
- 0x03, 0x00, 0x00,
79
- 0x03, 0x04, 0x00,
80
- 0x03, 0x08, 0x00,
81
- 0x03, 0x0C, 0x00,
82
- 0x03, 0x01, 0x00,
83
- 0x03, 0x05, 0x00,
84
- 0x03, 0x09, 0x00,
85
- 0x03, 0x0D, 0x00,
86
- 0x03, 0x02, 0x00,
87
- 0x03, 0x06, 0x00,
88
- 0x03, 0x0A, 0x00,
89
- 0x03, 0x0E, 0x00,
90
- 0x03, 0x03, 0x00,
91
- 0x03, 0x07, 0x00,
92
- 0x03, 0x0B, 0x00,
93
- 0x03, 0x0F, 0x00,
94
- 0x00, 0x00, 0x01,
95
- 0x00, 0x02, 0x00,
96
- 0x03, 0x00, 0x00,
97
- 0x01, 0x01, 0x01,
98
- 0x02, 0x01, 0x03,
99
- 0x01, 0x00, 0x02,
100
- 0x01, 0x02, 0x02,
101
- 0x02, 0x03, 0x02,
102
- 0x00, 0x03, 0x01,
103
- 0x03, 0x02, 0x03,
104
- 0x03, 0x03, 0x0C,
105
- 0x00, 0x0D, 0x00,
106
- 0x0C, 0x00, 0x0D,
107
- 0x00, 0x0C, 0x00,
108
- 0x0D, 0x00, 0x0C,
109
- 0x00, 0x0D, 0x00]
70
+ MAPPINGS = [0x03, 0x01, 0x70,
71
+
72
+ 0x00, 0x00, 0x00,
73
+ 0x00, 0x04, 0x00,
74
+ 0x00, 0x08, 0x00,
75
+ 0x00, 0x0C, 0x00,
76
+ 0x00, 0x01, 0x00,
77
+ 0x00, 0x05, 0x00,
78
+ 0x00, 0x09, 0x00,
79
+ 0x00, 0x0D, 0x00,
80
+ 0x00, 0x02, 0x00,
81
+ 0x00, 0x06, 0x00,
82
+ 0x00, 0x0A, 0x00,
83
+ 0x00, 0x0E, 0x00,
84
+ 0x00, 0x03, 0x00,
85
+ 0x00, 0x07, 0x00,
86
+ 0x00, 0x0B, 0x00,
87
+ 0x00, 0x0F, 0x00,
88
+ 0x01, 0x00, 0x00,
89
+ 0x01, 0x04, 0x00,
90
+ 0x01, 0x08, 0x00,
91
+ 0x01, 0x0C, 0x00,
92
+ 0x01, 0x01, 0x00,
93
+ 0x01, 0x05, 0x00,
94
+ 0x01, 0x09, 0x00,
95
+ 0x01, 0x0D, 0x00,
96
+ 0x01, 0x02, 0x00,
97
+ 0x01, 0x06, 0x00,
98
+ 0x01, 0x0A, 0x00,
99
+ 0x01, 0x0E, 0x00,
100
+ 0x01, 0x03, 0x00,
101
+ 0x01, 0x07, 0x00,
102
+ 0x01, 0x0B, 0x00,
103
+ 0x01, 0x0F, 0x00,
104
+ 0x02, 0x00, 0x00,
105
+ 0x02, 0x04, 0x00,
106
+ 0x02, 0x08, 0x00,
107
+ 0x02, 0x0C, 0x00,
108
+ 0x02, 0x01, 0x00,
109
+ 0x02, 0x05, 0x00,
110
+ 0x02, 0x09, 0x00,
111
+ 0x02, 0x0D, 0x00,
112
+ 0x02, 0x02, 0x00,
113
+ 0x02, 0x06, 0x00,
114
+ 0x02, 0x0A, 0x00,
115
+ 0x02, 0x0E, 0x00,
116
+ 0x02, 0x03, 0x00,
117
+ 0x02, 0x07, 0x00,
118
+ 0x02, 0x0B, 0x00,
119
+ 0x02, 0x0F, 0x00,
120
+ 0x03, 0x00, 0x00,
121
+ 0x03, 0x04, 0x00,
122
+ 0x03, 0x08, 0x00,
123
+ 0x03, 0x0C, 0x00,
124
+ 0x03, 0x01, 0x00,
125
+ 0x03, 0x05, 0x00,
126
+ 0x03, 0x09, 0x00,
127
+ 0x03, 0x0D, 0x00,
128
+ 0x03, 0x02, 0x00,
129
+ 0x03, 0x06, 0x00,
130
+ 0x03, 0x0A, 0x00,
131
+ 0x03, 0x0E, 0x00,
132
+ 0x03, 0x03, 0x00,
133
+ 0x03, 0x07, 0x00,
134
+ 0x03, 0x0B, 0x00,
135
+ 0x03, 0x0F, 0x00,
136
+
137
+ 0x00, 0x00, 0x01,
138
+ 0x00, 0x02, 0x00,
139
+ 0x03, 0x00, 0x00,
140
+ 0x01, 0x01, 0x01,
141
+ 0x02, 0x01, 0x03,
142
+ 0x01, 0x00, 0x02,
143
+ 0x01, 0x02, 0x02,
144
+ 0x02, 0x03, 0x02,
145
+ 0x00, 0x03, 0x01,
146
+ 0x03, 0x02, 0x03,
147
+ 0x03, 0x03, 0x0C,
148
+ 0x00, 0x0D, 0x00,
149
+ 0x0C, 0x00, 0x0D,
150
+ 0x00, 0x0C, 0x00,
151
+ 0x0D, 0x00, 0x0C,
152
+ 0x00, 0x0D, 0x00]
153
+ READ_STATE = [0x01, 0x00, 0x00]
110
154
 
111
155
  def sysex_prefix; @sysex_prefix ||= super + [0x00, 0x01, 0x3F, 0x2B]; end
112
156
 
113
157
  def decode_shoulder(decoded, note, _velocity)
114
- decoded[:control].merge!(SurfaceMaster::Orbit::Device::SHOULDERS[note])
158
+ decoded[:control] = decoded[:control].merge(SurfaceMaster::Orbit::Device::SHOULDERS[note])
115
159
  decoded
116
160
  end
117
161
 
118
162
  def decode_pad(decoded, note, _velocity)
119
- decoded[:control][:button] = note
163
+ decoded[:control] = decoded[:control].merge(button: note + 1)
120
164
  decoded
121
165
  end
122
166
 
123
167
  def decode_knob(decoded, note, velocity)
124
- decoded[:control][:bank] = note + 1
125
- decoded[:value] = velocity
168
+ decoded[:control] = decoded[:control].merge(bank: note + 1)
169
+ decoded[:value] = velocity
126
170
  decoded
127
171
  end
128
172
 
129
173
  def decode_control(decoded, note, velocity)
130
- tmp = SurfaceMaster::Orbit::Device::SELECTORS[note]
131
- tmp[:index] = velocity
132
- decoded[:control].merge!(tmp)
174
+ decoded = decoded.merge(SurfaceMaster::Orbit::Device::SELECTORS[note])
175
+ decoded[:control] = { button: velocity }
176
+ decoded
177
+ end
178
+
179
+ def decode_accelerometer(decoded, _note, velocity)
180
+ decoded[:value] = velocity
133
181
  decoded
134
182
  end
135
183
 
136
184
  def enrich_decoded_message(decoded, note, velocity, timestamp)
137
185
  case decoded[:type]
138
- when :shoulder then decoded = decode_shoulder(decoded, note, velocity)
139
- when :pad then decoded = decode_pad(decoded, note, velocity)
140
- when :knob then decoded = decode_knob(decoded, note, velocity)
141
- when :control then decoded = decode_control(decoded, note, velocity)
186
+ when :shoulder then decoded = decode_shoulder(decoded, note, velocity)
187
+ when :pad then decoded = decode_pad(decoded, note, velocity)
188
+ when :vknob then decoded = decode_knob(decoded, note, velocity)
189
+ when :accelerometer then decoded = decode_accelerometer(decoded, note, velocity)
190
+ else decoded = decode_control(decoded, note, velocity)
142
191
  end
143
192
  decoded[:timestamp] = timestamp
144
193
  decoded
145
194
  end
146
195
 
147
196
  def decode_input(input)
148
- # puts [input[:code].to_hex, input[:note].to_hex, input[:velocity].to_hex].join(" ")
149
197
  note = input[:note]
150
198
  velocity = input[:velocity]
151
199
  code_high = input[:code] & 0xF0
152
200
  code_low = input[:code] & 0x0F
153
201
  raw = SurfaceMaster::Orbit::Device::CONTROLS[code_high]
154
202
  raw = raw[code_low] if raw
155
- raw = enrich_decoded_message(raw, note, velocity, input[:timestamp]) if raw
203
+ raw = enrich_decoded_message(raw.dup, note, velocity, input[:timestamp]) if raw
156
204
  raw
157
205
  end
158
206
  end
@@ -7,24 +7,74 @@ module SurfaceMaster
7
7
  super(opts)
8
8
  end
9
9
 
10
- private
10
+ protected
11
11
 
12
+ def combined_types(type, opts = nil)
13
+ tmp = case type
14
+ when :shoulder
15
+ [:"#{type}-#{opts[:button]}"]
16
+ when :accelerometer
17
+ [:"#{type}-#{opts[:axis]}"]
18
+ when :vknob
19
+ knobs = opts[:vknob].nil? ? [1..4] : [opts[:vknob]]
20
+ banks = opts[:bank].nil? ? [1..4] : [opts[:bank]]
21
+
22
+ expand(knobs).product(expand(banks)).map { |k, b| :"#{type}-#{k}-#{b}" }
23
+ when :vknobs, :banks
24
+ buttons = opts[:button].nil? ? [1..4] : [opts[:button]]
25
+
26
+ expand(buttons).map { |b| [:"#{type}-#{b}"] }
27
+ when :pad
28
+ banks = opts[:bank].nil? ? [1..4] : [opts[:bank]]
29
+ buttons = opts[:button].nil? ? [1..16] : [opts[:button]]
30
+
31
+ expand(buttons).product(expand(banks)).map { |p, b| :"#{type}-#{p}-#{b}" }
32
+ else
33
+ [type]
34
+ end
35
+ tmp.flatten.compact
36
+ end
37
+
38
+ def responses_hash
39
+ { down: [],
40
+ up: [],
41
+ update: [],
42
+ tilt: [] }
43
+ end
44
+
45
+ def responses
46
+ @responses ||= Hash.new { |hash, key| hash[key] = responses_hash }
47
+ end
48
+
49
+ # TODO: Allow catching ranges of pads...
50
+ #
51
+ # TODO: Allow differentiating on bank/vknob/shoulder button...
12
52
  def respond_to_action(action)
13
- # type = action[:type].to_sym
14
- # state = action[:state].to_sym
15
- # actions = []
16
- # if type == :grid
17
- # actions += responses[:"grid#{action[:x]}#{action[:y]}"][state]
18
- # actions += responses[:"grid#{action[:x]}-"][state]
19
- # actions += responses[:"grid-#{action[:y]}"][state]
20
- # end
21
- # actions += responses[type][state]
22
- # actions += responses[:all][state]
23
- # actions.compact.each {|block| block.call(self, action)}
53
+ mappings_for_action(action).each do |block|
54
+ block.call(self, action)
55
+ end
56
+ nil
24
57
  rescue Exception => e # TODO: StandardException, RuntimeError, or Exception?
25
58
  logger.error "Error when responding to action #{action.inspect}: #{e.inspect}"
26
59
  raise e
27
60
  end
61
+
62
+ def mappings_for_action(action)
63
+ combined_types = combined_types(action[:type].to_sym, action[:control])
64
+ state = action[:state].to_sym
65
+ actions = []
66
+ actions += combined_types.map { |ct| responses[ct][state] }
67
+ actions += responses[:all][state]
68
+ actions.flatten.compact
69
+ end
70
+
71
+ def expand_states(state)
72
+ case state
73
+ when :both then %i(down up)
74
+ when :all then responses_hash.keys
75
+ else Array(state)
76
+ end
77
+ end
28
78
  end
29
79
  end
30
80
  end
@@ -6,28 +6,28 @@ module SurfaceMaster
6
6
  module MIDICodes
7
7
  # TODO: Use a lib to do a deep-freeze.
8
8
  # rubocop:disable Metrics/LineLength
9
- CONTROLS = { 0x90 => { 0x00 => { type: :pad, action: :down, control: { bank: 1 } },
10
- 0x01 => { type: :pad, action: :down, control: { bank: 2 } },
11
- 0x02 => { type: :pad, action: :down, control: { bank: 3 } },
12
- 0x03 => { type: :pad, action: :down, control: { bank: 4 } },
13
- 0x0F => { type: :shoulder, action: :down, control: {} } },
14
- 0x80 => { 0x00 => { type: :pad, action: :up, control: { bank: 1 } },
15
- 0x01 => { type: :pad, action: :up, control: { bank: 2 } },
16
- 0x02 => { type: :pad, action: :up, control: { bank: 3 } },
17
- 0x03 => { type: :pad, action: :up, control: { bank: 4 } },
18
- 0x0F => { type: :shoulder, action: :up, control: {} } },
19
- 0xB0 => { 0x00 => { type: :knob, action: :update, control: { vknob: 1 } },
20
- 0x01 => { type: :knob, action: :update, control: { vknob: 2 } },
21
- 0x02 => { type: :knob, action: :update, control: { vknob: 3 } },
22
- 0x03 => { type: :knob, action: :update, control: { vknob: 4 } },
23
- 0x0C => { type: :accelerometer, action: :tilt, control: { axis: :x } },
24
- 0x0D => { type: :accelerometer, action: :tilt, control: { axis: :y } },
25
- 0x0F => { type: :control, action: :switch, control: {} } } }.freeze
9
+ CONTROLS = { 0x90 => { 0x00 => { type: :pad, state: :down, control: { bank: 1 } },
10
+ 0x01 => { type: :pad, state: :down, control: { bank: 2 } },
11
+ 0x02 => { type: :pad, state: :down, control: { bank: 3 } },
12
+ 0x03 => { type: :pad, state: :down, control: { bank: 4 } },
13
+ 0x0F => { type: :shoulder, state: :down, control: {} } },
14
+ 0x80 => { 0x00 => { type: :pad, state: :up, control: { bank: 1 } },
15
+ 0x01 => { type: :pad, state: :up, control: { bank: 2 } },
16
+ 0x02 => { type: :pad, state: :up, control: { bank: 3 } },
17
+ 0x03 => { type: :pad, state: :up, control: { bank: 4 } },
18
+ 0x0F => { type: :shoulder, state: :up, control: {} } },
19
+ 0xB0 => { 0x00 => { type: :vknob, state: :update, control: { vknob: 1 } },
20
+ 0x01 => { type: :vknob, state: :update, control: { vknob: 2 } },
21
+ 0x02 => { type: :vknob, state: :update, control: { vknob: 3 } },
22
+ 0x03 => { type: :vknob, state: :update, control: { vknob: 4 } },
23
+ 0x0C => { type: :accelerometer, state: :tilt, control: { axis: :x } },
24
+ 0x0D => { type: :accelerometer, state: :tilt, control: { axis: :y } },
25
+ 0x0F => { state: :down } } }.freeze
26
26
  # rubocop:enable Metrics/LineLength
27
27
  SHOULDERS = { 0x03 => { button: :left },
28
28
  0x04 => { button: :right } }.freeze
29
- SELECTORS = { 0x01 => { selector: :banks },
30
- 0x02 => { selector: :vknobs } }.freeze
29
+ SELECTORS = { 0x01 => { type: :banks },
30
+ 0x02 => { type: :vknobs } }.freeze
31
31
  end
32
32
  end
33
33
  end
@@ -1,4 +1,4 @@
1
1
  #
2
2
  module SurfaceMaster
3
- VERSION = "0.2.1"
3
+ VERSION = "0.4.0"
4
4
  end
@@ -24,3 +24,4 @@ require "surface_master/launchpad/interaction"
24
24
 
25
25
  require "surface_master/orbit/midi_codes"
26
26
  require "surface_master/orbit/device"
27
+ require "surface_master/orbit/interaction"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surface_master
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Frisby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-09 00:00:00.000000000 Z
11
+ date: 2015-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: portmidi
@@ -73,9 +73,11 @@ files:
73
73
  - debug_tools/decode.rb
74
74
  - debug_tools/extract_midi_monitor_sample.sh
75
75
  - docs/Numark_Orbit_QuickRef.md
76
- - examples/launchpad_testbed.rb
77
- - examples/monitor.rb
78
- - examples/orbit_testbed.rb
76
+ - examples/launchpad_playground.rb
77
+ - examples/orbit_device.rb
78
+ - examples/orbit_interaction.rb
79
+ - examples/orbit_playground.rb
80
+ - examples/system_monitor.rb
79
81
  - lib/surface_master.rb
80
82
  - lib/surface_master/device.rb
81
83
  - lib/surface_master/errors.rb