surface_master 0.2.0 → 0.2.1

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: 5e7fbba40cb034f0db7198957749fc22fc7facee
4
- data.tar.gz: bfd9dbc0e31d58f6983824d1ce9ce998a8308903
3
+ metadata.gz: 0809d0fe24d9da2f41becb4e27a0ec785019c029
4
+ data.tar.gz: 43919aa3d44ec3006953501bebf6f3d5a4010f76
5
5
  SHA512:
6
- metadata.gz: ee30f694463a70d6ade774fdff8a5ab8d99492fedfd58dbdf519364a76e283f3db3b4f157a171acb6983c4c7507b1c7ed9103028923673fbf20a14821bedf76b
7
- data.tar.gz: f0d1340826652776bd3c6a3faab9f0f859a5be96358489a122b9a3664e2624bb9e9927d602ed11a42eca6f6aa05cfd818d18b1763aef7746c87e67c045552dc4
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
@@ -1,8 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.8.7
4
- - 1.9.3
5
- - 2.0.0
3
+ - 2.2.0
4
+ - 2.2.3
6
5
  before_install:
7
6
  - sudo apt-get update
8
7
  - sudo apt-get install libportmidi-dev
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 launchpad.gemspec
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 'bundler/gem_tasks'
1
+ require "bundler/gem_tasks"
2
2
 
3
- require 'rake/testtask'
3
+ require "rake/testtask"
4
4
  Rake::TestTask.new(:test) do |test|
5
- test.libs << 'lib' << 'test'
6
- test.pattern = 'test/**/test_*.rb'
5
+ test.libs << "lib" << "test"
6
+ test.pattern = "test/**/test_*.rb"
7
7
  test.verbose = true
8
8
  end
9
9
 
10
- task :default => :test
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]
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  fname = ARGV.shift
3
- raise "Must specify filename." unless fname
3
+ fail "Must specify filename." unless fname
4
4
  outname = fname.gsub(/\.raw/, ".txt")
5
5
 
6
6
  bytes = File.read(fname).bytes.map { |x| "0x%02X" % x }
@@ -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 { |x| (0..7).map { |y| false } }
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 scene8)
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 base_color(x, y)
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
- 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
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
- { red: clamp(tmp[:red]),
50
- green: clamp(tmp[:green]),
51
- blue: clamp(tmp[:blue]) }
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.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 }])
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] = !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, action|
154
+ interaction.response_to(:mixer, :down) do |_interaction, _action|
117
155
  interaction.stop
118
156
  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 } })
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 Exception => e
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
- 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 }
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
- ((val+1)..7).each do |y|
25
- data << { x: x, y: y, red: 0x00, green: 0x00, blue: 0x00 }
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
- monitor = Thread.new do
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
- bar(interaction, 0, cpu_usage, 0x3F, 0x00, 0x00)
47
- bar(interaction, 1, disk_usage, 0x00, 0x3F, 0x00)
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, action|
63
+ interaction.response_to(:mixer, :down) do |_interaction, _action|
52
64
  puts "Shutting down"
53
65
  begin
54
66
  monitor.kill
@@ -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 = { input: true,
11
- output: true }
12
- .merge(opts || {})
13
-
10
+ opts = { input: true, output: true }.merge(opts || {})
14
11
  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]
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: midi_message[:timestamp],
51
- state: (velocity == 127) ? :down : :up,
52
- velocity: velocity,
53
- code: code,
54
- note: 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(&:to_hex).join(", 0x")}" }
53
+ logger.debug { "#{msg.length}: 0x#{msg.map { |b| '%02X' % b }.join(' ')}" }
66
54
  @output.write_sysex(msg)
67
55
  end
68
56
 
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
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
- logger.fatal "Error creating #{device_type}: #{e.inspect}"
85
- raise SurfaceMaster::DeviceBusyError.new(e)
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