// Mutable Instruments Streams emulation for VCV Rack
// Copyright (C) 2020 Tyler Coy
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#pragma once

#include <cmath>
#include <rack.hpp>
#include "resampler.hpp"
#include "analog_engine.hpp"
#include "digital_engine.hpp"

namespace streams
{

using namespace rack;

class StreamsEngine
{
public:
    struct ChannelFrame
    {
        // Parameters
        float shape_knob;
        float mod_knob;
        float level_mod_knob;
        float response_knob;

        bool function_button;

        // Inputs
        float excite_in;
        float signal_in;
        float level_cv;

        bool signal_in_connected;
        bool level_cv_connected;

        // Outputs
        float signal_out;

        // Lights
        float led_green[4];
        float led_red[4];
    };

    struct Frame
    {
        ChannelFrame ch1;
        ChannelFrame ch2;
        bool metering_button;
        bool lights_updated;
    };

    StreamsEngine()
    {
        Reset();
        SetSampleRate(1.f);
    }

    void Reset(void)
    {
        adc_lpf_.reset();
        resampler_.Reset();
        analog_engine_.Reset();
        digital_engine_.Reset();
        adc_feedback_[0] = 0.f;
        adc_feedback_[1] = 0.f;
    }

    void SetSampleRate(float sample_rate)
    {
        adc_lpf_.setCutoffFreq(kAdcFilterCutoff / sample_rate);
        resampler_.Init(sample_rate, DigitalEngine::kSampleRate, 0);
        analog_engine_.SetSampleRate(sample_rate);
    }

    void Randomize(void)
    {
        digital_engine_.Randomize();
    }

    void ApplySettings(const UiSettings& settings)
    {
        digital_engine_.ApplySettings(settings);
    }

    const UiSettings& ui_settings(void)
    {
        return digital_engine_.ui_settings();
    }

    void SyncUI(const StreamsEngine& other)
    {
        digital_engine_.SyncUI(other.digital_engine_);
    }

    void Process(Frame& frame)
    {
        frame.lights_updated = false;

        float ch1_signal_in = frame.ch1.signal_in_connected ?
                              frame.ch1.signal_in : kSignalInNormalV;
        float ch2_signal_in = frame.ch2.signal_in_connected ?
                              frame.ch2.signal_in : kSignalInNormalV;

        float ch1_level_cv  = frame.ch1.level_cv_connected ?
                              frame.ch1.level_cv : kLevelNormalV;
        float ch2_level_cv  = frame.ch2.level_cv_connected ?
                              frame.ch2.level_cv : kLevelNormalV;

        Rsmp::InputFrame d_input;
        d_input.samples[0] = ch1_signal_in;
        d_input.samples[1] = ch2_signal_in;
        d_input.samples[2] = frame.ch1.excite_in;
        d_input.samples[3] = frame.ch2.excite_in;
        d_input.samples[4] = adc_feedback_[0];
        d_input.samples[5] = adc_feedback_[1];

        auto adc_input = simd::float_4::load(d_input.samples);
        adc_lpf_.process(adc_input);
        adc_input = adc_lpf_.lowpass();
        adc_input = kAdcFilterOffset + adc_input * kAdcFilterGain;
        adc_input.store(d_input.samples);

        DigitalEngine::Frame<kBlockSize> d_frame;

        Rsmp::OutputFrame d_output = resampler_.Process(d_input,
        [&](Rsmp::OutputFrame* output, const Rsmp::InputFrame* input)
        {
            d_frame.ch1.shape_knob          = frame.ch1.shape_knob;
            d_frame.ch1.mod_knob            = frame.ch1.mod_knob;
            d_frame.ch1.level_mod_knob      = frame.ch1.level_mod_knob;
            d_frame.ch1.response_knob       = frame.ch1.response_knob;
            d_frame.ch2.shape_knob          = frame.ch2.shape_knob;
            d_frame.ch2.mod_knob            = frame.ch2.mod_knob;
            d_frame.ch2.level_mod_knob      = frame.ch2.level_mod_knob;
            d_frame.ch2.response_knob       = frame.ch2.response_knob;

            d_frame.ch1.function_button     = frame.ch1.function_button;
            d_frame.ch2.function_button     = frame.ch2.function_button;
            d_frame.metering_button         = frame.metering_button;

            for (int i = 0; i < kBlockSize; i++)
            {
                d_frame.ch1.signal_in[i]    = input[i].samples[0];
                d_frame.ch2.signal_in[i]    = input[i].samples[1];
                d_frame.ch1.excite_in[i]    = input[i].samples[2];
                d_frame.ch2.excite_in[i]    = input[i].samples[3];
                d_frame.ch1.level_adc_in[i] = input[i].samples[4];
                d_frame.ch2.level_adc_in[i] = input[i].samples[5];
            }

            digital_engine_.Process(d_frame);

            for (int i = 0; i < kBlockSize; i++)
            {
                output[i].samples[0] = d_frame.ch1.dac_out[i];
                output[i].samples[1] = d_frame.ch1.pwm_out[i];
                output[i].samples[2] = d_frame.ch2.dac_out[i];
                output[i].samples[3] = d_frame.ch2.pwm_out[i];
            }

            frame.ch1.led_green[0] = d_frame.ch1.led_green[0];
            frame.ch1.led_green[1] = d_frame.ch1.led_green[1];
            frame.ch1.led_green[2] = d_frame.ch1.led_green[2];
            frame.ch1.led_green[3] = d_frame.ch1.led_green[3];
            frame.ch1.led_red[0]   = d_frame.ch1.led_red[0];
            frame.ch1.led_red[1]   = d_frame.ch1.led_red[1];
            frame.ch1.led_red[2]   = d_frame.ch1.led_red[2];
            frame.ch1.led_red[3]   = d_frame.ch1.led_red[3];
            frame.ch2.led_green[0] = d_frame.ch2.led_green[0];
            frame.ch2.led_green[1] = d_frame.ch2.led_green[1];
            frame.ch2.led_green[2] = d_frame.ch2.led_green[2];
            frame.ch2.led_green[3] = d_frame.ch2.led_green[3];
            frame.ch2.led_red[0]   = d_frame.ch2.led_red[0];
            frame.ch2.led_red[1]   = d_frame.ch2.led_red[1];
            frame.ch2.led_red[2]   = d_frame.ch2.led_red[2];
            frame.ch2.led_red[3]   = d_frame.ch2.led_red[3];
            frame.lights_updated   = true;
        });

        AnalogEngine::Frame a_frame;

        a_frame.ch1.level_mod_knob      = frame.ch1.level_mod_knob;
        a_frame.ch1.response_knob       = frame.ch1.response_knob;
        a_frame.ch2.level_mod_knob      = frame.ch2.level_mod_knob;
        a_frame.ch2.response_knob       = frame.ch2.response_knob;

        a_frame.ch1.signal_in           = ch1_signal_in;
        a_frame.ch1.level_cv            = ch1_level_cv;
        a_frame.ch2.signal_in           = ch2_signal_in;
        a_frame.ch2.level_cv            = ch2_level_cv;

        a_frame.ch1.dac_cv              = d_output.samples[0];
        a_frame.ch1.pwm_cv              = d_output.samples[1];
        a_frame.ch2.dac_cv              = d_output.samples[2];
        a_frame.ch2.pwm_cv              = d_output.samples[3];

        analog_engine_.Process(a_frame);

        frame.ch1.signal_out = a_frame.ch1.signal_out;
        frame.ch2.signal_out = a_frame.ch2.signal_out;

        adc_feedback_[0] = a_frame.ch1.adc_out;
        adc_feedback_[1] = a_frame.ch2.adc_out;
    }

protected:
    static constexpr int kBlockSize = 16;
    static constexpr float kAdcFilterCutoff = 1.f / (2 * M_PI * 20e3f * 1e-9f);
    static constexpr float kAdcFilterGain = -20e3f / 100e3f;
    static constexpr float kAdcFilterOffset = -10.f * -20e3f / 120e3f;
    static constexpr float kSignalInNormalV = 5.f;
    static constexpr float kLevelNormalV = 8.f;

    dsp::TRCFilter<simd::float_4> adc_lpf_;
    using Rsmp = InterpolatingResampler<6, 4, kBlockSize, 256>;
    Rsmp resampler_;
    AnalogEngine analog_engine_;
    DigitalEngine digital_engine_;
    float adc_feedback_[2];
};

}
