voicemeeter_api_ruby 4.3.1 → 4.4.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
  SHA256:
3
- metadata.gz: dfec081c74f1dbdd1b315bd0c3aeaff0b80da1e520b995c584d087f2753cb56e
4
- data.tar.gz: 5e40c74ebc5d8aef7f153f278b3737387f43748604d073d35a308fa817a21627
3
+ metadata.gz: 2d5c6df9dba5f3f6bb249665cc26a6aeb8541c6e77712b4ff45f73452cfac8c4
4
+ data.tar.gz: a4c14273ab0cff0f004a68b4b9aa1816c087c775627bb2ab7872c8275b497859
5
5
  SHA512:
6
- metadata.gz: 189d225992622d19c21d7eb6a3f81e08db74b5b5556b7f8794bcc3fe4c0efe17393fbd6897bae1c869d1b4ebef9eafae1433827a1ecafecb393e0349260bcfb8
7
- data.tar.gz: 933d748eb4ca2bcae4081a35c4b3cc9258b08defa79fca143c0caced8ec3c00773c55acc086f71f36c9ede579515dca945dcd26de335ed825dedd8a2529294a3
6
+ metadata.gz: ad89a1cd012ccc74a42e7f8886c17a531e72b5af8180bba87e28da1df191dee74ac2b24d6531c11c0493e554652779a334241823d31af1e73718a76fc1bdbb11
7
+ data.tar.gz: d7f1d28cc751ed6d40d770f66691e2d012e71d9a29e72d236d2a7800fca3b19f1bbb536fa066e9b0b392a109e27df76be8658dca0605d41769afbe82670eb1a9
data/CHANGELOG.md CHANGED
@@ -9,7 +9,26 @@ Before any major/minor/patch is released all unit tests will be run to verify th
9
9
 
10
10
  ## [Unreleased] - These changes have not been added to RubyGems yet
11
11
 
12
- - [ ]
12
+ - [ ] Add midi example
13
+
14
+ ## [4.4.0] - 2022-08-06
15
+
16
+ ### Added
17
+
18
+ - midi device support added.
19
+ - pdirty, mdirty, midi, ldirty kwargs added to Remote class. Initialize which events to sub to.
20
+ - Event class added. Toggle events, get list of currently subscribed.
21
+ - Voicemeeter.remote added to README in Remote class section.
22
+ - observer example updated to reflect changes.
23
+
24
+ ### Changed
25
+
26
+ - channel out props for strip/recorder now mixed in.
27
+ - By default no longer listen for level updates (high volume). Should be enabled explicitly.
28
+
29
+ ### Fixed
30
+
31
+ - bug in strip/bus levels if making capi calls directly.
13
32
 
14
33
  ## [4.3.0] - 2022-07-29
15
34
 
data/README.md CHANGED
@@ -12,9 +12,9 @@ For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
12
12
 
13
13
  ## Tested against
14
14
 
15
- - Basic 1.0.8.2
16
- - Banana 2.0.6.2
17
- - Potato 3.0.2.2
15
+ - Basic 1.0.8.4
16
+ - Banana 2.0.6.4
17
+ - Potato 3.0.2.4
18
18
 
19
19
  ## Requirements
20
20
 
@@ -229,7 +229,7 @@ puts vm.bus[0].levels.all
229
229
 
230
230
  ### Strip | Bus
231
231
 
232
- The following methods are Available
232
+ The following methods are available
233
233
 
234
234
  - `fadeto(amount, time)`: float, int
235
235
  - `fadeby(amount, time)`: float, int
@@ -266,7 +266,7 @@ The following properties accept boolean values.
266
266
  - `A1 - A5`: boolean
267
267
  - `B1 - A3`: boolean
268
268
 
269
- The following methods are Available
269
+ The following methods are available
270
270
 
271
271
  - `play`
272
272
  - `stop`
@@ -356,6 +356,27 @@ example:
356
356
  vm.run { (0...vm.device.ins).each { |i| puts vm.device.input(i) } }
357
357
  ```
358
358
 
359
+ ### Midi
360
+
361
+ The following properties are available:
362
+
363
+ - `channel`: int, returns the midi channel
364
+ - `current`: int, returns the current (or most recently pressed) key
365
+
366
+ The following methods are available:
367
+
368
+ - `get(key)`: int, returns most recent velocity value for a key
369
+
370
+
371
+ example:
372
+
373
+ ```ruby
374
+ puts vm.midi.get(12)
375
+ ```
376
+
377
+ get may return nil if no value for requested key in midi cache
378
+
379
+
359
380
  ### Multiple parameters
360
381
 
361
382
  - `apply`
@@ -411,6 +432,58 @@ will load a config file at mydir/configs/banana/example.toml for Voicemeeter Ban
411
432
 
412
433
  ### Remote class
413
434
 
435
+ #### Voicemeeter.remote
436
+
437
+ You may pass the following optional keyword arguments:
438
+
439
+ - `sync`: boolean=false, force the getters to wait for dirty parameters to clear. For most cases leave this as false.
440
+ - `ratelimit`: float=0.033, how often to check for updates in ms.
441
+ - `pdirty`: boolean=true, parameter updates
442
+ - `mdirty`: boolean=true, macrobutton updates
443
+ - `midi`: boolean=true, midi updates
444
+ - `ldirty`: boolean=false, level updates
445
+
446
+
447
+ #### Event updates
448
+
449
+ To receive event updates you should do the following:
450
+
451
+ - register your app to receive updates using the `vm.add_observer(observer)` method, where observer is your app.
452
+ - define an `update(subject)` callback function in your app. The value of subject may be checked for the type of event.
453
+
454
+ See `examples/observer` for a demonstration.
455
+
456
+ Level updates are considered high volume, by default they are NOT listened for. However, polling them with strip levels and bus levels methods will still work.
457
+
458
+ So if you don't wish to receive level updates, or you prefer to handle them yourself simply leave ldirty as default (False).
459
+
460
+ Each of the update types may be enabled/disabled separately. Don't use a midi controller? You have the option to disable midi updates.
461
+
462
+ example:
463
+
464
+ ```ruby
465
+ require 'voicemeeter'
466
+ # Set updates to occur every 50ms
467
+ # Listen for level updates but disable midi updates
468
+ vm = Voicemeeter.remote('banana', ratelimit: 0.05, ldirty: true, midi: false)
469
+ vm.run { ... }
470
+ ```
471
+
472
+ #### `vm.event`
473
+
474
+ You may also add/remove event subscriptions as necessary with the Event class.
475
+
476
+ example:
477
+
478
+ ```ruby
479
+ vm.event.add("ldirty")
480
+
481
+ vm.event.remove("pdirty")
482
+
483
+ # get a list of currently subscribed
484
+ p vm.event.get
485
+ ```
486
+
414
487
  Access to lower level Getters and Setters are provided with these functions:
415
488
 
416
489
  - `vm.get(param, string=false)`: For getting the value of any parameter. Set string to true if getting a property value expected to return a string.
@@ -429,7 +502,7 @@ vm.set('Strip[4].Label', 'stripname')
429
502
  vm.set('Strip[0].Gain', -3.6)
430
503
  ```
431
504
 
432
- #### Voicemeeter::start
505
+ #### Voicemeeter.start
433
506
 
434
507
  Use this function to start Voicemeeter of a kind independently of Remote class.
435
508
  for example:
@@ -1,223 +1,211 @@
1
- require "observer"
2
-
3
- require_relative "runvm"
4
- require_relative "configs"
5
- require_relative "errors"
6
-
7
- module Voicemeeter
8
- class Base
9
- "
10
- Base class responsible for wrapping the C Remote API
11
-
12
- Mixin required modules
13
- "
14
- include Observable
15
- include Configs
16
- include RunVM
17
-
18
- attr_accessor :strip, :bus, :button, :vban, :command, :recorder, :device
19
- attr_accessor :strip_mode
20
-
21
- attr_reader :kind, :p_in, :v_in, :p_out, :v_out, :retval, :cache
22
- attr_reader :running, :_strip_comp, :_bus_comp, :delay
23
-
24
- DELAY = 0.001
25
- SYNC = false
26
- RATELIMIT = 0.033
27
- SIZE = 1
28
-
29
- def initialize(kind, **kwargs)
30
- @kind = kind
31
- @p_in, @v_in = kind.layout[:strip].values
32
- @p_out, @v_out = kind.layout[:bus].values
33
- @cache = Hash.new
34
- @sync = kwargs[:sync] || SYNC
35
- @ratelimit = kwargs[:ratelimit] || RATELIMIT
36
- @running = false
37
- @strip_mode = 0
38
- @delay = DELAY
39
- end
40
-
41
- def init_thread
42
- @running = true
43
- @cache["strip_level"], @cache["bus_level"] = _get_levels
44
- Thread.new do
45
- loop do
46
- Thread.stop if !@running
47
- if pdirty?
48
- changed
49
- notify_observers("pdirty")
50
- end
51
- if mdirty?
52
- changed
53
- notify_observers("mdirty")
54
- end
55
- if ldirty?
56
- changed
57
- @_strip_comp =
58
- @cache["strip_level"].map.with_index do |x, i|
59
- !(x == @strip_buf[i])
60
- end
61
- @_bus_comp =
62
- @cache["bus_level"].map.with_index { |x, i| !(x == @bus_buf[i]) }
63
- @cache["strip_level"] = @strip_buf
64
- @cache["bus_level"] = @bus_buf
65
- notify_observers("ldirty")
66
- end
67
- sleep(@ratelimit)
68
- end
69
- end
70
- end
71
-
72
- def end_thread
73
- @running = false
74
- end
75
-
76
- def login
77
- @@cdll.call(:login)
78
- clear_polling
79
- rescue CAPIErrors => error
80
- case
81
- when error.value == 1
82
- self.start(@kind.name)
83
- clear_polling
84
- when error.value < 0
85
- raise
86
- end
87
- end
88
-
89
- def logout
90
- clear_polling
91
- sleep(0.1)
92
- @@cdll.call(:logout)
93
- end
94
-
95
- def type
96
- c_type = FFI::MemoryPointer.new(:long, SIZE)
97
- @@cdll.call(:vmtype, c_type)
98
- types = { 1 => "basic", 2 => "banana", 3 => "potato" }
99
- types[c_type.read_long]
100
- end
101
-
102
- def version
103
- c_ver = FFI::MemoryPointer.new(:long, SIZE)
104
- @@cdll.call(:vmversion, c_ver)
105
- v1 = (c_ver.read_long & 0xFF000000) >> 24
106
- v2 = (c_ver.read_long & 0x00FF0000) >> 16
107
- v3 = (c_ver.read_long & 0x0000FF00) >> 8
108
- v4 = c_ver.read_long & 0x000000FF
109
- "#{v1}.#{v2}.#{v3}.#{v4}"
110
- end
111
-
112
- def get_parameter(name, is_string = false)
113
- self.polling("get_parameter", name: name) do
114
- if is_string
115
- c_get = FFI::MemoryPointer.new(:string, 512, true)
116
- @@cdll.call(:get_parameter_string, name, c_get)
117
- c_get.read_string
118
- else
119
- c_get = FFI::MemoryPointer.new(:float, SIZE)
120
- @@cdll.call(:get_parameter_float, name, c_get)
121
- c_get.read_float.round(1)
122
- end
123
- end
124
- end
125
-
126
- def set_parameter(name, value)
127
- if value.is_a? String
128
- @@cdll.call(:set_parameter_string, name, value)
129
- else
130
- @@cdll.call(:set_parameter_float, name, value.to_f)
131
- end
132
- @cache.store(name, value)
133
- end
134
-
135
- def get_buttonstatus(id, mode)
136
- self.polling("get_buttonstatus", id: id, mode: mode) do
137
- c_get = FFI::MemoryPointer.new(:float, SIZE)
138
- @@cdll.call(:get_buttonstatus, id, c_get, mode)
139
- c_get.read_float.to_i
140
- end
141
- end
142
-
143
- def set_buttonstatus(id, state, mode)
144
- @@cdll.call(:set_buttonstatus, id, state, mode)
145
- @cache.store("mb_#{id}_#{mode}", state)
146
- end
147
-
148
- def set_parameter_multi(param_hash)
149
- param_hash.each do |(key, val)|
150
- prop, m2, m3, *rem = key.to_s.split("_")
151
- if m2.to_i.to_s == m2
152
- m2 = m2.to_i
153
- elsif m3.to_i.to_s == m3
154
- m3 = m3.to_i
155
- end
156
-
157
- case prop
158
- when "strip"
159
- self.strip[m2].set_multi(val)
160
- when "bus"
161
- self.bus[m2].set_multi(val)
162
- when "button", "mb"
163
- self.button[m2].set_multi(val)
164
- when "vban"
165
- if %w[instream in].include? m2
166
- self.vban.instream[m3].set_multi(val)
167
- elsif %w[outstream out].include? m2
168
- self.vban.outstream[m3].set_multi(val)
169
- end
170
- end
171
- sleep(DELAY)
172
- end
173
- end
174
-
175
- def get_level(type, index)
176
- c_get = FFI::MemoryPointer.new(:float, SIZE)
177
- @@cdll.call(:get_level, type, index, c_get)
178
- c_get.read_float
179
- end
180
-
181
- def _get_levels
182
- [
183
- (0...(2 * @p_in + 8 * @v_in)).map { |i| get_level(@strip_mode, i) },
184
- (0...(8 * (@p_out + @v_out))).map { |i| get_level(3, i) }
185
- ]
186
- end
187
-
188
- def get_num_devices(direction)
189
- unless %w[in out].include? direction
190
- raise VMRemoteErrors.new("expected in or out")
191
- end
192
- if direction == "in"
193
- val = @@cdll.call(:get_num_indevices)
194
- else
195
- val = @@cdll.call(:get_num_outdevices)
196
- end
197
- val[0]
198
- end
199
-
200
- def get_device_description(index, direction)
201
- unless %w[in out].include? direction
202
- raise VMRemoteErrors.new("expected in or out")
203
- end
204
- c_type = FFI::MemoryPointer.new(:long, SIZE)
205
- c_name = FFI::MemoryPointer.new(:string, 256, true)
206
- c_hwid = FFI::MemoryPointer.new(:string, 256, true)
207
- if direction == "in"
208
- @@cdll.call(:get_desc_indevices, index, c_type, c_name, c_hwid)
209
- else
210
- @@cdll.call(:get_desc_outdevices, index, c_type, c_name, c_hwid)
211
- end
212
- [c_name.read_string, c_type.read_long, c_hwid.read_string]
213
- end
214
-
215
- alias_method "set_multi", :set_parameter_multi
216
- alias_method "apply", :set_parameter_multi
217
- alias_method "get", :get_parameter
218
- alias_method "set", :set_parameter
219
- alias_method "pdirty", :pdirty?
220
- alias_method "mdirty", :mdirty?
221
- alias_method "ldirty", :ldirty?
222
- end
223
- end
1
+ require "observer"
2
+
3
+ require_relative "worker"
4
+ require_relative "runvm"
5
+ require_relative "configs"
6
+ require_relative "errors"
7
+ require_relative "event"
8
+ require_relative "midi"
9
+
10
+ module Voicemeeter
11
+ class Base
12
+ "
13
+ Base class responsible for wrapping the C Remote API
14
+
15
+ Mixin required modules
16
+ "
17
+ include Observable
18
+ include Configs
19
+ include RunVM
20
+ include Worker
21
+
22
+ attr_accessor :strip, :bus, :button, :vban, :command, :recorder, :device
23
+ attr_accessor :midi
24
+ attr_accessor :strip_mode, :event
25
+
26
+ attr_reader :kind, :p_in, :v_in, :p_out, :v_out, :retval, :cache
27
+ attr_reader :running, :_strip_comp, :_bus_comp, :delay
28
+
29
+ DELAY = 0.001
30
+ SYNC = false
31
+ RATELIMIT = 0.033
32
+ SIZE = 1
33
+
34
+ def initialize(kind, **kwargs)
35
+ @kind = kind
36
+ @sync = kwargs.delete(:sync) || SYNC
37
+ @ratelimit = kwargs.delete(:ratelimit) || RATELIMIT
38
+ @p_in, @v_in = kind.layout[:strip].values
39
+ @p_out, @v_out = kind.layout[:bus].values
40
+ @running = false
41
+ @strip_mode = 0
42
+ @delay = DELAY
43
+ @cache = Hash.new
44
+ @midi = Midi.new
45
+ @event = Event.new(**kwargs)
46
+ end
47
+
48
+ def login
49
+ @@cdll.call(:login)
50
+ clear_polling
51
+ rescue CAPIErrors => error
52
+ case
53
+ when error.value == 1
54
+ self.start(@kind.name)
55
+ clear_polling
56
+ when error.value < 0
57
+ raise
58
+ end
59
+ end
60
+
61
+ def logout
62
+ clear_polling
63
+ sleep(0.1)
64
+ @@cdll.call(:logout)
65
+ end
66
+
67
+ def type
68
+ c_type = FFI::MemoryPointer.new(:long, SIZE)
69
+ @@cdll.call(:vmtype, c_type)
70
+ types = { 1 => "basic", 2 => "banana", 3 => "potato" }
71
+ types[c_type.read_long]
72
+ end
73
+
74
+ def version
75
+ c_ver = FFI::MemoryPointer.new(:long, SIZE)
76
+ @@cdll.call(:vmversion, c_ver)
77
+ v1 = (c_ver.read_long & 0xFF000000) >> 24
78
+ v2 = (c_ver.read_long & 0x00FF0000) >> 16
79
+ v3 = (c_ver.read_long & 0x0000FF00) >> 8
80
+ v4 = c_ver.read_long & 0x000000FF
81
+ "#{v1}.#{v2}.#{v3}.#{v4}"
82
+ end
83
+
84
+ def get_parameter(name, is_string = false)
85
+ self.polling("get_parameter", name: name) do
86
+ if is_string
87
+ c_get = FFI::MemoryPointer.new(:string, 512, true)
88
+ @@cdll.call(:get_parameter_string, name, c_get)
89
+ c_get.read_string
90
+ else
91
+ c_get = FFI::MemoryPointer.new(:float, SIZE)
92
+ @@cdll.call(:get_parameter_float, name, c_get)
93
+ c_get.read_float.round(1)
94
+ end
95
+ end
96
+ end
97
+
98
+ def set_parameter(name, value)
99
+ if value.is_a? String
100
+ @@cdll.call(:set_parameter_string, name, value)
101
+ else
102
+ @@cdll.call(:set_parameter_float, name, value.to_f)
103
+ end
104
+ @cache.store(name, value)
105
+ end
106
+
107
+ def get_buttonstatus(id, mode)
108
+ self.polling("get_buttonstatus", id: id, mode: mode) do
109
+ c_get = FFI::MemoryPointer.new(:float, SIZE)
110
+ @@cdll.call(:get_buttonstatus, id, c_get, mode)
111
+ c_get.read_float.to_i
112
+ end
113
+ end
114
+
115
+ def set_buttonstatus(id, state, mode)
116
+ @@cdll.call(:set_buttonstatus, id, state, mode)
117
+ @cache.store("mb_#{id}_#{mode}", state)
118
+ end
119
+
120
+ def set_parameter_multi(param_hash)
121
+ param_hash.each do |(key, val)|
122
+ prop, m2, m3, *rem = key.to_s.split("_")
123
+ if m2.to_i.to_s == m2
124
+ m2 = m2.to_i
125
+ elsif m3.to_i.to_s == m3
126
+ m3 = m3.to_i
127
+ end
128
+
129
+ case prop
130
+ when "strip"
131
+ self.strip[m2].set_multi(val)
132
+ when "bus"
133
+ self.bus[m2].set_multi(val)
134
+ when "button", "mb"
135
+ self.button[m2].set_multi(val)
136
+ when "vban"
137
+ if %w[instream in].include? m2
138
+ self.vban.instream[m3].set_multi(val)
139
+ elsif %w[outstream out].include? m2
140
+ self.vban.outstream[m3].set_multi(val)
141
+ end
142
+ end
143
+ sleep(DELAY)
144
+ end
145
+ end
146
+
147
+ def get_level(type, index)
148
+ c_get = FFI::MemoryPointer.new(:float, SIZE)
149
+ @@cdll.call(:get_level, type, index, c_get)
150
+ c_get.read_float
151
+ end
152
+
153
+ def _get_levels
154
+ [
155
+ (0...(2 * @p_in + 8 * @v_in)).map { |i| get_level(@strip_mode, i) },
156
+ (0...(8 * (@p_out + @v_out))).map { |i| get_level(3, i) }
157
+ ]
158
+ end
159
+
160
+ def get_num_devices(direction)
161
+ unless %w[in out].include? direction
162
+ raise VMRemoteErrors.new("expected in or out")
163
+ end
164
+ if direction == "in"
165
+ res = @@cdll.call(:get_num_indevices)
166
+ else
167
+ res = @@cdll.call(:get_num_outdevices)
168
+ end
169
+ res[0]
170
+ end
171
+
172
+ def get_device_description(index, direction)
173
+ unless %w[in out].include? direction
174
+ raise VMRemoteErrors.new("expected in or out")
175
+ end
176
+ c_type = FFI::MemoryPointer.new(:long, SIZE)
177
+ c_name = FFI::MemoryPointer.new(:string, 256, true)
178
+ c_hwid = FFI::MemoryPointer.new(:string, 256, true)
179
+ if direction == "in"
180
+ @@cdll.call(:get_desc_indevices, index, c_type, c_name, c_hwid)
181
+ else
182
+ @@cdll.call(:get_desc_outdevices, index, c_type, c_name, c_hwid)
183
+ end
184
+ [c_name.read_string, c_type.read_long, c_hwid.read_string]
185
+ end
186
+
187
+ def get_midi_message
188
+ c_msg = FFI::MemoryPointer.new(:string, 1024, true)
189
+ res = @@cdll.call(:get_midi_message, c_msg, 1024)
190
+ if res[0] > 0
191
+ vals = c_msg.read_string.bytes
192
+ vals.each_slice(3) do |ch, key, vel|
193
+ @midi.channel = ch if @midi.channel.nil? || @midi.channel != ch
194
+ @midi.current = key.to_i
195
+ @midi.set(key.to_i, vel.to_i)
196
+ end
197
+ elsif res[0] == -1 || res[0] == -2
198
+ raise CAPIErrors.new("VBVMR_GetMidiMessage returned #{msg[0]}")
199
+ end
200
+ res[0] > 0
201
+ end
202
+
203
+ alias_method "set_multi", :set_parameter_multi
204
+ alias_method "apply", :set_parameter_multi
205
+ alias_method "get", :get_parameter
206
+ alias_method "set", :set_parameter
207
+ alias_method "pdirty", :pdirty?
208
+ alias_method "mdirty", :mdirty?
209
+ alias_method "ldirty", :ldirty?
210
+ end
211
+ end
@@ -82,10 +82,10 @@ module Voicemeeter
82
82
  end
83
83
 
84
84
  def getter(mode)
85
- if @remote.running
85
+ if @remote.running && @remote.event.ldirty
86
86
  vals = @remote.cache["bus_level"][@init, @offset]
87
87
  else
88
- vals = (@init...@offset).map { |i| @remote.get_level(mode, i) }
88
+ vals = (@init...@init + @offset).map { |i| @remote.get_level(mode, i) }
89
89
  end
90
90
  vals.map { |x| x > 0 ? (20 * Math.log(x, 10)).round(1) : -200.0 }
91
91
  end
@@ -1,25 +1,25 @@
1
- require_relative "iremote"
2
- require_relative "meta"
3
-
4
- module Voicemeeter
5
- class MacroButton < IRemote
6
- include MacroButton_Meta_Functions
7
-
8
- def self.make(remote, num_buttons)
9
- (0...num_buttons).map { |i| MacroButton.new(remote, i) }
10
- end
11
-
12
- def initialize(remote, i)
13
- super
14
- self.make_accessor_macrobutton :state, :stateonly, :trigger
15
- end
16
-
17
- def getter(mode)
18
- @remote.get_buttonstatus(@index, mode)
19
- end
20
-
21
- def setter(set, mode)
22
- @remote.set_buttonstatus(@index, set, mode)
23
- end
24
- end
25
- end
1
+ require_relative "iremote"
2
+ require_relative "meta"
3
+
4
+ module Voicemeeter
5
+ class MacroButton < IRemote
6
+ include MacroButton_Meta_Functions
7
+
8
+ def self.make(remote, num_buttons)
9
+ (0...num_buttons).map { |i| MacroButton.new(remote, i) }
10
+ end
11
+
12
+ def initialize(remote, i)
13
+ super
14
+ self.make_accessor_macrobutton :state, :stateonly, :trigger
15
+ end
16
+
17
+ def getter(mode)
18
+ @remote.get_buttonstatus(@index, mode)
19
+ end
20
+
21
+ def setter(set, mode)
22
+ @remote.set_buttonstatus(@index, set, mode)
23
+ end
24
+ end
25
+ end