2023-07-07 02:30:49 +01:00
|
|
|
require_relative "install"
|
|
|
|
require_relative "cbindings"
|
|
|
|
require_relative "kinds"
|
|
|
|
require_relative "midi"
|
2023-07-14 01:49:26 +01:00
|
|
|
require_relative "event"
|
|
|
|
require_relative "worker"
|
2023-07-16 11:08:24 +01:00
|
|
|
require_relative "errors"
|
2023-07-17 14:13:08 +01:00
|
|
|
require_relative "util"
|
2023-07-16 11:08:24 +01:00
|
|
|
require_relative "logger"
|
2023-07-07 02:30:49 +01:00
|
|
|
|
|
|
|
module Voicemeeter
|
|
|
|
class Base
|
2023-07-27 10:58:26 +01:00
|
|
|
# Base class for Remote types
|
2023-07-16 11:08:24 +01:00
|
|
|
include Logging
|
2023-07-14 01:49:26 +01:00
|
|
|
include Worker
|
2023-07-28 18:51:15 +01:00
|
|
|
include Events::Callback
|
2023-07-26 14:12:47 +01:00
|
|
|
prepend Util::Cache
|
2023-07-09 05:48:08 +01:00
|
|
|
|
2023-08-05 23:10:51 +01:00
|
|
|
attr_reader :kind, :midi, :event, :delay, :cache
|
2023-07-14 01:49:26 +01:00
|
|
|
|
|
|
|
RATELIMIT = 0.033
|
2023-07-17 19:59:49 +01:00
|
|
|
DELAY = 0.001
|
2023-07-07 02:30:49 +01:00
|
|
|
|
|
|
|
def initialize(kind, **kwargs)
|
|
|
|
@kind = kind
|
|
|
|
@sync = kwargs[:sync] || false
|
2023-07-14 01:49:26 +01:00
|
|
|
@ratelimit = kwargs[:ratelimit] || RATELIMIT
|
2023-07-17 19:59:49 +01:00
|
|
|
@delay = kwargs[:delay] || DELAY
|
2023-07-14 01:49:26 +01:00
|
|
|
@event =
|
|
|
|
Events::Tracker.new(
|
|
|
|
**(kwargs.select { |k, _| %i[pdirty mdirty ldirty midi].include? k })
|
|
|
|
)
|
2023-07-25 10:03:43 +01:00
|
|
|
@midi = Midi.new
|
2023-07-14 11:44:49 +01:00
|
|
|
@cache = {strip_mode: 0}
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
2023-07-09 05:48:08 +01:00
|
|
|
def to_s
|
2023-07-14 01:49:26 +01:00
|
|
|
"Voicemeeter #{kind}"
|
2023-07-09 05:48:08 +01:00
|
|
|
end
|
|
|
|
|
2023-07-07 02:30:49 +01:00
|
|
|
def login
|
2023-07-31 15:16:19 +01:00
|
|
|
CBindings.call(:bind_login, ok: [0, 1]) == 1 and run_voicemeeter(kind.name)
|
2023-07-07 02:30:49 +01:00
|
|
|
clear_dirty
|
2023-07-09 05:48:08 +01:00
|
|
|
logger.info "Successfully logged into #{self} version #{version}"
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def logout
|
|
|
|
sleep(0.1)
|
|
|
|
CBindings.call(:bind_logout)
|
2023-07-23 11:21:50 +01:00
|
|
|
logger.info "Successfully logged out of #{self}"
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def pdirty?
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_is_parameters_dirty, ok: [0, 1]) == 1
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def mdirty?
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_macro_button_is_dirty, ok: [0, 1]) == 1
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
2023-07-14 01:49:26 +01:00
|
|
|
def ldirty?
|
|
|
|
cache[:strip_buf], cache[:bus_buf] = _get_levels
|
|
|
|
!(
|
|
|
|
cache[:strip_level] == cache[:strip_buf] &&
|
2023-07-14 21:03:40 +01:00
|
|
|
cache[:bus_level] == cache[:bus_buf]
|
2023-07-14 01:49:26 +01:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-07-07 02:30:49 +01:00
|
|
|
def clear_dirty
|
2023-08-02 16:37:35 +01:00
|
|
|
catch(:clear) do
|
|
|
|
loop { throw(:clear) unless pdirty? || mdirty? }
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-07-16 11:08:24 +01:00
|
|
|
def run_voicemeeter(kind_id)
|
2023-07-07 02:30:49 +01:00
|
|
|
kinds = {
|
|
|
|
basic: Kinds::KindEnum::BASIC,
|
|
|
|
banana: Kinds::KindEnum::BANANA,
|
2023-07-17 08:41:03 +01:00
|
|
|
potato: (Install::OS_BITS == 64) ? Kinds::KindEnum::POTATOX64 : Kinds::KindEnum::POTATO
|
2023-07-07 02:30:49 +01:00
|
|
|
}
|
2023-07-31 15:16:19 +01:00
|
|
|
if caller(1..1).first[/`(.*)'/, 1] == "login"
|
2023-08-01 23:41:23 +01:00
|
|
|
logger.debug "Voicemeeter engine running but the GUI appears to be down... launching."
|
2023-07-31 15:16:19 +01:00
|
|
|
end
|
2023-07-16 11:08:24 +01:00
|
|
|
CBindings.call(:bind_run_voicemeeter, kinds[kind_id])
|
2023-07-07 02:30:49 +01:00
|
|
|
sleep(1)
|
|
|
|
end
|
|
|
|
|
|
|
|
def type
|
|
|
|
ckind = FFI::MemoryPointer.new(:long, 1)
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_get_voicemeeter_type, ckind)
|
2023-08-10 20:37:47 +01:00
|
|
|
kinds = [nil, :basic, :banana, :potato]
|
2023-07-07 02:30:49 +01:00
|
|
|
kinds[ckind.read_long]
|
|
|
|
end
|
|
|
|
|
|
|
|
def version
|
|
|
|
cver = FFI::MemoryPointer.new(:long, 1)
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_get_voicemeeter_version, cver)
|
2023-07-07 02:30:49 +01:00
|
|
|
[
|
|
|
|
(cver.read_long & 0xFF000000) >> 24,
|
|
|
|
(cver.read_long & 0x00FF0000) >> 16,
|
|
|
|
(cver.read_long & 0x0000FF00) >> 8,
|
|
|
|
cver.read_long & 0x000000FF
|
|
|
|
].join(".")
|
|
|
|
end
|
|
|
|
|
|
|
|
def get(name, is_string = false)
|
2023-07-26 14:12:47 +01:00
|
|
|
if is_string
|
|
|
|
cget = FFI::MemoryPointer.new(:string, 512, true)
|
|
|
|
CBindings.call(:bind_get_parameter_string_a, name, cget)
|
|
|
|
cget.read_string
|
|
|
|
else
|
|
|
|
cget = FFI::MemoryPointer.new(:float, 1)
|
|
|
|
CBindings.call(:bind_get_parameter_float, name, cget)
|
|
|
|
cget.read_float.round(1)
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def set(name, value)
|
|
|
|
if value.is_a? String
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_set_parameter_string_a, name, value)
|
2023-07-07 02:30:49 +01:00
|
|
|
else
|
|
|
|
CBindings.call(:bind_set_parameter_float, name, value.to_f)
|
|
|
|
end
|
2023-07-17 14:13:08 +01:00
|
|
|
cache.store(name, value)
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def get_buttonstatus(id, mode)
|
2023-07-26 14:12:47 +01:00
|
|
|
cget = FFI::MemoryPointer.new(:float, 1)
|
|
|
|
CBindings.call(:bind_macro_button_get_status, id, cget, mode)
|
|
|
|
cget.read_float.to_i
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
2023-07-17 14:13:08 +01:00
|
|
|
def set_buttonstatus(id, mode, state)
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_macro_button_set_status, id, state, mode)
|
2023-07-17 14:13:08 +01:00
|
|
|
cache.store("mb_#{id}_#{mode}", state)
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
|
2023-07-14 01:49:26 +01:00
|
|
|
def get_level(mode, index)
|
2023-07-07 02:30:49 +01:00
|
|
|
cget = FFI::MemoryPointer.new(:float, 1)
|
2023-07-14 01:49:26 +01:00
|
|
|
CBindings.call(:bind_get_level, mode, index, cget)
|
2023-07-07 02:30:49 +01:00
|
|
|
cget.read_float
|
|
|
|
end
|
|
|
|
|
2023-07-16 11:08:24 +01:00
|
|
|
private def _get_levels
|
2023-07-22 13:26:19 +01:00
|
|
|
strip_mode = cache[:strip_mode]
|
2023-07-14 01:49:26 +01:00
|
|
|
[
|
2023-07-22 13:26:19 +01:00
|
|
|
(0...kind.num_strip_levels).map { get_level(strip_mode, _1) },
|
|
|
|
(0...kind.num_bus_levels).map { get_level(3, _1) }
|
2023-07-14 01:49:26 +01:00
|
|
|
]
|
|
|
|
end
|
|
|
|
|
2023-07-07 02:30:49 +01:00
|
|
|
def get_num_devices(dir)
|
|
|
|
unless %i[in out].include? dir
|
2023-07-16 22:33:50 +01:00
|
|
|
raise Errors::VMError.new "dir got: #{dir}, expected :in or :out"
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
if dir == :in
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_input_get_device_number, exp: ->(x) { x >= 0 })
|
2023-07-07 02:30:49 +01:00
|
|
|
else
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(:bind_output_get_device_number, exp: ->(x) { x >= 0 })
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_device_description(index, dir)
|
2023-07-15 00:16:10 +01:00
|
|
|
unless %i[in out].include? dir
|
2023-07-16 22:33:50 +01:00
|
|
|
raise Errors::VMError.new "dir got: #{dir}, expected :in or :out"
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
ctype = FFI::MemoryPointer.new(:long, 1)
|
|
|
|
cname = FFI::MemoryPointer.new(:string, 256, true)
|
|
|
|
chwid = FFI::MemoryPointer.new(:string, 256, true)
|
|
|
|
if dir == :in
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(
|
|
|
|
:bind_input_get_device_desc_a,
|
|
|
|
index,
|
|
|
|
ctype,
|
|
|
|
cname,
|
|
|
|
chwid
|
|
|
|
)
|
2023-07-07 02:30:49 +01:00
|
|
|
else
|
2023-07-09 23:48:35 +01:00
|
|
|
CBindings.call(
|
|
|
|
:bind_output_get_device_desc_a,
|
|
|
|
index,
|
|
|
|
ctype,
|
|
|
|
cname,
|
|
|
|
chwid
|
|
|
|
)
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
[cname.read_string, ctype.read_long, chwid.read_string]
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_midi_message
|
|
|
|
cmsg = FFI::MemoryPointer.new(:string, 1024, true)
|
|
|
|
res =
|
|
|
|
CBindings.call(
|
|
|
|
:bind_get_midi_message,
|
|
|
|
cmsg,
|
|
|
|
1024,
|
2023-07-14 01:49:26 +01:00
|
|
|
ok: [-5, -6],
|
2023-07-07 02:30:49 +01:00
|
|
|
exp: ->(x) { x >= 0 }
|
|
|
|
)
|
|
|
|
if (got_midi = res > 0)
|
2023-07-29 18:11:52 +01:00
|
|
|
data = cmsg.read_bytes(res).bytes
|
|
|
|
data.each_slice(3) do |ch, key, velocity|
|
|
|
|
midi.channel = ch
|
|
|
|
midi.current = key
|
|
|
|
midi.cache[key] = velocity
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
got_midi
|
|
|
|
end
|
2023-07-09 05:48:08 +01:00
|
|
|
|
2023-08-02 14:57:28 +01:00
|
|
|
def sendtext(script)
|
|
|
|
raise ArgumentError, "script must not exceed 48kB" if script.length > 48000
|
|
|
|
CBindings.call(:bind_set_parameters, script)
|
|
|
|
end
|
|
|
|
|
2023-07-09 05:48:08 +01:00
|
|
|
def apply(data)
|
2023-08-10 20:37:47 +01:00
|
|
|
data.each do |key, hash|
|
|
|
|
case key.to_s.split("-")
|
|
|
|
in [/strip|bus|button/ => kls, /^[0-9]+$/ => index]
|
2023-07-14 01:49:26 +01:00
|
|
|
target = send(kls)
|
2023-08-10 21:57:03 +01:00
|
|
|
in ["vban", /in|instream|out|oustream/ => dir, /^[0-9]+$/ => index]
|
2023-08-10 20:37:47 +01:00
|
|
|
target = vban.send("#{dir.chomp("stream")}stream")
|
2023-08-09 17:43:52 +01:00
|
|
|
else
|
2023-08-10 20:37:47 +01:00
|
|
|
raise KeyError, "invalid config key '#{key}'"
|
2023-07-09 05:48:08 +01:00
|
|
|
end
|
2023-08-10 20:37:47 +01:00
|
|
|
target[index.to_i].apply(hash)
|
2023-07-09 05:48:08 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def apply_config(name)
|
2023-07-14 01:49:26 +01:00
|
|
|
apply(configs[name])
|
2023-07-09 23:48:35 +01:00
|
|
|
logger.info "profile #{name} applied!"
|
2023-07-09 05:48:08 +01:00
|
|
|
end
|
2023-07-07 02:30:49 +01:00
|
|
|
end
|
|
|
|
end
|