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.

537 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. struct PluginBusUtilities
  18. {
  19. //==============================================================================
  20. typedef Array<AudioProcessor::AudioProcessorBus> AudioBusArray;
  21. //==============================================================================
  22. PluginBusUtilities (AudioProcessor& plugin, bool markDiscreteLayoutsAsSupported, int maxProbeChannels = kDefaultMaxChannels)
  23. : processor (plugin),
  24. dynamicInBuses (false),
  25. dynamicOutBuses (false),
  26. plugInFormatSupportsDiscreteLayouts (markDiscreteLayoutsAsSupported),
  27. maxChannelsToProbe (maxProbeChannels)
  28. {
  29. }
  30. // this will invoke setPreferredLayout many times
  31. void init() { populateLayoutDetails(); }
  32. //==============================================================================
  33. // Useful short-cuts
  34. AudioBusArray& getFilterBus (bool inputBus) noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; }
  35. const AudioBusArray& getFilterBus (bool inputBus) const noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; }
  36. int getBusCount (bool inputBus) const noexcept { return getFilterBus (inputBus).size(); }
  37. AudioChannelSet getChannelSet (bool inp, int bus) noexcept { return isPositiveAndBelow (bus, getBusCount (inp)) ? getFilterBus (inp).getReference (bus).channels : AudioChannelSet(); }
  38. int getNumChannels (bool inp, int bus) const noexcept { return isPositiveAndBelow (bus, getBusCount (inp)) ? getFilterBus (inp).getReference (bus).channels.size() : 0; }
  39. bool isBusEnabled (bool inputBus, int bus) const noexcept { return (getNumChannels (inputBus, bus) > 0); }
  40. bool hasInputs (int bus) const noexcept { return isBusEnabled (true, bus); }
  41. bool hasOutputs (int bus) const noexcept { return isBusEnabled (false, bus); }
  42. bool hasDynamicInBuses() const noexcept { return dynamicInBuses; }
  43. bool hasDynamicOutBuses() const noexcept { return dynamicOutBuses; }
  44. //==============================================================================
  45. // Channel Counters
  46. int getNumEnabledBuses (bool inputBus) const noexcept { int i; for (i = 0; i < getBusCount (inputBus); ++i) if (! isBusEnabled (inputBus, i)) break; return i; }
  47. int findTotalNumChannels (bool isInput, int busOffset = 0) const noexcept
  48. {
  49. int total = 0;
  50. const AudioBusArray& ioBuses = getFilterBus (isInput);
  51. for (int i = busOffset; i < ioBuses.size(); ++i)
  52. total += ioBuses.getReference (i).channels.size();
  53. return total;
  54. }
  55. int getBusIdxForChannelIdx (bool isInput, int channelIdx, int& totalChannels, int startBusIdx)
  56. {
  57. const int numBuses = getBusCount (isInput);
  58. for (int busIdx = startBusIdx; busIdx < numBuses; ++busIdx)
  59. {
  60. const int numChannels = getNumChannels (isInput, busIdx);
  61. if ((totalChannels + numChannels) > channelIdx)
  62. return busIdx;
  63. totalChannels += numChannels;
  64. }
  65. return -1;
  66. }
  67. int getBusIdxForChannelIdx (bool isInput, int channelIdx)
  68. {
  69. int totalChannels = 0;
  70. return getBusIdxForChannelIdx (isInput, channelIdx, totalChannels, 0);
  71. }
  72. //==============================================================================
  73. // Bus properties & defaults
  74. bool busIgnoresLayout (bool inp, int bus) const noexcept
  75. {
  76. return isPositiveAndBelow (bus, getLayoutDetails (inp).size()) ? getBusLayoutDetails (inp, bus).busIgnoresLayout : true;
  77. }
  78. bool busCanBeDisabled (bool inp, int bus) const noexcept
  79. {
  80. return isPositiveAndBelow (bus, getLayoutDetails (inp).size()) ? getBusLayoutDetails (inp, bus).canBeDisabled : false;
  81. }
  82. bool isBusEnabledByDefault (bool inp, int bus) const noexcept
  83. {
  84. return isPositiveAndBelow (bus, getLayoutDetails (inp).size()) ? getBusLayoutDetails (inp, bus).isEnabledByDefault : true;
  85. }
  86. bool checkBusFormatsAreNotDiscrete() const { return (checkBusFormatsAreNotDiscrete (true) && checkBusFormatsAreNotDiscrete (false)); }
  87. const AudioChannelSet& getDefaultLayoutForBus (bool isInput, int busIdx) const noexcept { return getBusLayoutDetails (isInput, busIdx).defaultLayout; }
  88. AudioChannelSet getDefaultLayoutForChannelNumAndBus (bool isInput, int busIdx, int channelNum) const noexcept
  89. {
  90. if (busIdx < 0 || busIdx >= getBusCount (isInput) || channelNum == 0)
  91. return AudioChannelSet::disabled();
  92. const BusLayoutDetails& layouts = getBusLayoutDetails (isInput, busIdx);
  93. const AudioChannelSet& dflt = layouts.defaultLayout;
  94. const AudioChannelSet discreteChannels = AudioChannelSet::discreteChannels (channelNum);
  95. if (dflt.size() == channelNum && (plugInFormatSupportsDiscreteLayouts || (! dflt.isDiscreteLayout())))
  96. return dflt;
  97. Array<AudioChannelSet> potentialLayouts = layoutListCompatibleWithChannelCount (channelNum);
  98. ScopedBusRestorer busRestorer (*this);
  99. // prefer non-discrete layouts if no explicit default layout is given
  100. const int n = potentialLayouts.size();
  101. for (int i = 0; i < n; ++i)
  102. {
  103. const AudioChannelSet& layout = potentialLayouts.getReference (i);
  104. if (processor.setPreferredBusArrangement (isInput, busIdx, layout))
  105. return layout;
  106. }
  107. if (plugInFormatSupportsDiscreteLayouts && processor.setPreferredBusArrangement (isInput, busIdx, discreteChannels))
  108. return discreteChannels;
  109. // we are out of options, bail out
  110. return AudioChannelSet();
  111. }
  112. //==============================================================================
  113. // This function is quite heavy so please cache the return value
  114. int findMaxNumberOfChannelsForBus (bool isInput, int busNr, int upperlimit = std::numeric_limits<int>::max())
  115. {
  116. int maxChannelsPreprocessorDefs = -1;
  117. #ifdef JucePlugin_MaxNumInputChannels
  118. if (isInput)
  119. maxChannelsPreprocessorDefs = jmin (upperlimit, JucePlugin_MaxNumInputChannels);
  120. #endif
  121. #ifdef JucePlugin_MaxNumOutputChannels
  122. if (! isInput)
  123. maxChannelsPreprocessorDefs = jmin (upperlimit, JucePlugin_MaxNumOutputChannels);
  124. #endif
  125. #ifdef JucePlugin_PreferredChannelConfigurations
  126. if (busNr == 0)
  127. {
  128. int maxChannelCount = 0;
  129. const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations };
  130. const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelConfigs);
  131. for (int i = 0; i < numChannelConfigs; ++i)
  132. {
  133. const int numChannels = channelConfigs [i][isInput ? 0 : 1];
  134. if (numChannels < 0)
  135. return -1;
  136. maxChannelCount = jmax (maxChannelCount, numChannels);
  137. }
  138. return jmin (upperlimit, maxChannelCount);
  139. }
  140. #endif
  141. ScopedBusRestorer busRestorer (*this);
  142. if (plugInFormatSupportsDiscreteLayouts &&
  143. processor.setPreferredBusArrangement(isInput, busNr, AudioChannelSet::discreteChannels (insaneNumberOfChannels)))
  144. return -1; // no limit in channels
  145. int n = maxChannelsPreprocessorDefs > 0 ? maxChannelsPreprocessorDefs
  146. : (plugInFormatSupportsDiscreteLayouts ? maxChannelsToProbe
  147. : maxNumChannelsOfNonDiscreteLayouts);
  148. n = jmin (upperlimit, n);
  149. for (int i = n; i > 0; --i)
  150. {
  151. if (plugInFormatSupportsDiscreteLayouts && processor.setPreferredBusArrangement (isInput, busNr, AudioChannelSet::discreteChannels (i)))
  152. return i;
  153. Array<AudioChannelSet> sets = layoutListCompatibleWithChannelCount (i);
  154. for (int j = 0; j < sets.size(); ++j)
  155. {
  156. const AudioChannelSet& layout = sets.getReference (j);
  157. if (processor.setPreferredBusArrangement (isInput, busNr, layout))
  158. return i;
  159. }
  160. }
  161. return 0;
  162. }
  163. //==============================================================================
  164. void restoreBusArrangement (const AudioProcessor::AudioBusArrangement& original) const
  165. {
  166. const int numInputBuses = getBusCount (true);
  167. const int numOutputBuses = getBusCount (false);
  168. jassert (original.inputBuses. size() == numInputBuses);
  169. jassert (original.outputBuses.size() == numOutputBuses);
  170. for (int busNr = 0; busNr < numInputBuses; ++busNr)
  171. processor.setPreferredBusArrangement (true, busNr, original.inputBuses.getReference (busNr).channels);
  172. for (int busNr = 0; busNr < numOutputBuses; ++busNr)
  173. processor.setPreferredBusArrangement (false, busNr, original.outputBuses.getReference (busNr).channels);
  174. }
  175. void enableAllBuses()
  176. {
  177. for (int busIdx = 1; busIdx < getBusCount (true); ++busIdx)
  178. if (getChannelSet (true, busIdx) == AudioChannelSet::disabled())
  179. processor.setPreferredBusArrangement (true, busIdx, getDefaultLayoutForBus (true, busIdx));
  180. for (int busIdx = 1; busIdx < getBusCount (false); ++busIdx)
  181. if (getChannelSet (false, busIdx) == AudioChannelSet::disabled())
  182. processor.setPreferredBusArrangement (false, busIdx, getDefaultLayoutForBus (false, busIdx));
  183. }
  184. //==============================================================================
  185. // Helper class which restores the original arrangement when it leaves scope
  186. class ScopedBusRestorer
  187. {
  188. public:
  189. ScopedBusRestorer (const PluginBusUtilities& bUtils)
  190. : busUtils (bUtils),
  191. originalArr (bUtils.processor.busArrangement),
  192. shouldRestore (true)
  193. {}
  194. ~ScopedBusRestorer()
  195. {
  196. if (shouldRestore)
  197. busUtils.restoreBusArrangement (originalArr);
  198. }
  199. void release() noexcept { shouldRestore = false; }
  200. private:
  201. const PluginBusUtilities& busUtils;
  202. const AudioProcessor::AudioBusArrangement originalArr;
  203. bool shouldRestore;
  204. JUCE_DECLARE_NON_COPYABLE (ScopedBusRestorer)
  205. };
  206. //==============================================================================
  207. AudioProcessor& processor;
  208. enum
  209. {
  210. kDefaultMaxChannels = 64
  211. };
  212. private:
  213. friend class ScopedBusRestorer;
  214. enum
  215. {
  216. maxNumChannelsOfNonDiscreteLayouts = 8, // surround 7.1 has the maximum amount of channels
  217. pseudoChannelBitNum = 90, // use this bit index to check if plug-in really doesn't care about layouts
  218. insaneNumberOfChannels = 512
  219. };
  220. //==============================================================================
  221. // the first layout is the default layout
  222. struct BusLayoutDetails
  223. {
  224. BusLayoutDetails() : busIgnoresLayout (true), canBeDisabled (false), isEnabledByDefault (false) {}
  225. AudioChannelSet defaultLayout;
  226. bool busIgnoresLayout, canBeDisabled, isEnabledByDefault;
  227. };
  228. Array<BusLayoutDetails>& getLayoutDetails (bool isInput) noexcept { return isInput ? inputLayouts : outputLayouts; }
  229. const Array<BusLayoutDetails>& getLayoutDetails (bool isInput) const noexcept { return isInput ? inputLayouts : outputLayouts; }
  230. BusLayoutDetails& getBusLayoutDetails (bool isInput, int busNr) noexcept { return getLayoutDetails (isInput).getReference (busNr); }
  231. const BusLayoutDetails& getBusLayoutDetails (bool isInput, int busNr) const noexcept { return getLayoutDetails (isInput).getReference (busNr); }
  232. //==============================================================================
  233. Array<BusLayoutDetails> inputLayouts, outputLayouts;
  234. bool dynamicInBuses, dynamicOutBuses, plugInFormatSupportsDiscreteLayouts;
  235. int maxChannelsToProbe;
  236. //==============================================================================
  237. void populateLayoutDetails()
  238. {
  239. clear (getBusCount (true), getBusCount (false));
  240. // save the default layouts
  241. for (int i = 0; i < getBusCount (true); ++i)
  242. getBusLayoutDetails (true, i).defaultLayout = getChannelSet (true, i);
  243. for (int i = 0; i < getBusCount (false); ++i)
  244. getBusLayoutDetails (false, i).defaultLayout = getChannelSet (false, i);
  245. {
  246. ScopedBusRestorer restorer (*this);
  247. for (int i = 0; i < getBusCount (true); ++i) addLayoutDetails (true, i);
  248. for (int i = 0; i < getBusCount (false); ++i) addLayoutDetails (false, i);
  249. // find the defaults
  250. for (int i = 0; i < getBusCount (true); ++i)
  251. updateDefaultLayout (true, i);
  252. for (int i = 0; i < getBusCount (false); ++i)
  253. updateDefaultLayout (false, i);
  254. }
  255. // can any of the buses be disabled/enabled
  256. dynamicInBuses = doesPlugInHaveDynamicBuses (true);
  257. dynamicOutBuses = doesPlugInHaveDynamicBuses (false);
  258. }
  259. //==============================================================================
  260. bool busIgnoresLayoutForChannelNum (bool isInput, int busNr, int channelNum)
  261. {
  262. AudioChannelSet set;
  263. // If the plug-in does not complain about setting it's layout to an undefined layout
  264. // then we assume that the plug-in ignores the layout altogether
  265. for (int i = 0; i < channelNum; ++i)
  266. set.addChannel (static_cast<AudioChannelSet::ChannelType> (pseudoChannelBitNum + i));
  267. return processor.setPreferredBusArrangement (isInput, busNr, set);
  268. }
  269. void addLayoutDetails (bool isInput, int busNr)
  270. {
  271. BusLayoutDetails& layouts = getBusLayoutDetails (isInput, busNr);
  272. // check if the plug-in bus can be disabled
  273. layouts.canBeDisabled = processor.setPreferredBusArrangement (isInput, busNr, AudioChannelSet());
  274. layouts.busIgnoresLayout = true;
  275. for (int i = 1; i <= maxNumChannelsOfNonDiscreteLayouts; ++i)
  276. {
  277. const bool ignoresLayoutForChannel = busIgnoresLayoutForChannelNum (isInput, busNr, i);
  278. Array<AudioChannelSet> sets = layoutListCompatibleWithChannelCount (i);
  279. for (int j = 0; j < sets.size(); ++j)
  280. {
  281. const AudioChannelSet& layout = sets.getReference (j);
  282. if (processor.setPreferredBusArrangement (isInput, busNr, layout))
  283. {
  284. if (! ignoresLayoutForChannel)
  285. {
  286. layouts.busIgnoresLayout = false;
  287. return;
  288. }
  289. }
  290. }
  291. }
  292. }
  293. bool doesPlugInHaveDynamicBuses (bool isInput) const
  294. {
  295. for (int i = 0; i < getBusCount (isInput); ++i)
  296. if (getBusLayoutDetails (isInput, i).canBeDisabled)
  297. return true;
  298. return false;
  299. }
  300. bool checkBusFormatsAreNotDiscrete (bool isInput) const
  301. {
  302. const int n = getBusCount (isInput);
  303. const Array<AudioProcessor::AudioProcessorBus>& bus = isInput ? processor.busArrangement.inputBuses
  304. : processor.busArrangement.outputBuses;
  305. for (int busIdx = 0; busIdx < n; ++busIdx)
  306. if (bus.getReference (busIdx).channels.isDiscreteLayout())
  307. return false;
  308. return true;
  309. }
  310. void updateDefaultLayout (bool isInput, int busIdx)
  311. {
  312. BusLayoutDetails& layouts = getBusLayoutDetails (isInput, busIdx);
  313. AudioChannelSet& dfltLayout = layouts.defaultLayout;
  314. layouts.isEnabledByDefault = (dfltLayout.size() > 0);
  315. // If you hit this assertion then you are disabling the main bus by default
  316. // which is unsupported
  317. jassert (layouts.isEnabledByDefault || busIdx >= 0);
  318. if ((! plugInFormatSupportsDiscreteLayouts) && dfltLayout.isDiscreteLayout())
  319. {
  320. // The default layout is a discrete channel layout, yet some plug-in formats (VST-3)
  321. // do not support this format. We need to find a different default with the same
  322. // number of channels
  323. dfltLayout = getDefaultLayoutForChannelNumAndBus (isInput, busIdx, dfltLayout.size());
  324. }
  325. // are we done?
  326. if (dfltLayout != AudioChannelSet())
  327. return;
  328. const bool mainBusHasInputs = hasInputs (0);
  329. const bool mainBusHasOutputs = hasOutputs (0);
  330. if (busIdx != 0 && (mainBusHasInputs || mainBusHasOutputs))
  331. {
  332. // the AudioProcessor does not give us any default layout
  333. // for an aux bus. Use the same number of channels as the
  334. // default layout on the main bus as a sensible default for
  335. // the aux bus
  336. const bool useInput = mainBusHasInputs && mainBusHasOutputs ? isInput : mainBusHasInputs;
  337. dfltLayout = getBusLayoutDetails (useInput, 0).defaultLayout;
  338. const int numChannels = dfltLayout.size();
  339. const AudioChannelSet discreteChannelLayout = AudioChannelSet::discreteChannels (numChannels);
  340. if ((plugInFormatSupportsDiscreteLayouts || dfltLayout != discreteChannelLayout) &&
  341. processor.setPreferredBusArrangement (isInput, busIdx, dfltLayout))
  342. return;
  343. // no exact match: try at least to match the number of channels
  344. dfltLayout = getDefaultLayoutForChannelNumAndBus (isInput, busIdx, dfltLayout.size());
  345. if (dfltLayout != AudioChannelSet())
  346. return;
  347. }
  348. // check stereo first as this is often the more sensible default than mono
  349. if (processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = AudioChannelSet::stereo())))
  350. return;
  351. if (plugInFormatSupportsDiscreteLayouts &&
  352. processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = AudioChannelSet::discreteChannels (2))))
  353. return;
  354. // let's guess
  355. for (int numChans = 1; numChans < findMaxNumberOfChannelsForBus (isInput, busIdx); ++numChans)
  356. {
  357. Array<AudioChannelSet> sets = layoutListCompatibleWithChannelCount (numChans);
  358. for (int j = 0; j < sets.size(); ++j)
  359. if (processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = sets.getReference (j))))
  360. return;
  361. if (plugInFormatSupportsDiscreteLayouts &&
  362. processor.setPreferredBusArrangement (isInput, busIdx, (dfltLayout = AudioChannelSet::discreteChannels (numChans))))
  363. return;
  364. }
  365. // Your bus must support at least a single possible layout
  366. jassertfalse;
  367. }
  368. void clear (int inputCount, int outputCount)
  369. {
  370. inputLayouts.clear();
  371. inputLayouts.resize (inputCount);
  372. outputLayouts.clear();
  373. outputLayouts.resize (outputCount);
  374. }
  375. //==============================================================================
  376. static Array<AudioChannelSet> layoutListCompatibleWithChannelCount (const int channelCount) noexcept
  377. {
  378. jassert (channelCount > 0);
  379. Array<AudioChannelSet> sets;
  380. switch (channelCount)
  381. {
  382. case 1:
  383. sets.add (AudioChannelSet::mono());
  384. break;
  385. case 2:
  386. sets.add (AudioChannelSet::stereo());
  387. break;
  388. case 3:
  389. sets.add (AudioChannelSet::createLCR());
  390. sets.add (AudioChannelSet::createLRS());
  391. break;
  392. case 4:
  393. sets.add (AudioChannelSet::createLCRS());
  394. sets.add (AudioChannelSet::quadraphonic());
  395. sets.add (AudioChannelSet::ambisonic());
  396. break;
  397. case 5:
  398. sets.add (AudioChannelSet::pentagonal());
  399. sets.add (AudioChannelSet::create5point0());
  400. break;
  401. case 6:
  402. sets.add (AudioChannelSet::hexagonal());
  403. sets.add (AudioChannelSet::create5point1());
  404. sets.add (AudioChannelSet::create6point0());
  405. sets.add (AudioChannelSet::create6point0Music());
  406. break;
  407. case 7:
  408. sets.add (AudioChannelSet::create6point1());
  409. sets.add (AudioChannelSet::create7point0());
  410. break;
  411. case 8:
  412. sets.add (AudioChannelSet::octagonal());
  413. sets.add (AudioChannelSet::create7point1());
  414. sets.add (AudioChannelSet::create7point1AC3());
  415. sets.add (AudioChannelSet::createFront7point1());
  416. break;
  417. }
  418. return sets;
  419. }
  420. JUCE_DECLARE_NON_COPYABLE (PluginBusUtilities)
  421. };