Class: Wavify::DSP::Envelope

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(attack:, decay:, sustain:, release:, hold: 0.0, curve: :linear) ⇒ Envelope

Returns a new instance of Envelope.

Parameters:

  • attack: (Numeric)
  • decay: (Numeric)
  • sustain: (Numeric)
  • release: (Numeric)
  • hold: (Numeric) (defaults to: 0.0)
  • curve: (Symbol) (defaults to: :linear)


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

#attackFloat (readonly)

Returns the value of attribute attack.

Returns:

  • (Float)


9
10
11
# File 'lib/wavify/dsp/envelope.rb', line 9

def attack
  @attack
end

#curveSymbol (readonly)

Returns the value of attribute curve.

Returns:

  • (Symbol)


9
10
11
# File 'lib/wavify/dsp/envelope.rb', line 9

def curve
  @curve
end

#decayFloat (readonly)

Returns the value of attribute decay.

Returns:

  • (Float)


9
10
11
# File 'lib/wavify/dsp/envelope.rb', line 9

def decay
  @decay
end

#holdFloat (readonly)

Returns the value of attribute hold.

Returns:

  • (Float)


9
10
11
# File 'lib/wavify/dsp/envelope.rb', line 9

def hold
  @hold
end

#releaseFloat (readonly)

Returns the value of attribute release.

Returns:

  • (Float)


9
10
11
# File 'lib/wavify/dsp/envelope.rb', line 9

def release
  @release
end

#sustainFloat (readonly)

Returns the value of attribute sustain.

Returns:

  • (Float)


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.

Parameters:

Returns:

Raises:



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.

Parameters:

  • time (Numeric)
  • note_on_duration (Numeric)
  • note_on_duration: (Numeric)

Returns:

  • (Float)


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

#latencyFloat

Returns:

  • (Float)


90
91
92
# File 'lib/wavify/dsp/envelope.rb', line 90

def latency
  0.0
end

#lookaheadFloat

Returns:

  • (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.

Parameters:

Returns:

Raises:



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_durationFloat

Returns:

  • (Float)


86
87
88
# File 'lib/wavify/dsp/envelope.rb', line 86

def tail_duration
  @release
end