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