Module: Deftones::PortAudioSupport

Defined in:
lib/deftones/portaudio_support.rb

Class Method Summary collapse

Class Method Details

.acquire!Object



13
14
15
16
17
18
19
20
21
22
23
# File 'lib/deftones/portaudio_support.rb', line 13

def acquire!
  raise Deftones::MissingRealtimeBackendError, "PortAudio backend is unavailable" unless available?

  mutex.synchronize do
    PortAudio.init if ref_count.zero?
    @ref_count = ref_count + 1
  end
  self
rescue StandardError => error
  raise Deftones::MissingRealtimeBackendError, error.message
end

.available?Boolean

Returns:

  • (Boolean)


8
9
10
11
# File 'lib/deftones/portaudio_support.rb', line 8

def available?
  load_backend!
  !!defined?(PortAudio)
end

.build_stream_parameters(direction:, channels:, device_id: nil, label: nil, sample_rate: nil) ⇒ Object (private)



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/deftones/portaudio_support.rb', line 84

def build_stream_parameters(direction:, channels:, device_id: nil, label: nil, sample_rate: nil)
  device =
    resolve_device(direction: direction, device_id: device_id, label: label)

  raise Deftones::MissingRealtimeBackendError, "No default #{direction} device available" unless device
  detect_sample_rate_mismatch!(device, sample_rate) if sample_rate

  {
    device: device,
    channels: channels,
    format: :float32,
    latency: suggested_latency(device, direction)
  }
rescue StandardError => error
  raise Deftones::MissingRealtimeBackendError, error.message
end

.check_error!(result, fallback: nil) ⇒ Object



59
60
61
62
63
# File 'lib/deftones/portaudio_support.rb', line 59

def check_error!(result, fallback: nil)
  PortAudio.check_error!(result)
rescue StandardError => error
  raise Deftones::MissingRealtimeBackendError, fallback || error.message
end

.default_device(direction) ⇒ Object (private)



121
122
123
124
125
126
127
# File 'lib/deftones/portaudio_support.rb', line 121

def default_device(direction)
  case direction
  when :input then PortAudio::Device.default_input
  when :output then PortAudio::Device.default_output
  else raise ArgumentError, "Unsupported PortAudio direction: #{direction}"
  end
end

.detect_sample_rate_mismatch!(device, requested_sample_rate) ⇒ Object (private)



156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/deftones/portaudio_support.rb', line 156

def detect_sample_rate_mismatch!(device, requested_sample_rate)
  device_sample_rate =
    if device.respond_to?(:default_sample_rate)
      device.default_sample_rate
    elsif device.respond_to?(:sample_rate)
      device.sample_rate
    end
  return unless device_sample_rate
  return if device_sample_rate.to_f == requested_sample_rate.to_f

  raise Deftones::MissingRealtimeBackendError,
        "PortAudio device sample rate #{device_sample_rate} does not match requested #{requested_sample_rate}"
end

.input_parameters(channels, device_id: nil, label: nil, sample_rate: nil) ⇒ Object



49
50
51
52
53
54
55
56
57
# File 'lib/deftones/portaudio_support.rb', line 49

def input_parameters(channels, device_id: nil, label: nil, sample_rate: nil)
  build_stream_parameters(
    direction: :input,
    channels: channels,
    device_id: device_id,
    label: label,
    sample_rate: sample_rate
  )
end

.load_backend!Object (private)



67
68
69
70
71
72
73
74
# File 'lib/deftones/portaudio_support.rb', line 67

def load_backend!
  return true if defined?(PortAudio)

  require "portaudio"
  true
rescue LoadError
  false
end

.matches_device_id?(device, device_id) ⇒ Boolean (private)

Returns:

  • (Boolean)


129
130
131
132
133
134
135
136
137
# File 'lib/deftones/portaudio_support.rb', line 129

def matches_device_id?(device, device_id)
  return false if device_id.nil?

  candidates = []
  candidates << device.device_id if device.respond_to?(:device_id)
  candidates << device.index if device.respond_to?(:index)
  candidates << device.device_index if device.respond_to?(:device_index)
  candidates.compact.any? { |candidate| candidate.to_s == device_id.to_s }
end

.matches_device_label?(device, label) ⇒ Boolean (private)

Returns:

  • (Boolean)


139
140
141
142
143
144
145
146
147
# File 'lib/deftones/portaudio_support.rb', line 139

def matches_device_label?(device, label)
  return false if label.nil?

  candidates = []
  candidates << device.label if device.respond_to?(:label)
  candidates << device.name if device.respond_to?(:name)
  matcher = label.is_a?(Regexp) ? label : Regexp.new(Regexp.escape(label.to_s), Regexp::IGNORECASE)
  candidates.compact.any? { |candidate| candidate.to_s.match?(matcher) }
end

.mutexObject (private)



76
77
78
# File 'lib/deftones/portaudio_support.rb', line 76

def mutex
  @mutex ||= Mutex.new
end

.output_parameters(channels, device_id: nil, label: nil, sample_rate: nil) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/deftones/portaudio_support.rb', line 39

def output_parameters(channels, device_id: nil, label: nil, sample_rate: nil)
  build_stream_parameters(
    direction: :output,
    channels: channels,
    device_id: device_id,
    label: label,
    sample_rate: sample_rate
  )
end

.ref_countObject (private)



80
81
82
# File 'lib/deftones/portaudio_support.rb', line 80

def ref_count
  @ref_count ||= 0
end

.releaseObject



25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/deftones/portaudio_support.rb', line 25

def release
  return unless available?

  mutex.synchronize do
    return self if ref_count.zero?

    @ref_count = ref_count - 1
    PortAudio.terminate if ref_count.zero?
  end
  self
rescue StandardError
  nil
end

.resolve_device(direction:, device_id:, label:) ⇒ Object (private)



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/deftones/portaudio_support.rb', line 101

def resolve_device(direction:, device_id:, label:)
  return default_device(direction) if device_id.nil? && label.nil?

  devices =
    if PortAudio::Device.respond_to?(:all)
      Array(PortAudio::Device.all)
    elsif PortAudio::Device.respond_to?(:devices)
      Array(PortAudio::Device.devices)
    else
      []
    end

  matched = devices.find do |device|
    matches_device_id?(device, device_id) || matches_device_label?(device, label)
  end
  return matched if matched

  raise Deftones::MissingRealtimeBackendError, "No matching #{direction} device available"
end

.suggested_latency(device, direction) ⇒ Object (private)



149
150
151
152
153
154
# File 'lib/deftones/portaudio_support.rb', line 149

def suggested_latency(device, direction)
  method_name = direction == :input ? :default_low_input_latency : :default_low_output_latency
  return device.public_send(method_name) if device.respond_to?(method_name)

  0.05
end