Class: Wavify::DSP::Envelope
- Inherits:
-
Object
- Object
- Wavify::DSP::Envelope
- Defined in:
- lib/wavify/dsp/envelope.rb,
sig/dsp.rbs
Overview
AHDSR envelope generator and buffer processor.
Constant Summary collapse
- CURVES =
:nodoc:
%i[linear exp log].freeze
Instance Attribute Summary collapse
-
#attack ⇒ Float
readonly
Returns the value of attribute attack.
-
#curve ⇒ Symbol
readonly
Returns the value of attribute curve.
-
#decay ⇒ Float
readonly
Returns the value of attribute decay.
-
#hold ⇒ Float
readonly
Returns the value of attribute hold.
-
#release ⇒ Float
readonly
Returns the value of attribute release.
-
#sustain ⇒ Float
readonly
Returns the value of attribute sustain.
Instance Method Summary collapse
-
#apply(buffer, note_on_duration:) ⇒ Wavify::Core::SampleBuffer
Applies the envelope to a sample buffer.
-
#gain_at(time, note_on_duration:) ⇒ Float
Computes envelope gain at a given playback time.
-
#initialize(attack:, decay:, sustain:, release:, hold: 0.0, curve: :linear) ⇒ Envelope
constructor
A new instance of Envelope.
- #latency ⇒ Float
- #lookahead ⇒ Float
-
#render_with_tail(buffer, note_on_duration:) ⇒ Core::SampleBuffer
Applies the envelope and extends a short source through the release.
- #tail_duration ⇒ Float
Constructor Details
#initialize(attack:, decay:, sustain:, release:, hold: 0.0, curve: :linear) ⇒ Envelope
Returns a new instance of Envelope.
11 12 13 14 15 16 17 18 |
# File 'lib/wavify/dsp/envelope.rb', line 11 def initialize(attack:, decay:, sustain:, release:, hold: 0.0, curve: :linear) @attack = validate_time!(attack, :attack) @hold = validate_time!(hold, :hold) @decay = validate_time!(decay, :decay) @sustain = validate_sustain!(sustain) @release = validate_time!(release, :release) @curve = validate_curve!(curve) end |
Instance Attribute Details
#attack ⇒ Float (readonly)
Returns the value of attribute attack.
9 10 11 |
# File 'lib/wavify/dsp/envelope.rb', line 9 def attack @attack end |
#curve ⇒ Symbol (readonly)
Returns the value of attribute curve.
9 10 11 |
# File 'lib/wavify/dsp/envelope.rb', line 9 def curve @curve end |
#decay ⇒ Float (readonly)
Returns the value of attribute decay.
9 10 11 |
# File 'lib/wavify/dsp/envelope.rb', line 9 def decay @decay end |
#hold ⇒ Float (readonly)
Returns the value of attribute hold.
9 10 11 |
# File 'lib/wavify/dsp/envelope.rb', line 9 def hold @hold end |
#release ⇒ Float (readonly)
Returns the value of attribute release.
9 10 11 |
# File 'lib/wavify/dsp/envelope.rb', line 9 def release @release end |
#sustain ⇒ Float (readonly)
Returns the value of attribute sustain.
9 10 11 |
# File 'lib/wavify/dsp/envelope.rb', line 9 def sustain @sustain end |
Instance Method Details
#apply(buffer, note_on_duration:) ⇒ Wavify::Core::SampleBuffer
Applies the envelope to a sample buffer.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/wavify/dsp/envelope.rb', line 44 def apply(buffer, note_on_duration:) raise InvalidParameterError, "buffer must be Core::SampleBuffer" unless buffer.is_a?(Core::SampleBuffer) unless finite_nonnegative?(note_on_duration) raise InvalidParameterError, "note_on_duration must be a non-negative finite Numeric" end float_format = buffer.format.with(sample_format: :float, bit_depth: 32) float_buffer = buffer.convert(float_format) channels = float_format.channels sample_rate = float_format.sample_rate processed = float_buffer.samples.dup processed.each_slice(channels).with_index do |frame, frame_index| gain = gain_at(frame_index.to_f / sample_rate, note_on_duration: note_on_duration) base = frame_index * channels frame.each_index do |channel_index| processed[base + channel_index] = frame[channel_index] * gain end end Core::SampleBuffer.new(processed, float_format).convert(buffer.format) end |
#gain_at(time, note_on_duration:) ⇒ Float
Computes envelope gain at a given playback time.
25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/wavify/dsp/envelope.rb', line 25 def gain_at(time, note_on_duration:) unless finite_nonnegative?(time) raise InvalidParameterError, "time must be a non-negative finite Numeric" end unless finite_nonnegative?(note_on_duration) raise InvalidParameterError, "note_on_duration must be a non-negative finite Numeric" end return sustain_stage_gain(time) if time < note_on_duration release_time = time - note_on_duration release_gain(release_time, start_gain: sustain_stage_gain(note_on_duration)) end |
#latency ⇒ Float
90 91 92 |
# File 'lib/wavify/dsp/envelope.rb', line 90 def latency 0.0 end |
#lookahead ⇒ Float
94 95 96 |
# File 'lib/wavify/dsp/envelope.rb', line 94 def lookahead 0.0 end |
#render_with_tail(buffer, note_on_duration:) ⇒ Core::SampleBuffer
Applies the envelope and extends a short source through the release. The terminal source frame is held when more source audio is required.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/wavify/dsp/envelope.rb', line 69 def render_with_tail(buffer, note_on_duration:) raise InvalidParameterError, "buffer must be Core::SampleBuffer" unless buffer.is_a?(Core::SampleBuffer) unless finite_nonnegative?(note_on_duration) raise InvalidParameterError, "note_on_duration must be a non-negative finite Numeric" end target_frames = ((note_on_duration + @release) * buffer.format.sample_rate).ceil return apply(buffer, note_on_duration: note_on_duration) if buffer.sample_frame_count >= target_frames channels = buffer.format.channels terminal_frame = buffer.samples.last(channels) terminal_frame = Array.new(channels, 0) if terminal_frame.empty? missing_frames = target_frames - buffer.sample_frame_count extended = buffer.concat(Core::SampleBuffer.new(terminal_frame * missing_frames, buffer.format)) apply(extended, note_on_duration: note_on_duration) end |
#tail_duration ⇒ Float
86 87 88 |
# File 'lib/wavify/dsp/envelope.rb', line 86 def tail_duration @release end |