voicemeeter_api_ruby 2.0.4 → 4.1.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.
data/lib/base.rb CHANGED
@@ -1,171 +1,218 @@
1
- require 'toml'
2
- require 'ffi'
3
- require_relative 'inst'
1
+ require 'observer'
4
2
 
5
- include InstallationFunctions
3
+ require_relative 'runvm'
4
+ require_relative 'configs'
5
+ require_relative 'errors'
6
6
 
7
+ class Base
8
+ '
9
+ Base class responsible for wrapping the C Remote API
7
10
 
8
- module Base
9
- """
10
- Perform low level tasks.
11
- """
12
- extend FFI::Library
11
+ Mixin required modules
12
+ '
13
+ include Observable
14
+ include Configs
15
+ include RunVM
13
16
 
14
- $kinds_all = ["basic", "banana", "potato"]
17
+ attr_accessor :strip, :bus, :button, :vban, :command, :recorder, :device
18
+ attr_accessor :strip_mode
15
19
 
16
- begin
17
- OS_BITS = get_arch
18
- VM_PATH = get_vmpath(OS_BITS)
19
- DLL_NAME = "VoicemeeterRemote#{OS_BITS == 64 ? "64" : ""}.dll"
20
+ attr_reader :kind, :p_in, :v_in, :p_out, :v_out, :retval, :cache
21
+ attr_reader :running, :_strip_comp, :_bus_comp
20
22
 
21
- self.vmr_dll = VM_PATH.join(DLL_NAME)
22
- rescue InstallErrors => error
23
- puts "ERROR: #{error.message}"
24
- raise
23
+ DELAY = 0.001
24
+ SYNC = false
25
+ RATELIMIT = 0.033
26
+ SIZE = 1
27
+
28
+ def initialize(kind, **kwargs)
29
+ @kind = kind
30
+ @p_in, @v_in = kind.layout[:strip].values
31
+ @p_out, @v_out = kind.layout[:bus].values
32
+ @cache = Hash.new
33
+ @sync = kwargs[:sync] || SYNC
34
+ @ratelimit = kwargs[:ratelimit] || RATELIMIT
35
+ @running = false
36
+ @strip_mode = 0
25
37
  end
26
38
 
27
- ffi_lib @vmr_dll
28
- ffi_convention :stdcall
29
-
30
- attach_function :vmr_login, :VBVMR_Login, [], :long
31
- attach_function :vmr_logout, :VBVMR_Logout, [], :long
32
- attach_function :vmr_runvm, :VBVMR_RunVoicemeeter, [:long], :long
33
- attach_function :vmr_vmtype, :VBVMR_GetVoicemeeterType, [:pointer], :long
39
+ def init_thread
40
+ @running = true
41
+ @cache['strip_level'], @cache['bus_level'] = _get_levels
42
+ Thread.new do
43
+ loop do
44
+ Thread.stop if !@running
45
+ if pdirty?
46
+ changed
47
+ notify_observers('pdirty')
48
+ elsif mdirty?
49
+ changed
50
+ notify_observers('mdirty')
51
+ elsif ldirty?
52
+ changed
53
+ @_strip_comp =
54
+ @cache['strip_level'].map.with_index do |x, i|
55
+ !(x == @strip_buf[i])
56
+ end
57
+ @_bus_comp =
58
+ @cache['bus_level'].map.with_index do |x, i|
59
+ !(x == @bus_buf[i])
60
+ end
61
+ @cache['strip_level'] = @strip_buf
62
+ @cache['bus_level'] = @bus_buf
63
+ notify_observers('ldirty')
64
+ end
65
+ sleep(@ratelimit)
66
+ end
67
+ end
68
+ end
34
69
 
35
- attach_function :vmr_mdirty, :VBVMR_MacroButton_IsDirty, [], :long
36
- attach_function :vmr_macro_setstatus, :VBVMR_MacroButton_SetStatus, \
37
- [:long, :float, :long], :long
38
- attach_function :vmr_macro_getstatus, :VBVMR_MacroButton_GetStatus, \
39
- [:long, :pointer, :long], :long
70
+ def end_thread
71
+ @running = false
72
+ end
40
73
 
41
- attach_function :vmr_pdirty, :VBVMR_IsParametersDirty, [], :long
42
- attach_function :vmr_set_parameter_float, :VBVMR_SetParameterFloat, \
43
- [:string, :float], :long
44
- attach_function :vmr_get_parameter_float, :VBVMR_GetParameterFloat, \
45
- [:string, :pointer], :long
74
+ def login
75
+ @@cdll.call(:login)
76
+ clear_polling
77
+ rescue CAPIErrors => error
78
+ case
79
+ when error.value == 1
80
+ self.start(@kind.name)
81
+ clear_polling
82
+ when error.value < 0
83
+ raise
84
+ end
85
+ end
46
86
 
47
- attach_function :vmr_set_parameter_string, :VBVMR_SetParameterStringA, \
48
- [:string, :string], :long
49
- attach_function :vmr_get_parameter_string, :VBVMR_GetParameterStringA, \
50
- [:string, :pointer], :long
87
+ def logout
88
+ clear_polling
89
+ sleep(0.1)
90
+ @@cdll.call(:logout)
91
+ end
51
92
 
52
- attach_function :vmr_set_parameter_multi, :VBVMR_SetParameters, \
53
- [:string], :long
93
+ def type
94
+ c_type = FFI::MemoryPointer.new(:long, SIZE)
95
+ @@cdll.call(:vmtype, c_type)
96
+ types = { 1 => 'basic', 2 => 'banana', 3 => 'potato' }
97
+ types[c_type.read_long]
98
+ end
54
99
 
55
- DELAY = 0.001
56
- MAX_POLLS = 8
100
+ def version
101
+ c_ver = FFI::MemoryPointer.new(:long, SIZE)
102
+ @@cdll.call(:vmversion, c_ver)
103
+ v1 = (c_ver.read_long & 0xFF000000) >> 24
104
+ v2 = (c_ver.read_long & 0x00FF0000) >> 16
105
+ v3 = (c_ver.read_long & 0x0000FF00) >> 8
106
+ v4 = c_ver.read_long & 0x000000FF
107
+ "#{v1}.#{v2}.#{v3}.#{v4}"
108
+ end
57
109
 
58
- def pdirty?
59
- return vmr_pdirty&.nonzero?
110
+ def get_parameter(name, is_string = false)
111
+ self.polling('get_parameter', name: name) do
112
+ if is_string
113
+ c_get = FFI::MemoryPointer.new(:string, 512, true)
114
+ @@cdll.call(:get_parameter_string, name, c_get)
115
+ c_get.read_string
116
+ else
117
+ c_get = FFI::MemoryPointer.new(:float, SIZE)
118
+ @@cdll.call(:get_parameter_float, name, c_get)
119
+ c_get.read_float.round(1)
120
+ end
121
+ end
60
122
  end
61
123
 
62
- def mdirty?
63
- return vmr_mdirty&.nonzero?
124
+ def set_parameter(name, value)
125
+ if value.is_a? String
126
+ @@cdll.call(:set_parameter_string, name, value)
127
+ else
128
+ @@cdll.call(:set_parameter_float, name, value.to_f)
129
+ end
130
+ @cache.store(name, value)
64
131
  end
65
132
 
66
- private
67
- def clear_polling
68
- while self.pdirty? || self.mdirty?
133
+ def get_buttonstatus(id, mode)
134
+ self.polling('get_buttonstatus', id: id, mode: mode) do
135
+ c_get = FFI::MemoryPointer.new(:float, SIZE)
136
+ @@cdll.call(:get_buttonstatus, id, c_get, mode)
137
+ c_get.read_float.to_i
69
138
  end
70
139
  end
71
140
 
72
- def polling(func, **kwargs)
73
- params = {
74
- "get_parameter" => kwargs[:name],
75
- "macro_getstatus" => "mb_#{kwargs[:id]}_#{kwargs[:mode]}"
76
- }
77
- @max_polls.times do |i|
78
- if @cache.key? params[func]
79
- if func.include?('param') && self.pdirty? ||
80
- func.include?('macro') && self.mdirty?
81
- return @cache.delete(params[func])[0]
141
+ def set_buttonstatus(id, state, mode)
142
+ @@cdll.call(:set_buttonstatus, id, state, mode)
143
+ @cache.store("mb_#{id}_#{mode}", state)
144
+ end
145
+
146
+ def set_parameter_multi(param_hash)
147
+ param_hash.each do |(key, val)|
148
+ prop, m2, m3, *rem = key.to_s.split('_')
149
+ if m2.to_i.to_s == m2
150
+ m2 = m2.to_i
151
+ elsif m3.to_i.to_s == m3
152
+ m3 = m3.to_i
153
+ end
154
+
155
+ case prop
156
+ when 'strip'
157
+ self.strip[m2].set_multi(val)
158
+ when 'bus'
159
+ self.bus[m2].set_multi(val)
160
+ when 'button', 'mb'
161
+ self.button[m2].set_multi(val)
162
+ when 'vban'
163
+ if %w[instream in].include? m2
164
+ self.vban.instream[m3].set_multi(val)
165
+ elsif %w[outstream out].include? m2
166
+ self.vban.outstream[m3].set_multi(val)
82
167
  end
83
- sleep(DELAY)
84
- break if @cache[params[func]][1] == false && i == 1
85
168
  end
169
+ sleep(DELAY)
86
170
  end
87
-
88
- val = yield
89
- @cache.store(params[func], [val, false])
90
- val
91
171
  end
92
172
 
93
- def retval=(values)
94
- """ Writer validation for CAPI calls """
95
- retval, func = *values
96
- raise CAPIErrors.new(retval, func) if retval&.nonzero?
97
- @retval = retval
173
+ def get_level(type, index)
174
+ c_get = FFI::MemoryPointer.new(:float, SIZE)
175
+ @@cdll.call(:get_level, type, index, c_get)
176
+ c_get.read_float
98
177
  end
99
178
 
100
- def run_as(func, *args)
101
- val = send('vmr_' + func, *args)
102
- self.retval = [val, func]
103
- sleep(DELAY) if func.include?('set') && @wait
179
+ def _get_levels
180
+ s = (0...(2 * @p_in + 8 * @v_in)).map { |i| get_level(0, i) }
181
+ b = (0...(8 * (@p_out + @v_out))).map { |i| get_level(3, i) }
182
+ [s, b]
104
183
  end
105
- end
106
184
 
107
- module Define_Version
108
- """
109
- Defines the console layout for a specific kind of Voicemeeter.
110
- """
111
- include Base
112
- private
113
- def define_version(kind)
114
- case kind
115
- when "basic"
116
- @properties = {
117
- :name => kind,
118
- :exe => "voicemeeter.exe",
119
- }
120
- @layout = {
121
- :strip => {:p_in => 2, :v_in=> 1},
122
- :bus => {:p_out => 1, :v_out=> 1},
123
- :vban => {:instream => 4, :outstream => 4},
124
- :mb => 70,
125
- }
126
- when "banana"
127
- @properties = {
128
- :name => kind,
129
- :exe => "voicemeeterpro.exe",
130
- }
131
- @layout = {
132
- :strip => {:p_in => 3, :v_in=> 2},
133
- :bus => {:p_out => 3, :v_out=> 2},
134
- :vban => {:instream => 8, :outstream => 8},
135
- :mb => 70,
136
- }
137
- when "potato"
138
- @properties = {
139
- :name => kind,
140
- :exe => "voicemeeter8#{OS_BITS == 64 ? "x64" : ""}.exe",
141
- }
142
- @layout = {
143
- :strip => {:p_in => 5, :v_in=> 3},
144
- :bus => {:p_out => 5, :v_out=> 3},
145
- :vban => {:instream => 8, :outstream => 8},
146
- :mb => 70,
147
- }
185
+ def get_num_devices(direction)
186
+ unless %w[in out].include? direction
187
+ raise VMRemoteErrors.new('expected in or out')
188
+ end
189
+ if direction == 'in'
190
+ val = @@cdll.call(:get_num_indevices)
191
+ else
192
+ val = @@cdll.call(:get_num_outdevices)
148
193
  end
194
+ val[0]
149
195
  end
150
- end
151
-
152
196
 
153
- module Profiles
154
- include Define_Version
155
- private
156
- def get_profiles
157
- filepath = File.join(File.dirname(__dir__), "/profiles/#{@properties[:name]}/*.toml")
158
-
159
- Dir.glob(filepath).to_h do |toml_file|
160
- filename = File.basename(toml_file, ".toml")
161
- puts "loading profile #{@properties[:name]}/#{filename}"
162
- [filename, TOML::Parser.new(File.read(toml_file)).parsed]
197
+ def get_device_description(index, direction)
198
+ unless %w[in out].include? direction
199
+ raise VMRemoteErrors.new('expected in or out')
200
+ end
201
+ c_type = FFI::MemoryPointer.new(:long, SIZE)
202
+ c_name = FFI::MemoryPointer.new(:string, 256, true)
203
+ c_hwid = FFI::MemoryPointer.new(:string, 256, true)
204
+ if direction == 'in'
205
+ @@cdll.call(:get_desc_indevices, index, c_type, c_name, c_hwid)
206
+ else
207
+ @@cdll.call(:get_desc_outdevices, index, c_type, c_name, c_hwid)
163
208
  end
209
+ [c_name.read_string, c_type.read_long, c_hwid.read_string]
164
210
  end
165
- public
166
- def set_profile(value)
167
- raise VMRemoteErrors.new("No profile with name #{value} was loaded") unless @profiles.key? value
168
- self.send("set_multi", @profiles[value])
169
- sleep(DELAY)
170
- end
211
+
212
+ alias_method 'set_multi', :set_parameter_multi
213
+ alias_method 'get', :get_parameter
214
+ alias_method 'set', :set_parameter
215
+ alias_method 'pdirty', :pdirty?
216
+ alias_method 'mdirty', :mdirty?
217
+ alias_method 'ldirty', :ldirty?
171
218
  end
data/lib/bus.rb CHANGED
@@ -1,33 +1,97 @@
1
- require_relative 'channel'
1
+ require_relative 'iremote'
2
+ require_relative 'mixin'
2
3
 
4
+ class Bus < IRemote
5
+ '
6
+ Concrete Bus class
7
+ '
8
+ include Channel_Meta_Functions
9
+ include Fades
10
+
11
+ attr_accessor :mode, :levels
3
12
 
4
- class Bus < IChannel
5
- """
6
- Concrete class for Bus objects
7
- """
8
13
  def self.make(remote, layout_bus)
9
- "
14
+ '
10
15
  Factory function for Bus classes.
11
- "
12
- p_out, v_out = layout_bus.map { |k, v| v }
16
+ '
17
+ p_out, v_out = layout_bus.values
13
18
  (0...(p_out + v_out)).map do |i|
14
- i < p_out ? \
15
- PhysicalBus.new(remote, i) : \
16
- VirtualBus.new(remote, i)
19
+ i < p_out ? PhysicalBus.new(remote, i) : VirtualBus.new(remote, i)
17
20
  end
18
21
  end
19
22
 
20
23
  def initialize(remote, i)
21
24
  super
22
- self.make_accessor_bool :mute, :mono, :eq
25
+ self.make_accessor_bool :mute, :mono, :eq, :sel
23
26
  self.make_accessor_float :gain
24
27
  self.make_accessor_string :label
28
+
29
+ @mode = BusModes.new(remote, i)
30
+ @levels = BusLevels.new(remote, i)
31
+ end
32
+
33
+ def identifier
34
+ "bus[#{@index}]"
25
35
  end
36
+ end
26
37
 
27
- def cmd
28
- return "Bus[#{@index}]"
38
+ class PhysicalBus < Bus
39
+ def initialize(remote, i)
40
+ super
41
+ self.make_reader_only :device, :sr
29
42
  end
30
43
  end
31
44
 
32
- class PhysicalBus < Bus; end
33
- class VirtualBus < Bus; end
45
+ class VirtualBus < Bus
46
+ end
47
+
48
+ class BusModes < IRemote
49
+ include Channel_Meta_Functions
50
+
51
+ def initialize(remote, i)
52
+ super
53
+ self.make_bus_modes :normal,
54
+ :amix,
55
+ :bmix,
56
+ :repeat,
57
+ :composite,
58
+ :tvmix,
59
+ :upmix21,
60
+ :upmix41,
61
+ :upmix61,
62
+ :centeronly,
63
+ :lfeonly,
64
+ :rearonly
65
+ end
66
+
67
+ def identifier
68
+ "bus[#{@index}].mode"
69
+ end
70
+ end
71
+
72
+ class BusLevels < IRemote
73
+ def initialize(remote, i)
74
+ super
75
+ @init = i * 8
76
+ @offset = 8
77
+ end
78
+
79
+ def identifier
80
+ "bus[#{@index}]"
81
+ end
82
+
83
+ def getter(mode)
84
+ if @remote.running
85
+ vals = @remote.cache['bus_level'][@init, @offset]
86
+ else
87
+ vals = (@init...@offset).map { |i| @remote.get_level(mode, i) }
88
+ end
89
+ vals.map { |x| x > 0 ? (20 * Math.log(x, 10)).round(1) : -200.0 }
90
+ end
91
+
92
+ def all
93
+ getter(3)
94
+ end
95
+
96
+ def isdirty?() = @remote._bus_comp[@init, @offset].any?
97
+ end
data/lib/button.rb CHANGED
@@ -1,42 +1,23 @@
1
+ require_relative 'iremote'
1
2
  require_relative 'meta'
2
3
 
3
-
4
- class IMacroButton
4
+ class MacroButton < IRemote
5
5
  include MacroButton_Meta_Functions
6
6
 
7
- attr_accessor :remote, :index
7
+ def self.make(remote, num_buttons)
8
+ (0...num_buttons).map { |i| MacroButton.new(remote, i) }
9
+ end
8
10
 
9
- def initialize(remote, index)
10
- self.remote = remote
11
- self.index = index
11
+ def initialize(remote, i)
12
+ super
13
+ self.make_accessor_macrobutton :state, :stateonly, :trigger
12
14
  end
13
15
 
14
16
  def getter(mode)
15
- return @remote.macro_getstatus(@index, mode)
17
+ @remote.get_buttonstatus(@index, mode)
16
18
  end
17
19
 
18
20
  def setter(set, mode)
19
- @remote.macro_setstatus(@index, set, mode)
20
- end
21
-
22
- def set_multi(param_hash)
23
- param_hash.each do |(key,val)|
24
- self.send("#{key}=", val)
25
- end
26
- sleep(remote.delay)
27
- end
28
- end
29
-
30
-
31
- class MacroButton < IMacroButton
32
- def self.make(remote, num_buttons)
33
- (0...num_buttons).map do |i|
34
- MacroButton.new(remote, i)
35
- end
36
- end
37
-
38
- def initialize(remote, i)
39
- super
40
- self.make_accessor_macrobutton :state, :stateonly, :trigger
21
+ @remote.set_buttonstatus(@index, set, mode)
41
22
  end
42
23
  end
data/lib/cbindings.rb ADDED
@@ -0,0 +1,128 @@
1
+ require 'ffi'
2
+ require_relative 'inst'
3
+
4
+ include InstallationFunctions
5
+
6
+ module CBindings
7
+ '
8
+ Creates Ruby bindings to the C DLL
9
+
10
+ Performs other low level tasks
11
+ '
12
+ extend FFI::Library
13
+
14
+ private
15
+
16
+ begin
17
+ OS_BITS = FFI::Platform::CPU.downcase == 'x64' ? 64 : 32
18
+ VM_PATH = get_vmpath(OS_BITS)
19
+ DLL_NAME = "VoicemeeterRemote#{OS_BITS == 64 ? '64' : ''}.dll"
20
+
21
+ self.vm_dll = VM_PATH.join(DLL_NAME)
22
+ rescue InstallErrors => error
23
+ puts "ERROR: #{error.message}"
24
+ raise
25
+ end
26
+
27
+ ffi_lib @vm_dll
28
+ ffi_convention :stdcall
29
+
30
+ attach_function :vm_login, :VBVMR_Login, [], :long
31
+ attach_function :vm_logout, :VBVMR_Logout, [], :long
32
+ attach_function :vm_runvm, :VBVMR_RunVoicemeeter, [:long], :long
33
+ attach_function :vm_vmtype, :VBVMR_GetVoicemeeterType, [:pointer], :long
34
+ attach_function :vm_vmversion, :VBVMR_GetVoicemeeterVersion, [:pointer], :long
35
+
36
+ attach_function :vm_mdirty, :VBVMR_MacroButton_IsDirty, [], :long
37
+ attach_function :vm_get_buttonstatus,
38
+ :VBVMR_MacroButton_GetStatus,
39
+ %i[long pointer long],
40
+ :long
41
+ attach_function :vm_set_buttonstatus,
42
+ :VBVMR_MacroButton_SetStatus,
43
+ %i[long float long],
44
+ :long
45
+
46
+ attach_function :vm_pdirty, :VBVMR_IsParametersDirty, [], :long
47
+ attach_function :vm_get_parameter_float,
48
+ :VBVMR_GetParameterFloat,
49
+ %i[string pointer],
50
+ :long
51
+ attach_function :vm_set_parameter_float,
52
+ :VBVMR_SetParameterFloat,
53
+ %i[string float],
54
+ :long
55
+
56
+ attach_function :vm_get_parameter_string,
57
+ :VBVMR_GetParameterStringA,
58
+ %i[string pointer],
59
+ :long
60
+ attach_function :vm_set_parameter_string,
61
+ :VBVMR_SetParameterStringA,
62
+ %i[string string],
63
+ :long
64
+
65
+ attach_function :vm_set_parameter_multi,
66
+ :VBVMR_SetParameters,
67
+ [:string],
68
+ :long
69
+
70
+ attach_function :vm_get_level,
71
+ :VBVMR_GetLevel,
72
+ %i[long long pointer],
73
+ :long
74
+
75
+ attach_function :vm_get_num_indevices,
76
+ :VBVMR_Input_GetDeviceNumber, [], :long
77
+ attach_function :vm_get_desc_indevices,
78
+ :VBVMR_Input_GetDeviceDescA, %i[long pointer pointer pointer],
79
+ :long
80
+
81
+ attach_function :vm_get_num_outdevices,
82
+ :VBVMR_Output_GetDeviceNumber, [], :long
83
+ attach_function :vm_get_desc_outdevices,
84
+ :VBVMR_Output_GetDeviceDescA, %i[long pointer pointer pointer],
85
+ :long
86
+
87
+
88
+ @@cdll =
89
+ lambda do |func, *args|
90
+ self.retval = [send("vm_#{func}", *args), func]
91
+ end
92
+
93
+ def clear_polling() = while pdirty? || mdirty?; end
94
+
95
+ def polling(func, **kwargs)
96
+ params = {
97
+ 'get_parameter' => kwargs[:name],
98
+ 'get_buttonstatus' => "mb_#{kwargs[:id]}_#{kwargs[:mode]}",
99
+ }
100
+ return @cache.delete(params[func]) if @cache.key? params[func]
101
+
102
+ clear_polling if @sync
103
+
104
+ yield
105
+ end
106
+
107
+ def retval=(values)
108
+ ' Writer validation for CAPI calls '
109
+ retval, func = *values
110
+ unless [:get_num_indevices, :get_num_outdevices].include? func
111
+ raise CAPIErrors.new(retval, func) if retval&.nonzero?
112
+ end
113
+ @retval = retval
114
+ end
115
+
116
+ public
117
+
118
+ def pdirty?() = vm_pdirty&.nonzero?
119
+
120
+ def mdirty?() = vm_mdirty&.nonzero?
121
+
122
+ def ldirty?
123
+ @strip_buf, @bus_buf = _get_levels
124
+ return !(
125
+ @cache['strip_level'] == @strip_buf && @cache['bus_level'] == @bus_buf
126
+ )
127
+ end
128
+ end