/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2022 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 7 End-User License
   Agreement and JUCE Privacy Policy.

   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce::dsp
{

/**
    A 6 stage phaser that modulates first order all-pass filters to create sweeping
    notches in the magnitude frequency response.

    This audio effect can be controlled with standard phaser parameters: the speed
    and depth of the LFO controlling the frequency response, a mix control, a
    feedback control, and the centre frequency of the modulation.

    @tags{DSP}
*/
template <typename SampleType>
class Phaser
{
public:
    //==============================================================================
    /** Constructor. */
    Phaser();

    //==============================================================================
    /** Sets the rate (in Hz) of the LFO modulating the phaser all-pass filters. This
        rate must be lower than 100 Hz.
    */
    void setRate (SampleType newRateHz);

    /** Sets the volume (between 0 and 1) of the LFO modulating the phaser all-pass
        filters.
    */
    void setDepth (SampleType newDepth);

    /** Sets the centre frequency (in Hz) of the phaser all-pass filters modulation.
    */
    void setCentreFrequency (SampleType newCentreHz);

    /** Sets the feedback volume (between -1 and 1) of the phaser. Negative can be
        used to get specific phaser sounds.
    */
    void setFeedback (SampleType newFeedback);

    /** Sets the amount of dry and wet signal in the output of the phaser (between 0
        for full dry and 1 for full wet).
    */
    void setMix (SampleType newMix);

    //==============================================================================
    /** Initialises the processor. */
    void prepare (const ProcessSpec& spec);

    /** Resets the internal state variables of the processor. */
    void reset();

    //==============================================================================
    /** Processes the input and output samples supplied in the processing context. */
    template <typename ProcessContext>
    void process (const ProcessContext& context) noexcept
    {
        const auto& inputBlock = context.getInputBlock();
        auto& outputBlock      = context.getOutputBlock();
        const auto numChannels = outputBlock.getNumChannels();
        const auto numSamples  = outputBlock.getNumSamples();

        jassert (inputBlock.getNumChannels() == numChannels);
        jassert (inputBlock.getNumChannels() == lastOutput.size());
        jassert (inputBlock.getNumSamples()  == numSamples);

        if (context.isBypassed)
        {
            outputBlock.copyFrom (inputBlock);
            return;
        }

        int numSamplesDown = 0;
        auto counter = updateCounter;

        for (size_t i = 0; i < numSamples; ++i)
        {
            if (counter == 0)
                numSamplesDown++;

            counter++;

            if (counter == maxUpdateCounter)
                counter = 0;
        }

        if (numSamplesDown > 0)
        {
            auto freqBlock = AudioBlock<SampleType> (bufferFrequency).getSubBlock (0, (size_t) numSamplesDown);
            auto contextFreq = ProcessContextReplacing<SampleType> (freqBlock);
            freqBlock.clear();

            osc.process (contextFreq);
            freqBlock.multiplyBy (oscVolume);
        }

        auto* freqSamples = bufferFrequency.getWritePointer (0);

        for (int i = 0; i < numSamplesDown; ++i)
        {
            auto lfo = jlimit (static_cast<SampleType> (0.0),
                               static_cast<SampleType> (1.0),
                               freqSamples[i] + normCentreFrequency);

            freqSamples[i] = mapToLog10 (lfo, static_cast<SampleType> (20.0),
                                         static_cast<SampleType> (jmin (20000.0, 0.49 * sampleRate)));
        }

        auto currentFrequency = filters[0]->getCutoffFrequency();
        dryWet.pushDrySamples (inputBlock);

        for (size_t channel = 0; channel < numChannels; ++channel)
        {
            counter = updateCounter;
            int k = 0;

            auto* inputSamples  = inputBlock .getChannelPointer (channel);
            auto* outputSamples = outputBlock.getChannelPointer (channel);

            for (size_t i = 0; i < numSamples; ++i)
            {
                auto input = inputSamples[i];
                auto output = input - lastOutput[channel];

                if (i == 0 && counter != 0)
                    for (int n = 0; n < numStages; ++n)
                        filters[n]->setCutoffFrequency (currentFrequency);

                if (counter == 0)
                {
                    for (int n = 0; n < numStages; ++n)
                        filters[n]->setCutoffFrequency (freqSamples[k]);

                    k++;
                }

                for (int n = 0; n < numStages; ++n)
                    output = filters[n]->processSample ((int) channel, output);

                outputSamples[i] = output;
                lastOutput[channel] = output * feedbackVolume[channel].getNextValue();

                counter++;

                if (counter == maxUpdateCounter)
                    counter = 0;
            }
        }

        dryWet.mixWetSamples (outputBlock);
        updateCounter = (updateCounter + (int) numSamples) % maxUpdateCounter;
    }

private:
    //==============================================================================
    void update();

    //==============================================================================
    Oscillator<SampleType> osc;
    OwnedArray<FirstOrderTPTFilter<SampleType>> filters;
    SmoothedValue<SampleType, ValueSmoothingTypes::Linear> oscVolume;
    std::vector<SmoothedValue<SampleType, ValueSmoothingTypes::Linear>> feedbackVolume { 2 };
    DryWetMixer<SampleType> dryWet;
    std::vector<SampleType> lastOutput { 2 };
    AudioBuffer<SampleType> bufferFrequency;
    SampleType normCentreFrequency = 0.5;
    double sampleRate = 44100.0;

    int updateCounter = 0;
    static constexpr int maxUpdateCounter = 4;

    SampleType rate = 1.0, depth = 0.5, feedback = 0.0, mix = 0.5;
    SampleType centreFrequency = 1300.0;
    static constexpr int numStages = 6;
};

} // namespace juce::dsp
