Class: Deftones::Component::Panner3D

Inherits:
Deftones::Core::AudioNode show all
Defined in:
lib/deftones/component/panner3d.rb

Instance Attribute Summary collapse

Attributes inherited from Deftones::Core::AudioNode

#context, #input

Instance Method Summary collapse

Methods inherited from Deftones::Core::AudioNode

#>>, #attach_destination, #attach_source, #block_time, #chain, #channel_count, #channel_count_mode, #channel_interpretation, #connect, #connected?, #default_input_channels, #default_output_channels, #destination_for_connection, #detach_all_destinations, #detach_destination, #detach_source, #disconnect, #dispose, #disposed?, #fan, #get, #immediate, #input_for_index, #inputs, #mix_source_blocks, #name, #normalize_connection_index, #normalize_output_block, #now, #number_of_inputs, #number_of_outputs, #output, #output_for_connection, #output_for_index, #outputs, #raise_connection_index_error!, #reaches_node?, #render_block, #sample_time, #set, #to_destination, #to_frequency, #to_master, #to_midi, #to_output, #to_s, #to_seconds, #to_ticks, #validate_connectable!, #validate_connection_index!

Constructor Details

#initialize(position_x: 0.0, position_y: 0.0, position_z: 0.0, orientation_x: 1.0, orientation_y: 0.0, orientation_z: 0.0, panning_model: :equal_power, distance_model: :inverse, ref_distance: 1.0, rolloff_factor: 1.0, max_distance: 10_000.0, cone_inner_angle: 360.0, cone_outer_angle: 360.0, cone_outer_gain: 0.0, listener: Deftones.listener, context: Deftones.context) ⇒ Panner3D

Returns a new instance of Panner3D.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/deftones/component/panner3d.rb', line 10

def initialize(position_x: 0.0, position_y: 0.0, position_z: 0.0,
               orientation_x: 1.0, orientation_y: 0.0, orientation_z: 0.0,
               panning_model: :equal_power, distance_model: :inverse,
               ref_distance: 1.0, rolloff_factor: 1.0, max_distance: 10_000.0,
               cone_inner_angle: 360.0, cone_outer_angle: 360.0, cone_outer_gain: 0.0,
               listener: Deftones.listener, context: Deftones.context)
  super(context: context)
  @listener = listener
  @position_x = Core::Signal.new(value: position_x, units: :number, context: context)
  @position_y = Core::Signal.new(value: position_y, units: :number, context: context)
  @position_z = Core::Signal.new(value: position_z, units: :number, context: context)
  @orientation_x = Core::Signal.new(value: orientation_x, units: :number, context: context)
  @orientation_y = Core::Signal.new(value: orientation_y, units: :number, context: context)
  @orientation_z = Core::Signal.new(value: orientation_z, units: :number, context: context)
  @panning_model = panning_model.to_sym
  @distance_model = distance_model.to_sym
  @ref_distance = ref_distance.to_f
  @rolloff_factor = rolloff_factor.to_f
  @max_distance = max_distance.to_f
  @cone_inner_angle = cone_inner_angle.to_f
  @cone_outer_angle = cone_outer_angle.to_f
  @cone_outer_gain = cone_outer_gain.to_f
  @hrtf_delay_lines = []
end

Instance Attribute Details

#cone_inner_angleObject

Returns the value of attribute cone_inner_angle.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def cone_inner_angle
  @cone_inner_angle
end

#cone_outer_angleObject

Returns the value of attribute cone_outer_angle.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def cone_outer_angle
  @cone_outer_angle
end

#cone_outer_gainObject

Returns the value of attribute cone_outer_gain.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def cone_outer_gain
  @cone_outer_gain
end

#distance_modelObject

Returns the value of attribute distance_model.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def distance_model
  @distance_model
end

#listenerObject (readonly)

Returns the value of attribute listener.



8
9
10
# File 'lib/deftones/component/panner3d.rb', line 8

def listener
  @listener
end

#max_distanceObject

Returns the value of attribute max_distance.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def max_distance
  @max_distance
end

#orientation_xObject Also known as: orientationX

Returns the value of attribute orientation_x.



8
9
10
# File 'lib/deftones/component/panner3d.rb', line 8

def orientation_x
  @orientation_x
end

#orientation_yObject Also known as: orientationY

Returns the value of attribute orientation_y.



8
9
10
# File 'lib/deftones/component/panner3d.rb', line 8

def orientation_y
  @orientation_y
end

#orientation_zObject Also known as: orientationZ

Returns the value of attribute orientation_z.



8
9
10
# File 'lib/deftones/component/panner3d.rb', line 8

def orientation_z
  @orientation_z
end

#panning_modelObject

Returns the value of attribute panning_model.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def panning_model
  @panning_model
end

#position_xObject Also known as: positionX

Returns the value of attribute position_x.



8
9
10
# File 'lib/deftones/component/panner3d.rb', line 8

def position_x
  @position_x
end

#position_yObject Also known as: positionY

Returns the value of attribute position_y.



8
9
10
# File 'lib/deftones/component/panner3d.rb', line 8

def position_y
  @position_y
end

#position_zObject Also known as: positionZ

Returns the value of attribute position_z.



8
9
10
# File 'lib/deftones/component/panner3d.rb', line 8

def position_z
  @position_z
end

#ref_distanceObject

Returns the value of attribute ref_distance.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def ref_distance
  @ref_distance
end

#rolloff_factorObject

Returns the value of attribute rolloff_factor.



6
7
8
# File 'lib/deftones/component/panner3d.rb', line 6

def rolloff_factor
  @rolloff_factor
end

Instance Method Details

#cone_gain(source_position, listener_position, orientation) ⇒ Object (private)



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/deftones/component/panner3d.rb', line 212

def cone_gain(source_position, listener_position, orientation)
  return 1.0 if @cone_outer_angle >= 360.0 && @cone_inner_angle >= 360.0

  to_listener = normalize_vector(vector_between(source_position, listener_position))
  facing = normalize_vector(orientation)
  angle = Math.acos(dot_product(facing, to_listener).clamp(-1.0, 1.0)) * 180.0 / Math::PI
  inner_half = @cone_inner_angle * 0.5
  outer_half = @cone_outer_angle * 0.5

  return 1.0 if angle <= inner_half
  return @cone_outer_gain if angle >= outer_half

  progress = (angle - inner_half) / [outer_half - inner_half, 1.0e-6].max
  1.0 + ((@cone_outer_gain - 1.0) * progress)
end

#cross_product(left, right) ⇒ Object (private)



303
304
305
306
307
308
309
# File 'lib/deftones/component/panner3d.rb', line 303

def cross_product(left, right)
  [
    (left[1] * right[2]) - (left[2] * right[1]),
    (left[2] * right[0]) - (left[0] * right[2]),
    (left[0] * right[1]) - (left[1] * right[0])
  ]
end

#distance_between(point_a, point_b) ⇒ Object (private)



228
229
230
# File 'lib/deftones/component/panner3d.rb', line 228

def distance_between(point_a, point_b)
  Math.sqrt(point_a.zip(point_b).sum { |left, right| (left - right)**2 })
end

#distance_gain(source_position, listener_position) ⇒ Object (private)



197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/deftones/component/panner3d.rb', line 197

def distance_gain(source_position, listener_position)
  distance = distance_between(source_position, listener_position)
  return 1.0 if distance <= @ref_distance

  case @distance_model
  when :linear
    denominator = [@max_distance - @ref_distance, 1.0e-6].max
    1.0 - (@rolloff_factor * (distance - @ref_distance) / denominator)
  when :exponential
    (distance / @ref_distance)**(-@rolloff_factor)
  else
    @ref_distance / (@ref_distance + (@rolloff_factor * (distance - @ref_distance)))
  end.clamp(0.0, 1.0)
end

#dot_product(left, right) ⇒ Object (private)



243
244
245
# File 'lib/deftones/component/panner3d.rb', line 243

def dot_product(left, right)
  left.zip(right).sum { |lhs, rhs| lhs * rhs }
end

#ensure_hrtf_delay_linesObject (private)



292
293
294
295
296
297
# File 'lib/deftones/component/panner3d.rb', line 292

def ensure_hrtf_delay_lines
  return unless @hrtf_delay_lines.empty?

  max_delay = [(context.sample_rate * 0.0006).ceil, 2].max
  @hrtf_delay_lines = Array.new(2) { DSP::DelayLine.new(max_delay) }
end

#hrtf_delay_samples(pan_amount) ⇒ Object (private)



299
300
301
# File 'lib/deftones/component/panner3d.rb', line 299

def hrtf_delay_samples(pan_amount)
  pan_amount.to_f.clamp(0.0, 1.0) * (context.sample_rate * 0.0006)
end

#hrtf_frame(input_block, stereo_input, mono_input, index, gain, pan) ⇒ Object (private)



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/deftones/component/panner3d.rb', line 260

def hrtf_frame(input_block, stereo_input, mono_input, index, gain, pan)
  input_left, input_right =
    if input_block.channels == 1
      sample = mono_input[index]
      [sample, sample]
    else
      [stereo_input.channel_data[0][index], stereo_input.channel_data[1][index]]
    end

  ensure_hrtf_delay_lines
  delay = hrtf_delay_samples(pan.abs)
  near_gain = 1.0
  far_gain = 1.0 - (0.35 * pan.abs)

  if pan.positive?
    [
      @hrtf_delay_lines[0].tap(delay, input_sample: input_left * gain * far_gain),
      @hrtf_delay_lines[1].write(input_right * gain * near_gain)
    ]
  elsif pan.negative?
    [
      @hrtf_delay_lines[0].write(input_left * gain * near_gain),
      @hrtf_delay_lines[1].tap(delay, input_sample: input_right * gain * far_gain)
    ]
  else
    [
      @hrtf_delay_lines[0].write(input_left * gain),
      @hrtf_delay_lines[1].write(input_right * gain)
    ]
  end
end

#multichannel_process?Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/deftones/component/panner3d.rb', line 106

def multichannel_process?
  true
end

#normalize_vector(vector) ⇒ Object (private)



236
237
238
239
240
241
# File 'lib/deftones/component/panner3d.rb', line 236

def normalize_vector(vector)
  magnitude = Math.sqrt(vector.sum { |value| value * value })
  return [0.0, 0.0, -1.0] if magnitude.zero?

  vector.map { |value| value / magnitude }
end

#orientationX=(value) ⇒ Object



94
95
96
# File 'lib/deftones/component/panner3d.rb', line 94

def orientationX=(value)
  self.orientation_x = value
end

#orientationY=(value) ⇒ Object



98
99
100
# File 'lib/deftones/component/panner3d.rb', line 98

def orientationY=(value)
  self.orientation_y = value
end

#orientationZ=(value) ⇒ Object



102
103
104
# File 'lib/deftones/component/panner3d.rb', line 102

def orientationZ=(value)
  self.orientation_z = value
end

#positionX=(value) ⇒ Object



82
83
84
# File 'lib/deftones/component/panner3d.rb', line 82

def positionX=(value)
  self.position_x = value
end

#positionY=(value) ⇒ Object



86
87
88
# File 'lib/deftones/component/panner3d.rb', line 86

def positionY=(value)
  self.position_y = value
end

#positionZ=(value) ⇒ Object



90
91
92
# File 'lib/deftones/component/panner3d.rb', line 90

def positionZ=(value)
  self.position_z = value
end

#process(input_block, num_frames, start_frame, _cache) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/deftones/component/panner3d.rb', line 110

def process(input_block, num_frames, start_frame, _cache)
  position_x_values = @position_x.process(num_frames, start_frame)
  position_y_values = @position_y.process(num_frames, start_frame)
  position_z_values = @position_z.process(num_frames, start_frame)
  orientation_x_values = @orientation_x.process(num_frames, start_frame)
  orientation_y_values = @orientation_y.process(num_frames, start_frame)
  orientation_z_values = @orientation_z.process(num_frames, start_frame)

  listener_position = [
    @listener.position_x.value,
    @listener.position_y.value,
    @listener.position_z.value
  ]
  listener_forward = normalize_vector([
    @listener.forward_x.value,
    @listener.forward_y.value,
    @listener.forward_z.value
  ])
  listener_up = normalize_vector([
    @listener.up_x.value,
    @listener.up_y.value,
    @listener.up_z.value
  ])

  stereo_input = input_block.fit_channels(2)
  mono_input = input_block.mono
  left = Array.new(num_frames)
  right = Array.new(num_frames)

  num_frames.times do |index|
    source_position = [position_x_values[index], position_y_values[index], position_z_values[index]]
    orientation = [orientation_x_values[index], orientation_y_values[index], orientation_z_values[index]]
    gain = distance_gain(source_position, listener_position) * cone_gain(source_position, listener_position, orientation)
    pan = stereo_pan_value(source_position, listener_position, listener_forward, listener_up)
    if @panning_model == :hrtf
      left[index], right[index] = hrtf_frame(
        input_block,
        stereo_input,
        mono_input,
        index,
        gain,
        pan
      )
      next
    end

    angle = stereo_pan_angle(pan)
    if input_block.channels == 1
      left[index] = mono_input[index] * gain * Math.cos(angle)
      right[index] = mono_input[index] * gain * Math.sin(angle)
    else
      left[index] = stereo_input.channel_data[0][index] * gain * Math.cos(angle)
      right[index] = stereo_input.channel_data[1][index] * gain * Math.sin(angle)
    end
  end

  Core::AudioBlock.from_channel_data([left, right])
end

#render(num_frames, start_frame = 0, cache = {}) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/deftones/component/panner3d.rb', line 169

def render(num_frames, start_frame = 0, cache = {})
  position_x_values = @position_x.process(num_frames, start_frame)
  position_y_values = @position_y.process(num_frames, start_frame)
  position_z_values = @position_z.process(num_frames, start_frame)
  orientation_x_values = @orientation_x.process(num_frames, start_frame)
  orientation_y_values = @orientation_y.process(num_frames, start_frame)
  orientation_z_values = @orientation_z.process(num_frames, start_frame)
  listener_position = [
    @listener.position_x.value,
    @listener.position_y.value,
    @listener.position_z.value
  ]
  mono_input = send(:mix_source_blocks, num_frames, start_frame, cache).mono

  Array.new(num_frames) do |index|
    source_position = [position_x_values[index], position_y_values[index], position_z_values[index]]
    orientation = [orientation_x_values[index], orientation_y_values[index], orientation_z_values[index]]
    gain = distance_gain(source_position, listener_position) * cone_gain(source_position, listener_position, orientation)
    mono_input[index] * gain
  end
end

#set_orientation(x, y, z) ⇒ Object Also known as: setOrientation



66
67
68
69
70
71
# File 'lib/deftones/component/panner3d.rb', line 66

def set_orientation(x, y, z)
  self.orientation_x = x
  self.orientation_y = y
  self.orientation_z = z
  self
end

#set_position(x, y, z) ⇒ Object Also known as: setPosition



59
60
61
62
63
64
# File 'lib/deftones/component/panner3d.rb', line 59

def set_position(x, y, z)
  self.position_x = x
  self.position_y = y
  self.position_z = z
  self
end

#stereo_pan_angle(pan) ⇒ Object (private)



256
257
258
# File 'lib/deftones/component/panner3d.rb', line 256

def stereo_pan_angle(pan)
  ((pan + 1.0) * Math::PI) * 0.25
end

#stereo_pan_value(source_position, listener_position, listener_forward, listener_up) ⇒ Object (private)



247
248
249
250
251
252
253
254
# File 'lib/deftones/component/panner3d.rb', line 247

def stereo_pan_value(source_position, listener_position, listener_forward, listener_up)
  relative = vector_between(listener_position, source_position)
  listener_right = normalize_vector(cross_product(listener_forward, listener_up))
  lateral = dot_product(relative, listener_right)
  forwardness = dot_product(relative, listener_forward)
  angle = Math.atan2(lateral, forwardness)
  (angle / (Math::PI * 0.5)).clamp(-1.0, 1.0)
end

#uses_legacy_render_for_block?Boolean (private)

Returns:

  • (Boolean)


193
194
195
# File 'lib/deftones/component/panner3d.rb', line 193

def uses_legacy_render_for_block?
  false
end

#vector_between(from, to) ⇒ Object (private)



232
233
234
# File 'lib/deftones/component/panner3d.rb', line 232

def vector_between(from, to)
  to.zip(from).map { |target, origin| target - origin }
end