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.

520 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #if JUCE_ENABLE_ALLOCATION_HOOKS
  19. #define JUCE_FAIL_ON_ALLOCATION_IN_SCOPE const UnitTestAllocationChecker checker (*this)
  20. #else
  21. #define JUCE_FAIL_ON_ALLOCATION_IN_SCOPE
  22. #endif
  23. namespace juce
  24. {
  25. namespace dsp
  26. {
  27. namespace
  28. {
  29. class ConvolutionTest : public UnitTest
  30. {
  31. template <typename Callback>
  32. static void nTimes (int n, Callback&& callback)
  33. {
  34. for (auto i = 0; i < n; ++i)
  35. callback();
  36. }
  37. static AudioBuffer<float> makeRamp (int length)
  38. {
  39. AudioBuffer<float> result (1, length);
  40. result.clear();
  41. const auto writePtr = result.getWritePointer (0);
  42. std::fill (writePtr, writePtr + length, 1.0f);
  43. result.applyGainRamp (0, length, 1.0f, 0.0f);
  44. return result;
  45. }
  46. static AudioBuffer<float> makeStereoRamp (int length)
  47. {
  48. AudioBuffer<float> result (2, length);
  49. result.clear();
  50. auto** channels = result.getArrayOfWritePointers();
  51. std::for_each (channels, channels + result.getNumChannels(), [&] (auto* channel)
  52. {
  53. std::fill (channel, channel + length, 1.0f);
  54. });
  55. result.applyGainRamp (0, 0, length, 1.0f, 0.0f);
  56. result.applyGainRamp (1, 0, length, 0.0f, 1.0f);
  57. return result;
  58. }
  59. static void addDiracImpulse (const AudioBlock<float>& block)
  60. {
  61. block.clear();
  62. for (size_t channel = 0; channel != block.getNumChannels(); ++channel)
  63. block.setSample ((int) channel, 0, 1.0f);
  64. }
  65. void checkForNans (const AudioBlock<float>& block)
  66. {
  67. for (size_t channel = 0; channel != block.getNumChannels(); ++channel)
  68. for (size_t sample = 0; sample != block.getNumSamples(); ++sample)
  69. expect (! std::isnan (block.getSample ((int) channel, (int) sample)));
  70. }
  71. template <typename T>
  72. void nonAllocatingExpectWithinAbsoluteError (const T& a, const T& b, const T& error)
  73. {
  74. expect (std::abs (a - b) < error);
  75. }
  76. enum class InitSequence { prepareThenLoad, loadThenPrepare };
  77. void checkLatency (const Convolution& convolution, const Convolution::Latency& latency)
  78. {
  79. const auto reportedLatency = convolution.getLatency();
  80. if (latency.latencyInSamples == 0)
  81. expect (reportedLatency == 0);
  82. expect (reportedLatency >= latency.latencyInSamples);
  83. }
  84. void checkLatency (const Convolution&, const Convolution::NonUniform&) {}
  85. template <typename ConvolutionConfig>
  86. void testConvolution (const ProcessSpec& spec,
  87. const ConvolutionConfig& config,
  88. const AudioBuffer<float>& ir,
  89. double irSampleRate,
  90. Convolution::Stereo stereo,
  91. Convolution::Trim trim,
  92. Convolution::Normalise normalise,
  93. const AudioBlock<const float>& expectedResult,
  94. InitSequence initSequence)
  95. {
  96. AudioBuffer<float> buffer (static_cast<int> (spec.numChannels),
  97. static_cast<int> (spec.maximumBlockSize));
  98. AudioBlock<float> block { buffer };
  99. ProcessContextReplacing<float> context { block };
  100. const auto numBlocksPerSecond = (int) std::ceil (spec.sampleRate / spec.maximumBlockSize);
  101. const auto numBlocksForImpulse = (int) std::ceil ((double) expectedResult.getNumSamples() / spec.maximumBlockSize);
  102. AudioBuffer<float> outBuffer (static_cast<int> (spec.numChannels),
  103. numBlocksForImpulse * static_cast<int> (spec.maximumBlockSize));
  104. Convolution convolution (config);
  105. auto copiedIr = ir;
  106. if (initSequence == InitSequence::loadThenPrepare)
  107. convolution.loadImpulseResponse (std::move (copiedIr), irSampleRate, stereo, trim, normalise);
  108. convolution.prepare (spec);
  109. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  110. if (initSequence == InitSequence::prepareThenLoad)
  111. convolution.loadImpulseResponse (std::move (copiedIr), irSampleRate, stereo, trim, normalise);
  112. checkLatency (convolution, config);
  113. auto processBlocksWithDiracImpulse = [&]
  114. {
  115. for (auto i = 0; i != numBlocksForImpulse; ++i)
  116. {
  117. if (i == 0)
  118. addDiracImpulse (block);
  119. else
  120. block.clear();
  121. convolution.process (context);
  122. for (auto c = 0; c != static_cast<int> (spec.numChannels); ++c)
  123. {
  124. outBuffer.copyFrom (c,
  125. i * static_cast<int> (spec.maximumBlockSize),
  126. block.getChannelPointer (static_cast<size_t> (c)),
  127. static_cast<int> (spec.maximumBlockSize));
  128. }
  129. }
  130. };
  131. const auto time = Time::getMillisecondCounter();
  132. // Wait 10 seconds to load the impulse response
  133. while (Time::getMillisecondCounter() - time < 10'000)
  134. {
  135. processBlocksWithDiracImpulse();
  136. // Check if the impulse response was loaded
  137. if (block.getSample (0, 1) != 0.0f)
  138. break;
  139. }
  140. // At this point, our convolution should be loaded and the current IR size should
  141. // match the expected result size
  142. expect (convolution.getCurrentIRSize() == static_cast<int> (expectedResult.getNumSamples()));
  143. // Make sure we get any smoothing out of the way
  144. nTimes (numBlocksPerSecond, processBlocksWithDiracImpulse);
  145. nTimes (5, [&]
  146. {
  147. processBlocksWithDiracImpulse();
  148. const auto actualLatency = static_cast<size_t> (convolution.getLatency());
  149. // The output should be the same as the IR
  150. for (size_t c = 0; c != static_cast<size_t> (expectedResult.getNumChannels()); ++c)
  151. {
  152. for (size_t i = 0; i != static_cast<size_t> (expectedResult.getNumSamples()); ++i)
  153. {
  154. const auto equivalentSample = i + actualLatency;
  155. if (static_cast<int> (equivalentSample) >= outBuffer.getNumSamples())
  156. continue;
  157. nonAllocatingExpectWithinAbsoluteError (outBuffer.getSample ((int) c, (int) equivalentSample),
  158. expectedResult.getSample ((int) c, (int) i),
  159. 0.01f);
  160. }
  161. }
  162. });
  163. }
  164. template <typename ConvolutionConfig>
  165. void testConvolution (const ProcessSpec& spec,
  166. const ConvolutionConfig& config,
  167. const AudioBuffer<float>& ir,
  168. double irSampleRate,
  169. Convolution::Stereo stereo,
  170. Convolution::Trim trim,
  171. Convolution::Normalise normalise,
  172. const AudioBlock<const float>& expectedResult)
  173. {
  174. for (const auto sequence : { InitSequence::prepareThenLoad, InitSequence::loadThenPrepare })
  175. testConvolution (spec, config, ir, irSampleRate, stereo, trim, normalise, expectedResult, sequence);
  176. }
  177. public:
  178. ConvolutionTest()
  179. : UnitTest ("Convolution", UnitTestCategories::dsp)
  180. {}
  181. void runTest() override
  182. {
  183. const ProcessSpec spec { 44100.0, 512, 2 };
  184. AudioBuffer<float> buffer (static_cast<int> (spec.numChannels),
  185. static_cast<int> (spec.maximumBlockSize));
  186. AudioBlock<float> block { buffer };
  187. ProcessContextReplacing<float> context { block };
  188. const auto impulseData = [&]
  189. {
  190. Random random;
  191. AudioBuffer<float> result (2, 1000);
  192. for (auto channel = 0; channel != result.getNumChannels(); ++channel)
  193. for (auto sample = 0; sample != result.getNumSamples(); ++sample)
  194. result.setSample (channel, sample, random.nextFloat());
  195. return result;
  196. }();
  197. beginTest ("Impulse responses can be loaded without allocating on the audio thread");
  198. {
  199. Convolution convolution;
  200. convolution.prepare (spec);
  201. auto copy = impulseData;
  202. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  203. nTimes (100, [&]
  204. {
  205. convolution.loadImpulseResponse (std::move (copy),
  206. 1000,
  207. Convolution::Stereo::yes,
  208. Convolution::Trim::yes,
  209. Convolution::Normalise::no);
  210. addDiracImpulse (block);
  211. convolution.process (context);
  212. checkForNans (block);
  213. });
  214. }
  215. beginTest ("Convolution can be reset without allocating on the audio thread");
  216. {
  217. Convolution convolution;
  218. convolution.prepare (spec);
  219. auto copy = impulseData;
  220. convolution.loadImpulseResponse (std::move (copy),
  221. 1000,
  222. Convolution::Stereo::yes,
  223. Convolution::Trim::yes,
  224. Convolution::Normalise::yes);
  225. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  226. nTimes (100, [&]
  227. {
  228. addDiracImpulse (block);
  229. convolution.reset();
  230. convolution.process (context);
  231. convolution.reset();
  232. });
  233. checkForNans (block);
  234. }
  235. beginTest ("Completely empty IRs don't crash");
  236. {
  237. AudioBuffer<float> emptyBuffer;
  238. Convolution convolution;
  239. convolution.prepare (spec);
  240. auto copy = impulseData;
  241. convolution.loadImpulseResponse (std::move (copy),
  242. 2000,
  243. Convolution::Stereo::yes,
  244. Convolution::Trim::yes,
  245. Convolution::Normalise::yes);
  246. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  247. nTimes (100, [&]
  248. {
  249. addDiracImpulse (block);
  250. convolution.reset();
  251. convolution.process (context);
  252. convolution.reset();
  253. });
  254. checkForNans (block);
  255. }
  256. beginTest ("Short uniform convolutions work");
  257. {
  258. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) / 2);
  259. testConvolution (spec,
  260. Convolution::Latency { 0 },
  261. ramp,
  262. spec.sampleRate,
  263. Convolution::Stereo::yes,
  264. Convolution::Trim::yes,
  265. Convolution::Normalise::no,
  266. ramp);
  267. }
  268. beginTest ("Longer uniform convolutions work");
  269. {
  270. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  271. testConvolution (spec,
  272. Convolution::Latency { 0 },
  273. ramp,
  274. spec.sampleRate,
  275. Convolution::Stereo::yes,
  276. Convolution::Trim::yes,
  277. Convolution::Normalise::no,
  278. ramp);
  279. }
  280. beginTest ("Normalisation works");
  281. {
  282. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  283. auto copy = ramp;
  284. const auto channels = copy.getArrayOfWritePointers();
  285. const auto numChannels = copy.getNumChannels();
  286. const auto numSamples = copy.getNumSamples();
  287. const auto factor = 0.125f / std::sqrt (std::accumulate (channels, channels + numChannels, 0.0f, [&] (auto max, auto* channel)
  288. {
  289. return juce::jmax (max, std::accumulate (channel, channel + numSamples, 0.0f, [] (auto sum, auto sample)
  290. {
  291. return sum + sample * sample;
  292. }));
  293. }));
  294. std::for_each (channels, channels + numChannels, [&] (auto* channel)
  295. {
  296. FloatVectorOperations::multiply (channel, factor, numSamples);
  297. });
  298. testConvolution (spec,
  299. Convolution::Latency { 0 },
  300. ramp,
  301. spec.sampleRate,
  302. Convolution::Stereo::yes,
  303. Convolution::Trim::yes,
  304. Convolution::Normalise::yes,
  305. copy);
  306. }
  307. beginTest ("Stereo convolutions work");
  308. {
  309. const auto ramp = makeStereoRamp (static_cast<int> (spec.maximumBlockSize) * 5);
  310. testConvolution (spec,
  311. Convolution::Latency { 0 },
  312. ramp,
  313. spec.sampleRate,
  314. Convolution::Stereo::yes,
  315. Convolution::Trim::yes,
  316. Convolution::Normalise::no,
  317. ramp);
  318. }
  319. beginTest ("Stereo IRs only use first channel if stereo is disabled");
  320. {
  321. const auto length = static_cast<int> (spec.maximumBlockSize) * 5;
  322. const auto ramp = makeStereoRamp (length);
  323. const float* channels[] { ramp.getReadPointer (0), ramp.getReadPointer (0) };
  324. testConvolution (spec,
  325. Convolution::Latency { 0 },
  326. ramp,
  327. spec.sampleRate,
  328. Convolution::Stereo::no,
  329. Convolution::Trim::yes,
  330. Convolution::Normalise::no,
  331. AudioBlock<const float> (channels, numElementsInArray (channels), length));
  332. }
  333. beginTest ("IRs with extra silence are trimmed appropriately");
  334. {
  335. const auto length = static_cast<int> (spec.maximumBlockSize) * 3;
  336. const auto ramp = makeRamp (length);
  337. AudioBuffer<float> paddedRamp (ramp.getNumChannels(), ramp.getNumSamples() * 2);
  338. paddedRamp.clear();
  339. const auto offset = (paddedRamp.getNumSamples() - ramp.getNumSamples()) / 2;
  340. for (auto channel = 0; channel != ramp.getNumChannels(); ++channel)
  341. paddedRamp.copyFrom (channel, offset, ramp.getReadPointer (channel), length);
  342. testConvolution (spec,
  343. Convolution::Latency { 0 },
  344. paddedRamp,
  345. spec.sampleRate,
  346. Convolution::Stereo::no,
  347. Convolution::Trim::yes,
  348. Convolution::Normalise::no,
  349. ramp);
  350. }
  351. beginTest ("IRs are resampled if their sample rate is different to the playback rate");
  352. {
  353. for (const auto resampleRatio : { 0.1, 0.5, 2.0, 10.0 })
  354. {
  355. const auto length = static_cast<int> (spec.maximumBlockSize) * 2;
  356. const auto ramp = makeStereoRamp (length);
  357. const auto resampled = [&]
  358. {
  359. AudioBuffer<float> original = ramp;
  360. MemoryAudioSource memorySource (original, false);
  361. ResamplingAudioSource resamplingSource (&memorySource, false, original.getNumChannels());
  362. const auto finalSize = roundToInt (original.getNumSamples() / resampleRatio);
  363. resamplingSource.setResamplingRatio (resampleRatio);
  364. resamplingSource.prepareToPlay (finalSize, spec.sampleRate * resampleRatio);
  365. AudioBuffer<float> result (original.getNumChannels(), finalSize);
  366. resamplingSource.getNextAudioBlock ({ &result, 0, result.getNumSamples() });
  367. return result;
  368. }();
  369. testConvolution (spec,
  370. Convolution::Latency { 0 },
  371. ramp,
  372. spec.sampleRate * resampleRatio,
  373. Convolution::Stereo::yes,
  374. Convolution::Trim::yes,
  375. Convolution::Normalise::no,
  376. resampled);
  377. }
  378. }
  379. beginTest ("Non-uniform convolutions work");
  380. {
  381. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  382. for (auto headSize : { spec.maximumBlockSize / 2, spec.maximumBlockSize, spec.maximumBlockSize * 9 })
  383. {
  384. testConvolution (spec,
  385. Convolution::NonUniform { static_cast<int> (headSize) },
  386. ramp,
  387. spec.sampleRate,
  388. Convolution::Stereo::yes,
  389. Convolution::Trim::yes,
  390. Convolution::Normalise::no,
  391. ramp);
  392. }
  393. }
  394. beginTest ("Convolutions with latency work");
  395. {
  396. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  397. using BlockSize = decltype (spec.maximumBlockSize);
  398. for (auto latency : { /*static_cast<BlockSize> (0),
  399. spec.maximumBlockSize / 3,
  400. spec.maximumBlockSize,
  401. spec.maximumBlockSize * 2, */
  402. static_cast<BlockSize> (spec.maximumBlockSize * 2.5) })
  403. {
  404. testConvolution (spec,
  405. Convolution::Latency { static_cast<int> (latency) },
  406. ramp,
  407. spec.sampleRate,
  408. Convolution::Stereo::yes,
  409. Convolution::Trim::yes,
  410. Convolution::Normalise::no,
  411. ramp);
  412. }
  413. }
  414. }
  415. };
  416. ConvolutionTest convolutionUnitTest;
  417. }
  418. }
  419. }
  420. #undef JUCE_FAIL_ON_ALLOCATION_IN_SCOPE