Audio plugin host https://kx.studio/carla
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.

582 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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(), [length] (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. void checkAllChannelsNonZero (const AudioBlock<float>& block)
  72. {
  73. for (size_t i = 0; i != block.getNumChannels(); ++i)
  74. {
  75. const auto* channel = block.getChannelPointer (i);
  76. expect (std::any_of (channel, channel + block.getNumSamples(), [] (float sample)
  77. {
  78. return sample != 0.0f;
  79. }));
  80. }
  81. }
  82. template <typename T>
  83. void nonAllocatingExpectWithinAbsoluteError (const T& a, const T& b, const T& error)
  84. {
  85. expect (std::abs (a - b) < error);
  86. }
  87. enum class InitSequence { prepareThenLoad, loadThenPrepare };
  88. void checkLatency (const Convolution& convolution, const Convolution::Latency& latency)
  89. {
  90. const auto reportedLatency = convolution.getLatency();
  91. if (latency.latencyInSamples == 0)
  92. expect (reportedLatency == 0);
  93. expect (reportedLatency >= latency.latencyInSamples);
  94. }
  95. void checkLatency (const Convolution&, const Convolution::NonUniform&) {}
  96. template <typename ConvolutionConfig>
  97. void testConvolution (const ProcessSpec& spec,
  98. const ConvolutionConfig& config,
  99. const AudioBuffer<float>& ir,
  100. double irSampleRate,
  101. Convolution::Stereo stereo,
  102. Convolution::Trim trim,
  103. Convolution::Normalise normalise,
  104. const AudioBlock<const float>& expectedResult,
  105. InitSequence initSequence)
  106. {
  107. AudioBuffer<float> buffer (static_cast<int> (spec.numChannels),
  108. static_cast<int> (spec.maximumBlockSize));
  109. AudioBlock<float> block { buffer };
  110. ProcessContextReplacing<float> context { block };
  111. const auto numBlocksPerSecond = (int) std::ceil (spec.sampleRate / spec.maximumBlockSize);
  112. const auto numBlocksForImpulse = (int) std::ceil ((double) expectedResult.getNumSamples() / spec.maximumBlockSize);
  113. AudioBuffer<float> outBuffer (static_cast<int> (spec.numChannels),
  114. numBlocksForImpulse * static_cast<int> (spec.maximumBlockSize));
  115. Convolution convolution (config);
  116. auto copiedIr = ir;
  117. if (initSequence == InitSequence::loadThenPrepare)
  118. convolution.loadImpulseResponse (std::move (copiedIr), irSampleRate, stereo, trim, normalise);
  119. convolution.prepare (spec);
  120. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  121. if (initSequence == InitSequence::prepareThenLoad)
  122. convolution.loadImpulseResponse (std::move (copiedIr), irSampleRate, stereo, trim, normalise);
  123. checkLatency (convolution, config);
  124. auto processBlocksWithDiracImpulse = [&]
  125. {
  126. for (auto i = 0; i != numBlocksForImpulse; ++i)
  127. {
  128. if (i == 0)
  129. addDiracImpulse (block);
  130. else
  131. block.clear();
  132. convolution.process (context);
  133. for (auto c = 0; c != static_cast<int> (spec.numChannels); ++c)
  134. {
  135. outBuffer.copyFrom (c,
  136. i * static_cast<int> (spec.maximumBlockSize),
  137. block.getChannelPointer (static_cast<size_t> (c)),
  138. static_cast<int> (spec.maximumBlockSize));
  139. }
  140. }
  141. };
  142. // If we load an IR while the convolution is already running, we'll need to wait
  143. // for it to be loaded on a background thread
  144. if (initSequence == InitSequence::prepareThenLoad)
  145. {
  146. const auto time = Time::getMillisecondCounter();
  147. // Wait 10 seconds to load the impulse response
  148. while (Time::getMillisecondCounter() - time < 10'000)
  149. {
  150. processBlocksWithDiracImpulse();
  151. // Check if the impulse response was loaded
  152. if (block.getSample (0, 1) != 0.0f)
  153. break;
  154. }
  155. }
  156. // At this point, our convolution should be loaded and the current IR size should
  157. // match the expected result size
  158. expect (convolution.getCurrentIRSize() == static_cast<int> (expectedResult.getNumSamples()));
  159. // Make sure we get any smoothing out of the way
  160. nTimes (numBlocksPerSecond, processBlocksWithDiracImpulse);
  161. nTimes (5, [&]
  162. {
  163. processBlocksWithDiracImpulse();
  164. const auto actualLatency = static_cast<size_t> (convolution.getLatency());
  165. // The output should be the same as the IR
  166. for (size_t c = 0; c != static_cast<size_t> (expectedResult.getNumChannels()); ++c)
  167. {
  168. for (size_t i = 0; i != static_cast<size_t> (expectedResult.getNumSamples()); ++i)
  169. {
  170. const auto equivalentSample = i + actualLatency;
  171. if (static_cast<int> (equivalentSample) >= outBuffer.getNumSamples())
  172. continue;
  173. nonAllocatingExpectWithinAbsoluteError (outBuffer.getSample ((int) c, (int) equivalentSample),
  174. expectedResult.getSample ((int) c, (int) i),
  175. 0.01f);
  176. }
  177. }
  178. });
  179. }
  180. template <typename ConvolutionConfig>
  181. void testConvolution (const ProcessSpec& spec,
  182. const ConvolutionConfig& config,
  183. const AudioBuffer<float>& ir,
  184. double irSampleRate,
  185. Convolution::Stereo stereo,
  186. Convolution::Trim trim,
  187. Convolution::Normalise normalise,
  188. const AudioBlock<const float>& expectedResult)
  189. {
  190. for (const auto sequence : { InitSequence::prepareThenLoad, InitSequence::loadThenPrepare })
  191. testConvolution (spec, config, ir, irSampleRate, stereo, trim, normalise, expectedResult, sequence);
  192. }
  193. public:
  194. ConvolutionTest()
  195. : UnitTest ("Convolution", UnitTestCategories::dsp)
  196. {}
  197. void runTest() override
  198. {
  199. const ProcessSpec spec { 44100.0, 512, 2 };
  200. AudioBuffer<float> buffer (static_cast<int> (spec.numChannels),
  201. static_cast<int> (spec.maximumBlockSize));
  202. AudioBlock<float> block { buffer };
  203. ProcessContextReplacing<float> context { block };
  204. const auto impulseData = []
  205. {
  206. Random random;
  207. AudioBuffer<float> result (2, 1000);
  208. for (auto channel = 0; channel != result.getNumChannels(); ++channel)
  209. for (auto sample = 0; sample != result.getNumSamples(); ++sample)
  210. result.setSample (channel, sample, random.nextFloat());
  211. return result;
  212. }();
  213. beginTest ("Impulse responses can be loaded without allocating on the audio thread");
  214. {
  215. Convolution convolution;
  216. convolution.prepare (spec);
  217. auto copy = impulseData;
  218. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  219. nTimes (100, [&]
  220. {
  221. convolution.loadImpulseResponse (std::move (copy),
  222. 1000,
  223. Convolution::Stereo::yes,
  224. Convolution::Trim::yes,
  225. Convolution::Normalise::no);
  226. addDiracImpulse (block);
  227. convolution.process (context);
  228. checkForNans (block);
  229. });
  230. }
  231. beginTest ("Convolution can be reset without allocating on the audio thread");
  232. {
  233. Convolution convolution;
  234. convolution.prepare (spec);
  235. auto copy = impulseData;
  236. convolution.loadImpulseResponse (std::move (copy),
  237. 1000,
  238. Convolution::Stereo::yes,
  239. Convolution::Trim::yes,
  240. Convolution::Normalise::yes);
  241. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  242. nTimes (100, [&]
  243. {
  244. addDiracImpulse (block);
  245. convolution.reset();
  246. convolution.process (context);
  247. convolution.reset();
  248. });
  249. checkForNans (block);
  250. }
  251. beginTest ("Completely empty IRs don't crash");
  252. {
  253. AudioBuffer<float> emptyBuffer;
  254. Convolution convolution;
  255. convolution.prepare (spec);
  256. auto copy = impulseData;
  257. convolution.loadImpulseResponse (std::move (copy),
  258. 2000,
  259. Convolution::Stereo::yes,
  260. Convolution::Trim::yes,
  261. Convolution::Normalise::yes);
  262. JUCE_FAIL_ON_ALLOCATION_IN_SCOPE;
  263. nTimes (100, [&]
  264. {
  265. addDiracImpulse (block);
  266. convolution.reset();
  267. convolution.process (context);
  268. convolution.reset();
  269. });
  270. checkForNans (block);
  271. }
  272. beginTest ("Convolutions can cope with a change in samplerate and blocksize");
  273. {
  274. Convolution convolution;
  275. auto copy = impulseData;
  276. convolution.loadImpulseResponse (std::move (copy),
  277. 2000,
  278. Convolution::Stereo::yes,
  279. Convolution::Trim::no,
  280. Convolution::Normalise::yes);
  281. const dsp::ProcessSpec specs[] = { { 96'000.0, 1024, 2 },
  282. { 48'000.0, 512, 2 },
  283. { 44'100.0, 256, 2 } };
  284. for (const auto& thisSpec : specs)
  285. {
  286. convolution.prepare (thisSpec);
  287. expectWithinAbsoluteError ((double) convolution.getCurrentIRSize(),
  288. thisSpec.sampleRate * 0.5,
  289. 1.0);
  290. juce::AudioBuffer<float> thisBuffer ((int) thisSpec.numChannels,
  291. (int) thisSpec.maximumBlockSize);
  292. AudioBlock<float> thisBlock { thisBuffer };
  293. ProcessContextReplacing<float> thisContext { thisBlock };
  294. nTimes (100, [&]
  295. {
  296. addDiracImpulse (thisBlock);
  297. convolution.process (thisContext);
  298. checkForNans (thisBlock);
  299. checkAllChannelsNonZero (thisBlock);
  300. });
  301. }
  302. }
  303. beginTest ("Short uniform convolutions work");
  304. {
  305. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) / 2);
  306. testConvolution (spec,
  307. Convolution::Latency { 0 },
  308. ramp,
  309. spec.sampleRate,
  310. Convolution::Stereo::yes,
  311. Convolution::Trim::yes,
  312. Convolution::Normalise::no,
  313. ramp);
  314. }
  315. beginTest ("Longer uniform convolutions work");
  316. {
  317. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  318. testConvolution (spec,
  319. Convolution::Latency { 0 },
  320. ramp,
  321. spec.sampleRate,
  322. Convolution::Stereo::yes,
  323. Convolution::Trim::yes,
  324. Convolution::Normalise::no,
  325. ramp);
  326. }
  327. beginTest ("Normalisation works");
  328. {
  329. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  330. auto copy = ramp;
  331. const auto channels = copy.getArrayOfWritePointers();
  332. const auto numChannels = copy.getNumChannels();
  333. const auto numSamples = copy.getNumSamples();
  334. const auto factor = 0.125f / std::sqrt (std::accumulate (channels, channels + numChannels, 0.0f,
  335. [numSamples] (auto max, auto* channel)
  336. {
  337. return juce::jmax (max, std::accumulate (channel, channel + numSamples, 0.0f,
  338. [] (auto sum, auto sample)
  339. {
  340. return sum + sample * sample;
  341. }));
  342. }));
  343. std::for_each (channels, channels + numChannels, [factor, numSamples] (auto* channel)
  344. {
  345. FloatVectorOperations::multiply (channel, factor, numSamples);
  346. });
  347. testConvolution (spec,
  348. Convolution::Latency { 0 },
  349. ramp,
  350. spec.sampleRate,
  351. Convolution::Stereo::yes,
  352. Convolution::Trim::yes,
  353. Convolution::Normalise::yes,
  354. copy);
  355. }
  356. beginTest ("Stereo convolutions work");
  357. {
  358. const auto ramp = makeStereoRamp (static_cast<int> (spec.maximumBlockSize) * 5);
  359. testConvolution (spec,
  360. Convolution::Latency { 0 },
  361. ramp,
  362. spec.sampleRate,
  363. Convolution::Stereo::yes,
  364. Convolution::Trim::yes,
  365. Convolution::Normalise::no,
  366. ramp);
  367. }
  368. beginTest ("Stereo IRs only use first channel if stereo is disabled");
  369. {
  370. const auto length = static_cast<int> (spec.maximumBlockSize) * 5;
  371. const auto ramp = makeStereoRamp (length);
  372. const float* channels[] { ramp.getReadPointer (0), ramp.getReadPointer (0) };
  373. testConvolution (spec,
  374. Convolution::Latency { 0 },
  375. ramp,
  376. spec.sampleRate,
  377. Convolution::Stereo::no,
  378. Convolution::Trim::yes,
  379. Convolution::Normalise::no,
  380. AudioBlock<const float> (channels, numElementsInArray (channels), length));
  381. }
  382. beginTest ("IRs with extra silence are trimmed appropriately");
  383. {
  384. const auto length = static_cast<int> (spec.maximumBlockSize) * 3;
  385. const auto ramp = makeRamp (length);
  386. AudioBuffer<float> paddedRamp (ramp.getNumChannels(), ramp.getNumSamples() * 2);
  387. paddedRamp.clear();
  388. const auto offset = (paddedRamp.getNumSamples() - ramp.getNumSamples()) / 2;
  389. for (auto channel = 0; channel != ramp.getNumChannels(); ++channel)
  390. paddedRamp.copyFrom (channel, offset, ramp.getReadPointer (channel), length);
  391. testConvolution (spec,
  392. Convolution::Latency { 0 },
  393. paddedRamp,
  394. spec.sampleRate,
  395. Convolution::Stereo::no,
  396. Convolution::Trim::yes,
  397. Convolution::Normalise::no,
  398. ramp);
  399. }
  400. beginTest ("IRs are resampled if their sample rate is different to the playback rate");
  401. {
  402. for (const auto resampleRatio : { 0.1, 0.5, 2.0, 10.0 })
  403. {
  404. const auto length = static_cast<int> (spec.maximumBlockSize) * 2;
  405. const auto ramp = makeStereoRamp (length);
  406. const auto resampled = [&]
  407. {
  408. AudioBuffer<float> original = ramp;
  409. MemoryAudioSource memorySource (original, false);
  410. ResamplingAudioSource resamplingSource (&memorySource, false, original.getNumChannels());
  411. const auto finalSize = roundToInt (original.getNumSamples() / resampleRatio);
  412. resamplingSource.setResamplingRatio (resampleRatio);
  413. resamplingSource.prepareToPlay (finalSize, spec.sampleRate * resampleRatio);
  414. AudioBuffer<float> result (original.getNumChannels(), finalSize);
  415. resamplingSource.getNextAudioBlock ({ &result, 0, result.getNumSamples() });
  416. result.applyGain ((float) resampleRatio);
  417. return result;
  418. }();
  419. testConvolution (spec,
  420. Convolution::Latency { 0 },
  421. ramp,
  422. spec.sampleRate * resampleRatio,
  423. Convolution::Stereo::yes,
  424. Convolution::Trim::yes,
  425. Convolution::Normalise::no,
  426. resampled);
  427. }
  428. }
  429. beginTest ("Non-uniform convolutions work");
  430. {
  431. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  432. for (auto headSize : { spec.maximumBlockSize / 2, spec.maximumBlockSize, spec.maximumBlockSize * 9 })
  433. {
  434. testConvolution (spec,
  435. Convolution::NonUniform { static_cast<int> (headSize) },
  436. ramp,
  437. spec.sampleRate,
  438. Convolution::Stereo::yes,
  439. Convolution::Trim::yes,
  440. Convolution::Normalise::no,
  441. ramp);
  442. }
  443. }
  444. beginTest ("Convolutions with latency work");
  445. {
  446. const auto ramp = makeRamp (static_cast<int> (spec.maximumBlockSize) * 8);
  447. using BlockSize = decltype (spec.maximumBlockSize);
  448. for (auto latency : { static_cast<BlockSize> (0),
  449. spec.maximumBlockSize / 3,
  450. spec.maximumBlockSize,
  451. spec.maximumBlockSize * 2,
  452. static_cast<BlockSize> (spec.maximumBlockSize * 2.5) })
  453. {
  454. testConvolution (spec,
  455. Convolution::Latency { static_cast<int> (latency) },
  456. ramp,
  457. spec.sampleRate,
  458. Convolution::Stereo::yes,
  459. Convolution::Trim::yes,
  460. Convolution::Normalise::no,
  461. ramp);
  462. }
  463. }
  464. }
  465. };
  466. ConvolutionTest convolutionUnitTest;
  467. }
  468. }
  469. }
  470. #undef JUCE_FAIL_ON_ALLOCATION_IN_SCOPE