Vital
Loading...
Searching...
No Matches
random_lfo.cpp
Go to the documentation of this file.
1#include "random_lfo.h"
2
3#include "synth_lfo.h"
4#include "utils.h"
5#include "futils.h"
6#include "synth_constants.h"
7
8namespace {
9 // Lorenz system constants:
10 constexpr float kLorenzInitial1 = 0.0f;
11 constexpr float kLorenzInitial2 = 0.0f;
12 constexpr float kLorenzInitial3 = 37.6f;
13 constexpr float kLorenzA = 10.0f;
14 constexpr float kLorenzB = 28.0f;
15 constexpr float kLorenzC = 8.0f / 3.0f;
16 constexpr float kLorenzTimeScale = 1.0f;
17 constexpr float kLorenzSize = 40.0f;
18 constexpr float kLorenzScale = 1.0f / kLorenzSize;
19}
20
21namespace vital {
22 RandomLfo::RandomLfo() : Processor(kNumInputs, 1), random_generator_(-1.0f, 1.0f) {
23 last_sync_ = std::make_shared<double>();
24 sync_seconds_ = std::make_shared<double>();
25 shared_state_ = std::make_shared<RandomState>();
26 *sync_seconds_ = 0;
27 }
28
29 void RandomLfo::doReset(RandomState* state, bool mono, poly_float frequency) {
36 poly_mask reset_mask = getResetMask(kReset);
37 // If no reset trigger or if sync input is active, no reset is performed.
38 if (reset_mask.anyMask() == 0 || input(kSync)->at(0)[0])
39 return;
40
41 poly_float sample_offset = utils::toFloat(input(kReset)->source->trigger_offset);
42 poly_float start_offset = frequency * (1.0f / getSampleRate()) * sample_offset;
43 state->offset = utils::maskLoad(state->offset, -start_offset, reset_mask);
44
45 poly_float from_random = 0.0f;
46 poly_float to_random = 0.0f;
47 // Generate new random values for mono or stereo voices.
48 if (mono) {
49 from_random = random_generator_.polyVoiceNext();
50 to_random = random_generator_.polyVoiceNext();
51 }
52 else {
53 from_random = random_generator_.polyNext();
54 to_random = random_generator_.polyNext();
55 }
56
57 state->last_random_value = utils::maskLoad(state->last_random_value, from_random, reset_mask);
58 state->next_random_value = utils::maskLoad(state->next_random_value, to_random, reset_mask);
59 last_value_ = utils::maskLoad(last_value_, state->last_random_value * 0.5f + 0.5f, reset_mask);
60 }
61
62 poly_int RandomLfo::updatePhase(RandomState* state, int num_samples) {
71 poly_float frequency = input(kFrequency)->at(0);
72 poly_float phase_delta = frequency * (1.0f / getSampleRate()) * num_samples;
73 bool mono = input(kStereo)->at(0)[0] == 0.0f;
74 poly_mask new_random_mask = 0;
75
76 if (input(kSync)->at(0)[0]) {
77 // Sync mode: If external sync time changes, adjust offset accordingly.
78 if (*last_sync_ != *sync_seconds_) {
80 new_random_mask = poly_float::lessThan(new_offset, 0.5f) & poly_float::greaterThanOrEqual(state->offset, 0.5f);
81 state->offset = new_offset;
82 }
83 }
84 else {
85 // Non-sync mode: normal phase increment, with resets if triggered.
86 poly_float frequency = input(kFrequency)->at(0);
87 doReset(state, mono, frequency);
88
89 state->offset += phase_delta;
90 new_random_mask = poly_float::greaterThanOrEqual(state->offset, 1.0f);
91 state->offset = utils::mod(state->offset);
92 }
93
94 // If the phase passed 1.0 (full cycle), select a new random value pair.
95 if (new_random_mask.anyMask()) {
96 state->last_random_value = utils::maskLoad(state->last_random_value, state->next_random_value, new_random_mask);
98 state->next_random_value = utils::maskLoad(state->next_random_value, next_random, new_random_mask);
99
100 // Compute how many samples until wrapping, for accurate timing.
101 poly_float delta = utils::maskLoad(phase_delta, 1.0f, poly_float::lessThanOrEqual(phase_delta, 0.0f));
102 poly_float samples_to_wrap = state->offset / delta;
103 return utils::roundToInt(samples_to_wrap);
104 }
105
106 return 0;
107 }
108
109 void RandomLfo::process(int num_samples) {
116 if (input(kSync)->at(0)[0]) {
117 if (*last_sync_ != *sync_seconds_) {
118 // Sync changed, so process with the shared state.
119 process(shared_state_.get(), num_samples);
120
121 poly_float* dest = output()->buffer;
122 int update_samples = isControlRate() ? 1 : num_samples;
123 // Stereo handling: combine left and right values if needed.
124 for (int i = 0; i < update_samples; ++i) {
125 poly_float value = dest[i] & constants::kFirstMask;
126 dest[i] = value + utils::swapVoices(value);
127 }
128
130 output()->trigger_value = trigger_value + utils::swapVoices(trigger_value);
132 }
133 }
134 else
135 process(&state_, num_samples);
136 }
137
138 void RandomLfo::process(RandomState* state, int num_samples) {
146 int random_type_int = std::round(utils::clamp(input(kStyle)->at(0)[0], 0.0f, kNumStyles - 1.0f));
147 RandomType random_type = static_cast<RandomType>(random_type_int);
148
149 if (random_type == kLorenzAttractor) {
150 processLorenzAttractor(state, num_samples);
151 return;
152 }
153 if (random_type == kSampleAndHold) {
154 processSampleAndHold(state, num_samples);
155 return;
156 }
157
158 updatePhase(state, num_samples);
159
160 poly_float result;
161 switch (random_type) {
162 case kPerlin:
164 break;
165 case kSinInterpolate:
166 result = futils::sinInterpolate(state->last_random_value, state->next_random_value, state->offset);
167 break;
168 default:
169 result = 0.0f;
170 }
171
172 result = result * 0.5f + 0.5f; // Normalize from [-1, 1] to [0, 1].
173 output()->trigger_value = result;
174
175 poly_float* dest = output()->buffer;
176 if (!isControlRate()) {
177 // Audio-rate: interpolate values smoothly across samples.
178 poly_float current_value = last_value_;
179 poly_float delta_value = (result - current_value) * (1.0f / num_samples);
180 for (int i = 0; i < num_samples; ++i) {
181 current_value += delta_value;
182 dest[i] = current_value;
183 }
184 }
185 else
186 dest[0] = result;
187
188 last_value_ = result;
189 }
190
191 void RandomLfo::processSampleAndHold(RandomState* state, int num_samples) {
198 poly_float last_random_value = state->last_random_value * 0.5f + 0.5f;
199 poly_int sample_change = updatePhase(state, num_samples);
200 poly_float current_random_value = state->last_random_value * 0.5f + 0.5f;
201
202 poly_float* dest = output()->buffer;
203 if (!isControlRate()) {
204 for (int i = 0; i < num_samples; ++i) {
205 poly_mask over = poly_int::greaterThan(i, sample_change);
206 dest[i] = utils::maskLoad(last_random_value, current_random_value, over);
207 }
208 }
209 else
210 dest[0] = current_random_value;
211
212 output()->trigger_value = current_random_value;
213 }
214
215 void RandomLfo::processLorenzAttractor(RandomState* state, int num_samples) {
222 static constexpr float kMaxFrequency = 0.01f;
223
224 bool mono = input(kStereo)->at(0)[0] == 0.0f;
225 poly_float state1 = state->state1;
226 poly_float state2 = state->state2;
227 poly_float state3 = state->state3;
228
229 poly_mask reset_mask = getResetMask(kReset);
230 if (reset_mask.anyMask() && input(kSync)->at(0)[0] == 0.0f) {
231 // On reset, re-initialize states with random offsets if not synced.
232 if (mono) {
233 poly_float value1 = random_generator_.polyVoiceNext() + kLorenzInitial1;
234 poly_float value2 = random_generator_.polyVoiceNext() + kLorenzInitial2;
235 poly_float value3 = random_generator_.polyVoiceNext() + kLorenzInitial3;
236 state1 = utils::maskLoad(state1, value1, reset_mask);
237 state2 = utils::maskLoad(state2, value2, reset_mask);
238 state3 = utils::maskLoad(state3, value3, reset_mask);
239 }
240 else {
241 poly_float value1 = random_generator_.polyNext() + kLorenzInitial1;
242 poly_float value2 = random_generator_.polyNext() + kLorenzInitial2;
243 poly_float value3 = random_generator_.polyNext() + kLorenzInitial3;
244 state1 = utils::maskLoad(state1, value1, reset_mask);
245 state2 = utils::maskLoad(state2, value2, reset_mask);
246 state3 = utils::maskLoad(state3, value3, reset_mask);
247 }
248 }
249
250 if (mono) {
251 // In mono mode, ensure states are consistent across stereo pairs if any.
252 state1 = state1 & constants::kLeftMask;
253 state1 += utils::swapStereo(state1);
254 state2 = state2 & constants::kLeftMask;
255 state2 += utils::swapStereo(state2);
256 state3 = state3 & constants::kLeftMask;
257 state3 += utils::swapStereo(state3);
258 }
259
260 poly_float frequency = input(kFrequency)->at(0);
261 poly_float t = utils::min(kMaxFrequency, frequency * (0.5f / getSampleRate()));
262
263 poly_float* dest = output()->buffer;
264 // Integrate the Lorenz system:
265 for (int i = 0; i < num_samples; ++i) {
266 poly_float delta1 = (state2 - state1) * kLorenzA;
267 poly_float delta2 = (-state3 + kLorenzB) * state1 - state2;
268 poly_float delta3 = state1 * state2 - state3 * kLorenzC;
269 state1 += delta1 * t;
270 state2 += delta2 * t;
271 state3 += delta3 * t;
272
273 dest[i] = state1 * kLorenzScale + 0.5f;
274 }
275
276 state->state1 = state1;
277 state->state2 = state2;
278 state->state3 = state3;
279
280 output()->trigger_value = state->state1 * kLorenzScale + 0.5f;
281 }
282
283 void RandomLfo::correctToTime(double seconds) {
287 *sync_seconds_ = seconds;
288 }
289} // namespace vital
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 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
RandomLfo()
Constructs a RandomLfo processor with default parameters.
Definition random_lfo.cpp:22
std::shared_ptr< double > sync_seconds_
A shared double holding the sync reference time in seconds.
Definition random_lfo.h:169
std::shared_ptr< double > last_sync_
A shared double holding the last sync time for comparison.
Definition random_lfo.h:170
utils::RandomGenerator random_generator_
A random generator to produce random values for the LFO.
Definition random_lfo.h:166
std::shared_ptr< RandomState > shared_state_
A shared RandomState (used when syncing across instances).
Definition random_lfo.h:164
void process(int num_samples) override
Processes a block of samples.
Definition random_lfo.cpp:109
RandomType
The types of random waveforms supported by RandomLfo.
Definition random_lfo.h:72
@ kSampleAndHold
Definition random_lfo.h:74
@ kNumStyles
Definition random_lfo.h:77
@ kSinInterpolate
Definition random_lfo.h:75
@ kLorenzAttractor
Definition random_lfo.h:76
@ kPerlin
Definition random_lfo.h:73
void processSampleAndHold(RandomState *state, int num_samples)
Processes the LFO in Sample-And-Hold mode.
Definition random_lfo.cpp:191
void correctToTime(double seconds)
Adjusts the LFO to match a specific time reference (in seconds), for synchronization.
Definition random_lfo.cpp:283
poly_int updatePhase(RandomState *state, int num_samples)
Updates the LFO phase and determines if a new random value is needed.
Definition random_lfo.cpp:62
poly_float last_value_
The last output value of the LFO.
Definition random_lfo.h:167
@ kStereo
Definition random_lfo.h:60
@ kReset
Definition random_lfo.h:56
@ kStyle
Definition random_lfo.h:58
@ kFrequency
Definition random_lfo.h:54
@ kSync
Definition random_lfo.h:57
void processLorenzAttractor(RandomState *state, int num_samples)
Processes the LFO using a Lorenz attractor model.
Definition random_lfo.cpp:215
void doReset(RandomState *state, bool mono, poly_float frequency)
Resets the LFO phase and random values if a reset trigger occurs.
Definition random_lfo.cpp:29
RandomState state_
The main internal RandomState for this LFO instance.
Definition random_lfo.h:163
force_inline poly_float polyVoiceNext()
Produces a poly_float with random values assigned in pairs (every 2 lanes share the same random value...
Definition utils.h:105
force_inline poly_float polyNext()
Produces a poly_float with random values in each lane.
Definition utils.h:93
Contains faster but less accurate versions of utility math functions, such as exponential,...
const poly_mask kFirstMask
A mask identifying the first voice slots in a polyphonic vector.
Definition synth_constants.h:266
const poly_mask kLeftMask
A mask identifying the left channel when comparing to kLeftOne.
Definition synth_constants.h:260
force_inline poly_float sinInterpolate(poly_float from, poly_float to, poly_float t)
Definition futils.h:409
force_inline poly_float mod(poly_float value)
Returns the fractional part of each lane by subtracting the floored value.
Definition poly_utils.h:814
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_int roundToInt(poly_float value)
Rounds each lane to the nearest integer.
Definition poly_utils.h:792
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 getCycleOffsetFromSeconds(double seconds, poly_float frequency)
Computes a cycle offset given a time in seconds and a frequency.
Definition poly_utils.h:885
force_inline poly_float swapVoices(poly_float value)
Swaps the first half of the lanes with the second half.
Definition poly_utils.h:437
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 perlinInterpolate(poly_float from, poly_float to, poly_float t)
A specialized interpolation function used in perlin-like routines.
Definition poly_utils.h:296
force_inline poly_float swapStereo(poly_float value)
Swaps the left and right channels of a stereo poly_float.
Definition poly_utils.h:411
Contains classes and functions used within the Vital synthesizer framework.
force_inline poly_float at(int i) const
Returns the sample at index i from the source buffer.
Definition processor.h:141
poly_float * buffer
Pointer to the output buffer.
Definition processor.h:110
poly_float trigger_value
Trigger values for voices.
Definition processor.h:116
Holds the internal state of the RandomLfo for a given voice or channel.
Definition random_lfo.h:24
poly_float state1
Definition random_lfo.h:39
poly_float last_random_value
The previously generated random value.
Definition random_lfo.h:35
poly_float state2
Definition random_lfo.h:39
poly_float next_random_value
The next target random value for interpolation.
Definition random_lfo.h:36
poly_float state3
Definition random_lfo.h:39
poly_float offset
Current offset (phase) in the LFO cycle.
Definition random_lfo.h:34
Represents a vector of floating-point values using SIMD instructions.
Definition poly_values.h:600
static force_inline mask_simd_type vector_call greaterThanOrEqual(simd_type one, simd_type two)
Compares two SIMD float registers, element-wise, for greater than or equal.
Definition poly_values.h:987
static force_inline poly_mask vector_call lessThan(poly_float one, poly_float two)
Definition poly_values.h:1105
static force_inline poly_mask vector_call lessThanOrEqual(poly_float one, poly_float two)
Definition poly_values.h:1108
Represents a vector of integer values using SIMD instructions.
Definition poly_values.h:56
static force_inline uint32_t vector_call anyMask(simd_type value)
Returns a bitmask that indicates which bytes/elements in the register are non-zero.
Definition poly_values.h:352
static force_inline simd_type vector_call greaterThan(simd_type one, simd_type two)
Compares two SIMD integer registers, element-wise, for greater than.
Definition poly_values.h:309
Provides various utility functions, classes, and constants for audio, math, and general-purpose opera...