Class: Deftones::Component::Envelope

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

Direct Known Subclasses

AmplitudeEnvelope, FrequencyEnvelope

Constant Summary collapse

STATES =
%i[idle attack decay sustain release].freeze

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, #render_block, #set, #to_destination, #to_frequency, #to_master, #to_midi, #to_output, #to_s, #to_seconds, #to_ticks, #uses_legacy_render_for_block?, #validate_connectable!, #validate_connection_index!

Constructor Details

#initialize(attack: 0.01, decay: 0.1, sustain: 0.5, release: 1.0, context: Deftones.context) ⇒ Envelope

Returns a new instance of Envelope.



11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/deftones/component/envelope.rb', line 11

def initialize(attack: 0.01, decay: 0.1, sustain: 0.5, release: 1.0, context: Deftones.context)
  super(context: context)
  @attack = attack.to_f
  @decay = decay.to_f
  @sustain = sustain.to_f
  @release = release.to_f
  @state = :idle
  @events = []
  @current_value = 0.0
  @velocity = 1.0
  @stage_started_at = 0.0
  @stage_from_value = 0.0
end

Instance Attribute Details

#attackObject

Returns the value of attribute attack.



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

def attack
  @attack
end

#decayObject

Returns the value of attribute decay.



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

def decay
  @decay
end

#releaseObject

Returns the value of attribute release.



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

def release
  @release
end

#stateObject (readonly)

Returns the value of attribute state.



9
10
11
# File 'lib/deftones/component/envelope.rb', line 9

def state
  @state
end

#sustainObject

Returns the value of attribute sustain.



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

def sustain
  @sustain
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


39
40
41
# File 'lib/deftones/component/envelope.rb', line 39

def active?
  @state != :idle || !@events.empty?
end

#attack_value(time) ⇒ Object (private)



109
110
111
112
113
114
115
116
# File 'lib/deftones/component/envelope.rb', line 109

def attack_value(time)
  return transition_to(:decay, @velocity, time) if @attack <= 0.0

  progress = (time - @stage_started_at) / @attack
  return transition_to(:decay, @velocity, @stage_started_at + @attack) if progress >= 1.0

  lerp(@stage_from_value, @velocity, progress)
end

#consume_events(time) ⇒ Object (private)



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/deftones/component/envelope.rb', line 74

def consume_events(time)
  while @events.any? && @events.first[:time] <= time
    event = @events.shift

    case event[:type]
    when :attack
      @state = :attack
      @velocity = event[:velocity] || 1.0
      @stage_started_at = event[:time]
      @stage_from_value = @current_value
    when :release
      @state = :release
      @stage_started_at = event[:time]
      @stage_from_value = @current_value
    end
  end
end

#decay_value(time) ⇒ Object (private)



118
119
120
121
122
123
124
125
126
# File 'lib/deftones/component/envelope.rb', line 118

def decay_value(time)
  sustain_level = @velocity * @sustain
  return transition_to(:sustain, sustain_level, time) if @decay <= 0.0

  progress = (time - @stage_started_at) / @decay
  return transition_to(:sustain, sustain_level, @stage_started_at + @decay) if progress >= 1.0

  lerp(@velocity, sustain_level, progress)
end

#envelope_value_at(time) ⇒ Object (private)



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/deftones/component/envelope.rb', line 92

def envelope_value_at(time)
  case @state
  when :idle
    0.0
  when :attack
    attack_value(time)
  when :decay
    decay_value(time)
  when :sustain
    @velocity * @sustain
  when :release
    release_value(time)
  else
    0.0
  end
end

#idle?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/deftones/component/envelope.rb', line 43

def idle?
  !active?
end

#lerp(from, to, progress) ⇒ Object (private)



145
146
147
# File 'lib/deftones/component/envelope.rb', line 145

def lerp(from, to, progress)
  from + ((to - from) * progress.clamp(0.0, 1.0))
end

#multichannel_process?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/deftones/component/envelope.rb', line 47

def multichannel_process?
  true
end

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



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/deftones/component/envelope.rb', line 51

def process(input_block, num_frames, start_frame, _cache)
  values = Array.new(num_frames) do |index|
    time = sample_time(start_frame + index)
    consume_events(time)
    @current_value = envelope_value_at(time)
    @current_value
  end

  Core::AudioBlock.from_channel_data(
    input_block.channel_data.map do |channel|
      Array.new(num_frames) { |index| channel[index] * values[index] }
    end
  )
end

#release_value(time) ⇒ Object (private)



128
129
130
131
132
133
134
135
# File 'lib/deftones/component/envelope.rb', line 128

def release_value(time)
  return transition_to(:idle, 0.0, time) if @release <= 0.0

  progress = (time - @stage_started_at) / @release
  return transition_to(:idle, 0.0, @stage_started_at + @release) if progress >= 1.0

  lerp(@stage_from_value, 0.0, progress)
end

#resolve_time(time) ⇒ Object (private)



149
150
151
152
153
# File 'lib/deftones/component/envelope.rb', line 149

def resolve_time(time)
  return context.current_time if time.nil?

  Deftones::Music::Time.parse(time)
end

#sample_time(frame_index) ⇒ Object (private)



155
156
157
# File 'lib/deftones/component/envelope.rb', line 155

def sample_time(frame_index)
  frame_index.to_f / context.sample_rate
end

#schedule_event(type, time, velocity) ⇒ Object (private)



68
69
70
71
72
# File 'lib/deftones/component/envelope.rb', line 68

def schedule_event(type, time, velocity)
  @events << { type: type, time: time, velocity: velocity }
  @events.sort_by! { |event| event[:time] }
  self
end

#transition_to(next_state, value, time) ⇒ Object (private)



137
138
139
140
141
142
143
# File 'lib/deftones/component/envelope.rb', line 137

def transition_to(next_state, value, time)
  @state = next_state
  @stage_started_at = time
  @stage_from_value = value
  @current_value = value
  value
end

#trigger_attack(time = nil, velocity = 1.0) ⇒ Object



25
26
27
# File 'lib/deftones/component/envelope.rb', line 25

def trigger_attack(time = nil, velocity = 1.0)
  schedule_event(:attack, resolve_time(time), velocity.to_f)
end

#trigger_attack_release(duration, time = nil, velocity = 1.0) ⇒ Object



33
34
35
36
37
# File 'lib/deftones/component/envelope.rb', line 33

def trigger_attack_release(duration, time = nil, velocity = 1.0)
  attack_time = resolve_time(time)
  trigger_attack(attack_time, velocity)
  trigger_release(attack_time + Deftones::Music::Time.parse(duration))
end

#trigger_release(time = nil) ⇒ Object



29
30
31
# File 'lib/deftones/component/envelope.rb', line 29

def trigger_release(time = nil)
  schedule_event(:release, resolve_time(time), nil)
end