voicemeeter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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