From dabfe09c70047c0e5f5cfe0c041ad8e09bdf7006 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 24 Dec 2024 11:39:25 -0500 Subject: [PATCH] Move to SC-style envelope (#4) 1. Add delay stage to SC-style envelope 2. Move to 25 second with zero style 3. Add shape and shape knobs 4. Do patch migration so old patches load and convert --- doc/design.md | 4 +-- libs/sst/sst-basic-blocks | 2 +- src/configuration.h | 1 + src/dsp/node_support.h | 20 ++++++++----- src/dsp/op_source.h | 7 +++-- src/dsp/sr_provider.h | 4 ++- src/synth/patch.cpp | 48 +++++++++++++++++++++++++++-- src/synth/patch.h | 60 ++++++++++++++++++++++++++++--------- src/synth/synth.cpp | 5 +--- src/ui/dahdsr-components.h | 20 +++++++++++-- src/ui/six-sines-editor.cpp | 2 +- 11 files changed, 135 insertions(+), 38 deletions(-) diff --git a/doc/design.md b/doc/design.md index 2e38982..f033429 100644 --- a/doc/design.md +++ b/doc/design.md @@ -35,9 +35,7 @@ GENERAL AND CLEANUPS - AM is somehow not quiet right. Signal to zero seems 'no modulation' not 'no output' - Don't send VU etc when editor not attached - Edit area says "click a knbo to edit on startup" -- LFO in Pulse and S&H need a tiny little LPF to avoid super-clicka +- LFO in Pulse and S&H need a tiny little LPF to avoid super-clicka on modulation nodes -INFRASTRUCTURE: -- mac signing diff --git a/libs/sst/sst-basic-blocks b/libs/sst/sst-basic-blocks index 263c51d..6f23f92 160000 --- a/libs/sst/sst-basic-blocks +++ b/libs/sst/sst-basic-blocks @@ -1 +1 @@ -Subproject commit 263c51ddca44d986f67cdf3707151841d99785bc +Subproject commit 6f23f9234d9a58960743f0a9add1584370293191 diff --git a/src/configuration.h b/src/configuration.h index 7732b25..78f8a46 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -47,5 +47,6 @@ inline std::string fileTrunc(const std::string &f) #define SXSNLOG(...) \ std::cout << fileTrunc(__FILE__) << ":" << __LINE__ << " " << __VA_ARGS__ << std::endl; +#define SXSNV(x) " " << #x << "=" << x #endif // CONFIGURATION_H diff --git a/src/dsp/node_support.h b/src/dsp/node_support.h index db16b7d..91d326b 100644 --- a/src/dsp/node_support.h +++ b/src/dsp/node_support.h @@ -19,7 +19,7 @@ #include #include -#include "sst/basic-blocks/modulators/DAHDSREnvelope.h" +#include "sst/basic-blocks/modulators/AHDSRShapedSC.h" #include "sst/basic-blocks/modulators/SimpleLFO.h" #include "dsp/sr_provider.h" @@ -34,21 +34,26 @@ template struct EnvelopeSupport SRProvider sr; const float &delay, &attackv, &hold, &decay, &sustain, &release, &powerV; + const float &ash, &dsh, ↱ EnvelopeSupport(const T &mn, const MonoValues &mv) : sr(mv), env(&sr), delay(mn.delay), attackv(mn.attack), hold(mn.hold), decay(mn.decay), - sustain(mn.sustain), release(mn.release), powerV(mn.envPower) + sustain(mn.sustain), release(mn.release), powerV(mn.envPower), ash(mn.aShape), + dsh(mn.dShape), rsh(mn.rShape) { } bool active{true}, constantEnv{false}; - sst::basic_blocks::modulators::DAHDSREnvelope env; + using range_t = sst::basic_blocks::modulators::TwentyFiveSecondExp; + using env_t = sst::basic_blocks::modulators::AHDSRShapedSC; + env_t env; void envAttack() { + env.initializeLuts(); active = powerV > 0.5; - auto mn = sst::basic_blocks::modulators::TenSecondRange::etMin + 0.001; - auto mx = sst::basic_blocks::modulators::TenSecondRange::etMax - 0.001; + auto mn = 0.0001; + auto mx = 1 - mn; if (decay < mn && attackv < mn && hold < mn && delay < mn && release > mx) { @@ -60,7 +65,7 @@ template struct EnvelopeSupport } if (active && !constantEnv) - env.attack(delay); + env.attackFromWithDelay(0.f, delay, attackv); else if (constantEnv) { for (int i = 0; i < blockSize; ++i) @@ -74,7 +79,8 @@ template struct EnvelopeSupport if (!active || constantEnv) return; - env.processBlockScaledAD(delay, attackv, hold, decay, sustain, release, gated); + env.processBlockWithDelay(delay, attackv, hold, decay, sustain, release, ash, dsh, rsh, + gated, true); } }; diff --git a/src/dsp/op_source.h b/src/dsp/op_source.h index 8e45a83..e5d73ff 100644 --- a/src/dsp/op_source.h +++ b/src/dsp/op_source.h @@ -95,10 +95,11 @@ struct alignas(16) OpSource : public EnvelopeSupport, } envProcess(gated); lfoProcess(); - auto lfoFac = lfoByEnv > 0.5 ? env.output : 1.f; + auto lfoFac = lfoByEnv > 0.5 ? env.outputCache[blockSize - 1] : 1.f; - auto rf = monoValues.twoToTheX.twoToThe(ratio + envToRatio * env.output + - lfoFac * lfoToRatio * lfo.outputBlock[0]); + auto rf = + monoValues.twoToTheX.twoToThe(ratio + envToRatio * env.outputCache[blockSize - 1] + + lfoFac * lfoToRatio * lfo.outputBlock[0]); if (priorRF < -10000) priorRF = rf; auto dRF = (priorRF - rf) / blockSize; diff --git a/src/dsp/sr_provider.h b/src/dsp/sr_provider.h index bd5ff59..994b191 100644 --- a/src/dsp/sr_provider.h +++ b/src/dsp/sr_provider.h @@ -29,7 +29,9 @@ struct SRProvider { return (blockSize / gSampleRate) * monoValues.twoToTheX.twoToThe(-f); } - static constexpr float samplerate{gSampleRate}; + static constexpr double samplerate{gSampleRate}; + static constexpr double sampleRate{gSampleRate}; + static constexpr double sampleRateInv{1.0 / gSampleRate}; }; } // namespace baconpaul::six_sines #endif // SR_PROVIDER_H diff --git a/src/synth/patch.cpp b/src/synth/patch.cpp index fc0d377..3884086 100644 --- a/src/synth/patch.cpp +++ b/src/synth/patch.cpp @@ -33,7 +33,7 @@ std::string Patch::toState() const TiXmlDocument doc; TiXmlElement rootNode("patch"); rootNode.SetAttribute("id", "org.baconpaul.six-sines"); - rootNode.SetAttribute("version", "2"); + rootNode.SetAttribute("version", Patch::patchVersion); TiXmlElement paramsNode("params"); @@ -80,7 +80,7 @@ bool Patch::fromState(const std::string &idata) SXSNLOG("No Version"); return false; } - if (ver != 2) + if (ver < 2 || ver > Patch::patchVersion) { SXSNLOG("Unknown version " << ver); return false; @@ -108,6 +108,8 @@ bool Patch::fromState(const std::string &idata) } else { + auto *param = it->second; + value = migrateParamValueFromVersion(param, value, ver); it->second->value = value; } } @@ -154,6 +156,8 @@ bool Patch::fromStateV1(const std::string &idata) } else { + auto *param = it->second; + vv = migrateParamValueFromVersion(param, vv, 1); it->second->value = vv; } } @@ -164,4 +168,44 @@ bool Patch::fromStateV1(const std::string &idata) return true; } +float Patch::migrateParamValueFromVersion(Param *p, float value, uint32_t version) +{ + if ((p->adhocFeatures & Param::AdHocFeatureValues::ENVTIME) && version <= 2) + { + /* + * This is a gross way to do this but really its just to not break + * Jacky's patches from the very first weekend, so... + */ + static auto oldStyle = md_t().asFloat().asLog2SecondsRange( + sst::basic_blocks::modulators::TenSecondRange::etMin, + sst::basic_blocks::modulators::TenSecondRange::etMax); + ; + if (value < oldStyle.minVal + 0.0001) + return 0.f; + if (value > oldStyle.maxVal - 0.0001) + return 1.f; + auto osv = oldStyle.valueToString(value); + if (osv.has_value()) + { + std::string em; + auto nsv = p->meta.valueFromString(*osv, em); + if (nsv.has_value()) + { + SXSNLOG("Converting version " << version << " node '" << p->meta.name + << "' val=" << value << " -> " << *nsv); + value = *nsv; + } + else + { + value = 0.f; + } + } + else + { + value = 0.f; + } + } + return value; +} + } // namespace baconpaul::six_sines \ No newline at end of file diff --git a/src/synth/patch.h b/src/synth/patch.h index 5556f9d..52a84c6 100644 --- a/src/synth/patch.h +++ b/src/synth/patch.h @@ -44,10 +44,17 @@ struct Param } operator const float &() const { return value; } + + uint64_t adhocFeatures{0}; + enum AdHocFeatureValues + { + ENVTIME = 1 << 0 // tag for ADSR envs we changed version 2-3 + }; }; struct Patch { + static constexpr uint32_t patchVersion{3}; std::vector params; std::unordered_map paramMap; @@ -57,11 +64,7 @@ struct Patch static md_t floatMd() { return md_t().asFloat().withFlags(floatFlags); } static md_t floatEnvRateMd() { - return md_t() - .asFloat() - .withFlags(floatFlags) - .asLog2SecondsRange(sst::basic_blocks::modulators::TenSecondRange::etMin, - sst::basic_blocks::modulators::TenSecondRange::etMax); + return md_t().asFloat().withFlags(floatFlags).as25SecondExpTime(); } static md_t boolMd() { return md_t().asBool().withFlags(boolFlags); } static md_t intMd() { return md_t().asInt().withFlags(boolFlags); } @@ -81,8 +84,9 @@ struct Patch { if (paramMap.find(p->meta.id) != paramMap.end()) { - SXSNLOG("Duplicate param id " << p->meta.id << " at " << p->meta.name); - SXSNLOG("Collision with " << paramMap[p->meta.id]->meta.name); + SXSNLOG("Duplicate param id " << p->meta.id); + SXSNLOG(" - New Param : '" << p->meta.name << "'"); + SXSNLOG(" - Other Param : '" << paramMap[p->meta.id]->meta.name << "'"); std::terminate(); } paramMap.emplace(p->meta.id, p); @@ -185,27 +189,26 @@ struct Patch struct DAHDSRMixin { - using tsr = sst::basic_blocks::modulators::TenSecondRange; DAHDSRMixin(const std::string name, int id0, bool longAdsr) : delay(floatEnvRateMd() .withName(name + " Env Delay") .withGroupName(name) - .withDefault(tsr::etMin) + .withDefault(0.f) .withID(id0 + 0)), attack(floatEnvRateMd() .withName(name + " Env Attack") .withGroupName(name) - .withDefault(longAdsr ? -7.f : tsr::etMin) + .withDefault(longAdsr ? 0.05 : 0.f) .withID(id0 + 1)), hold(floatEnvRateMd() .withName(name + " Env Hold") .withGroupName(name) - .withDefault(tsr::etMin) + .withDefault(0.f) .withID(id0 + 2)), decay(floatEnvRateMd() .withName(name + " Env Decay") .withGroupName(name) - .withDefault(longAdsr ? 0.f : tsr::etMin) + .withDefault(longAdsr ? 0.3 : 0.f) .withID(id0 + 3)), sustain(floatMd() .asPercent() @@ -216,17 +219,41 @@ struct Patch release(floatEnvRateMd() .withName(name + " Env Release") .withGroupName(name) - .withDefault(longAdsr ? -2.f : tsr::etMax) + .withDefault(longAdsr ? 0.4 : 1.f) .withID(id0 + 5)), envPower(boolMd() .withName(name + " Env Power") .withGroupName(name) .withDefault(true) - .withID(id0 + 6)) + .withID(id0 + 6)), + aShape(floatMd() + .asPercentBipolar() + .withName(name + " Attack Shape") + .withGroupName(name) + .withDefault(0) + .withID(id0 + 7)), + dShape(floatMd() + .asPercentBipolar() + .withName(name + " Decay Shape") + .withGroupName(name) + .withDefault(0) + .withID(id0 + 8)), + rShape(floatMd() + .asPercentBipolar() + .withName(name + " Release Shape") + .withGroupName(name) + .withDefault(0) + .withID(id0 + 9)) { + delay.adhocFeatures = Param::AdHocFeatureValues::ENVTIME; + attack.adhocFeatures = Param::AdHocFeatureValues::ENVTIME; + hold.adhocFeatures = Param::AdHocFeatureValues::ENVTIME; + decay.adhocFeatures = Param::AdHocFeatureValues::ENVTIME; + release.adhocFeatures = Param::AdHocFeatureValues::ENVTIME; } Param delay, attack, hold, decay, sustain, release, envPower; + Param aShape, dShape, rShape; void appendDAHDSRParams(std::vector &res) { @@ -237,6 +264,9 @@ struct Patch res.push_back(&sustain); res.push_back(&release); res.push_back(&envPower); + res.push_back(&aShape); + res.push_back(&dShape); + res.push_back(&rShape); } }; @@ -546,6 +576,8 @@ struct Patch private: bool fromStateV1(const std::string &); + + float migrateParamValueFromVersion(Param *p, float value, uint32_t version); }; } // namespace baconpaul::six_sines #endif // PATCH_H diff --git a/src/synth/synth.cpp b/src/synth/synth.cpp index 6dcd75c..531a33b 100644 --- a/src/synth/synth.cpp +++ b/src/synth/synth.cpp @@ -73,10 +73,7 @@ void Synth::process(const clap_output_events_t *outq) mech::accumulate_from_to(cvoice->output[0], lOutput[0]); mech::accumulate_from_to(cvoice->output[1], lOutput[1]); - if (cvoice->out.env.stage > - sst::basic_blocks::modulators::DAHDSREnvelope::s_release || - cvoice->fadeBlocks == 0) + if (cvoice->out.env.stage > OutputNode::env_t::s_release || cvoice->fadeBlocks == 0) { responder.doVoiceEndCallback(cvoice); cvoice = removeFromVoiceList(cvoice); diff --git a/src/ui/dahdsr-components.h b/src/ui/dahdsr-components.h index 3b3b2c9..e4c15a1 100644 --- a/src/ui/dahdsr-components.h +++ b/src/ui/dahdsr-components.h @@ -48,6 +48,15 @@ template struct DAHDSRComponents mk(v.sustain, 4, "S"); mk(v.release, 5, "R"); + auto c = asComp(); + createComponent(e, *c, v.aShape, shapes[0], shapesD[0]); + createComponent(e, *c, v.dShape, shapes[1], shapesD[1]); + createComponent(e, *c, v.rShape, shapes[2], shapesD[2]); + + c->addAndMakeVisible(*shapes[0]); + c->addAndMakeVisible(*shapes[1]); + c->addAndMakeVisible(*shapes[2]); + titleLab = std::make_unique(); titleLab->setText("Envelope"); asComp()->addAndMakeVisible(*titleLab); @@ -71,7 +80,11 @@ template struct DAHDSRComponents { if (!slider[i]) continue; - slider[i]->setBounds(bx.withHeight(q)); + slider[i]->setBounds(bx.withHeight(q).withTrimmedTop(uicSliderWidth)); + if (i % 2) + { + shapes[i / 2]->setBounds(bx.withHeight(uicSliderWidth)); + } lab[i]->setBounds(bx.withTrimmedTop(q + uicLabelGap)); bx = bx.translated(uicSliderWidth, 0); } @@ -79,9 +92,12 @@ template struct DAHDSRComponents return juce::Rectangle(x, y, 0, 0).withLeft(bx.getRight()).withBottom(bx.getBottom()); } - static constexpr int nels{6}; // dahdsr + static constexpr int nels{6}; // dahdsr + static constexpr int nShape{3}; // no shape for delay jp;d or release std::array, nels> slider; std::array, nels> sliderD; + std::array, nShape> shapes; + std::array, nShape> shapesD; std::array, nels> lab; std::unique_ptr titleLab; }; diff --git a/src/ui/six-sines-editor.cpp b/src/ui/six-sines-editor.cpp index 9c247e1..e289646 100644 --- a/src/ui/six-sines-editor.cpp +++ b/src/ui/six-sines-editor.cpp @@ -312,7 +312,7 @@ struct MenuValueTypein : HasEditor, juce::PopupMenu::CustomComponent, juce::Text MenuValueTypein(SixSinesEditor &editor, juce::Component::SafePointer under) - : HasEditor(editor), underComp(under) + : juce::PopupMenu::CustomComponent(false), HasEditor(editor), underComp(under) { textEditor = std::make_unique(); textEditor->setWantsKeyboardFocus(true);