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.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/CHANGELOG.md +12 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +197 -0
  6. data/Rakefile +8 -0
  7. data/examples/poly_chord.rb +14 -0
  8. data/examples/render_sampler.rb +16 -0
  9. data/examples/render_sequence.rb +13 -0
  10. data/examples/render_synth.rb +18 -0
  11. data/lib/deftones/analysis/analyser.rb +162 -0
  12. data/lib/deftones/analysis/dc_meter.rb +56 -0
  13. data/lib/deftones/analysis/fft.rb +128 -0
  14. data/lib/deftones/analysis/meter.rb +83 -0
  15. data/lib/deftones/analysis/waveform.rb +93 -0
  16. data/lib/deftones/component/amplitude_envelope.rb +8 -0
  17. data/lib/deftones/component/biquad_filter.rb +7 -0
  18. data/lib/deftones/component/channel.rb +109 -0
  19. data/lib/deftones/component/compressor.rb +64 -0
  20. data/lib/deftones/component/convolver.rb +99 -0
  21. data/lib/deftones/component/cross_fade.rb +67 -0
  22. data/lib/deftones/component/envelope.rb +160 -0
  23. data/lib/deftones/component/eq3.rb +73 -0
  24. data/lib/deftones/component/feedback_comb_filter.rb +75 -0
  25. data/lib/deftones/component/filter.rb +75 -0
  26. data/lib/deftones/component/follower.rb +55 -0
  27. data/lib/deftones/component/frequency_envelope.rb +24 -0
  28. data/lib/deftones/component/gate.rb +46 -0
  29. data/lib/deftones/component/lfo.rb +88 -0
  30. data/lib/deftones/component/limiter.rb +11 -0
  31. data/lib/deftones/component/lowpass_comb_filter.rb +43 -0
  32. data/lib/deftones/component/merge.rb +45 -0
  33. data/lib/deftones/component/mid_side_compressor.rb +43 -0
  34. data/lib/deftones/component/mid_side_merge.rb +53 -0
  35. data/lib/deftones/component/mid_side_split.rb +54 -0
  36. data/lib/deftones/component/mono.rb +8 -0
  37. data/lib/deftones/component/multiband_compressor.rb +70 -0
  38. data/lib/deftones/component/multiband_split.rb +133 -0
  39. data/lib/deftones/component/one_pole_filter.rb +71 -0
  40. data/lib/deftones/component/pan_vol.rb +26 -0
  41. data/lib/deftones/component/panner.rb +75 -0
  42. data/lib/deftones/component/panner3d.rb +322 -0
  43. data/lib/deftones/component/solo.rb +56 -0
  44. data/lib/deftones/component/split.rb +47 -0
  45. data/lib/deftones/component/volume.rb +31 -0
  46. data/lib/deftones/context.rb +213 -0
  47. data/lib/deftones/core/audio_block.rb +82 -0
  48. data/lib/deftones/core/audio_node.rb +262 -0
  49. data/lib/deftones/core/clock.rb +91 -0
  50. data/lib/deftones/core/computed_signal.rb +69 -0
  51. data/lib/deftones/core/delay.rb +44 -0
  52. data/lib/deftones/core/effect.rb +66 -0
  53. data/lib/deftones/core/emitter.rb +51 -0
  54. data/lib/deftones/core/gain.rb +39 -0
  55. data/lib/deftones/core/instrument.rb +109 -0
  56. data/lib/deftones/core/param.rb +31 -0
  57. data/lib/deftones/core/signal.rb +452 -0
  58. data/lib/deftones/core/signal_operator_methods.rb +73 -0
  59. data/lib/deftones/core/signal_operators.rb +138 -0
  60. data/lib/deftones/core/signal_shapers.rb +83 -0
  61. data/lib/deftones/core/source.rb +213 -0
  62. data/lib/deftones/core/synced_signal.rb +88 -0
  63. data/lib/deftones/destination.rb +132 -0
  64. data/lib/deftones/draw.rb +100 -0
  65. data/lib/deftones/dsp/biquad.rb +129 -0
  66. data/lib/deftones/dsp/delay_line.rb +41 -0
  67. data/lib/deftones/dsp/helpers.rb +25 -0
  68. data/lib/deftones/effect/auto_filter.rb +92 -0
  69. data/lib/deftones/effect/auto_panner.rb +57 -0
  70. data/lib/deftones/effect/auto_wah.rb +98 -0
  71. data/lib/deftones/effect/bit_crusher.rb +38 -0
  72. data/lib/deftones/effect/chebyshev.rb +36 -0
  73. data/lib/deftones/effect/chorus.rb +73 -0
  74. data/lib/deftones/effect/distortion.rb +22 -0
  75. data/lib/deftones/effect/feedback_delay.rb +38 -0
  76. data/lib/deftones/effect/freeverb.rb +11 -0
  77. data/lib/deftones/effect/frequency_shifter.rb +89 -0
  78. data/lib/deftones/effect/jc_reverb.rb +11 -0
  79. data/lib/deftones/effect/modulation_control.rb +159 -0
  80. data/lib/deftones/effect/phaser.rb +72 -0
  81. data/lib/deftones/effect/ping_pong_delay.rb +40 -0
  82. data/lib/deftones/effect/pitch_shift.rb +156 -0
  83. data/lib/deftones/effect/reverb.rb +71 -0
  84. data/lib/deftones/effect/stereo_widener.rb +34 -0
  85. data/lib/deftones/effect/tremolo.rb +52 -0
  86. data/lib/deftones/effect/vibrato.rb +47 -0
  87. data/lib/deftones/event/callback_behavior.rb +61 -0
  88. data/lib/deftones/event/loop.rb +53 -0
  89. data/lib/deftones/event/part.rb +51 -0
  90. data/lib/deftones/event/pattern.rb +94 -0
  91. data/lib/deftones/event/sequence.rb +87 -0
  92. data/lib/deftones/event/tone_event.rb +77 -0
  93. data/lib/deftones/event/transport.rb +276 -0
  94. data/lib/deftones/instrument/am_synth.rb +56 -0
  95. data/lib/deftones/instrument/duo_synth.rb +68 -0
  96. data/lib/deftones/instrument/fm_synth.rb +60 -0
  97. data/lib/deftones/instrument/membrane_synth.rb +60 -0
  98. data/lib/deftones/instrument/metal_synth.rb +61 -0
  99. data/lib/deftones/instrument/mono_synth.rb +88 -0
  100. data/lib/deftones/instrument/noise_synth.rb +56 -0
  101. data/lib/deftones/instrument/pluck_synth.rb +41 -0
  102. data/lib/deftones/instrument/poly_synth.rb +96 -0
  103. data/lib/deftones/instrument/sampler.rb +97 -0
  104. data/lib/deftones/instrument/synth.rb +60 -0
  105. data/lib/deftones/io/buffer.rb +352 -0
  106. data/lib/deftones/io/buffers.rb +77 -0
  107. data/lib/deftones/io/recorder.rb +89 -0
  108. data/lib/deftones/listener.rb +120 -0
  109. data/lib/deftones/music/frequency.rb +128 -0
  110. data/lib/deftones/music/midi.rb +206 -0
  111. data/lib/deftones/music/note.rb +58 -0
  112. data/lib/deftones/music/ticks.rb +106 -0
  113. data/lib/deftones/music/time.rb +209 -0
  114. data/lib/deftones/music/transport_time.rb +94 -0
  115. data/lib/deftones/music/unit_helpers.rb +30 -0
  116. data/lib/deftones/offline_context.rb +46 -0
  117. data/lib/deftones/portaudio_support.rb +112 -0
  118. data/lib/deftones/source/am_oscillator.rb +42 -0
  119. data/lib/deftones/source/fat_oscillator.rb +49 -0
  120. data/lib/deftones/source/fm_oscillator.rb +47 -0
  121. data/lib/deftones/source/grain_player.rb +198 -0
  122. data/lib/deftones/source/karplus_strong.rb +51 -0
  123. data/lib/deftones/source/noise.rb +99 -0
  124. data/lib/deftones/source/omni_oscillator.rb +175 -0
  125. data/lib/deftones/source/oscillator.rb +74 -0
  126. data/lib/deftones/source/player.rb +228 -0
  127. data/lib/deftones/source/players.rb +133 -0
  128. data/lib/deftones/source/pulse_oscillator.rb +38 -0
  129. data/lib/deftones/source/pwm_oscillator.rb +49 -0
  130. data/lib/deftones/source/tone_buffer_source.rb +136 -0
  131. data/lib/deftones/source/tone_oscillator_node.rb +65 -0
  132. data/lib/deftones/source/user_media.rb +519 -0
  133. data/lib/deftones/version.rb +5 -0
  134. data/lib/deftones.rb +542 -0
  135. 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