From 1a2c853566c63791a2c470c60ab59f3d540e2054 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Mon, 23 Dec 2024 16:44:08 -0500 Subject: [PATCH] Add a shared 'MonoValues' structure with shared monophonic data Currently includes - temposync ratio (unused) - tables Use those tables to remove final pow 2 calls --- doc/design.md | 7 ++----- src/clap/six-sines-clap.cpp | 5 +++++ src/dsp/matrix_node.h | 23 +++++++++++--------- src/dsp/node_support.h | 26 ++++++++++++++++------- src/dsp/op_source.h | 21 ++++++++++++++----- src/dsp/sr_provider.h | 8 ++++++- src/synth/mono_values.h | 42 +++++++++++++++++++++++++++++++++++++ src/synth/patch.h | 11 ++++++++-- src/synth/synth.cpp | 9 ++++---- src/synth/synth.h | 11 +++++----- src/synth/voice.cpp | 25 +++++++++++----------- src/synth/voice.h | 7 +++---- 12 files changed, 137 insertions(+), 58 deletions(-) create mode 100644 src/synth/mono_values.h diff --git a/doc/design.md b/doc/design.md index d2da33e..09ac681 100644 --- a/doc/design.md +++ b/doc/design.md @@ -5,8 +5,6 @@ SOURCE: - waveshapes: - TX waveshapes - maybe a smoothed-saw and smoothed-square if i can figure out an analytic form i like -- Ratio uses 2^x table rather than pow -- Ratio lerps across block - 90%-100% of internal nyquist mute fades MAIN: @@ -29,16 +27,15 @@ PRESETS: - preset scan user directory to make tree PLAY MODE: -- Voice limit count +- Voice limit count and Voice Count display - Pitch Bend support - Portamento maybe GENERAL AND CLEANUPS -- AM is somehow not quiet right. Signal to zero seems 'no modulation' not 'no output' - Temposync the LFO +- 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" -- SRProvider uses table, not pow - LFO in Pulse and S&H need a tiny little LPF to avoid super-clicka INFRASTRUCTURE: diff --git a/src/clap/six-sines-clap.cpp b/src/clap/six-sines-clap.cpp index 6910c76..d97231c 100644 --- a/src/clap/six-sines-clap.cpp +++ b/src/clap/six-sines-clap.cpp @@ -115,6 +115,11 @@ struct SixSinesClap : public plugHelper_t, sst::clap_juce_shim::EditorProvider nextEvent = ev->get(ev, nextEventIndex); } + if (process->transport) + { + engine->monoValues.tempoSyncRatio = process->transport->tempo / 120.0; + } + float **out = process->audio_outputs[0].data32; for (auto s = 0U; s < process->frames_count; ++s) diff --git a/src/dsp/matrix_node.h b/src/dsp/matrix_node.h index 82973dd..0790652 100644 --- a/src/dsp/matrix_node.h +++ b/src/dsp/matrix_node.h @@ -24,6 +24,7 @@ #include "dsp/sr_provider.h" #include "dsp/node_support.h" #include "synth/patch.h" +#include "synth/mono_values.h" namespace baconpaul::six_sines { @@ -34,9 +35,10 @@ struct MatrixNodeFrom : public EnvelopeSupport, { OpSource &onto, &from; const float &level, &activeV, &pmrmV, &lfoToDepth, &mulLfoV; - MatrixNodeFrom(const Patch::MatrixNode &mn, OpSource &on, OpSource &fr) + MatrixNodeFrom(const Patch::MatrixNode &mn, OpSource &on, OpSource &fr, const MonoValues &mv) : onto(on), from(fr), level(mn.level), pmrmV(mn.pmOrRM), activeV(mn.active), - EnvelopeSupport(mn), LFOSupport(mn), lfoToDepth(mn.lfoToDepth), mulLfoV(mn.envLfoSum) + EnvelopeSupport(mn, mv), LFOSupport(mn, mv), lfoToDepth(mn.lfoToDepth), + mulLfoV(mn.envLfoSum) { } @@ -103,9 +105,9 @@ struct MatrixNodeSelf : EnvelopeSupport, LFOSupport, LFOSupport const float &level, &velSen; - OutputNode(const Patch::OutputNode &on, std::array &f) - : fromArr(f), level(on.level), velSen(on.velSensitivity), EnvelopeSupport(on) + OutputNode(const Patch::OutputNode &on, std::array &f, const MonoValues &mv) + : sr(mv), fromArr(f), level(on.level), velSen(on.velSensitivity), EnvelopeSupport(on, mv) { memset(output, 0, sizeof(output)); } diff --git a/src/dsp/node_support.h b/src/dsp/node_support.h index 9d19aee..db16b7d 100644 --- a/src/dsp/node_support.h +++ b/src/dsp/node_support.h @@ -23,6 +23,7 @@ #include "sst/basic-blocks/modulators/SimpleLFO.h" #include "dsp/sr_provider.h" +#include "synth/mono_values.h" namespace baconpaul::six_sines { @@ -33,8 +34,8 @@ template struct EnvelopeSupport SRProvider sr; const float &delay, &attackv, &hold, &decay, &sustain, &release, &powerV; - EnvelopeSupport(const T &mn) - : env(&sr), delay(mn.delay), attackv(mn.attack), hold(mn.hold), decay(mn.decay), + 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) { } @@ -80,24 +81,35 @@ template struct EnvelopeSupport template struct LFOSupport { SRProvider sr; + const T ¶mBundle; + const MonoValues &monoValues; - const float &lfoRate, &lfoDeform, &lfoShape, &lfoActiveV; + const float &lfoRate, &lfoDeform, &lfoShape, &lfoActiveV, &tempoSyncV; bool active; sst::basic_blocks::modulators::SimpleLFO lfo; - LFOSupport(const T &mn) - : lfo(&sr), lfoRate(mn.lfoRate), lfoDeform(mn.lfoDeform), lfoShape(mn.lfoShape), - lfoActiveV(mn.lfoActive) + LFOSupport(const T &mn, const MonoValues &mv) + : sr(mv), paramBundle(mn), lfo(&sr), lfoRate(mn.lfoRate), lfoDeform(mn.lfoDeform), + lfoShape(mn.lfoShape), lfoActiveV(mn.lfoActive), tempoSyncV(mn.tempoSync), monoValues(mv) { } int shape; + bool tempoSync{false}; void lfoAttack() { + tempoSync = tempoSyncV > 0.5; shape = static_cast(std::round(lfoShape)); lfo.attack(shape); } - void lfoProcess() { lfo.process_block(lfoRate, lfoDeform, shape); } + void lfoProcess() + { + auto rate = lfoRate; + if (tempoSync) + rate = monoValues.tempoSyncRatio * paramBundle.lfoRate.meta.snapToTemposync(rate); + + lfo.process_block(rate, lfoDeform, shape); + } }; } // namespace baconpaul::six_sines diff --git a/src/dsp/op_source.h b/src/dsp/op_source.h index aced734..8e45a83 100644 --- a/src/dsp/op_source.h +++ b/src/dsp/op_source.h @@ -23,6 +23,7 @@ #include "dsp/sintable.h" #include "dsp/node_support.h" #include "synth/patch.h" +#include "synth/mono_values.h" namespace baconpaul::six_sines { @@ -36,6 +37,8 @@ struct alignas(16) OpSource : public EnvelopeSupport, float output alignas(16)[blockSize]; + const MonoValues &monoValues; + bool keytrack{true}; const float &ratio, &activeV, &envToRatio, &lfoToRatio, &lfoByEnv; // in frequency multiple bool active{false}; @@ -45,9 +48,10 @@ struct alignas(16) OpSource : public EnvelopeSupport, uint32_t phase; uint32_t dPhase; - OpSource(const Patch::SourceNode &sn) - : EnvelopeSupport(sn), LFOSupport(sn), ratio(sn.ratio), activeV(sn.active), - envToRatio(sn.envToRatio), lfoToRatio(sn.lfoToRatio), lfoByEnv(sn.envLfoSum) + OpSource(const Patch::SourceNode &sn, const MonoValues &mv) + : monoValues(mv), EnvelopeSupport(sn, mv), LFOSupport(sn, mv), ratio(sn.ratio), + activeV(sn.active), envToRatio(sn.envToRatio), lfoToRatio(sn.lfoToRatio), + lfoByEnv(sn.envLfoSum) { reset(); } @@ -79,6 +83,7 @@ struct alignas(16) OpSource : public EnvelopeSupport, float baseFrequency{0}; void setBaseFrequency(float freq) { baseFrequency = freq; } + float priorRF{-10000000}; void renderBlock(bool gated) { if (!active) @@ -92,12 +97,18 @@ struct alignas(16) OpSource : public EnvelopeSupport, lfoProcess(); auto lfoFac = lfoByEnv > 0.5 ? env.output : 1.f; - auto rf = - pow(2.f, ratio + envToRatio * env.output + lfoFac * lfoToRatio * lfo.outputBlock[0]); + auto rf = monoValues.twoToTheX.twoToThe(ratio + envToRatio * env.output + + lfoFac * lfoToRatio * lfo.outputBlock[0]); + if (priorRF < -10000) + priorRF = rf; + auto dRF = (priorRF - rf) / blockSize; + std::swap(rf, priorRF); for (int i = 0; i < blockSize; ++i) { dPhase = st.dPhase(baseFrequency * rf); + rf += dRF; + phase += dPhase; auto fb = 0.5 * (fbVal[0] + fbVal[1]); auto out = st.at(phase + phaseInput[i] + (int32_t)(feedbackLevel[i] * fb)) * rmLevel[i]; diff --git a/src/dsp/sr_provider.h b/src/dsp/sr_provider.h index 34fd027..bd5ff59 100644 --- a/src/dsp/sr_provider.h +++ b/src/dsp/sr_provider.h @@ -17,12 +17,18 @@ #define BACONPAUL_SIX_SINES_DSP_SR_PROVIDER_H #include "configuration.h" +#include "synth/mono_values.h" namespace baconpaul::six_sines { struct SRProvider { - float envelope_rate_linear_nowrap(float f) { return (blockSize / gSampleRate) * pow(2.0, -f); } + const MonoValues &monoValues; + SRProvider(const MonoValues &mv) : monoValues(mv) {} + float envelope_rate_linear_nowrap(float f) + { + return (blockSize / gSampleRate) * monoValues.twoToTheX.twoToThe(-f); + } static constexpr float samplerate{gSampleRate}; }; } // namespace baconpaul::six_sines diff --git a/src/synth/mono_values.h b/src/synth/mono_values.h new file mode 100644 index 0000000..f360553 --- /dev/null +++ b/src/synth/mono_values.h @@ -0,0 +1,42 @@ +/* + * Six Sines + * + * A synth with audio rate modulation. + * + * Copyright 2024, Paul Walker and Various authors, as described in the github + * transaction log. + * + * This source repo is released under the MIT license, but has + * GPL3 dependencies, as such the combined work will be + * released under GPL3. + * + * The source code and license are at https://github.com/baconpaul/six-sines + */ + +#ifndef BACONPAUL_SIX_SINES_SYNTH_MONO_VALUES_H +#define BACONPAUL_SIX_SINES_SYNTH_MONO_VALUES_H + +#include +#include + +struct MTSClient; + +namespace baconpaul::six_sines +{ +struct MonoValues +{ + MonoValues() + { + tuningProvider.init(); + twoToTheX.init(); + } + + float tempoSyncRatio{1}; + + MTSClient *mtsClient{nullptr}; + + sst::basic_blocks::tables::EqualTuningProvider tuningProvider; + sst::basic_blocks::tables::TwoToTheXProvider twoToTheX; +}; +}; // namespace baconpaul::six_sines +#endif // MONO_VALUES_H diff --git a/src/synth/patch.h b/src/synth/patch.h index b8b64e1..5556f9d 100644 --- a/src/synth/patch.h +++ b/src/synth/patch.h @@ -162,17 +162,24 @@ struct Patch .withDefault(true) .withID(id0 + 4) .withName(name + " LFO Active") - .withGroupName(name)) + .withGroupName(name)), + tempoSync(md_t() // non-automatable + .asBool() + .withID(id0 + 5) + .withName(name + " Temposync") + .withGroupName(name) + .withDefault(false)) { } - Param lfoRate, lfoDeform, lfoShape, lfoActive; + Param lfoRate, lfoDeform, lfoShape, lfoActive, tempoSync; void appendLFOParams(std::vector &res) { res.push_back(&lfoRate); res.push_back(&lfoDeform); res.push_back(&lfoShape); + res.push_back(&tempoSync); } }; diff --git a/src/synth/synth.cpp b/src/synth/synth.cpp index e8f5c89..5b64138 100644 --- a/src/synth/synth.cpp +++ b/src/synth/synth.cpp @@ -28,20 +28,19 @@ namespace sdsp = sst::basic_blocks::dsp; Synth::Synth() : responder(*this), - voices(sst::cpputils::make_array(patch, tuningProvider)) + voices(sst::cpputils::make_array(patch, monoValues)) { - tuningProvider.init(); voiceManager = std::make_unique(responder, monoResponder); lagHandler.setRate(60, blockSize, gSampleRate); vuPeak.setSampleRate(gSampleRate); - mtsClient = MTS_RegisterClient(); + monoValues.mtsClient = MTS_RegisterClient(); } Synth::~Synth() { - if (mtsClient) + if (monoValues.mtsClient) { - MTS_DeregisterClient(mtsClient); + MTS_DeregisterClient(monoValues.mtsClient); } } diff --git a/src/synth/synth.h b/src/synth/synth.h index 486e6f9..7dc4796 100644 --- a/src/synth/synth.h +++ b/src/synth/synth.h @@ -33,8 +33,7 @@ #include "synth/voice.h" #include "synth/patch.h" - -struct MTSClient; +#include "mono_values.h" namespace baconpaul::six_sines { @@ -48,7 +47,7 @@ struct Synth std::unique_ptr resampler; Patch patch; - MTSClient *mtsClient{nullptr}; + MonoValues monoValues; struct VMConfig { @@ -75,6 +74,7 @@ struct Synth v->key = k; v->velocity = ve; } + void moveAndRetriggerVoice(Voice *v, uint16_t p, uint16_t c, uint16_t k, float ve) { v->key = k; @@ -90,7 +90,9 @@ struct Synth buffer[0].polyphonyGroup = 0; return 1; }; + void endVoiceCreationTransaction(uint16_t, uint16_t, uint16_t, int32_t, float) {} + void terminateVoice(Voice *voice) { voice->gated = false; @@ -117,7 +119,6 @@ struct Synth synth.voices[i].gated = true; synth.voices[i].key = key; synth.voices[i].channel = ch; - synth.voices[i].mtsClient = synth.mtsClient; synth.voices[i].velocity = vel; synth.voices[i].attack(); @@ -159,7 +160,6 @@ struct Synth double realSampleRate{0}; void setSampleRate(double sampleRate) { - SXSNLOG("Creating resampler at " << sampleRate << " from " << gSampleRate); realSampleRate = sampleRate; resampler = std::make_unique(gSampleRate, realSampleRate); } @@ -203,7 +203,6 @@ struct Synth uiToAudioQueue_T uiToAudio; std::atomic doFullRefresh{false}; sst::basic_blocks::dsp::UIComponentLagHandler lagHandler; - sst::basic_blocks::tables::EqualTuningProvider tuningProvider; void pushFullUIRefresh(); void postLoad() diff --git a/src/synth/voice.cpp b/src/synth/voice.cpp index c0dd5c3..e86617e 100644 --- a/src/synth/voice.cpp +++ b/src/synth/voice.cpp @@ -24,20 +24,19 @@ namespace baconpaul::six_sines namespace scpu = sst::cpputils; -Voice::Voice(const Patch &p, const sst::basic_blocks::tables::EqualTuningProvider &tp) - : out(p.output, mixerNode), output{out.output[0], out.output[1]}, - src(scpu::make_array_lambda([this, &p](auto i) - { return OpSource(p.sourceNodes[i]); })), +Voice::Voice(const Patch &p, const MonoValues &mv) + : monoValues(mv), out(p.output, mixerNode, mv), output{out.output[0], out.output[1]}, + src(scpu::make_array_lambda([this, &p, &mv](auto i) + { return OpSource(p.sourceNodes[i], mv); })), mixerNode(scpu::make_array_lambda( - [this, &p](auto i) { return MixerNode(p.mixerNodes[i], this->src[i]); })), + [this, &p, &mv](auto i) { return MixerNode(p.mixerNodes[i], this->src[i], mv); })), selfNode(scpu::make_array_lambda( - [this, &p](auto i) { return MatrixNodeSelf(p.selfNodes[i], this->src[i]); })), + [this, &p, &mv](auto i) { return MatrixNodeSelf(p.selfNodes[i], this->src[i], mv); })), matrixNode(scpu::make_array_lambda( - [&p, this](auto i) { + [&p, this, &mv](auto i) { return MatrixNodeFrom(p.matrixNodes[i], this->targetAtMatrix(i), - this->sourceAtMatrix(i)); - })), - tuningProvider(tp) + this->sourceAtMatrix(i), mv); + })) { std::fill(isKeytrack.begin(), isKeytrack.end(), true); std::fill(cmRatio.begin(), cmRatio.end(), 1.0); @@ -61,11 +60,11 @@ void Voice::attack() void Voice::renderBlock() { float retuneKey = key; - if (mtsClient && MTS_HasMaster(mtsClient)) + if (monoValues.mtsClient && MTS_HasMaster(monoValues.mtsClient)) { - retuneKey += MTS_RetuningInSemitones(mtsClient, key, channel); + retuneKey += MTS_RetuningInSemitones(monoValues.mtsClient, key, channel); } - auto baseFreq = tuningProvider.note_to_pitch(retuneKey - 69) * 440.0; + auto baseFreq = monoValues.tuningProvider.note_to_pitch(retuneKey - 69) * 440.0; for (int i = 0; i < numOps; ++i) { diff --git a/src/synth/voice.h b/src/synth/voice.h index 89fc5a4..dfe119a 100644 --- a/src/synth/voice.h +++ b/src/synth/voice.h @@ -19,6 +19,7 @@ #include #include "dsp/op_source.h" #include "dsp/matrix_node.h" +#include "synth/mono_values.h" struct MTSClient; @@ -29,11 +30,9 @@ struct Patch; struct Voice { const float *const output[2]; - const sst::basic_blocks::tables::EqualTuningProvider &tuningProvider; + const MonoValues &monoValues; - MTSClient *mtsClient{nullptr}; - - Voice(const Patch &, const sst::basic_blocks::tables::EqualTuningProvider &); + Voice(const Patch &, const MonoValues &); ~Voice() = default; void attack();