Created
January 9, 2023 16:53
-
-
Save Simon-L/5dee8f5a7b812c0e12fda810193bd73e to your computer and use it in GitHub Desktop.
Reusing ADAREnvelope...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Taken from Surge XT for Rack, minimal changes - 2023 SL | |
| // Original license below: | |
| /* | |
| * SurgeXT for VCV Rack - a Surge Synth Team product | |
| * | |
| * Copyright 2019 - 2022, Various authors, as described in the github | |
| * transaction log. | |
| * | |
| * SurgeXT for VCV Rack is released under the Gnu General Public Licence | |
| * V3 or later (GPL-3.0-or-later). The license is found in the file | |
| * "LICENSE" in the root of this repository or at | |
| * https://www.gnu.org/licenses/gpl-3.0.en.html | |
| * | |
| * All source for Surge XT for VCV Rack is available at | |
| * https://github.com/surge-synthesizer/surge-rack/ | |
| */ | |
| #ifndef SXT_RACK_ADARENVELOPE_H | |
| #define SXT_RACK_ADARENVELOPE_H | |
| namespace sst::surgext_rack::dsp::envelopes | |
| { | |
| struct ADAREnvelope | |
| { | |
| static constexpr int tuning_table_size = 512; | |
| float table_envrate_linear alignas(16)[512]; | |
| double dsamplerate_os{0}; | |
| // Constants from Surge | |
| static const int BLOCK_SIZE = 32; | |
| const int BLOCK_SIZE_OS = BLOCK_SIZE * 2; // default oversampling is 2 | |
| const float BLOCK_SIZE_INV = (1.f / BLOCK_SIZE); | |
| float sample_rate = 0.0; | |
| ADAREnvelope() | |
| { | |
| for (int i = 0; i < BLOCK_SIZE; ++i) { | |
| outputCache[i] = 0; | |
| } | |
| } | |
| void activate(double sr) | |
| { | |
| sample_rate = sr; | |
| dsamplerate_os = sample_rate * 2.0; | |
| // from SurgeStorage::init_tables | |
| for (int i = 0; i < tuning_table_size; i++) | |
| { | |
| double k = dsamplerate_os * pow(2.0, (((double)i - 256.0) / 16.0)) / (double)BLOCK_SIZE_OS; | |
| table_envrate_linear[i] = (float)(1.f / k); | |
| } | |
| } | |
| // from SurgeStorage | |
| float envelope_rate_linear(float x) | |
| { | |
| x *= 16.f; | |
| x += 256.f; | |
| int e = (int)x; | |
| float a = x - (float)e; | |
| return (1 - a) * table_envrate_linear[e & 0x1ff] + a * table_envrate_linear[(e + 1) & 0x1ff]; | |
| } | |
| enum Stage | |
| { | |
| s_attack, | |
| s_hold, | |
| s_decay, | |
| s_analog_residual_decay, | |
| s_eoc, | |
| s_complete | |
| } stage{s_complete}; | |
| bool isDigital{true}; | |
| bool isGated{false}; | |
| float phase{0}, start{0}; | |
| float output{0}, eoc_output{0}; | |
| float outputCache[BLOCK_SIZE], outBlock0{0.f}; | |
| int current{BLOCK_SIZE}; | |
| int eoc_countdown{0}; | |
| // Analog Mode | |
| float v_c1{0}, v_c1_delayed{0.f}; | |
| bool discharge{false}; | |
| void attackFrom(float fv, int ashp, bool id, bool ig) | |
| { | |
| float f = fv; | |
| switch (ashp) | |
| { | |
| case 0: | |
| // target = sqrt(target); | |
| f = f * f; | |
| break; | |
| case 2: | |
| // target = target * target * target; | |
| f = pow(f, 1.0 / 3.0); | |
| break; | |
| } | |
| phase = f; | |
| stage = s_attack; | |
| current = BLOCK_SIZE; | |
| outBlock0 = f; | |
| isDigital = id; | |
| isGated = ig; | |
| eoc_output = 0; | |
| eoc_countdown = 0; | |
| v_c1 = f; | |
| v_c1_delayed = f; | |
| discharge = false; | |
| } | |
| template <bool gated> | |
| inline float stepDigital(const float a, const float d, const bool gateActive) | |
| { | |
| float target = 0; | |
| switch (stage) | |
| { | |
| default: | |
| break; | |
| case s_attack: | |
| { | |
| phase += envelope_rate_linear(a); | |
| if (phase >= 1) | |
| { | |
| phase = 1; | |
| if constexpr (gated) | |
| stage = s_hold; | |
| else | |
| stage = s_decay; | |
| } | |
| if constexpr (gated) | |
| if (!gateActive) | |
| stage = s_decay; | |
| target = phase; | |
| break; | |
| } | |
| case s_decay: | |
| { | |
| phase -= envelope_rate_linear(d); | |
| if (phase <= 0) | |
| { | |
| phase = 0; | |
| stage = s_eoc; | |
| eoc_countdown = (int)std::round(sample_rate * 0.01); | |
| } | |
| target = phase; | |
| } | |
| } | |
| return target; | |
| } | |
| inline void immediatelyEnd() | |
| { | |
| stage = s_complete; | |
| eoc_output = 0; | |
| output = 0; | |
| eoc_countdown = 0; | |
| current = BLOCK_SIZE; | |
| outBlock0 = 0; | |
| } | |
| inline void process(const float a, const float d, const int ashape, const int dshape, | |
| const bool gateActive) | |
| { | |
| if (stage == s_complete) | |
| { | |
| output = 0; | |
| return; | |
| } | |
| if (stage == s_eoc) | |
| { | |
| output = 0; | |
| eoc_output = 1; | |
| eoc_countdown--; | |
| if (eoc_countdown == 0) | |
| { | |
| eoc_output = 0; | |
| stage = s_complete; | |
| } | |
| return; | |
| } | |
| eoc_output = 0; | |
| if (stage == s_analog_residual_decay && eoc_countdown) | |
| { | |
| eoc_output = 1; | |
| eoc_countdown--; | |
| } | |
| if (current == BLOCK_SIZE) | |
| { | |
| float target = 0; | |
| if (isGated && stage == s_hold) | |
| { | |
| target = 1; | |
| if (!gateActive) | |
| { | |
| phase = 1; | |
| stage = s_decay; | |
| } | |
| } | |
| else if (isDigital) | |
| { | |
| if (isGated) | |
| target = stepDigital<true>(a, d, gateActive); | |
| else | |
| target = stepDigital<false>(a, d, gateActive); | |
| } | |
| else | |
| { | |
| const float coeff_offset = 2.f - std::log2(sample_rate * BLOCK_SIZE_INV); | |
| auto ndc = (v_c1_delayed >= 0.99999f); | |
| if (ndc && !discharge) | |
| { | |
| phase = 1; | |
| stage = isGated ? s_hold : s_decay; | |
| } | |
| if (isGated && !discharge) | |
| { | |
| ndc = !gateActive; | |
| } | |
| discharge = ndc || discharge; | |
| v_c1_delayed = v_c1; | |
| static constexpr float v_gate = 1.02f; | |
| auto v_attack = (!discharge) * v_gate; | |
| auto v_decay = (!discharge) * v_gate; | |
| // In this case we only need the coefs in their stage | |
| float coef_A = !discharge ? powf(2.f, std::min(0.f, coeff_offset - a)) : 0; | |
| float coef_D = discharge ? powf(2.f, std::min(0.f, coeff_offset - d)) : 0; | |
| auto diff_v_a = std::max(0.f, v_attack - v_c1); | |
| auto diff_v_d = std::min(0.f, v_decay - v_c1); | |
| v_c1 = v_c1 + diff_v_a * coef_A + diff_v_d * coef_D; | |
| target = v_c1; | |
| if (stage == s_decay) | |
| { | |
| phase -= envelope_rate_linear(d); | |
| if (phase <= 0) | |
| { | |
| eoc_countdown = (int)std::round(sample_rate * 0.01); | |
| stage = s_analog_residual_decay; | |
| } | |
| } | |
| if (v_c1 < 1e-6 && discharge) | |
| { | |
| v_c1 = 0; | |
| v_c1_delayed = 0; | |
| discharge = false; | |
| target = 0; | |
| if (stage != s_analog_residual_decay) | |
| { | |
| eoc_countdown = (int)std::round(sample_rate * 0.01); | |
| stage = s_eoc; | |
| } | |
| else | |
| { | |
| stage = s_complete; | |
| eoc_countdown = 0; | |
| } | |
| } | |
| } | |
| if (stage == s_attack) | |
| { | |
| switch (ashape) | |
| { | |
| case 0: | |
| target = sqrt(target); | |
| break; | |
| case 2: | |
| target = target * target * target; | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| switch (dshape) | |
| { | |
| case 0: | |
| target = sqrt(target); | |
| break; | |
| case 2: | |
| target = target * target * target; | |
| break; | |
| } | |
| } | |
| float dO = (target - outBlock0) * BLOCK_SIZE_INV; | |
| for (int i = 0; i < BLOCK_SIZE; ++i) | |
| { | |
| outputCache[i] = outBlock0 + dO * i; | |
| } | |
| outBlock0 = target; | |
| current = 0; | |
| } | |
| output = outputCache[current]; | |
| current++; | |
| } | |
| }; | |
| } // namespace sst::surgext_rack::dsp::envelopes | |
| #endif // RACK_HACK_ADARENVELOPE_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Usage: | |
| sst::surgext_rack::dsp::envelopes::ADAREnvelope vca_env; | |
| // on prepare/activate | |
| vca_env.activate(getSampleRate()); | |
| // on note on | |
| vca_env.attackFrom(vca_env.output, 1, true, false); // from, shape, isDigital, isGated | |
| // process, outputs buffer already contains a waveform, frames = blocksize | |
| for (int i = 0; i < frames; ++i) | |
| { | |
| vca_env.process(0.0, 0.0, 1, 1, true); // atk, dec, atk shape, dec shape, gate | |
| outputs[0][i] *= vca_env.output; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment