Vital
Loading...
Searching...
No Matches
modulation_connection_processor.cpp
Go to the documentation of this file.
2#include "futils.h"
3
4namespace vital {
5
7 SynthModule(kNumInputs, kNumOutputs), index_(index), polyphonic_(true), current_value_(nullptr),
8 bipolar_(nullptr), stereo_(nullptr) {
9 setControlRate(true);
10
11 modulation_amount_ = 0.0f;
12
13 destination_scale_ = std::make_shared<mono_float>();
14 *destination_scale_ = 0.0f;
16
17 power_ = 0.0f;
18
19 map_generator_ = std::make_shared<LineGenerator>();
20 map_generator_->initLinear();
21 }
22
27 std::string bipolar_name = "modulation_" + std::to_string(index_ + 1) + "_bipolar";
28 bipolar_ = createBaseControl(bipolar_name);
29
30 std::string stereo_name = "modulation_" + std::to_string(index_ + 1) + "_stereo";
31 stereo_ = createBaseControl(stereo_name);
32
33 std::string bypass_name = "modulation_" + std::to_string(index_ + 1) + "_bypass";
34 bypass_ = createBaseControl(bypass_name);
35
37 }
38
44 const Output* source = input(kModulationInput)->source;
45 poly_float modulation_input = source->trigger_value;
46 output(kModulationSource)->buffer[0] = modulation_input;
47
49 modulation_amount_ = 0.0f;
51
52 if (isControlRate() || source->isControlRate())
53 processControlRate(source);
54 else
55 processAudioRate(num_samples, source);
56 }
57
58 void ModulationConnectionProcessor::processAudioRate(int num_samples, const Output* source) {
62 if (bypass_->value()) {
65 return;
66 }
67
68 poly_float power = -input(kModulationPower)->at(0);
69 bool using_power = (poly_float::notEqual(0.0f, power) | poly_float::notEqual(0.0f, power_)).anyMask();
70 bool using_map = !map_generator_->linear();
71
72 if (using_power && using_map)
73 processAudioRateRemappedAndMorphed(num_samples, source, power);
74 else if (using_power)
75 processAudioRateMorphed(num_samples, source, power);
76 else if (using_map)
77 processAudioRateRemapped(num_samples, source);
78 else
79 processAudioRateLinear(num_samples, source);
80
81 power_ = power;
82 }
83
84 // The following methods implement different audio-rate processing strategies:
85 // - processAudioRateLinear: no remapping, no morphing
86 // - processAudioRateMorphed: morphing power applied, no remapping
87 // - processAudioRateRemapped: remapping via LineGenerator, no morphing
88 // - processAudioRateRemappedAndMorphed: both remapping and morphing power
89
91 // Basic linear scaling and bipolar offset.
93 const poly_float* modulation_source = source->buffer;
94
95 poly_float bipolar_offset = -bipolar_->value() * 0.5f;
96 poly_float current_amount = modulation_amount_;
97 poly_float stereo_scale = poly_float(1.0f) - (constants::kRightOne * 2.0f * stereo_->value());
98 poly_float modulation_amount = utils::clamp(input(kModulationAmount)->at(0), -1.0f, 1.0f) * stereo_scale;
99 modulation_amount_ = modulation_amount * (*destination_scale_);
100 current_amount = utils::maskLoad(current_amount, modulation_amount_, getResetMask(kReset));
101 poly_float delta_amount = (modulation_amount_ - current_amount) * (1.0f / num_samples);
102
103 for (int i = 0; i < num_samples; ++i) {
104 current_amount += delta_amount;
105 poly_float modulation_value = modulation_source[i];
106 dest[i] = (modulation_value + bipolar_offset) * current_amount;
107 }
108
109 output(kModulationPreScale)->buffer[0] = (modulation_source[0] + bipolar_offset) * modulation_amount;
111 }
112
114 poly_float power) {
115 // Applies a power curve to shape the modulation in addition to linear scaling and bipolar offset.
117 const poly_float* modulation_source = source->buffer;
118 poly_float bipolar_offset = -bipolar_->value() * 0.5f;
119
120 poly_float bipolar = bipolar_->value();
121 poly_float polarity_pre_scale = bipolar + 1.0f;
122 poly_float polarity_post_scale = -bipolar * 0.5f + 1.0f;
123 polarity_post_scale *= poly_float(1.0f) - (constants::kRightOne * 2.0f * stereo_->value());
124
125 poly_float current_amount = modulation_amount_;
126 poly_float current_power = power_;
127
128 poly_float modulation_amount = utils::clamp(input(kModulationAmount)->at(0), -1.0f, 1.0f);
129 modulation_amount_ = modulation_amount * (*destination_scale_);
130
131 poly_mask reset_mask = getResetMask(kReset);
132 current_amount = utils::maskLoad(current_amount, modulation_amount_, reset_mask);
133 current_power = utils::maskLoad(current_power, power, reset_mask);
134
135 float sample_inc = 1.0f / num_samples;
136 poly_float delta_amount = (modulation_amount_ - current_amount) * sample_inc;
137 poly_float delta_power = (power - current_power) * sample_inc;
138
139 for (int i = 0; i < num_samples; ++i) {
140 current_amount += delta_amount;
141 current_power += delta_power;
142
143 poly_float modulation_value = modulation_source[i];
144 poly_float modulation_shift = modulation_value * polarity_pre_scale - bipolar;
145 poly_float modulation_abs = poly_float::abs(modulation_shift);
146 poly_mask sign_mask = poly_float::sign_mask(modulation_shift);
147
148 poly_float shifted_modulation = futils::powerScale(modulation_abs, current_power);
149 poly_float pre_modulation = current_amount * shifted_modulation;
150 dest[i] = (pre_modulation ^ sign_mask) * polarity_post_scale;
151 }
152
153 output(kModulationPreScale)->buffer[0] = dest[0] * (1.0f / (*destination_scale_));
155 }
156
158 poly_float power) {
159 // Combines remapping and morphing power to nonlinearly shape the modulation.
161 const poly_float* modulation_source = source->buffer;
162 poly_float bipolar_offset = -bipolar_->value() * 0.5f;
163
164 poly_float bipolar = bipolar_->value();
165 poly_float polarity_pre_scale = bipolar + 1.0f;
166 poly_float polarity_post_scale = -bipolar * 0.5f + 1.0f;
167 polarity_post_scale *= poly_float(1.0f) - (constants::kRightOne * 2.0f * stereo_->value());
168
169 poly_float current_amount = modulation_amount_;
170 poly_float current_power = power_;
171
172 poly_float modulation_amount = utils::clamp(input(kModulationAmount)->at(0), -1.0f, 1.0f);
173 modulation_amount_ = modulation_amount * (*destination_scale_);
174
175 poly_mask reset_mask = getResetMask(kReset);
176 current_amount = utils::maskLoad(current_amount, modulation_amount_, reset_mask);
177 current_power = utils::maskLoad(current_power, power, reset_mask);
178
179 float sample_inc = 1.0f / num_samples;
180 poly_float delta_amount = (modulation_amount_ - current_amount) * sample_inc;
181 poly_float delta_power = (power - current_power) * sample_inc;
182
183 mono_float* buffer = map_generator_->getCubicInterpolationBuffer();
184 for (int i = 0; i < num_samples; ++i) {
185 current_amount += delta_amount;
186 current_power += delta_power;
187 poly_float modulation_value = modulation_source[i];
188
189 mono_float resolution = map_generator_->resolution();
190 poly_float boost = utils::clamp(modulation_value * resolution, 0.0f, resolution);
191 poly_int indices = utils::clamp(utils::toInt(boost), 0, resolution - 1);
192 poly_float t = boost - utils::toFloat(indices);
193
194 matrix interpolation_matrix = utils::getCatmullInterpolationMatrix(t);
195 matrix value_matrix = utils::getValueMatrix(buffer, indices);
196
197 value_matrix.transpose();
198 modulation_value = utils::clamp(interpolation_matrix.multiplyAndSumRows(value_matrix), -1.0f, 1.0f);
199
200 poly_float modulation_shift = modulation_value * polarity_pre_scale - bipolar;
201 poly_float modulation_abs = poly_float::abs(modulation_shift);
202 poly_mask sign_mask = poly_float::sign_mask(modulation_shift);
203
204 poly_float shifted_modulation = futils::powerScale(modulation_abs, current_power);
205 poly_float pre_modulation = current_amount * shifted_modulation;
206 dest[i] = (pre_modulation ^ sign_mask) * polarity_post_scale;
207 }
208
209 output(kModulationPreScale)->buffer[0] = dest[0] * (1.0f / (*destination_scale_));
211 }
212
214 // Remaps the modulation value via the LineGenerator before linear scaling and bipolar offset.
216 const poly_float* modulation_source = source->buffer;
217
218 poly_float bipolar_offset = -bipolar_->value() * 0.5f;
219 poly_float current_amount = modulation_amount_;
220 poly_float stereo_scale = poly_float(1.0f) - (constants::kRightOne * 2.0f * stereo_->value());
221 poly_float modulation_amount = utils::clamp(input(kModulationAmount)->at(0), -1.0f, 1.0f) * stereo_scale;
222 modulation_amount_ = modulation_amount * (*destination_scale_);
223 current_amount = utils::maskLoad(current_amount, modulation_amount_, getResetMask(kReset));
224 poly_float delta_amount = (modulation_amount_ - current_amount) * (1.0f / num_samples);
225
226 mono_float* buffer = map_generator_->getCubicInterpolationBuffer();
227 for (int i = 0; i < num_samples; ++i) {
228 current_amount += delta_amount;
229 poly_float modulation_value = modulation_source[i];
230
231 mono_float resolution = map_generator_->resolution();
232 poly_float boost = utils::clamp(modulation_value * resolution, 0.0f, resolution);
233 poly_int indices = utils::clamp(utils::toInt(boost), 0, resolution - 1);
234 poly_float t = boost - utils::toFloat(indices);
235
236 matrix interpolation_matrix = utils::getCatmullInterpolationMatrix(t);
237 matrix value_matrix = utils::getValueMatrix(buffer, indices);
238
239 value_matrix.transpose();
240
241 modulation_value = utils::clamp(interpolation_matrix.multiplyAndSumRows(value_matrix), -1.0f, 1.0f);
242 dest[i] = (modulation_value + bipolar_offset) * current_amount;
243 }
244
245 output(kModulationPreScale)->buffer[0] = (modulation_source[0] + bipolar_offset) * modulation_amount;
247 }
248
255 if (bypass_->value()) {
256 output(kModulationOutput)->buffer[0] = 0.0f;
258 return;
259 }
260
261 poly_float modulation_input = utils::clamp(source->buffer[0], 0.0f, 1.0f);
262
263 if (!map_generator_->linear()) {
264 mono_float* buffer = map_generator_->getCubicInterpolationBuffer();
265 mono_float resolution = map_generator_->resolution();
266 poly_float boost = utils::clamp(modulation_input * resolution, 0.0f, resolution);
267 poly_int indices = utils::clamp(utils::toInt(boost), 0, resolution - 1);
268 poly_float t = boost - utils::toFloat(indices);
269
270 matrix interpolation_matrix = utils::getCatmullInterpolationMatrix(t);
271 matrix value_matrix = utils::getValueMatrix(buffer, indices);
272
273 value_matrix.transpose();
274
275 modulation_input = utils::clamp(interpolation_matrix.multiplyAndSumRows(value_matrix), -1.0f, 1.0f);
276 }
277
278 poly_float bipolar = bipolar_->value();
279 poly_float polarity_pre_scale = bipolar + 1.0f;
280 poly_float polarity_post_scale = -bipolar * 0.5f + 1.0f;
281 polarity_post_scale *= poly_float(1.0f) - (constants::kRightOne * 2.0f * stereo_->value());
282
283 poly_float modulation_shift = modulation_input * polarity_pre_scale - bipolar;
284 poly_float modulation_abs = poly_float::abs(modulation_shift);
285 poly_mask sign_mask = poly_float::sign_mask(modulation_shift);
286
287 poly_float power = -input(kModulationPower)->at(0);
288 poly_float shifted_modulation = futils::powerScale(modulation_abs, power);
289 poly_float modulation_amount = utils::clamp(input(kModulationAmount)->at(0), -1.0f, 1.0f);
290 poly_float pre_modulation = modulation_amount * shifted_modulation;
291 poly_float raw_modulation = (pre_modulation ^ sign_mask) * polarity_post_scale;
292 output(kModulationPreScale)->buffer[0] = raw_modulation;
293 output(kModulationOutput)->buffer[0] = raw_modulation * (*destination_scale_);
294 VITAL_ASSERT(utils::isFinite(output()->buffer[0]));
295 }
296} // namespace vital
Value * bypass_
Controls if the modulation connection is bypassed.
Definition modulation_connection_processor.h:218
std::shared_ptr< LineGenerator > map_generator_
The line mapping function for remapping modulation.
Definition modulation_connection_processor.h:226
ModulationConnectionProcessor(int index)
Constructs a ModulationConnectionProcessor with a given index.
Definition modulation_connection_processor.cpp:6
void processAudioRateRemappedAndMorphed(int num_samples, const Output *source, poly_float power)
Audio-rate processing with both remapping and morphing power applied.
Definition modulation_connection_processor.cpp:157
void processAudioRateLinear(int num_samples, const Output *source)
Audio-rate processing with a linear transform (no remap, no morph).
Definition modulation_connection_processor.cpp:90
@ kModulationOutput
Definition modulation_connection_processor.h:44
@ kModulationPreScale
Definition modulation_connection_processor.h:45
@ kModulationSource
Definition modulation_connection_processor.h:46
void process(int num_samples) override
Processes a block of samples, handling both control-rate and audio-rate modulation.
Definition modulation_connection_processor.cpp:39
void init() override
Initializes the processor, creating and connecting parameter controls (e.g., bipolar,...
Definition modulation_connection_processor.cpp:23
int index_
Unique identifier for this modulation connection.
Definition modulation_connection_processor.h:213
Value * bipolar_
Controls if the modulation is bipolar.
Definition modulation_connection_processor.h:216
std::shared_ptr< mono_float > destination_scale_
The scale factor for final modulation output.
Definition modulation_connection_processor.h:223
poly_float power_
Stored current power for morphing modulation.
Definition modulation_connection_processor.h:220
void processAudioRateMorphed(int num_samples, const Output *source, poly_float power)
Audio-rate processing with morphing power applied (no remapping).
Definition modulation_connection_processor.cpp:113
void processAudioRateRemapped(int num_samples, const Output *source)
Audio-rate processing with remapping via LineGenerator, but no morphing power.
Definition modulation_connection_processor.cpp:213
poly_float modulation_amount_
The current modulation amount scaled by the destination scale.
Definition modulation_connection_processor.h:221
mono_float last_destination_scale_
The last known destination scale to detect changes.
Definition modulation_connection_processor.h:224
void processAudioRate(int num_samples, const Output *source)
Processes the modulation at audio-rate using the provided source output.
Definition modulation_connection_processor.cpp:58
@ kReset
Definition modulation_connection_processor.h:32
@ kModulationAmount
Definition modulation_connection_processor.h:30
@ kModulationInput
Definition modulation_connection_processor.h:29
@ kModulationPower
Definition modulation_connection_processor.h:31
void processControlRate(const Output *source)
Processes the modulation at control-rate (once per block), when modulation or source is not audio-rat...
Definition modulation_connection_processor.cpp:249
Value * stereo_
Controls if the modulation is stereo.
Definition modulation_connection_processor.h:217
virtual void setControlRate(bool control_rate)
Sets whether this Processor runs at control rate.
Definition processor.h:350
force_inline Input * input(unsigned int index=0) const
Retrieves the Input pointer at a given index.
Definition processor.h:587
force_inline bool isControlRate() const
Checks if this Processor is running at control rate (buffer_size == 1).
Definition processor.h:342
force_inline poly_mask getResetMask(int input_index) const
Retrieves a mask indicating which voices triggered a note-on event. Compares the input's trigger_valu...
Definition processor.h:360
force_inline Output * output(unsigned int index=0) const
Retrieves the Output pointer at a given index.
Definition processor.h:616
virtual void init()
Called after constructor, used for any additional initialization. Subclasses can override....
Definition processor.h:258
A ProcessorRouter that encapsulates a cohesive unit of functionality in the synthesizer.
Definition synth_module.h:129
Value * createBaseControl(std::string name, bool audio_rate=false, bool smooth_value=false)
Creates a simple control processor for a given parameter name.
Definition synth_module.cpp:22
force_inline mono_float value() const
Returns the current mono_float value of the first lane.
Definition value.h:60
#define VITAL_ASSERT(x)
Definition common.h:11
Contains faster but less accurate versions of utility math functions, such as exponential,...
const poly_float kRightOne(0.0f, 1.0f)
A poly_float representing a vector [0.0f, 1.0f] commonly used for stereo channel operations.
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 bool isFinite(poly_float value)
Checks if all lanes in a poly_float are finite.
Definition poly_utils.h:610
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 matrix getCatmullInterpolationMatrix(poly_float t)
Creates a Catmull-Rom interpolation matrix from a poly_float t.
Definition poly_utils.h:227
force_inline matrix getValueMatrix(const mono_float *buffer, poly_int indices)
Creates a matrix of 4 poly_float lanes from a single buffer at varying indices.
Definition poly_utils.h:262
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_int toInt(poly_float floats)
Casts a poly_float to poly_int by truncation.
Definition poly_utils.h:748
Contains classes and functions used within the Vital synthesizer framework.
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
Holds and manages a buffer of samples (poly_float) for a Processor's output.
Definition processor.h:35
void clearBuffer()
Zeros out the entire output buffer.
Definition processor.h:81
poly_float * buffer
Pointer to the output buffer.
Definition processor.h:110
force_inline bool isControlRate() const
Checks whether this output runs at control rate (buffer_size == 1).
Definition processor.h:89
poly_float trigger_value
Trigger values for voices.
Definition processor.h:116
A structure representing a 4x1 matrix of poly_float rows.
Definition matrix.h:19
force_inline poly_float multiplyAndSumRows(const matrix &other)
Multiplies and sums corresponding rows of this matrix with another matrix.
Definition matrix.h:93
force_inline void transpose()
Transposes the matrix in-place.
Definition matrix.h:44
Represents a vector of floating-point values using SIMD instructions.
Definition poly_values.h:600
static force_inline mask_simd_type vector_call sign_mask(simd_type value)
Extracts the sign bit mask from each element in the SIMD float register.
Definition poly_values.h:944
static force_inline simd_type vector_call abs(simd_type value)
Computes the absolute value of each element in the SIMD float register.
Definition poly_values.h:935
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