


HIGH LEVEL CONTROLS


namespace gui
{
	struct HighLevelKnobsGroup :
		public Comp
	{
		HighLevelKnobsGroup(Utils& u) :
			Comp(u, "", CursorType::Default),
			knobs
			{
#if PPDHasGainIn
				Knob(u, "> Gain", PID::GainIn),
#endif
				Knob(u, "Mix", PID::Mix),
#if PPDHasLookahead
				Knob(u, "Lookahead", PID::Lookahead),
#endif
				Knob(u, "Gain >", PID::Gain)
			},
			meters
			{
#if PPDHasGainIn
				KnobMeter(knobs.front(), u.getMeter(audio::Meters::Type::In)),
#endif
				KnobMeter(knobs.back(), u.getMeter(audio::Meters::Type::Out))
			}
		{
			for (auto& k : knobs)
				addAndMakeVisible(k);
		}
	protected:
		std::array<Knob, param::NumHighLevelKnobs> knobs;
		std::array<KnobMeter, param::NumHighLevelGainKnobs> meters;

		void paint(Graphics&) override {}

		void resized() override
		{
			auto x = 0.f;
			auto y = 0.f;
			auto w = static_cast<float>(getWidth()) / static_cast<float>(knobs.size());
			auto h = static_cast<float>(getHeight());

			for (auto& knob : knobs)
			{
				knob.setBounds(maxQuadIn({ x,y,w,h }).toNearestInt());
				x += w;
			}
		}
	};

	struct HighLevelButtonsGroup :
		public Comp
	{
		static constexpr int NumButtons = param::NumHighLevelParams - param::NumHighLevelKnobs - 1;

		HighLevelButtonsGroup(Utils& u) :
			Comp(u, "", CursorType::Default),
			buttons
			{
#if PPDHasUnityGain
				Button(u, param::toTooltip(PID::UnityGain)),
#endif
#if PPDHasHQ
				Button(u, param::toTooltip(PID::HQ)),
#endif
				Button(u, param::toTooltip(PID::Polarity)),
				Button(u, param::toTooltip(PID::StereoConfig)),
				Button(u, param::toTooltip(PID::Bypass))
			}
		{
			auto idx = NumButtons - 1;
			makeParameterSwitchButton(buttons[idx], PID::Bypass, ButtonSymbol::Bypass);
			--idx;
			makeParameterSwitchButton(buttons[idx], PID::StereoConfig, ButtonSymbol::StereoConfig);
			--idx;
			makeParameterSwitchButton(buttons[idx], PID::Polarity, ButtonSymbol::Polarity);
#if PPDHasHQ
			--idx;
			makeParameterSwitchButton(buttons[idx], PID::HQ, "HQ");
#endif
#if PPDHasUnityGain
			--idx;
			makeParameterSwitchButton(buttons[idx], PID::UnityGain, ButtonSymbol::UnityGain);
#endif
			
			for (auto& b : buttons)
				addAndMakeVisible(b);
		}
	protected:
		std::array<Button, NumButtons> buttons;

		void paint(Graphics&) override {}

		void resized() override
		{
			auto x = 0.f;
			auto y = 0.f;
			auto w = static_cast<float>(getWidth()) / static_cast<float>(buttons.size());
			auto h = static_cast<float>(getHeight());

			for (auto& button : buttons)
			{
				button.setBounds(maxQuadIn({ x,y,w,h }).toNearestInt());
				x += w;
			}
		}
	};

	struct HighLevel :
		public Comp
	{
		HighLevel(Utils& u) :
			Comp(u, "", CursorType::Default),
			layout(*this),
			pluginTitle(u, static_cast<String>(JucePlugin_Name)),
			options(u, "you can find additional plugin options here."),
			knobsGroup(u),
			buttonsGroup(u),
			patchXSelect(u, "Select Patch X to modify its parameter values."),
			patchYSelect(u, "Select Patch Y to modify its parameter values."),
			patchXSolo(u, param::toTooltip(PID::PatchXSolo)),
			patchYSolo(u, param::toTooltip(PID::PatchYSolo)),
			macro(u, "", "Interpolate between Patch X and Y.", PID::Macro)
		{
			setInterceptsMouseClicks(false, true);

			pluginTitle.outlineCID = ColourID::Transp;

			makeTextButton(options, "opt", true);

			patchXSelect.setHitBoxType(HitBox::Type::ConvexClockwise);
			patchYSelect.setHitBoxType(HitBox::Type::ConvexClockwise);
			makeTextButton(patchXSelect, "X", true, true);
			makeTextButton(patchYSelect, "Y", true, true);

			makeParameterSwitchButton(patchXSolo, PID::PatchXSolo, "S");
			makeParameterSwitchButton(patchYSolo, PID::PatchYSolo, "S");

			addAndMakeVisible(pluginTitle);
			addAndMakeVisible(options);
			addAndMakeVisible(knobsGroup);
			addAndMakeVisible(buttonsGroup);
			addAndMakeVisible(patchXSelect);
			addAndMakeVisible(patchYSelect);
			addAndMakeVisible(patchXSolo);
			addAndMakeVisible(patchYSolo);
			addAndMakeVisible(macro);

			layout.init(
				{ 8, 12, 10, 50, 10, 12, 8 },
				{ 5, 10, 20, 8, 8, 70 }
			);
		}

	protected:
		Layout layout;
		Label pluginTitle;
		Button options;
		HighLevelKnobsGroup knobsGroup;
		HighLevelButtonsGroup buttonsGroup;
		Button patchXSelect, patchYSelect, patchXSolo, patchYSolo;
		Dial macro;

		void paint(Graphics& g) override
		{
			const auto thicc = utils.thicc();

			//Comp::paint(g);
			//layout.paint(g);

			g.setColour(Colours::c(ColourID::Txt));
			Path path;
			make(path, layout, { {0,2}, {2,2}, {3,4}, {4,4}, {5,2}, {7,2} });
			g.strokePath(path, juce::PathStrokeType(thicc));
		}

		void resized() override
		{
			layout.resized();

			const auto thicc = utils.thicc();
			const auto thicc3 = thicc * 3.f;

			pluginTitle.setBounds(layout(3, 0, 1, 2, false).reduced(thicc3).toNearestInt());
			options.setBounds(layout(0, 0, 1, 2, true).reduced(thicc).toNearestInt());
			layout.place(knobsGroup, 3, 2, 1, 1, false);
			layout.place(buttonsGroup, 3, 3, 1, 1, false);
			layout.place(macro, 3, 4, 1, 1, false);

			layout.place(patchXSelect, 0, 2, 3, 2, false);
			layout.place(patchYSelect, 4, 2, 3, 2, false);

			{
				const auto off = PointF(0.f, 0.f) - layout(4, 2);
				{
					Path path;
					make(path, layout, { { 0,2 }, { 2,2 }, { 3,4 }, { 0,4 } });
					path.applyTransform(Affine::translation(0.f, off.y));
					path.closeSubPath();

					patchXSelect.updateHitBox(path);
				}
				{
					Path path;
					make(path, layout, { { 4,4 }, { 5,2 }, { 7,2 }, { 7,4 } });
					path.applyTransform(Affine::translation(off));
					path.closeSubPath();

					patchYSelect.updateHitBox(path);
				}
			}

			layout.place(patchXSolo, 2, 4, 1, 1, true);
			layout.place(patchYSolo, 4, 4, 1, 1, true);
		}
	};
}











HITBOX


struct HitBox
	{
		enum class Type { Rect, ConvexClockwise, NumTypes };

		HitBox(const Component& _comp) :
			comp(_comp),
			points(),
			area(),
			type(Type::Rect)
		{}

		void setType(Type t) noexcept { type = t; }

		void update(const Path& _area)
		{
			const auto w = comp.getWidth();
			const auto h = comp.getHeight();

			points.resize(w);
			for (auto& pt : points)
				pt.resize(h, true);

			if (type == Type::ConvexClockwise) // right hand turn
			{
				area = _area;

				for (auto x = 0; x < w; ++x)
				{
					for (auto y = 0; y < h; ++y)
					{
						bool inShape = true;

						juce::PathFlatteningIterator it(area);
						while (it.next())
						{
							Point a(it.x1, it.y1);
							Point b(it.x2, it.y2);
							Point c(x, y);

							const auto cp = ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x));

							if (cp == 0.f) // colinear
							{
								inShape = true;
								break;
							}
							else if (cp < 0.f) // left-hand turn
							{
								inShape = false;
								break;
							}
						}

						points[x][y] = inShape;
					}
				}
			}
		}

		bool operator()(int x, int y) noexcept
		{
			if (type == Type::Rect)
				return true;
			return points[x][y];
		}

		const Path& getArea() noexcept
		{
			if (type == Type::Rect)
			{
				area.clear();
				area.addRectangle(comp.getLocalBounds().toFloat());
			}
			return area;
		}

	protected:
		const Component& comp;
		std::vector<std::vector<bool>> points;
		Path area;
		Type type;
	};








// LAYOUT OF HIGHLEVEL COMPONENT:

struct HighLevel :
		public Comp
	{
		HighLevel(Utils& u, LowLevel* _lowLevel) :
			Comp(u, "", CursorType::Default),
			pluginTitle(u, JucePlugin_Name),
			macro(u, "Macro", PID::Macro, false),
#if PPDHasGainIn
			gainIn(u, "Gain >", PID::GainIn, false),
			meterIn(gainIn, u.getMeter(0)),
#endif
			gainOut(u, "< Gain", PID::Gain, false),
			meterOut(gainOut, u.getMeter(PPDHasGainIn ? 1 : 0)),
			mix(u, "Mix", PID::Mix, false),
#if PPDHasUnityGain
			unityGain(u, param::toTooltip(PID::UnityGain)),
#endif
#if PPDHasHQ
			hq(u, param::toTooltip(PID::HQ)),
#endif
			stereoConfig(u, param::toTooltip(PID::StereoConfig)),
			power(u, param::toTooltip(PID::Power)),
			polarity(u, param::toTooltip(PID::Polarity)),
			patchSelect{
				Button(u, param::toTooltip(PID::PatchSelect)),
				Button(u, param::toTooltip(PID::PatchSelect))
			},
			patchMode(u, param::toTooltip(PID::PatchMode)),

			lowLevel(_lowLevel),

			menu(nullptr),
			menuButton(u, "Click here to open or close the panel with the advanced settings.")
		{
			layout.init(
				{ 5, 30, 30, 30, 5, 5 },
				{ 5, 15, 30, 30, 40, 15, 5, 15, 15, 5 }
			);

			pluginTitle.font = getFontNEL();

			addAndMakeVisible(pluginTitle);
			addAndMakeVisible(macro);
#if PPDHasGainIn
			addAndMakeVisible(gainIn);
#endif
			addAndMakeVisible(gainOut);
			addAndMakeVisible(mix);
#if PPDHasUnityGain
			makeParameterSwitchButton(unityGain, PID::UnityGain, ButtonSymbol::UnityGain);
			addAndMakeVisible(unityGain);
#endif
#if PPDHasHQ
			makeParameterSwitchButton(hq, PID::HQ, "HQ");
			addAndMakeVisible(hq);
#endif
			makeParameterSwitchButton(stereoConfig, PID::StereoConfig, ButtonSymbol::StereoConfig);
			addAndMakeVisible(stereoConfig);

			makeParameterSwitchButton(power, PID::Power, ButtonSymbol::Power);
			addAndMakeVisible(power);

			makeParameterSwitchButton(polarity, PID::Polarity, ButtonSymbol::Polarity);
			addAndMakeVisible(polarity);

			makeParameterButtonsGroup(patchSelect, PID::PatchSelect, "AB", true);
			for(auto i = 0; i < 2; ++i)
			{
				auto& ab = patchSelect[i];
				auto& label = ab.getLabel();
				label.textCID = ColourID::Mod;
				addAndMakeVisible(ab);
			}

			makeParameterSwitchButton(patchMode, PID::PatchMode, ButtonSymbol::PatchMode);
			addAndMakeVisible(patchMode);

			makeSymbolButton(menuButton, ButtonSymbol::Settings);
			menuButton.toggleState = 0;
			menuButton.onClick.push_back([this]()
			{
				auto& btn = menuButton;

				auto& ts = btn.toggleState;
				ts = ts == 0 ? 1 : 0;
				repaintWithChildren(&btn);

				if (ts == 1)
				{
					auto& pluginTop = utils.pluginTop;

					const auto xml = loadXML(BinaryData::menu_xml, BinaryData::menu_xmlSize);
					if (xml == nullptr)
						return;
					const auto vt = ValueTree::fromXml(*xml);
					if (!vt.isValid())
						return;

					menu.reset(new Menu(utils, vt));
					pluginTop.addAndMakeVisible(*menu);

					const auto bounds1 = lowLevel->getBounds().toFloat();
					const auto bounds0 = bounds1.withLeft(static_cast<float>(pluginTop.getRight()));

					menu->defineBounds(bounds0, bounds1);
					menu->initWidget(.1f, false);
				}
				else
				{
					menu.reset(nullptr);
				}
			});
			addAndMakeVisible(menuButton);

			setInterceptsMouseClicks(false, true);
		}

		void paint(Graphics& g) override
		{
			//Comp::paint(g);
			//layout.paint(g);

			g.setColour(Colours::c(ColourID::Hover));
			layout.label(g, "delta", 3.f, 1.25f, .5f, .75f, true);
			layout.label(g, "<", 1.25f, 5, .25f, 1, false);
			layout.label(g, "preset browser", 1.5f, 5, 1.5f, 1, false);
			layout.label(g, ">", 3.f, 5, .25f, 1, false);
			layout.label(g, "save", 3.25f, 5, .5f, 1, false);
			layout.label(g, "<\n<\n<", 5, 0, 1, 10, false);
			
			const auto thicc = utils.thicc();
			auto thiccI = static_cast<int>(thicc) / 2;
			if (thiccI == 0)
				thiccI = 1;
			g.setColour(Colours::c(ColourID::Txt));
			{
				const auto y = static_cast<int>(layout.getY(4.5f));
				const auto left = layout.getX(1.5f);
				const auto right = layout.getX(2.125f);
				for (auto i = -thiccI; i < thiccI; ++i)
					g.drawHorizontalLine(y + i, left, right);
			}
#if PPDHasUnityGain
			{
				const auto y = static_cast<int>(layout.getY(3.5f));
				{
					const auto left = layout.getX(2.f);
					const auto right = layout.getX(2.125f);
					for (auto i = -thiccI; i < thiccI; ++i)
						g.drawHorizontalLine(y + i, left, right);
				}
				{
					const auto right = layout.getX(3.f);
					const auto left = layout.getX(2.875f);
					for (auto i = -thiccI; i < thiccI; ++i)
						g.drawHorizontalLine(y + i, left, right);
				}
			}
#endif
		}

		void resized() override
		{
			layout.resized();

			layout.place(pluginTitle, 1.25f, 8, 2.5f, 1, false);

			layout.place(macro, 2, 4, 1, 1, true);
#if PPDHasGainIn
			layout.place(gainIn, 1, 3, 1, 1, true);
#endif
			layout.place(gainOut, 3, 3, 1, 1, true);
			layout.place(mix, 2, 2, 1, 1, true);
#if PPDHasUnityGain
			layout.place(unityGain, 2.25f, 3.25f, .5f, .5f, true);
#endif
#if PPDHasHQ
			layout.place(hq, 2.f, 7, 1.f, 1, true);
#endif
			layout.place(stereoConfig, 3.f, 7, 1.f, 1, true);
			layout.place(power, 2.25f, 1.f, .5f, .75f, true);
			layout.place(polarity, 1.5f, 1.25f, .5f, .75f, true);
			layout.place(patchSelect[0], 1.5f + .125f, 4.5f, .5f, .5f, true);
			layout.place(patchSelect[1], 2.75f + .125f, 4.5f, .5f, .5f, true);
			layout.place(patchMode, 1.125f, 4.25f, .5f, .5f, true);

			layout.place(menuButton, 1.f, 7, 1.f, 1, true);

			if (menu != nullptr)
			{
				menu->defineBounds(menu->getBounds().toFloat(), lowLevel->getBounds().toFloat());
				menu->initWidget(.3f, false);
			}
		}

	protected:
		Label pluginTitle;
		Knob macro;
#if PPDHasGainIn
		Knob gainIn;
		KnobMeter meterIn;
#endif
		Knob gainOut;
		KnobMeter meterOut;
		Knob mix;
#if PPDHasUnityGain
		Button unityGain;
#endif
#if PPDHasHQ
		Button hq;
#endif
		Button stereoConfig;
		Button power;
		Button polarity;
		std::array<Button, 2> patchSelect;
		Button patchMode;

		LowLevel* lowLevel;
		std::unique_ptr<Menu> menu;
		Button menuButton;
	};






;





BESTE RESONATOR BUG:

namespace audio
{
    

    class AbsorbProcessor
    {
        struct NoteHandler
        {
            NoteHandler() :
                readHead(0.f),
                noteOnBuffer(),
                
                Fs(1.f),
                curNote(-1.f),
                curFreqSamples(0.f)
            {}

            void prepare(float sampleRate, int blockSize)
            {
                Fs = sampleRate;
                readHead.prepare(Fs, blockSize, 10.f);
                for (auto& r : readHead.buf)
                    r = 0.f;

                noteOnBuffer.resize(blockSize);
            }

            void operator()(float* noteBuf, const int* wHead, float resoSizeF, int numSamples) noexcept
            {
                auto rHeadBuf = readHead.buf.data();

                for (auto s = 0; s < numSamples; ++s)
                {
                    auto nextNote = noteBuf[s];
                    if (nextNote == 0.f)
                        noteOnBuffer[s] = 0.f;
                    else if (curNote != nextNote)
                    {
                        curNote = nextNote;
                        auto freqHz = noteInFreqHz(curNote, 69.f, 12.f, 440.f);
                        while (freqHz < 20.f)
                            freqHz *= 2.f;

                        curFreqSamples = freqHzInSamples(freqHz, Fs);

                        const auto rHead = readHead(curFreqSamples);

                        const auto w = static_cast<float>(wHead[s]);
                        auto r = w - rHead;
                        if (r < 0.f)
                            r += resoSizeF;

                        rHeadBuf[s] = r;
                    }
                    else
                    {
                        curFreqSamples = freqHzInSamples(curFreqSamples, Fs);

                        const auto rHead = readHead(curFreqSamples);

                        const auto w = static_cast<float>(wHead[s]);
                        auto r = w - rHead;
                        if (r < 0.f)
                            r += resoSizeF;

                        rHeadBuf[s] = r;
                    }

                    noteOnBuffer[s] = 1.f;
                }
            }

            PRM readHead;
            std::vector<float> noteOnBuffer;
        protected:
            float Fs, curNote, curFreqSamples;
        };

        struct Textures
        {
            Textures() :
                rm(0.f),
                am(0.f),
                shapr(0.f),
                crushr(0.f),
                foldr(0.f)
            {}

            void prepare(float sampleRate, int blockSize)
            {
                rm.prepare(sampleRate, blockSize, 10.f);
                am.prepare(sampleRate, blockSize, 10.f);
                shapr.prepare(sampleRate, blockSize, 10.f);
                crushr.prepare(sampleRate, blockSize, 10.f);
                foldr.prepare(sampleRate, blockSize, 10.f);
            }

            void operator()(float** samples, int numChannels, int numSamples,
                float** samplesSC, int numChannelsSC,
                float _rm, float _am, float _shapr, float _crushr, float _foldr) noexcept
            {
                auto rmBuf = rm(Decibels::decibelsToGain(_rm, -20.f), numSamples);
                auto amBuf = am(Decibels::decibelsToGain(_am, -20.f), numSamples);
                auto shaprBuf = shapr(Decibels::decibelsToGain(_shapr, -40.f), numSamples);
                auto crushrBuf = crushr(Decibels::decibelsToGain(_crushr, -40.f), numSamples);
                auto foldrBuf = foldr(Decibels::decibelsToGain(_foldr, -40.f), numSamples);

                for (auto ch = 0; ch < numChannels; ++ch)
                {
                    const auto chSC = ch % numChannelsSC;
                    const auto smplsSC = samplesSC[chSC];

                    auto smpls = samples[ch];

                    float A, B, C, D, E;

                    for (auto s = 0; s < numSamples; ++s)
                    {
                        const auto main = smpls[s];
                        const auto sc = smplsSC[s];

                        A = main * sc * rmBuf[s];

                        const auto scsc = sc * sc;
                        const auto scscSqrt = std::sqrt(scsc);

                        B = main * scscSqrt * amBuf[s];

                        if (sc == 0.f)
                        {
                            C = D = E = 0.f;
                        }
                        else
                        {
                            const auto mainInvSc = main / sc;

                            C = std::tanh(mainInvSc) * sc * shaprBuf[s];
                            D = std::round(mainInvSc) * sc * crushrBuf[s];

                            E = std::fmod(main, sc) * sc * foldrBuf[s];
                        }

                        smpls[s] = A + B + C + D + E;
                    }
                }
            }

        protected:
            PRM rm, am, shapr, crushr, foldr;
        };

        struct Resonator
        {
            Resonator() :
                ringBuffer(),
                lowpass{ 0.f, 0.f },
                feedback(0.f),
                Fs(1.f)
            {}

            void prepare(float sampleRate, int blockSize, int size)
            {
                Fs = sampleRate;

                for (auto& r : ringBuffer)
                    r.resize(size);

                feedback.prepare(Fs, blockSize, 10.f);
            }

            void operator()(float** samples, int numChannels, int numSamples,
                const int* wHead, const float* rHeadBuf, const float* noteOnBuf, int size,
                float _feedback, float _lp) noexcept
            {
                const auto fbBuf = feedback(_feedback, numSamples);

                lowpass[0].makeFromDecayInHz(_lp, Fs);

                for (auto ch = 0; ch < numChannels; ++ch)
                {
                    auto smpls = samples[ch];
                    auto ring = ringBuffer[ch].data();

                    auto& lp = lowpass[ch];
                    lp.copyCutoffFrom(lowpass[0]);

                    for (auto s = 0; s < numSamples; ++s)
                    {
                        const auto w = wHead[s];
                        const auto r = rHeadBuf[s];
                        const auto fb = fbBuf[s] * noteOnBuf[s];

                        const auto sOut = std::tanh(lp(interpolate::lerp(ring, r, size)));
                        const auto sIn = smpls[s] + sOut * fb;

                        ring[w] = sIn;
                        smpls[s] = sOut;
                    }
                }
            }

        protected:
            std::array<std::vector<float>, 2> ringBuffer;
            std::array<Smooth, 2> lowpass;
            PRM feedback;
            float Fs;
        };

    public:
        AbsorbProcessor(MIDIManager& midiManager) :
            noteBuffer(),

            wHead(),
            noteHandler(),
            textures(),
            resonator(),

            resonatorSizeF(1.f),
            curNoteValue(0.f),
            resonatorSize(1)
        {
            midiManager.onNoteOn.push_back([&](const MidiMessage& msg, int s)
            {
                noteBuffer[s] = curNoteValue = static_cast<float>(msg.getNoteNumber());
            });

            midiManager.onNoteOff.push_back([&](const MidiMessage&, int s)
            {
                noteBuffer[s] = curNoteValue = 0.f;
            });

            midiManager.onNoEvt.push_back([&](int s)
            {
                noteBuffer[s] = curNoteValue;
            });
        }

        void prepare(float sampleRate, int blockSize)
        {
            resonatorSizeF = freqHzInSamples(20.f, sampleRate);
            resonatorSize = static_cast<int>(resonatorSizeF);

            noteBuffer.resize(blockSize);

            wHead.prepare(blockSize, resonatorSize);

            noteHandler.prepare(sampleRate, blockSize);
            textures.prepare(sampleRate, blockSize);
            resonator.prepare(sampleRate, blockSize, resonatorSize);
        }

        void operator()(float** samples, int numChannels, int numSamples,
            float** samplesSC, int numChannelsSC,
            float _rm, float _am, float _shapr, float _crushr, float _foldr,
            float _resoFB, float _resoLP) noexcept
        {
            wHead(numSamples);

            noteHandler(noteBuffer.data(), wHead.data(), resonatorSizeF, numSamples);

            textures(samples, numChannels, numSamples, samplesSC, numChannelsSC,
                _rm, _am, _shapr, _crushr, _foldr);

            resonator(samples, numChannels, numSamples,
                wHead.data(), noteHandler.readHead.buf.data(), noteHandler.noteOnBuffer.data(),
                resonatorSize, _resoFB, _resoLP);
        }

    protected:
        std::vector<float> noteBuffer;

        WHead wHead;
        NoteHandler noteHandler;
        Textures textures;
        Resonator resonator;

        float resonatorSizeF, curNoteValue;
        int resonatorSize;
    };

}





float Param::biased(float start, float end, float bias/*[0,1]*/, float x) const noexcept
	{
		const auto r = end - start;
		if (r == 0.f)
			return 0.f;
		const auto a2 = 2.f * bias;
		const auto aM = 1.f - bias;
		const auto aR = r * bias;
		return start + aR * x / (aM - x + a2 * x);
	}












