Vital
Loading...
Searching...
No Matches
envelope.cpp
Go to the documentation of this file.
1#include "envelope.h"
2#include "futils.h"
3
4namespace vital {
5
7 Processor(kNumInputs, kNumOutputs),
8 current_value_(0.0f),
9 position_(0.0f),
10 value_(0.0f),
11 poly_state_(0.0f),
12 start_value_(0.0f),
13 attack_power_(0.0f),
14 decay_power_(0.0f),
15 release_power_(0.0f),
16 sustain_(0.0f) { }
17
18 void Envelope::process(int num_samples) {
19 // Determine if we should process at control rate or audio rate based on the Processor base class logic.
20 if (isControlRate())
21 processControlRate(num_samples);
22 else
23 processAudioRate(num_samples);
24 }
25
26 void Envelope::processControlRate(int num_samples) {
34 poly_mask trigger_mask = input(kTrigger)->source->trigger_mask;
35 poly_float trigger_value = input(kTrigger)->source->trigger_value;
36 poly_float delay_time = utils::max(input(kDelay)->at(0), 0.0f);
37 poly_mask has_delay_mask = poly_float::notEqual(delay_time, 0.0f);
38 poly_mask note_on_mask = poly_float::equal(trigger_value, kVoiceOn);
39 trigger_value = utils::maskLoad(trigger_value, kVoiceIdle, has_delay_mask & note_on_mask);
40
41 poly_state_ = utils::maskLoad(poly_state_, trigger_value, trigger_mask);
42 position_ = utils::maskLoad(position_, 0.0f, trigger_mask);
43
44 poly_int triggered_remaining = poly_int(num_samples) - input(kTrigger)->source->trigger_offset;
45 poly_int remaining_samples = utils::maskLoad(num_samples, triggered_remaining, trigger_mask);
46 start_value_ = utils::maskLoad(start_value_, value_, trigger_mask);
47
48 // Identify which segment the envelope is currently in
49 poly_mask delay_mask = poly_float::equal(poly_state_, kVoiceIdle);
50 poly_mask attack_mask = poly_float::equal(poly_state_, kVoiceOn);
51 poly_mask hold_mask = poly_float::equal(poly_state_, kVoiceHold);
52 poly_mask decay_mask = poly_float::equal(poly_state_, kVoiceDecay);
53 poly_mask release_mask = poly_float::equal(poly_state_, kVoiceOff);
54 poly_mask kill_mask = poly_float::equal(poly_state_, kVoiceKill);
55
56 // Compute the normalized increments for each stage based on remaining samples and sample rate
57 poly_float delta_time = utils::toFloat(remaining_samples) * (1.0f / getSampleRate());
58 poly_float delta_delay = delta_time / utils::max(delay_time, 0.0000001f);
59 position_ += delta_delay & delay_mask;
60
61 poly_float attack_time = utils::max(input(kAttack)->at(0), 0.000000001f);
62 poly_float delta_attack = delta_time / attack_time;
63 position_ += delta_attack & attack_mask;
64
65 poly_float hold_time = utils::max(input(kHold)->at(0), 0.0f);
66 poly_mask has_hold_mask = poly_float::notEqual(hold_time, 0.0f);
67 poly_float delta_hold = delta_time / utils::max(hold_time, 0.0000001f);
68 position_ += delta_hold & hold_mask;
69
70 poly_float decay_time = utils::max(input(kDecay)->at(0), 0.000000001f);
71 poly_float delta_decay = delta_time / decay_time;
72 position_ += delta_decay & decay_mask;
73
74 poly_float release_time = utils::max(input(kRelease)->at(0), 0.000000001f);
75 poly_float delta_release = delta_time / release_time;
76 position_ += delta_release & release_mask;
77
78 poly_float delta_kill = delta_time * (1.0f / kVoiceKillTime);
79 position_ += delta_kill & kill_mask;
80 position_ = utils::clamp(position_, 0.0f, 1.0f);
81
82 // Apply power scaling to shape the envelope curve
83 poly_float power = (-input(kAttackPower)->at(0) & attack_mask) +
84 (input(kDecayPower)->at(0) & decay_mask) +
85 (input(kReleasePower)->at(0) & release_mask);
86
87 poly_float attack_value = futils::powerScale(position_, power);
88 poly_float sustain = input(kSustain)->at(0);
89
90 // Interpolate the envelope value based on the current segment
91 poly_float decay_value = poly_float(1.0f) - (poly_float(1.0f) - sustain) * attack_value;
92 poly_float release_value = start_value_ * (poly_float(1.0f) - attack_value);
93 poly_float kill_value = start_value_ * (poly_float(1.0f) - attack_value);
94
95 value_ = (attack_value & attack_mask) +
96 (poly_float(1.0f) & hold_mask) +
97 (decay_value & decay_mask) +
98 (release_value & release_mask) +
99 (kill_value & kill_mask);
100
101 value_ = utils::clamp(value_, 0.0f, 1.0f);
102
103 // Output the computed envelope values
104 output(kValue)->trigger_value = value_;
105 output(kValue)->buffer[0] = value_;
106 output(kPhase)->buffer[0] = poly_state_ + position_;
107
108 // Check for state transitions
109 poly_mask attack_transition_mask = delay_mask & poly_float::equal(position_, 1.0f);
110 poly_mask hold_transition_mask = attack_mask & poly_float::equal(position_, 1.0f) & has_hold_mask;
111 poly_mask decay_turn_mask = (attack_mask & ~has_hold_mask) | hold_mask;
112 poly_mask decay_transition_mask = decay_turn_mask & poly_float::equal(position_, 1.0f);
113
114 poly_state_ = utils::maskLoad(poly_state_, kVoiceOn, attack_transition_mask);
115 poly_state_ = utils::maskLoad(poly_state_, kVoiceHold, hold_transition_mask);
116 poly_state_ = utils::maskLoad(poly_state_, kVoiceDecay, decay_transition_mask);
117
118 poly_mask transition_mask = attack_transition_mask | hold_transition_mask | decay_transition_mask;
119 position_ = position_ & ~transition_mask;
120
121 poly_mask dead_transition_mask = release_mask & poly_float::equal(position_, 1.0f);
122 poly_state_ = utils::maskLoad(poly_state_, kVoiceKill, dead_transition_mask);
123 }
124
125 poly_float Envelope::processSection(poly_float* audio_out, int from, int to,
126 poly_float power, poly_float delta_power,
127 poly_float position, poly_float delta_position,
128 poly_float start, poly_float end, poly_float delta_end) {
136 int num_samples = to - from;
137 poly_float current_power = power;
138 poly_float current_position = position;
139 poly_float current_end = end;
140
141 for (int i = from; i < to; ++i) {
142 poly_float t = futils::powerScale(current_position, current_power);
143 audio_out[i] = utils::interpolate(start, current_end, t);
144
145 current_power += delta_power;
146 current_position = utils::clamp(current_position + delta_position, 0.0f, 1.0f);
147 current_end += delta_end;
148 }
149
150 return utils::clamp(position + delta_position * num_samples, 0.0f, 1.0f);
151 }
152
153 void Envelope::processAudioRate(int num_samples) {
161 poly_float delta_time = 1.0f / getSampleRate();
162 mono_float delta_sample = 1.0f / num_samples;
163
164 poly_float sustain_end = utils::clamp(input(kSustain)->at(0), 0.0f, 1.0f);
165
166 poly_float delay_time = utils::max(input(kDelay)->at(0), 0.0f);
167 poly_float delta_delay = delta_time / utils::max(delay_time, 0.0000001f);
168
169 poly_float attack_time = utils::max(input(kAttack)->at(0), 0.000000001f);
170 poly_float delta_attack = delta_time / attack_time;
171 poly_float attack_power_end = -input(kAttackPower)->at(0);
172
173 poly_float hold_time = utils::max(input(kHold)->at(0), 0.0f);
174 poly_mask has_hold_mask = poly_float::notEqual(hold_time, 0.0f);
175 poly_float delta_hold = delta_time / utils::max(hold_time, 0.0000001f);
176
177 poly_float decay_time = utils::max(input(kDecay)->at(0), 0.000000001f);
178 poly_float delta_decay = delta_time / decay_time;
179 poly_float decay_power_end = input(kDecayPower)->at(0);
180
181 poly_float release_time = utils::max(input(kRelease)->at(0), 0.000000001f);
182 poly_float delta_release = delta_time / release_time;
183 poly_float release_power_end = input(kReleasePower)->at(0);
184
185 poly_float delta_kill = delta_time * (1.0f / kVoiceKillTime);
186
187 poly_mask trigger_mask = input(kTrigger)->source->trigger_mask;
188 poly_float trigger_value = input(kTrigger)->source->trigger_value;
189 poly_mask has_delay_mask = poly_float::notEqual(delay_time, 0.0f);
190 poly_mask note_on_mask = poly_float::equal(trigger_value, kVoiceOn);
191 trigger_value = utils::maskLoad(trigger_value, kVoiceIdle, has_delay_mask & note_on_mask);
192
193 poly_int triggered_position = input(kTrigger)->source->trigger_offset;
194 triggered_position = utils::maskLoad(num_samples, triggered_position, trigger_mask);
195
196 poly_float current_position = position_;
197 poly_float* audio_out = output(kValue)->buffer;
198
199 // Step through each sample at audio rate, updating envelope state and output
200 for (int i = 0; i < num_samples;) {
201 poly_mask triggering = trigger_mask & poly_int::equal(i, triggered_position);
202 triggered_position = utils::maskLoad(triggered_position, num_samples, triggering);
203 poly_state_ = utils::maskLoad(poly_state_, trigger_value, triggering);
204 current_position = utils::maskLoad(current_position, 0.0f, triggering);
205
206 // Update start values and powers on triggers
207 start_value_ = utils::maskLoad(start_value_, value_, triggering);
208 attack_power_ = utils::maskLoad(attack_power_, attack_power_end, triggering);
209 decay_power_ = utils::maskLoad(decay_power_, decay_power_end, triggering);
210 release_power_ = utils::maskLoad(release_power_, release_power_end, triggering);
211 sustain_ = utils::maskLoad(sustain_, sustain_end, triggering);
212
213 // Identify envelope stages
214 poly_mask delay_mask = poly_float::equal(poly_state_, kVoiceIdle);
215 poly_mask attack_mask = poly_float::equal(poly_state_, kVoiceOn);
216 poly_mask hold_mask = poly_float::equal(poly_state_, kVoiceHold);
217 poly_mask decay_mask = poly_float::equal(poly_state_, kVoiceDecay);
218 poly_mask release_mask = poly_float::equal(poly_state_, kVoiceOff);
219 poly_mask kill_mask = poly_float::equal(poly_state_, kVoiceKill);
220
221 // Determine the current segment increment
222 poly_float delta_position = (delta_delay & delay_mask) + (delta_attack & attack_mask) +
223 (delta_hold & hold_mask) + (delta_decay & decay_mask) +
224 (delta_release & release_mask) + (delta_kill & kill_mask);
225
226 poly_float from_power = (attack_power_ & attack_mask) +
227 (decay_power_ & decay_mask) +
228 (release_power_ & release_mask);
229 poly_float to_power = (attack_power_end & attack_mask) +
230 (decay_power_end & decay_mask) +
231 (release_power_end & release_mask);
232
233 // Interpolate power and sustain based on current sample position
234 poly_float power = utils::interpolate(from_power, to_power, i / (1.0f * num_samples));
235 poly_float delta_power = (to_power - from_power) * delta_sample;
236
237 poly_float cycles_remaining = (utils::ceil(current_position) - current_position) / delta_position;
238 poly_float end_cycle = utils::maskLoad(num_samples, cycles_remaining + i, attack_mask);
239 end_cycle = utils::min(end_cycle, utils::toFloat(triggered_position));
240 int last_cycle = std::max((int)utils::minFloat(end_cycle), i + 1);
241
242 poly_float current_sustain = utils::interpolate(sustain_, sustain_end, i / (1.0f * num_samples));
243 poly_float start = utils::maskLoad(start_value_, 1.0f, decay_mask | hold_mask);
244 poly_float end = (poly_float(1.0f) & (attack_mask | hold_mask)) + (current_sustain & decay_mask);
245 poly_float delta_end = ((sustain_end - sustain_) * delta_sample) & decay_mask;
246
247 // Process the segment up to the calculated endpoint
248 current_position = processSection(audio_out, i, last_cycle, power, delta_power,
249 current_position, delta_position, start, end, delta_end);
250 i = last_cycle;
251
252 value_ = audio_out[i - 1];
253
254 // Check and handle state transitions after processing the segment
255 poly_mask attack_transition_mask = delay_mask & poly_float::equal(current_position, 1.0f);
256 poly_mask hold_transition_mask = attack_mask & poly_float::equal(current_position, 1.0f) & has_hold_mask;
257 poly_mask decay_turn_mask = (attack_mask & ~has_hold_mask) | hold_mask;
258 poly_mask decay_transition_mask = decay_turn_mask & poly_float::equal(current_position, 1.0f);
259
260 poly_state_ = utils::maskLoad(poly_state_, kVoiceOn, attack_transition_mask);
261 poly_state_ = utils::maskLoad(poly_state_, kVoiceHold, hold_transition_mask);
262 poly_state_ = utils::maskLoad(poly_state_, kVoiceDecay, decay_transition_mask);
263
264 poly_mask transition_mask = attack_transition_mask | hold_transition_mask | decay_transition_mask;
265 current_position = current_position & ~transition_mask;
266
267 poly_mask dead_transition_mask = release_mask & poly_float::equal(current_position, 1.0f);
268 poly_state_ = utils::maskLoad(poly_state_, kVoiceKill, dead_transition_mask);
269 }
270
271 output(kValue)->trigger_value = audio_out[0];
272 position_ = current_position;
273 attack_power_ = attack_power_end;
274 decay_power_ = decay_power_end;
275 release_power_ = release_power_end;
276 sustain_ = sustain_end;
277 output(kPhase)->buffer[0] = poly_state_ + position_;
278 }
279
280} // namespace vital
virtual void process(int num_samples) override
Processes a block of samples. The processing mode (control-rate or audio-rate) is determined by the p...
Definition envelope.cpp:18
@ kReleasePower
Definition envelope.h:41
@ kDelay
Definition envelope.h:33
@ kDecay
Definition envelope.h:37
@ kAttackPower
Definition envelope.h:35
@ kSustain
Definition envelope.h:39
@ kTrigger
Definition envelope.h:42
@ kHold
Definition envelope.h:36
@ kAttack
Definition envelope.h:34
@ kRelease
Definition envelope.h:40
@ kDecayPower
Definition envelope.h:38
@ kValue
Definition envelope.h:53
@ kPhase
Definition envelope.h:54
Envelope()
Constructs a new Envelope processor with default parameters.
Definition envelope.cpp:6
Base class for all signal-processing units in Vital.
Definition processor.h:212
force_inline Input * input(unsigned int index=0) const
Retrieves the Input pointer at a given index.
Definition processor.h:587
force_inline int getSampleRate() const
Retrieves the current (effective) sample rate.
Definition processor.h:326
force_inline bool isControlRate() const
Checks if this Processor is running at control rate (buffer_size == 1).
Definition processor.h:342
force_inline Output * output(unsigned int index=0) const
Retrieves the Output pointer at a given index.
Definition processor.h:616
Contains faster but less accurate versions of utility math functions, such as exponential,...
force_inline mono_float powerScale(mono_float value, mono_float power)
A power-scaling function to map a linear range to a curved response.
Definition futils.h:455
force_inline poly_float clamp(poly_float value, mono_float min, mono_float max)
Clamps each lane of a vector to [min, max].
Definition poly_utils.h:306
force_inline poly_float toFloat(poly_int integers)
Casts a poly_int to poly_float lane-by-lane.
Definition poly_utils.h:733
force_inline poly_float min(poly_float left, poly_float right)
Returns the minimum of two poly_floats lane-by-lane.
Definition poly_utils.h:334
force_inline poly_float max(poly_float left, poly_float right)
Returns the maximum of two poly_floats lane-by-lane.
Definition poly_utils.h:327
force_inline mono_float minFloat(poly_float values)
Returns the minimum lane value from a poly_float.
Definition poly_utils.h:534
force_inline poly_float maskLoad(poly_float zero_value, poly_float one_value, poly_mask reset_mask)
Selects between two values (zero_value or one_value) based on a mask in each lane.
Definition poly_utils.h:351
force_inline poly_float interpolate(poly_float from, poly_float to, mono_float t)
Performs a linear interpolation between two poly_floats using a scalar t in [0..1].
Definition poly_utils.h:182
force_inline poly_float ceil(poly_float value)
Ceils each lane in value.
Definition poly_utils.h:799
Contains classes and functions used within the Vital synthesizer framework.
@ kVoiceIdle
Definition common.h:76
@ kVoiceOn
Definition common.h:77
@ kVoiceKill
Definition common.h:81
@ kVoiceDecay
Definition common.h:79
@ kVoiceHold
Definition common.h:78
@ kVoiceOff
Definition common.h:80
poly_int poly_mask
Alias for clarity; used as a mask type in poly_float.
Definition poly_values.h:590
constexpr mono_float kVoiceKillTime
Time in seconds after which a silent voice is considered dead.
Definition common.h:56
float mono_float
Definition common.h:33
force_inline poly_float at(int i) const
Returns the sample at index i from the source buffer.
Definition processor.h:141
const Output * source
The output from which this input reads samples.
Definition processor.h:134
poly_float * buffer
Pointer to the output buffer.
Definition processor.h:110
poly_int trigger_offset
Sample offset (per voice) for triggers.
Definition processor.h:117
poly_mask trigger_mask
Mask for triggered voices.
Definition processor.h:115
poly_float trigger_value
Trigger values for voices.
Definition processor.h:116
Represents a vector of floating-point values using SIMD instructions.
Definition poly_values.h:600
static force_inline mask_simd_type vector_call equal(simd_type one, simd_type two)
Compares two SIMD float registers for equality, element-wise.
Definition poly_values.h:954
static force_inline mask_simd_type vector_call notEqual(simd_type one, simd_type two)
Compares two SIMD float registers for non-equality, element-wise.
Definition poly_values.h:1003
Represents a vector of integer values using SIMD instructions.
Definition poly_values.h:56
static force_inline simd_type vector_call equal(simd_type one, simd_type two)
Compares two SIMD integer registers for equality, element-wise.
Definition poly_values.h:291