surface_master 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|