From a8ece91339e6d4307c28e92ecda6ff4376141abe Mon Sep 17 00:00:00 2001 From: lnd3 Date: Thu, 12 Sep 2024 16:20:10 +0200 Subject: [PATCH] Add custom band limited saw generator and chamberlin two-pole low pass multi output filter. --- packages/math/include/math/MathFunc.h | 49 +++++- .../include/nodegraph/NodeGraphSchema.h | 2 + .../nodegraph/operations/NodeGraphOpFilter.h | 44 ++++++ .../nodegraph/operations/NodeGraphOpSignal.h | 143 +++++++++++++++++- .../source/common/NodeGraphSchema.cpp | 6 + .../common/operations/NodeGraphOpFilter.cpp | 48 ++++++ .../common/operations/NodeGraphOpSignal.cpp | 91 +++++------ .../source/common/ui/UIContainer.cpp | 2 + .../rendering/source/common/ui/UICreator.cpp | 5 +- .../rendering/source/common/ui/UIVisitors.cpp | 2 +- 10 files changed, 335 insertions(+), 57 deletions(-) diff --git a/packages/math/include/math/MathFunc.h b/packages/math/include/math/MathFunc.h index eebbb8c2..e97043b8 100644 --- a/packages/math/include/math/MathFunc.h +++ b/packages/math/include/math/MathFunc.h @@ -125,7 +125,6 @@ namespace l::math::functions { } } - template T cos(T val) { if constexpr (std::is_floating_point_v) { @@ -138,6 +137,54 @@ namespace l::math::functions { } } + template + T tan(T val) { + if constexpr (std::is_floating_point_v) { + if constexpr (sizeof(T) == 4) { + return tanf(val); + } + else if constexpr (sizeof(T) == 8) { + return ::tan(val); + } + } + } + + template + T asin(T val) { + if constexpr (std::is_floating_point_v) { + if constexpr (sizeof(T) == 4) { + return asinf(val); + } + else if constexpr (sizeof(T) == 8) { + return ::asin(val); + } + } + } + + template + T acos(T val) { + if constexpr (std::is_floating_point_v) { + if constexpr (sizeof(T) == 4) { + return acosf(val); + } + else if constexpr (sizeof(T) == 8) { + return ::acos(val); + } + } + } + + template + T atan(T val) { + if constexpr (std::is_floating_point_v) { + if constexpr (sizeof(T) == 4) { + return atanf(val); + } + else if constexpr (sizeof(T) == 8) { + return ::atan(val); + } + } + } + template T mod(T val, T mod) { if constexpr (std::is_floating_point_v) { diff --git a/packages/nodegraph/include/nodegraph/NodeGraphSchema.h b/packages/nodegraph/include/nodegraph/NodeGraphSchema.h index 9e6e25be..4eeb98aa 100644 --- a/packages/nodegraph/include/nodegraph/NodeGraphSchema.h +++ b/packages/nodegraph/include/nodegraph/NodeGraphSchema.h @@ -59,6 +59,7 @@ namespace l::nodegraph { RegisterNodeType("Logic", 102, "Xor"); RegisterNodeType("Filter", 150, "Lowpass"); RegisterNodeType("Filter", 151, "Highpass"); + RegisterNodeType("Filter", 152, "Chamberlin two-pole (4 mode)"); RegisterNodeType("Output", 200, "Debug"); RegisterNodeType("Output", 201, "Speaker"); RegisterNodeType("Output", 202, "Plot"); @@ -84,6 +85,7 @@ namespace l::nodegraph { RegisterNodeType("Signal", 353, "Sine FM 3"); RegisterNodeType("Signal", 354, "Saw"); RegisterNodeType("Signal", 355, "Sine 2"); + RegisterNodeType("Signal", 356, "Saw 2"); } diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpFilter.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpFilter.h index e7009ef5..c1ec2616 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpFilter.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpFilter.h @@ -86,5 +86,49 @@ namespace l::nodegraph { float mState1 = 0.0f; }; + /*********************************************************************/ + // source: https://www.musicdsp.org/en/latest/Filters/23-state-variable.html + class GraphFilterChamberlain2pole : public NodeGraphOp { + public: + std::string defaultInStrings[4] = { "In", "Cutoff", "Resonance", "Mode"}; + std::string defaultOutStrings[1] = { "Out"}; + + GraphFilterChamberlain2pole(NodeGraphBase* node) : + NodeGraphOp(node, 4, 1) + { + mState.resize(4); + } + + virtual ~GraphFilterChamberlain2pole() = default; + virtual void Reset() override; + virtual void Process(int32_t numSamples, std::vector& inputs, std::vector& outputs) override; + virtual bool IsDataVisible(int8_t) override { return true; } + virtual bool IsDataEditable(int8_t channel) override { return channel > 0 ? true : false; } + virtual std::string_view GetInputName(int8_t inputChannel) override { + return defaultInStrings[inputChannel]; + } + + virtual std::string_view GetOutputName(int8_t outputChannel) override { + return defaultOutStrings[outputChannel]; + } + + virtual std::string_view GetName() override { + return "Chamberlin two-pole"; + } + protected: + float mSamplesUntilUpdate = 0.0f; + float mUpdateSamples = 16.0f; + + float mInputValuePrev = 0.0f; + float mCutoff = 0.0f; + float mResonance = 0.0f; + + float mSampleRate = 44100.0f; + float mFreq = 0.0f; + float mScale = 0.0f; + + std::vector mState; + }; + } diff --git a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignal.h b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignal.h index a78978da..5989fad6 100644 --- a/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignal.h +++ b/packages/nodegraph/include/nodegraph/operations/NodeGraphOpSignal.h @@ -25,7 +25,7 @@ namespace l::nodegraph { class GraphSignalBase : public NodeGraphOp { public: - static const int8_t mNumDefaultInputs = 7; + static const int8_t mNumDefaultInputs = 4; static const int8_t mNumDefaultOutputs = 1; GraphSignalBase(NodeGraphBase* node, std::string_view name, int32_t numInputs = 0, int32_t numOutputs = 0, int32_t numConstants = 0) : @@ -33,7 +33,7 @@ namespace l::nodegraph { mName(name) {} - std::string defaultInStrings[mNumDefaultInputs] = { "Reset", "Freq", "Volume", "Smooth", "Cutoff", "Resonance", "Phase expansion"}; + std::string defaultInStrings[mNumDefaultInputs] = { "Reset", "Freq", "Volume", "Smooth"}; std::string defaultOutStrings[mNumDefaultOutputs] = { "Out" }; virtual ~GraphSignalBase() = default; @@ -73,12 +73,6 @@ namespace l::nodegraph { float mVolumeTarget = 0.0f; float mSamplesUntilUpdate = 0.0f; float mUpdateSamples = 256.0f; - - // high pass - float mHPCutoff = 0.5f; - float mHPResonance = 0.0001f; - float mHPState0 = 0.0f; - float mHPState1 = 0.0f; }; /*********************************************************************/ @@ -104,6 +98,139 @@ namespace l::nodegraph { float mPhaseFmod = 0.0f; }; + /*********************************************************************/ + struct LowpassType { + double x1 = 0.0; + double y1 = 0.0; + double a = 0.0; + double b = 0.0; + }; + + struct WaveformBlit { + double phase = 0.0; /* phase accumulator */ + double aNQ = 0.0; /* attenuation at nyquist */ + double curcps = 0.0; /* current frequency, updated once per cycle */ + double curper = 0.0; /* current period, updated once per cycle */ + LowpassType leaky; /* leaky integrator */ + double N = 0.0; /* # partials */ + double a = 0.0; /* dsf parameter which controls roll-off */ + double aN = 0.0; /* former to the N */ + }; + + class GraphSignalSaw2 : public GraphSignalBase { + public: + GraphSignalSaw2(NodeGraphBase* node) : + GraphSignalBase(node, "Saw 2", 2) + {} + std::string extraString[2] = { "Attenuation", "Cutoff" }; + + virtual ~GraphSignalSaw2() = default; + virtual std::string_view GetInputNameExtra(int8_t extraInputChannel) override { + if (extraInputChannel < 2) return extraString[static_cast(extraInputChannel)]; + return ""; + } + void ResetSignal() override; + void UpdateSignal(std::vector& inputs, std::vector& outputs) override; + float GenerateSignal(float deltaTime, float freq, float deltaPhase) override; + + void InitSaw(WaveformBlit* b, double aNQ, double cutoff) + { + b->phase = 0.0; + b->aNQ = aNQ; + b->curcps = 0.0; + b->curper = 0.0; + InitLowpass(&b->leaky, cutoff); + } + + void UpdateSaw(WaveformBlit* b, double aNQ, double cutoff) { + b->aNQ = aNQ; + UpdateLowpass(&b->leaky, cutoff + 0.00001); + } + + /* Returns a sawtooth computed from a leaky integration + * of a DSF bandlimited impulse train. + * + * cps (cycles per sample) is the fundamental + * frequency: 0 -> 0.5 == 0 -> nyquist + */ + + double ProcessSaw(WaveformBlit* b, double cps) { + double P2, beta, Nbeta, cosbeta, n, d, blit, saw; + + if (b->phase >= 1.0 || b->curcps == 0.0) + { + /* New cycle, update frequency and everything + * that depends on it + */ + + if (b->phase >= 1.0) + b->phase -= 1.0; + double cpsClamped = l::math::functions::clamp(cps, 2.0 / 44100, 0.5); + b->curcps = cpsClamped; /* this cycle\'s frequency */ + b->curper = 1.0 / cpsClamped; /* this cycle\'s period */ + + P2 = b->curper / 2.0; + b->N = 1.0 + l::math::functions::floor(P2); /* # of partials incl. dc */ + + /* find the roll-off parameter which gives + * the desired attenuation at nyquist + */ + + b->a = l::math::functions::pow(b->aNQ, 1.0 / P2); + b->aN = l::math::functions::pow(b->a, b->N); + } + + beta = 2.0 * l::math::constants::PI * b->phase; + + Nbeta = b->N * beta; + cosbeta = l::math::functions::cos(beta); + + /* The dsf blit is scaled by 1 / period to give approximately the same + * peak-to-peak over a wide range of frequencies. + */ + + n = 1.0 - + b->aN * l::math::functions::cos(Nbeta) - + b->a * (cosbeta - b->aN * l::math::functions::cos(Nbeta - beta)); + d = b->curper * (1.0 + b->a * (-2.0 * cosbeta + b->a)); + + b->phase += b->curcps; /* update phase */ + + blit = n / d - b->curcps; /* This division can only fail if |a| == 1.0 + * Subtracting the fundamental frq rids of DC + */ + + saw = ProcessLowpass(&b->leaky, blit); /* shape blit spectrum into a saw */ + + return 4.0 * saw; + } + + void InitLowpass(LowpassType* lp, double cutoff) { + lp->x1 = lp->y1 = 0.0; + UpdateLowpass(lp, cutoff); + } + + void UpdateLowpass(LowpassType* lp, double cutoff) { + double Omega = l::math::functions::atan(l::math::constants::PI * cutoff); + lp->a = -(1.0 - Omega) / (1.0 + Omega); + lp->b = (1.0 - lp->b) / 2.0; + } + + double ProcessLowpass(LowpassType* lp, double x) { + double y; + y = lp->b * (x + lp->x1) - lp->a * lp->y1; + lp->x1 = x; + lp->y1 = y; + return y; + } + + protected: + double mAttenuation = 0.0f; + double mCutoff = 0.0f; + + WaveformBlit mSaw; + }; + /*********************************************************************/ class GraphSignalSine : public NodeGraphOp { public: diff --git a/packages/nodegraph/source/common/NodeGraphSchema.cpp b/packages/nodegraph/source/common/NodeGraphSchema.cpp index 527bf93a..282b1245 100644 --- a/packages/nodegraph/source/common/NodeGraphSchema.cpp +++ b/packages/nodegraph/source/common/NodeGraphSchema.cpp @@ -79,6 +79,9 @@ namespace l::nodegraph { case 151: node = mMainNodeGraph.NewNode(OutputType::Default); break; + case 152: + node = mMainNodeGraph.NewNode(OutputType::Default); + break; case 200: node = mMainNodeGraph.NewNode(OutputType::ExternalOutput); break; @@ -154,6 +157,9 @@ namespace l::nodegraph { case 355: node = mMainNodeGraph.NewNode(OutputType::Default); break; + case 356: + node = mMainNodeGraph.NewNode(OutputType::Default); + break; default: diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpFilter.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpFilter.cpp index 0bc5e164..e0648649 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpFilter.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpFilter.cpp @@ -59,4 +59,52 @@ namespace l::nodegraph { outputs.at(0).mOutput = inputValue - mState1; } + + /*********************************************************************/ + void GraphFilterChamberlain2pole::Reset() { + for (int32_t i = 0; i < 4; i++) { + mState.at(i) = 0.0f; + } + + mSamplesUntilUpdate = 0.0f; + mUpdateSamples = 4.0f; + + mNode->SetInput(1, 0.99f); + mNode->SetInput(2, 0.01f); + mNode->SetInput(3, 0.0f); + mNode->SetInputBound(1, InputBound::INPUT_0_TO_1); + mNode->SetInputBound(2, InputBound::INPUT_0_TO_1); + mNode->SetInputBound(3, InputBound::INPUT_0_TO_1); + } + + void GraphFilterChamberlain2pole::Process(int32_t numSamples, std::vector& inputs, std::vector& outputs) { + auto input = &inputs.at(0).Get(numSamples); + + auto mode = static_cast(3.0f * inputs.at(3).Get() + 0.5f); + auto output0 = &outputs.at(0).GetOutput(numSamples); + + mSamplesUntilUpdate = l::audio::BatchUpdate(mUpdateSamples, mSamplesUntilUpdate, 0, numSamples, + [&]() { + mCutoff = inputs.at(1).Get(); + mResonance = 1.0f - inputs.at(2).Get(); + + mFreq = l::math::functions::sin(l::math::constants::PI_f * mCutoff * mCutoff / 2.0f); + mScale = l::math::functions::sqrt(mResonance); + }, + [&](int32_t start, int32_t end, bool) { + for (int32_t i = start; i < end; i++) { + float inputValue = *input++; + float inputValueInbetween = (mInputValuePrev + inputValue) * 0.5f; + for (int32_t oversample = 0; oversample < 2; oversample++) { + mState.at(0) = mState.at(0) + mFreq * mState.at(2); + mState.at(1) = mScale * (oversample == 0 ? inputValueInbetween : inputValue) - mState.at(0) - mResonance * mState.at(2); + mState.at(2) = mFreq * mState.at(1) + mState.at(2); + mState.at(3) = mState.at(1) + mState.at(0); + } + *output0++ = mState.at(mode); + mInputValuePrev = inputValue; + } + } + ); + } } diff --git a/packages/nodegraph/source/common/operations/NodeGraphOpSignal.cpp b/packages/nodegraph/source/common/operations/NodeGraphOpSignal.cpp index 3174f7db..9aead8a7 100644 --- a/packages/nodegraph/source/common/operations/NodeGraphOpSignal.cpp +++ b/packages/nodegraph/source/common/operations/NodeGraphOpSignal.cpp @@ -20,27 +20,16 @@ namespace l::nodegraph { mDeltaTime = 0.0f; mVolume = 0.0f; mSamplesUntilUpdate = 0.0f; - mUpdateSamples = 16.0f; - mHPCutoff = 0.5f; - mHPResonance = 0.0001f; - mHPState0 = 0.0f; - mHPState1 = 0.0f; - mHPCutoff = 0.0f; + mUpdateSamples = 4.0f; mNode->SetInput(0, 0.0f); - mNode->SetInput(1, 0.0f); + mNode->SetInput(1, 441.0047f); mNode->SetInput(2, 0.5f); - mNode->SetInput(3, 1.0f); - mNode->SetInput(4, 0.0f); - mNode->SetInput(5, 0.0f); - mNode->SetInput(6, 0.5f); + mNode->SetInput(3, 0.5f); mNode->SetInputBound(0, InputBound::INPUT_0_TO_1); mNode->SetInputBound(1, InputBound::INPUT_CUSTOM, 0.0f, l::math::constants::FLTMAX); mNode->SetInputBound(2, InputBound::INPUT_0_100); mNode->SetInputBound(3, InputBound::INPUT_0_TO_1); - mNode->SetInputBound(4, InputBound::INPUT_0_TO_1); - mNode->SetInputBound(5, InputBound::INPUT_0_TO_1); - mNode->SetInputBound(6, InputBound::INPUT_0_TO_1); ResetSignal(); } @@ -50,22 +39,26 @@ namespace l::nodegraph { mSamplesUntilUpdate = l::audio::BatchUpdate(mUpdateSamples, mSamplesUntilUpdate, 0, numSamples, [&]() { - mDeltaTime = 1.0f / 44100.0f; mReset = inputs.at(0).Get(); mFreq = inputs.at(1).Get(); - mVolumeTarget = inputs.at(2).Get(); - mSmooth = 0.5f * inputs.at(3).Get(); - float phaseExpansion = inputs.at(6).Get(); - - mDeltaPhase = mDeltaTime * mFreq; - //float deltaPhaseExpanded = l::math::functions::pow(mDeltaPhase, phaseExpansion); - float deltaPhaseExpanded = l::math::functions::pow(mDeltaPhase, 0.4f - phaseExpansion * mDeltaPhase * 6.0f); - mHPCutoff = deltaPhaseExpanded + (1.0f - deltaPhaseExpanded) * inputs.at(4).Get(); - mHPResonance = 1.0f - inputs.at(5).Get(); - if (mFreq == 0.0f || mReset > 0.5f) { + if (mReset > 0.5f) { mVolumeTarget = 0.0f; } + else if (mFreq <= 0.0f) { + mFreq = 0.0f; + mVolumeTarget *= mVolumeTarget; + if (mVolumeTarget < 0.00001f) { + mVolumeTarget = 0.0f; + } + } + else { + mVolumeTarget = inputs.at(2).Get(); + } + + mSmooth = inputs.at(3).Get(); + mDeltaTime = 1.0f / 44100.0f; + mDeltaPhase = mDeltaTime * mFreq; UpdateSignal(inputs, outputs); @@ -73,22 +66,8 @@ namespace l::nodegraph { [&](int32_t start, int32_t end, bool) { for (int32_t i = start; i < end; i++) { float signalTarget = GenerateSignal(mDeltaTime, mFreq, mDeltaPhase); - - // highpass filter - { - //float deltaPhase2 = l::math::functions::pow(mDeltaPhase, 0.125f); - float cutoff = mHPCutoff; - cutoff *= cutoff; - float rc = 1.0f - mHPResonance * cutoff; - float v01 = mHPState0 - mHPState1; - mHPState0 += cutoff * (signalTarget - mHPState0 + rc * v01); - mHPState1 += cutoff * v01; - - signalTarget = signalTarget - mHPState1; - } - mSignal += mSmooth * (signalTarget - mSignal); - mVolume += (1.0f / 256.0f) * (mVolumeTarget - mVolume); + mVolume += 0.5f * (mVolumeTarget - mVolume); *output0++ = mVolume * mSignal; } } @@ -102,7 +81,7 @@ namespace l::nodegraph { mNode->SetInput(mNumDefaultInputs + 1, 0.0f); mNode->SetInputBound(mNumDefaultInputs + 0, InputBound::INPUT_0_TO_1); mNode->SetInputBound(mNumDefaultInputs + 1, InputBound::INPUT_0_TO_1); - mUpdateSamples = 16.0f; + mUpdateSamples = 4.0f; } void GraphSignalSine2::UpdateSignal(std::vector& inputs, std::vector&) { @@ -114,19 +93,41 @@ namespace l::nodegraph { float GraphSignalSine2::GenerateSignal(float, float, float deltaPhase) { mPhaseFmod += deltaPhase; mPhaseFmod = l::math::functions::mod(mPhaseFmod, 1.0f); - float modulation = l::math::functions::cos(l::math::constants::PI_f * mPhaseFmod * 2.0f); + float modulation = 0.5f * (mFmod + mFmod * l::math::functions::cos(l::math::constants::PI_f * mPhaseFmod * 2.0f)); + //float modulation = (0.5f - deltaPhase) + (mFmod) * 0.5f * l::math::functions::cos(l::math::constants::PI_f * mPhaseFmod * 2.0f); + mPhase = mPhaseFmod; - mPhase += mFmod * modulation; + mPhase += modulation; mPhase -= l::math::functions::floor(mPhase); - float phaseMod = mPhaseFmod + mPmod * modulation; + float phaseMod = mPhaseFmod + mPmod * modulation * 4.0f; phaseMod -= l::math::functions::floor(phaseMod); - //mWave = mSmooth * (mPhase + phaseMod - mWave); return 0.5f * (l::math::functions::sin(l::math::constants::PI_f * mPhase * 2.0f) + l::math::functions::sin(l::math::constants::PI_f * phaseMod * 2.0f)); } + /*********************************************************************/ + + void GraphSignalSaw2::ResetSignal() { + mNode->SetInput(mNumDefaultInputs + 0, 0.0f); + mNode->SetInput(mNumDefaultInputs + 1, 0.0f); + mNode->SetInputBound(mNumDefaultInputs + 0, InputBound::INPUT_0_TO_1); + mNode->SetInputBound(mNumDefaultInputs + 1, InputBound::INPUT_0_TO_1); + mUpdateSamples = 4.0f; + } + + void GraphSignalSaw2::UpdateSignal(std::vector& inputs, std::vector&) { + mAttenuation = inputs.at(mNumDefaultInputs + 0).Get(); + mCutoff = inputs.at(mNumDefaultInputs + 1).Get(); + + UpdateSaw(&mSaw, 0.00001f + 0.99999f * mAttenuation * mAttenuation * mAttenuation, mCutoff * mCutoff); + } + + float GraphSignalSaw2::GenerateSignal(float, float, float deltaPhase) { + return static_cast(ProcessSaw(&mSaw, static_cast(deltaPhase))); + } + /*********************************************************************/ void GraphSignalSine::Reset() { // { "Freq Hz", "Freq Mod", "Phase Mod", "Reset"}; diff --git a/packages/rendering/source/common/ui/UIContainer.cpp b/packages/rendering/source/common/ui/UIContainer.cpp index 78a860cb..c86698b1 100644 --- a/packages/rendering/source/common/ui/UIContainer.cpp +++ b/packages/rendering/source/common/ui/UIContainer.cpp @@ -259,9 +259,11 @@ namespace l::ui { break; case UISplitMode::AppendH: current.mPosition.x += content->GetSize().x * mLayoutArea.mScale; + current.mSize.x -= content->GetSize().x * mLayoutArea.mScale; break; case UISplitMode::AppendV: current.mPosition.y += content->GetSize().y * mLayoutArea.mScale; + current.mSize.y -= content->GetSize().y * mLayoutArea.mScale; break; case UISplitMode::EqualResizeH: break; diff --git a/packages/rendering/source/common/ui/UICreator.cpp b/packages/rendering/source/common/ui/UICreator.cpp index f8050eb1..4ab77f4a 100644 --- a/packages/rendering/source/common/ui/UICreator.cpp +++ b/packages/rendering/source/common/ui/UICreator.cpp @@ -90,12 +90,13 @@ namespace l::ui { } if (node.GetOutputType() == l::nodegraph::OutputType::ExternalVisualOutput) { - auto row = CreateContainer(uiStorage, l::ui::UIContainer_DrawFlag, l::ui::UIRenderType::Rect, l::ui::UIAlignH::Left, l::ui::UIAlignV::Top, l::ui::UILayoutH::Parent, l::ui::UILayoutV::Parent); + auto row = CreateContainer(uiStorage, l::ui::UIContainer_DrawFlag, l::ui::UIRenderType::Rect, l::ui::UIAlignH::Left, l::ui::UIAlignV::Bottom, l::ui::UILayoutH::Parent, l::ui::UILayoutV::Parent); + row->SetPosition(ImVec2(0.0f, 0.0f)); row->GetContainerArea().mMargin = ioSize; node4->Add(row); float estimatedWidth = 0.0f; - auto plot = CreateContainer(uiStorage, l::ui::UIContainer_DrawFlag, l::ui::UIRenderType::NodeOutputGraph, l::ui::UIAlignH::Center, l::ui::UIAlignV::Middle, l::ui::UILayoutH::Parent, l::ui::UILayoutV::Parent); + auto plot = CreateContainer(uiStorage, l::ui::UIContainer_DrawFlag, l::ui::UIRenderType::NodeOutputGraph, l::ui::UIAlignH::Center, l::ui::UIAlignV::Bottom, l::ui::UILayoutH::Parent, l::ui::UILayoutV::Parent); plot->SetPosition(ImVec2(estimatedWidth, 0.0f)); plot->SetSize(ImVec2(100, 100)); plot->SetNodeId(node.GetId()); diff --git a/packages/rendering/source/common/ui/UIVisitors.cpp b/packages/rendering/source/common/ui/UIVisitors.cpp index 4e9d9546..992da034 100644 --- a/packages/rendering/source/common/ui/UIVisitors.cpp +++ b/packages/rendering/source/common/ui/UIVisitors.cpp @@ -379,7 +379,7 @@ namespace l::ui { float xpart1 = i / static_cast(nodeValueCount); float xpart2 = (i+1) / static_cast(nodeValueCount); ImVec2 graphP1 = ImVec2(startPos.x + size.x * xpart1, startPos.y + 0.5f * nodeValues[i] * size.y); - ImVec2 graphP2 = ImVec2(startPos.x + size.x * xpart2, startPos.y + 0.5f * nodeValues[i] * size.y); + ImVec2 graphP2 = ImVec2(startPos.x + size.x * xpart2, startPos.y + 0.5f * nodeValues[i+1] * size.y); mDrawList->AddLine(graphP1, graphP2, color, 2.0f * container.GetScale()); } }