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 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