/*
   ┏━━━┓╱╱╱╱╱┏┓╱╱┏┓╱╱╱╱╱┏┓╱╱
   ┃┏━┓┃╱╱╱╱╱┃┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱
   ┃┗━┛┣┓┏┳━━┫┃┏┓┃┃╱╱┏━━┫┗━┳━━┓
   ┃┏━━┫┃┃┃┏┓┃┗┛┃┃┃╱┏┫┏┓┃┏┓┃━━┫
   ┃┃╱╱┃┗┛┃┃┃┃┏┓┃┃┗━┛┃┏┓┃┗┛┣━━┃
   ┗┛╱╱┗━━┻┛┗┻┛┗┛┗━━━┻┛┗┻━━┻━━┛
    ━━━━━━━━━━━━━━━━━━━━━━━━━━

    Copyright (c) 2023 Punk Labs LLC

    This section is part of OneTrick B-BOI

    OneTrick B-BOI 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.

    OneTrick B-BOI 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
    OneTrick B-BOI.  If not, see <http://www.gnu.org/licenses/>.
*/

use std::cell::RefCell;
//use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

#[cfg(feature = "profile")]
use std::sync::atomic::AtomicI32;

use nih_plug::prelude::*;

use num_enum::TryFromPrimitive;

// #[cfg(feature = "egui")]
// use egui_extras::image::RetainedImage;

#[cfg(feature = "egui")]
use nih_plug_egui::{
    create_egui_editor,
    egui::{
        epaint::{
            Shadow,
        },
        Key,
        style,
        Margin,
        vec2,
        pos2,
        //Align,
        Align2,
        Area,
        CentralPanel,
        Color32,
        Context,
        FontDefinitions,
        FontFamily,
        FontId,
        Frame,
        Id,
        //LayerId,
        //Layout,
        Order,
        Pos2,
        Rect,
        Rounding,
        Sense,
        Stroke,
        Vec2,
    },
    EguiState,
};

mod dsp;
use onetrick::prelude::*;

//256 is 187.5Hz at 48kHz
//512 is 94Hz at 48kHz
//1024 is 47Hz at 48kHz
const MAX_BLOCK_SIZE: usize = 4096;
const NUM_INPUT_CHANNELS: u32 = 2;
const NUM_OUTPUT_CHANNELS: u32 = 2;
#[cfg(feature = "multi_out")]
const AUX_BUS_COUNT: usize = 9;


#[derive(PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum MidiMap {
    Kick = GeneralMidiDrums::BassDrum as u8,
    Snare = GeneralMidiDrums::AcousticSnare as u8,
    Hihat = GeneralMidiDrums::ClosedHihat as u8,
    HihatPedal = GeneralMidiDrums::PedalHihat as u8,
    HihatOpen = GeneralMidiDrums::OpenHihat as u8,
    Cowbell = GeneralMidiDrums::Cowbell as u8,
    CowbellB = GeneralMidiDrums::SideStick as u8,
    Scratch1 = GeneralMidiDrums::ElectricSnare as u8,
    Scratch2 = GeneralMidiDrums::LowFloorTom as u8,
    Scratch2B = GeneralMidiDrums::HighFloorTom as u8,
    Scratch3 = GeneralMidiDrums::LowTom as u8,
    Scratch3B = GeneralMidiDrums::LowMidTom as u8,
    Vocoder = GeneralMidiDrums::HighMidTom as u8,
    VocoderB = GeneralMidiDrums::HighTom as u8,
    Hit1 = GeneralMidiDrums::HandClap as u8,
    Hit2 = GeneralMidiDrums::ChineseCymbal as u8,
    Hit2B = GeneralMidiDrums::RideBell as u8,
    Hit3 = GeneralMidiDrums::SplashCymbal as u8,
    Hit3B = GeneralMidiDrums::CrashCymbal2 as u8,
    Hit3C = GeneralMidiDrums::RideCymbal2 as u8,
    Chops1 = GeneralMidiDrums::CrashCymbal as u8,
    Chops2 = GeneralMidiDrums::RideCymbal as u8,
    Chops3 = GeneralMidiDrums::Tambourine as u8,
    Chops4 = GeneralMidiDrums::VibraSlap as u8,
}

// NOTE: Should probably use an array of &dyn OneTrickGeneral, but couldn't make it work
macro_rules! for_each_voice_dsp {
    ($this:ident, $expression:expr) => {{
        $expression(&mut *$this.dsp_kick.borrow_mut());
        $expression(&mut *$this.dsp_snare.borrow_mut());
        $expression(&mut *$this.dsp_hihat.borrow_mut());
        $expression(&mut *$this.dsp_cowbell.borrow_mut());
        $expression(&mut *$this.dsp_scratch.borrow_mut());
        $expression(&mut *$this.dsp_vocoder.borrow_mut());
        $expression(&mut *$this.dsp_hit.borrow_mut());
        $expression(&mut *$this.dsp_chops.borrow_mut());
    }};
}

#[cfg(feature = "multi_out")]
macro_rules! for_each_voice_dsp_grouped {
    ($this:ident, $expression:expr) => {{
        $expression(&[&mut *$this.dsp_kick.borrow_mut()]);
        $expression(&[&mut *$this.dsp_snare.borrow_mut()]);
        $expression(&[&mut *$this.dsp_hihat.borrow_mut()]);
        $expression(&[&mut *$this.dsp_cowbell.borrow_mut()]);
        $expression(&[&mut *$this.dsp_scratch.borrow_mut()]);
        $expression(&[&mut *$this.dsp_vocoder.borrow_mut()]);
        $expression(&[&mut *$this.dsp_hit.borrow_mut()]);
        $expression(&[&mut *$this.dsp_chops.borrow_mut()]);
    }};
}
macro_rules! for_each_dsp {
    ($this:ident, $expression:expr) => {{
        for_each_voice_dsp!($this, $expression);
        $expression(&mut *$this.dsp_voicechanger.borrow_mut());
        $expression(&mut *$this.dsp_output.borrow_mut());
    }};
}

/// Main Struct for OneTrick Bboi
pub struct OneTrickBBoi {
    params: Arc<OneTrickPluginParams>,
    //processor: FixedProcessor,

    accum_buffer: ResizableBuffer,

    dsp_output: RefCell<OneTrickDSP<dsp::modules::output::DSP_Output>>,
    dsp_kick: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    dsp_snare: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    dsp_hihat: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    dsp_cowbell: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    dsp_scratch: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    dsp_vocoder: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    dsp_hit: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    dsp_voicechanger: RefCell<OneTrickDSP<dsp::modules::voice_changer::DSP_VoiceChanger>>,
    dsp_chops: RefCell<OneTrickDSP<dsp::modules::sampler::DSP_Sampler>>,
    //dsp: [dyn OneTrickDSPGeneral],
    param_global_sensitivity: usize,
    param_global_transpose: usize,
    // param_hihat_closed: i32,
    // param_snare_rimshot: i32,

    kick_indicator: Arc<AtomicBool>,
    snare_indicator: Arc<AtomicBool>,
    cowbell_indicator: Arc<AtomicBool>,
    scratch_indicator: Arc<AtomicBool>,
    vocoder_indicator: Arc<AtomicBool>,
    hihat_indicator: Arc<AtomicBool>,
    hit_indicator: Arc<AtomicBool>,
    chops_indicator: Arc<AtomicBool>,
    
    

    #[cfg(feature = "profile")]
    dsp_cost: Arc<AtomicI32>,
    sample_rate: usize,
}

impl Default for OneTrickBBoi {
    fn default() -> Self {
        /*
        {
            use faust_types::FaustDsp;
            let explosion = Box::new(dsp::modules::DSP_Output::new());
        }
        */

        /* Generate Params?
            let params = OneTrickPluginParams::default();
            // Maybe use indices to reference subgroups?... Group names can be used by NIH-Plug
            params.append_dsp(&dsp_kick, 0, "Kick");
            params.append_dsp(&dsp_snare, 1, "Snare");
            ...
            params.append_dsp(&dsp_output, 11, "");

            //Later when we need to update params:
            params.apply_to_dsp(&mut dsp_kick, 0);
            params.apply_to_dsp(&mut dsp_snare, 1);
            ...
            params.apply_to_dsp(&mut dsp_output, 11);
        */

        let mut params = OneTrickPluginParams::default();
        #[cfg(feature = "egui")]
        {
            params.editor_state = EguiState::from_size(530, 670);
        }

        let mut dsp_output = OneTrickDSP::new();
        let mut dsp_kick = OneTrickDSP::new();
        let mut dsp_snare = OneTrickDSP::new();
        let mut dsp_hihat = OneTrickDSP::new();
        let mut dsp_cowbell = OneTrickDSP::new();
        let mut dsp_scratch = OneTrickDSP::new();
        let mut dsp_vocoder = OneTrickDSP::new();
        let mut dsp_hit = OneTrickDSP::new();
        let mut dsp_chops = OneTrickDSP::new();
        let mut dsp_voicechanger = OneTrickDSP::new();
        
        // ==== OVERRIDES ====
        // KICK
        for p in dsp_kick.params_mut().params_mut() {
            //if p.name() == "Tuning" { p.set_init(46.0); } //46*1.74=80hz for audible range

            if p.name() == "Voice_Reverb" { p.set_init(0.0); }
        }


        params.append_dsp(&mut dsp_output, "Global");
        let param_global_sensitivity = params.append_float(
            FloatParam::new(
                "Global Sensitivity",
                30.0,
                FloatRange::Linear {
                    min: 0.0,
                    max: 100.0,
                },
            )
            .with_unit("%")
            .with_value_to_string(Arc::new(|v| {
                format!("{value:.precision$}", precision = 1, value = v)
            })),
            "Global",
        );
        let param_global_transpose = params.append_float(
            FloatParam::new(
                "Global Transpose",
                0.0,
                FloatRange::Linear {
                    min: -12.0,
                    max: 12.0,
                },
            )
            .with_unit("st")
            .with_value_to_string(Arc::new(|v| {
                format!("{value:.precision$}", precision = 1, value = v)
            })),
            "Global",
        );
        /*
        params.append_int(
            IntParam::new(
                "Global Bits",
                12,
                IntRange::Linear {
                    min: 8,
                    max: 16,
                },
            )
            .with_unit("bit")
            .with_value_to_string(Arc::new(|v| {
                match v {
                    16 => "Full ".to_string(),
                    _ => format!("{value}", value = v),
                }
            })),
            "Global",
        );
        */

        params.append_dsp(&mut dsp_kick, "Kick");
        params.append_dsp(&mut dsp_snare, "Snare");
        params.append_dsp(&mut dsp_hihat, "Hihat");
        params.append_dsp(&mut dsp_cowbell, "Cowbell");
        params.append_dsp(&mut dsp_scratch, "Scratch");
        params.append_dsp(&mut dsp_vocoder, "Vocoder");
        params.append_dsp(&mut dsp_hit, "Hit");
        params.append_dsp(&mut dsp_chops, "Chops");
        params.append_dsp(&mut dsp_voicechanger, "VoiceChanger");

        params.append_field_int("tab_index", 0);


        dsp_kick.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-kick.wav")
        ));
        dsp_snare.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-snare.wav")
        ));
        dsp_hihat.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-hihat.wav")
        ));
        dsp_cowbell.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap1-cowbell.wav")
        ));
        dsp_scratch.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap2-scratch.wav")
        ));
        dsp_scratch.set_sample(1, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-scratch-01.wav")
        ));
        dsp_scratch.set_sample(2, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-scratch-02.wav")
        ));
        dsp_scratch.set_sample(3, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap1-scratch-01.wav")
        ));
        dsp_scratch.set_sample(4, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap1-scratch-02.wav")
        ));
        dsp_vocoder.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-vocoder-01.wav")
        ));
        dsp_vocoder.set_sample(1, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-vocoder-02.wav")
        ));
        dsp_hit.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-orch.wav") // Same as 'Orchestra' when up an octave...
        ));
        dsp_hit.set_sample(1, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-brass.wav") // Same as 'Orchestra' when up an octave...
        ));
        dsp_hit.set_sample(2, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-guitar-02.wav") // Same as 'Orchestra' when up an octave...
        ));
        dsp_chops.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-voice-clap-01.wav")
        ));
        dsp_chops.set_sample(1, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-voice-yahands-01.wav")
        ));
        dsp_chops.set_sample(2, FaustDspExtendedSample::from_wav_bytes(
            include_bytes!("assets/samples/rap10-voice-hey.wav")
        ));
        dsp_chops.set_sample(3, FaustDspExtendedSample::from_wav_bytes(
            // include_bytes!("assets/samples/rap2-woo.wav") // This sounds like James Brown, so leaving out...
            include_bytes!("assets/samples/rap2-uh.wav")
        ));

        // // For testing...
        // dsp_voicechanger.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
        //     include_bytes!("assets/samples/bboi-everybodygrabyabboi.wav")
        //  ));
                    

        let result = Self {
            //params: Arc::new(OneTrickPluginParams::default()),
            params: Arc::new(params),
            //processor: FixedProcessor::default(),

            accum_buffer: ResizableBuffer::default(),

            dsp_output: RefCell::new(dsp_output),
            dsp_kick: RefCell::new(dsp_kick),
            dsp_snare: RefCell::new(dsp_snare),
            dsp_hihat: RefCell::new(dsp_hihat),
            dsp_cowbell: RefCell::new(dsp_cowbell),
            dsp_scratch: RefCell::new(dsp_scratch),
            dsp_vocoder: RefCell::new(dsp_vocoder),
            dsp_hit: RefCell::new(dsp_hit),
            dsp_chops: RefCell::new(dsp_chops),
            dsp_voicechanger: RefCell::new(dsp_voicechanger),

            param_global_sensitivity,
            param_global_transpose,
            // param_hihat_closed,
            // param_snare_rimshot,

            kick_indicator: Arc::new(AtomicBool::new(false)),
            snare_indicator: Arc::new(AtomicBool::new(false)),
            cowbell_indicator: Arc::new(AtomicBool::new(false)),
            scratch_indicator: Arc::new(AtomicBool::new(false)),
            vocoder_indicator: Arc::new(AtomicBool::new(false)),
            hihat_indicator: Arc::new(AtomicBool::new(false)),
            hit_indicator: Arc::new(AtomicBool::new(false)),
            chops_indicator: Arc::new(AtomicBool::new(false)),
            
            #[cfg(feature = "profile")]
            dsp_cost: Arc::new(AtomicI32::new(0)),

            sample_rate: 1, // Invalid until initilized
        };

        // dsp::modules::sampler::load_audio_samples();
        // let sample = FaustDspExtendedSample::from_wav_bytes(
        //     include_bytes!("assets/samples/placeholder-09.wav")
        //     );

        // for_each_voice_dsp!(result, &mut |dsp: &mut dyn OneTrickDSPGeneral| {
        //     dsp.set_sample(0, sample.clone());
        // });

        // for_each_voice_dsp!(result, &mut |dsp: &mut dyn OneTrickDSPGeneral| {
        //     dsp.set_sample(0, FaustDspExtendedSample::from_wav_bytes(
        //         include_bytes!("assets/samples/placeholders/RPMN-Hit_11-Snare.wav")
        //     ));
        // });
    
        result
    }
}

impl OneTrickBBoi {
    /// Set up fonts and default styles for Egui
    #[cfg(feature = "egui")]
    fn setup_egui_style(ctx: &Context) {
        // Start with the default fonts (we will be adding to them rather than replacing them).
        let mut fonts = FontDefinitions::default();

        BasicFonts::add_fonts(&mut fonts);
        Icons::add_fonts(&mut fonts);

        let mut style = style::Style::default();

        // Setup defaults for using generic controls:
        let font_id = FontId::new(17.0, FontFamily::Proportional);
        style.override_font_id = Some(font_id);
        //style.override_text_style = Some(TextStyle::Body);

        //style.widgets.noninteractive.bg_stroke = 
        //    Stroke::new(2.0, palette.grey().alpha(0.666).to_color32());

        // Set the base Style
        style.visuals = style::Visuals::dark();

        // Tooltips and Popup Windows:
        style.visuals.window_fill = Color32::DARK_GRAY;
        style.visuals.window_stroke = Stroke::default();
        style.visuals.popup_shadow = Shadow {offset: Vec2::new(0.0, 0.0), blur: 6.0, spread: 0.0, color: Color32::from_black_alpha(32)};
        style.visuals.override_text_color = Some(Color32::WHITE);
        
        style.visuals.selection.stroke = Stroke::new(2.0, Color32::WHITE);
        ctx.set_style(style);

        // Tell egui to use these fonts:
        ctx.set_fonts(fonts);
    }

    fn midi_channel_ok(channel: u8) -> bool {
        channel <= 15
        // channel == 0 || channel == 9
    }

    fn map_midi_note(note: u8, channel: u8) -> Option<MidiMap> {
        match channel {
            //0: Reserved for Kit
            1  => Some(MidiMap::Kick),
            2  => Some(MidiMap::Snare),
            3  => Some(MidiMap::Hihat),
            4  => Some(MidiMap::Cowbell),
            5  => Some(MidiMap::Scratch1),
            6  => Some(MidiMap::Scratch2),
            7  => Some(MidiMap::Scratch3),
            8  => Some(MidiMap::Vocoder),
            10 => Some(MidiMap::Hit1),
            11 => Some(MidiMap::Hit2),
            12 => Some(MidiMap::Hit3),
            13 => Some(MidiMap::Chops1),
            14 => Some(MidiMap::Chops3),
            15 => Some(MidiMap::Chops4),
            //9: Reserved for Kit
            // Scratch 2 / 3
            // Hit Brass / Guitar
            // Chops Hey / Clap
            _ => MidiMap::try_from(note).ok()
        }        
    }
    fn map_midi_chromatic(note: u8, channel: u8) -> Option<f32> {
        match channel {
            0|9 => None, // Full Kit
            1 => Some(note as f32 - 60.0), // Kick
            2 => Some(note as f32 - 55.0), // Snare
            3 => Some(note as f32 - 60.0), // Hihat
            4 => Some(note as f32 - 68.0), // Cowbell
            5 => Some(note as f32 - 50.0), // Scratch
            6 => Some(note as f32 - 50.0), // Scratch
            7 => Some(note as f32 - 71.5), // Scratch
            8 => Some(note as f32 - 62.5), // Vocoder
            10 => Some(note as f32 - 66.0+1.2), // Hit
            11 => Some(note as f32 - 50.5), // Hit
            12 => Some(note as f32 - 54.5), // Hit
            13 => Some(note as f32 - 54.0), // Chops
            14 => Some(note as f32 - 57.0), // Chops
            15 => Some(note as f32 - 55.0), // Chops
            _ => Some(note as f32 - 60.0) // Chromatic
        }        
    }

    fn set_internal_params(&mut self) {
        // self.dsp_kick.borrow_mut().set_param_by_name("SampleIndex", 10.0);
    }
}


impl Plugin for OneTrickBBoi {
    const NAME: &'static str = "OneTrick B-BOI";
    const VENDOR: &'static str = "Punk Labs";
    const URL: &'static str = env!("CARGO_PKG_HOMEPAGE");
    const EMAIL: &'static str = "contact@punklabs.com";

    const VERSION: &'static str = env!("CARGO_PKG_VERSION");

    const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
        #[cfg(feature = "multi_out")]
        AudioIOLayout {
            // Multi-Out Stereo
            main_input_channels: NonZeroU32::new(NUM_INPUT_CHANNELS),
            main_output_channels: NonZeroU32::new(NUM_OUTPUT_CHANNELS),
            aux_input_ports: &[],
            // Two to five of these ports will be used at a time
            aux_output_ports: &[new_nonzero_u32(NUM_OUTPUT_CHANNELS); AUX_BUS_COUNT],

            names: PortNames {
                layout: Some("Multi-Out Stereo"),

                main_input: None,
                main_output: Some("Global"),
                aux_inputs: &[],
                aux_outputs: &[
                    "Kick",
                    "Snare",
                    "Hihat",
                    "Cowbell",
                    "Scratch",
                    "Vocoder",
                    "Hit",
                    "Chops",
                    "VoiceChanger",
                ],
            },
        },
        AudioIOLayout {
            // Stereo
            main_input_channels: NonZeroU32::new(NUM_INPUT_CHANNELS),
            main_output_channels: NonZeroU32::new(NUM_OUTPUT_CHANNELS),
            names: PortNames {
                layout: Some("Stereo"),

                main_input: None,
                main_output: Some("Output"),
                aux_inputs: &[],
                aux_outputs: &[],
            },
            ..AudioIOLayout::const_default()
        },
    ];

    const MIDI_INPUT: MidiConfig = MidiConfig::Basic; //Basic or MidiCCs
    const MIDI_OUTPUT: MidiConfig = MidiConfig::None;

    const SAMPLE_ACCURATE_AUTOMATION: bool = false; // Too bad this doesn't split on Midi NoteOn events

    type SysExMessage = ();
    type BackgroundTask = ();

    fn params(&self) -> Arc<dyn Params> {
        self.params.clone()
    }

    fn initialize(
        &mut self,
        _audio_io_layout: &AudioIOLayout,
        buffer_config: &BufferConfig,
        _context: &mut impl InitContext<Self>,
    ) -> bool {
        // Resize buffers and perform other potentially expensive initialization operations here.
        // The `reset()` function is always called right after this function. You can remove this
        // function if you do not need it.

        let sample_rate = buffer_config.sample_rate as usize;

        //self.processor.initialize(48000, sample_rate, 2, MAX_BLOCK_SIZE);

        self.accum_buffer.resize(3, MAX_BLOCK_SIZE);

        //nih_log!("Sample Rate: {}", sample_rate);
        self.sample_rate = sample_rate;
        const SILENCE_MS: u32 = 510; // 500ms is max delay
        for_each_dsp!(self, &mut |dsp: &mut dyn OneTrickDSPGeneral| {
            dsp.initialize(sample_rate)
                .resize_buffer(MAX_BLOCK_SIZE)
                .track_silence(SILENCE_MS);
        });

        // Voice changer MUST be able to handle full max_buffer_size
        self.dsp_voicechanger.borrow_mut().initialize(sample_rate)
            .resize_buffer(buffer_config.max_buffer_size as usize)
            .track_silence(SILENCE_MS);

        self.set_internal_params();

        true
    }

    fn reset(&mut self) {
        // Reset buffers and envelopes here. This can be called from the audio thread and may not
        // allocate. You can remove this function if you do not need it.
        for_each_dsp!(self, &mut |dsp: &mut dyn OneTrickDSPGeneral| {
            dsp.reset();
        });
        
        self.set_internal_params();
    }

    fn process(
        &mut self,
        buffer: &mut Buffer,
        aux: &mut AuxiliaryBuffers,
        context: &mut impl ProcessContext<Self>,
    ) -> ProcessStatus {
        #[cfg(feature = "profile")]
        let dsp_timer = std::time::Instant::now();

        let _is_standalone = match context.plugin_api() {
            PluginApi::Standalone => true,
            _ => false,
        };

        { // Special handling for voice changer
            let frames = buffer.samples();
            let mut voice = self.dsp_voicechanger.borrow_mut();
            voice.wake_up(); // Just in case
            voice.compute(frames, buffer.as_slice_actually_immutable());
        }

        ProcessEx::process_split_notes(
        //self.processor.process_split_notes(
            buffer,
            aux,
            MAX_BLOCK_SIZE,
            context,
            |event| {
                // MIDI
                match event {
                    NoteEvent::MidiPitchBend {
                        timing: _,
                        channel,
                        value,
                    } => {
                        // if !Self::midi_channel_ok(channel) {
                        if channel == 0 || channel == 9 { // Full Kit
                            // Pitch Shifting through synthesis:
                            // Artifact-free and Latency-free
                            for_each_voice_dsp!(self, &mut |dsp: &mut dyn OneTrickDSPGeneral| {
                                dsp.pitch_wheel(value);
                            });
                            self.dsp_voicechanger.borrow_mut().pitch_wheel(value);
                        } else {
                            match channel {
                                1 => self.dsp_kick.borrow_mut().pitch_wheel(value),
                                2 => self.dsp_snare.borrow_mut().pitch_wheel(value),
                                3 => self.dsp_hihat.borrow_mut().pitch_wheel(value),
                                4 => self.dsp_cowbell.borrow_mut().pitch_wheel(value),
                                5|6|7 => self.dsp_scratch.borrow_mut().pitch_wheel(value),
                                8 => self.dsp_vocoder.borrow_mut().pitch_wheel(value),
                                10|11|12 => self.dsp_hit.borrow_mut().pitch_wheel(value),
                                13|14|15 => self.dsp_chops.borrow_mut().pitch_wheel(value),
                                _ => {}
                            }
                        }
                    }
                    NoteEvent::MidiCC {
                        timing: _,
                        channel,
                        cc,
                        value,
                    } => {
                        // if !Self::midi_channel_ok(channel) {
                        if channel != 0 && channel != 9 { // Only allow Kit channels
                                return;
                        }
                        if cc == 1 {
                            self.dsp_output.borrow_mut().mod_wheel(value);
                        }
                    }
                    NoteEvent::NoteOn {
                        timing: _,
                        voice_id: _,
                        channel,
                        note,
                        velocity,
                    } => {
                        if !Self::midi_channel_ok(channel) {
                            return;
                        }
                        if let Some(midi_drum_note) = Self::map_midi_note(note, channel) {
                            let chromatic = Self::map_midi_chromatic(note, channel).unwrap_or(0.0);
                            let velocity = if let Some(p) =
                                self.params.param_float_at(self.param_global_sensitivity)
                            {
                                let sensitivity = p.value() * 0.01;
                                velocity * sensitivity + (1.0 - sensitivity) * (80.0 / 127.0)
                            } else {
                                velocity
                            };

                            self.dsp_output.borrow_mut().wake_up();
                            match midi_drum_note {
                                MidiMap::Kick => {
                                    self.kick_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_kick.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::CowbellB => {
                                    self.cowbell_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_cowbell.borrow_mut().note_on(chromatic+20.0, velocity);
                                }
                                MidiMap::Snare => {
                                    self.snare_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_snare.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Hit1 => {
                                    self.hit_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hit.borrow_mut().sample_index(0);
                                    self.dsp_hit.borrow_mut().note_on(chromatic-1.2, velocity);
                                }             
                                MidiMap::Scratch1 => {
                                    self.scratch_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_scratch.borrow_mut().sample_index(0);
                                    self.dsp_scratch.borrow_mut().note_on(chromatic+1.0, velocity);
                                }
                                MidiMap::Scratch2 => {
                                    self.scratch_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_scratch.borrow_mut().sample_index(1);
                                    self.dsp_scratch.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Hihat => {
                                    self.hihat_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hihat.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Scratch2B => {
                                    self.scratch_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_scratch.borrow_mut().sample_index(2);
                                    self.dsp_scratch.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::HihatPedal => {
                                    self.hihat_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hihat.borrow_mut().note_on(chromatic-3.5, velocity * 0.15);
                                }
                                MidiMap::Scratch3 => {
                                    self.scratch_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_scratch.borrow_mut().sample_index(3);
                                    self.dsp_scratch.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::HihatOpen => {
                                    self.hihat_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hihat.borrow_mut().note_on(chromatic-1.5, velocity);
                                }
                                MidiMap::Scratch3B => {
                                    self.scratch_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_scratch.borrow_mut().sample_index(4);
                                    self.dsp_scratch.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Vocoder => {
                                    self.vocoder_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_vocoder.borrow_mut().sample_index(0);
                                    self.dsp_vocoder.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Chops1 => {
                                    self.chops_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_chops.borrow_mut().sample_index(0);
                                    self.dsp_chops.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::VocoderB => {
                                    self.vocoder_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_vocoder.borrow_mut().sample_index(1);
                                    self.dsp_vocoder.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Chops2 => {
                                    self.chops_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_chops.borrow_mut().sample_index(1);
                                    self.dsp_chops.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Hit2 => {
                                    self.hit_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hit.borrow_mut().sample_index(1);
                                    self.dsp_hit.borrow_mut().note_on(chromatic - 21.0, velocity);
                                }             
                                MidiMap::Hit2B => {
                                    self.hit_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hit.borrow_mut().sample_index(1);
                                    self.dsp_hit.borrow_mut().note_on(chromatic - 9.0, velocity);
                                }             
                                MidiMap::Chops3 => {
                                    self.chops_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_chops.borrow_mut().sample_index(2);
                                    self.dsp_chops.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Hit3 => {
                                    self.hit_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hit.borrow_mut().sample_index(2);
                                    self.dsp_hit.borrow_mut().note_on(chromatic+3.0, velocity);
                                }
                                MidiMap::Cowbell => {
                                    self.cowbell_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_cowbell.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Hit3B => {
                                    self.hit_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hit.borrow_mut().sample_index(2);
                                    self.dsp_hit.borrow_mut().note_on(chromatic+6.0, velocity);
                                }
                                MidiMap::Chops4 => {
                                    self.chops_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_chops.borrow_mut().sample_index(3);
                                    self.dsp_chops.borrow_mut().note_on(chromatic, velocity);
                                }
                                MidiMap::Hit3C => {
                                    self.hit_indicator.store(true, Ordering::Relaxed);
                                    self.dsp_hit.borrow_mut().sample_index(2);
                                    self.dsp_hit.borrow_mut().note_on(chromatic+8.0, velocity);
                                }             
                                // MidiMap::Hit3C => { // Testing Mic
                                //     self.dsp_output.borrow_mut().wake_up();
                                //     self.dsp_voicechanger.borrow_mut().note_on(0.0, velocity);
                                // }
                                // _ => {}
                            }
                        }
                    }
                    // NoteEvent::NoteOff { // Testing Mic
                    //     timing: _,
                    //     voice_id: _,
                    //     channel: _,
                    //     note,
                    //     velocity: _,
                    // } => {
                    //     if let Some(note) = MidiMap::try_from(note).ok() {
                    //         match note {
                    //             // MidiMap::Hit3C => { // Testing Mic
                    //             //     self.dsp_voicechanger.borrow_mut().note_off();
                    //             // }
                    //             _ => {}
                    //         }
                    //     }
                    // }
                    _ => (),
                }
            },
            #[allow(unused_variables)]
            &mut |buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, block_start, block_end| // AUDIO
            {
                //let frames = output[0].len() as i32;
                let frames = block_end - block_start;

                // Attempt to process audio in blocks split by events:
                let output = &mut buffer.slice2ch_range_mut(block_start, block_end);
                for channel in output.iter_mut() {
                    channel.fill(0.0);
                }

                //self.accum_buffer.clear();
                self.accum_buffer.buffer.clear_frames(frames);

                let pitch_shift = if let Some(p) =
                    self.params.param_float_at(self.param_global_transpose)
                {
                    p.value()
                } else {
                    0.0
                };
                for_each_voice_dsp!(self, &mut |dsp: &mut dyn OneTrickDSPGeneral| {
                    // Compute Voice:
                    if dsp.is_active() {
                        dsp.transpose(pitch_shift);
                        dsp.compute(frames, &[]);
                    } else {
                        dsp.skip_compute();
                    }
                    // Accumulate Voice:
                    dsp.add_to_buffer(&mut self.accum_buffer.buffer);
                });
                // Process Main Output:
                {
                    let mut dsp = self.dsp_output.borrow_mut();
                    if dsp.is_active() {
                        dsp.compute_to(frames, &self.accum_buffer.buffer.as_slice_actually_immutable(), Some(output));
                    } else {
                        dsp.skip_compute();
                    }
                }

                // Handle Aux Output:
                #[cfg(feature = "multi_out") ]
                {
                    // NIH Plug will zero out Aux buffers if it gets "weird data" from host...
                    // Ardour: Uses only the first Aux output..
                    let aux_supported: bool = !aux.outputs.is_empty()
                        && !aux.outputs[0].is_empty()
                        && !aux.outputs[0].as_slice()[0].is_empty();
                    if aux_supported {

                        let mut aux_outs = aux.outputs.iter_mut();

                        for_each_voice_dsp_grouped!(self, &mut |group: &[&mut dyn OneTrickDSPGeneral]| {
                            if let Some(aux_out) = aux_outs.next() {
                                if !aux_out.is_empty() && !aux_out.as_slice()[0].is_empty() {
                                    let mut is_first = true;
                                    for dsp in group {
                                        if dsp.compute_ran() {
                                            let mut slice = aux_out.slice2ch_range_mut(block_start, block_end);
                                            if is_first {
                                                dsp.write_to_slice(&mut slice);
                                                is_first = false;
                                            } else {
                                                dsp.add_to_slice(&mut slice);
                                            }
                                        }
                                    }
                                }
                            }
                        });
                    }
                }
            },
        );

        { // Special handling for voice changer
            #[cfg(feature = "multi_out") ]
            {
                if let Some(voice_bus) = aux.outputs.get_mut(AUX_BUS_COUNT-1) {
                    self.dsp_voicechanger.borrow_mut().write_to_buffer(voice_bus);
                }

            }
            self.dsp_voicechanger.borrow_mut().add_to_buffer(buffer); // Add to main output...
        }


        #[cfg(feature = "profile")]
        {
            let sec: f64 = dsp_timer.elapsed().as_secs_f64().try_into().unwrap();
            let max_sec: f64 = buffer.samples() as f64 / self.sample_rate as f64;
            let ratio_int = (sec / max_sec * 100. * 100000.) as i32; // Put it into %, then 5 decimals of precision
                                                                     // VU-style falling:
            let old_ratio_int = self.dsp_cost.load(Ordering::Relaxed);
            let ratio_int = ratio_int.max(old_ratio_int - 2000);
            self.dsp_cost.store(ratio_int, Ordering::Relaxed);
        }

        ProcessStatus::Normal
    }

    #[cfg(feature = "egui")]
    fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
        //async_executor.execute_gui((||{nih_log!("Hello from the GUI")})());
        //async_executor.execute_background((||{nih_log!("Hello from a Background Thread")})());

        let params = self.params.clone();

        //nih_log!("editor()");

        let tab_field: Arc<FieldI32> = params.field_int("tab_index").unwrap();

        /*
        let grunge1_svg: Arc<AtomicRefCell<Option<RetainedImage>>> =
            Arc::new(AtomicRefCell::new(None));
        let grunge1_svg_loader = grunge1_svg.clone();

        let grunge2_svg: Arc<AtomicRefCell<Option<RetainedImage>>> =
            Arc::new(AtomicRefCell::new(None));
        let grunge2_svg_loader = grunge2_svg.clone();
        */

        let palette = Palette::new(8)
            .shades(6)
            .white_level(1.0)
            .black_level(0.15)
            .saturation(0.75)
            .shift(0.49)
            .alt_hue_step(4.533)
            .dark_shift(0.1)
            .dark_desaturation(0.5);
        //let palette_alt = palette.alternate(1.5);

        let mut knob_style = ParamKnobStyle {
            radius: 35.0,
            indicator_style: ParamKnobIndicatorStyle::Continuous,
            ..Default::default()
        };
        let column_width = knob_style.required_width();


        let mut heading_style = LabelStyle::default_heading();
        heading_style.width = Some(column_width);
        heading_style.color = palette.black().into();
        heading_style.bg_color = palette.white().brightness(0.1).into(); //Color32::from_gray(128);
        let mut subheading_style = heading_style.clone();
        subheading_style.color = palette.black().into();
        subheading_style.bg_color = Color32::TRANSPARENT;
        subheading_style.shadow = None;
        heading_style.color = palette.white().into();



        knob_style.knob_color = palette.grey().into();
        knob_style.label_style.bg_color = palette.black().into();
        knob_style.label_style.color = palette.white().into();
        knob_style.label_style_hover.bg_color = palette.black().into();
        knob_style.label_style_hover.color = palette.color(0).into();
        knob_style.bg_color = palette.black().into();
        knob_style.indicator_fill_color = palette.grey().into();

        let mut knob_style_1 = knob_style.clone();
        knob_style_1.indicator_fill_color = palette.color_alt(0, 1).into();
        let mut knob_style_2 = knob_style.clone();
        knob_style_2.indicator_fill_color = palette.color(0).into();
        let mut knob_style_3 = knob_style.clone();
        knob_style_3.indicator_fill_color = palette.color(1).into();
        let mut knob_style_4 = knob_style.clone();
        knob_style_4.indicator_fill_color = palette.color(2).into();
        let mut knob_style_5 = knob_style.clone();
        knob_style_5.indicator_fill_color = palette.color(4).into();

        let mut slider_style = ParamSliderStyle {
            length: 70.0,
            ..Default::default()
        };
        /*
        slider_style.knob_aspect = 2.0/1.0;
        slider_style.knob_width = 20.0;
        slider_style.knob_round_ratio = 0.5;
        */
        slider_style.knob_color = knob_style.knob_color;
        slider_style.label_width = 70.0;
        slider_style.label_style.bg_color = knob_style.label_style.bg_color;
        slider_style.label_style.color = knob_style.label_style.color;
        slider_style.label_style_hover.bg_color = knob_style.label_style_hover.bg_color;
        slider_style.label_style_hover.color = knob_style.label_style.color;
        slider_style.bg_color = palette.black().into();
        slider_style.fill_color = knob_style.indicator_fill_color;

        let mut slider_style_1 = slider_style.clone();
        slider_style_1.fill_color = knob_style_1.indicator_fill_color;
        let mut slider_style_2 = slider_style.clone();
        slider_style_2.fill_color = knob_style_2.indicator_fill_color;
        let mut slider_style_3 = slider_style.clone();
        slider_style_3.fill_color = knob_style_3.indicator_fill_color;
        let mut slider_style_4 = slider_style.clone();
        slider_style_4.fill_color = knob_style_4.indicator_fill_color;
        #[allow(clippy::redundant_clone)]
        let mut slider_style_5 = slider_style.clone();
        slider_style_5.fill_color = knob_style_5.indicator_fill_color;

        let show_credits = Arc::new(AtomicBool::new(false));
        //let anim_id = Id::new("local_anim"); // Can use in each context once
        let credits_anim_id = Id::new("credits_anim");
        let kick_anim_id = Id::new("kick_anim");
        let snare_anim_id = Id::new("snare_anim");
        let hihat_anim_id = Id::new("hihat_anim");
        let cowbell_anim_id = Id::new("cowbell_anim");
        let scratch_anim_id = Id::new("scratch_anim");
        let vocoder_anim_id = Id::new("vocoder_anim");
        let hit_anim_id = Id::new("hit_anim");
        let chops_anim_id = Id::new("chops_anim");
        
        #[cfg(feature = "profile")]
        let dsp_cost = self.dsp_cost.clone();

        let kick_indicator = self.kick_indicator.clone();
        let snare_indicator = self.snare_indicator.clone();
        let cowbell_indicator = self.cowbell_indicator.clone();
        let scratch_indicator = self.scratch_indicator.clone();
        let vocoder_indicator = self.vocoder_indicator.clone();
        let hihat_indicator = self.hihat_indicator.clone();
        let hit_indicator = self.hit_indicator.clone();
        let chops_indicator = self.chops_indicator.clone();

        create_egui_editor(
            self.params.editor_state.clone(),
            (),
            move |ctx, _| {
                // DPI:
                // egui_baseview needs to be updated to support DPI.
                // We can adjust egui's rendering, but not egui_baseview's resolution.
                //ctx.set_pixels_per_point(2.0);

                //nih_log!("Editor::Open()");
                Self::setup_egui_style(ctx);
            },
            move |ctx, setter, _state| {
                #[cfg(feature = "stress_test")] // Add some randomizing functionality
                {
                    // Stress regular automation (not multiple at once!)
                    Preset::from_random_values(&params)
                        .filter_params_include(&["Global Volume"])
                        .apply_with_automation(&params, setter);

                    // Stress applying a preset
                    Preset::from_random_values(&params)
                        .apply(&params, setter);
                }

                //update()
                //let window_rect = ctx.available_rect();

                #[cfg(feature = "profile")]
                let dsp_cost = dsp_cost.load(Ordering::Relaxed) as f32 * 0.00001; // Scale for 5 decimals of precision

                let zoom_amount = 0.2;
                let zoom_time = 0.150;
                let indicator_raw = kick_indicator.swap(false, Ordering::Relaxed);
                let kick_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        kick_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                let indicator_raw = snare_indicator.swap(false, Ordering::Relaxed);
                let snare_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        snare_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                let indicator_raw = hihat_indicator.swap(false, Ordering::Relaxed);
                let hihat_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        hihat_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                let indicator_raw = cowbell_indicator.swap(false, Ordering::Relaxed);
                let cowbell_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        cowbell_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                let indicator_raw = scratch_indicator.swap(false, Ordering::Relaxed);
                let scratch_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        scratch_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                let indicator_raw = vocoder_indicator.swap(false, Ordering::Relaxed);
                let vocoder_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        vocoder_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                let indicator_raw = hit_indicator.swap(false, Ordering::Relaxed);
                let hit_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        hit_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                let indicator_raw = chops_indicator.swap(false, Ordering::Relaxed);
                let chops_indicator_zoom = 1.0
                    + ctx.animate_bool_with_time(
                        chops_anim_id,
                        indicator_raw,
                        if indicator_raw { 0.0 } else { zoom_time },
                ) * zoom_amount;
                            

                let show_credits_amount =
                    ctx.animate_bool_with_time(credits_anim_id, show_credits.load(Ordering::Relaxed), 0.300);

                if show_credits_amount > 0.0 {
                    Area::new("credits_area".into())
                        .fixed_pos(Pos2::new(0.0, 0.0))
                        .order(Order::Foreground)
                        .show(ctx, |ui| {
                            Frame::none()
                                .outer_margin(Margin::same(0.0))
                                .inner_margin(Margin::same(10.0))
                                .fill(Color32::from_black_alpha(
                                    (200.0 * show_credits_amount) as u8,
                                ))
                                //.rounding(panel_rounding)
                                .show(ui, |ui| {
                                    let available_size =
                                        Vec2::new(ui.available_width(), ui.available_height());
                                    //let animated_offset = Vec2::new(0.0, available_size.y * (1.0-show_credits_amount));
                                    ui.painter().text(
                                        (available_size * 0.5).to_pos2(),
                                        Align2::CENTER_CENTER,
                                        format!(
                                            include_str!("CREDITS"),
                                            VERSION = env!("CARGO_PKG_VERSION")
                                        ),
                                        FontId::new(24.0, FontFamily::Name("Title".into())),
                                        Color32::from_white_alpha(
                                            (255.0 * show_credits_amount) as u8,
                                        ),
                                    );
                                    /*
                                    let galley = ui.painter().layout_no_wrap(
                                        include_str!("CREDITS").to_string(),
                                        FontId::new(20.0, FontFamily::Name("Title".into())),
                                        Color32::WHITE);
                                    let rect = Align2::CENTER_CENTER.anchor_rect(Rect::from_min_size((available_size * 0.5).to_pos2(), galley.size()));
                                    ui.painter().galley(rect.min, galley);
                                    */

                                    if ui
                                        .allocate_response(available_size, Sense::click())
                                        .clicked()
                                        || ui.input(|i| i.key_pressed(Key::Escape))
                                    {
                                        show_credits.store(false, Ordering::Relaxed);
                                    }
                                });
                        });
                }
                let background_color = palette.white().brightness(0.05).to_color32();
                CentralPanel::default()
                    .frame(
                        Frame::none()
                            .outer_margin(Margin::same(0.0))
                            .inner_margin(Margin::same(10.0))
                            //.fill(panel_bg_color), //.rounding(Rounding{se: 20.0, sw: 20.0, ..Default::default()})
                            //.fill(palette.color(0).shade(1).into()),
                            //.fill(palette.white().brightness(0.15).into()),
                            //.fill(palette.color(2).shade(1).into()),
                            .fill(background_color)
                    )
                    .show(ctx, |ui| {
                        //ctx.set_debug_on_hover(false);

                        let header_height = 70.0;
                        let panel_height = 595.0;
                        let panel_rounding = 20.0;
                        let panel_gap = 6.0;

                        // Buttons
                        let color = palette.color(4).saturation(1.2).into();
                        let light_direction = vec2(1.0, 1.0);
                        let shine_brightness = 0.20;
                        ui.painter().grippy_button(pos2(300.0, 50.0), 15.0, color, light_direction, shine_brightness, 4, 0.2);
                        ui.painter().grippy_button(pos2(350.0, 40.0), 20.0, color, light_direction, shine_brightness, 4, 0.5);
                        ui.painter().grippy_button(pos2(410.0, 35.0), 25.0, color, light_direction, shine_brightness, 4, 0.8);
                        ui.painter().grippy_button(pos2(470.0, 40.0), 20.0, color, light_direction, shine_brightness, 4, 1.1);

                        // Left Panel
                        ui.painter().rect_filled(
                            Rect::from_min_size(pos2(0.0, header_height), vec2(300.0-panel_gap/2.0, panel_height)),
                            Rounding{nw:0.0, ne: panel_rounding, sw: 0.0, se: panel_rounding},
                            palette.color(2).shade(1).to_color32(),
                        );
                        
                        // Right Panel
                        ui.painter().rect_filled(
                            Rect::from_min_size(pos2(300.0+panel_gap/2.0, header_height), vec2(230.0, panel_height)),
                            Rounding{nw:panel_rounding, ne:0.0, sw:panel_rounding, se:0.0},
                            palette.color(0).shade(1).to_color32(),
                        );

                        // ui.painter().parallelograms(
                        //     Rect::from_min_size(Pos2::new(325.0, 0.0), Vec2::new(220.0, header_height)),
                        //     1.0,
                        //     &[
                        //         //palette.color(0).shade(1).to_color32(),                                
                        //         palette.white().to_color32(),
                        //         palette.color(6).shade(1).to_color32(),
                        //         palette.color(5).shade(1).to_color32(),
                        //         palette.color(4).shade(1).to_color32(),
                        //         //palette.color(0).shade(1).to_color32(),
                        //     ],
                        // );


                        Area::new("logo_area".into()) // Prevents layout being affected by Logo
                        .fixed_pos(Pos2::new(0.0, 0.0))
                        .order(Order::Foreground)
                        .show(ctx, |ui| {
                            let response = ui.allocate_rect(
                                Rect::from_min_size(Pos2::new(0.0, 0.0),
                                Vec2::new(240.0, 70.0)),
                                Sense::click());
                            if response.clicked() {
                                show_credits.store(true, Ordering::Relaxed);
                            }
                            ui.painter().one_trick_logo(
                                "B-BOI",
                                Pos2::new(15.0, 35.0),
                                52.0,
                                palette.white().brightness(0.66).into(),
                                if response.hovered() {palette.color(5).into()} else {palette.white().into()},
                            );
                            // Fix for nothing being selected...
                            if ui.memory(|mem| mem.focused().is_none()) {
                                response.request_focus();
                            }
                        });

                        ui.set_width(ui.available_width());

                        // ============================================
                        // ============== MAIN 2 COLUMNS ==============
                        // ============================================
                        ui.horizontal(|ui| {
                            // ==========================================
                            // ============== VOICES PANEL ==============
                            // ==========================================
                            Frame::none()
                                .outer_margin(Margin::same(0.0))
                                .inner_margin(Margin::symmetric(10.0, 10.0))
                                .fill(Color32::TRANSPARENT)
                                //.stroke(Stroke::new(1.0, palette.black()))
                                .show(ui, |ui| {
                                ui.vertical(|ui| { // Voices
                                    ui.set_width(75.0*8.0);
                                    ui.set_height(660.0);
                                    ui.add_space(header_height);
                                    ui.horizontal_top(|ui| {
                                        /*
                                        ui.vertical_centered(|ui| {
                                            ui.add(
                                                Label::new("Kit").with_style(&heading_style),
                                            );
                                        });
                                        ui.add_space(5.0);
                                        */
                                        let tabs_style = IconTabsStyle{
                                            orientation: IconTabsOrientation::Vertical,
                                            panel_length: 555.0.into(),
                                            panel_width: 64.0.into(),
                                            icon_padding: 13.0.into(),
                                            panel_rounding: 15.0.into(),
                                            tab_rounding: 15.0.into(),
                                            tab_hover_resize: vec2(4.0, 4.0),
                                            // panel_color: palette.color(2).brightness(0.5).alpha(1.0).into(),
                                            //panel_color: palette.color(0).shade(1).into(),
                                            panel_color: background_color,
                                            //bg_color_active: palette.color(3).into(),
                                            tab_color_active: palette.white().into(),
                                            color: palette.white().alpha(0.6).into(),
                                            color_active: palette.black().into(),
                                            ..Default::default()
                                        };

                                        let mut tab_index: u32 = tab_field.get() as u32;
                                        let is_mic_selected = tab_index == 10;
                                        ui.add(IconTabs::new(
                                            &mut tab_index,
                                            tabs_style,
                                            vec![
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::KickDrum)
                                                    .with_zoom(kick_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::SnareDrum)
                                                    .with_zoom(snare_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::Hihat)
                                                    .with_zoom(hihat_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::Bell)
                                                    .with_zoom(cowbell_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::Disc)
                                                    .with_zoom(scratch_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::Robot)
                                                    .with_zoom(vocoder_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::Truck)
                                                    .with_zoom(hit_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::Megaphone)
                                                    .with_zoom(chops_indicator_zoom),
                                                IconTabStyle::default()
                                                    .with_dingbat(Dingbat::Mic)
                                                    .with_background_color(
                                                        palette.color(4)
                                                        .brightness(0.8)
                                                        .saturation(1.5)
                                                        .alpha(if is_mic_selected {0.0} else {1.0})
                                                        .into())
                                                    .with_zoom(1.0),
                                            ],
                                        ));
                                        tab_field.set(tab_index as i32);
                                        ui.add_space(15.0);
                                        let voices = [
                                            "Kick",
                                            "Snare",
                                            "Hihat",
                                            "Cowbell",
                                            "Scratch",
                                            "Vocoder",
                                            "Hit",
                                            "Chops",
                                            "VoiceChanger",
                                        ];
                                        let voice_name = voices[tab_index as usize];
                                        let _center_offset_x = 38.0;
                                        let spacing_y = 15.0;
                                        ui.vertical(|ui| {
                                            ui.horizontal(|ui| {
                                                let center_offset_x = 45.0;
                                                let justify_spacing_x = 22.0;
                                                ui.spacing_mut().item_spacing = vec2(justify_spacing_x, spacing_y);

                                                ui.set_height(232.0);
                                                ui.vertical(|ui| {
                                                    ui.horizontal(|ui| {
                                                        ui.add_space(center_offset_x);
                                                        ui.add(Label::new("Voice").with_style(&subheading_style));
                                                    });
                                                    ui.horizontal(|ui| {
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float(&format!("{} Mix Gain", voice_name)),
                                                                setter,
                                                            )
                                                            .with_label("Gain")
                                                            .with_style(&knob_style_5),
                                                        );
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float(&format!(
                                                                    "{} Voice Lowpass",
                                                                    voice_name
                                                                )),
                                                                setter,
                                                            )
                                                            .with_label("Filter")
                                                            .with_style(&knob_style_4),
                                                        );
                                                    });
                                                    ui.horizontal(|ui| {
                                                        ui.add_space(center_offset_x-35.0);
                                                        ui.add(
                                                            ParamSlider::for_param(
                                                                params.param_float(&format!("{} Mix Pan", voice_name)),
                                                                setter,
                                                            )
                                                            .with_label("Pan")
                                                            .with_style(&slider_style_5)
                                                            .with_indicator_from_center()
                                                            .with_orientation(ParamSliderOrientation::Horizontal)
                                                            .with_length(140.0)
                                                            .with_label_width(70.0),
                                                        );
                                                    });
                                                    ui.horizontal(|ui|{
                                                        ui.add_space(center_offset_x);
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float(&format!(
                                                                    "{} Voice Transpose",
                                                                    voice_name
                                                                )),
                                                                setter,
                                                            )
                                                            .with_label("Tune")
                                                            .with_style(&knob_style_2)
                                                            .with_indicator_from_center(),
                                                        );
                                                    });
                                                    ui.vertical(|ui| {
                                                        ui.horizontal(|ui| {
                                                            ui.add_space(center_offset_x);
                                                            ui.add(Label::new("Echo").with_style(&subheading_style));
                                                        });
                                                        ui.horizontal(|ui| {
                                                            ui.add_space(center_offset_x);
                                                            ui.add(
                                                                ParamKnob::for_param(
                                                                    params.param_float(&format!("{} Voice Delay Amount", voice_name)),
                                                                    setter,
                                                                )
                                                                .with_label("Amount")
                                                                .with_style(&knob_style_1),
                                                            );
                                                        });
                                                        ui.horizontal(|ui| {
                                                            ui.add(
                                                                ParamKnob::for_param(
                                                                    params.param_float(&format!(
                                                                        "{} Voice Delay MS",
                                                                        voice_name
                                                                    )),
                                                                    setter,
                                                                )
                                                                .with_label("Delay")
                                                                .with_style(&knob_style_1),
                                                            );
                                                            ui.add(
                                                                ParamKnob::for_param(
                                                                    params.param_float(&format!(
                                                                        "{} Voice Delay Lowpass",
                                                                        voice_name
                                                                    )),
                                                                    setter,
                                                                )
                                                                .with_label("Filter")
                                                                .with_style(&knob_style_1),
                                                            );
                                                        });
                                                    });                                                });
                                                //ui.add(Separator::new().with_style(&separator_style));
                                                ui.add_space(40.0);
                                                ui.vertical(|ui|{
                                                    ui.horizontal(|ui| {
                                                        ui.add_space(center_offset_x);
                                                        ui.add(Label::new("Global").with_style(&subheading_style));
                                                    });
                                                    ui.horizontal(|ui|{
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float("Global Sensitivity"),
                                                                setter,
                                                            )
                                                            .with_label("Sensitivity")
                                                            .with_style(&knob_style),
                                                        );
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float("Global Mix Gain"),
                                                                setter,
                                                            )
                                                            .with_label("Volume")
                                                            .with_style(&knob_style_5),
                                                        );                                                        
                                                    });
                                                    ui.horizontal(|ui|{
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float("Global Mix Saturation"),
                                                                setter,
                                                            )
                                                            .with_label("Saturation")
                                                            .with_style(&knob_style_1),
                                                        );
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float("Global Transpose"),
                                                                setter,
                                                            )
                                                            .with_label("Transpose")
                                                            .with_style(&knob_style_2)
                                                            .with_indicator_from_center(),
                                                        );
                                                    });
                                                    ui.add_space(69.0);
                                                    ui.horizontal(|ui| {
                                                        ui.add_space(center_offset_x);
                                                        ui.add(Label::new("Filters").with_style(&subheading_style));
                                                    });
                                                    ui.horizontal(|ui|{
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float("Global Media Highpass"),
                                                                setter,
                                                            )
                                                            .with_label("Low Cut")
                                                            .with_style(&knob_style_4),
                                                        );
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float("Global Media Lowpass"),
                                                                setter,
                                                            )
                                                            .with_label("High Cut")
                                                            .with_style(&knob_style_4)
                                                            .with_indicator_from_end(),
                                                        );
                                                    });
                                                    ui.horizontal(|ui|{
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_float("Global Sampler Samplerate"),
                                                                setter,
                                                            )
                                                            .with_label("Samplerate")
                                                            .with_style(&knob_style_5),
                                                        );
                                                        ui.add(
                                                            ParamKnob::for_param(
                                                                params.param_int("Global Sampler Bits"),
                                                                setter,
                                                            )
                                                            .with_label("Bits")
                                                            .with_style(&knob_style_5)
                                                            .with_indicator_style(ParamKnobIndicatorStyle::Dots)
                                                            .with_indicator_count(13),
                                                        );
                                                    });
                                                });
                                            });
                                        });
                                    });
                                });
                            });
                        });
                    });
            },
        )
    }
}

impl ClapPlugin for OneTrickBBoi {
    const CLAP_ID: &'static str = "com.punklabs.onetrick.bboi";
    const CLAP_DESCRIPTION: Option<&'static str> = Some("A Drum Machine");
    const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
    const CLAP_SUPPORT_URL: Option<&'static str> = None;
    const CLAP_FEATURES: &'static [ClapFeature] = &[
        ClapFeature::Instrument,  //Plugin Category
        ClapFeature::DrumMachine, // Plugin Sub-Category
        ClapFeature::AudioEffect,   // Plugin Category
        ClapFeature::PitchShifter, // Plugin Sub-Category
        ClapFeature::Stereo,
    ]; // Audio Capabilities
       /*
       const CLAP_POLY_MODULATION_CONFIG: Option<PolyModulationConfig> = Some(PolyModulationConfig {
           // If the plugin's voice capacity changes at runtime (for instance, when switching to a
           // monophonic mode), then the plugin should inform the host in the `initialize()` function
           // as well as in the `process()` function if it changes at runtime using
           // `context.set_current_voice_capacity()`
           max_voice_capacity: NUM_VOICES,
           // This enables voice stacking in Bitwig.
           supports_overlapping_voices: true,
       });
       */
}

impl Vst3Plugin for OneTrickBBoi {
    const VST3_CLASS_ID: [u8; 16] = *b"OneTrick-BBOI   ";
    const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
        &[Vst3SubCategory::Instrument, Vst3SubCategory::Drum, Vst3SubCategory::Fx, Vst3SubCategory::PitchShift];
}

nih_export_clap!(OneTrickBBoi);
nih_export_vst3!(OneTrickBBoi);
