surface_master 0.2.0 → 0.2.1
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 +4 -4
- data/.rubocop.yml +107 -0
- data/.travis.yml +2 -3
- data/CHANGELOG.md +16 -0
- data/Gemfile +5 -1
- data/Rakefile +73 -5
- data/debug_tools/decode.rb +1 -1
- data/examples/launchpad_testbed.rb +67 -28
- data/examples/monitor.rb +23 -11
- data/examples/orbit_testbed.rb +1 -42
- data/lib/surface_master/device.rb +40 -39
- data/lib/surface_master/interaction.rb +81 -55
- data/lib/surface_master/launchpad/device.rb +70 -58
- data/lib/surface_master/launchpad/errors.rb +8 -9
- data/lib/surface_master/launchpad/interaction.rb +24 -42
- data/lib/surface_master/orbit/device.rb +85 -85
- data/lib/surface_master/orbit/interaction.rb +1 -0
- data/lib/surface_master/orbit/midi_codes.rb +3 -1
- data/lib/surface_master/version.rb +2 -1
- data/lib/{control_center.rb → surface_master.rb} +0 -0
- data/surface_master.gemspec +7 -5
- data/test/helper.rb +16 -17
- data/test/test_device.rb +167 -355
- data/test/test_interaction.rb +177 -188
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0809d0fe24d9da2f41becb4e27a0ec785019c029
|
4
|
+
data.tar.gz: 43919aa3d44ec3006953501bebf6f3d5a4010f76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ab45e7ab7dc4761d108f5dd5dc758516bf35a93b6cc4ea0b5142ed80c2ac5a4c11469756762a57aea5de98a7821f79ed12ace9649bb4ec12ae85532b1bd4493
|
7
|
+
data.tar.gz: 4ede3f9b33450d69c3bcd250494426b4a66cda5d25c1907ce370fd4bc3a8c7a17cc1131d8d077c832fc4dad342854327c57440e25f522651a9a7593118ec2374
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
AllCops:
|
2
|
+
Include:
|
3
|
+
- '**/Gemfile'
|
4
|
+
- '**/Rakefile'
|
5
|
+
- '**/*.rake'
|
6
|
+
- '**/*.gemspec'
|
7
|
+
Exclude:
|
8
|
+
- tmp/**/*
|
9
|
+
- pkg/**/*
|
10
|
+
|
11
|
+
Lint/FormatParameterMismatch:
|
12
|
+
Enabled: true
|
13
|
+
|
14
|
+
# Lint/OptionHash:
|
15
|
+
# Enabled: true
|
16
|
+
|
17
|
+
Metrics/LineLength:
|
18
|
+
Max: 100
|
19
|
+
|
20
|
+
Metrics/MethodLength:
|
21
|
+
Exclude:
|
22
|
+
- 'db/migrate/*'
|
23
|
+
|
24
|
+
Performance/StringReplacement:
|
25
|
+
Enabled: true
|
26
|
+
|
27
|
+
Style/AccessModifierIndentation:
|
28
|
+
EnforcedStyle: outdent
|
29
|
+
|
30
|
+
Style/AlignHash:
|
31
|
+
EnforcedColonStyle: table
|
32
|
+
# TODO: Err, maybe keys here?
|
33
|
+
EnforcedLastArgumentHashStyle: ignore_implicit
|
34
|
+
|
35
|
+
Style/CollectionMethods:
|
36
|
+
PreferredMethods:
|
37
|
+
find: detect
|
38
|
+
inject: reduce
|
39
|
+
collect: map
|
40
|
+
find_all: select
|
41
|
+
|
42
|
+
# Allow comments to be aligned to one another.
|
43
|
+
Style/CommentIndentation:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
# Don't bother with class documentation for Rails application class, and DB
|
47
|
+
# migrations.
|
48
|
+
Style/Documentation:
|
49
|
+
Exclude:
|
50
|
+
- 'db/migrate/*'
|
51
|
+
- 'config/application.rb'
|
52
|
+
|
53
|
+
# We like the truthiness operator.
|
54
|
+
Style/DoubleNegation:
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
# Style/DotPosition:
|
58
|
+
# EnforcedStyle: trailing
|
59
|
+
|
60
|
+
Style/EmptyLineBetweenDefs:
|
61
|
+
AllowAdjacentOneLineDefs: true
|
62
|
+
|
63
|
+
Style/ExtraSpacing:
|
64
|
+
AllowForAlignment: true
|
65
|
+
|
66
|
+
Style/FormatString:
|
67
|
+
EnforcedStyle: 'String#%'
|
68
|
+
SupportedStyles: 'String#%'
|
69
|
+
|
70
|
+
# Style/GuardClause:
|
71
|
+
# Enabled: false
|
72
|
+
|
73
|
+
Style/IfUnlessModifier:
|
74
|
+
Enabled: false
|
75
|
+
|
76
|
+
Style/LineEndConcatenation:
|
77
|
+
Enabled: false
|
78
|
+
|
79
|
+
Style/RegexpLiteral:
|
80
|
+
AllowInnerSlashes: false
|
81
|
+
|
82
|
+
Style/RescueEnsureAlignment:
|
83
|
+
Enabled: true
|
84
|
+
|
85
|
+
# Style/RescueModifier:
|
86
|
+
# Enabled: false
|
87
|
+
|
88
|
+
# We like terse methods.
|
89
|
+
Style/SingleLineMethods:
|
90
|
+
Enabled: false
|
91
|
+
|
92
|
+
Style/SpaceAroundOperators:
|
93
|
+
MultiSpaceAllowedForOperators:
|
94
|
+
- '='
|
95
|
+
- '=>'
|
96
|
+
- '||='
|
97
|
+
- '+='
|
98
|
+
- '-='
|
99
|
+
- '*='
|
100
|
+
- '/='
|
101
|
+
|
102
|
+
Style/StringLiterals:
|
103
|
+
EnforcedStyle: double_quotes
|
104
|
+
|
105
|
+
Style/TrailingComma:
|
106
|
+
EnforcedStyleForMultiline: comma
|
107
|
+
SupportedStyles: comma
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Changes
|
2
|
+
|
3
|
+
## v0.2.1
|
4
|
+
|
5
|
+
* Missed a file rename.
|
6
|
+
* Remove some broken/not-quite-supported, and no longer particurly useful features from Novation Launchpad support. (Specifically, column-wise/row-wise/board-wise changing of lights in paletted mode.)
|
7
|
+
* Fix test suite.
|
8
|
+
* Break apart a lot of complexity.
|
9
|
+
* Apply style guide (mostly).
|
10
|
+
|
11
|
+
|
12
|
+
## v0.2.0
|
13
|
+
|
14
|
+
* Initial release, based on [launchpad](https://github.com/thomasjachmann/launchpad) gem.
|
15
|
+
* Almost full support for Novation Launchpad Mark 2 (the one with RGB LEDs), missing flashing/pulsing of lights, etc.
|
16
|
+
* Partial support for Numark Orbit.
|
data/Gemfile
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
+
# rubocop:disable Style/LeadingCommentSpace
|
1
2
|
#ruby-gemset=surface_master
|
3
|
+
# rubocop:enable Style/LeadingCommentSpace
|
2
4
|
ruby "2.2.3"
|
3
5
|
source "https://rubygems.org"
|
4
6
|
|
5
|
-
# Specify your gem's dependencies in
|
7
|
+
# Specify your gem's dependencies in surface_master.gemspec
|
6
8
|
gemspec
|
7
9
|
|
8
10
|
group :development do
|
9
11
|
gem "rake", require: false
|
10
12
|
gem "minitest-reporters", require: false
|
11
13
|
gem "mocha", require: false
|
14
|
+
gem "bundler-audit", require: false
|
15
|
+
gem "rubocop", require: false
|
12
16
|
gem "pry"
|
13
17
|
end
|
data/Rakefile
CHANGED
@@ -1,10 +1,78 @@
|
|
1
|
-
require
|
1
|
+
require "bundler/gem_tasks"
|
2
2
|
|
3
|
-
require
|
3
|
+
require "rake/testtask"
|
4
4
|
Rake::TestTask.new(:test) do |test|
|
5
|
-
test.libs <<
|
6
|
-
test.pattern =
|
5
|
+
test.libs << "lib" << "test"
|
6
|
+
test.pattern = "test/**/test_*.rb"
|
7
7
|
test.verbose = true
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
lib = File.expand_path("../lib", __FILE__)
|
11
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
12
|
+
|
13
|
+
task_dir = File.expand_path("../tasks", __FILE__)
|
14
|
+
raw_task_files = FileList["#{task_dir}/**/*.rake"] +
|
15
|
+
FileList["tasks/**/*.rake"]
|
16
|
+
raw_task_files
|
17
|
+
.map { |fname| File.expand_path(fname) }
|
18
|
+
.sort
|
19
|
+
.each do |fname|
|
20
|
+
load fname
|
21
|
+
end
|
22
|
+
|
23
|
+
# Define a task named `name` that runs all tasks under an identically
|
24
|
+
# named `namespace`.
|
25
|
+
def parent_task(name)
|
26
|
+
task name do
|
27
|
+
::Rake::Task
|
28
|
+
.tasks
|
29
|
+
.select { |t| t.name =~ /^#{name}:/ }
|
30
|
+
.sort { |a, b| a.name <=> b.name }
|
31
|
+
.each(&:execute)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Open a Ruby console to Pry."
|
36
|
+
task :console do
|
37
|
+
# rubocop:disable Lint/Debugger
|
38
|
+
require "pry"
|
39
|
+
binding.pry
|
40
|
+
# rubocop:enable Lint/Debugger
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :lint do
|
44
|
+
desc "Run Rubocop against the codebase."
|
45
|
+
task :rubocop do
|
46
|
+
require "yaml"
|
47
|
+
sh "rubocop --display-cop-names"
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Run bundler-audit against the Gemfile."
|
51
|
+
task :'bundler-audit' do
|
52
|
+
require "bundler/audit/cli"
|
53
|
+
|
54
|
+
%w(update check).each do |command|
|
55
|
+
Bundler::Audit::CLI.start [command]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
have_cloc = `which cloc`.strip != ""
|
60
|
+
if have_cloc
|
61
|
+
desc "Show LOC metrics for project using cloc."
|
62
|
+
task :cloc do
|
63
|
+
sh "cloc . --exclude-dir=pkg,.bundle,tmp"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Check for outdated gems."
|
68
|
+
task :bundler do
|
69
|
+
# Don't error-out if this fails, since we may not be able to update some
|
70
|
+
# deps.
|
71
|
+
sh "bundle outdated || true"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "Run all lint checks against the code."
|
76
|
+
parent_task :lint
|
77
|
+
|
78
|
+
task default: [:test, :lint]
|
data/debug_tools/decode.rb
CHANGED
@@ -21,34 +21,68 @@ QUADRANTS = [
|
|
21
21
|
[{ red: 0x00, green: 0x00, blue: 0x2F }, { red: 0x2F, green: 0x2F, blue: 0x00 }],
|
22
22
|
]
|
23
23
|
FLIPPED = [[false, false], [false, false]]
|
24
|
-
PRESSED = (0..7).map { |
|
24
|
+
PRESSED = (0..7).map { |_x| (0..7).map { |_y| false } }
|
25
25
|
NOW = [Time.now.to_f]
|
26
26
|
|
27
27
|
# Helpers
|
28
|
-
CC = %i(up down left right session user1 user2 scene1 scene2 scene3 scene4 scene5 scene6 scene7
|
28
|
+
CC = %i(up down left right session user1 user2 scene1 scene2 scene3 scene4 scene5 scene6 scene7
|
29
|
+
scene8)
|
29
30
|
GRID = (0..7).map { |x| (0..7).map { |y| { grid: [x, y] } } }.flatten
|
30
31
|
WHITE = { red: 0x3F, green: 0x3F, blue: 0x3F }
|
32
|
+
BLACK = { red: 0x00, green: 0x00, blue: 0x00 }
|
31
33
|
|
32
34
|
def clamp(val); (val > 0x3F) ? 0x3F : val; end
|
33
35
|
|
34
|
-
def
|
35
|
-
return nil if PRESSED[x][y]
|
36
|
+
def quad_for(x, y)
|
36
37
|
quad_x = x / 4
|
37
38
|
quad_y = 1 - (y / 4)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
39
|
+
[QUADRANTS[quad_y][quad_x], FLIPPED[quad_y][quad_x]]
|
40
|
+
end
|
41
|
+
|
42
|
+
def positional_color(x, y)
|
43
|
+
{ red: 0x00,
|
44
|
+
green: (x * SCALE),
|
45
|
+
blue: (y * SCALE) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def temporal_color(_x, _y)
|
49
|
+
s_t = (Math.sin(NOW[0] * TIME_SCALE) * 0.5) + 0.5
|
50
|
+
{ red: (s_t * 0x3F).round,
|
51
|
+
green: 0x00,
|
52
|
+
blue: 0x00 }
|
53
|
+
end
|
54
|
+
|
55
|
+
def clamp_color(color)
|
56
|
+
{ red: clamp(color[:red]),
|
57
|
+
green: clamp(color[:green]),
|
58
|
+
blue: clamp(color[:blue]) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def apply_flip!(flipped, color)
|
62
|
+
return unless flipped
|
63
|
+
carry = color[:red]
|
64
|
+
color[:red] = color[:green]
|
65
|
+
color[:green] = color[:blue]
|
66
|
+
color[:blue] = carry
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_colors(*colors)
|
70
|
+
result = {}
|
71
|
+
%i(red green blue).each do |component|
|
72
|
+
result[component] = colors.inject(0) { |a, e| a + e[component] }
|
48
73
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
77
|
+
def base_color(x, y)
|
78
|
+
return nil if PRESSED[x][y]
|
79
|
+
quad, flipped = quad_for(x, y)
|
80
|
+
p_color = positional_color(x, y)
|
81
|
+
t_color = temporal_color(x, y)
|
82
|
+
tmp = add_colors(quad, p_color, t_color)
|
83
|
+
apply_flip!(flipped, tmp)
|
84
|
+
|
85
|
+
clamp_color(tmp)
|
52
86
|
end
|
53
87
|
|
54
88
|
def init_board(interaction)
|
@@ -60,17 +94,13 @@ def init_board(interaction)
|
|
60
94
|
interaction.changes(values.compact)
|
61
95
|
end
|
62
96
|
|
63
|
-
def set_grid_rgb(interaction, red:, green:, blue:
|
97
|
+
def set_grid_rgb(interaction, red:, green:, blue:)
|
64
98
|
values = GRID.map { |value| value.merge(red: red, green: green, blue: blue) }
|
65
99
|
interaction.changes(values)
|
66
100
|
end
|
67
101
|
|
68
102
|
def goodbye(interaction)
|
69
|
-
interaction
|
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 }])
|
103
|
+
buttons_off(interaction)
|
74
104
|
(0..63).step(2).each do |i|
|
75
105
|
ii = (63 - i) - 1
|
76
106
|
set_grid_rgb(interaction, red: ii, green: 0x00, blue: ii)
|
@@ -79,6 +109,14 @@ def goodbye(interaction)
|
|
79
109
|
interaction.close
|
80
110
|
end
|
81
111
|
|
112
|
+
def buttons_off(interaction)
|
113
|
+
interaction.changes([BLACK.merge(cc: :mixer),
|
114
|
+
BLACK.merge(cc: :scene1),
|
115
|
+
BLACK.merge(cc: :scene2),
|
116
|
+
BLACK.merge(cc: :scene3),
|
117
|
+
BLACK.merge(cc: :scene4)])
|
118
|
+
end
|
119
|
+
|
82
120
|
SurfaceMaster.init!
|
83
121
|
interaction = SurfaceMaster::Launchpad::Interaction.new
|
84
122
|
interaction.response_to(:grid) do |inter, action|
|
@@ -91,7 +129,7 @@ interaction.response_to(:grid) do |inter, action|
|
|
91
129
|
end
|
92
130
|
|
93
131
|
def flip_quad!(inter, cc, quad_x, quad_y)
|
94
|
-
FLIPPED[quad_y][quad_x]
|
132
|
+
FLIPPED[quad_y][quad_x] = !FLIPPED[quad_y][quad_x]
|
95
133
|
if FLIPPED[quad_y][quad_x]
|
96
134
|
color = { red: 0x1F, green: 0x1F, blue: 0x1F }
|
97
135
|
else
|
@@ -113,11 +151,12 @@ interaction.response_to(:scene4, :down) do |inter, action|
|
|
113
151
|
flip_quad!(inter, action[:type], 1, 1)
|
114
152
|
end
|
115
153
|
|
116
|
-
interaction.response_to(:mixer, :down) do |_interaction,
|
154
|
+
interaction.response_to(:mixer, :down) do |_interaction, _action|
|
117
155
|
interaction.stop
|
118
156
|
end
|
119
|
-
interaction.change(
|
120
|
-
|
157
|
+
interaction.change(red: 0x03, green: 0x00, blue: 0x00, cc: :mixer)
|
158
|
+
BTN_COL = { red: 0x03, green: 0x03, blue: 0x03, cc: cc }
|
159
|
+
interaction.changes(%i(scene1 scene2 scene3 scene4).map { |cc| BTN_COL.merge(cc: cc) })
|
121
160
|
|
122
161
|
init_board(interaction)
|
123
162
|
input_thread = Thread.new do
|
@@ -128,7 +167,7 @@ animation_thread = Thread.new do
|
|
128
167
|
begin
|
129
168
|
NOW[0] = Time.now.to_f
|
130
169
|
init_board(interaction)
|
131
|
-
rescue
|
170
|
+
rescue StandardError => e
|
132
171
|
puts e.inspect
|
133
172
|
puts e.backtrace.join("\n")
|
134
173
|
end
|
data/examples/monitor.rb
CHANGED
@@ -16,19 +16,31 @@ def goodbye(interaction)
|
|
16
16
|
interaction.changes(data)
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
# Janky bar-graph widget.
|
20
|
+
class Bar
|
21
|
+
BLACK = { red: 0x00, green: 0x00, blue: 0x00 }
|
22
|
+
def initialize(interaction, x, color)
|
23
|
+
@interaction = interaction
|
24
|
+
@x = x
|
25
|
+
@color = color
|
23
26
|
end
|
24
|
-
|
25
|
-
|
27
|
+
|
28
|
+
def update(val)
|
29
|
+
data = []
|
30
|
+
(0..val).each do |y|
|
31
|
+
data << @color.merge(grid: [@x, y])
|
32
|
+
end
|
33
|
+
((val + 1)..7).each do |y|
|
34
|
+
data << BLACK.merge(grid: [@x, y])
|
35
|
+
end
|
36
|
+
@interaction.changes(data)
|
26
37
|
end
|
27
|
-
interaction.changes(data)
|
28
38
|
end
|
29
39
|
|
30
40
|
interaction = SurfaceMaster::Launchpad::Interaction.new
|
31
|
-
|
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)
|
43
|
+
monitor = Thread.new do
|
32
44
|
loop do
|
33
45
|
fields = `iostat -c 2 disk0`.split(/\n/).last.strip.split(/\s+/)
|
34
46
|
cpu_pct = 100 - fields[-4].to_i
|
@@ -43,12 +55,12 @@ monitor = Thread.new do
|
|
43
55
|
|
44
56
|
# TODO: Make block I/O not be a bar but a fill, with scale indicated by color...
|
45
57
|
|
46
|
-
|
47
|
-
|
58
|
+
cpu_bar.update(cpu_usage)
|
59
|
+
io_bar.update(disk_usage)
|
48
60
|
end
|
49
61
|
end
|
50
62
|
|
51
|
-
interaction.response_to(:mixer, :down) do |_interaction,
|
63
|
+
interaction.response_to(:mixer, :down) do |_interaction, _action|
|
52
64
|
puts "Shutting down"
|
53
65
|
begin
|
54
66
|
monitor.kill
|
data/examples/orbit_testbed.rb
CHANGED
@@ -6,18 +6,11 @@ Bundler.require(:default, :development)
|
|
6
6
|
|
7
7
|
require "surface_master"
|
8
8
|
|
9
|
+
# Monkey-patching to make debugging easier.
|
9
10
|
class Fixnum
|
10
11
|
def to_hex; "%02X" % self; end
|
11
12
|
end
|
12
13
|
|
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
14
|
SurfaceMaster.init!
|
22
15
|
device = SurfaceMaster::Orbit::Device.new
|
23
16
|
loop do
|
@@ -26,37 +19,3 @@ loop do
|
|
26
19
|
end
|
27
20
|
sleep 0.1
|
28
21
|
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)
|
@@ -7,21 +7,10 @@ module SurfaceMaster
|
|
7
7
|
include Logging
|
8
8
|
|
9
9
|
def initialize(opts = nil)
|
10
|
-
opts
|
11
|
-
output: true }
|
12
|
-
.merge(opts || {})
|
13
|
-
|
10
|
+
opts = { input: true, output: true }.merge(opts || {})
|
14
11
|
self.logger = opts[:logger]
|
15
|
-
|
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]
|
12
|
+
@input = create_input_device(opts)
|
13
|
+
@output = create_output_device(opts)
|
25
14
|
end
|
26
15
|
|
27
16
|
# Closes the device - nothing can be done with the device afterwards.
|
@@ -40,18 +29,15 @@ module SurfaceMaster
|
|
40
29
|
def reset!; end
|
41
30
|
|
42
31
|
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
|
32
|
+
fail SurfaceMaster::NoInputAllowedError unless input_enabled?
|
47
33
|
|
48
34
|
Array(@input.read(16)).collect do |midi_message|
|
49
35
|
(code, note, velocity) = midi_message[:message]
|
50
|
-
{ timestamp:
|
51
|
-
state:
|
52
|
-
velocity:
|
53
|
-
code:
|
54
|
-
note:
|
36
|
+
{ timestamp: midi_message[:timestamp],
|
37
|
+
state: (velocity == 127) ? :down : :up,
|
38
|
+
velocity: velocity,
|
39
|
+
code: code,
|
40
|
+
note: note }
|
55
41
|
end
|
56
42
|
end
|
57
43
|
|
@@ -60,29 +46,44 @@ module SurfaceMaster
|
|
60
46
|
def sysex_prefix; [0xF0]; end
|
61
47
|
def sysex_suffix; 0xF7; end
|
62
48
|
def sysex_msg(*payload); (sysex_prefix + [payload, sysex_suffix]).flatten.compact; end
|
49
|
+
|
63
50
|
def sysex!(*payload)
|
51
|
+
fail NoOutputAllowedError unless output_enabled?
|
64
52
|
msg = sysex_msg(payload)
|
65
|
-
logger.debug { "#{msg.length}: 0x#{msg.map
|
53
|
+
logger.debug { "#{msg.length}: 0x#{msg.map { |b| '%02X' % b }.join(' ')}" }
|
66
54
|
@output.write_sysex(msg)
|
67
55
|
end
|
68
56
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
57
|
+
|
58
|
+
def create_input_device(opts)
|
59
|
+
return nil unless opts[:input]
|
60
|
+
create_device(Portmidi.input_devices,
|
61
|
+
Portmidi::Input,
|
62
|
+
id: opts[:input_device_id],
|
63
|
+
name: opts[:device_name])
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_output_device(opts)
|
67
|
+
return nil unless opts[:output]
|
68
|
+
create_device(Portmidi.output_devices,
|
69
|
+
Portmidi::Output,
|
70
|
+
id: opts[:output_device_id],
|
71
|
+
name: opts[:device_name])
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_device(devices, device_type, opts)
|
75
|
+
id = opts[:id] || find_device_id(devices, opts[:name])
|
76
|
+
fail SurfaceMaster::NoSuchDeviceError if id.nil?
|
82
77
|
device_type.new(id)
|
83
78
|
rescue RuntimeError => e # TODO: Uh, this should be StandardException, perhaps?
|
84
|
-
|
85
|
-
|
79
|
+
raise SurfaceMaster::DeviceBusyError, e
|
80
|
+
end
|
81
|
+
|
82
|
+
def find_device_id(devices, name)
|
83
|
+
name ||= @name
|
84
|
+
device = devices.find { |dev| dev.name == name }
|
85
|
+
id = device.device_id unless device.nil?
|
86
|
+
id
|
86
87
|
end
|
87
88
|
|
88
89
|
def message(status, data1, data2); { message: [status, data1, data2], timestamp: 0 }; end
|