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,82 @@
1
+ module Voicemeeter
2
+ module Events
3
+ module Director
4
+ def observers
5
+ @observers ||= {}
6
+ end
7
+
8
+ def on(event, method = nil, &block)
9
+ (observers[event] ||= []) << (block || method)
10
+ end
11
+
12
+ def register(cbs)
13
+ cbs = Array(cbs) unless cbs.respond_to? :each
14
+ cbs.each { |cb| on(cb.name[3..].to_sym, cb) }
15
+ end
16
+
17
+ def deregister(cbs)
18
+ cbs = Array(cbs) unless cbs.respond_to? :each
19
+ cbs.each { |cb| observers[cb.name[3..].to_sym]&.reject! { |o| cbs.include? o } }
20
+ end
21
+
22
+ def fire(event)
23
+ observers[event]&.each { |block| block.call }
24
+ end
25
+ end
26
+
27
+ class Tracker
28
+ include Logging
29
+
30
+ attr_reader :pdirty, :mdirty, :midi, :ldirty
31
+
32
+ def initialize(**kwargs)
33
+ make_writer_methods :pdirty, :mdirty, :midi, :ldirty
34
+
35
+ kwargs.each do |key, value|
36
+ instance_variable_set("@#{key}", value || false)
37
+ end
38
+ end
39
+
40
+ def to_s
41
+ self.class.name.split("::").last.to_s
42
+ end
43
+
44
+ def info(msg = nil)
45
+ info_msg = msg ? ["#{msg} events."] : []
46
+ info_msg << if any?
47
+ ["Now listening for #{get.join(", ")} events"]
48
+ else
49
+ ["Not listening for any events"]
50
+ end
51
+ logger.info info_msg.join(" ")
52
+ end
53
+
54
+ private def make_writer_methods(*params)
55
+ params.each do |param|
56
+ define_singleton_method("#{param}=") do |value|
57
+ instance_variable_set("@#{param}", value)
58
+ info("#{param} #{value ? "added to" : "removed from"}")
59
+ end
60
+ end
61
+ end
62
+
63
+ def get
64
+ %i[pdirty mdirty midi ldirty].reject { |ev| !send(ev) }
65
+ end
66
+
67
+ def any?
68
+ [pdirty, mdirty, midi, ldirty].any?
69
+ end
70
+
71
+ def add(events)
72
+ events = [events] unless events.respond_to? :each
73
+ events.each { |e| send("#{e}=", true) }
74
+ end
75
+
76
+ def remove(events)
77
+ events = [events] unless events.respond_to? :each
78
+ events.each { |e| send("#{e}=", false) }
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ module Voicemeeter
2
+ class Fx
3
+ include IRemote
4
+ attr_reader :reverb, :delay
5
+
6
+ def initialize(remote)
7
+ super
8
+ @reverb = FxReverb.new(remote)
9
+ @delay = FxDelay.new(remote)
10
+ end
11
+
12
+ def identifier
13
+ :fx
14
+ end
15
+ end
16
+
17
+ class FxReverb
18
+ include IRemote
19
+
20
+ def initialize(remote)
21
+ super
22
+ make_accessor_bool :on, :ab
23
+ end
24
+
25
+ def identifier
26
+ "fx.reverb"
27
+ end
28
+ end
29
+
30
+ class FxDelay
31
+ include IRemote
32
+
33
+ def initialize(remote)
34
+ super
35
+ make_accessor_bool :on, :ab
36
+ end
37
+
38
+ def identifier
39
+ "fx.delay"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,38 @@
1
+ module Voicemeeter
2
+ private
3
+
4
+ module Install
5
+ extend Logging
6
+
7
+ OS_BITS = (FFI::Platform::CPU.downcase == "x64") ? 64 : 32
8
+
9
+ def get_vmpath
10
+ reg_key = [
11
+ :Software,
12
+ ((OS_BITS == 64) ? :WOW6432Node : nil),
13
+ :Microsoft,
14
+ :Windows,
15
+ :CurrentVersion,
16
+ :Uninstall,
17
+ :"VB:Voicemeeter {17359A74-1236-5467}"
18
+ ]
19
+
20
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(
21
+ reg_key.compact.join("\\")
22
+ ) do |reg|
23
+ value = reg["UninstallString"]
24
+
25
+ Pathname.new(value).dirname
26
+ end
27
+ rescue Win32::Registry::Error => e
28
+ err_msg = [
29
+ "#{e.class.name}: #{e.message}",
30
+ *e.backtrace
31
+ ]
32
+ logger.error err_msg.join("\n")
33
+ raise Errors::VMInstallError.new "unable to read Voicemeeter path from the registry"
34
+ end
35
+
36
+ module_function :get_vmpath
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ module Voicemeeter
2
+ # Common interface with the base Remote class.
3
+ module IRemote
4
+ include Logging
5
+ include MetaFunctions
6
+
7
+ def initialize(remote, i = nil)
8
+ @remote = remote
9
+ @index = i
10
+ end
11
+
12
+ def to_s
13
+ "#{self.class.name.split("::").last}#{@index}#{@remote.kind}"
14
+ end
15
+
16
+ private
17
+
18
+ def getter(param, is_string = false)
19
+ logger.debug "getter: #{_cmd(param)}"
20
+ @remote.get(_cmd(param), is_string)
21
+ end
22
+
23
+ def setter(param, value)
24
+ logger.debug "setter: #{_cmd(param)}=#{value}"
25
+ @remote.set(_cmd(param), value)
26
+ end
27
+
28
+ def _cmd(param)
29
+ param.empty? ? identifier : "#{identifier}.#{param}"
30
+ end
31
+
32
+ def identifier
33
+ raise "Called abstract method: identifier"
34
+ end
35
+
36
+ public
37
+
38
+ def apply(params)
39
+ params.each do |key, val|
40
+ if val.is_a? Hash
41
+ target = send(key)
42
+ target.apply(val)
43
+ elsif key == :mode
44
+ mode.send("#{val}=", true)
45
+ else
46
+ send("#{key}=", val)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ module Voicemeeter
2
+ module Kinds
3
+ private
4
+
5
+ module KindEnum
6
+ BASIC = 1
7
+ BANANA = 2
8
+ POTATO = 3
9
+ POTATOX64 = 6
10
+ end
11
+
12
+ KindMap =
13
+ Data.define(:name, :ins, :outs, :vban, :asio, :insert, :num_buttons) do
14
+ def phys_in = ins.first
15
+
16
+ def virt_in = ins.last
17
+
18
+ def phys_out = outs.first
19
+
20
+ def virt_out = outs.last
21
+
22
+ def num_strip = ins.sum
23
+
24
+ def num_bus = outs.sum
25
+
26
+ def num_strip_levels = 2 * phys_in + 8 * virt_in
27
+
28
+ def num_bus_levels = 8 * (phys_out + virt_out)
29
+
30
+ def to_s = name.to_s.capitalize
31
+ end
32
+
33
+ basic = KindMap.new(:basic, [2, 1], [1, 1], [4, 4, 1, 1], [0, 0], 0, 80)
34
+
35
+ banana = KindMap.new(:banana, [3, 2], [3, 2], [8, 8, 1, 1], [6, 8], 22, 80)
36
+
37
+ potato = KindMap.new(:potato, [5, 3], [5, 3], [8, 8, 1, 1], [10, 8], 34, 80)
38
+
39
+ KIND_MAPS = [basic, banana, potato].to_h { |kind| [kind.name, kind] }
40
+
41
+ public
42
+
43
+ def get(kind_id)
44
+ KIND_MAPS.fetch(kind_id)
45
+ end
46
+
47
+ ALL = KIND_MAPS.values
48
+
49
+ module_function :get
50
+ end
51
+ end
@@ -0,0 +1,9 @@
1
+ module Voicemeeter
2
+ module Logging
3
+ def logger
4
+ @logger ||= Logger.new($stdout, level: ENV.fetch("VM_LOG_LEVEL", "WARN"))
5
+ @logger.progname = instance_of?(::Module) ? name : self.class.name
6
+ @logger
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,75 @@
1
+ module Voicemeeter
2
+ module MetaFunctions
3
+ private
4
+
5
+ # Accessor methods
6
+ def make_accessor_bool(*params)
7
+ params.each do |param|
8
+ define_singleton_method(param) { getter(param).to_i == 1 }
9
+
10
+ define_singleton_method("#{param}=") do |value|
11
+ setter(param, value && 1 || 0)
12
+ end
13
+ end
14
+ end
15
+
16
+ def make_accessor_string(*params)
17
+ params.each do |param|
18
+ define_singleton_method(param) { getter(param, true) }
19
+
20
+ define_singleton_method("#{param}=") { |value| setter(param, value) }
21
+ end
22
+ end
23
+
24
+ def make_accessor_int(*params)
25
+ params.each do |param|
26
+ define_singleton_method(param) { getter(param).to_i }
27
+
28
+ define_singleton_method("#{param}=") { |value| setter(param, value) }
29
+ end
30
+ end
31
+
32
+ def make_accessor_float(*params)
33
+ params.each do |param|
34
+ define_singleton_method(param) { getter(param) }
35
+
36
+ define_singleton_method("#{param}=") { |value| setter(param, value) }
37
+ end
38
+ end
39
+
40
+ # reader methods
41
+ def make_reader_string(*params)
42
+ params.each do |param|
43
+ define_singleton_method(param) { getter(param, true) }
44
+ end
45
+ end
46
+
47
+ def make_reader_int(*params)
48
+ params.each do |param|
49
+ define_singleton_method(param) { getter(param).to_i }
50
+ end
51
+ end
52
+
53
+ # writer methods
54
+ def make_writer_bool(*params)
55
+ params.each do |param|
56
+ define_singleton_method("#{param}=") do |value|
57
+ setter(param, value && 1 || 0)
58
+ end
59
+ end
60
+ end
61
+
62
+ def make_writer_string(*params)
63
+ params.each do |param|
64
+ define_singleton_method("#{param}=") { |value| setter(param, value) }
65
+ end
66
+ end
67
+
68
+ # methods for performing certain actions as opposed to setting values
69
+ def make_action_method(*params)
70
+ params.each do |param|
71
+ define_singleton_method(param) { setter(param, 1) }
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,14 @@
1
+ module Voicemeeter
2
+ class Midi
3
+ attr_accessor :current, :channel
4
+ attr_reader :cache
5
+
6
+ def initialize
7
+ @cache = {}
8
+ end
9
+
10
+ def get(key)
11
+ cache[key]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,83 @@
1
+ module Voicemeeter
2
+ module Mixins
3
+ module Fades
4
+ def fadeto(target, time)
5
+ setter("FadeTo", "(#{target}, #{time})")
6
+ sleep(@remote.delay)
7
+ end
8
+
9
+ def fadeby(change, time)
10
+ setter("FadeBy", "(#{change}, #{time})")
11
+ sleep(@remote.delay)
12
+ end
13
+ end
14
+
15
+ module Return
16
+ def initialize(remote, i)
17
+ super
18
+ make_accessor_float :returnreverb, :returndelay, :returnfx1, :returnfx2
19
+ end
20
+ end
21
+
22
+ module Apps
23
+ def appgain(name, gain)
24
+ setter("AppGain", "(\"#{name}\", #{gain})")
25
+ end
26
+
27
+ def appmute(name, mute)
28
+ setter("AppMute", "(\"#{name}\", #{mute ? 1 : 0})")
29
+ end
30
+ end
31
+
32
+ module Outputs
33
+ def initialize(*args)
34
+ super
35
+ remote, *_ = args
36
+ num_a, num_b = remote.kind.outs
37
+ channels =
38
+ (1..(num_a + num_b)).map do |i|
39
+ (i <= num_a) ? "A#{i}" : "B#{i - num_a}"
40
+ end
41
+ make_accessor_bool(*channels)
42
+ end
43
+ end
44
+
45
+ module Xy
46
+ module Pan
47
+ def initialize(remote, i)
48
+ super
49
+ make_accessor_float :pan_x, :pan_y
50
+ end
51
+ end
52
+
53
+ module Color
54
+ def initialize(remote, i)
55
+ super
56
+ make_accessor_float :color_x, :color_y
57
+ end
58
+ end
59
+
60
+ module Fx
61
+ def initialize(remote, i)
62
+ super
63
+ make_accessor_float :fx_x, :fx_y
64
+ end
65
+ end
66
+ end
67
+
68
+ module Fx
69
+ def initialize(remote, i)
70
+ super
71
+ make_accessor_float :reverb, :delay, :fx1, :fx2
72
+ make_accessor_bool :postreverb, :postdelay, :postfx1, :postfx2
73
+ end
74
+ end
75
+
76
+ module LevelEnum
77
+ PREFADER = 0
78
+ POSTFADER = 1
79
+ POSTMUTE = 2
80
+ BUS = 3
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,69 @@
1
+ module Voicemeeter
2
+ module Option
3
+ class Base
4
+ include IRemote
5
+ attr_reader :delay, :buffer, :mode
6
+
7
+ def initialize(remote)
8
+ super
9
+ make_accessor_int :sr
10
+ make_accessor_bool :asiosr, :monitoronsel, :slidermode
11
+
12
+ @delay = (0...remote.kind.phys_out).map { OptionDelay.new(remote, _1) }
13
+ @buffer = OptionBuffer.new(remote)
14
+ @mode = OptionMode.new(remote)
15
+ end
16
+
17
+ def identifier
18
+ :option
19
+ end
20
+ end
21
+
22
+ class OptionDelay
23
+ include IRemote
24
+
25
+ def initialize(remote, i)
26
+ super
27
+ make_accessor_bool :on, :ab
28
+ end
29
+
30
+ def identifier
31
+ "option.delay"
32
+ end
33
+
34
+ def get
35
+ getter("[#{@index}]").to_i
36
+ end
37
+
38
+ def set(val)
39
+ setter("[#{@index}]", val)
40
+ end
41
+ end
42
+
43
+ class OptionBuffer
44
+ include IRemote
45
+
46
+ def initialize(remote)
47
+ super
48
+ make_accessor_int :mme, :wdm, :ks, :asio
49
+ end
50
+
51
+ def identifier
52
+ "option.buffer"
53
+ end
54
+ end
55
+
56
+ class OptionMode
57
+ include IRemote
58
+
59
+ def initialize(remote)
60
+ super
61
+ make_accessor_bool :exclusif, :swift
62
+ end
63
+
64
+ def identifier
65
+ "option.mode"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,79 @@
1
+ module Voicemeeter
2
+ module Patch
3
+ # Base class for Patch
4
+ class Base
5
+ include IRemote
6
+ attr_reader :asio, :A2, :A3, :A4, :A5, :composite, :insert
7
+
8
+ def initialize(remote)
9
+ super
10
+ make_accessor_bool :postfadercomposite, :postfxinsert
11
+
12
+ asio_in, asio_out = remote.kind.asio
13
+ @asio = (0...asio_in).map { PatchAsioIn.new(remote, _1) }
14
+ %i[A2 A3 A4 A5].each do |param|
15
+ instance_variable_set("@#{param}", (0...asio_out).map { PatchAsioOut.new(remote, _1, param) })
16
+ end
17
+ @composite = (0...8).map { PatchComposite.new(remote, _1) }
18
+ @insert = (0...remote.kind.insert).map { PatchInsert.new(remote, _1) }
19
+ end
20
+ end
21
+
22
+ class PatchAsio
23
+ include IRemote
24
+
25
+ def identifier
26
+ :patch
27
+ end
28
+ end
29
+
30
+ class PatchAsioIn < PatchAsio
31
+ def get
32
+ getter("asio[#{@index}]").to_i
33
+ end
34
+
35
+ def set(val)
36
+ setter("asio[#{@index}]", val)
37
+ end
38
+ end
39
+
40
+ class PatchAsioOut < PatchAsio
41
+ def initialize(remote, i, param)
42
+ super(remote, i)
43
+ @param = param
44
+ end
45
+
46
+ def get
47
+ getter("out#{@param}[#{@index}]").to_i
48
+ end
49
+
50
+ def set(val)
51
+ setter("out#{@param}[#{@index}]", val)
52
+ end
53
+ end
54
+
55
+ class PatchComposite
56
+ include IRemote
57
+
58
+ def get
59
+ getter("composite[#{@index}]").to_i
60
+ end
61
+
62
+ def set(val)
63
+ setter("composite[#{@index}]", val)
64
+ end
65
+ end
66
+
67
+ class PatchInsert
68
+ include IRemote
69
+
70
+ def get
71
+ getter("insert[#{@index}]").to_i == 1
72
+ end
73
+
74
+ def set(val)
75
+ setter("insert[#{@index}]", val && 1 || 0)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,90 @@
1
+ module Voicemeeter
2
+ module Recorder
3
+ module FileTypeEnum
4
+ WAV = 1
5
+ AIFF = 2
6
+ BWF = 3
7
+ MP3 = 100
8
+ end
9
+
10
+ # Base class for Recorder
11
+ class Base
12
+ include IRemote
13
+ include Mixins::Outputs
14
+
15
+ attr_reader :mode, :armstrip, :armbus
16
+
17
+ def initialize(remote)
18
+ super
19
+ make_action_method :play, :stop, :pause, :replay, :record, :ff, :rew
20
+ make_accessor_int :bitresolution, :channel, :kbps
21
+ make_accessor_float :gain
22
+
23
+ @mode = RecorderMode.new(remote)
24
+ @armstrip = (0...remote.kind.num_strip).map { RecorderArmStrip.new(remote, _1) }
25
+ @armbus = (0...remote.kind.num_bus).map { RecorderArmBus.new(remote, _1) }
26
+ end
27
+
28
+ def identifier
29
+ :recorder
30
+ end
31
+
32
+ def load(filepath)
33
+ setter("load", filepath)
34
+ end
35
+
36
+ def goto(timestr)
37
+ unless /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/.match?(timestr)
38
+ logger.error "goto got: '#{timestr}', but expects a time string in the format 'hh:mm:ss'"
39
+ return
40
+ end
41
+ dt = DateTime.parse(timestr)
42
+ seconds = dt.hour * 3600 + dt.min * 60 + dt.second
43
+ setter("goto", seconds)
44
+ end
45
+
46
+ def filetype(val)
47
+ opts = {wav: FileTypeEnum::WAV, aiff: FileTypeEnum::AIFF, bwf: FileTypeEnum::BWF, mp3: FileTypeEnum::MP3}
48
+ setter("filetype", opts[val])
49
+ end
50
+ end
51
+
52
+ class RecorderMode
53
+ include IRemote
54
+
55
+ def initialize(remote)
56
+ super
57
+ make_accessor_bool :recbus, :playonload, :loop, :multitrack
58
+ end
59
+
60
+ def identifier
61
+ "recorder.mode"
62
+ end
63
+ end
64
+
65
+ class RecorderArmChannel
66
+ include IRemote
67
+
68
+ def initialize(remote, j)
69
+ super(remote)
70
+ @j = j
71
+ end
72
+
73
+ def set
74
+ setter("", val && 1 || 0)
75
+ end
76
+ end
77
+
78
+ class RecorderArmStrip < RecorderArmChannel
79
+ def identifier
80
+ "recorder.armstrip[#{@j}]"
81
+ end
82
+ end
83
+
84
+ class RecorderArmBus < RecorderArmChannel
85
+ def identifier
86
+ "recorder.armbus[#{@j}]"
87
+ end
88
+ end
89
+ end
90
+ end