| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library - "Jules' Utility Class Extensions"
 -    Copyright 2004-12 by Raw Material Software Ltd.
 - 
 -   ------------------------------------------------------------------------------
 - 
 -    JUCE can be redistributed and/or modified under the terms of the GNU General
 -    Public License (Version 2), as published by the Free Software Foundation.
 -    A copy of the license is included in the JUCE distribution, or can be found
 -    online at www.gnu.org/licenses.
 - 
 -    JUCE 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.
 - 
 -   ------------------------------------------------------------------------------
 - 
 -    To release a closed-source product which uses JUCE, commercial licenses are
 -    available: visit www.rawmaterialsoftware.com/juce for more information.
 - 
 -   ==============================================================================
 - */
 - 
 - #include "../JuceDemoHeader.h"
 - #include "AudioLiveScrollingDisplay.h"
 - 
 - 
 - class LatencyTester  : public AudioIODeviceCallback,
 -                        private Timer
 - {
 - public:
 -     LatencyTester (TextEditor& resultsBox_)
 -         : testSound (1, 1),
 -           recordedSound (1, 1),
 -           playingSampleNum (0),
 -           recordedSampleNum (-1),
 -           sampleRate (0),
 -           testIsRunning (false),
 -           resultsBox (resultsBox_)
 -     {
 -         MainAppWindow::getSharedAudioDeviceManager().addAudioCallback (this);
 -     }
 - 
 -     ~LatencyTester()
 -     {
 -         MainAppWindow::getSharedAudioDeviceManager().removeAudioCallback (this);
 -     }
 - 
 -     //==============================================================================
 -     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()
 -     {
 -         if (testIsRunning && recordedSampleNum >= recordedSound.getNumSamples())
 -         {
 -             testIsRunning = false;
 -             stopTimer();
 - 
 -             // Test has finished, so calculate the result..
 -             const int 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)
 -     {
 -         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()
 -     {
 -         // (nothing to do here)
 -     }
 - 
 -     void audioDeviceIOCallback (const float** inputChannelData,
 -                                 int numInputChannels,
 -                                 float** outputChannelData,
 -                                 int numOutputChannels,
 -                                 int numSamples)
 -     {
 -         const ScopedLock sl (lock);
 - 
 -         if (testIsRunning)
 -         {
 -             float* const recordingBuffer = recordedSound.getSampleData (0, 0);
 -             const float* const playBuffer = testSound.getSampleData (0, 0);
 - 
 -             for (int i = 0; i < numSamples; ++i)
 -             {
 -                 if (recordedSampleNum < recordedSound.getNumSamples())
 -                 {
 -                     float inputSamp = 0;
 -                     for (int j = numInputChannels; --j >= 0;)
 -                         if (inputChannelData[j] != 0)
 -                             inputSamp += inputChannelData[j][i];
 - 
 -                     recordingBuffer [recordedSampleNum] = inputSamp;
 -                 }
 - 
 -                 ++recordedSampleNum;
 - 
 -                 float outputSamp = (playingSampleNum < testSound.getNumSamples()) ? playBuffer [playingSampleNum] : 0;
 - 
 -                 for (int j = numOutputChannels; --j >= 0;)
 -                     if (outputChannelData[j] != 0)
 -                         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] != 0)
 -                     zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples);
 -         }
 -     }
 - 
 - private:
 -     AudioSampleBuffer testSound, recordedSound;
 -     Array<int> spikePositions;
 -     int playingSampleNum, recordedSampleNum;
 -     CriticalSection lock;
 -     double sampleRate;
 -     bool testIsRunning;
 -     TextEditor& resultsBox;
 -     int deviceInputLatency, deviceOutputLatency;
 - 
 -     // create a test sound which consists of a series of randomly-spaced audio spikes..
 -     void createTestSound()
 -     {
 -         const int length = ((int) sampleRate) / 4;
 -         testSound.setSize (1, length);
 -         testSound.clear();
 -         float* s = testSound.getSampleData (0, 0);
 - 
 -         Random rand;
 - 
 -         for (int i = 0; i < length; ++i)
 -             s[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);
 - 
 -             s[spikePos] = 0.99f;
 -             s[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 AudioSampleBuffer& buffer) const
 -     {
 -         const float minSpikeLevel = 5.0f;
 -         const double smooth = 0.975;
 -         const float* s = buffer.getSampleData (0, 0);
 -         const int spikeDriftAllowed = 5;
 - 
 -         Array<int> spikesFound;
 -         spikesFound.ensureStorageAllocated (100);
 -         double runningAverage = 0;
 -         int lastSpike = 0;
 - 
 -         for (int i = 0; i < buffer.getNumSamples() - 10; ++i)
 -         {
 -             const float 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;
 -         int 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)
 -             {
 -                 const int 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..
 -         const int referenceStart = findOffsetOfSpikes (testSound);
 -         jassert (referenceStart >= 0);
 - 
 -         const int recordedStart = findOffsetOfSpikes (recordedSound);
 - 
 -         return (recordedStart < 0) ? -1
 -                                    : (recordedStart - referenceStart);
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LatencyTester);
 - };
 - 
 - //==============================================================================
 - class AudioLatencyDemo  : public Component,
 -                           private Button::Listener
 - {
 - public:
 -     AudioLatencyDemo()
 -     {
 -         setOpaque (true);
 - 
 -         addAndMakeVisible (liveAudioScroller = new LiveScrollingAudioDisplay());
 - 
 -         addAndMakeVisible (resultsBox);
 -         resultsBox.setMultiLine (true);
 -         resultsBox.setReturnKeyStartsNewLine (true);
 -         resultsBox.setReadOnly (true);
 -         resultsBox.setScrollbarsShown (true);
 -         resultsBox.setCaretVisible (false);
 -         resultsBox.setPopupMenuEnabled (true);
 -         resultsBox.setColour (TextEditor::backgroundColourId, Colour (0x32ffffff));
 -         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.addListener (this);
 -         startTestButton.setButtonText ("Test Latency");
 - 
 -         MainAppWindow::getSharedAudioDeviceManager().addAudioCallback (liveAudioScroller);
 -     }
 - 
 -     ~AudioLatencyDemo()
 -     {
 -         MainAppWindow::getSharedAudioDeviceManager().removeAudioCallback (liveAudioScroller);
 -         startTestButton.removeListener (this);
 -         latencyTester = nullptr;
 -         liveAudioScroller = nullptr;
 -     }
 - 
 -     void startTest()
 -     {
 -         if (latencyTester == nullptr)
 -             latencyTester = new LatencyTester (resultsBox);
 - 
 -         latencyTester->beginTest();
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         fillBrushedAluminiumBackground (g);
 -     }
 - 
 -     void resized() override
 -     {
 -         liveAudioScroller->setBounds (8, 8, getWidth() - 16, 64);
 -         startTestButton.setBounds (8, getHeight() - 41, 168, 32);
 -         resultsBox.setBounds (8, 88, getWidth() - 16, getHeight() - 137);
 -     }
 - 
 - private:
 -     ScopedPointer<LatencyTester> latencyTester;
 - 
 -     ScopedPointer<LiveScrollingAudioDisplay> liveAudioScroller;
 -     TextButton startTestButton;
 -     TextEditor resultsBox;
 - 
 -     void buttonClicked (Button* buttonThatWasClicked) override
 -     {
 -         if (buttonThatWasClicked == &startTestButton)
 -             startTest();
 -     }
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioLatencyDemo)
 - };
 - 
 - 
 - // This static object will register this demo type in a global list of demos..
 - static JuceDemoType<AudioLatencyDemo> demo ("31 Audio: Latency Detector");
 
 
  |