| 
							- /*
 -   ==============================================================================
 - 
 -    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:             AudioLatencyDemo
 -  version:          1.0.0
 -  vendor:           JUCE
 -  website:          http://juce.com
 -  description:      Tests the audio latency of a device.
 - 
 -  dependencies:     juce_audio_basics, juce_audio_devices, juce_audio_formats,
 -                    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, linux_make, androidstudio, xcode_iphone
 - 
 -  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
 - 
 -  type:             Component
 -  mainClass:        AudioLatencyDemo
 - 
 -  useLocalCopy:     1
 - 
 -  END_JUCE_PIP_METADATA
 - 
 - *******************************************************************************/
 - 
 - #pragma once
 - 
 - #include "../Assets/DemoUtilities.h"
 - #include "../Assets/AudioLiveScrollingDisplay.h"
 - 
 - //==============================================================================
 - class LatencyTester  : public AudioIODeviceCallback,
 -                        private Timer
 - {
 - public:
 -     LatencyTester (TextEditor& editorBox)
 -         : resultsBox (editorBox)
 -     {}
 - 
 -     //==============================================================================
 -     void beginTest()
 -     {
 -         resultsBox.moveCaretToEnd();
 -         resultsBox.insertTextAtCaret (newLine + newLine + "Starting test..." + newLine);
 -         resultsBox.moveCaretToEnd();
 - 
 -         startTimer (50);
 - 
 -         const ScopedLock sl (lock);
 -         createTestSound();
 -         recordedSound.clear();
 -         playingSampleNum = recordedSampleNum = 0;
 -         testIsRunning = true;
 -     }
 - 
 -     void timerCallback() override
 -     {
 -         if (testIsRunning && recordedSampleNum >= recordedSound.getNumSamples())
 -         {
 -             testIsRunning = false;
 -             stopTimer();
 - 
 -             // Test has finished, so calculate the result..
 -             auto latencySamples = calculateLatencySamples();
 - 
 -             resultsBox.moveCaretToEnd();
 -             resultsBox.insertTextAtCaret (getMessageDescribingResult (latencySamples));
 -             resultsBox.moveCaretToEnd();
 -         }
 -     }
 - 
 -     String getMessageDescribingResult (int latencySamples)
 -     {
 -         String message;
 - 
 -         if (latencySamples >= 0)
 -         {
 -             message << newLine
 -                     << "Results:" << newLine
 -                     << latencySamples << " samples (" << String (latencySamples * 1000.0 / sampleRate, 1)
 -                     << " milliseconds)" << newLine
 -                     << "The audio device reports an input latency of "
 -                     << deviceInputLatency << " samples, output latency of "
 -                     << deviceOutputLatency << " samples." << newLine
 -                     << "So the corrected latency = "
 -                     << (latencySamples - deviceInputLatency - deviceOutputLatency)
 -                     << " samples (" << String ((latencySamples - deviceInputLatency - deviceOutputLatency) * 1000.0 / sampleRate, 2)
 -                     << " milliseconds)";
 -         }
 -         else
 -         {
 -             message << newLine
 -                     << "Couldn't detect the test signal!!" << newLine
 -                     << "Make sure there's no background noise that might be confusing it..";
 -         }
 - 
 -         return message;
 -     }
 - 
 -     //==============================================================================
 -     void audioDeviceAboutToStart (AudioIODevice* device) override
 -     {
 -         testIsRunning = false;
 -         playingSampleNum = recordedSampleNum = 0;
 - 
 -         sampleRate          = device->getCurrentSampleRate();
 -         deviceInputLatency  = device->getInputLatencyInSamples();
 -         deviceOutputLatency = device->getOutputLatencyInSamples();
 - 
 -         recordedSound.setSize (1, (int) (0.9 * sampleRate));
 -         recordedSound.clear();
 -     }
 - 
 -     void audioDeviceStopped() override {}
 - 
 -     void audioDeviceIOCallback (const float** inputChannelData, int numInputChannels,
 -                                 float** outputChannelData, int numOutputChannels, int numSamples) override
 -     {
 -         const ScopedLock sl (lock);
 - 
 -         if (testIsRunning)
 -         {
 -             auto* recordingBuffer = recordedSound.getWritePointer (0);
 -             auto* playBuffer = testSound.getReadPointer (0);
 - 
 -             for (int i = 0; i < numSamples; ++i)
 -             {
 -                 if (recordedSampleNum < recordedSound.getNumSamples())
 -                 {
 -                     auto inputSamp = 0.0f;
 - 
 -                     for (auto j = numInputChannels; --j >= 0;)
 -                         if (inputChannelData[j] != nullptr)
 -                             inputSamp += inputChannelData[j][i];
 - 
 -                     recordingBuffer[recordedSampleNum] = inputSamp;
 -                 }
 - 
 -                 ++recordedSampleNum;
 - 
 -                 auto outputSamp = (playingSampleNum < testSound.getNumSamples()) ? playBuffer[playingSampleNum] : 0.0f;
 - 
 -                 for (auto j = numOutputChannels; --j >= 0;)
 -                     if (outputChannelData[j] != nullptr)
 -                         outputChannelData[j][i] = outputSamp;
 - 
 -                 ++playingSampleNum;
 -             }
 -         }
 -         else
 -         {
 -             // We need to clear the output buffers, in case they're full of junk..
 -             for (int i = 0; i < numOutputChannels; ++i)
 -                 if (outputChannelData[i] != nullptr)
 -                     zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples);
 -         }
 -     }
 - 
 - private:
 -     TextEditor& resultsBox;
 -     AudioBuffer<float> testSound, recordedSound;
 -     Array<int> spikePositions;
 -     CriticalSection lock;
 - 
 -     int playingSampleNum  = 0;
 -     int recordedSampleNum = -1;
 -     double sampleRate     = 0.0;
 -     bool testIsRunning    = false;
 -     int deviceInputLatency, deviceOutputLatency;
 - 
 -     //==============================================================================
 -     // create a test sound which consists of a series of randomly-spaced audio spikes..
 -     void createTestSound()
 -     {
 -         auto length = ((int) sampleRate) / 4;
 -         testSound.setSize (1, length);
 -         testSound.clear();
 - 
 -         Random rand;
 - 
 -         for (int i = 0; i < length; ++i)
 -             testSound.setSample (0, i, (rand.nextFloat() - rand.nextFloat() + rand.nextFloat() - rand.nextFloat()) * 0.06f);
 - 
 -         spikePositions.clear();
 - 
 -         int spikePos   = 0;
 -         int spikeDelta = 50;
 - 
 -         while (spikePos < length - 1)
 -         {
 -             spikePositions.add (spikePos);
 - 
 -             testSound.setSample (0, spikePos,      0.99f);
 -             testSound.setSample (0, spikePos + 1, -0.99f);
 - 
 -             spikePos += spikeDelta;
 -             spikeDelta += spikeDelta / 6 + rand.nextInt (5);
 -         }
 -     }
 - 
 -     // Searches a buffer for a set of spikes that matches those in the test sound
 -     int findOffsetOfSpikes (const AudioBuffer<float>& buffer) const
 -     {
 -         auto minSpikeLevel = 5.0f;
 -         auto smooth = 0.975;
 -         auto* s = buffer.getReadPointer (0);
 -         int spikeDriftAllowed = 5;
 - 
 -         Array<int> spikesFound;
 -         spikesFound.ensureStorageAllocated (100);
 -         auto runningAverage = 0.0;
 -         int lastSpike = 0;
 - 
 -         for (int i = 0; i < buffer.getNumSamples() - 10; ++i)
 -         {
 -             auto samp = std::abs (s[i]);
 - 
 -             if (samp > runningAverage * minSpikeLevel && i > lastSpike + 20)
 -             {
 -                 lastSpike = i;
 -                 spikesFound.add (i);
 -             }
 - 
 -             runningAverage = runningAverage * smooth + (1.0 - smooth) * samp;
 -         }
 - 
 -         int bestMatch = -1;
 -         auto bestNumMatches = spikePositions.size() / 3; // the minimum number of matches required
 - 
 -         if (spikesFound.size() < bestNumMatches)
 -             return -1;
 - 
 -         for (int offsetToTest = 0; offsetToTest < buffer.getNumSamples() - 2048; ++offsetToTest)
 -         {
 -             int numMatchesHere = 0;
 -             int foundIndex     = 0;
 - 
 -             for (int refIndex = 0; refIndex < spikePositions.size(); ++refIndex)
 -             {
 -                 auto referenceSpike = spikePositions.getUnchecked (refIndex) + offsetToTest;
 -                 int spike = 0;
 - 
 -                 while ((spike = spikesFound.getUnchecked (foundIndex)) < referenceSpike - spikeDriftAllowed
 -                          && foundIndex < spikesFound.size() - 1)
 -                     ++foundIndex;
 - 
 -                 if (spike >= referenceSpike - spikeDriftAllowed && spike <= referenceSpike + spikeDriftAllowed)
 -                     ++numMatchesHere;
 -             }
 - 
 -             if (numMatchesHere > bestNumMatches)
 -             {
 -                 bestNumMatches = numMatchesHere;
 -                 bestMatch = offsetToTest;
 - 
 -                 if (numMatchesHere == spikePositions.size())
 -                     break;
 -             }
 -         }
 - 
 -         return bestMatch;
 -     }
 - 
 -     int calculateLatencySamples() const
 -     {
 -         // Detect the sound in both our test sound and the recording of it, and measure the difference
 -         // in their start times..
 -         auto referenceStart = findOffsetOfSpikes (testSound);
 -         jassert (referenceStart >= 0);
 - 
 -         auto recordedStart = findOffsetOfSpikes (recordedSound);
 - 
 -         return (recordedStart < 0) ? -1
 -                                    : (recordedStart - referenceStart);
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LatencyTester)
 - };
 - 
 - //==============================================================================
 - class AudioLatencyDemo  : public Component
 - {
 - public:
 -     AudioLatencyDemo()
 -     {
 -         setOpaque (true);
 - 
 -         liveAudioScroller.reset (new LiveScrollingAudioDisplay());
 -         addAndMakeVisible (liveAudioScroller.get());
 - 
 -         addAndMakeVisible (resultsBox);
 -         resultsBox.setMultiLine (true);
 -         resultsBox.setReturnKeyStartsNewLine (true);
 -         resultsBox.setReadOnly (true);
 -         resultsBox.setScrollbarsShown (true);
 -         resultsBox.setCaretVisible (false);
 -         resultsBox.setPopupMenuEnabled (true);
 - 
 -         resultsBox.setColour (TextEditor::outlineColourId, Colour (0x1c000000));
 -         resultsBox.setColour (TextEditor::shadowColourId,  Colour (0x16000000));
 - 
 -         resultsBox.setText ("Running this test measures the round-trip latency between the audio output and input "
 -                             "devices you\'ve got selected.\n\n"
 -                             "It\'ll play a sound, then try to measure the time at which the sound arrives "
 -                             "back at the audio input. Obviously for this to work you need to have your "
 -                             "microphone somewhere near your speakers...");
 - 
 -         addAndMakeVisible (startTestButton);
 -         startTestButton.onClick = [this] { startTest(); };
 - 
 -        #ifndef JUCE_DEMO_RUNNER
 -         RuntimePermissions::request (RuntimePermissions::recordAudio,
 -                                      [this] (bool granted)
 -                                      {
 -                                          int numInputChannels = granted ? 2 : 0;
 -                                          audioDeviceManager.initialise (numInputChannels, 2, nullptr, true, {}, nullptr);
 -                                      });
 -        #endif
 - 
 -         audioDeviceManager.addAudioCallback (liveAudioScroller.get());
 - 
 -         setSize (500, 500);
 -     }
 - 
 -     ~AudioLatencyDemo()
 -     {
 -         audioDeviceManager.removeAudioCallback (liveAudioScroller.get());
 -         audioDeviceManager.removeAudioCallback (latencyTester    .get());
 -         latencyTester    .reset();
 -         liveAudioScroller.reset();
 -     }
 - 
 -     void startTest()
 -     {
 -         if (latencyTester.get() == nullptr)
 -         {
 -             latencyTester.reset (new LatencyTester (resultsBox));
 -             audioDeviceManager.addAudioCallback (latencyTester.get());
 -         }
 - 
 -         latencyTester->beginTest();
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         g.fillAll (findColour (ResizableWindow::backgroundColourId));
 -     }
 - 
 -     void resized() override
 -     {
 -         auto b = getLocalBounds().reduced (5);
 - 
 -         if (liveAudioScroller.get() != nullptr)
 -         {
 -             liveAudioScroller->setBounds (b.removeFromTop (b.getHeight() / 5));
 -             b.removeFromTop (10);
 -         }
 - 
 -         startTestButton.setBounds (b.removeFromBottom (b.getHeight() / 10));
 -         b.removeFromBottom (10);
 - 
 -         resultsBox.setBounds (b);
 -     }
 - 
 - private:
 -     // if this PIP is running inside the demo runner, we'll use the shared device manager instead
 -    #ifndef JUCE_DEMO_RUNNER
 -     AudioDeviceManager audioDeviceManager;
 -    #else
 -     AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (1, 2) };
 -    #endif
 - 
 -     std::unique_ptr<LatencyTester> latencyTester;
 -     std::unique_ptr<LiveScrollingAudioDisplay> liveAudioScroller;
 - 
 -     TextButton startTestButton  { "Test Latency" };
 -     TextEditor resultsBox;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioLatencyDemo)
 - };
 
 
  |