Class: Deftones::Effects::PitchShift

Inherits:
Core::Effect show all
Defined in:
lib/deftones/effect/pitch_shift.rb

Instance Attribute Summary collapse

Attributes inherited from Core::Effect

#wet

Instance Method Summary collapse

Methods inherited from Core::Effect

#multichannel_process?, #normalize_channel_output, #process, #process_effect

Constructor Details

#initialize(pitch: nil, semitones: 0.0, window: 0.1, window_size: nil, delay_time: nil, feedback: 0.0, context: Deftones.context, **options) ⇒ PitchShift

Returns a new instance of PitchShift.



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

def initialize(
  pitch: nil,
  semitones: 0.0,
  window: 0.1,
  window_size: nil,
  delay_time: nil,
  feedback: 0.0,
  context: Deftones.context,
  **options
)
  super(context: context, **options)
  @pitch = pitch.nil? ? semitones.to_f : pitch.to_f
  @window_size = resolve_window_size(window_size || window)
  @delay_time = Core::Signal.new(
    value: delay_time.nil? ? default_delay_time : delay_time,
    units: :time,
    context: context
  )
  @feedback = Core::Signal.new(value: feedback, units: :number, context: context)
  @phase = 0.0
  @delay_lines = []
  @max_delay_samples = 0
  ensure_delay_line_capacity!(1, @delay_time.value.to_f, pitch_ratio)
end

Instance Attribute Details

#delay_timeObject (readonly) Also known as: delayTime

Returns the value of attribute delay_time.



6
7
8
# File 'lib/deftones/effect/pitch_shift.rb', line 6

def delay_time
  @delay_time
end

#feedbackObject (readonly)

Returns the value of attribute feedback.



6
7
8
# File 'lib/deftones/effect/pitch_shift.rb', line 6

def feedback
  @feedback
end

#pitchObject

Returns the value of attribute pitch.



6
7
8
# File 'lib/deftones/effect/pitch_shift.rb', line 6

def pitch
  @pitch
end

#window_sizeObject Also known as: windowSize

Returns the value of attribute window_size.



7
8
9
# File 'lib/deftones/effect/pitch_shift.rb', line 7

def window_size
  @window_size
end

Instance Method Details

#advance_phaseObject (private)



112
113
114
# File 'lib/deftones/effect/pitch_shift.rb', line 112

def advance_phase
  @phase = (@phase + phase_step) % 1.0
end

#default_delay_timeObject (private)



150
151
152
# File 'lib/deftones/effect/pitch_shift.rb', line 150

def default_delay_time
  @window_size * 0.5
end

#ensure_delay_line_capacity!(channels, delay_seconds, ratio) ⇒ Object (private)



136
137
138
139
140
141
142
143
144
# File 'lib/deftones/effect/pitch_shift.rb', line 136

def ensure_delay_line_capacity!(channels, delay_seconds, ratio)
  required = [required_delay_samples(delay_seconds, ratio), 2].max
  required_channels = [channels.to_i, 1].max
  needs_resize = required > @max_delay_samples || @delay_lines.length < required_channels
  return unless needs_resize

  @max_delay_samples = required
  @delay_lines = Array.new(required_channels) { DSP::DelayLine.new(@max_delay_samples) }
end

#head_delay_samples(phase, delay_seconds, ratio) ⇒ Object (private)



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/deftones/effect/pitch_shift.rb', line 96

def head_delay_samples(phase, delay_seconds, ratio)
  base_delay = [delay_seconds, context.sample_time].max * context.sample_rate
  sweep = sweep_span_samples(ratio)
  offset = if ratio >= 1.0
             (1.0 - phase) * sweep
           else
             phase * sweep
           end

  [base_delay + offset, 1.0].max
end

#head_gain(phase) ⇒ Object (private)



108
109
110
# File 'lib/deftones/effect/pitch_shift.rb', line 108

def head_gain(phase)
  Math.sin(Math::PI * phase)**2
end

#phase_stepObject (private)



116
117
118
# File 'lib/deftones/effect/pitch_shift.rb', line 116

def phase_step
  1.0 / (@window_size * context.sample_rate)
end

#pitch_ratioObject (private)



120
121
122
# File 'lib/deftones/effect/pitch_shift.rb', line 120

def pitch_ratio
  2.0**(@pitch.to_f / 12.0)
end

#process_effect_block(input_block, num_frames, start_frame, _cache) ⇒ Object (private)



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/deftones/effect/pitch_shift.rb', line 61

def process_effect_block(input_block, num_frames, start_frame, _cache)
  delays = @delay_time.process(num_frames, start_frame)
  feedbacks = @feedback.process(num_frames, start_frame)
  ratio = pitch_ratio
  ensure_delay_line_capacity!(input_block.channels, delays.max.to_f, ratio)
  output = Array.new(input_block.channels) { Array.new(num_frames, 0.0) }

  num_frames.times do |index|
    delay_seconds = delays[index].to_f
    feedback = feedbacks[index].to_f.clamp(-0.999, 0.999)
    phase = @phase
    input_block.channel_data.each_with_index do |channel, channel_index|
      sample = shifted_sample(@delay_lines[channel_index], delay_seconds, ratio, phase)
      @delay_lines[channel_index].write(channel[index] + (sample * feedback))
      output[channel_index][index] = sample
    end
    advance_phase
  end

  Core::AudioBlock.from_channel_data(output)
end

#required_delay_samples(delay_seconds, ratio) ⇒ Object (private)



128
129
130
131
132
133
134
# File 'lib/deftones/effect/pitch_shift.rb', line 128

def required_delay_samples(delay_seconds, ratio)
  (
    ([delay_seconds, context.sample_time].max * context.sample_rate) +
    sweep_span_samples(ratio) +
    (@window_size * context.sample_rate)
  ).ceil + 2
end

#resolve_window_size(value) ⇒ Object (private)



146
147
148
# File 'lib/deftones/effect/pitch_shift.rb', line 146

def resolve_window_size(value)
  [value.to_f, context.sample_time * 2.0].max
end

#semitonesObject



34
35
36
# File 'lib/deftones/effect/pitch_shift.rb', line 34

def semitones
  @pitch
end

#semitones=(value) ⇒ Object



38
39
40
# File 'lib/deftones/effect/pitch_shift.rb', line 38

def semitones=(value)
  self.pitch = value
end

#shifted_head_sample(delay_line, phase, delay_seconds, ratio) ⇒ Object (private)



91
92
93
94
# File 'lib/deftones/effect/pitch_shift.rb', line 91

def shifted_head_sample(delay_line, phase, delay_seconds, ratio)
  delay_samples = head_delay_samples(phase, delay_seconds, ratio)
  delay_line.read(delay_samples)
end

#shifted_sample(delay_line, delay_seconds, ratio, phase) ⇒ Object (private)



83
84
85
86
87
88
89
# File 'lib/deftones/effect/pitch_shift.rb', line 83

def shifted_sample(delay_line, delay_seconds, ratio, phase)
  primary_phase = phase
  secondary_phase = (phase + 0.5) % 1.0
  primary = shifted_head_sample(delay_line, primary_phase, delay_seconds, ratio)
  secondary = shifted_head_sample(delay_line, secondary_phase, delay_seconds, ratio)
  (primary * head_gain(primary_phase)) + (secondary * head_gain(secondary_phase))
end

#sweep_span_samples(ratio) ⇒ Object (private)



124
125
126
# File 'lib/deftones/effect/pitch_shift.rb', line 124

def sweep_span_samples(ratio)
  @window_size * context.sample_rate * (1.0 - ratio).abs
end

#windowSize=(value) ⇒ Object



55
56
57
# File 'lib/deftones/effect/pitch_shift.rb', line 55

def windowSize=(value)
  self.window_size = value
end