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,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class MultibandCompressor < Core::AudioNode
|
|
6
|
+
attr_reader :high, :high_frequency, :input, :low, :low_frequency, :mid, :output, :q, :split
|
|
7
|
+
|
|
8
|
+
def initialize(low_frequency: 400.0, high_frequency: 2_500.0, q: 1.0, low: {}, mid: {}, high: {},
|
|
9
|
+
context: Deftones.context)
|
|
10
|
+
super(context: context)
|
|
11
|
+
@split = MultibandSplit.new(
|
|
12
|
+
low_frequency: low_frequency,
|
|
13
|
+
high_frequency: high_frequency,
|
|
14
|
+
q: q,
|
|
15
|
+
context: context
|
|
16
|
+
)
|
|
17
|
+
@input = @split.input
|
|
18
|
+
@output = self
|
|
19
|
+
@low = Compressor.new(context: context, **compressor_options(low))
|
|
20
|
+
@mid = Compressor.new(context: context, **compressor_options(mid))
|
|
21
|
+
@high = Compressor.new(context: context, **compressor_options(high))
|
|
22
|
+
@split.low >> @low
|
|
23
|
+
@split.mid >> @mid
|
|
24
|
+
@split.high >> @high
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def low_frequency
|
|
28
|
+
@split.low_frequency
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def high_frequency
|
|
32
|
+
@split.high_frequency
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def q
|
|
36
|
+
@split.q
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def render(num_frames, start_frame = 0, cache = {})
|
|
40
|
+
render_block(num_frames, start_frame, cache).mono
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_block(num_frames, start_frame = 0, cache = {})
|
|
44
|
+
cache_key = [object_id, start_frame, num_frames]
|
|
45
|
+
return cache.fetch(cache_key).dup if cache.key?(cache_key)
|
|
46
|
+
|
|
47
|
+
low_buffer = @low.send(:render_block, num_frames, start_frame, cache)
|
|
48
|
+
mid_buffer = @mid.send(:render_block, num_frames, start_frame, cache)
|
|
49
|
+
high_buffer = @high.send(:render_block, num_frames, start_frame, cache)
|
|
50
|
+
channels = [low_buffer.channels, mid_buffer.channels, high_buffer.channels].max
|
|
51
|
+
output_buffer = Core::AudioBlock.silent(num_frames, channels)
|
|
52
|
+
output_buffer.mix!(low_buffer).mix!(mid_buffer).mix!(high_buffer)
|
|
53
|
+
|
|
54
|
+
cache[cache_key] = output_buffer
|
|
55
|
+
output_buffer.dup
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def compressor_options(options)
|
|
61
|
+
{
|
|
62
|
+
threshold: options.fetch(:threshold, -24.0),
|
|
63
|
+
ratio: options.fetch(:ratio, 3.0),
|
|
64
|
+
attack: options.fetch(:attack, 0.01),
|
|
65
|
+
release: options.fetch(:release, 0.1)
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class MultibandSplit < Core::AudioNode
|
|
6
|
+
attr_reader :input, :high, :high_frequency, :low, :low_frequency, :mid, :output, :q
|
|
7
|
+
|
|
8
|
+
def initialize(low_frequency: 400.0, high_frequency: 2_500.0, q: 1.0, context: Deftones.context)
|
|
9
|
+
super(context: context)
|
|
10
|
+
@input = Core::Gain.new(context: context)
|
|
11
|
+
@output = self
|
|
12
|
+
@low_frequency = Core::Signal.new(value: low_frequency, units: :frequency, context: context)
|
|
13
|
+
@high_frequency = Core::Signal.new(value: high_frequency, units: :frequency, context: context)
|
|
14
|
+
@q = Core::Signal.new(value: q, units: :number, context: context)
|
|
15
|
+
@low = OutputTap.new(parent: self, band: :low, context: context)
|
|
16
|
+
@mid = OutputTap.new(parent: self, band: :mid, context: context)
|
|
17
|
+
@high = OutputTap.new(parent: self, band: :high, context: context)
|
|
18
|
+
@low_filters = []
|
|
19
|
+
@mid_highpasses = []
|
|
20
|
+
@mid_lowpasses = []
|
|
21
|
+
@high_filters = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def render(num_frames, start_frame = 0, cache = {})
|
|
25
|
+
render_block(num_frames, start_frame, cache).mono
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def render_band(band, num_frames, start_frame = 0, cache = {})
|
|
29
|
+
render_band_block(band, num_frames, start_frame, cache).mono
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def render_block(num_frames, start_frame = 0, cache = {})
|
|
33
|
+
cache_key = [object_id, :block, start_frame, num_frames]
|
|
34
|
+
return cache.fetch(cache_key).dup if cache.key?(cache_key)
|
|
35
|
+
|
|
36
|
+
bands = render_bands_block(num_frames, start_frame, cache)
|
|
37
|
+
channels = bands[:low].channels
|
|
38
|
+
output = Core::AudioBlock.silent(num_frames, channels)
|
|
39
|
+
output.mix!(bands[:low]).mix!(bands[:mid]).mix!(bands[:high])
|
|
40
|
+
|
|
41
|
+
cache[cache_key] = output
|
|
42
|
+
output.dup
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render_band_block(band, num_frames, start_frame = 0, cache = {})
|
|
46
|
+
render_bands_block(num_frames, start_frame, cache).fetch(band).dup
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reset!
|
|
50
|
+
[@low_filters, @mid_highpasses, @mid_lowpasses, @high_filters].each do |filters|
|
|
51
|
+
filters.each(&:reset!)
|
|
52
|
+
end
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def render_bands_block(num_frames, start_frame, cache)
|
|
59
|
+
cache_key = [object_id, :bands_block, start_frame, num_frames]
|
|
60
|
+
return cache.fetch(cache_key) if cache.key?(cache_key)
|
|
61
|
+
|
|
62
|
+
input_block = @input.send(:render_block, num_frames, start_frame, cache)
|
|
63
|
+
update_filters(input_block.channels, start_frame)
|
|
64
|
+
|
|
65
|
+
low = Array.new(input_block.channels) { Array.new(num_frames, 0.0) }
|
|
66
|
+
mid = Array.new(input_block.channels) { Array.new(num_frames, 0.0) }
|
|
67
|
+
high = Array.new(input_block.channels) { Array.new(num_frames, 0.0) }
|
|
68
|
+
|
|
69
|
+
input_block.channel_data.each_with_index do |channel, channel_index|
|
|
70
|
+
num_frames.times do |index|
|
|
71
|
+
sample = channel[index]
|
|
72
|
+
low[channel_index][index] = @low_filters[channel_index].process_sample(sample)
|
|
73
|
+
mid[channel_index][index] =
|
|
74
|
+
@mid_lowpasses[channel_index].process_sample(@mid_highpasses[channel_index].process_sample(sample))
|
|
75
|
+
high[channel_index][index] = @high_filters[channel_index].process_sample(sample)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
cache[cache_key] = {
|
|
80
|
+
low: Core::AudioBlock.from_channel_data(low),
|
|
81
|
+
mid: Core::AudioBlock.from_channel_data(mid),
|
|
82
|
+
high: Core::AudioBlock.from_channel_data(high)
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def update_filters(channels, start_frame)
|
|
87
|
+
ensure_filter_banks(channels)
|
|
88
|
+
current_low_frequency = @low_frequency.process(1, start_frame).first
|
|
89
|
+
current_high_frequency = @high_frequency.process(1, start_frame).first
|
|
90
|
+
current_q = @q.process(1, start_frame).first
|
|
91
|
+
|
|
92
|
+
@low_filters.each do |filter|
|
|
93
|
+
filter.update(type: :lowpass, frequency: current_low_frequency, q: current_q, gain_db: 0.0, sample_rate: context.sample_rate)
|
|
94
|
+
end
|
|
95
|
+
@mid_highpasses.each do |filter|
|
|
96
|
+
filter.update(type: :highpass, frequency: current_low_frequency, q: current_q, gain_db: 0.0, sample_rate: context.sample_rate)
|
|
97
|
+
end
|
|
98
|
+
@mid_lowpasses.each do |filter|
|
|
99
|
+
filter.update(type: :lowpass, frequency: current_high_frequency, q: current_q, gain_db: 0.0, sample_rate: context.sample_rate)
|
|
100
|
+
end
|
|
101
|
+
@high_filters.each do |filter|
|
|
102
|
+
filter.update(type: :highpass, frequency: current_high_frequency, q: current_q, gain_db: 0.0, sample_rate: context.sample_rate)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def ensure_filter_banks(channels)
|
|
107
|
+
required = [channels.to_i, 1].max
|
|
108
|
+
while @low_filters.length < required
|
|
109
|
+
@low_filters << DSP::Biquad.new
|
|
110
|
+
@mid_highpasses << DSP::Biquad.new
|
|
111
|
+
@mid_lowpasses << DSP::Biquad.new
|
|
112
|
+
@high_filters << DSP::Biquad.new
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
class OutputTap < Core::AudioNode
|
|
117
|
+
def initialize(parent:, band:, context: Deftones.context)
|
|
118
|
+
super(context: context)
|
|
119
|
+
@parent = parent
|
|
120
|
+
@band = band
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def render(num_frames, start_frame = 0, cache = {})
|
|
124
|
+
@parent.render_band(@band, num_frames, start_frame, cache)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def render_block(num_frames, start_frame = 0, cache = {})
|
|
128
|
+
@parent.render_band_block(@band, num_frames, start_frame, cache)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class OnePoleFilter < Core::AudioNode
|
|
6
|
+
TYPES = %i[lowpass highpass].freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :frequency
|
|
9
|
+
attr_accessor :type
|
|
10
|
+
|
|
11
|
+
def initialize(frequency: 880.0, type: :lowpass, context: Deftones.context)
|
|
12
|
+
super(context: context)
|
|
13
|
+
@frequency = Core::Signal.new(value: frequency, units: :frequency, context: context)
|
|
14
|
+
@type = normalize_type(type)
|
|
15
|
+
@lowpass_state = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def frequency=(value)
|
|
19
|
+
@frequency.value = value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def multichannel_process?
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def process(input_block, num_frames, start_frame, _cache)
|
|
27
|
+
frequencies = @frequency.process(num_frames, start_frame)
|
|
28
|
+
ensure_state(input_block.channels)
|
|
29
|
+
output_channels = input_block.channel_data.each_with_index.map do |channel, channel_index|
|
|
30
|
+
Array.new(num_frames) do |index|
|
|
31
|
+
input_sample = channel[index]
|
|
32
|
+
coefficient = coefficient_for(frequencies[index])
|
|
33
|
+
@lowpass_state[channel_index] += (1.0 - coefficient) * (input_sample - @lowpass_state[channel_index])
|
|
34
|
+
|
|
35
|
+
if normalize_type(@type) == :lowpass
|
|
36
|
+
@lowpass_state[channel_index]
|
|
37
|
+
else
|
|
38
|
+
input_sample - @lowpass_state[channel_index]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Core::AudioBlock.from_channel_data(output_channels)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def reset!
|
|
47
|
+
@lowpass_state = []
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def coefficient_for(frequency)
|
|
54
|
+
normalized = [[frequency.to_f, 1.0].max, (context.sample_rate * 0.49)].min
|
|
55
|
+
Math.exp((-2.0 * Math::PI * normalized) / context.sample_rate)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def normalize_type(type)
|
|
59
|
+
normalized = type.to_sym
|
|
60
|
+
return normalized if TYPES.include?(normalized)
|
|
61
|
+
|
|
62
|
+
raise ArgumentError, "Unsupported one pole filter type: #{type}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def ensure_state(channels)
|
|
66
|
+
required = [channels.to_i, 1].max
|
|
67
|
+
@lowpass_state.fill(0.0, @lowpass_state.length...required)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class PanVol < Core::AudioNode
|
|
6
|
+
attr_reader :input, :output, :panner, :volume
|
|
7
|
+
|
|
8
|
+
def initialize(pan: 0.0, volume: 0.0, context: Deftones.context)
|
|
9
|
+
super(context: context)
|
|
10
|
+
@panner = Panner.new(pan: pan, context: context)
|
|
11
|
+
@volume = Volume.new(volume: volume, context: context)
|
|
12
|
+
@input = @panner
|
|
13
|
+
@output = @volume
|
|
14
|
+
@panner >> @volume
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render(num_frames, start_frame = 0, cache = {})
|
|
18
|
+
@output.render(num_frames, start_frame, cache)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def render_block(num_frames, start_frame = 0, cache = {})
|
|
22
|
+
@output.send(:render_block, num_frames, start_frame, cache)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Deftones
|
|
4
|
+
module Component
|
|
5
|
+
class Panner < Core::AudioNode
|
|
6
|
+
attr_reader :pan
|
|
7
|
+
|
|
8
|
+
def initialize(pan: 0.0, context: Deftones.context)
|
|
9
|
+
super(context: context)
|
|
10
|
+
@pan = Core::Signal.new(value: pan, units: :number, context: context)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def pan=(value)
|
|
14
|
+
@pan.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
|
+
pans = @pan.process(num_frames, start_frame)
|
|
23
|
+
return process_mono_input(input_block, num_frames, pans) if input_block.channels == 1
|
|
24
|
+
|
|
25
|
+
stereo_input = input_block.fit_channels(2)
|
|
26
|
+
left = Array.new(num_frames)
|
|
27
|
+
right = Array.new(num_frames)
|
|
28
|
+
|
|
29
|
+
num_frames.times do |index|
|
|
30
|
+
left[index] = stereo_input.channel_data[0][index] * stereo_left_gain(pans[index])
|
|
31
|
+
right[index] = stereo_input.channel_data[1][index] * stereo_right_gain(pans[index])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Core::AudioBlock.from_channel_data([left, right])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def process_mono_input(input_block, num_frames, pans)
|
|
40
|
+
mono_input = input_block.mono
|
|
41
|
+
|
|
42
|
+
Core::AudioBlock.from_channel_data([
|
|
43
|
+
Array.new(num_frames) { |index| mono_input[index] * left_gain(pans[index]) },
|
|
44
|
+
Array.new(num_frames) { |index| mono_input[index] * right_gain(pans[index]) }
|
|
45
|
+
])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def stereo_left_gain(pan)
|
|
49
|
+
normalized = pan.to_f.clamp(-1.0, 1.0)
|
|
50
|
+
return 1.0 if normalized <= 0.0
|
|
51
|
+
|
|
52
|
+
Math.cos(normalized * Math::PI * 0.5)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stereo_right_gain(pan)
|
|
56
|
+
normalized = pan.to_f.clamp(-1.0, 1.0)
|
|
57
|
+
return 1.0 if normalized >= 0.0
|
|
58
|
+
|
|
59
|
+
Math.cos(normalized.abs * Math::PI * 0.5)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def left_gain(pan)
|
|
63
|
+
Math.cos(angle_for(pan))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def right_gain(pan)
|
|
67
|
+
Math.sin(angle_for(pan))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def angle_for(pan)
|
|
71
|
+
((pan.to_f.clamp(-1.0, 1.0) + 1.0) * Math::PI) * 0.25
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|