deftones 0.1.0
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.
- checksums.yaml +7 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +197 -0
- data/Rakefile +8 -0
- data/examples/poly_chord.rb +14 -0
- data/examples/render_sampler.rb +16 -0
- data/examples/render_sequence.rb +13 -0
- data/examples/render_synth.rb +18 -0
- data/lib/deftones/analysis/analyser.rb +162 -0
- data/lib/deftones/analysis/dc_meter.rb +56 -0
- data/lib/deftones/analysis/fft.rb +128 -0
- data/lib/deftones/analysis/meter.rb +83 -0
- data/lib/deftones/analysis/waveform.rb +93 -0
- data/lib/deftones/component/amplitude_envelope.rb +8 -0
- data/lib/deftones/component/biquad_filter.rb +7 -0
- data/lib/deftones/component/channel.rb +109 -0
- data/lib/deftones/component/compressor.rb +64 -0
- data/lib/deftones/component/convolver.rb +99 -0
- data/lib/deftones/component/cross_fade.rb +67 -0
- data/lib/deftones/component/envelope.rb +160 -0
- data/lib/deftones/component/eq3.rb +73 -0
- data/lib/deftones/component/feedback_comb_filter.rb +75 -0
- data/lib/deftones/component/filter.rb +75 -0
- data/lib/deftones/component/follower.rb +55 -0
- data/lib/deftones/component/frequency_envelope.rb +24 -0
- data/lib/deftones/component/gate.rb +46 -0
- data/lib/deftones/component/lfo.rb +88 -0
- data/lib/deftones/component/limiter.rb +11 -0
- data/lib/deftones/component/lowpass_comb_filter.rb +43 -0
- data/lib/deftones/component/merge.rb +45 -0
- data/lib/deftones/component/mid_side_compressor.rb +43 -0
- data/lib/deftones/component/mid_side_merge.rb +53 -0
- data/lib/deftones/component/mid_side_split.rb +54 -0
- data/lib/deftones/component/mono.rb +8 -0
- data/lib/deftones/component/multiband_compressor.rb +70 -0
- data/lib/deftones/component/multiband_split.rb +133 -0
- data/lib/deftones/component/one_pole_filter.rb +71 -0
- data/lib/deftones/component/pan_vol.rb +26 -0
- data/lib/deftones/component/panner.rb +75 -0
- data/lib/deftones/component/panner3d.rb +322 -0
- data/lib/deftones/component/solo.rb +56 -0
- data/lib/deftones/component/split.rb +47 -0
- data/lib/deftones/component/volume.rb +31 -0
- data/lib/deftones/context.rb +213 -0
- data/lib/deftones/core/audio_block.rb +82 -0
- data/lib/deftones/core/audio_node.rb +262 -0
- data/lib/deftones/core/clock.rb +91 -0
- data/lib/deftones/core/computed_signal.rb +69 -0
- data/lib/deftones/core/delay.rb +44 -0
- data/lib/deftones/core/effect.rb +66 -0
- data/lib/deftones/core/emitter.rb +51 -0
- data/lib/deftones/core/gain.rb +39 -0
- data/lib/deftones/core/instrument.rb +109 -0
- data/lib/deftones/core/param.rb +31 -0
- data/lib/deftones/core/signal.rb +452 -0
- data/lib/deftones/core/signal_operator_methods.rb +73 -0
- data/lib/deftones/core/signal_operators.rb +138 -0
- data/lib/deftones/core/signal_shapers.rb +83 -0
- data/lib/deftones/core/source.rb +213 -0
- data/lib/deftones/core/synced_signal.rb +88 -0
- data/lib/deftones/destination.rb +132 -0
- data/lib/deftones/draw.rb +100 -0
- data/lib/deftones/dsp/biquad.rb +129 -0
- data/lib/deftones/dsp/delay_line.rb +41 -0
- data/lib/deftones/dsp/helpers.rb +25 -0
- data/lib/deftones/effect/auto_filter.rb +92 -0
- data/lib/deftones/effect/auto_panner.rb +57 -0
- data/lib/deftones/effect/auto_wah.rb +98 -0
- data/lib/deftones/effect/bit_crusher.rb +38 -0
- data/lib/deftones/effect/chebyshev.rb +36 -0
- data/lib/deftones/effect/chorus.rb +73 -0
- data/lib/deftones/effect/distortion.rb +22 -0
- data/lib/deftones/effect/feedback_delay.rb +38 -0
- data/lib/deftones/effect/freeverb.rb +11 -0
- data/lib/deftones/effect/frequency_shifter.rb +89 -0
- data/lib/deftones/effect/jc_reverb.rb +11 -0
- data/lib/deftones/effect/modulation_control.rb +159 -0
- data/lib/deftones/effect/phaser.rb +72 -0
- data/lib/deftones/effect/ping_pong_delay.rb +40 -0
- data/lib/deftones/effect/pitch_shift.rb +156 -0
- data/lib/deftones/effect/reverb.rb +71 -0
- data/lib/deftones/effect/stereo_widener.rb +34 -0
- data/lib/deftones/effect/tremolo.rb +52 -0
- data/lib/deftones/effect/vibrato.rb +47 -0
- data/lib/deftones/event/callback_behavior.rb +61 -0
- data/lib/deftones/event/loop.rb +53 -0
- data/lib/deftones/event/part.rb +51 -0
- data/lib/deftones/event/pattern.rb +94 -0
- data/lib/deftones/event/sequence.rb +87 -0
- data/lib/deftones/event/tone_event.rb +77 -0
- data/lib/deftones/event/transport.rb +276 -0
- data/lib/deftones/instrument/am_synth.rb +56 -0
- data/lib/deftones/instrument/duo_synth.rb +68 -0
- data/lib/deftones/instrument/fm_synth.rb +60 -0
- data/lib/deftones/instrument/membrane_synth.rb +60 -0
- data/lib/deftones/instrument/metal_synth.rb +61 -0
- data/lib/deftones/instrument/mono_synth.rb +88 -0
- data/lib/deftones/instrument/noise_synth.rb +56 -0
- data/lib/deftones/instrument/pluck_synth.rb +41 -0
- data/lib/deftones/instrument/poly_synth.rb +96 -0
- data/lib/deftones/instrument/sampler.rb +97 -0
- data/lib/deftones/instrument/synth.rb +60 -0
- data/lib/deftones/io/buffer.rb +352 -0
- data/lib/deftones/io/buffers.rb +77 -0
- data/lib/deftones/io/recorder.rb +89 -0
- data/lib/deftones/listener.rb +120 -0
- data/lib/deftones/music/frequency.rb +128 -0
- data/lib/deftones/music/midi.rb +206 -0
- data/lib/deftones/music/note.rb +58 -0
- data/lib/deftones/music/ticks.rb +106 -0
- data/lib/deftones/music/time.rb +209 -0
- data/lib/deftones/music/transport_time.rb +94 -0
- data/lib/deftones/music/unit_helpers.rb +30 -0
- data/lib/deftones/offline_context.rb +46 -0
- data/lib/deftones/portaudio_support.rb +112 -0
- data/lib/deftones/source/am_oscillator.rb +42 -0
- data/lib/deftones/source/fat_oscillator.rb +49 -0
- data/lib/deftones/source/fm_oscillator.rb +47 -0
- data/lib/deftones/source/grain_player.rb +198 -0
- data/lib/deftones/source/karplus_strong.rb +51 -0
- data/lib/deftones/source/noise.rb +99 -0
- data/lib/deftones/source/omni_oscillator.rb +175 -0
- data/lib/deftones/source/oscillator.rb +74 -0
- data/lib/deftones/source/player.rb +228 -0
- data/lib/deftones/source/players.rb +133 -0
- data/lib/deftones/source/pulse_oscillator.rb +38 -0
- data/lib/deftones/source/pwm_oscillator.rb +49 -0
- data/lib/deftones/source/tone_buffer_source.rb +136 -0
- data/lib/deftones/source/tone_oscillator_node.rb +65 -0
- data/lib/deftones/source/user_media.rb +519 -0
- data/lib/deftones/version.rb +5 -0
- data/lib/deftones.rb +542 -0
- metadata +221 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class Panner3D < Core::AudioNode
|
|
6
|
+
attr_accessor :cone_inner_angle, :cone_outer_angle, :cone_outer_gain,
|
|
7
|
+
:distance_model, :max_distance, :panning_model, :ref_distance, :rolloff_factor
|
|
8
|
+
attr_reader :listener, :orientation_x, :orientation_y, :orientation_z, :position_x, :position_y, :position_z
|
|
9
|
+
|
|
10
|
+
def initialize(position_x: 0.0, position_y: 0.0, position_z: 0.0,
|
|
11
|
+
orientation_x: 1.0, orientation_y: 0.0, orientation_z: 0.0,
|
|
12
|
+
panning_model: :equal_power, distance_model: :inverse,
|
|
13
|
+
ref_distance: 1.0, rolloff_factor: 1.0, max_distance: 10_000.0,
|
|
14
|
+
cone_inner_angle: 360.0, cone_outer_angle: 360.0, cone_outer_gain: 0.0,
|
|
15
|
+
listener: Deftones.listener, context: Deftones.context)
|
|
16
|
+
super(context: context)
|
|
17
|
+
@listener = listener
|
|
18
|
+
@position_x = Core::Signal.new(value: position_x, units: :number, context: context)
|
|
19
|
+
@position_y = Core::Signal.new(value: position_y, units: :number, context: context)
|
|
20
|
+
@position_z = Core::Signal.new(value: position_z, units: :number, context: context)
|
|
21
|
+
@orientation_x = Core::Signal.new(value: orientation_x, units: :number, context: context)
|
|
22
|
+
@orientation_y = Core::Signal.new(value: orientation_y, units: :number, context: context)
|
|
23
|
+
@orientation_z = Core::Signal.new(value: orientation_z, units: :number, context: context)
|
|
24
|
+
@panning_model = panning_model.to_sym
|
|
25
|
+
@distance_model = distance_model.to_sym
|
|
26
|
+
@ref_distance = ref_distance.to_f
|
|
27
|
+
@rolloff_factor = rolloff_factor.to_f
|
|
28
|
+
@max_distance = max_distance.to_f
|
|
29
|
+
@cone_inner_angle = cone_inner_angle.to_f
|
|
30
|
+
@cone_outer_angle = cone_outer_angle.to_f
|
|
31
|
+
@cone_outer_gain = cone_outer_gain.to_f
|
|
32
|
+
@hrtf_delay_lines = []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def position_x=(value)
|
|
36
|
+
@position_x.value = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def position_y=(value)
|
|
40
|
+
@position_y.value = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def position_z=(value)
|
|
44
|
+
@position_z.value = value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def orientation_x=(value)
|
|
48
|
+
@orientation_x.value = value
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def orientation_y=(value)
|
|
52
|
+
@orientation_y.value = value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def orientation_z=(value)
|
|
56
|
+
@orientation_z.value = value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def set_position(x, y, z)
|
|
60
|
+
self.position_x = x
|
|
61
|
+
self.position_y = y
|
|
62
|
+
self.position_z = z
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set_orientation(x, y, z)
|
|
67
|
+
self.orientation_x = x
|
|
68
|
+
self.orientation_y = y
|
|
69
|
+
self.orientation_z = z
|
|
70
|
+
self
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
alias positionX position_x
|
|
74
|
+
alias positionY position_y
|
|
75
|
+
alias positionZ position_z
|
|
76
|
+
alias orientationX orientation_x
|
|
77
|
+
alias orientationY orientation_y
|
|
78
|
+
alias orientationZ orientation_z
|
|
79
|
+
alias setPosition set_position
|
|
80
|
+
alias setOrientation set_orientation
|
|
81
|
+
|
|
82
|
+
def positionX=(value)
|
|
83
|
+
self.position_x = value
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def positionY=(value)
|
|
87
|
+
self.position_y = value
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def positionZ=(value)
|
|
91
|
+
self.position_z = value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def orientationX=(value)
|
|
95
|
+
self.orientation_x = value
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def orientationY=(value)
|
|
99
|
+
self.orientation_y = value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def orientationZ=(value)
|
|
103
|
+
self.orientation_z = value
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def multichannel_process?
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def process(input_block, num_frames, start_frame, _cache)
|
|
111
|
+
position_x_values = @position_x.process(num_frames, start_frame)
|
|
112
|
+
position_y_values = @position_y.process(num_frames, start_frame)
|
|
113
|
+
position_z_values = @position_z.process(num_frames, start_frame)
|
|
114
|
+
orientation_x_values = @orientation_x.process(num_frames, start_frame)
|
|
115
|
+
orientation_y_values = @orientation_y.process(num_frames, start_frame)
|
|
116
|
+
orientation_z_values = @orientation_z.process(num_frames, start_frame)
|
|
117
|
+
|
|
118
|
+
listener_position = [
|
|
119
|
+
@listener.position_x.value,
|
|
120
|
+
@listener.position_y.value,
|
|
121
|
+
@listener.position_z.value
|
|
122
|
+
]
|
|
123
|
+
listener_forward = normalize_vector([
|
|
124
|
+
@listener.forward_x.value,
|
|
125
|
+
@listener.forward_y.value,
|
|
126
|
+
@listener.forward_z.value
|
|
127
|
+
])
|
|
128
|
+
listener_up = normalize_vector([
|
|
129
|
+
@listener.up_x.value,
|
|
130
|
+
@listener.up_y.value,
|
|
131
|
+
@listener.up_z.value
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
stereo_input = input_block.fit_channels(2)
|
|
135
|
+
mono_input = input_block.mono
|
|
136
|
+
left = Array.new(num_frames)
|
|
137
|
+
right = Array.new(num_frames)
|
|
138
|
+
|
|
139
|
+
num_frames.times do |index|
|
|
140
|
+
source_position = [position_x_values[index], position_y_values[index], position_z_values[index]]
|
|
141
|
+
orientation = [orientation_x_values[index], orientation_y_values[index], orientation_z_values[index]]
|
|
142
|
+
gain = distance_gain(source_position, listener_position) * cone_gain(source_position, listener_position, orientation)
|
|
143
|
+
pan = stereo_pan_value(source_position, listener_position, listener_forward, listener_up)
|
|
144
|
+
if @panning_model == :hrtf
|
|
145
|
+
left[index], right[index] = hrtf_frame(
|
|
146
|
+
input_block,
|
|
147
|
+
stereo_input,
|
|
148
|
+
mono_input,
|
|
149
|
+
index,
|
|
150
|
+
gain,
|
|
151
|
+
pan
|
|
152
|
+
)
|
|
153
|
+
next
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
angle = stereo_pan_angle(pan)
|
|
157
|
+
if input_block.channels == 1
|
|
158
|
+
left[index] = mono_input[index] * gain * Math.cos(angle)
|
|
159
|
+
right[index] = mono_input[index] * gain * Math.sin(angle)
|
|
160
|
+
else
|
|
161
|
+
left[index] = stereo_input.channel_data[0][index] * gain * Math.cos(angle)
|
|
162
|
+
right[index] = stereo_input.channel_data[1][index] * gain * Math.sin(angle)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
Core::AudioBlock.from_channel_data([left, right])
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def render(num_frames, start_frame = 0, cache = {})
|
|
170
|
+
position_x_values = @position_x.process(num_frames, start_frame)
|
|
171
|
+
position_y_values = @position_y.process(num_frames, start_frame)
|
|
172
|
+
position_z_values = @position_z.process(num_frames, start_frame)
|
|
173
|
+
orientation_x_values = @orientation_x.process(num_frames, start_frame)
|
|
174
|
+
orientation_y_values = @orientation_y.process(num_frames, start_frame)
|
|
175
|
+
orientation_z_values = @orientation_z.process(num_frames, start_frame)
|
|
176
|
+
listener_position = [
|
|
177
|
+
@listener.position_x.value,
|
|
178
|
+
@listener.position_y.value,
|
|
179
|
+
@listener.position_z.value
|
|
180
|
+
]
|
|
181
|
+
listener_forward = normalize_vector([
|
|
182
|
+
@listener.forward_x.value,
|
|
183
|
+
@listener.forward_y.value,
|
|
184
|
+
@listener.forward_z.value
|
|
185
|
+
])
|
|
186
|
+
listener_up = normalize_vector([
|
|
187
|
+
@listener.up_x.value,
|
|
188
|
+
@listener.up_y.value,
|
|
189
|
+
@listener.up_z.value
|
|
190
|
+
])
|
|
191
|
+
mono_input = send(:mix_source_blocks, num_frames, start_frame, cache).mono
|
|
192
|
+
|
|
193
|
+
Array.new(num_frames) do |index|
|
|
194
|
+
source_position = [position_x_values[index], position_y_values[index], position_z_values[index]]
|
|
195
|
+
orientation = [orientation_x_values[index], orientation_y_values[index], orientation_z_values[index]]
|
|
196
|
+
gain = distance_gain(source_position, listener_position) * cone_gain(source_position, listener_position, orientation)
|
|
197
|
+
mono_input[index] * gain
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def uses_legacy_render_for_block?
|
|
204
|
+
false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def distance_gain(source_position, listener_position)
|
|
208
|
+
distance = distance_between(source_position, listener_position)
|
|
209
|
+
return 1.0 if distance <= @ref_distance
|
|
210
|
+
|
|
211
|
+
case @distance_model
|
|
212
|
+
when :linear
|
|
213
|
+
denominator = [@max_distance - @ref_distance, 1.0e-6].max
|
|
214
|
+
1.0 - (@rolloff_factor * (distance - @ref_distance) / denominator)
|
|
215
|
+
when :exponential
|
|
216
|
+
(distance / @ref_distance)**(-@rolloff_factor)
|
|
217
|
+
else
|
|
218
|
+
@ref_distance / (@ref_distance + (@rolloff_factor * (distance - @ref_distance)))
|
|
219
|
+
end.clamp(0.0, 1.0)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def cone_gain(source_position, listener_position, orientation)
|
|
223
|
+
return 1.0 if @cone_outer_angle >= 360.0 && @cone_inner_angle >= 360.0
|
|
224
|
+
|
|
225
|
+
to_listener = normalize_vector(vector_between(source_position, listener_position))
|
|
226
|
+
facing = normalize_vector(orientation)
|
|
227
|
+
angle = Math.acos(dot_product(facing, to_listener).clamp(-1.0, 1.0)) * 180.0 / Math::PI
|
|
228
|
+
inner_half = @cone_inner_angle * 0.5
|
|
229
|
+
outer_half = @cone_outer_angle * 0.5
|
|
230
|
+
|
|
231
|
+
return 1.0 if angle <= inner_half
|
|
232
|
+
return @cone_outer_gain if angle >= outer_half
|
|
233
|
+
|
|
234
|
+
progress = (angle - inner_half) / [outer_half - inner_half, 1.0e-6].max
|
|
235
|
+
1.0 + ((@cone_outer_gain - 1.0) * progress)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def distance_between(point_a, point_b)
|
|
239
|
+
Math.sqrt(point_a.zip(point_b).sum { |left, right| (left - right)**2 })
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def vector_between(from, to)
|
|
243
|
+
to.zip(from).map { |target, origin| target - origin }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def normalize_vector(vector)
|
|
247
|
+
magnitude = Math.sqrt(vector.sum { |value| value * value })
|
|
248
|
+
return [0.0, 0.0, -1.0] if magnitude.zero?
|
|
249
|
+
|
|
250
|
+
vector.map { |value| value / magnitude }
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def dot_product(left, right)
|
|
254
|
+
left.zip(right).sum { |lhs, rhs| lhs * rhs }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def stereo_pan_value(source_position, listener_position, listener_forward, listener_up)
|
|
258
|
+
relative = vector_between(listener_position, source_position)
|
|
259
|
+
listener_right = normalize_vector(cross_product(listener_forward, listener_up))
|
|
260
|
+
lateral = dot_product(relative, listener_right)
|
|
261
|
+
forwardness = dot_product(relative, listener_forward)
|
|
262
|
+
angle = Math.atan2(lateral, forwardness)
|
|
263
|
+
(angle / (Math::PI * 0.5)).clamp(-1.0, 1.0)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def stereo_pan_angle(pan)
|
|
267
|
+
((pan + 1.0) * Math::PI) * 0.25
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def hrtf_frame(input_block, stereo_input, mono_input, index, gain, pan)
|
|
271
|
+
input_left, input_right =
|
|
272
|
+
if input_block.channels == 1
|
|
273
|
+
sample = mono_input[index]
|
|
274
|
+
[sample, sample]
|
|
275
|
+
else
|
|
276
|
+
[stereo_input.channel_data[0][index], stereo_input.channel_data[1][index]]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
ensure_hrtf_delay_lines
|
|
280
|
+
delay = hrtf_delay_samples(pan.abs)
|
|
281
|
+
near_gain = 1.0
|
|
282
|
+
far_gain = 1.0 - (0.35 * pan.abs)
|
|
283
|
+
|
|
284
|
+
if pan.positive?
|
|
285
|
+
[
|
|
286
|
+
@hrtf_delay_lines[0].tap(delay, input_sample: input_left * gain * far_gain),
|
|
287
|
+
@hrtf_delay_lines[1].write(input_right * gain * near_gain)
|
|
288
|
+
]
|
|
289
|
+
elsif pan.negative?
|
|
290
|
+
[
|
|
291
|
+
@hrtf_delay_lines[0].write(input_left * gain * near_gain),
|
|
292
|
+
@hrtf_delay_lines[1].tap(delay, input_sample: input_right * gain * far_gain)
|
|
293
|
+
]
|
|
294
|
+
else
|
|
295
|
+
[
|
|
296
|
+
@hrtf_delay_lines[0].write(input_left * gain),
|
|
297
|
+
@hrtf_delay_lines[1].write(input_right * gain)
|
|
298
|
+
]
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def ensure_hrtf_delay_lines
|
|
303
|
+
return unless @hrtf_delay_lines.empty?
|
|
304
|
+
|
|
305
|
+
max_delay = [(context.sample_rate * 0.0006).ceil, 2].max
|
|
306
|
+
@hrtf_delay_lines = Array.new(2) { DSP::DelayLine.new(max_delay) }
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def hrtf_delay_samples(pan_amount)
|
|
310
|
+
pan_amount.to_f.clamp(0.0, 1.0) * (context.sample_rate * 0.0006)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def cross_product(left, right)
|
|
314
|
+
[
|
|
315
|
+
(left[1] * right[2]) - (left[2] * right[1]),
|
|
316
|
+
(left[2] * right[0]) - (left[0] * right[2]),
|
|
317
|
+
(left[0] * right[1]) - (left[1] * right[0])
|
|
318
|
+
]
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class Solo < Core::AudioNode
|
|
6
|
+
@instances = []
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_reader :instances
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_accessor :muted
|
|
13
|
+
|
|
14
|
+
def initialize(solo: false, muted: false, context: Deftones.context)
|
|
15
|
+
super(context: context)
|
|
16
|
+
@solo = solo
|
|
17
|
+
@muted = muted
|
|
18
|
+
self.class.instances << self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def solo?
|
|
22
|
+
@solo
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def solo=(value)
|
|
26
|
+
@solo = !!value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def mute
|
|
30
|
+
@muted
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def mute=(value)
|
|
34
|
+
@muted = !!value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def dispose
|
|
38
|
+
self.class.instances.delete(self)
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def multichannel_process?
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def process(input_block, num_frames, _start_frame, _cache)
|
|
47
|
+
return Core::AudioBlock.silent(num_frames, input_block.channels) if @muted
|
|
48
|
+
return input_block.dup unless self.class.instances.any?(&:solo?)
|
|
49
|
+
|
|
50
|
+
@solo ? input_block.dup : Core::AudioBlock.silent(num_frames, input_block.channels)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
alias mute? mute
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class Split < Core::AudioNode
|
|
6
|
+
attr_reader :left, :right
|
|
7
|
+
|
|
8
|
+
def initialize(context: Deftones.context)
|
|
9
|
+
super(context: context)
|
|
10
|
+
@left = OutputTap.new(parent: self, channel: 0, context: context)
|
|
11
|
+
@right = OutputTap.new(parent: self, channel: 1, context: context)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def render_channel(_channel, num_frames, start_frame = 0, cache = {})
|
|
15
|
+
render_channel_block(_channel, num_frames, start_frame, cache).mono
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def render_channel_block(channel, num_frames, start_frame = 0, cache = {})
|
|
19
|
+
input_block = send(:mix_source_blocks, num_frames, start_frame, cache)
|
|
20
|
+
output_channel = if input_block.channels == 1
|
|
21
|
+
input_block.channel(0)
|
|
22
|
+
elsif channel < input_block.channels
|
|
23
|
+
input_block.channel(channel)
|
|
24
|
+
else
|
|
25
|
+
Array.new(num_frames, 0.0)
|
|
26
|
+
end
|
|
27
|
+
Core::AudioBlock.from_channel_data([output_channel])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class OutputTap < Core::AudioNode
|
|
31
|
+
def initialize(parent:, channel:, context: Deftones.context)
|
|
32
|
+
super(context: context)
|
|
33
|
+
@parent = parent
|
|
34
|
+
@channel = channel
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def render_block(num_frames, start_frame = 0, cache = {})
|
|
38
|
+
@parent.render_channel_block(@channel, num_frames, start_frame, cache)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def render(num_frames, start_frame = 0, cache = {})
|
|
42
|
+
@parent.render_channel(@channel, num_frames, start_frame, cache)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class Volume < Core::AudioNode
|
|
6
|
+
attr_reader :volume
|
|
7
|
+
|
|
8
|
+
def initialize(volume: 0.0, context: Deftones.context)
|
|
9
|
+
super(context: context)
|
|
10
|
+
@volume = Core::Signal.new(value: volume, units: :decibels, context: context)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def volume=(value)
|
|
14
|
+
@volume.value = value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def multichannel_process?
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def process(input_block, num_frames, start_frame, _cache)
|
|
22
|
+
gains = @volume.process(num_frames, start_frame)
|
|
23
|
+
Core::AudioBlock.from_channel_data(
|
|
24
|
+
input_block.channel_data.map do |channel|
|
|
25
|
+
Array.new(num_frames) { |index| channel[index] * gains[index] }
|
|
26
|
+
end
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
class Context
|
|
5
|
+
DEFAULT_SAMPLE_RATE = 44_100
|
|
6
|
+
DEFAULT_BUFFER_SIZE = 256
|
|
7
|
+
DEFAULT_CHANNELS = 2
|
|
8
|
+
|
|
9
|
+
attr_reader :sample_rate, :buffer_size, :channels, :stream_error, :latency_hint, :look_ahead
|
|
10
|
+
|
|
11
|
+
def initialize(sample_rate: DEFAULT_SAMPLE_RATE, buffer_size: DEFAULT_BUFFER_SIZE, channels: DEFAULT_CHANNELS,
|
|
12
|
+
realtime_backend: nil, autostart: true, latency_hint: "interactive", look_ahead: nil)
|
|
13
|
+
@sample_rate = sample_rate
|
|
14
|
+
@buffer_size = buffer_size
|
|
15
|
+
@channels = channels
|
|
16
|
+
@realtime_backend = realtime_backend
|
|
17
|
+
@autostart = autostart
|
|
18
|
+
@latency_hint = latency_hint
|
|
19
|
+
@look_ahead = look_ahead || (buffer_size.to_f / sample_rate)
|
|
20
|
+
@output = Core::Gain.new(context: self, gain: 1.0)
|
|
21
|
+
@running = false
|
|
22
|
+
@closed = false
|
|
23
|
+
@started_at = monotonic_time
|
|
24
|
+
@stream = nil
|
|
25
|
+
@rendered_frames = 0
|
|
26
|
+
@stream_error = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def start(use_realtime: true)
|
|
30
|
+
@closed = false
|
|
31
|
+
@started_at = monotonic_time
|
|
32
|
+
@rendered_frames = 0
|
|
33
|
+
@stream_error = nil
|
|
34
|
+
@running = true
|
|
35
|
+
start_realtime_stream if use_realtime
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def resume(use_realtime: true)
|
|
40
|
+
start(use_realtime: use_realtime)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def stop
|
|
44
|
+
@stream&.stop
|
|
45
|
+
@stream&.close if @stream.respond_to?(:close)
|
|
46
|
+
@stream = nil
|
|
47
|
+
@running = false
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def close
|
|
52
|
+
stop
|
|
53
|
+
@closed = true
|
|
54
|
+
self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def running?
|
|
58
|
+
@running
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def state
|
|
62
|
+
return "closed" if @closed
|
|
63
|
+
return "running" if running?
|
|
64
|
+
|
|
65
|
+
"suspended"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def realtime?
|
|
69
|
+
!@stream.nil?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def output
|
|
73
|
+
start if @autostart && !running?
|
|
74
|
+
@output
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def current_time
|
|
78
|
+
return @stream.time if @stream&.respond_to?(:time)
|
|
79
|
+
return 0.0 unless @running
|
|
80
|
+
|
|
81
|
+
monotonic_time - @started_at
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def render_frames(num_frames, start_frame = 0)
|
|
85
|
+
render_block_frames(num_frames, start_frame).mono
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def render_block_frames(num_frames, start_frame = 0)
|
|
89
|
+
@output.send(:render_block, num_frames, start_frame, {})
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def raw_context
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def sample_time
|
|
97
|
+
1.0 / sample_rate
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def block_time
|
|
101
|
+
buffer_size.to_f / sample_rate
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
alias rawContext raw_context
|
|
105
|
+
alias sampleTime sample_time
|
|
106
|
+
alias blockTime block_time
|
|
107
|
+
alias latencyHint latency_hint
|
|
108
|
+
alias lookAhead look_ahead
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def start_realtime_stream
|
|
113
|
+
return if @stream
|
|
114
|
+
|
|
115
|
+
backend = build_realtime_backend
|
|
116
|
+
return unless backend
|
|
117
|
+
|
|
118
|
+
backend.start
|
|
119
|
+
@stream = backend
|
|
120
|
+
rescue StandardError => error
|
|
121
|
+
backend&.close if backend.respond_to?(:close)
|
|
122
|
+
@stream_error = error
|
|
123
|
+
@stream = nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_realtime_backend
|
|
127
|
+
case @realtime_backend
|
|
128
|
+
when nil
|
|
129
|
+
return unless Deftones.portaudio_available?
|
|
130
|
+
|
|
131
|
+
PortAudioOutputStream.new(context: self)
|
|
132
|
+
when Class
|
|
133
|
+
@realtime_backend.new(context: self)
|
|
134
|
+
else
|
|
135
|
+
@realtime_backend
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def pull_realtime_samples(frames)
|
|
140
|
+
start_frame = @rendered_frames
|
|
141
|
+
chunk = render_block_frames(frames, start_frame).fit_channels(@channels)
|
|
142
|
+
@rendered_frames += frames
|
|
143
|
+
Deftones.draw.advance_to(@rendered_frames.to_f / sample_rate)
|
|
144
|
+
chunk.interleaved
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def monotonic_time
|
|
148
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class PortAudioOutputStream
|
|
152
|
+
def initialize(context:)
|
|
153
|
+
@context = context
|
|
154
|
+
@stream = nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def start
|
|
158
|
+
open_stream unless @stream
|
|
159
|
+
@stream.start
|
|
160
|
+
self
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def stop
|
|
164
|
+
return self unless @stream
|
|
165
|
+
return self if @stream.stopped?
|
|
166
|
+
|
|
167
|
+
@stream.stop
|
|
168
|
+
self
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def close
|
|
172
|
+
return self unless @stream
|
|
173
|
+
|
|
174
|
+
stream = @stream
|
|
175
|
+
@stream = nil
|
|
176
|
+
stream.close
|
|
177
|
+
self
|
|
178
|
+
ensure
|
|
179
|
+
Deftones::PortAudioSupport.release
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def time
|
|
183
|
+
return 0.0 unless @stream
|
|
184
|
+
|
|
185
|
+
@stream.time
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
private
|
|
189
|
+
|
|
190
|
+
def open_stream
|
|
191
|
+
Deftones::PortAudioSupport.acquire!
|
|
192
|
+
@stream = PortAudio::Stream.new(
|
|
193
|
+
output: Deftones::PortAudioSupport.output_parameters(@context.channels),
|
|
194
|
+
sample_rate: @context.sample_rate.to_f,
|
|
195
|
+
frames_per_buffer: @context.buffer_size,
|
|
196
|
+
&method(:process)
|
|
197
|
+
)
|
|
198
|
+
rescue StandardError
|
|
199
|
+
Deftones::PortAudioSupport.release
|
|
200
|
+
raise
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def process(_input, output, frame_count, _time_info, _status_flags, _user_data)
|
|
204
|
+
output.write_array_of_float(@context.send(:pull_realtime_samples, frame_count))
|
|
205
|
+
:continue
|
|
206
|
+
rescue StandardError => error
|
|
207
|
+
@context.instance_variable_set(:@stream_error, error) if @context.stream_error.nil?
|
|
208
|
+
output.write_array_of_float(Array.new(frame_count * @context.channels, 0.0)) unless output.null?
|
|
209
|
+
:abort
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|