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

    This file was auto-generated by the Introjucer!

    It contains the basic framework code for a JUCE plugin processor.

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

#include "PluginProcessor.h"
#include "PluginEditor.h"
#include <random>

//==============================================================================
static juce::String onOffTextFunction (const gin::Parameter&, float v)
{
    return v > 0.0f ? "On" : "Off";
}

//==============================================================================
inline bool oddEven (int x)
{
    return (x % 2 == 0) ? 1 : -1;
}
//==============================================================================
slToneAudioProcessor::slToneAudioProcessor()
{
    //==============================================================================
    auto sineFunc = [&] (float in) -> float
    {
        return sin (in);
    };
    
    auto triangleFunc = [&] (float in) -> float
    {
        if (bandLimited)
        {
            const double f = parameterValue (PARAM_FREQ);
            const double sr = getSampleRate();
            
            double sum = 0;
            int k = 1;
            while (f * k < sr / 2)
            {
                sum += std::pow (-1, (k - 1) / 2.0f) / (k * k) * sin (k * in);
                k += 2;
            }
            return float (8.0f / (juce::MathConstants<float>::pi * juce::MathConstants<float>::pi) * sum);
        }
        return (in < 0 ? in / -juce::MathConstants<float>::pi : in / juce::MathConstants<float>::pi) * 2 - 1;
    };
    
    auto sawUpFunc = [&] (float in) -> float
    {
        if (bandLimited)
        {
            const double f = parameterValue (PARAM_FREQ);
            const double sr = getSampleRate();
            
            double sum = 0;
            int k = 1;
            while (f * k < sr / 2)
            {
                sum += oddEven (k) * std::sin (k * in) / k;
                k++;
            }
            return float (-2.0f / juce::MathConstants<float>::pi * sum);
        }
        return ((in + juce::MathConstants<float>::pi) / (2 * juce::MathConstants<float>::pi)) * 2 - 1;
    };
    
    auto sawDownFunc = [&] (float in) -> float
    {
        if (bandLimited)
        {
            const double f = parameterValue (PARAM_FREQ);
            const double sr = getSampleRate();
            
            double sum = 0;
            int k = 1;
            while (f * k < sr / 2)
            {
                sum += oddEven (k) * std::sin (k * in) / k;
                k++;
            }
            return float (2.0f / juce::MathConstants<float>::pi * sum);
        }
        return -(((in + juce::MathConstants<float>::pi) / (2 * juce::MathConstants<float>::pi)) * 2 - 1);
    };
    
    auto squareFunc = [&] (float in) -> float
    {
        if (bandLimited)
        {
            const double f = parameterValue (PARAM_FREQ);
            const double sr = getSampleRate();
            
            double sum = 0;
            int i = 1;
            while (f * (2 * i - 1) < sr / 2)
            {
                sum += std::sin ((2 * i - 1) * in) / ((2 * i - 1));
                i++;
            }
            
            return float (4.0f / juce::MathConstants<float>::pi * sum);
        }
        return in < 0 ? -1.0f : 1.0f;
    };
    
    auto noiseFunc = [&] (float) -> float
    {
        const float mean = 0.0f;
        const float stddev = 0.1f;
        
        static std::default_random_engine generator;
        static std::normal_distribution<float> dist (mean, stddev);
        
        return dist (generator);
    };
    
    sine.initialise (sineFunc);
    triangle.initialise (triangleFunc);
    sawUp.initialise (sawUpFunc);
    sawDown.initialise (sawDownFunc);
    square.initialise (squareFunc);
    noise.initialise (noiseFunc);

    //==============================================================================
    
    addExtParam (PARAM_ENABLE,       "Enable",       "", "",   {  0.0f,      1.0f, 1.0f, 1.0f},    1.0f, 0.0f, onOffTextFunction);
    addExtParam (PARAM_BANDLIMIT,    "Bandlimit",    "", "",   {  0.0f,      1.0f, 1.0f, 1.0f},    1.0f, 0.0f, onOffTextFunction);
    addExtParam (PARAM_FREQ,         "Frequency",    "", "Hz", {  30.0f, 20000.0f, 0.0f, 0.3f}, 1000.0f, 0.0f);
    addExtParam (PARAM_SINE_LEVEL,   "Sine",         "", "dB", {-100.0f,     6.0f, 0.0f, 5.0f}, -100.0f, 0.0f);
    addExtParam (PARAM_TRI_LEVEL,    "Triangle",     "", "dB", {-100.0f,     6.0f, 0.0f, 5.0f}, -100.0f, 0.0f);
    addExtParam (PARAM_SAW_UP_LEVEL, "Saw Up",       "", "dB", {-100.0f,     6.0f, 0.0f, 5.0f}, -100.0f, 0.0f);
    addExtParam (PARAM_SAW_DN_LEVEL, "Saw Down",     "", "dB", {-100.0f,     6.0f, 0.0f, 5.0f}, -100.0f, 0.0f);
    addExtParam (PARAM_SQUARE_LEVEL, "Square",       "", "dB", {-100.0f,     6.0f, 0.0f, 5.0f}, -100.0f, 0.0f);
    addExtParam (PARAM_NOISE_LEVEL,  "Noise",        "", "dB", {-100.0f,     6.0f, 0.0f, 5.0f}, -100.0f, 0.0f);
}

slToneAudioProcessor::~slToneAudioProcessor()
{
}

//==============================================================================
void slToneAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    sine.prepare ({ sampleRate, juce::uint32 (samplesPerBlock), 1});
    triangle.prepare ({ sampleRate, juce::uint32 (samplesPerBlock), 1});
    sawUp.prepare ({ sampleRate, juce::uint32 (samplesPerBlock), 1});
    sawDown.prepare ({ sampleRate, juce::uint32 (samplesPerBlock), 1});
    square.prepare ({ sampleRate, juce::uint32 (samplesPerBlock), 1});
    noise.prepare ({ sampleRate, juce::uint32 (samplesPerBlock), 1});
    
    enableVal.reset (sampleRate, 0.05);
    sineVal.reset (sampleRate, 0.05);
    triangleVal.reset (sampleRate, 0.05);
    sawUpVal.reset (sampleRate, 0.05);
    sawDownVal.reset (sampleRate, 0.05);
    squareVal.reset (sampleRate, 0.05);
    noiseVal.reset (sampleRate, 0.05);
}

void slToneAudioProcessor::releaseResources()
{
}

void slToneAudioProcessor::processBlock (juce::AudioSampleBuffer& buffer, juce::MidiBuffer&)
{
    buffer.clear();

    int numSamples = buffer.getNumSamples();
    gin::ScratchBuffer scratch (1, numSamples);
    
    bandLimited = parameterIntValue (PARAM_BANDLIMIT) != 0;
    float freq = getParameter (PARAM_FREQ)->getUserValue();
    
    sine.setFrequency (freq);
    triangle.setFrequency (freq);
    sawUp.setFrequency (freq);
    sawDown.setFrequency (freq);
    square.setFrequency (freq);

    enableVal.setTargetValue (getParameter (PARAM_ENABLE)->getUserValue());
    sineVal.setTargetValue (juce::Decibels::decibelsToGain (getParameter (PARAM_SINE_LEVEL)->getUserValue()));
    triangleVal.setTargetValue (juce::Decibels::decibelsToGain (getParameter (PARAM_TRI_LEVEL)->getUserValue()));
    sawUpVal.setTargetValue (juce::Decibels::decibelsToGain (getParameter (PARAM_SAW_UP_LEVEL)->getUserValue()));
    sawDownVal.setTargetValue (juce::Decibels::decibelsToGain (getParameter (PARAM_SAW_DN_LEVEL)->getUserValue()));
    squareVal.setTargetValue (juce::Decibels::decibelsToGain (getParameter (PARAM_SQUARE_LEVEL)->getUserValue()));
    noiseVal.setTargetValue (juce::Decibels::decibelsToGain (getParameter (PARAM_NOISE_LEVEL)->getUserValue()));
    
    if (sineVal.isSmoothing() || sineVal.getTargetValue() > 0)
    {
        scratch.clear();
        AudioBlock<float> block (scratch);

        sine.process (ProcessContextReplacing<float> (block));
        applyGain (scratch, sineVal);
        buffer.addFrom (0, 0, scratch, 0, 0, numSamples);
    }
    
    if (triangleVal.isSmoothing() || triangleVal.getTargetValue() > 0)
    {
        scratch.clear();
        AudioBlock<float> block (scratch);

        triangle.process (ProcessContextReplacing<float> (block));
        applyGain (scratch, triangleVal);
        buffer.addFrom (0, 0, scratch, 0, 0, numSamples);
    }
    
    if (sawUpVal.isSmoothing() || sawUpVal.getTargetValue() > 0)
    {
        scratch.clear();
        AudioBlock<float> block (scratch);

        sawUp.process (ProcessContextReplacing<float> (block));
        applyGain (scratch, sawUpVal);
        buffer.addFrom (0, 0, scratch, 0, 0, numSamples);
    }

    if (sawDownVal.isSmoothing() || sawDownVal.getTargetValue() > 0)
    {
        scratch.clear();
        AudioBlock<float> block (scratch);

        sawDown.process (ProcessContextReplacing<float> (block));
        applyGain (scratch, sawDownVal);
        buffer.addFrom (0, 0, scratch, 0, 0, numSamples);
    }

    if (squareVal.isSmoothing() || squareVal.getTargetValue() > 0)
    {
        scratch.clear();
        AudioBlock<float> block (scratch);

        square.process (ProcessContextReplacing<float> (block));
        applyGain (scratch, squareVal);
        buffer.addFrom (0, 0, scratch, 0, 0, numSamples);
    }
    
    if (noiseVal.isSmoothing() || noiseVal.getTargetValue() > 0)
    {
        scratch.clear();
        AudioBlock<float> block (scratch);

        noise.process (ProcessContextReplacing<float> (block));
        applyGain (scratch, noiseVal);
        buffer.addFrom (0, 0, scratch, 0, 0, numSamples);
    }
    
    gin::applyGain (buffer, enableVal);
    
    if (fifo.getFreeSpace() >= numSamples)
        fifo.writeMono (buffer.getReadPointer (0), numSamples);
}

//==============================================================================
bool slToneAudioProcessor::hasEditor() const
{
    return true;
}

juce::AudioProcessorEditor* slToneAudioProcessor::createEditor()
{
    return new slToneAudioProcessorEditor (*this);
}

//==============================================================================
// This creates new instances of the plugin..
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new slToneAudioProcessor();
}
