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.
@@ -0,0 +1,102 @@
1
+ module Voicemeeter
2
+ # Builder module for Remote factories.
3
+ module Builder
4
+ private
5
+
6
+ def steps(step)
7
+ case step
8
+ when :strip then -> { (0...kind.num_strip).map { Strip::Base.make(self, _1) } }
9
+ when :bus then -> { (0...kind.num_bus).map { Bus::Base.make(self, _1) } }
10
+ when :button then -> { (0...kind.num_buttons).map { Button::Base.new(self, _1) } }
11
+ when :vban then -> { Vban::Base.new(self) }
12
+ when :command then -> { Command.new(self) }
13
+ when :recorder then -> { Recorder::Base.new(self) }
14
+ when :device then -> { Device.new(self) }
15
+ when :fx then -> { Fx.new(self) }
16
+ when :patch then -> { Patch::Base.new(self) }
17
+ when :option then -> { Option::Base.new(self) }
18
+ end
19
+ end
20
+
21
+ def director
22
+ %i[strip bus button vban command device option]
23
+ end
24
+ end
25
+
26
+ module Remote
27
+ extend Logging
28
+
29
+ # Concrete class for Remote
30
+ class Remote < Base
31
+ include Builder
32
+
33
+ public attr_reader :strip, :bus, :button, :vban, :command, :device, :option
34
+
35
+ def initialize(...)
36
+ super
37
+ director.each { |step| instance_variable_set("@#{step}", steps(step).call) }
38
+ end
39
+
40
+ def configs
41
+ Configs.get(kind.name)
42
+ end
43
+
44
+ def run
45
+ login
46
+ if event.any?
47
+ init_event_threads
48
+ end
49
+
50
+ yield(self) if block_given?
51
+ ensure
52
+ end_event_threads
53
+ logout
54
+ end
55
+ end
56
+
57
+ # Represents a RemoteBasic type
58
+ class RemoteBasic < Remote; end
59
+
60
+ # Represents a RemoteBanana type
61
+ class RemoteBanana < Remote
62
+ public attr_reader :recorder, :patch
63
+
64
+ private def director
65
+ super | [:recorder, :patch]
66
+ end
67
+ end
68
+
69
+ # Represents a RemotePotato type
70
+ class RemotePotato < Remote
71
+ public attr_reader :recorder, :patch, :fx
72
+
73
+ private def director
74
+ super | [:recorder, :patch, :fx]
75
+ end
76
+ end
77
+
78
+ # Factory class for Remote. Returns a Remote class of a kind.
79
+ class RequestRemote
80
+ def self.for(kind, **)
81
+ case kind.name
82
+ when :basic
83
+ RemoteBasic.new(kind, **)
84
+ when :banana
85
+ RemoteBanana.new(kind, **)
86
+ when :potato
87
+ RemotePotato.new(kind, **)
88
+ end
89
+ end
90
+ end
91
+
92
+ # Interface entry point. Wraps factory class and handles kind errors.
93
+ def self.new(kind_id, **)
94
+ kind = Kinds.get(kind_id)
95
+ rescue KeyError => e
96
+ logger.error "#{e.class.name}: #{e.message}"
97
+ raise Errors::VMError.new "unknown Voicemeeter kind '#{kind_id}'"
98
+ else
99
+ RequestRemote.for(kind, **)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,254 @@
1
+ module Voicemeeter
2
+ module Strip
3
+ # Base class for Strip
4
+ class Base
5
+ include IRemote
6
+ include Mixins::Outputs
7
+ include Mixins::Fades
8
+
9
+ attr_reader :gainlayer, :levels
10
+
11
+ def self.make(remote, i)
12
+ (i < remote.kind.phys_in) ? PhysicalStrip.new(remote, i) : VirtualStrip.new(remote, i)
13
+ end
14
+
15
+ def initialize(remote, i)
16
+ super
17
+ make_accessor_bool :solo, :mute, :mono
18
+ make_accessor_float :gain
19
+ make_accessor_int :limit
20
+ make_accessor_string :label
21
+
22
+ @gainlayer = (0...8).map { GainLayer.new(remote, i, _1) }
23
+ @levels = StripLevels.new(remote, i)
24
+ end
25
+
26
+ def identifier
27
+ "strip[#{@index}]"
28
+ end
29
+ end
30
+
31
+ # Represents a Physical Strip
32
+ class PhysicalStrip < Base
33
+ include Mixins::Xy::Pan
34
+ include Mixins::Xy::Color
35
+ include Mixins::Xy::Fx
36
+ include Mixins::Fx
37
+
38
+ attr_reader :comp, :gate, :denoiser, :eq, :device
39
+
40
+ def initialize(remote, i)
41
+ super
42
+ make_accessor_float :audibility
43
+
44
+ @comp = StripComp.new(remote, i)
45
+ @gate = StripGate.new(remote, i)
46
+ @denoiser = StripDenoiser.new(remote, i)
47
+ @eq = StripEq.new(remote, i)
48
+ @device = StripDevice.new(remote, i)
49
+ end
50
+ end
51
+
52
+ class StripComp
53
+ include IRemote
54
+
55
+ def initialize(remote, i)
56
+ super
57
+ make_accessor_float :gainin,
58
+ :ratio,
59
+ :threshold,
60
+ :attack,
61
+ :release,
62
+ :knee,
63
+ :gainout
64
+ make_accessor_bool :makeup
65
+ end
66
+
67
+ def identifier
68
+ "strip[#{@index}].comp"
69
+ end
70
+
71
+ def knob
72
+ getter("")
73
+ end
74
+
75
+ def knob=(val)
76
+ setter("", val)
77
+ end
78
+ end
79
+
80
+ class StripGate
81
+ include IRemote
82
+
83
+ def initialize(remote, i)
84
+ super
85
+ make_accessor_float :threshold, :damping, :attack, :hold, :release
86
+ make_accessor_int :bpsidechain
87
+ end
88
+
89
+ def identifier
90
+ "strip[#{@index}].gate"
91
+ end
92
+
93
+ def knob
94
+ getter("")
95
+ end
96
+
97
+ def knob=(val)
98
+ setter("", val)
99
+ end
100
+ end
101
+
102
+ class StripDenoiser
103
+ include IRemote
104
+
105
+ def identifier
106
+ "strip[#{@index}].denoiser"
107
+ end
108
+
109
+ def knob
110
+ getter("")
111
+ end
112
+
113
+ def knob=(val)
114
+ setter("", val)
115
+ end
116
+ end
117
+
118
+ class StripEq
119
+ include IRemote
120
+
121
+ def initialize(remote, i)
122
+ super
123
+ make_accessor_bool :on, :ab
124
+ end
125
+
126
+ def identifier
127
+ "strip[#{@index}].eq"
128
+ end
129
+ end
130
+
131
+ class StripDevice
132
+ include IRemote
133
+
134
+ def initialize(remote, i)
135
+ super
136
+ make_reader_int :sr
137
+ make_reader_string :name
138
+ make_writer_string :wdm, :ks, :mme, :asio
139
+ end
140
+
141
+ def identifier
142
+ "strip[#{@index}].device"
143
+ end
144
+ end
145
+
146
+ # Represents a Virtual Strip
147
+ class VirtualStrip < Base
148
+ include Mixins::Xy::Pan
149
+ include Mixins::Apps
150
+
151
+ def initialize(remote, i)
152
+ super
153
+ make_accessor_bool :mc
154
+ make_accessor_int :karaoke
155
+ end
156
+
157
+ def bass
158
+ round(getter("EQGain1"), 1)
159
+ end
160
+
161
+ def bass=(val)
162
+ setter("EQGain1", val)
163
+ end
164
+
165
+ def mid
166
+ round(getter("EQGain2"), 1)
167
+ end
168
+
169
+ def mid=(val)
170
+ setter("EQGain2", val)
171
+ end
172
+
173
+ def treble
174
+ round(getter("EQGain3"), 1)
175
+ end
176
+
177
+ def treble=(val)
178
+ setter("EQGain3", val)
179
+ end
180
+
181
+ alias_method :med, :mid
182
+ alias_method :med=, :mid=
183
+ alias_method :high, :treble
184
+ alias_method :high=, :treble=
185
+ end
186
+
187
+ class GainLayer
188
+ include IRemote
189
+
190
+ def initialize(remote, i, j)
191
+ super(remote, i)
192
+ @j = j
193
+ end
194
+
195
+ def identifier
196
+ "strip[#{@index}]"
197
+ end
198
+
199
+ def gain
200
+ getter("gainlayer[#{@j}]")
201
+ end
202
+
203
+ def gain=(value)
204
+ setter("gainlayer[#{@j}]", value)
205
+ end
206
+ end
207
+
208
+ class StripLevels
209
+ include IRemote
210
+
211
+ def initialize(remote, i)
212
+ super
213
+ p_in = remote.kind.phys_in
214
+ if i < p_in
215
+ @init = i * 2
216
+ @offset = 2
217
+ else
218
+ @init = (p_in * 2) + ((i - p_in) * 8)
219
+ @offset = 8
220
+ end
221
+ end
222
+
223
+ def identifier
224
+ "strip[#{@index}]"
225
+ end
226
+
227
+ def get_level(mode)
228
+ convert = ->(x) { (x > 0) ? (20 * Math.log(x, 10)).round(1) : -200.0 }
229
+
230
+ @remote.cache[:strip_mode] = mode
231
+ vals = if @remote.running? && @remote.event.ldirty
232
+ @remote.cache[:strip_level][@init, @offset]
233
+ else
234
+ (@init...@init + @offset).map { |i| @remote.get_level(mode, i) }
235
+ end
236
+ vals.map(&convert)
237
+ end
238
+
239
+ def prefader
240
+ get_level(Mixins::LevelEnum::PREFADER)
241
+ end
242
+
243
+ def postfader
244
+ get_level(Mixins::LevelEnum::POSTFADER)
245
+ end
246
+
247
+ def postmute
248
+ get_level(Mixins::LevelEnum::POSTMUTE)
249
+ end
250
+
251
+ def isdirty? = @remote.cache[:strip_comp][@init, @offset].any?
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,35 @@
1
+ module Voicemeeter
2
+ module Util
3
+ module CoreExtensions
4
+ refine String do
5
+ def snakecase
6
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
7
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
8
+ .tr("-", "_")
9
+ .gsub(/\s/, "_")
10
+ .gsub(/__+/, "_")
11
+ .downcase
12
+ end
13
+
14
+ def camelcase
15
+ self if self !~ /_/ && self =~ /[A-Z]+.*/
16
+ split("_").map(&:capitalize).join
17
+ end
18
+ end
19
+ end
20
+
21
+ module Cache
22
+ def get(name, is_string = false)
23
+ return cache.delete(name) if cache.key? name
24
+ clear_dirty if @sync
25
+ super
26
+ end
27
+
28
+ def get_buttonstatus(id, mode)
29
+ return cache.delete("mb_#{id}_#{mode}") if cache.key? "mb_#{id}_#{mode}"
30
+ clear_dirty if @sync
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,105 @@
1
+ module Voicemeeter
2
+ module Vban
3
+ # Base class for Vban In/Out Streams
4
+ class VbanStream
5
+ include IRemote
6
+
7
+ def initialize(remote, i)
8
+ super
9
+ make_accessor_bool :on
10
+ make_accessor_string :name, :ip
11
+ make_accessor_int :quality, :route
12
+ end
13
+
14
+ def identifier
15
+ "vban.#{direction}stream[#{@index}]"
16
+ end
17
+
18
+ def direction
19
+ raise "Called abstract method: direction"
20
+ end
21
+ end
22
+
23
+ # Represents a Vban InStream
24
+ class VbanInstream < VbanStream
25
+ def initialize(remote, i)
26
+ super
27
+ make_reader_int :sr, :channel, :bit
28
+ end
29
+
30
+ def direction
31
+ :in
32
+ end
33
+ end
34
+
35
+ # Represents a Vban Audio InStream
36
+ class VbanAudioInstream < VbanInstream; end
37
+
38
+ # Represents a Vban Midi InStream
39
+ class VbanMidiInstream < VbanInstream; end
40
+
41
+ # Represents a Vban Text InStream
42
+ class VbanTextInstream < VbanInstream; end
43
+
44
+ # Represents a Vban OutStream
45
+ class VbanOutstream < VbanStream
46
+ def initialize(remote, i)
47
+ super
48
+ make_accessor_int :sr, :channel, :bit
49
+ end
50
+
51
+ def direction
52
+ :out
53
+ end
54
+ end
55
+
56
+ # Represents a Vban Audio OutStream
57
+ class VbanAudioOutstream < VbanOutstream; end
58
+
59
+ # Represents a Vban Midi OutStream
60
+ class VbanMidiOutstream < VbanOutstream; end
61
+
62
+ class RequestVbanStream
63
+ def self.for(remote, i, dir)
64
+ vban_in, vban_out, midi, _ = remote.kind.vban
65
+ case dir
66
+ when :in
67
+ if i < vban_in
68
+ VbanAudioInstream.new(remote, i)
69
+ elsif i < vban_in + midi
70
+ VbanMidiInstream.new(remote, i)
71
+ else
72
+ VbanTextInstream.new(remote, i)
73
+ end
74
+ when :out
75
+ if i < vban_out
76
+ VbanAudioOutstream.new(remote, i)
77
+ elsif i < vban_out + midi
78
+ VbanMidiOutstream.new(remote, i)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # Base class for Vban
85
+ class Base
86
+ attr_reader :instream, :outstream
87
+
88
+ def initialize(remote)
89
+ vban_in, vban_out, midi, text = remote.kind.vban
90
+ @instream = (0...vban_in + midi + text).map { RequestVbanStream.for(remote, _1, :in) }
91
+ @outstream = (0...vban_out + midi).map { RequestVbanStream.for(remote, _1, :out) }
92
+
93
+ @remote = remote
94
+ end
95
+
96
+ def enable
97
+ @remote.set("vban.enable", 1)
98
+ end
99
+
100
+ def disable
101
+ @remote.set("vban.enable", 0)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,25 @@
1
+ module Voicemeeter
2
+ module VERSION
3
+ module_function
4
+
5
+ def major
6
+ 0
7
+ end
8
+
9
+ def minor
10
+ 0
11
+ end
12
+
13
+ def patch
14
+ 1
15
+ end
16
+
17
+ def to_a
18
+ [major, minor, patch]
19
+ end
20
+
21
+ def to_s
22
+ to_a.join(".")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,64 @@
1
+ module Voicemeeter
2
+ module Worker
3
+ include Logging
4
+
5
+ private
6
+
7
+ def init_producer(que)
8
+ @producer = Thread.new do
9
+ Thread.current.name = "producer"
10
+ Thread.current[:running] = true
11
+ while Thread.current[:running]
12
+ que << :pdirty if event.pdirty
13
+ que << :mdirty if event.mdirty
14
+ que << :midi if event.midi
15
+ que << :ldirty if event.ldirty
16
+ sleep(@ratelimit)
17
+ end
18
+ logger.debug "closing #{Thread.current.name} thread"
19
+ que << Thread.current[:running]
20
+ end
21
+ end
22
+
23
+ def init_worker(que)
24
+ logger.info "Listening for #{event.get.join(", ")} events"
25
+ cache[:strip_level], cache[:bus_level] = _get_levels
26
+ cache[:strip_comp] = Array.new(kind.num_strip_levels, false)
27
+ cache[:bus_comp] = Array.new(kind.num_bus_levels, false)
28
+ Thread.new do
29
+ Thread.current.name = "worker"
30
+ while (event = que.pop)
31
+ fire :pdirty if event == :pdirty && pdirty?
32
+ fire :mdirty if event == :mdirty && mdirty?
33
+ fire :midi if event == :midi && get_midi_message
34
+ if event == :ldirty && ldirty?
35
+ cache[:strip_comp] = cache[:strip_level].zip(cache[:strip_buf]).map { |a, b| a != b }
36
+ cache[:bus_comp] = cache[:bus_level].zip(cache[:bus_buf]).map { |a, b| a != b }
37
+ cache[:strip_level] = cache[:strip_buf]
38
+ cache[:bus_level] = cache[:bus_buf]
39
+ fire :ldirty
40
+ end
41
+ end
42
+ logger.debug "closing #{Thread.current.name} thread"
43
+ end
44
+ end
45
+
46
+ public
47
+
48
+ def running?
49
+ @producer&.alive?
50
+ end
51
+
52
+ def init_event_threads
53
+ que = Queue.new
54
+ init_worker(que) and init_producer(que)
55
+ end
56
+
57
+ def end_event_threads
58
+ if running?
59
+ @producer[:running] = false
60
+ @producer.join
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,56 @@
1
+ require "yaml"
2
+ require "pathname"
3
+ require "win32/registry"
4
+ require "ffi"
5
+ require "date"
6
+ require "logger"
7
+
8
+ require_relative "voicemeeter/logger"
9
+ require_relative "voicemeeter/worker"
10
+ require_relative "voicemeeter/event"
11
+ require_relative "voicemeeter/util"
12
+ require_relative "voicemeeter/meta"
13
+ require_relative "voicemeeter/iremote"
14
+ require_relative "voicemeeter/mixins"
15
+ require_relative "voicemeeter/install"
16
+
17
+ require_relative "voicemeeter/base"
18
+ require_relative "voicemeeter/bus"
19
+ require_relative "voicemeeter/button"
20
+ require_relative "voicemeeter/cbindings"
21
+ require_relative "voicemeeter/command"
22
+ require_relative "voicemeeter/configs"
23
+ require_relative "voicemeeter/device"
24
+ require_relative "voicemeeter/fx"
25
+ require_relative "voicemeeter/kinds"
26
+ require_relative "voicemeeter/midi"
27
+ require_relative "voicemeeter/option"
28
+ require_relative "voicemeeter/patch"
29
+ require_relative "voicemeeter/recorder"
30
+ require_relative "voicemeeter/remote"
31
+ require_relative "voicemeeter/strip"
32
+ require_relative "voicemeeter/vban"
33
+
34
+ require_relative "voicemeeter/version"
35
+
36
+ module Voicemeeter
37
+ module Errors
38
+ # Base Voicemeeter error class
39
+ class VMError < StandardError; end
40
+
41
+ # Raised when errors occur during installation.
42
+ class VMInstallError < VMError; end
43
+
44
+ # Raised when the C-API returns error codes
45
+ class VMCAPIError < VMError
46
+ attr_reader :fn_name, :code
47
+
48
+ # create a new VMCAPIError with given C-API function name and error code
49
+ def initialize(fn_name, code)
50
+ @fn_name = fn_name
51
+ @code = code
52
+ super("#{fn_name} returned #{code}")
53
+ end
54
+ end
55
+ end
56
+ end