mirror of
				https://github.com/onyx-and-iris/voicemeeter-rb.git
				synced 2025-11-04 06:01:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			226 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
module Voicemeeter
 | 
						|
  # Base class for Remote
 | 
						|
  class Base
 | 
						|
    include Logging
 | 
						|
    include Worker
 | 
						|
    include Events::Director
 | 
						|
    prepend Util::Cache
 | 
						|
 | 
						|
    attr_reader :kind, :midi, :event, :delay, :cache
 | 
						|
 | 
						|
    RATELIMIT = 0.033
 | 
						|
    DELAY = 0.001
 | 
						|
 | 
						|
    def initialize(kind, **kwargs)
 | 
						|
      @kind = kind
 | 
						|
      @sync = kwargs[:sync] || false
 | 
						|
      @ratelimit = kwargs[:ratelimit] || RATELIMIT
 | 
						|
      @delay = kwargs[:delay] || DELAY
 | 
						|
      @event =
 | 
						|
        Events::Tracker.new(
 | 
						|
          **(kwargs.select { |k, _| %i[pdirty mdirty ldirty midi].include? k })
 | 
						|
        )
 | 
						|
      @midi = Midi.new
 | 
						|
      @cache = {strip_mode: 0}
 | 
						|
    end
 | 
						|
 | 
						|
    def to_s
 | 
						|
      "Voicemeeter #{kind}"
 | 
						|
    end
 | 
						|
 | 
						|
    def login
 | 
						|
      CBindings.call(:bind_login, ok: [0, 1]) == 1 and run_voicemeeter(kind.name)
 | 
						|
      clear_dirty
 | 
						|
      logger.info "Successfully logged into #{self} version #{version}"
 | 
						|
    end
 | 
						|
 | 
						|
    def logout
 | 
						|
      sleep(0.1)
 | 
						|
      CBindings.call(:bind_logout)
 | 
						|
      logger.info "Successfully logged out of #{self}"
 | 
						|
    end
 | 
						|
 | 
						|
    def pdirty?
 | 
						|
      CBindings.call(:bind_is_parameters_dirty, ok: [0, 1]) == 1
 | 
						|
    end
 | 
						|
 | 
						|
    def mdirty?
 | 
						|
      CBindings.call(:bind_macro_button_is_dirty, ok: [0, 1]) == 1
 | 
						|
    end
 | 
						|
 | 
						|
    def ldirty?
 | 
						|
      cache[:strip_buf], cache[:bus_buf] = _get_levels
 | 
						|
      !(
 | 
						|
        cache[:strip_level] == cache[:strip_buf] &&
 | 
						|
          cache[:bus_level] == cache[:bus_buf]
 | 
						|
      )
 | 
						|
    end
 | 
						|
 | 
						|
    def clear_dirty
 | 
						|
      catch(:clear) do
 | 
						|
        loop { throw(:clear) unless pdirty? || mdirty? }
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def run_voicemeeter(kind_id)
 | 
						|
      kinds = {
 | 
						|
        basic: Kinds::KindEnum::BASIC,
 | 
						|
        banana: Kinds::KindEnum::BANANA,
 | 
						|
        potato: (Install::OS_BITS == 64) ? Kinds::KindEnum::POTATOX64 : Kinds::KindEnum::POTATO
 | 
						|
      }
 | 
						|
      if caller(1..1).first[/`(.*)'/, 1] == "login"
 | 
						|
        logger.debug "Voicemeeter engine running but the GUI appears to be down... launching."
 | 
						|
      end
 | 
						|
      CBindings.call(:bind_run_voicemeeter, kinds[kind_id])
 | 
						|
      sleep(1)
 | 
						|
    end
 | 
						|
 | 
						|
    def type
 | 
						|
      ckind = FFI::MemoryPointer.new(:long, 1)
 | 
						|
      CBindings.call(:bind_get_voicemeeter_type, ckind)
 | 
						|
      kinds = [nil, :basic, :banana, :potato]
 | 
						|
      kinds[ckind.read_long]
 | 
						|
    end
 | 
						|
 | 
						|
    def version
 | 
						|
      cver = FFI::MemoryPointer.new(:long, 1)
 | 
						|
      CBindings.call(:bind_get_voicemeeter_version, cver)
 | 
						|
      [
 | 
						|
        (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)
 | 
						|
      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)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def set(name, value)
 | 
						|
      if value.is_a? String
 | 
						|
        CBindings.call(:bind_set_parameter_string_a, name, value)
 | 
						|
      else
 | 
						|
        CBindings.call(:bind_set_parameter_float, name, value.to_f)
 | 
						|
      end
 | 
						|
      cache.store(name, value)
 | 
						|
    end
 | 
						|
 | 
						|
    def get_buttonstatus(id, mode)
 | 
						|
      cget = FFI::MemoryPointer.new(:float, 1)
 | 
						|
      CBindings.call(:bind_macro_button_get_status, id, cget, mode)
 | 
						|
      cget.read_float.to_i
 | 
						|
    end
 | 
						|
 | 
						|
    def set_buttonstatus(id, mode, state)
 | 
						|
      CBindings.call(:bind_macro_button_set_status, id, state, mode)
 | 
						|
      cache.store("mb_#{id}_#{mode}", state)
 | 
						|
    end
 | 
						|
 | 
						|
    def get_level(mode, index)
 | 
						|
      cget = FFI::MemoryPointer.new(:float, 1)
 | 
						|
      CBindings.call(:bind_get_level, mode, index, cget)
 | 
						|
      cget.read_float
 | 
						|
    end
 | 
						|
 | 
						|
    private def _get_levels
 | 
						|
      strip_mode = cache[:strip_mode]
 | 
						|
      [
 | 
						|
        (0...kind.num_strip_levels).map { get_level(strip_mode, _1) },
 | 
						|
        (0...kind.num_bus_levels).map { get_level(3, _1) }
 | 
						|
      ]
 | 
						|
    end
 | 
						|
 | 
						|
    def get_num_devices(dir)
 | 
						|
      unless %i[in out].include? dir
 | 
						|
        raise Errors::VMError.new "dir got: #{dir}, expected :in or :out"
 | 
						|
      end
 | 
						|
      if dir == :in
 | 
						|
        CBindings.call(:bind_input_get_device_number, exp: ->(x) { x >= 0 })
 | 
						|
      else
 | 
						|
        CBindings.call(:bind_output_get_device_number, exp: ->(x) { x >= 0 })
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def get_device_description(index, dir)
 | 
						|
      unless %i[in out].include? dir
 | 
						|
        raise Errors::VMError.new "dir got: #{dir}, expected :in or :out"
 | 
						|
      end
 | 
						|
      ctype = FFI::MemoryPointer.new(:long, 1)
 | 
						|
      cname = FFI::MemoryPointer.new(:string, 256, true)
 | 
						|
      chwid = FFI::MemoryPointer.new(:string, 256, true)
 | 
						|
      if dir == :in
 | 
						|
        CBindings.call(
 | 
						|
          :bind_input_get_device_desc_a,
 | 
						|
          index,
 | 
						|
          ctype,
 | 
						|
          cname,
 | 
						|
          chwid
 | 
						|
        )
 | 
						|
      else
 | 
						|
        CBindings.call(
 | 
						|
          :bind_output_get_device_desc_a,
 | 
						|
          index,
 | 
						|
          ctype,
 | 
						|
          cname,
 | 
						|
          chwid
 | 
						|
        )
 | 
						|
      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,
 | 
						|
          ok: [-5, -6],
 | 
						|
          exp: ->(x) { x >= 0 }
 | 
						|
        )
 | 
						|
      if (got_midi = res > 0)
 | 
						|
        data = cmsg.read_bytes(res).bytes
 | 
						|
        data.each_slice(3) do |ch, key, velocity|
 | 
						|
          midi.channel = ch
 | 
						|
          midi.current = key
 | 
						|
          midi.cache[key] = velocity
 | 
						|
        end
 | 
						|
      end
 | 
						|
      got_midi
 | 
						|
    end
 | 
						|
 | 
						|
    def sendtext(script)
 | 
						|
      raise ArgumentError, "script must not exceed 48kB" if script.length > 48000
 | 
						|
      CBindings.call(:bind_set_parameters, script)
 | 
						|
    end
 | 
						|
 | 
						|
    def apply(data)
 | 
						|
      data.each do |key, hash|
 | 
						|
        case key.to_s.split("-")
 | 
						|
        in [/strip|bus|button/ => kls, /^[0-9]+$/ => index]
 | 
						|
          target = send(kls)
 | 
						|
        in ["vban", /in|instream|out|oustream/ => dir, /^[0-9]+$/ => index]
 | 
						|
          target = vban.send("#{dir.chomp("stream")}stream")
 | 
						|
        else
 | 
						|
          raise KeyError, "invalid config key '#{key}'"
 | 
						|
        end
 | 
						|
        target[index.to_i].apply(hash)
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    def apply_config(name)
 | 
						|
      apply(configs[name])
 | 
						|
      logger.info "profile #{name} applied!"
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |