| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE examples.
 -    Copyright (c) 2022 - Raw Material Software Limited
 - 
 -    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:                  AudioPluginDemo
 -  version:               1.0.0
 -  vendor:                JUCE
 -  website:               http://juce.com
 -  description:           Synthesiser audio plugin.
 - 
 -  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_events, juce_graphics, juce_gui_basics, juce_gui_extra
 -  exporters:             xcode_mac, vs2017, vs2022, linux_make, xcode_iphone, androidstudio
 - 
 -  moduleFlags:           JUCE_STRICT_REFCOUNTEDPOINTER=1
 - 
 -  type:                  AudioProcessor
 -  mainClass:             JuceDemoPluginAudioProcessor
 - 
 -  useLocalCopy:          1
 - 
 -  pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn, pluginProducesMidiOut,
 -                         pluginEditorRequiresKeys
 -  extraPluginFormats:    AUv3
 - 
 -  END_JUCE_PIP_METADATA
 - 
 - *******************************************************************************/
 - 
 - #pragma once
 - 
 - 
 - //==============================================================================
 - /** A demo synth sound that's just a basic sine wave.. */
 - class SineWaveSound : public SynthesiserSound
 - {
 - public:
 -     SineWaveSound() {}
 - 
 -     bool appliesToNote (int /*midiNoteNumber*/) override    { return true; }
 -     bool appliesToChannel (int /*midiChannel*/) override    { return true; }
 - };
 - 
 - //==============================================================================
 - /** A simple demo synth voice that just plays a sine wave.. */
 - class SineWaveVoice   : public SynthesiserVoice
 - {
 - public:
 -     SineWaveVoice() {}
 - 
 -     bool canPlaySound (SynthesiserSound* sound) override
 -     {
 -         return dynamic_cast<SineWaveSound*> (sound) != nullptr;
 -     }
 - 
 -     void startNote (int midiNoteNumber, float velocity,
 -                     SynthesiserSound* /*sound*/,
 -                     int /*currentPitchWheelPosition*/) override
 -     {
 -         currentAngle = 0.0;
 -         level = velocity * 0.15;
 -         tailOff = 0.0;
 - 
 -         auto cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
 -         auto cyclesPerSample = cyclesPerSecond / getSampleRate();
 - 
 -         angleDelta = cyclesPerSample * MathConstants<double>::twoPi;
 -     }
 - 
 -     void stopNote (float /*velocity*/, bool allowTailOff) override
 -     {
 -         if (allowTailOff)
 -         {
 -             // start a tail-off by setting this flag. The render callback will pick up on
 -             // this and do a fade out, calling clearCurrentNote() when it's finished.
 - 
 -             if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
 -                                 // stopNote method could be called more than once.
 -                 tailOff = 1.0;
 -         }
 -         else
 -         {
 -             // we're being told to stop playing immediately, so reset everything..
 - 
 -             clearCurrentNote();
 -             angleDelta = 0.0;
 -         }
 -     }
 - 
 -     void pitchWheelMoved (int /*newValue*/) override
 -     {
 -         // not implemented for the purposes of this demo!
 -     }
 - 
 -     void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override
 -     {
 -         // not implemented for the purposes of this demo!
 -     }
 - 
 -     void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
 -     {
 -         if (angleDelta != 0.0)
 -         {
 -             if (tailOff > 0.0)
 -             {
 -                 while (--numSamples >= 0)
 -                 {
 -                     auto currentSample = (float) (sin (currentAngle) * level * tailOff);
 - 
 -                     for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
 -                         outputBuffer.addSample (i, startSample, currentSample);
 - 
 -                     currentAngle += angleDelta;
 -                     ++startSample;
 - 
 -                     tailOff *= 0.99;
 - 
 -                     if (tailOff <= 0.005)
 -                     {
 -                         // tells the synth that this voice has stopped
 -                         clearCurrentNote();
 - 
 -                         angleDelta = 0.0;
 -                         break;
 -                     }
 -                 }
 -             }
 -             else
 -             {
 -                 while (--numSamples >= 0)
 -                 {
 -                     auto currentSample = (float) (sin (currentAngle) * level);
 - 
 -                     for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
 -                         outputBuffer.addSample (i, startSample, currentSample);
 - 
 -                     currentAngle += angleDelta;
 -                     ++startSample;
 -                 }
 -             }
 -         }
 -     }
 - 
 -     using SynthesiserVoice::renderNextBlock;
 - 
 - private:
 -     double currentAngle = 0.0;
 -     double angleDelta   = 0.0;
 -     double level        = 0.0;
 -     double tailOff      = 0.0;
 - };
 - 
 - //==============================================================================
 - /** As the name suggest, this class does the actual audio processing. */
 - class JuceDemoPluginAudioProcessor  : public AudioProcessor
 - {
 - public:
 -     //==============================================================================
 -     JuceDemoPluginAudioProcessor()
 -         : AudioProcessor (getBusesProperties()),
 -           state (*this, nullptr, "state",
 -                  { std::make_unique<AudioParameterFloat> (ParameterID { "gain",  1 }, "Gain",           NormalisableRange<float> (0.0f, 1.0f), 0.9f),
 -                    std::make_unique<AudioParameterFloat> (ParameterID { "delay", 1 }, "Delay Feedback", NormalisableRange<float> (0.0f, 1.0f), 0.5f) })
 -     {
 -         // Add a sub-tree to store the state of our UI
 -         state.state.addChild ({ "uiState", { { "width",  400 }, { "height", 200 } }, {} }, -1, nullptr);
 - 
 -         initialiseSynth();
 -     }
 - 
 -     ~JuceDemoPluginAudioProcessor() override = default;
 - 
 -     //==============================================================================
 -     bool isBusesLayoutSupported (const BusesLayout& layouts) const override
 -     {
 -         // Only mono/stereo and input/output must have same layout
 -         const auto& mainOutput = layouts.getMainOutputChannelSet();
 -         const auto& mainInput  = layouts.getMainInputChannelSet();
 - 
 -         // input and output layout must either be the same or the input must be disabled altogether
 -         if (! mainInput.isDisabled() && mainInput != mainOutput)
 -             return false;
 - 
 -         // only allow stereo and mono
 -         if (mainOutput.size() > 2)
 -             return false;
 - 
 -         return true;
 -     }
 - 
 -     void prepareToPlay (double newSampleRate, int /*samplesPerBlock*/) override
 -     {
 -         // Use this method as the place to do any pre-playback
 -         // initialisation that you need..
 -         synth.setCurrentPlaybackSampleRate (newSampleRate);
 -         keyboardState.reset();
 - 
 -         if (isUsingDoublePrecision())
 -         {
 -             delayBufferDouble.setSize (2, 12000);
 -             delayBufferFloat .setSize (1, 1);
 -         }
 -         else
 -         {
 -             delayBufferFloat .setSize (2, 12000);
 -             delayBufferDouble.setSize (1, 1);
 -         }
 - 
 -         reset();
 -     }
 - 
 -     void releaseResources() override
 -     {
 -         // When playback stops, you can use this as an opportunity to free up any
 -         // spare memory, etc.
 -         keyboardState.reset();
 -     }
 - 
 -     void reset() override
 -     {
 -         // Use this method as the place to clear any delay lines, buffers, etc, as it
 -         // means there's been a break in the audio's continuity.
 -         delayBufferFloat .clear();
 -         delayBufferDouble.clear();
 -     }
 - 
 -     //==============================================================================
 -     void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
 -     {
 -         jassert (! isUsingDoublePrecision());
 -         process (buffer, midiMessages, delayBufferFloat);
 -     }
 - 
 -     void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
 -     {
 -         jassert (isUsingDoublePrecision());
 -         process (buffer, midiMessages, delayBufferDouble);
 -     }
 - 
 -     //==============================================================================
 -     bool hasEditor() const override                                   { return true; }
 - 
 -     AudioProcessorEditor* createEditor() override
 -     {
 -         return new JuceDemoPluginAudioProcessorEditor (*this);
 -     }
 - 
 -     //==============================================================================
 -     const String getName() const override                             { return "AudioPluginDemo"; }
 -     bool acceptsMidi() const override                                 { return true; }
 -     bool producesMidi() const override                                { return true; }
 -     double getTailLengthSeconds() const override                      { return 0.0; }
 - 
 -     //==============================================================================
 -     int getNumPrograms() override                                     { return 0; }
 -     int getCurrentProgram() override                                  { return 0; }
 -     void setCurrentProgram (int) override                             {}
 -     const String getProgramName (int) override                        { return "None"; }
 -     void changeProgramName (int, const String&) override              {}
 - 
 -     //==============================================================================
 -     void getStateInformation (MemoryBlock& destData) override
 -     {
 -         // Store an xml representation of our state.
 -         if (auto xmlState = state.copyState().createXml())
 -             copyXmlToBinary (*xmlState, destData);
 -     }
 - 
 -     void setStateInformation (const void* data, int sizeInBytes) override
 -     {
 -         // Restore our plug-in's state from the xml representation stored in the above
 -         // method.
 -         if (auto xmlState = getXmlFromBinary (data, sizeInBytes))
 -             state.replaceState (ValueTree::fromXml (*xmlState));
 -     }
 - 
 -     //==============================================================================
 -     void updateTrackProperties (const TrackProperties& properties) override
 -     {
 -         {
 -             const ScopedLock sl (trackPropertiesLock);
 -             trackProperties = properties;
 -         }
 - 
 -         MessageManager::callAsync ([this]
 -         {
 -             if (auto* editor = dynamic_cast<JuceDemoPluginAudioProcessorEditor*> (getActiveEditor()))
 -                  editor->updateTrackProperties();
 -         });
 -     }
 - 
 -     TrackProperties getTrackProperties() const
 -     {
 -         const ScopedLock sl (trackPropertiesLock);
 -         return trackProperties;
 -     }
 - 
 -     class SpinLockedPosInfo
 -     {
 -     public:
 -         // Wait-free, but setting new info may fail if the main thread is currently
 -         // calling `get`. This is unlikely to matter in practice because
 -         // we'll be calling `set` much more frequently than `get`.
 -         void set (const AudioPlayHead::PositionInfo& newInfo)
 -         {
 -             const juce::SpinLock::ScopedTryLockType lock (mutex);
 - 
 -             if (lock.isLocked())
 -                 info = newInfo;
 -         }
 - 
 -         AudioPlayHead::PositionInfo get() const noexcept
 -         {
 -             const juce::SpinLock::ScopedLockType lock (mutex);
 -             return info;
 -         }
 - 
 -     private:
 -         juce::SpinLock mutex;
 -         AudioPlayHead::PositionInfo info;
 -     };
 - 
 -     //==============================================================================
 -     // These properties are public so that our editor component can access them
 -     // A bit of a hacky way to do it, but it's only a demo! Obviously in your own
 -     // code you'll do this much more neatly..
 - 
 -     // this is kept up to date with the midi messages that arrive, and the UI component
 -     // registers with it so it can represent the incoming messages
 -     MidiKeyboardState keyboardState;
 - 
 -     // this keeps a copy of the last set of time info that was acquired during an audio
 -     // callback - the UI component will read this and display it.
 -     SpinLockedPosInfo lastPosInfo;
 - 
 -     // Our plug-in's current state
 -     AudioProcessorValueTreeState state;
 - 
 - private:
 -     //==============================================================================
 -     /** This is the editor component that our filter will display. */
 -     class JuceDemoPluginAudioProcessorEditor  : public AudioProcessorEditor,
 -                                                 private Timer,
 -                                                 private Value::Listener
 -     {
 -     public:
 -         JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor& owner)
 -             : AudioProcessorEditor (owner),
 -               midiKeyboard         (owner.keyboardState, MidiKeyboardComponent::horizontalKeyboard),
 -               gainAttachment       (owner.state, "gain",  gainSlider),
 -               delayAttachment      (owner.state, "delay", delaySlider)
 -         {
 -             // add some sliders..
 -             addAndMakeVisible (gainSlider);
 -             gainSlider.setSliderStyle (Slider::Rotary);
 - 
 -             addAndMakeVisible (delaySlider);
 -             delaySlider.setSliderStyle (Slider::Rotary);
 - 
 -             // add some labels for the sliders..
 -             gainLabel.attachToComponent (&gainSlider, false);
 -             gainLabel.setFont (Font (11.0f));
 - 
 -             delayLabel.attachToComponent (&delaySlider, false);
 -             delayLabel.setFont (Font (11.0f));
 - 
 -             // add the midi keyboard component..
 -             addAndMakeVisible (midiKeyboard);
 - 
 -             // add a label that will display the current timecode and status..
 -             addAndMakeVisible (timecodeDisplayLabel);
 -             timecodeDisplayLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 15.0f, Font::plain));
 - 
 -             // set resize limits for this plug-in
 -             setResizeLimits (400, 200, 1024, 700);
 -             setResizable (true, owner.wrapperType != wrapperType_AudioUnitv3);
 - 
 -             lastUIWidth .referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("width",  nullptr));
 -             lastUIHeight.referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("height", nullptr));
 - 
 -             // set our component's initial size to be the last one that was stored in the filter's settings
 -             setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
 - 
 -             lastUIWidth. addListener (this);
 -             lastUIHeight.addListener (this);
 - 
 -             updateTrackProperties();
 - 
 -             // start a timer which will keep our timecode display updated
 -             startTimerHz (30);
 -         }
 - 
 -         ~JuceDemoPluginAudioProcessorEditor() override {}
 - 
 -         //==============================================================================
 -         void paint (Graphics& g) override
 -         {
 -             g.setColour (backgroundColour);
 -             g.fillAll();
 -         }
 - 
 -         void resized() override
 -         {
 -             // This lays out our child components...
 - 
 -             auto r = getLocalBounds().reduced (8);
 - 
 -             timecodeDisplayLabel.setBounds (r.removeFromTop (26));
 -             midiKeyboard        .setBounds (r.removeFromBottom (70));
 - 
 -             r.removeFromTop (20);
 -             auto sliderArea = r.removeFromTop (60);
 -             gainSlider.setBounds  (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth() / 2)));
 -             delaySlider.setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth())));
 - 
 -             lastUIWidth  = getWidth();
 -             lastUIHeight = getHeight();
 -         }
 - 
 -         void timerCallback() override
 -         {
 -             updateTimecodeDisplay (getProcessor().lastPosInfo.get());
 -         }
 - 
 -         void hostMIDIControllerIsAvailable (bool controllerIsAvailable) override
 -         {
 -             midiKeyboard.setVisible (! controllerIsAvailable);
 -         }
 - 
 -         int getControlParameterIndex (Component& control) override
 -         {
 -             if (&control == &gainSlider)
 -                 return 0;
 - 
 -             if (&control == &delaySlider)
 -                 return 1;
 - 
 -             return -1;
 -         }
 - 
 -         void updateTrackProperties()
 -         {
 -             auto trackColour = getProcessor().getTrackProperties().colour;
 -             auto& lf = getLookAndFeel();
 - 
 -             backgroundColour = (trackColour == Colour() ? lf.findColour (ResizableWindow::backgroundColourId)
 -                                                         : trackColour.withAlpha (1.0f).withBrightness (0.266f));
 -             repaint();
 -         }
 - 
 -     private:
 -         MidiKeyboardComponent midiKeyboard;
 - 
 -         Label timecodeDisplayLabel,
 -               gainLabel  { {}, "Throughput level:" },
 -               delayLabel { {}, "Delay:" };
 - 
 -         Slider gainSlider, delaySlider;
 -         AudioProcessorValueTreeState::SliderAttachment gainAttachment, delayAttachment;
 -         Colour backgroundColour;
 - 
 -         // these are used to persist the UI's size - the values are stored along with the
 -         // filter's other parameters, and the UI component will update them when it gets
 -         // resized.
 -         Value lastUIWidth, lastUIHeight;
 - 
 -         //==============================================================================
 -         JuceDemoPluginAudioProcessor& getProcessor() const
 -         {
 -             return static_cast<JuceDemoPluginAudioProcessor&> (processor);
 -         }
 - 
 -         //==============================================================================
 -         // quick-and-dirty function to format a timecode string
 -         static String timeToTimecodeString (double seconds)
 -         {
 -             auto millisecs = roundToInt (seconds * 1000.0);
 -             auto absMillisecs = std::abs (millisecs);
 - 
 -             return String::formatted ("%02d:%02d:%02d.%03d",
 -                                       millisecs / 3600000,
 -                                       (absMillisecs / 60000) % 60,
 -                                       (absMillisecs / 1000)  % 60,
 -                                       absMillisecs % 1000);
 -         }
 - 
 -         // quick-and-dirty function to format a bars/beats string
 -         static String quarterNotePositionToBarsBeatsString (double quarterNotes, AudioPlayHead::TimeSignature sig)
 -         {
 -             if (sig.numerator == 0 || sig.denominator == 0)
 -                 return "1|1|000";
 - 
 -             auto quarterNotesPerBar = (sig.numerator * 4 / sig.denominator);
 -             auto beats  = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * sig.numerator;
 - 
 -             auto bar    = ((int) quarterNotes) / quarterNotesPerBar + 1;
 -             auto beat   = ((int) beats) + 1;
 -             auto ticks  = ((int) (fmod (beats, 1.0) * 960.0 + 0.5));
 - 
 -             return String::formatted ("%d|%d|%03d", bar, beat, ticks);
 -         }
 - 
 -         // Updates the text in our position label.
 -         void updateTimecodeDisplay (const AudioPlayHead::PositionInfo& pos)
 -         {
 -             MemoryOutputStream displayText;
 - 
 -             const auto sig = pos.getTimeSignature().orFallback (AudioPlayHead::TimeSignature{});
 - 
 -             displayText << "[" << SystemStats::getJUCEVersion() << "]   "
 -                         << String (pos.getBpm().orFallback (120.0), 2) << " bpm, "
 -                         << sig.numerator << '/' << sig.denominator
 -                         << "  -  " << timeToTimecodeString (pos.getTimeInSeconds().orFallback (0.0))
 -                         << "  -  " << quarterNotePositionToBarsBeatsString (pos.getPpqPosition().orFallback (0.0), sig);
 - 
 -             if (pos.getIsRecording())
 -                 displayText << "  (recording)";
 -             else if (pos.getIsPlaying())
 -                 displayText << "  (playing)";
 - 
 -             timecodeDisplayLabel.setText (displayText.toString(), dontSendNotification);
 -         }
 - 
 -         // called when the stored window size changes
 -         void valueChanged (Value&) override
 -         {
 -             setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
 -         }
 -     };
 - 
 -     //==============================================================================
 -     template <typename FloatType>
 -     void process (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioBuffer<FloatType>& delayBuffer)
 -     {
 -         auto gainParamValue  = state.getParameter ("gain") ->getValue();
 -         auto delayParamValue = state.getParameter ("delay")->getValue();
 -         auto numSamples = buffer.getNumSamples();
 - 
 -         // In case we have more outputs than inputs, we'll clear any output
 -         // channels that didn't contain input data, (because these aren't
 -         // guaranteed to be empty - they may contain garbage).
 -         for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
 -             buffer.clear (i, 0, numSamples);
 - 
 -         // Now pass any incoming midi messages to our keyboard state object, and let it
 -         // add messages to the buffer if the user is clicking on the on-screen keys
 -         keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
 - 
 -         // and now get our synth to process these midi events and generate its output.
 -         synth.renderNextBlock (buffer, midiMessages, 0, numSamples);
 - 
 -         // Apply our delay effect to the new output..
 -         applyDelay (buffer, delayBuffer, delayParamValue);
 - 
 -         // Apply our gain change to the outgoing data..
 -         applyGain (buffer, delayBuffer, gainParamValue);
 - 
 -         // Now ask the host for the current time so we can store it to be displayed later...
 -         updateCurrentTimeInfoFromHost();
 -     }
 - 
 -     template <typename FloatType>
 -     void applyGain (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float gainLevel)
 -     {
 -         ignoreUnused (delayBuffer);
 - 
 -         for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel)
 -             buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel);
 -     }
 - 
 -     template <typename FloatType>
 -     void applyDelay (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float delayLevel)
 -     {
 -         auto numSamples = buffer.getNumSamples();
 - 
 -         auto delayPos = 0;
 - 
 -         for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel)
 -         {
 -             auto channelData = buffer.getWritePointer (channel);
 -             auto delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1));
 -             delayPos = delayPosition;
 - 
 -             for (auto i = 0; i < numSamples; ++i)
 -             {
 -                 auto in = channelData[i];
 -                 channelData[i] += delayData[delayPos];
 -                 delayData[delayPos] = (delayData[delayPos] + in) * delayLevel;
 - 
 -                 if (++delayPos >= delayBuffer.getNumSamples())
 -                     delayPos = 0;
 -             }
 -         }
 - 
 -         delayPosition = delayPos;
 -     }
 - 
 -     AudioBuffer<float> delayBufferFloat;
 -     AudioBuffer<double> delayBufferDouble;
 - 
 -     int delayPosition = 0;
 - 
 -     Synthesiser synth;
 - 
 -     CriticalSection trackPropertiesLock;
 -     TrackProperties trackProperties;
 - 
 -     void initialiseSynth()
 -     {
 -         auto numVoices = 8;
 - 
 -         // Add some voices...
 -         for (auto i = 0; i < numVoices; ++i)
 -             synth.addVoice (new SineWaveVoice());
 - 
 -         // ..and give the synth a sound to play
 -         synth.addSound (new SineWaveSound());
 -     }
 - 
 -     void updateCurrentTimeInfoFromHost()
 -     {
 -         const auto newInfo = [&]
 -         {
 -             if (auto* ph = getPlayHead())
 -                 if (auto result = ph->getPosition())
 -                     return *result;
 - 
 -             // If the host fails to provide the current time, we'll just use default values
 -             return AudioPlayHead::PositionInfo{};
 -         }();
 - 
 -         lastPosInfo.set (newInfo);
 -     }
 - 
 -     static BusesProperties getBusesProperties()
 -     {
 -         return BusesProperties().withInput  ("Input",  AudioChannelSet::stereo(), true)
 -                                 .withOutput ("Output", AudioChannelSet::stereo(), true);
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceDemoPluginAudioProcessor)
 - };
 
 
  |