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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +8 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +48 -0
- data/Rakefile +10 -0
- data/debug_tools/Numark_Orbit_Pad_Coloring.mmon +98 -0
- data/debug_tools/OrbitLightingExample.json +662 -0
- data/debug_tools/Orbit_Color_Test.json +662 -0
- data/debug_tools/Orbit_Colors_And_Reset.1.raw +0 -0
- data/debug_tools/Orbit_Colors_And_Reset.1.txt +82 -0
- data/debug_tools/Orbit_Colors_And_Reset.2.raw +0 -0
- data/debug_tools/Orbit_Colors_And_Reset.2.txt +2 -0
- data/debug_tools/Orbit_Colors_And_Reset.3.raw +0 -0
- data/debug_tools/Orbit_Colors_And_Reset.3.txt +82 -0
- data/debug_tools/Orbit_Colors_And_Reset.mmon +93 -0
- data/debug_tools/Orbit_Preset.1.raw +0 -0
- data/debug_tools/Orbit_Preset.1.txt +82 -0
- data/debug_tools/Orbit_Preset.2.raw +0 -0
- data/debug_tools/Orbit_Preset.2.txt +2 -0
- data/debug_tools/Orbit_Preset.mmon +72 -0
- data/debug_tools/compare.sh +12 -0
- data/debug_tools/decode.rb +14 -0
- data/debug_tools/extract_midi_monitor_sample.sh +33 -0
- data/docs/Numark_Orbit_QuickRef.md +50 -0
- data/examples/launchpad_testbed.rb +141 -0
- data/examples/monitor.rb +61 -0
- data/examples/orbit_testbed.rb +62 -0
- data/lib/control_center.rb +26 -0
- data/lib/surface_master/device.rb +90 -0
- data/lib/surface_master/errors.rb +27 -0
- data/lib/surface_master/interaction.rb +133 -0
- data/lib/surface_master/launchpad/device.rb +159 -0
- data/lib/surface_master/launchpad/errors.rb +11 -0
- data/lib/surface_master/launchpad/interaction.rb +86 -0
- data/lib/surface_master/launchpad/midi_codes.rb +51 -0
- data/lib/surface_master/logging.rb +15 -0
- data/lib/surface_master/orbit/device.rb +160 -0
- data/lib/surface_master/orbit/interaction.rb +29 -0
- data/lib/surface_master/orbit/midi_codes.rb +31 -0
- data/lib/surface_master/version.rb +3 -0
- data/mappings/Orbit_Preset.json +662 -0
- data/surface_master.gemspec +26 -0
- data/test/helper.rb +44 -0
- data/test/test_device.rb +530 -0
- data/test/test_interaction.rb +456 -0
- metadata +121 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
fname = ARGV.shift
|
3
|
+
raise "Must specify filename." unless fname
|
4
|
+
outname = fname.gsub(/\.raw/, ".txt")
|
5
|
+
|
6
|
+
bytes = File.read(fname).bytes.map { |x| "0x%02X" % x }
|
7
|
+
output = [bytes.shift(5).join(", ")] # SysEx vendor header...
|
8
|
+
|
9
|
+
output << bytes.shift(2).join(", ") # Apparent prefix...
|
10
|
+
while (row = bytes.shift(3).join(", ")) != ""
|
11
|
+
output << row
|
12
|
+
end
|
13
|
+
|
14
|
+
File.write(outname, output.join("\n") + "\n")
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
|
5
|
+
FNAME=${1:-}
|
6
|
+
|
7
|
+
if [[ $FNAME == "" ]]; then
|
8
|
+
echo "Usage: extract_midi_monitor_sample.sh <save_file.mmon>"
|
9
|
+
exit 1
|
10
|
+
fi
|
11
|
+
|
12
|
+
plutil -convert xml1 $FNAME
|
13
|
+
|
14
|
+
xpath $FNAME "//dict/data" 2>/dev/null |
|
15
|
+
grep -v -E '(^<data>)|(</data>$)' |
|
16
|
+
base64 -D |
|
17
|
+
plutil -convert xml1 - -o - > ${FNAME%.mmon}.tmp
|
18
|
+
|
19
|
+
# Not sure we're guaranteed to get the same number of elements every time,
|
20
|
+
# so the `5` below may be brittle!
|
21
|
+
xpath ${FNAME%.mmon}.tmp "//dict/array/dict[5]/data" 2>/dev/null |
|
22
|
+
grep -v -E '(^<data>)|(</data>$)' |
|
23
|
+
base64 -D > ${FNAME%.mmon}.1.raw
|
24
|
+
|
25
|
+
xpath ${FNAME%.mmon}.tmp "//dict/array/dict[9]/data" 2>/dev/null |
|
26
|
+
grep -v -E '(^<data>)|(</data>$)' |
|
27
|
+
base64 -D > ${FNAME%.mmon}.2.raw
|
28
|
+
|
29
|
+
xpath ${FNAME%.mmon}.tmp "//dict/array/dict[11]/data" 2>/dev/null |
|
30
|
+
grep -v -E '(^<data>)|(</data>$)' |
|
31
|
+
base64 -D > ${FNAME%.mmon}.3.raw
|
32
|
+
|
33
|
+
rm ${FNAME%.mmon}.tmp
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Numark Orbit Quick Reference
|
2
|
+
|
3
|
+
## Low Battery Indicator
|
4
|
+
|
5
|
+
Any blinking `Pad Bank Selectors` indicates the battery is low.
|
6
|
+
|
7
|
+
|
8
|
+
## Check Battery Level
|
9
|
+
|
10
|
+
Hold down `Virtual Knob Selector` `K1` for several seconds. `Pad Bank Selectors` will light up to indicate charge level.
|
11
|
+
|
12
|
+
|
13
|
+
## Reset Lights
|
14
|
+
|
15
|
+
1. Turn Orbit off.
|
16
|
+
1. Press and hold `Pad Bank Selectors` `1` and `4`.
|
17
|
+
1. Turn Orbit on.
|
18
|
+
1. Press the upper-left-most pad.
|
19
|
+
|
20
|
+
|
21
|
+
## Pairing
|
22
|
+
|
23
|
+
1. Turn Orbit off, and unplug USB adapter.
|
24
|
+
1. Press and hold `Pad Bank Selectors` `1` and `4`.
|
25
|
+
1. Turn Orbit on.
|
26
|
+
1. Press the lower-right-most pad.
|
27
|
+
1. Connect the USB adapter __within 10 seconds__.
|
28
|
+
1. Press the pad __above__ the lower-right-most pad.
|
29
|
+
1. The `Virtual Knob Selectors` will flash once to indicate successful pairing.
|
30
|
+
|
31
|
+
|
32
|
+
## Sleep Mode
|
33
|
+
|
34
|
+
Sleep mode will activate after 3 minutes of inactivity. Move the Orbit to wake it up.
|
35
|
+
|
36
|
+
|
37
|
+
## Wireless Concerns.
|
38
|
+
|
39
|
+
* 100 ft maximum distance with line of sight.
|
40
|
+
* 2.4Ghz frequency spectrum (turn off wifi/etc for best results).
|
41
|
+
|
42
|
+
|
43
|
+
## Hard Reset
|
44
|
+
|
45
|
+
1. Turn Orbit off, and unplug USB adapter.
|
46
|
+
1. Press and hold `Pad Bank Selectors` `1` and `4`.
|
47
|
+
1. Turn Orbit on.
|
48
|
+
1. Press the upper-left-most pad.
|
49
|
+
1. Press the pad to the right of the upper-left-most pad.
|
50
|
+
1. Turn Orbit off, and back on.
|
@@ -0,0 +1,141 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "rubygems"
|
3
|
+
require "bundler/setup"
|
4
|
+
Bundler.require(:default, :development)
|
5
|
+
|
6
|
+
require "surface_master"
|
7
|
+
|
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
|
16
|
+
TIME_SCALE = 4.0
|
17
|
+
|
18
|
+
# 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
|
+
FLIPPED = [[false, false], [false, false]]
|
24
|
+
PRESSED = (0..7).map { |x| (0..7).map { |y| false } }
|
25
|
+
NOW = [Time.now.to_f]
|
26
|
+
|
27
|
+
# Helpers
|
28
|
+
CC = %i(up down left right session user1 user2 scene1 scene2 scene3 scene4 scene5 scene6 scene7 scene8)
|
29
|
+
GRID = (0..7).map { |x| (0..7).map { |y| { grid: [x, y] } } }.flatten
|
30
|
+
WHITE = { red: 0x3F, green: 0x3F, blue: 0x3F }
|
31
|
+
|
32
|
+
def clamp(val); (val > 0x3F) ? 0x3F : val; end
|
33
|
+
|
34
|
+
def base_color(x, y)
|
35
|
+
return nil if PRESSED[x][y]
|
36
|
+
quad_x = x / 4
|
37
|
+
quad_y = 1 - (y / 4)
|
38
|
+
quad = QUADRANTS[quad_y][quad_x]
|
39
|
+
s_t = (Math.sin(NOW[0] * TIME_SCALE) * 0.5) + 0.5
|
40
|
+
tmp = { red: 0x00 + quad[:red] + (s_t * 0x3F).round,
|
41
|
+
green: (x * SCALE) + quad[:green],
|
42
|
+
blue: (y * SCALE) + quad[:blue] }
|
43
|
+
if FLIPPED[quad_y][quad_x]
|
44
|
+
carry = tmp[:red]
|
45
|
+
tmp[:red] = tmp[:green]
|
46
|
+
tmp[:green] = tmp[:blue]
|
47
|
+
tmp[:blue] = carry
|
48
|
+
end
|
49
|
+
{ red: clamp(tmp[:red]),
|
50
|
+
green: clamp(tmp[:green]),
|
51
|
+
blue: clamp(tmp[:blue]) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def init_board(interaction)
|
55
|
+
values = GRID.map do |value|
|
56
|
+
tmp = base_color(*value[:grid])
|
57
|
+
next unless tmp
|
58
|
+
value.merge(tmp)
|
59
|
+
end
|
60
|
+
interaction.changes(values.compact)
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_grid_rgb(interaction, red:, green:, blue: )
|
64
|
+
values = GRID.map { |value| value.merge(red: red, green: green, blue: blue) }
|
65
|
+
interaction.changes(values)
|
66
|
+
end
|
67
|
+
|
68
|
+
def goodbye(interaction)
|
69
|
+
interaction.changes([{ red: 0x00, green: 0x00, blue: 0x00, cc: :mixer },
|
70
|
+
{ red: 0x00, green: 0x00, blue: 0x00, cc: :scene1 },
|
71
|
+
{ red: 0x00, green: 0x00, blue: 0x00, cc: :scene2 },
|
72
|
+
{ red: 0x00, green: 0x00, blue: 0x00, cc: :scene3 },
|
73
|
+
{ red: 0x00, green: 0x00, blue: 0x00, cc: :scene4 }])
|
74
|
+
(0..63).step(2).each do |i|
|
75
|
+
ii = (63 - i) - 1
|
76
|
+
set_grid_rgb(interaction, red: ii, green: 0x00, blue: ii)
|
77
|
+
sleep 0.01
|
78
|
+
end
|
79
|
+
interaction.close
|
80
|
+
end
|
81
|
+
|
82
|
+
SurfaceMaster.init!
|
83
|
+
interaction = SurfaceMaster::Launchpad::Interaction.new
|
84
|
+
interaction.response_to(:grid) do |inter, action|
|
85
|
+
x = action[:x]
|
86
|
+
y = action[:y]
|
87
|
+
PRESSED[x][y] = (action[:state] == :down)
|
88
|
+
value = base_color(x, y) || WHITE
|
89
|
+
value[:grid] = [x, y]
|
90
|
+
inter.change(value)
|
91
|
+
end
|
92
|
+
|
93
|
+
def flip_quad!(inter, cc, quad_x, quad_y)
|
94
|
+
FLIPPED[quad_y][quad_x] = !FLIPPED[quad_y][quad_x]
|
95
|
+
if FLIPPED[quad_y][quad_x]
|
96
|
+
color = { red: 0x1F, green: 0x1F, blue: 0x1F }
|
97
|
+
else
|
98
|
+
color = { red: 0x03, green: 0x03, blue: 0x03 }
|
99
|
+
end
|
100
|
+
inter.change(color.merge(cc: cc))
|
101
|
+
end
|
102
|
+
|
103
|
+
interaction.response_to(:scene1, :down) do |inter, action|
|
104
|
+
flip_quad!(inter, action[:type], 0, 0)
|
105
|
+
end
|
106
|
+
interaction.response_to(:scene2, :down) do |inter, action|
|
107
|
+
flip_quad!(inter, action[:type], 1, 0)
|
108
|
+
end
|
109
|
+
interaction.response_to(:scene3, :down) do |inter, action|
|
110
|
+
flip_quad!(inter, action[:type], 0, 1)
|
111
|
+
end
|
112
|
+
interaction.response_to(:scene4, :down) do |inter, action|
|
113
|
+
flip_quad!(inter, action[:type], 1, 1)
|
114
|
+
end
|
115
|
+
|
116
|
+
interaction.response_to(:mixer, :down) do |_interaction, action|
|
117
|
+
interaction.stop
|
118
|
+
end
|
119
|
+
interaction.change({ red: 0x03, green: 0x00, blue: 0x00, cc: :mixer })
|
120
|
+
interaction.changes(%i(scene1 scene2 scene3 scene4).map { |cc| { red: 0x03, green: 0x03, blue: 0x03, cc: cc } })
|
121
|
+
|
122
|
+
init_board(interaction)
|
123
|
+
input_thread = Thread.new do
|
124
|
+
interaction.start
|
125
|
+
end
|
126
|
+
animation_thread = Thread.new do
|
127
|
+
loop do
|
128
|
+
begin
|
129
|
+
NOW[0] = Time.now.to_f
|
130
|
+
init_board(interaction)
|
131
|
+
rescue Exception => e
|
132
|
+
puts e.inspect
|
133
|
+
puts e.backtrace.join("\n")
|
134
|
+
end
|
135
|
+
sleep 0.01
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
input_thread.join
|
140
|
+
animation_thread.terminate
|
141
|
+
goodbye(interaction)
|
data/examples/monitor.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# require "bignum"
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler/setup"
|
5
|
+
Bundler.require(:default, :development)
|
6
|
+
|
7
|
+
require "surface_master"
|
8
|
+
|
9
|
+
def goodbye(interaction)
|
10
|
+
data = []
|
11
|
+
(0..7).each do |x|
|
12
|
+
(0..7).each do |y|
|
13
|
+
data << { x: x, y: y, red: 0x00, green: 0x00, blue: 0x00 }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
interaction.changes(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def bar(interaction, x, val, r, g, b)
|
20
|
+
data = []
|
21
|
+
(0..val).each do |y|
|
22
|
+
data << { x: x, y: y, red: r, green: g, blue: b }
|
23
|
+
end
|
24
|
+
((val+1)..7).each do |y|
|
25
|
+
data << { x: x, y: y, red: 0x00, green: 0x00, blue: 0x00 }
|
26
|
+
end
|
27
|
+
interaction.changes(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
interaction = SurfaceMaster::Launchpad::Interaction.new
|
31
|
+
monitor = Thread.new do
|
32
|
+
loop do
|
33
|
+
fields = `iostat -c 2 disk0`.split(/\n/).last.strip.split(/\s+/)
|
34
|
+
cpu_pct = 100 - fields[-4].to_i
|
35
|
+
cpu_usage = ((cpu_pct / 100.0) * 8.0).round.to_i
|
36
|
+
|
37
|
+
disk_pct = (fields[2].to_f / 750.0) * 100.0
|
38
|
+
disk_usage = ((disk_pct / 100.0) * 8.0).round.to_i
|
39
|
+
|
40
|
+
puts "I/O=#{disk_pct}%, CPU=#{cpu_pct}%"
|
41
|
+
|
42
|
+
# TODO: Network in/out...
|
43
|
+
|
44
|
+
# TODO: Make block I/O not be a bar but a fill, with scale indicated by color...
|
45
|
+
|
46
|
+
bar(interaction, 0, cpu_usage, 0x3F, 0x00, 0x00)
|
47
|
+
bar(interaction, 1, disk_usage, 0x00, 0x3F, 0x00)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
interaction.response_to(:mixer, :down) do |_interaction, action|
|
52
|
+
puts "Shutting down"
|
53
|
+
begin
|
54
|
+
monitor.kill
|
55
|
+
goodbye(interaction)
|
56
|
+
interaction.stop
|
57
|
+
rescue Exception => e
|
58
|
+
puts e.inspect
|
59
|
+
end
|
60
|
+
end
|
61
|
+
interaction.start
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# require "bignum"
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler/setup"
|
5
|
+
Bundler.require(:default, :development)
|
6
|
+
|
7
|
+
require "surface_master"
|
8
|
+
|
9
|
+
class Fixnum
|
10
|
+
def to_hex; "%02X" % self; end
|
11
|
+
end
|
12
|
+
|
13
|
+
# def debug(msg)
|
14
|
+
# STDERR.puts "DEBUG: #{msg}"
|
15
|
+
# end
|
16
|
+
|
17
|
+
# def fmt_message(message)
|
18
|
+
# message[:raw][:message].map(&:to_hex).join(' ')
|
19
|
+
# end
|
20
|
+
|
21
|
+
SurfaceMaster.init!
|
22
|
+
device = SurfaceMaster::Orbit::Device.new
|
23
|
+
loop do
|
24
|
+
device.read.each do |input|
|
25
|
+
puts input.inspect
|
26
|
+
end
|
27
|
+
sleep 0.1
|
28
|
+
end
|
29
|
+
|
30
|
+
# interaction = ControlCenter::Launchpad::Interaction.new
|
31
|
+
# interaction.response_to(:grid) do |inter, action|
|
32
|
+
# x = action[:x]
|
33
|
+
# y = action[:y]
|
34
|
+
# PRESSED[x][y] = (action[:state] == :down)
|
35
|
+
# value = base_color(x, y) || WHITE
|
36
|
+
# value[:grid] = [x, y]
|
37
|
+
# inter.device.change(value)
|
38
|
+
# end
|
39
|
+
|
40
|
+
# interaction.device.change({ red: 0x03, green: 0x00, blue: 0x00, cc: :mixer })
|
41
|
+
# interaction.device.changes(%i(scene1 scene2 scene3 scene4).map { |cc| { red: 0x03, green: 0x03, blue: 0x03, cc: cc } })
|
42
|
+
|
43
|
+
# init_board(interaction)
|
44
|
+
# input_thread = Thread.new do
|
45
|
+
# interaction.start
|
46
|
+
# end
|
47
|
+
# animation_thread = Thread.new do
|
48
|
+
# loop do
|
49
|
+
# begin
|
50
|
+
# NOW[0] = Time.now.to_f
|
51
|
+
# init_board(interaction)
|
52
|
+
# rescue Exception => e
|
53
|
+
# puts e.inspect
|
54
|
+
# puts e.backtrace.join("\n")
|
55
|
+
# end
|
56
|
+
# sleep 0.01
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
|
60
|
+
# input_thread.join
|
61
|
+
# animation_thread.terminate
|
62
|
+
# goodbye(interaction)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "portmidi"
|
2
|
+
require "logger"
|
3
|
+
|
4
|
+
# APIs to enable access to various MIDI-based control surfaces.
|
5
|
+
module SurfaceMaster
|
6
|
+
def self.init!
|
7
|
+
@initialized ||= begin
|
8
|
+
Portmidi.start
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require "surface_master/version"
|
15
|
+
require "surface_master/errors"
|
16
|
+
require "surface_master/logging"
|
17
|
+
require "surface_master/device"
|
18
|
+
require "surface_master/interaction"
|
19
|
+
|
20
|
+
require "surface_master/launchpad/errors"
|
21
|
+
require "surface_master/launchpad/midi_codes"
|
22
|
+
require "surface_master/launchpad/device"
|
23
|
+
require "surface_master/launchpad/interaction"
|
24
|
+
|
25
|
+
require "surface_master/orbit/midi_codes"
|
26
|
+
require "surface_master/orbit/device"
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module SurfaceMaster
|
2
|
+
# Base class for MIDI controller drivers.
|
3
|
+
#
|
4
|
+
# Sub-classes should extend the constructor, extend `sysex_prefix`, implement `reset!`, and add
|
5
|
+
# whatever methods are appropriate for them.
|
6
|
+
class Device
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
def initialize(opts = nil)
|
10
|
+
opts = { input: true,
|
11
|
+
output: true }
|
12
|
+
.merge(opts || {})
|
13
|
+
|
14
|
+
self.logger = opts[:logger]
|
15
|
+
logger.debug "Initializing #{self.class}##{object_id} with #{opts.inspect}"
|
16
|
+
|
17
|
+
@input = create_device!(Portmidi.input_devices,
|
18
|
+
Portmidi::Input,
|
19
|
+
id: opts[:input_device_id],
|
20
|
+
name: opts[:device_name]) if opts[:input]
|
21
|
+
@output = create_device!(Portmidi.output_devices,
|
22
|
+
Portmidi::Output,
|
23
|
+
id: opts[:output_device_id],
|
24
|
+
name: opts[:device_name]) if opts[:output]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Closes the device - nothing can be done with the device afterwards.
|
28
|
+
def close
|
29
|
+
logger.debug "Closing #{self.class}##{object_id}"
|
30
|
+
@input.close unless @input.nil?
|
31
|
+
@input = nil
|
32
|
+
@output.close unless @output.nil?
|
33
|
+
@output = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def closed?; !(input_enabled? || output_enabled?); end
|
37
|
+
def input_enabled?; !@input.nil?; end
|
38
|
+
def output_enabled?; !@output.nil?; end
|
39
|
+
|
40
|
+
def reset!; end
|
41
|
+
|
42
|
+
def read
|
43
|
+
unless input_enabled?
|
44
|
+
logger.error "Trying to read from device that's not been initialized for input!"
|
45
|
+
raise SurfaceMaster::NoInputAllowedError
|
46
|
+
end
|
47
|
+
|
48
|
+
Array(@input.read(16)).collect do |midi_message|
|
49
|
+
(code, note, velocity) = midi_message[:message]
|
50
|
+
{ timestamp: midi_message[:timestamp],
|
51
|
+
state: (velocity == 127) ? :down : :up,
|
52
|
+
velocity: velocity,
|
53
|
+
code: code,
|
54
|
+
note: note }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def sysex_prefix; [0xF0]; end
|
61
|
+
def sysex_suffix; 0xF7; end
|
62
|
+
def sysex_msg(*payload); (sysex_prefix + [payload, sysex_suffix]).flatten.compact; end
|
63
|
+
def sysex!(*payload)
|
64
|
+
msg = sysex_msg(payload)
|
65
|
+
logger.debug { "#{msg.length}: 0x#{msg.map(&:to_hex).join(", 0x")}" }
|
66
|
+
@output.write_sysex(msg)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_device!(devices, device_type, opts)
|
70
|
+
logger.debug "Creating #{device_type} with #{opts.inspect}, choosing from portmidi devices: #{devices.inspect}"
|
71
|
+
id = opts[:id]
|
72
|
+
if id.nil?
|
73
|
+
name = opts[:name] || @name
|
74
|
+
device = devices.select { |dev| dev.name == name }.first
|
75
|
+
id = device.device_id unless device.nil?
|
76
|
+
end
|
77
|
+
if id.nil?
|
78
|
+
message = "MIDI Device `#{opts[:id] || opts[:name]}` doesn't exist!"
|
79
|
+
logger.fatal message
|
80
|
+
raise SurfaceMaster::NoSuchDeviceError.new(message)
|
81
|
+
end
|
82
|
+
device_type.new(id)
|
83
|
+
rescue RuntimeError => e # TODO: Uh, this should be StandardException, perhaps?
|
84
|
+
logger.fatal "Error creating #{device_type}: #{e.inspect}"
|
85
|
+
raise SurfaceMaster::DeviceBusyError.new(e)
|
86
|
+
end
|
87
|
+
|
88
|
+
def message(status, data1, data2); { message: [status, data1, data2], timestamp: 0 }; end
|
89
|
+
end
|
90
|
+
end
|