voicemeeter 0.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +775 -0
- data/lib/voicemeeter/base.rb +225 -0
- data/lib/voicemeeter/bus.rb +129 -0
- data/lib/voicemeeter/button.rb +70 -0
- data/lib/voicemeeter/cbindings.rb +81 -0
- data/lib/voicemeeter/command.rb +35 -0
- data/lib/voicemeeter/configs.rb +117 -0
- data/lib/voicemeeter/device.rb +28 -0
- data/lib/voicemeeter/event.rb +82 -0
- data/lib/voicemeeter/fx.rb +42 -0
- data/lib/voicemeeter/install.rb +38 -0
- data/lib/voicemeeter/iremote.rb +51 -0
- data/lib/voicemeeter/kinds.rb +51 -0
- data/lib/voicemeeter/logger.rb +9 -0
- data/lib/voicemeeter/meta.rb +75 -0
- data/lib/voicemeeter/midi.rb +14 -0
- data/lib/voicemeeter/mixins.rb +83 -0
- data/lib/voicemeeter/option.rb +69 -0
- data/lib/voicemeeter/patch.rb +79 -0
- data/lib/voicemeeter/recorder.rb +90 -0
- data/lib/voicemeeter/remote.rb +102 -0
- data/lib/voicemeeter/strip.rb +254 -0
- data/lib/voicemeeter/util.rb +35 -0
- data/lib/voicemeeter/vban.rb +105 -0
- data/lib/voicemeeter/version.rb +25 -0
- data/lib/voicemeeter/worker.rb +64 -0
- data/lib/voicemeeter.rb +56 -0
- metadata +149 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
module Voicemeeter
|
2
|
+
# Base class for Remote
|
3
|
+
class Base
|
4
|
+
include Logging
|
5
|
+
include Worker
|
6
|
+
include Events::Director
|
7
|
+
prepend Util::Cache
|
8
|
+
|
9
|
+
attr_reader :kind, :midi, :event, :delay, :cache
|
10
|
+
|
11
|
+
RATELIMIT = 0.033
|
12
|
+
DELAY = 0.001
|
13
|
+
|
14
|
+
def initialize(kind, **kwargs)
|
15
|
+
@kind = kind
|
16
|
+
@sync = kwargs[:sync] || false
|
17
|
+
@ratelimit = kwargs[:ratelimit] || RATELIMIT
|
18
|
+
@delay = kwargs[:delay] || DELAY
|
19
|
+
@event =
|
20
|
+
Events::Tracker.new(
|
21
|
+
**(kwargs.select { |k, _| %i[pdirty mdirty ldirty midi].include? k })
|
22
|
+
)
|
23
|
+
@midi = Midi.new
|
24
|
+
@cache = {strip_mode: 0}
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
"Voicemeeter #{kind}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def login
|
32
|
+
CBindings.call(:bind_login, ok: [0, 1]) == 1 and run_voicemeeter(kind.name)
|
33
|
+
clear_dirty
|
34
|
+
logger.info "Successfully logged into #{self} version #{version}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def logout
|
38
|
+
sleep(0.1)
|
39
|
+
CBindings.call(:bind_logout)
|
40
|
+
logger.info "Successfully logged out of #{self}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def pdirty?
|
44
|
+
CBindings.call(:bind_is_parameters_dirty, ok: [0, 1]) == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def mdirty?
|
48
|
+
CBindings.call(:bind_macro_button_is_dirty, ok: [0, 1]) == 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def ldirty?
|
52
|
+
cache[:strip_buf], cache[:bus_buf] = _get_levels
|
53
|
+
!(
|
54
|
+
cache[:strip_level] == cache[:strip_buf] &&
|
55
|
+
cache[:bus_level] == cache[:bus_buf]
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear_dirty
|
60
|
+
catch(:clear) do
|
61
|
+
loop { throw(:clear) unless pdirty? || mdirty? }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def run_voicemeeter(kind_id)
|
66
|
+
kinds = {
|
67
|
+
basic: Kinds::KindEnum::BASIC,
|
68
|
+
banana: Kinds::KindEnum::BANANA,
|
69
|
+
potato: (Install::OS_BITS == 64) ? Kinds::KindEnum::POTATOX64 : Kinds::KindEnum::POTATO
|
70
|
+
}
|
71
|
+
if caller(1..1).first[/`(.*)'/, 1] == "login"
|
72
|
+
logger.debug "Voicemeeter engine running but the GUI appears to be down... launching."
|
73
|
+
end
|
74
|
+
CBindings.call(:bind_run_voicemeeter, kinds[kind_id])
|
75
|
+
sleep(1)
|
76
|
+
end
|
77
|
+
|
78
|
+
def type
|
79
|
+
ckind = FFI::MemoryPointer.new(:long, 1)
|
80
|
+
CBindings.call(:bind_get_voicemeeter_type, ckind)
|
81
|
+
kinds = [nil, :basic, :banana, :potato]
|
82
|
+
kinds[ckind.read_long]
|
83
|
+
end
|
84
|
+
|
85
|
+
def version
|
86
|
+
cver = FFI::MemoryPointer.new(:long, 1)
|
87
|
+
CBindings.call(:bind_get_voicemeeter_version, cver)
|
88
|
+
[
|
89
|
+
(cver.read_long & 0xFF000000) >> 24,
|
90
|
+
(cver.read_long & 0x00FF0000) >> 16,
|
91
|
+
(cver.read_long & 0x0000FF00) >> 8,
|
92
|
+
cver.read_long & 0x000000FF
|
93
|
+
].join(".")
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(name, is_string = false)
|
97
|
+
if is_string
|
98
|
+
cget = FFI::MemoryPointer.new(:string, 512, true)
|
99
|
+
CBindings.call(:bind_get_parameter_string_a, name, cget)
|
100
|
+
cget.read_string
|
101
|
+
else
|
102
|
+
cget = FFI::MemoryPointer.new(:float, 1)
|
103
|
+
CBindings.call(:bind_get_parameter_float, name, cget)
|
104
|
+
cget.read_float.round(1)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def set(name, value)
|
109
|
+
if value.is_a? String
|
110
|
+
CBindings.call(:bind_set_parameter_string_a, name, value)
|
111
|
+
else
|
112
|
+
CBindings.call(:bind_set_parameter_float, name, value.to_f)
|
113
|
+
end
|
114
|
+
cache.store(name, value)
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_buttonstatus(id, mode)
|
118
|
+
cget = FFI::MemoryPointer.new(:float, 1)
|
119
|
+
CBindings.call(:bind_macro_button_get_status, id, cget, mode)
|
120
|
+
cget.read_float.to_i
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_buttonstatus(id, mode, state)
|
124
|
+
CBindings.call(:bind_macro_button_set_status, id, state, mode)
|
125
|
+
cache.store("mb_#{id}_#{mode}", state)
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_level(mode, index)
|
129
|
+
cget = FFI::MemoryPointer.new(:float, 1)
|
130
|
+
CBindings.call(:bind_get_level, mode, index, cget)
|
131
|
+
cget.read_float
|
132
|
+
end
|
133
|
+
|
134
|
+
private def _get_levels
|
135
|
+
strip_mode = cache[:strip_mode]
|
136
|
+
[
|
137
|
+
(0...kind.num_strip_levels).map { get_level(strip_mode, _1) },
|
138
|
+
(0...kind.num_bus_levels).map { get_level(3, _1) }
|
139
|
+
]
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_num_devices(dir)
|
143
|
+
unless %i[in out].include? dir
|
144
|
+
raise Errors::VMError.new "dir got: #{dir}, expected :in or :out"
|
145
|
+
end
|
146
|
+
if dir == :in
|
147
|
+
CBindings.call(:bind_input_get_device_number, exp: ->(x) { x >= 0 })
|
148
|
+
else
|
149
|
+
CBindings.call(:bind_output_get_device_number, exp: ->(x) { x >= 0 })
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_device_description(index, dir)
|
154
|
+
unless %i[in out].include? dir
|
155
|
+
raise Errors::VMError.new "dir got: #{dir}, expected :in or :out"
|
156
|
+
end
|
157
|
+
ctype = FFI::MemoryPointer.new(:long, 1)
|
158
|
+
cname = FFI::MemoryPointer.new(:string, 256, true)
|
159
|
+
chwid = FFI::MemoryPointer.new(:string, 256, true)
|
160
|
+
if dir == :in
|
161
|
+
CBindings.call(
|
162
|
+
:bind_input_get_device_desc_a,
|
163
|
+
index,
|
164
|
+
ctype,
|
165
|
+
cname,
|
166
|
+
chwid
|
167
|
+
)
|
168
|
+
else
|
169
|
+
CBindings.call(
|
170
|
+
:bind_output_get_device_desc_a,
|
171
|
+
index,
|
172
|
+
ctype,
|
173
|
+
cname,
|
174
|
+
chwid
|
175
|
+
)
|
176
|
+
end
|
177
|
+
[cname.read_string, ctype.read_long, chwid.read_string]
|
178
|
+
end
|
179
|
+
|
180
|
+
def get_midi_message
|
181
|
+
cmsg = FFI::MemoryPointer.new(:string, 1024, true)
|
182
|
+
res =
|
183
|
+
CBindings.call(
|
184
|
+
:bind_get_midi_message,
|
185
|
+
cmsg,
|
186
|
+
1024,
|
187
|
+
ok: [-5, -6],
|
188
|
+
exp: ->(x) { x >= 0 }
|
189
|
+
)
|
190
|
+
if (got_midi = res > 0)
|
191
|
+
data = cmsg.read_bytes(res).bytes
|
192
|
+
data.each_slice(3) do |ch, key, velocity|
|
193
|
+
midi.channel = ch
|
194
|
+
midi.current = key
|
195
|
+
midi.cache[key] = velocity
|
196
|
+
end
|
197
|
+
end
|
198
|
+
got_midi
|
199
|
+
end
|
200
|
+
|
201
|
+
def sendtext(script)
|
202
|
+
raise ArgumentError, "script must not exceed 48kB" if script.length > 48000
|
203
|
+
CBindings.call(:bind_set_parameters, script)
|
204
|
+
end
|
205
|
+
|
206
|
+
def apply(data)
|
207
|
+
data.each do |key, hash|
|
208
|
+
case key.to_s.split("-")
|
209
|
+
in [/strip|bus|button/ => kls, /^[0-9]+$/ => index]
|
210
|
+
target = send(kls)
|
211
|
+
in ["vban", /in|instream|out|oustream/ => dir, /^[0-9]+$/ => index]
|
212
|
+
target = vban.send("#{dir.chomp("stream")}stream")
|
213
|
+
else
|
214
|
+
raise KeyError, "invalid config key '#{key}'"
|
215
|
+
end
|
216
|
+
target[index.to_i].apply(hash)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def apply_config(name)
|
221
|
+
apply(configs[name])
|
222
|
+
logger.info "profile #{name} applied!"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Voicemeeter
|
2
|
+
module Bus
|
3
|
+
# Base class for Bus
|
4
|
+
class Base
|
5
|
+
include IRemote
|
6
|
+
include Mixins::Fades
|
7
|
+
include Mixins::Return
|
8
|
+
|
9
|
+
attr_reader :eq, :mode, :levels
|
10
|
+
|
11
|
+
def self.make(remote, i)
|
12
|
+
(i < remote.kind.phys_out) ? PhysicalBus.new(remote, i) : VirtualBus.new(remote, i)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(remote, i)
|
16
|
+
super
|
17
|
+
make_accessor_bool :mute, :mono, :sel, :monitor
|
18
|
+
make_accessor_float :gain
|
19
|
+
make_accessor_string :label
|
20
|
+
|
21
|
+
@eq = BusEq.new(remote, i)
|
22
|
+
@mode = BusModes.new(remote, i)
|
23
|
+
@levels = BusLevels.new(remote, i)
|
24
|
+
end
|
25
|
+
|
26
|
+
def identifier
|
27
|
+
"bus[#{@index}]"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Represents a Physical Bus
|
32
|
+
class PhysicalBus < Base; end
|
33
|
+
|
34
|
+
# Represents a Virtual Bus
|
35
|
+
class VirtualBus < Base; end
|
36
|
+
|
37
|
+
class BusEq
|
38
|
+
include IRemote
|
39
|
+
|
40
|
+
def initialize(remote, i)
|
41
|
+
super
|
42
|
+
make_accessor_bool :on, :ab
|
43
|
+
end
|
44
|
+
|
45
|
+
def identifier
|
46
|
+
"bus[#{@index}].eq"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class BusModes
|
51
|
+
include IRemote
|
52
|
+
|
53
|
+
def initialize(remote, i)
|
54
|
+
super
|
55
|
+
make_accessor_bool :normal,
|
56
|
+
:amix,
|
57
|
+
:bmix,
|
58
|
+
:repeat,
|
59
|
+
:composite,
|
60
|
+
:tvmix,
|
61
|
+
:upmix21,
|
62
|
+
:upmix41,
|
63
|
+
:upmix61,
|
64
|
+
:centeronly,
|
65
|
+
:lfeonly,
|
66
|
+
:rearonly
|
67
|
+
end
|
68
|
+
|
69
|
+
def identifier
|
70
|
+
"bus[#{@index}].mode"
|
71
|
+
end
|
72
|
+
|
73
|
+
def get
|
74
|
+
sleep(@remote.delay)
|
75
|
+
%i[amix bmix repeat composite tvmix upmix21 upmix41 upmix61 centeronly lfeonly rearonly].each do |mode|
|
76
|
+
if send(mode)
|
77
|
+
return mode
|
78
|
+
end
|
79
|
+
end
|
80
|
+
:normal
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class BusLevels
|
86
|
+
include IRemote
|
87
|
+
|
88
|
+
def initialize(remote, i)
|
89
|
+
super
|
90
|
+
@init = i * 8
|
91
|
+
@offset = 8
|
92
|
+
end
|
93
|
+
|
94
|
+
def identifier
|
95
|
+
"bus[#{@index}]"
|
96
|
+
end
|
97
|
+
|
98
|
+
def getter(mode)
|
99
|
+
convert = ->(x) { (x > 0) ? (20 * Math.log(x, 10)).round(1) : -200.0 }
|
100
|
+
|
101
|
+
vals = if @remote.running? && @remote.event.ldirty
|
102
|
+
@remote.cache[:bus_level][@init, @offset]
|
103
|
+
else
|
104
|
+
(@init...@init + @offset).map { |i| @remote.get_level(mode, i) }
|
105
|
+
end
|
106
|
+
vals.map(&convert)
|
107
|
+
end
|
108
|
+
|
109
|
+
def all
|
110
|
+
getter(Mixins::LevelEnum::BUS)
|
111
|
+
end
|
112
|
+
|
113
|
+
def isdirty? = @remote.cache[:bus_comp][@init, @offset].any?
|
114
|
+
end
|
115
|
+
|
116
|
+
class BusDevice
|
117
|
+
include IRemote
|
118
|
+
|
119
|
+
def initialize(remote, i)
|
120
|
+
super
|
121
|
+
make_reader_only :name, :sr
|
122
|
+
make_writer_only :wdm, :ks, :mme, :asio
|
123
|
+
end
|
124
|
+
|
125
|
+
def identifier
|
126
|
+
"bus[#{@index}].device"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Voicemeeter
|
2
|
+
module Button
|
3
|
+
module ButtonEnum
|
4
|
+
STATE = 1
|
5
|
+
STATEONLY = 2
|
6
|
+
TRIGGER = 3
|
7
|
+
|
8
|
+
def identifier(val)
|
9
|
+
[nil, :state, :stateonly, :trigger][val]
|
10
|
+
end
|
11
|
+
|
12
|
+
module_function :identifier
|
13
|
+
end
|
14
|
+
|
15
|
+
module ButtonColorMixin
|
16
|
+
def identifier
|
17
|
+
"command.button[#{@index}]"
|
18
|
+
end
|
19
|
+
|
20
|
+
def color
|
21
|
+
method(:getter).super_method.call("color").to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def color=(val)
|
25
|
+
method(:setter).super_method.call("color", val)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Base class for Button
|
30
|
+
class Base
|
31
|
+
include Logging
|
32
|
+
include IRemote
|
33
|
+
include ButtonColorMixin
|
34
|
+
|
35
|
+
def getter(mode)
|
36
|
+
logger.debug "getter: button[#{@index}].#{ButtonEnum.identifier(mode)}"
|
37
|
+
@remote.get_buttonstatus(@index, mode)
|
38
|
+
end
|
39
|
+
|
40
|
+
def setter(mode, val)
|
41
|
+
logger.debug "setter: button[#{@index}].#{ButtonEnum.identifier(mode)}=#{val}"
|
42
|
+
@remote.set_buttonstatus(@index, mode, val)
|
43
|
+
end
|
44
|
+
|
45
|
+
def state
|
46
|
+
getter(ButtonEnum::STATE) == 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def state=(value)
|
50
|
+
setter(ButtonEnum::STATE, value && 1 || 0)
|
51
|
+
end
|
52
|
+
|
53
|
+
def stateonly
|
54
|
+
getter(ButtonEnum::STATEONLY) == 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def stateonly=(value)
|
58
|
+
setter(ButtonEnum::STATEONLY, value && 1 || 0)
|
59
|
+
end
|
60
|
+
|
61
|
+
def trigger
|
62
|
+
getter(ButtonEnum::TRIGGER) == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def trigger=(value)
|
66
|
+
setter(ButtonEnum::TRIGGER, value && 1 || 0)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Voicemeeter
|
2
|
+
# Ruby bindings for the C-API functions
|
3
|
+
module CBindings
|
4
|
+
extend Logging
|
5
|
+
extend FFI::Library
|
6
|
+
using Util::CoreExtensions
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
VM_PATH = Install.get_vmpath
|
11
|
+
|
12
|
+
ffi_lib VM_PATH.join(
|
13
|
+
"VoicemeeterRemote#{(Install::OS_BITS == 64) ? "64" : "32"}.dll"
|
14
|
+
)
|
15
|
+
ffi_convention :stdcall
|
16
|
+
|
17
|
+
private_class_method def self.attach_function(c_name, args, returns)
|
18
|
+
ruby_name = :"bind_#{c_name.to_s.delete_prefix("VBVMR_").snakecase}"
|
19
|
+
super(ruby_name, c_name, args, returns)
|
20
|
+
end
|
21
|
+
|
22
|
+
attach_function :VBVMR_Login, [], :long
|
23
|
+
attach_function :VBVMR_Logout, [], :long
|
24
|
+
attach_function :VBVMR_RunVoicemeeter, [:long], :long
|
25
|
+
attach_function :VBVMR_GetVoicemeeterType, [:pointer], :long
|
26
|
+
attach_function :VBVMR_GetVoicemeeterVersion, [:pointer], :long
|
27
|
+
attach_function :VBVMR_MacroButton_IsDirty, [], :long
|
28
|
+
attach_function :VBVMR_MacroButton_GetStatus, %i[long pointer long], :long
|
29
|
+
attach_function :VBVMR_MacroButton_SetStatus, %i[long float long], :long
|
30
|
+
|
31
|
+
attach_function :VBVMR_IsParametersDirty, [], :long
|
32
|
+
attach_function :VBVMR_GetParameterFloat, %i[string pointer], :long
|
33
|
+
attach_function :VBVMR_SetParameterFloat, %i[string float], :long
|
34
|
+
|
35
|
+
attach_function :VBVMR_GetParameterStringA, %i[string pointer], :long
|
36
|
+
attach_function :VBVMR_SetParameterStringA, %i[string string], :long
|
37
|
+
|
38
|
+
attach_function :VBVMR_SetParameters, [:string], :long
|
39
|
+
|
40
|
+
attach_function :VBVMR_GetLevel, %i[long long pointer], :long
|
41
|
+
|
42
|
+
attach_function :VBVMR_Input_GetDeviceNumber, [], :long
|
43
|
+
attach_function :VBVMR_Input_GetDeviceDescA,
|
44
|
+
%i[long pointer pointer pointer],
|
45
|
+
:long
|
46
|
+
|
47
|
+
attach_function :VBVMR_Output_GetDeviceNumber, [], :long
|
48
|
+
attach_function :VBVMR_Output_GetDeviceDescA,
|
49
|
+
%i[long pointer pointer pointer],
|
50
|
+
:long
|
51
|
+
|
52
|
+
attach_function :VBVMR_GetMidiMessage, %i[pointer long], :long
|
53
|
+
|
54
|
+
def call(fn, *args, ok: [0], exp: nil)
|
55
|
+
to_cname = -> {
|
56
|
+
"VBVMR_#{fn.to_s.delete_prefix("bind_").camelcase.gsub(/(Button|Input|Output)/, '\1_')}"
|
57
|
+
}
|
58
|
+
|
59
|
+
res = send(fn, *args)
|
60
|
+
if exp
|
61
|
+
unless exp.call(res) || ok.include?(res)
|
62
|
+
raise Errors::VMCAPIError.new to_cname.call, res
|
63
|
+
end
|
64
|
+
else
|
65
|
+
unless ok.include?(res)
|
66
|
+
raise Errors::VMCAPIError.new to_cname.call, res
|
67
|
+
end
|
68
|
+
end
|
69
|
+
res
|
70
|
+
rescue Errors::VMCAPIError => e
|
71
|
+
err_msg = [
|
72
|
+
"#{e.class.name}: #{e.message}",
|
73
|
+
*e.backtrace
|
74
|
+
]
|
75
|
+
logger.error err_msg.join("\n")
|
76
|
+
raise
|
77
|
+
end
|
78
|
+
|
79
|
+
module_function :call
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Voicemeeter
|
2
|
+
class Command
|
3
|
+
include IRemote
|
4
|
+
|
5
|
+
def initialize(remote)
|
6
|
+
super
|
7
|
+
make_action_method :show, :restart, :shutdown
|
8
|
+
make_writer_bool :showvbanchat, :lock
|
9
|
+
end
|
10
|
+
|
11
|
+
def identifier
|
12
|
+
:command
|
13
|
+
end
|
14
|
+
|
15
|
+
def hide
|
16
|
+
setter("show", 0)
|
17
|
+
end
|
18
|
+
|
19
|
+
def load(value)
|
20
|
+
raise VMError.new("load got: #{value}, but expected a string") unless value.is_a? String
|
21
|
+
setter("load", value)
|
22
|
+
sleep(0.2)
|
23
|
+
end
|
24
|
+
|
25
|
+
def save(value)
|
26
|
+
raise VMError.new("save got: #{value}, but expected a string") unless value.is_a? String
|
27
|
+
setter("save", value)
|
28
|
+
sleep(0.2)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset
|
32
|
+
@remote.apply_config(:reset)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Voicemeeter
|
2
|
+
module Configs
|
3
|
+
class TOMLConfBuilder
|
4
|
+
def self.run(kind)
|
5
|
+
aouts = (0...kind.phys_out).to_h { |i| [:"A#{i + 1}", false] }
|
6
|
+
bouts = (0...kind.virt_out).to_h { |i| [:"B#{i + 1}", false] }
|
7
|
+
strip_bools = %i[mute mono solo].to_h { |param| [param, false] }
|
8
|
+
gain = [:gain].to_h { |param| [param, 0.0] }
|
9
|
+
|
10
|
+
phys_float =
|
11
|
+
%i[comp gate denoiser].to_h { |param| [param, {knob: 0.0}] }
|
12
|
+
eq = [:eq].to_h { |param| [param, {on: false}] }
|
13
|
+
|
14
|
+
overrides = {B1: true}
|
15
|
+
phys_strip =
|
16
|
+
(0...kind.phys_in).to_h do |i|
|
17
|
+
[
|
18
|
+
"strip-#{i}".to_sym,
|
19
|
+
{**aouts, **bouts, **strip_bools, **gain, **phys_float, **eq, **overrides}
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
overrides = {A1: true}
|
24
|
+
virt_strip =
|
25
|
+
(kind.phys_in...kind.phys_in + kind.virt_in).to_h do |i|
|
26
|
+
[
|
27
|
+
:"strip-#{i}",
|
28
|
+
{**aouts, **bouts, **strip_bools, **gain, **overrides}
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
bus_bools = %i[mute mono].to_h { |param| [param, false] }
|
33
|
+
bus =
|
34
|
+
(0...kind.num_bus).to_h do |i|
|
35
|
+
[:"bus-#{i}", {**bus_bools, **gain, **eq}]
|
36
|
+
end
|
37
|
+
|
38
|
+
{**phys_strip, **virt_strip, **bus}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class FileReader
|
43
|
+
include Logging
|
44
|
+
|
45
|
+
def initialize(kind)
|
46
|
+
@configpaths = [
|
47
|
+
Pathname.getwd.join("configs", kind.name.to_s),
|
48
|
+
Pathname.new(Dir.home).join(".config", "voicemeeter-rb", kind.name.to_s),
|
49
|
+
Pathname.new(Dir.home).join("Documents", "Voicemeeter", "configs", kind.name.to_s)
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
def each
|
54
|
+
@configpaths.each do |configpath|
|
55
|
+
if configpath.exist?
|
56
|
+
logger.debug "checking #{configpath} for configs"
|
57
|
+
filepaths = configpath.glob("*.{yaml,yml}")
|
58
|
+
filepaths.each do |filepath|
|
59
|
+
@filename = (filepath.basename.sub_ext "").to_s.to_sym
|
60
|
+
|
61
|
+
yield @filename, YAML.load_file(
|
62
|
+
filepath,
|
63
|
+
symbolize_names: true
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
rescue Psych::SyntaxError => e
|
69
|
+
logger.error "#{e.class.name}: #{e.message}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Loader
|
74
|
+
include Logging
|
75
|
+
|
76
|
+
attr_reader :configs
|
77
|
+
|
78
|
+
def initialize(kind)
|
79
|
+
@kind = kind
|
80
|
+
@configs = Hash.new do |hash, key|
|
81
|
+
raise Errors::VMError.new "unknown config '#{key}'. known configs: #{hash.keys}"
|
82
|
+
end
|
83
|
+
@filereader = FileReader.new(kind)
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
"Loader #{@kind}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def run
|
91
|
+
logger.debug "Running #{self}"
|
92
|
+
configs[:reset] = TOMLConfBuilder.run(@kind)
|
93
|
+
@filereader.each(&method(:register))
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
private def register(identifier, data)
|
98
|
+
if configs.key? identifier
|
99
|
+
logger.debug "config with name '#{identifier}' already in memory, skipping..."
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
configs[identifier] = data
|
104
|
+
logger.info "#{@kind.name}/#{identifier} loaded into memory"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def get(kind_id)
|
109
|
+
unless defined? @loaders
|
110
|
+
@loaders = Kinds::ALL.to_h { |kind| [kind.name, Loader.new(kind).run] }
|
111
|
+
end
|
112
|
+
@loaders[kind_id].configs
|
113
|
+
end
|
114
|
+
|
115
|
+
module_function :get
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Voicemeeter
|
2
|
+
class Device
|
3
|
+
def initialize(remote)
|
4
|
+
@remote = remote
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
"#{self.class.name.split("::").last}#{@index}#{@remote.kind}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def getter(**kwargs)
|
12
|
+
kwargs => {direction:}
|
13
|
+
return @remote.get_num_devices(direction) unless kwargs.key? :index
|
14
|
+
|
15
|
+
vals = @remote.get_device_description(kwargs[:index], direction)
|
16
|
+
types = {1 => "mme", 3 => "wdm", 4 => "ks", 5 => "asio"}
|
17
|
+
{name: vals[0], type: types[vals[1]], id: vals[2]}
|
18
|
+
end
|
19
|
+
|
20
|
+
def ins = getter(direction: :in)
|
21
|
+
|
22
|
+
def outs = getter(direction: :out)
|
23
|
+
|
24
|
+
def input(i) = getter(index: i, direction: :in)
|
25
|
+
|
26
|
+
def output(i) = getter(index: i, direction: :out)
|
27
|
+
end
|
28
|
+
end
|