voicemeeter_api_ruby 4.3.1 → 4.4.0

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
  SHA256:
3
- metadata.gz: dfec081c74f1dbdd1b315bd0c3aeaff0b80da1e520b995c584d087f2753cb56e
4
- data.tar.gz: 5e40c74ebc5d8aef7f153f278b3737387f43748604d073d35a308fa817a21627
3
+ metadata.gz: 4b5218c12fdf2ff6d99a68ce6ac4e896f8fc213848c17301bbaaf1897b71446b
4
+ data.tar.gz: 66f1a8e56633c592e9fd78b0188ac5061328e6324dd7b23eaee7077a33228e1f
5
5
  SHA512:
6
- metadata.gz: 189d225992622d19c21d7eb6a3f81e08db74b5b5556b7f8794bcc3fe4c0efe17393fbd6897bae1c869d1b4ebef9eafae1433827a1ecafecb393e0349260bcfb8
7
- data.tar.gz: 933d748eb4ca2bcae4081a35c4b3cc9258b08defa79fca143c0caced8ec3c00773c55acc086f71f36c9ede579515dca945dcd26de335ed825dedd8a2529294a3
6
+ metadata.gz: efab5bb8f17a001f170cbf5f257ff523c1647a42b77dd962f1e95329b38b76ccfd458f5b18f9f66342e544b1e9fd6e1a2c4037a1411309f7a616e3c2b26e2ffc
7
+ data.tar.gz: b3a47db4e3cfa1f82baa9dfda397be0a0b64fd9fa91d4c483c387c97e99004f47efd8dc06c6fac077feef6f3c92c35d484ee27fd03eb4415b35abd13f928605d
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
@@ -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:
@@ -3,6 +3,8 @@ require "observer"
3
3
  require_relative "runvm"
4
4
  require_relative "configs"
5
5
  require_relative "errors"
6
+ require_relative "event"
7
+ require_relative "midi"
6
8
 
7
9
  module Voicemeeter
8
10
  class Base
@@ -15,8 +17,15 @@ module Voicemeeter
15
17
  include Configs
16
18
  include RunVM
17
19
 
18
- attr_accessor :strip, :bus, :button, :vban, :command, :recorder, :device
19
- attr_accessor :strip_mode
20
+ attr_accessor :strip,
21
+ :bus,
22
+ :button,
23
+ :vban,
24
+ :command,
25
+ :recorder,
26
+ :device,
27
+ :midi
28
+ attr_accessor :strip_mode, :event
20
29
 
21
30
  attr_reader :kind, :p_in, :v_in, :p_out, :v_out, :retval, :cache
22
31
  attr_reader :running, :_strip_comp, :_bus_comp, :delay
@@ -28,31 +37,38 @@ module Voicemeeter
28
37
 
29
38
  def initialize(kind, **kwargs)
30
39
  @kind = kind
40
+ @sync = kwargs.delete(:sync) || SYNC
41
+ @ratelimit = kwargs.delete(:ratelimit) || RATELIMIT
31
42
  @p_in, @v_in = kind.layout[:strip].values
32
43
  @p_out, @v_out = kind.layout[:bus].values
33
- @cache = Hash.new
34
- @sync = kwargs[:sync] || SYNC
35
- @ratelimit = kwargs[:ratelimit] || RATELIMIT
36
44
  @running = false
37
45
  @strip_mode = 0
38
46
  @delay = DELAY
47
+ @cache = Hash.new
48
+ @midi = Midi.new
49
+ @event = Event.new(**kwargs)
39
50
  end
40
51
 
41
52
  def init_thread
42
- @running = true
53
+ puts "Listening for #{@event.get.join(", ")} events"
43
54
  @cache["strip_level"], @cache["bus_level"] = _get_levels
55
+ @running = true
44
56
  Thread.new do
45
57
  loop do
46
58
  Thread.stop if !@running
47
- if pdirty?
59
+ if @event.pdirty && pdirty?
48
60
  changed
49
61
  notify_observers("pdirty")
50
62
  end
51
- if mdirty?
63
+ if @event.mdirty && mdirty?
52
64
  changed
53
65
  notify_observers("mdirty")
54
66
  end
55
- if ldirty?
67
+ if @event.midi && get_midi_message
68
+ changed
69
+ notify_observers("midi")
70
+ end
71
+ if @event.ldirty && ldirty?
56
72
  changed
57
73
  @_strip_comp =
58
74
  @cache["strip_level"].map.with_index do |x, i|
@@ -190,11 +206,11 @@ module Voicemeeter
190
206
  raise VMRemoteErrors.new("expected in or out")
191
207
  end
192
208
  if direction == "in"
193
- val = @@cdll.call(:get_num_indevices)
209
+ res = @@cdll.call(:get_num_indevices)
194
210
  else
195
- val = @@cdll.call(:get_num_outdevices)
211
+ res = @@cdll.call(:get_num_outdevices)
196
212
  end
197
- val[0]
213
+ res[0]
198
214
  end
199
215
 
200
216
  def get_device_description(index, direction)
@@ -212,6 +228,22 @@ module Voicemeeter
212
228
  [c_name.read_string, c_type.read_long, c_hwid.read_string]
213
229
  end
214
230
 
231
+ def get_midi_message
232
+ c_msg = FFI::MemoryPointer.new(:string, 1024, true)
233
+ res = @@cdll.call(:get_midi_message, c_msg, 1024)
234
+ if res[0] > 0
235
+ vals = c_msg.read_string.bytes
236
+ vals.each_slice(3) do |ch, key, vel|
237
+ @midi.channel = ch if @midi.channel.nil? || @midi.channel != ch
238
+ @midi.current = key.to_i
239
+ @midi.set(key.to_i, vel.to_i)
240
+ end
241
+ elsif res[0] == -1 || res[0] == -2
242
+ raise CAPIErrors.new("VBVMR_GetMidiMessage returned #{msg[0]}")
243
+ end
244
+ res[0] > 0
245
+ end
246
+
215
247
  alias_method "set_multi", :set_parameter_multi
216
248
  alias_method "apply", :set_parameter_multi
217
249
  alias_method "get", :get_parameter
@@ -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
@@ -84,6 +84,11 @@ module Voicemeeter
84
84
  %i[long pointer pointer pointer],
85
85
  :long
86
86
 
87
+ attach_function :vm_get_midi_message,
88
+ :VBVMR_GetMidiMessage,
89
+ %i[pointer long],
90
+ :long
91
+
87
92
  @@cdll =
88
93
  lambda { |func, *args| retval = [send("vm_#{func}", *args), func] }
89
94
 
@@ -104,7 +109,7 @@ module Voicemeeter
104
109
  def retval=(values)
105
110
  " Writer validation for CAPI calls "
106
111
  retval, func = *values
107
- unless %i[get_num_indevices get_num_outdevices].include? func
112
+ unless %i[get_num_indevices get_num_outdevices get_midi_message].include? func
108
113
  raise CAPIErrors.new(retval, func) if retval&.nonzero?
109
114
  end
110
115
  @retval = retval
@@ -0,0 +1,31 @@
1
+ module Voicemeeter
2
+ class Event
3
+ attr_accessor :pdirty, :mdirty, :midi, :ldirty
4
+
5
+ def initialize(pdirty: true, mdirty: true, midi: true, ldirty: false)
6
+ @pdirty = pdirty
7
+ @mdirty = mdirty
8
+ @midi = midi
9
+ @ldirty = ldirty
10
+ end
11
+
12
+ def info(msg)
13
+ info_msg = ["#{msg} events", "Now listening for #{get.join(", ")} events"]
14
+ puts info_msg.join("\n")
15
+ end
16
+
17
+ def get
18
+ %w[pdirty mdirty midi ldirty].reject { |ev| !send("#{ev}") }
19
+ end
20
+
21
+ def add(event)
22
+ send("#{event}=", true)
23
+ info("#{event} added to")
24
+ end
25
+
26
+ def remove(event)
27
+ send("#{event}=", false)
28
+ info("#{event} removed from")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ module Voicemeeter
2
+ class Midi
3
+ attr_accessor :cache, :current, :channel
4
+
5
+ def initialize
6
+ @cache = Hash.new
7
+ end
8
+
9
+ def get(key)
10
+ @cache[key]
11
+ end
12
+
13
+ def set(key, vel)
14
+ @cache[key] = vel
15
+ end
16
+ end
17
+ end
@@ -24,8 +24,10 @@ module Voicemeeter
24
24
 
25
25
  module Outputs
26
26
  include Channel_Meta_Functions
27
- def initialize(remote, i = nil)
27
+
28
+ def initialize(*args)
28
29
  super
30
+ remote, *rem = args
29
31
  num_A, num_B = remote.kind.layout[:bus].values
30
32
  channels =
31
33
  (1..(num_A + num_B)).map do |i|
@@ -96,10 +96,10 @@ module Voicemeeter
96
96
  end
97
97
 
98
98
  def get_level(mode)
99
- if @remote.running
99
+ if @remote.running && @remote.event.ldirty
100
100
  vals = @remote.cache["strip_level"][@init, @offset]
101
101
  else
102
- vals = (@init...@offset).map { |i| get_level(mode, i) }
102
+ vals = (@init...@init + @offset).map { |i| @remote.get_level(mode, i) }
103
103
  end
104
104
  vals.map { |x| x > 0 ? (20 * Math.log(x, 10)).round(1) : -200.0 }
105
105
  end
@@ -7,11 +7,11 @@ module Voicemeeter
7
7
  end
8
8
 
9
9
  def minor
10
- 3
10
+ 4
11
11
  end
12
12
 
13
13
  def patch
14
- 1
14
+ 0
15
15
  end
16
16
 
17
17
  def to_a
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voicemeeter_api_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.1
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - onyx_online
@@ -93,10 +93,12 @@ files:
93
93
  - lib/voicemeeter/configs.rb
94
94
  - lib/voicemeeter/device.rb
95
95
  - lib/voicemeeter/errors.rb
96
+ - lib/voicemeeter/event.rb
96
97
  - lib/voicemeeter/inst.rb
97
98
  - lib/voicemeeter/iremote.rb
98
99
  - lib/voicemeeter/kinds.rb
99
100
  - lib/voicemeeter/meta.rb
101
+ - lib/voicemeeter/midi.rb
100
102
  - lib/voicemeeter/mixin.rb
101
103
  - lib/voicemeeter/recorder.rb
102
104
  - lib/voicemeeter/runvm.rb