WE Core
Loading...
Searching...
No Matches
StepSequencer.h
Go to the documentation of this file.
1/*
2 * File: StepSequencer.h
3 *
4 * Created: 04/04/2026
5 *
6 * This file is part of WECore.
7 *
8 * WECore is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * WECore is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with WECore. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#pragma once
23
24#include <vector>
25#include <cmath>
26#include <algorithm>
29
30namespace WECore::StepSeq {
31
32 static constexpr int DEFAULT_NUM_STEPS {16};
33
34 enum class StepShape : int {
35 FLAT = 1,
36 RAMP = 2,
37 EXP = 3,
38 REV_EXP = 4
39 };
40
54
55 struct Step {
56 double value {Parameters::STEP_VALUE.defaultValue};
58 bool reverse {false};
59 int repeat {Parameters::STEP_REPEAT.defaultValue};
61 };
62
63 struct Pattern {
64 std::vector<Step> steps;
65
66 Pattern(int numSteps = DEFAULT_NUM_STEPS) : steps(numSteps) { }
67 };
68
69 class StepSequencer : public WECore::ModulationSource<double> {
70 public:
71 StepSequencer() : _tempoSyncSwitch(Parameters::TEMPOSYNC_DEFAULT),
73 _tempoNumer(Parameters::TEMPONUMER.defaultValue),
74 _tempoDenom(Parameters::TEMPODENOM.defaultValue),
75 _rawFreq(Parameters::FREQ.defaultValue),
77 _rawDepth(Parameters::DEPTH.defaultValue),
79 _sampleRate(44100),
80 _bpm(120),
81 _currentStep(0),
83 _patterns(1) {
84 }
85
86 virtual ~StepSequencer() override = default;
87
90 bool getTempoSyncSwitch() const { return _tempoSyncSwitch; }
91 int getTempoNumer() const { return _tempoNumer; }
92 int getTempoDenom() const { return _tempoDenom; }
93 double getFreq() const { return _rawFreq; }
94 double getModulatedFreqValue() const { return _modulatedFreqValue; }
95 double getDepth() const { return _rawDepth; }
97 int getCurrentStep() const { return _currentStep; }
102 void setTempoSyncSwitch(bool val) { _tempoSyncSwitch = val; }
103 void setTempoNumer(int val) { _tempoNumer = Parameters::TEMPONUMER.BoundsCheck(val); }
104 void setTempoDenom(int val) { _tempoDenom = Parameters::TEMPODENOM.BoundsCheck(val); }
105 void setFreq(double val) { _rawFreq = Parameters::FREQ.BoundsCheck(val); }
106 void setDepth(double val) { _rawDepth = Parameters::DEPTH.BoundsCheck(val); }
107 void setSampleRate(double val) { _sampleRate = val; }
110 inline bool addFreqModulationSource(std::shared_ptr<ModulationSource> source);
111 inline bool removeFreqModulationSource(std::shared_ptr<ModulationSource> source);
112 inline bool setFreqModulationAmount(size_t index, double amount);
114 std::vector<ModulationSourceWrapper<double>> getFreqModulationSources() const { return _freqModulationSources; }
115
116 inline bool addDepthModulationSource(std::shared_ptr<ModulationSource> source);
117 inline bool removeDepthModulationSource(std::shared_ptr<ModulationSource> source);
118 inline bool setDepthModulationAmount(size_t index, double amount);
120 std::vector<ModulationSourceWrapper<double>> getDepthModulationSources() const { return _depthModulationSources; }
121
124 int getNumPatterns() const { return static_cast<int>(_patterns.size()); }
125
126 const Pattern& getPattern(int patternIndex) const { return _patterns[patternIndex]; }
131 void addStep(int patternIndex) {
132 if (patternIndex >= _patterns.size()) {
133 return;
134 }
135 _patterns[patternIndex].steps.emplace_back();
136 }
137
138 void removeStep(int patternIndex) {
139 if (patternIndex >= _patterns.size()) {
140 return;
141 }
142
143 Pattern& pattern = _patterns[patternIndex];
144 if (pattern.steps.size() > 1) {
145 pattern.steps.pop_back();
146
147 // Ensure current step index is valid after removing a step
148 if (_currentStep >= static_cast<int>(pattern.steps.size())) {
149 _currentStep = 0;
150 _samplePosition = 0;
151 }
152 }
153 }
154
155 int getNumSteps(int patternIndex) const {
156 if (patternIndex >= _patterns.size()) {
157 return 0;
158 }
159 return static_cast<int>(_patterns[patternIndex].steps.size());
160 }
165 void setStepValue(int patternIndex, int stepIndex, double value) {
166 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
167 return;
168 }
169 _patterns[patternIndex].steps[stepIndex].value = Parameters::STEP_VALUE.BoundsCheck(value);
170 }
171
172 void setStepShape(int patternIndex, int stepIndex, StepShape shape) {
173 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
174 return;
175 }
176 _patterns[patternIndex].steps[stepIndex].shape = shape;
177 }
178
179 void setStepReverse(int patternIndex, int stepIndex, bool reverse) {
180 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
181 return;
182 }
183 _patterns[patternIndex].steps[stepIndex].reverse = reverse;
184 }
185
186 void setStepRepeat(int patternIndex, int stepIndex, int repeat) {
187 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
188 return;
189 }
190 _patterns[patternIndex].steps[stepIndex].repeat = Parameters::STEP_REPEAT.BoundsCheck(repeat);
191 }
192
193 void setStepLengthMultiplier(int patternIndex, int stepIndex, double lengthMultiplier) {
194 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
195 return;
196 }
197 _patterns[patternIndex].steps[stepIndex].lengthMultiplier = Parameters::STEP_LENGTH_MULTIPLIER.BoundsCheck(lengthMultiplier);
198 }
199
200 double getStepValue(int patternIndex, int stepIndex) const {
201 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
202 return 0;
203 }
204 return _patterns[patternIndex].steps[stepIndex].value;
205 }
206
207 StepShape getStepShape(int patternIndex, int stepIndex) const {
208 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
209 return StepShape::FLAT;
210 }
211
212 return _patterns[patternIndex].steps[stepIndex].shape;
213 }
214
215 bool getStepReverse(int patternIndex, int stepIndex) const {
216 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
217 return false;
218 }
219 return _patterns[patternIndex].steps[stepIndex].reverse;
220 }
221
222 int getStepRepeat(int patternIndex, int stepIndex) const {
223 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
224 return 1;
225 }
226 return _patterns[patternIndex].steps[stepIndex].repeat;
227 }
228
229 double getStepLengthMultiplier(int patternIndex, int stepIndex) const {
230 if (patternIndex >= _patterns.size() || stepIndex >= static_cast<int>(_patterns[patternIndex].steps.size())) {
231 return 1.0;
232 }
233 return _patterns[patternIndex].steps[stepIndex].lengthMultiplier;
234 }
242 void prepareForNextBuffer(double bpm, double timeInSeconds) {
243 _bpm = bpm;
244 _calcPhaseOffset(timeInSeconds);
245 }
246
248
254 return new StepSequencer(*this);
255 }
256
257 protected:
260
263
264 double _rawFreq,
270
273
274 std::vector<Pattern> _patterns;
275
276 std::vector<ModulationSourceWrapper<double>> _freqModulationSources,
278
279 inline double _getNextOutputImpl(double inSample) override;
280 inline void _resetImpl() override;
281
282 inline void _calcPhaseOffset(double timeInSeconds);
283 inline double _calcBaseStepDuration() const;
284 inline double _calcShapedValue(double t, StepShape shape) const;
285
286 private:
287 // Used when cloning
311 };
312
318
319 void StepSequencer::_calcPhaseOffset(double timeInSeconds) {
321 return;
322 }
323
324 _needsSeekOffsetCalc = false;
325
326 const Pattern& pattern = _patterns[0];
327 const int numSteps = static_cast<int>(pattern.steps.size());
328 const double baseDuration = _calcBaseStepDuration();
329
330 // Calculate total cycle length in samples, accounting for per-step length multipliers
331 double totalCycleSamples = 0;
332 for (int i = 0; i < numSteps; i++) {
333 totalCycleSamples += baseDuration * pattern.steps[i].lengthMultiplier;
334 }
335
336 // Convert playhead time to samples and find position within the cycle
337 const double positionInSamples = std::fmod(timeInSeconds * _sampleRate, totalCycleSamples);
338
339 // Walk through steps to find which step and position we should be at
340 double accumulated = 0;
341 for (int i = 0; i < numSteps; i++) {
342 const double stepDuration = baseDuration * pattern.steps[i].lengthMultiplier;
343
344 if (accumulated + stepDuration > positionInSamples) {
345 _currentStep = i;
346 _samplePosition = positionInSamples - accumulated;
347 return;
348 }
349
350 accumulated += stepDuration;
351 }
352
353 // Shouldn't reach here, but fall back to start
354 _currentStep = 0;
355 _samplePosition = 0;
356 }
357
359 if (_tempoSyncSwitch) {
360 return _sampleRate * (static_cast<double>(_tempoNumer) / _tempoDenom) * (60.0 / _bpm);
361 } else {
362 return _sampleRate / _rawFreq;
363 }
364 }
365
366 double StepSequencer::_calcShapedValue(double t, StepShape shape) const {
367 switch (shape) {
368 case StepShape::FLAT: return 1.0;
369 case StepShape::RAMP: return t;
370 case StepShape::EXP: return t * t;
371 case StepShape::REV_EXP: return 1.0 - t * t;
372 default: return 1.0;
373 }
374 }
375
376 double StepSequencer::_getNextOutputImpl(double /*inSample*/) {
377 const double freqModValue {calcModValue(_freqModulationSources) / 2};
378 const double depthModValue {calcModValue(_depthModulationSources) / 2};
379
380 // Calculate modulated frequency
381 const double freq {Parameters::FREQ.BoundsCheck(
382 _rawFreq + ((Parameters::FREQ.maxValue / 2) * freqModValue)
383 )};
384 _modulatedFreqValue = freq;
385
386 const Pattern& pattern = _patterns[0];
387 const int numSteps = static_cast<int>(pattern.steps.size());
388
389 const double baseDuration = _tempoSyncSwitch
390 ? _sampleRate * (static_cast<double>(_tempoNumer) / _tempoDenom) * (60.0 / _bpm)
392
393 // Advance position by one sample and handle step transitions. The while loop
394 // handles the case where a step duration is shorter than one sample, advancing
395 // multiple steps if needed. Each step may have a different duration via lengthMultiplier.
396 _samplePosition += 1.0;
397 while (_samplePosition >= baseDuration * pattern.steps[_currentStep].lengthMultiplier) {
398 _samplePosition -= baseDuration * pattern.steps[_currentStep].lengthMultiplier;
399 _currentStep = (_currentStep + 1) % numSteps;
400 }
401
402 const Step& step = pattern.steps[_currentStep];
403 const double currentStepDuration = baseDuration * step.lengthMultiplier;
404
405 // Calculate position within current step (0..1), cycling repeat times
406 const double repetitionDuration = currentStepDuration / step.repeat;
407 const double t = std::fmod(_samplePosition, repetitionDuration) / repetitionDuration;
408
409 // Apply shape (reverse flips the interpolation direction)
410 const double tShaped = step.reverse ? 1.0 - t : t;
411 const double shaped = _calcShapedValue(tShaped, step.shape);
412
413 // Calculate modulated depth
414 const double depth {Parameters::DEPTH.BoundsCheck(
415 _rawDepth + (Parameters::DEPTH.maxValue * depthModValue)
416 )};
417 _modulatedDepthValue = depth;
418
419 // Calculate output (always unipolar: step values are 0..1, output is 0..depth)
420 return step.value * shaped * _modulatedDepthValue;
421 }
422
423 bool StepSequencer::addFreqModulationSource(std::shared_ptr<ModulationSource> source) {
424 // Check if the source is already in the list
425 for (const ModulationSourceWrapper<double>& existingSource : _freqModulationSources) {
426 if (existingSource.source == source) {
427 return false;
428 }
429 }
430
431 _freqModulationSources.push_back({source, 0});
432 return true;
433 }
434
435 bool StepSequencer::removeFreqModulationSource(std::shared_ptr<ModulationSource> source) {
436 for (auto it = _freqModulationSources.begin(); it != _freqModulationSources.end(); it++) {
437 if ((*it).source == source) {
438 _freqModulationSources.erase(it);
439 return true;
440 }
441 }
442 return false;
443 }
444
445 bool StepSequencer::setFreqModulationAmount(size_t index, double amount) {
446 if (index < _freqModulationSources.size()) {
447 _freqModulationSources[index].amount = amount;
448 return true;
449 }
450 return false;
451 }
452
453 bool StepSequencer::addDepthModulationSource(std::shared_ptr<ModulationSource> source) {
454 // Check if the source is already in the list
455 for (const ModulationSourceWrapper<double>& existingSource : _depthModulationSources) {
456 if (existingSource.source == source) {
457 return false;
458 }
459 }
460
461 _depthModulationSources.push_back({source, 0});
462 return true;
463 }
464
465 bool StepSequencer::removeDepthModulationSource(std::shared_ptr<ModulationSource> source) {
466 for (auto it = _depthModulationSources.begin(); it != _depthModulationSources.end(); it++) {
467 if ((*it).source == source) {
468 _depthModulationSources.erase(it);
469 return true;
470 }
471 }
472 return false;
473 }
474
475 bool StepSequencer::setDepthModulationAmount(size_t index, double amount) {
476 if (index < _depthModulationSources.size()) {
477 _depthModulationSources[index].amount = amount;
478 return true;
479 }
480 return false;
481 }
482}
std::vector< ModulationSourceWrapper< double > > getFreqModulationSources() const
int getNumSteps(int patternIndex) const
bool getStepReverse(int patternIndex, int stepIndex) const
StepShape getStepShape(int patternIndex, int stepIndex) const
std::vector< Pattern > _patterns
void setStepShape(int patternIndex, int stepIndex, StepShape shape)
bool addFreqModulationSource(std::shared_ptr< ModulationSource > source)
double _getNextOutputImpl(double inSample) override
bool removeDepthModulationSource(std::shared_ptr< ModulationSource > source)
double _calcShapedValue(double t, StepShape shape) const
void setStepValue(int patternIndex, int stepIndex, double value)
double getStepLengthMultiplier(int patternIndex, int stepIndex) const
void setStepRepeat(int patternIndex, int stepIndex, int repeat)
void prepareForNextBuffer(double bpm, double timeInSeconds)
void addStep(int patternIndex)
bool addDepthModulationSource(std::shared_ptr< ModulationSource > source)
virtual ~StepSequencer() override=default
double getModulatedDepthValue() const
StepSequencer(const StepSequencer &other)
void setDepthModulationSources(std::vector< ModulationSourceWrapper< double > > sources)
bool setFreqModulationAmount(size_t index, double amount)
void removeStep(int patternIndex)
StepSequencer * clone() const
const Pattern & getPattern(int patternIndex) const
double getStepValue(int patternIndex, int stepIndex) const
void setStepLengthMultiplier(int patternIndex, int stepIndex, double lengthMultiplier)
std::vector< ModulationSourceWrapper< double > > _freqModulationSources
int getStepRepeat(int patternIndex, int stepIndex) const
std::vector< ModulationSourceWrapper< double > > _depthModulationSources
std::vector< ModulationSourceWrapper< double > > getDepthModulationSources() const
void _calcPhaseOffset(double timeInSeconds)
StepSequencer operator=(StepSequencer &other)=delete
bool setDepthModulationAmount(size_t index, double amount)
void setStepReverse(int patternIndex, int stepIndex, bool reverse)
void setFreqModulationSources(std::vector< ModulationSourceWrapper< double > > sources)
bool removeFreqModulationSource(std::shared_ptr< ModulationSource > source)
const ParameterDefinition::RangedParameter< double > FREQ(0.05, 20, 2)
const ParameterDefinition::RangedParameter< int > TEMPONUMER(1, 32, 1)
const ParameterDefinition::RangedParameter< double > STEP_VALUE(0, 1, 0)
const ParameterDefinition::RangedParameter< int > STEP_REPEAT(1, 8, 1)
const ParameterDefinition::RangedParameter< double > STEP_LENGTH_MULTIPLIER(0.25, 4.0, 1.0)
const ParameterDefinition::RangedParameter< int > TEMPODENOM(1, 32, 1)
constexpr bool TEMPOSYNC_DEFAULT
const ParameterDefinition::RangedParameter< double > DEPTH(0, 1, 0.5)
static constexpr int DEFAULT_NUM_STEPS
SampleType calcModValue(const std::vector< ModulationSourceWrapper< SampleType > > &sources)
Pattern(int numSteps=DEFAULT_NUM_STEPS)
std::vector< Step > steps