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

    This file contains the basic framework code for a JUCE plugin processor.

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

#include "PluginProcessor.h"
#include "PluginEditor.h"

//==============================================================================
LeiwandizerAudioProcessor::LeiwandizerAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  juce::AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
                     #endif
                       )
#endif
{
}

LeiwandizerAudioProcessor::~LeiwandizerAudioProcessor()
{
}

//==============================================================================
const juce::String LeiwandizerAudioProcessor::getName() const
{
    return JucePlugin_Name;
}

bool LeiwandizerAudioProcessor::acceptsMidi() const
{
   #if JucePlugin_WantsMidiInput
    return true;
   #else
    return false;
   #endif
}

bool LeiwandizerAudioProcessor::producesMidi() const
{
   #if JucePlugin_ProducesMidiOutput
    return true;
   #else
    return false;
   #endif
}

bool LeiwandizerAudioProcessor::isMidiEffect() const
{
   #if JucePlugin_IsMidiEffect
    return true;
   #else
    return false;
   #endif
}

double LeiwandizerAudioProcessor::getTailLengthSeconds() const
{
    return 0.0;
}

int LeiwandizerAudioProcessor::getNumPrograms()
{
    return 1;   // NB: some hosts don't cope very well if you tell them there are 0 programs,
                // so this should be at least 1, even if you're not really implementing programs.
}

int LeiwandizerAudioProcessor::getCurrentProgram()
{
    return 0;
}

void LeiwandizerAudioProcessor::setCurrentProgram (int index)
{
}

const juce::String LeiwandizerAudioProcessor::getProgramName (int index)
{
    return {};
}

void LeiwandizerAudioProcessor::changeProgramName (int index, const juce::String& newName)
{
}

//==============================================================================
void LeiwandizerAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    // Use this method as the place to do any pre-playback
    // initialisation that you need..
    juce::dsp::ProcessSpec spec;
    spec.sampleRate = sampleRate;
    spec.maximumBlockSize = samplesPerBlock;
    spec.numChannels = getTotalNumOutputChannels();

    lowShelfFilterLw.prepare(spec);
    // Example: low shelf at 120 Hz, boost +6 dB, Q = 0.707
    auto lowShelfLw = juce::dsp::IIR::Coefficients<float>::makeLowShelf(
        sampleRate, 80.0f, 0.707f, juce::Decibels::decibelsToGain(4.0f));
    lowShelfFilterLw.coefficients = *lowShelfLw;

    highShelfFilterLw.prepare(spec);
    // Example: high shelf at 8000 Hz, boost 3 dB, Q = 0.707
    auto highShelfLw = juce::dsp::IIR::Coefficients<float>::makeHighShelf(
        sampleRate, 8000.0f, 0.707f, juce::Decibels::decibelsToGain(3.0f));
    highShelfFilterLw.coefficients = *highShelfLw;

    compressor.prepare(spec);
    compressor.reset();
    compressor.setThreshold(-15.0f); // in dB
    compressor.setRatio(1.5f);
    compressor.setAttack(3.0f);     // ms
    compressor.setRelease(100.0f);   // ms

    limiter.prepare(spec);
    limiter.reset();
    limiter.setThreshold(-3.0f);
    limiter.setRelease(200.0f);

    gain.prepare(spec);
    gain.reset();
    gain.setGainDecibels(-2.0f);

    lowCutFilterOa.prepare(spec);
    auto lowCutOa = juce::dsp::IIR::Coefficients<float>::makeHighPass(sampleRate, 100.0f, 5.0f);
    lowCutFilterOa.coefficients = *lowCutOa;

    highCutFilterOa.prepare(spec);
    // Example: high shelf at 8000 Hz, boost 3 dB, Q = 0.707
    auto highCutOa = juce::dsp::IIR::Coefficients<float>::makeLowPass(sampleRate, 10000.0f, 5.0f);
    highCutFilterOa.coefficients = *highCutOa;

    waveshaper.prepare(spec);
    waveshaper.reset();
    waveshaper.functionToUse = [](float x) {
        //return std::tanh(x * 10.0f);
        return std::sin(x * 3.0f);
        };

    phaser.prepare(spec);
    phaser.reset();
    phaser.setCentreFrequency(100.0f);
    phaser.setDepth(0.6f);
    phaser.setFeedback(0.1f);
    phaser.setRate(99.0f);

    chorus.prepare(spec);
    chorus.reset();
    chorus.setCentreDelay(6.0f);
    chorus.setDepth(0.2f);
    chorus.setFeedback(-0.2f);
    chorus.setRate(0.05f);

    reverb.prepare(spec);
    reverb.reset();
    juce::Reverb::Parameters params;
    params.roomSize = 0.1f;
    params.wetLevel = 0.3f;
    params.dryLevel = 0.7;
    //params.freezeMode = 0.8f;
    reverb.setParameters(params);
}

void LeiwandizerAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
}

#ifndef JucePlugin_PreferredChannelConfigurations
bool LeiwandizerAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
  #if JucePlugin_IsMidiEffect
    juce::ignoreUnused (layouts);
    return true;
  #else
    // This is the place where you check if the layout is supported.
    // In this template code we only support mono or stereo.
    // Some plugin hosts, such as certain GarageBand versions, will only
    // load plugins that support stereo bus layouts.
    if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
     && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
        return false;

    // This checks if the input layout matches the output layout
   #if ! JucePlugin_IsSynth
    if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
        return false;
   #endif

    return true;
  #endif
}
#endif

void LeiwandizerAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    // In case we have more outputs than inputs, this code clears any output
    // channels that didn't contain input data, (because these aren't
    // guaranteed to be empty - they may contain garbage).
    // This is here to avoid people getting screaming feedback
    // when they first compile a plugin, but obviously you don't need to keep
    // this code if your algorithm always overwrites all the output channels.
    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());

    // This is the place where you'd normally do the guts of your plugin's
    // audio processing...
    // Make sure to reset the state if your inner loop is processing
    // the samples and the outer loop is handling the channels.
    // Alternatively, you can process the samples with the channels
    // interleaved by keeping the same state.
    

    juce::dsp::AudioBlock<float> block(buffer);
    juce::dsp::ProcessContextReplacing<float> context(block);
    if (isLeiwand)
    {
        lowShelfFilterLw.process(context);
        highShelfFilterLw.process(context);
        gain.process(context);
        compressor.process(context);
        limiter.process(context);
    }
    else
    {
        phaser.process(context);
        chorus.process(context);
        reverb.process(context);
        lowCutFilterOa.process(context);
        highCutFilterOa.process(context);
        waveshaper.process(context);

        for (int channel = 0; channel < totalNumInputChannels; ++channel)
        {
            auto* channelData = buffer.getWritePointer(channel);
            float lastSample = 0.0f;

            int crushFactor = 1;

            for (int i = 0; i < buffer.getNumSamples(); ++i)
            {
                if (i % crushFactor == 0)
                    lastSample = channelData[i];

                channelData[i] = lastSample;
                if (channel % 2) channelData[i] = -1.0f * channelData[i];
            }
        }
        limiter.process(context);
    }
    
}

//==============================================================================
bool LeiwandizerAudioProcessor::hasEditor() const
{
    return true; // (change this to false if you choose to not supply an editor)
}

juce::AudioProcessorEditor* LeiwandizerAudioProcessor::createEditor()
{
    return new LeiwandizerAudioProcessorEditor (*this);
}

//==============================================================================
void LeiwandizerAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
}

void LeiwandizerAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
}

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