text: /*
    ==============================================================================
  
     This file is part of the JUCE examples.
     Copyright (c) 2017 - ROLI Ltd.
  
     The code included in this file is provided under the terms of the ISC license
     http://www.isc.org/downloads/software-support-policy/isc-license. Permission
     To use, copy, modify, and/or distribute this software for any purpose with or
     without fee is hereby granted provided that the above copyright notice and
     this permission notice appear in all copies.
  
     THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
     WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
     PURPOSE, ARE DISCLAIMED.
  
    ==============================================================================
  */
  
  /*******************************************************************************
   The block below describes the properties of this PIP. A PIP is a short snippet
   of code that can be read by the Projucer and used to generate a JUCE project.
  
   BEGIN_JUCE_PIP_METADATA
  
   name:             DSPModulePluginDemo
   version:          1.0.0
   vendor:           JUCE
   website:          http://juce.com
   description:      Audio plugin using the DSP module.
  
   dependencies:     juce_audio_basics, juce_audio_devices, juce_audio_formats,
                     juce_audio_plugin_client, juce_audio_processors,
                     juce_audio_utils, juce_core, juce_data_structures, juce_dsp,
                     juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
   exporters:        xcode_mac, vs2019
  
   moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
  
   type:             AudioProcessor
   mainClass:        DspModulePluginDemoAudioProcessor
  
   useLocalCopy:     1
  
   END_JUCE_PIP_METADATA
  
  *******************************************************************************/
  
  #pragma once
  
  #include "../Assets/DemoUtilities.h"
  
  //==============================================================================
  struct ParameterSlider    : public Slider,
                              public Timer
  {
      ParameterSlider (AudioProcessorParameter& p)
          : Slider (p.getName (256)), param (p)
      {
          setRange (0.0, 1.0, 0.0);
          startTimerHz (30);
          updateSliderPos();
      }
  
      void valueChanged() override
      {
          if (isMouseButtonDown())
              param.setValueNotifyingHost ((float) Slider::getValue());
          else
              param.setValue ((float) Slider::getValue());
      }
  
      void timerCallback() override       { updateSliderPos(); }
  
      void startedDragging() override     { param.beginChangeGesture(); }
      void stoppedDragging() override     { param.endChangeGesture();   }
  
      double getValueFromText (const String& text) override   { return param.getValueForText (text); }
      String getTextFromValue (double value) override         { return param.getText ((float) value, 1024) + " " + param.getLabel(); }
  
      void updateSliderPos()
      {
          auto newValue = param.getValue();
  
          if (newValue != (float) Slider::getValue() && ! isMouseButtonDown())
              Slider::setValue (newValue);
      }
  
      AudioProcessorParameter& param;
  };
  
  //==============================================================================
  /**
      This class handles the audio processing for the DSP module plugin demo.
  */
  class DspModulePluginDemoAudioProcessor  : public AudioProcessor
  {
  public:
      //==============================================================================
      DspModulePluginDemoAudioProcessor()
           : AudioProcessor (BusesProperties().withInput  ("Input",  AudioChannelSet::stereo(), true)
                                              .withOutput ("Output", AudioChannelSet::stereo(), true)),
             lowPassFilter  (dsp::IIR::Coefficients<float>::makeFirstOrderLowPass  (48000.0, 20000.0f)),
             highPassFilter (dsp::IIR::Coefficients<float>::makeFirstOrderHighPass (48000.0, 20.0f)),
             waveShapers    { { std::tanh }, { dsp::FastMathApproximations::tanh } },
             clipping       { clip }
      {
          // Oversampling 2 times with IIR filtering
          oversampling.reset (new dsp::Oversampling<float> (2, 1, dsp::Oversampling<float>::filterHalfBandPolyphaseIIR, false));
  
          addParameter (inputVolumeParam        = new AudioParameterFloat  ("INPUT",   "Input Volume",       { 0.0f,  60.0f,    0.0f, 1.0f }, 0.0f,     "dB"));
          addParameter (highPassFilterFreqParam = new AudioParameterFloat  ("HPFREQ",  "Pre Highpass Freq.", { 20.0f, 20000.0f, 0.0f, 0.5f }, 20.0f,    "Hz"));
          addParameter (lowPassFilterFreqParam  = new AudioParameterFloat  ("LPFREQ",  "Post Lowpass Freq.", { 20.0f, 20000.0f, 0.0f, 0.5f }, 20000.0f, "Hz"));
  
          addParameter (stereoParam             = new AudioParameterChoice ("STEREO",  "Stereo Processing",  { "Always mono", "Yes" },                 1));
          addParameter (slopeParam              = new AudioParameterChoice ("SLOPE",   "Slope",              { "-6 dB / octave", "-12 dB / octave" },  0));
          addParameter (waveshaperParam         = new AudioParameterChoice ("WVSHP",   "Waveshaper",         { "std::tanh", "Fast tanh approx." },     0));
  
          addParameter (cabinetTypeParam        = new AudioParameterChoice ("CABTYPE", "Cabinet Type",       { "Guitar amplifier 8'' cabinet ",
                                                                                                               "Cassette recorder cabinet" },          0));
  
          addParameter (cabinetSimParam         = new AudioParameterBool   ("CABSIM",  "Cabinet Sim",  false));
          addParameter (oversamplingParam       = new AudioParameterBool   ("OVERS",   "Oversampling", false));
  
          addParameter (outputVolumeParam       = new AudioParameterFloat  ("OUTPUT",  "Output Volume",      { -40.0f, 40.0f, 0.0f, 1.0f }, 0.0f, "dB"));
  
          cabinetType.set (0);
      }
  
      ~DspModulePluginDemoAudioProcessor() {}
  
      //==============================================================================
      bool isBusesLayoutSupported (const BusesLayout& layouts) const override
      {
          // This is the place where you check if the layout is supported.
          // In this template code we only support mono or stereo.
          if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
              return false;
  
          // This checks if the input layout matches the output layout
          if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
              return false;
  
          return true;
      }
  
      void prepareToPlay (double sampleRate, int samplesPerBlock) override
      {
          auto channels = static_cast<uint32> (jmin (getMainBusNumInputChannels(), getMainBusNumOutputChannels()));
          dsp::ProcessSpec spec { sampleRate, static_cast<uint32> (samplesPerBlock), channels };
  
          lowPassFilter .prepare (spec);
          highPassFilter.prepare (spec);
  
          inputVolume .prepare (spec);
          outputVolume.prepare (spec);
  
          convolution.prepare (spec);
          cabinetType.set (-1);
  
          oversampling->initProcessing (static_cast<size_t> (samplesPerBlock));
  
          updateParameters();
          reset();
      }
  
      void releaseResources() override {}
  
      void processBlock (AudioBuffer<float>& inoutBuffer, MidiBuffer&) override
      {
          auto totalNumInputChannels  = getTotalNumInputChannels();
          auto totalNumOutputChannels = getTotalNumOutputChannels();
  
          auto numSamples = inoutBuffer.getNumSamples();
  
          for (auto i = jmin (2, totalNumInputChannels); i < totalNumOutputChannels; ++i)
              inoutBuffer.clear (i, 0, numSamples);
  
          updateParameters();
  
          dsp::AudioBlock<float> block (inoutBuffer);
  
          if (stereoParam->getIndex() == 1)
          {
              // Stereo processing mode:
              if (block.getNumChannels() > 2)
                  block = block.getSubsetChannelBlock (0, 2);
  
              process (dsp::ProcessContextReplacing<float> (block));
          }
          else
          {
              // Mono processing mode:
              auto firstChan = block.getSingleChannelBlock (0);
  
              process (dsp::ProcessContextReplacing<float> (firstChan));
  
              for (size_t chan = 1; chan < block.getNumChannels(); ++chan)
                  block.getSingleChannelBlock (chan).copy (firstChan);
          }
      }
  
      void reset() override
      {
          lowPassFilter .reset();
          highPassFilter.reset();
          convolution   .reset();
          oversampling->reset();
      }
  
      //==============================================================================
      bool hasEditor() const override                                       { return true; }
  
      AudioProcessorEditor* createEditor() override
      {
          return new DspModulePluginDemoAudioProcessorEditor (*this);
      }
  
      //==============================================================================
      bool acceptsMidi() const override                                     { return false; }
      bool producesMidi() const override                                    { return false; }
      const String getName() const override                                 { return JucePlugin_Name; }
      double getTailLengthSeconds() const override                          { return 0.0; }
  
      //==============================================================================
      int getNumPrograms() override                                         { return 1; }
      int getCurrentProgram() override                                      { return 0; }
      void setCurrentProgram (int) override                                 {}
      const String getProgramName (int) override                            { return {}; }
      void changeProgramName (int, const String&) override                  {}
  
      //==============================================================================
      void getStateInformation (MemoryBlock&) override                      {}
      void setStateInformation (const void*, int) override                  {}
  
      //==============================================================================
      void updateParameters()
      {
          auto newOversampling = oversamplingParam->get();
          if (newOversampling != audioCurrentlyOversampled)
          {
              audioCurrentlyOversampled = newOversampling;
              oversampling->reset();
          }
  
          //==============================================================================
          auto inputdB  = Decibels::decibelsToGain  (inputVolumeParam->get());
          auto outputdB = Decibels::decibelsToGain (outputVolumeParam->get());
  
          if (inputVolume .getGainLinear() != inputdB)     inputVolume.setGainLinear (inputdB);
          if (outputVolume.getGainLinear() != outputdB)   outputVolume.setGainLinear (outputdB);
  
          auto newSlopeType = slopeParam->getIndex();
  
          if (newSlopeType == 0)
          {
              *lowPassFilter .state = *dsp::IIR::Coefficients<float>::makeFirstOrderLowPass  (getSampleRate(),  lowPassFilterFreqParam->get());
              *highPassFilter.state = *dsp::IIR::Coefficients<float>::makeFirstOrderHighPass (getSampleRate(), highPassFilterFreqParam->get());
          }
          else
          {
              *lowPassFilter .state = *dsp::IIR::Coefficients<float>::makeLowPass  (getSampleRate(),  lowPassFilterFreqParam->get());
              *highPassFilter.state = *dsp::IIR::Coefficients<float>::makeHighPass (getSampleRate(), highPassFilterFreqParam->get());
          }
  
          //==============================================================================
          auto type = cabinetTypeParam->getIndex();
          auto currentType = cabinetType.get();
  
          if (type != currentType)
          {
              cabinetType.set (type);
  
              auto maxSize = static_cast<size_t> (roundToInt (getSampleRate() * (8192.0 / 44100.0)));
              auto assetName = (type == 0 ? "Impulse1.wav" : "Impulse2.wav");
  
              std::unique_ptr<InputStream> assetInputStream (createAssetInputStream (assetName));
  
              if (assetInputStream != nullptr)
              {
                  currentCabinetData.reset();
                  assetInputStream->readIntoMemoryBlock (currentCabinetData);
  
                  convolution.loadImpulseResponse (currentCabinetData.getData(), currentCabinetData.getSize(),
                                                   false, true, maxSize);
              }
          }
  
          cabinetIsBypassed = ! cabinetSimParam->get();
  
      }
  
      static inline float clip (float x) { return jmax (-1.0f, jmin (1.0f, x)); }
  
      //==============================================================================
      AudioParameterFloat* inputVolumeParam;
      AudioParameterFloat* outputVolumeParam;
      AudioParameterFloat* lowPassFilterFreqParam;
      AudioParameterFloat* highPassFilterFreqParam;
  
      AudioParameterChoice* stereoParam;
      AudioParameterChoice* slopeParam;
      AudioParameterChoice* waveshaperParam;
      AudioParameterChoice* cabinetTypeParam;
  
      AudioParameterBool* cabinetSimParam;
      AudioParameterBool* oversamplingParam;
  
  private:
      //==============================================================================
      /**
          This is the editor component that will be displayed.
      */
      class DspModulePluginDemoAudioProcessorEditor  : public AudioProcessorEditor
      {
      public:
          //==============================================================================
          DspModulePluginDemoAudioProcessorEditor (DspModulePluginDemoAudioProcessor& p)
              : AudioProcessorEditor    (&p),
                processor               (p),
                inputVolumeLabel        ({}, processor.inputVolumeParam->name),
                outputVolumeLabel       ({}, processor.outputVolumeParam->name),
                lowPassFilterFreqLabel  ({}, processor.lowPassFilterFreqParam->name),
                highPassFilterFreqLabel ({}, processor.highPassFilterFreqParam->name),
                stereoLabel             ({}, processor.stereoParam->name),
                slopeLabel              ({}, processor.slopeParam->name),
                waveshaperLabel         ({}, processor.waveshaperParam->name),
                cabinetTypeLabel        ({}, processor.cabinetTypeParam->name)
          {
              //==============================================================================
              inputVolumeSlider       .reset (new ParameterSlider (*processor.inputVolumeParam));
              outputVolumeSlider      .reset (new ParameterSlider (*processor.outputVolumeParam));
              lowPassFilterFreqSlider .reset (new ParameterSlider (*processor.lowPassFilterFreqParam));
              highPassFilterFreqSlider.reset (new ParameterSlider (*processor.highPassFilterFreqParam));
  
              addAndMakeVisible (inputVolumeSlider       .get());
              addAndMakeVisible (outputVolumeSlider      .get());
              addAndMakeVisible (lowPassFilterFreqSlider .get());
              addAndMakeVisible (highPassFilterFreqSlider.get());
  
              addAndMakeVisible (inputVolumeLabel);
              inputVolumeLabel.setJustificationType (Justification::centredLeft);
              inputVolumeLabel.attachToComponent (inputVolumeSlider.get(), true);
  
              addAndMakeVisible (outputVolumeLabel);
              outputVolumeLabel.setJustificationType (Justification::centredLeft);
              outputVolumeLabel.attachToComponent (outputVolumeSlider.get(), true);
  
              addAndMakeVisible (lowPassFilterFreqLabel);
              lowPassFilterFreqLabel.setJustificationType (Justification::centredLeft);
              lowPassFilterFreqLabel.attachToComponent (lowPassFilterFreqSlider.get(), true);
  
              addAndMakeVisible (highPassFilterFreqLabel);
              highPassFilterFreqLabel.setJustificationType (Justification::centredLeft);
              highPassFilterFreqLabel.attachToComponent (highPassFilterFreqSlider.get(), true);
  
              //==============================================================================
              addAndMakeVisible (stereoBox);
  
              auto i = 1;
              for (auto choice : processor.stereoParam->choices)
                  stereoBox.addItem (choice, i++);
  
              stereoBox.onChange = [this] { processor.stereoParam->operator= (stereoBox.getSelectedItemIndex()); };
              stereoBox.setSelectedId (processor.stereoParam->getIndex() + 1);
  
              addAndMakeVisible (stereoLabel);
              stereoLabel.setJustificationType (Justification::centredLeft);
              stereoLabel.attachToComponent (&stereoBox, true);
  
              //==============================================================================
              addAndMakeVisible(slopeBox);
  
              i = 1;
              for (auto choice : processor.slopeParam->choices)
                  slopeBox.addItem(choice, i++);
  
              slopeBox.onChange = [this] { processor.slopeParam->operator= (slopeBox.getSelectedItemIndex()); };
              slopeBox.setSelectedId(processor.slopeParam->getIndex() + 1);
  
              addAndMakeVisible(slopeLabel);
              slopeLabel.setJustificationType(Justification::centredLeft);
              slopeLabel.attachToComponent(&slopeBox, true);
  
              //==============================================================================
              addAndMakeVisible (waveshaperBox);
  
              i = 1;
              for (auto choice : processor.waveshaperParam->choices)
                  waveshaperBox.addItem (choice, i++);
  
              waveshaperBox.onChange = [this] { processor.waveshaperParam->operator= (waveshaperBox.getSelectedItemIndex()); };
              waveshaperBox.setSelectedId (processor.waveshaperParam->getIndex() + 1);
  
              addAndMakeVisible (waveshaperLabel);
              waveshaperLabel.setJustificationType (Justification::centredLeft);
              waveshaperLabel.attachToComponent (&waveshaperBox, true);
  
              //==============================================================================
              addAndMakeVisible (cabinetTypeBox);
  
              i = 1;
              for (auto choice : processor.cabinetTypeParam->choices)
                  cabinetTypeBox.addItem (choice, i++);
  
              cabinetTypeBox.onChange = [this] { processor.cabinetTypeParam->operator= (cabinetTypeBox.getSelectedItemIndex()); };
              cabinetTypeBox.setSelectedId (processor.cabinetTypeParam->getIndex() + 1);
  
              addAndMakeVisible (cabinetTypeLabel);
              cabinetTypeLabel.setJustificationType (Justification::centredLeft);
              cabinetTypeLabel.attachToComponent (&cabinetTypeBox, true);
  
              //==============================================================================
              addAndMakeVisible (cabinetSimButton);
              cabinetSimButton.onClick = [this] { processor.cabinetSimParam->operator= (cabinetSimButton.getToggleState()); };
              cabinetSimButton.setButtonText  (processor.cabinetSimParam->name);
              cabinetSimButton.setToggleState (processor.cabinetSimParam->get(), NotificationType::dontSendNotification);
  
              addAndMakeVisible (oversamplingButton);
              oversamplingButton.onClick = [this] { processor.oversamplingParam->operator= (oversamplingButton.getToggleState()); };
              oversamplingButton.setButtonText  (processor.oversamplingParam->name);
              oversamplingButton.setToggleState (processor.oversamplingParam->get(), NotificationType::dontSendNotification);
  
              //==============================================================================
              setSize (600, 400);
          }
  
          ~DspModulePluginDemoAudioProcessorEditor() {}
  
          //==============================================================================
          void paint (Graphics& g) override
          {
              g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
              g.fillAll();
          }
  
          void resized() override
          {
              auto bounds = getLocalBounds().reduced (10);
              bounds.removeFromTop (10);
              bounds.removeFromLeft (125);
  
              //==============================================================================
              inputVolumeSlider->setBounds (bounds.removeFromTop (30));
              bounds.removeFromTop (5);
  
              outputVolumeSlider->setBounds (bounds.removeFromTop (30));
              bounds.removeFromTop (15);
  
              highPassFilterFreqSlider->setBounds (bounds.removeFromTop (30));
              bounds.removeFromTop (5);
  
              lowPassFilterFreqSlider->setBounds (bounds.removeFromTop (30));
              bounds.removeFromTop (15);
  
              //==============================================================================
              stereoBox.setBounds (bounds.removeFromTop(30));
              bounds.removeFromTop (5);
  
              slopeBox.setBounds (bounds.removeFromTop (30));
              bounds.removeFromTop (5);
  
              waveshaperBox.setBounds (bounds.removeFromTop (30));
              bounds.removeFromTop (5);
  
              cabinetTypeBox.setBounds (bounds.removeFromTop (30));
              bounds.removeFromTop (15);
  
              //==============================================================================
              auto buttonSlice = bounds.removeFromTop (30);
              cabinetSimButton.setSize (200, buttonSlice.getHeight());
              cabinetSimButton.setCentrePosition (buttonSlice.getCentre());
              bounds.removeFromTop(5);
  
              buttonSlice = bounds.removeFromTop (30);
              oversamplingButton.setSize(200, buttonSlice.getHeight());
              oversamplingButton.setCentrePosition(buttonSlice.getCentre());
          }
  
      private:
          //==============================================================================
          DspModulePluginDemoAudioProcessor& processor;
  
          std::unique_ptr<ParameterSlider> inputVolumeSlider, outputVolumeSlider,
                                           lowPassFilterFreqSlider, highPassFilterFreqSlider;
          ComboBox stereoBox, slopeBox, waveshaperBox, cabinetTypeBox;
          ToggleButton cabinetSimButton, oversamplingButton;
  
          Label inputVolumeLabel, outputVolumeLabel, lowPassFilterFreqLabel,
                highPassFilterFreqLabel, stereoLabel, slopeLabel, waveshaperLabel,
                cabinetTypeLabel;
  
          JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DspModulePluginDemoAudioProcessorEditor)
      };
  
      //==============================================================================
      void process (dsp::ProcessContextReplacing<float> context) noexcept
      {
          ScopedNoDenormals noDenormals;
  
          // Input volume applied with a SmoothedValue
          inputVolume.process (context);
  
          // Pre-highpass filtering, very useful for distortion audio effects
          // Note : try frequencies around 700 Hz
          highPassFilter.process (context);
  
          // Upsampling
          dsp::AudioBlock<float> oversampledBlock;
  
          setLatencySamples (audioCurrentlyOversampled ? roundToInt (oversampling->getLatencyInSamples()) : 0);
  
          if (audioCurrentlyOversampled)
              oversampledBlock = oversampling->processSamplesUp (context.getOutputBlock());
  
          auto waveshaperContext = audioCurrentlyOversampled ? dsp::ProcessContextReplacing<float> (oversampledBlock)
                                                             : context;
  
          // Waveshaper processing, for distortion generation, thanks to the input gain
          // The fast tanh can be used instead of std::tanh to reduce the CPU load
          auto waveshaperIndex = waveshaperParam->getIndex();
  
          if (isPositiveAndBelow (waveshaperIndex, numWaveShapers) )
          {
              waveShapers[waveshaperIndex].process (waveshaperContext);
  
              if (waveshaperIndex == 1)
                  clipping.process (waveshaperContext);
  
              waveshaperContext.getOutputBlock() *= 0.7f;
          }
  
          // Downsampling
          if (audioCurrentlyOversampled)
              oversampling->processSamplesDown (context.getOutputBlock());
  
          // Post-lowpass filtering
          lowPassFilter.process (context);
  
          // Convolution with the impulse response of a guitar cabinet
          auto wasBypassed = context.isBypassed;
          context.isBypassed = context.isBypassed || cabinetIsBypassed;
          convolution.process (context);
          context.isBypassed = wasBypassed;
  
          // Output volume applied with a SmoothedValue
          outputVolume.process (context);
      }
  
      //==============================================================================
      dsp::ProcessorDuplicator<dsp::IIR::Filter<float>, dsp::IIR::Coefficients<float>> lowPassFilter, highPassFilter;
      dsp::Convolution convolution;
      MemoryBlock currentCabinetData;
  
      static constexpr size_t numWaveShapers = 2;
      dsp::WaveShaper<float> waveShapers[numWaveShapers];
      dsp::WaveShaper<float> clipping;
  
      dsp::Gain<float> inputVolume, outputVolume;
  
      std::unique_ptr<dsp::Oversampling<float>> oversampling;
      bool audioCurrentlyOversampled = false;
  
      Atomic<int> cabinetType;
      bool cabinetIsBypassed = false;
  
      //==============================================================================
      JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DspModulePluginDemoAudioProcessor)
  };
  