The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
15KB

  1. /*
  2. ==============================================================================
  3. This is an automatically generated GUI class created by the Introjucer!
  4. Be careful when adding custom code to these files, as only the code within
  5. the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded
  6. and re-saved.
  7. Created with Introjucer version: 3.1.0
  8. ------------------------------------------------------------------------------
  9. The Introjucer is part of the JUCE library - "Jules' Utility Class Extensions"
  10. Copyright 2004-13 by Raw Material Software Ltd.
  11. ==============================================================================
  12. */
  13. //[Headers] You can add your own extra header files here...
  14. //[/Headers]
  15. #include "AudioDemoLatencyPage.h"
  16. //[MiscUserDefs] You can add your own user definitions and misc code here...
  17. //==============================================================================
  18. class LatencyTester : public AudioIODeviceCallback,
  19. public Timer
  20. {
  21. public:
  22. LatencyTester (TextEditor* resultsBox_)
  23. : testSound (1, 1),
  24. recordedSound (1, 1),
  25. playingSampleNum (0),
  26. recordedSampleNum (-1),
  27. sampleRate (0),
  28. isRunning (false),
  29. resultsBox (resultsBox_),
  30. deviceInputLatency (0),
  31. deviceOutputLatency (0)
  32. {
  33. }
  34. //==============================================================================
  35. void beginTest()
  36. {
  37. startTimer (50);
  38. const ScopedLock sl (lock);
  39. createTestSound();
  40. recordedSound.clear();
  41. playingSampleNum = recordedSampleNum = 0;
  42. isRunning = true;
  43. }
  44. void timerCallback()
  45. {
  46. if (isRunning && recordedSampleNum >= recordedSound.getNumSamples())
  47. {
  48. isRunning = false;
  49. stopTimer();
  50. // Test has finished, so calculate the result..
  51. String message;
  52. const int latencySamples = calculateLatencySamples();
  53. if (latencySamples >= 0)
  54. {
  55. message << "\n\nLatency test results:\n"
  56. << latencySamples << " samples (" << String (latencySamples * 1000.0 / sampleRate, 1) << " milliseconds)\n"
  57. << "The audio device reports an input latency of "
  58. << deviceInputLatency << " samples, output latency of "
  59. << deviceOutputLatency << " samples."
  60. << "\nSo the corrected latency = "
  61. << (latencySamples - deviceInputLatency - deviceOutputLatency)
  62. << " samples (" << String ((latencySamples - deviceInputLatency - deviceOutputLatency) * 1000.0 / sampleRate, 2)
  63. << " milliseconds)";
  64. }
  65. else
  66. {
  67. message = "\n\nCouldn't detect the test signal!!\nMake sure there's no background noise that might be confusing it..";
  68. }
  69. resultsBox->setCaretPosition (INT_MAX);
  70. resultsBox->insertTextAtCaret (message);
  71. resultsBox->setCaretPosition (INT_MAX);
  72. }
  73. }
  74. //==============================================================================
  75. void audioDeviceAboutToStart (AudioIODevice* device)
  76. {
  77. isRunning = false;
  78. sampleRate = device->getCurrentSampleRate();
  79. deviceInputLatency = device->getInputLatencyInSamples();
  80. deviceOutputLatency = device->getOutputLatencyInSamples();
  81. playingSampleNum = recordedSampleNum = 0;
  82. recordedSound.setSize (1, (int) (1.5 * sampleRate));
  83. recordedSound.clear();
  84. }
  85. void audioDeviceStopped()
  86. {
  87. }
  88. void audioDeviceIOCallback (const float** inputChannelData,
  89. int numInputChannels,
  90. float** outputChannelData,
  91. int numOutputChannels,
  92. int numSamples)
  93. {
  94. const ScopedLock sl (lock);
  95. if (isRunning)
  96. {
  97. float* const recordingBuffer = recordedSound.getSampleData (0, 0);
  98. const float* const playBuffer = testSound.getSampleData (0, 0);
  99. for (int i = 0; i < numSamples; ++i)
  100. {
  101. if (recordedSampleNum < recordedSound.getNumSamples())
  102. {
  103. float inputSamp = 0;
  104. for (int j = numInputChannels; --j >= 0;)
  105. if (inputChannelData[j] != 0)
  106. inputSamp += inputChannelData[j][i];
  107. recordingBuffer [recordedSampleNum] = inputSamp;
  108. }
  109. ++recordedSampleNum;
  110. float outputSamp = (playingSampleNum < testSound.getNumSamples()) ? playBuffer [playingSampleNum] : 0;
  111. for (int j = numOutputChannels; --j >= 0;)
  112. if (outputChannelData[j] != 0)
  113. outputChannelData[j][i] = outputSamp;
  114. ++playingSampleNum;
  115. }
  116. }
  117. else
  118. {
  119. // We need to clear the output buffers, in case they're full of junk..
  120. for (int i = 0; i < numOutputChannels; ++i)
  121. if (outputChannelData[i] != 0)
  122. zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples);
  123. }
  124. }
  125. private:
  126. AudioSampleBuffer testSound, recordedSound;
  127. int playingSampleNum, recordedSampleNum;
  128. CriticalSection lock;
  129. double sampleRate;
  130. bool isRunning;
  131. TextEditor* resultsBox;
  132. int deviceInputLatency, deviceOutputLatency;
  133. Array <int> spikes;
  134. void createTestSound()
  135. {
  136. const int length = ((int) sampleRate) / 4;
  137. testSound.setSize (1, length);
  138. testSound.clear();
  139. float* s = testSound.getSampleData (0, 0);
  140. Random rand (0);
  141. rand.setSeedRandomly();
  142. for (int i = 0; i < length; ++i)
  143. s[i] = (rand.nextFloat() - rand.nextFloat() + rand.nextFloat() - rand.nextFloat()) * 0.06f;
  144. spikes.clear();
  145. int spikePos = 0;
  146. int spikeDelta = 50;
  147. while (spikePos < length)
  148. {
  149. spikes.add (spikePos);
  150. s [spikePos] = 0.99f;
  151. s [spikePos + 1] = -0.99f;
  152. spikePos += spikeDelta;
  153. spikeDelta += spikeDelta / 6 + rand.nextInt (5);
  154. }
  155. }
  156. // Searches a buffer for a set of spikes that matches those in the test sound
  157. int findOffsetOfSpikes (const AudioSampleBuffer& buffer) const
  158. {
  159. const float minSpikeLevel = 5.0f;
  160. const double smooth = 0.975;
  161. const float* s = buffer.getSampleData (0, 0);
  162. const int spikeDriftAllowed = 5;
  163. Array <int> spikesFound;
  164. spikesFound.ensureStorageAllocated (100);
  165. double runningAverage = 0;
  166. int lastSpike = 0;
  167. for (int i = 0; i < buffer.getNumSamples() - 10; ++i)
  168. {
  169. const float samp = fabsf (s[i]);
  170. if (samp > runningAverage * minSpikeLevel && i > lastSpike + 20)
  171. {
  172. lastSpike = i;
  173. spikesFound.add (i);
  174. }
  175. runningAverage = runningAverage * smooth + (1.0 - smooth) * samp;
  176. }
  177. int bestMatch = -1;
  178. int bestNumMatches = spikes.size() / 3; // the minimum number of matches required
  179. if (spikesFound.size() < bestNumMatches)
  180. return -1;
  181. for (int offsetToTest = 0; offsetToTest < buffer.getNumSamples() - 2048; ++offsetToTest)
  182. {
  183. int numMatchesHere = 0;
  184. int foundIndex = 0;
  185. for (int refIndex = 0; refIndex < spikes.size(); ++refIndex)
  186. {
  187. const int referenceSpike = spikes.getUnchecked (refIndex) + offsetToTest;
  188. int spike = 0;
  189. while ((spike = spikesFound.getUnchecked (foundIndex)) < referenceSpike - spikeDriftAllowed
  190. && foundIndex < spikesFound.size() - 1)
  191. ++foundIndex;
  192. if (spike >= referenceSpike - spikeDriftAllowed && spike <= referenceSpike + spikeDriftAllowed)
  193. ++numMatchesHere;
  194. }
  195. if (numMatchesHere > bestNumMatches)
  196. {
  197. bestNumMatches = numMatchesHere;
  198. bestMatch = offsetToTest;
  199. if (numMatchesHere == spikes.size())
  200. break;
  201. }
  202. }
  203. return bestMatch;
  204. }
  205. int calculateLatencySamples() const
  206. {
  207. // Detect the sound in both our test sound and the recording of it, and measure the difference
  208. // in their start times..
  209. const int referenceStart = findOffsetOfSpikes (testSound);
  210. jassert (referenceStart >= 0);
  211. const int recordedStart = findOffsetOfSpikes (recordedSound);
  212. return (recordedStart < 0) ? -1 : (recordedStart - referenceStart);
  213. }
  214. LatencyTester (const LatencyTester&);
  215. LatencyTester& operator= (const LatencyTester&);
  216. };
  217. //[/MiscUserDefs]
  218. //==============================================================================
  219. AudioDemoLatencyPage::AudioDemoLatencyPage (AudioDeviceManager& deviceManager_)
  220. : deviceManager (deviceManager_)
  221. {
  222. addAndMakeVisible (liveAudioDisplayComp = new LiveAudioInputDisplayComp());
  223. addAndMakeVisible (startTestButton = new TextButton (String::empty));
  224. startTestButton->setButtonText ("Test Latency");
  225. startTestButton->addListener (this);
  226. addAndMakeVisible (testResultsBox = new TextEditor (String::empty));
  227. testResultsBox->setMultiLine (true);
  228. testResultsBox->setReturnKeyStartsNewLine (true);
  229. testResultsBox->setReadOnly (true);
  230. testResultsBox->setScrollbarsShown (true);
  231. testResultsBox->setCaretVisible (false);
  232. testResultsBox->setPopupMenuEnabled (true);
  233. testResultsBox->setColour (TextEditor::backgroundColourId, Colour (0x32ffffff));
  234. testResultsBox->setColour (TextEditor::outlineColourId, Colour (0x1c000000));
  235. testResultsBox->setColour (TextEditor::shadowColourId, Colour (0x16000000));
  236. testResultsBox->setText ("Running this test measures the round-trip latency between the audio output and input devices you\'ve got selected.\n\nIt\'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...");
  237. //[UserPreSize]
  238. //[/UserPreSize]
  239. setSize (600, 400);
  240. //[Constructor] You can add your own custom stuff here..
  241. deviceManager.addAudioCallback (liveAudioDisplayComp);
  242. latencyTester = new LatencyTester (testResultsBox);
  243. deviceManager.addAudioCallback (latencyTester);
  244. //[/Constructor]
  245. }
  246. AudioDemoLatencyPage::~AudioDemoLatencyPage()
  247. {
  248. //[Destructor_pre]. You can add your own custom destruction code here..
  249. deviceManager.removeAudioCallback (liveAudioDisplayComp);
  250. deviceManager.removeAudioCallback (latencyTester);
  251. //[/Destructor_pre]
  252. liveAudioDisplayComp = nullptr;
  253. startTestButton = nullptr;
  254. testResultsBox = nullptr;
  255. //[Destructor]. You can add your own custom destruction code here..
  256. //[/Destructor]
  257. }
  258. //==============================================================================
  259. void AudioDemoLatencyPage::paint (Graphics& g)
  260. {
  261. //[UserPrePaint] Add your own custom painting code here..
  262. //[/UserPrePaint]
  263. g.fillAll (Colours::lightgrey);
  264. //[UserPaint] Add your own custom painting code here..
  265. //[/UserPaint]
  266. }
  267. void AudioDemoLatencyPage::resized()
  268. {
  269. liveAudioDisplayComp->setBounds (8, 8, getWidth() - 16, 64);
  270. startTestButton->setBounds (8, getHeight() - 41, 168, 32);
  271. testResultsBox->setBounds (8, 88, getWidth() - 16, getHeight() - 137);
  272. //[UserResized] Add your own custom resize handling here..
  273. //[/UserResized]
  274. }
  275. void AudioDemoLatencyPage::buttonClicked (Button* buttonThatWasClicked)
  276. {
  277. //[UserbuttonClicked_Pre]
  278. //[/UserbuttonClicked_Pre]
  279. if (buttonThatWasClicked == startTestButton)
  280. {
  281. //[UserButtonCode_startTestButton] -- add your button handler code here..
  282. latencyTester->beginTest();
  283. //[/UserButtonCode_startTestButton]
  284. }
  285. //[UserbuttonClicked_Post]
  286. //[/UserbuttonClicked_Post]
  287. }
  288. //[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
  289. //[/MiscUserCode]
  290. //==============================================================================
  291. #if 0
  292. /* -- Introjucer information section --
  293. This is where the Introjucer stores the metadata that describe this GUI layout, so
  294. make changes in here at your peril!
  295. BEGIN_JUCER_METADATA
  296. <JUCER_COMPONENT documentType="Component" className="AudioDemoLatencyPage" componentName=""
  297. parentClasses="public Component" constructorParams="AudioDeviceManager&amp; deviceManager_"
  298. variableInitialisers="deviceManager (deviceManager_)" snapPixels="8"
  299. snapActive="1" snapShown="1" overlayOpacity="0.330000013" fixedSize="0"
  300. initialWidth="600" initialHeight="400">
  301. <BACKGROUND backgroundColour="ffd3d3d3"/>
  302. <GENERICCOMPONENT name="" id="7d70eb2617f56220" memberName="liveAudioDisplayComp"
  303. virtualName="" explicitFocusOrder="0" pos="8 8 16M 64" class="LiveAudioInputDisplayComp"
  304. params=""/>
  305. <TEXTBUTTON name="" id="83ed8fcbf36419df" memberName="startTestButton" virtualName=""
  306. explicitFocusOrder="0" pos="8 41R 168 32" buttonText="Test Latency"
  307. connectedEdges="0" needsCallback="1" radioGroupId="0"/>
  308. <TEXTEDITOR name="" id="95d03a8403bc35da" memberName="testResultsBox" virtualName=""
  309. explicitFocusOrder="0" pos="8 88 16M 137M" bkgcol="32ffffff"
  310. outlinecol="1c000000" shadowcol="16000000" initialText="Running this test measures the round-trip latency between the audio output and input devices you've got selected.&#10;&#10;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..."
  311. multiline="1" retKeyStartsLine="1" readonly="1" scrollbars="1"
  312. caret="0" popupmenu="1"/>
  313. </JUCER_COMPONENT>
  314. END_JUCER_METADATA
  315. */
  316. #endif
  317. //[EndFile] You can add extra defines here...
  318. //[/EndFile]