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.

387 lines
17KB

  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)
  23. : processor (plugin),
  24. dynamicInBuses (false),
  25. dynamicOutBuses (false),
  26. addDiscreteLayouts (markDiscreteLayoutsAsSupported)
  27. {
  28. }
  29. //==============================================================================
  30. // the first layout is the default layout
  31. struct SupportedBusLayouts
  32. {
  33. enum
  34. {
  35. pseudoChannelBitNum = 90 // use this bit index to check if plug-in really doesn't care about layouts
  36. };
  37. //==============================================================================
  38. SupportedBusLayouts() : defaultLayoutIndex (0), busIgnoresLayout (true), canBeDisabled (false) {}
  39. AudioChannelSet& getDefault() noexcept { return supportedLayouts.getReference (defaultLayoutIndex); }
  40. const AudioChannelSet& getDefault() const noexcept { return supportedLayouts.getReference (defaultLayoutIndex); }
  41. void updateDefaultLayout (const AudioChannelSet& defaultLayout) noexcept { defaultLayoutIndex = jmax (supportedLayouts.indexOf (defaultLayout), 0); }
  42. bool busSupportsNumChannels (int numChannels) const noexcept { return getDefaultLayoutForChannelNum (numChannels) != nullptr; }
  43. //==============================================================================
  44. const AudioChannelSet* getDefaultLayoutForChannelNum (int channelNum) const noexcept
  45. {
  46. const AudioChannelSet& dflt = getDefault();
  47. if (dflt.size() == channelNum)
  48. return &dflt;
  49. for (int i = 0; i < supportedLayouts.size(); ++i)
  50. {
  51. const AudioChannelSet& layout = supportedLayouts.getReference (i);
  52. if (layout.size() == channelNum)
  53. return &layout;
  54. }
  55. return nullptr;
  56. }
  57. int maxNumberOfChannels() const noexcept
  58. {
  59. int maxChannels = 0;
  60. for (int i = 0; i < supportedLayouts.size(); ++i)
  61. maxChannels = jmax (maxChannels, supportedLayouts.getReference (i).size());
  62. return maxChannels;
  63. }
  64. int defaultLayoutIndex;
  65. bool busIgnoresLayout, canBeDisabled, isEnabledByDefault;
  66. SortedSet<AudioChannelSet> supportedLayouts;
  67. };
  68. //==============================================================================
  69. AudioBusArray& getFilterBus (bool inputBus) noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; }
  70. const AudioBusArray& getFilterBus (bool inputBus) const noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; }
  71. int getBusCount (bool inputBus) const noexcept { return getFilterBus (inputBus).size(); }
  72. AudioChannelSet getChannelSet (bool inputBus, int bus) noexcept { return getFilterBus (inputBus).getReference (bus).channels; }
  73. int getNumChannels (bool inp, int bus) const noexcept { return isPositiveAndBelow (bus, getBusCount (inp)) ? getFilterBus (inp).getReference (bus).channels.size() : 0; }
  74. bool isBusEnabled (bool inputBus, int bus) const noexcept { return (getNumChannels (inputBus, bus) > 0); }
  75. bool hasInputs (int bus) const noexcept { return isBusEnabled (true, bus); }
  76. bool hasOutputs (int bus) const noexcept { return isBusEnabled (false, bus); }
  77. int getNumEnabledBuses (bool inputBus) const noexcept { int i; for (i = 0; i < getBusCount (inputBus); ++i) if (! isBusEnabled (inputBus, i)) break; return i; }
  78. int findTotalNumChannels (bool isInput, int busOffset = 0) const noexcept
  79. {
  80. int total = 0;
  81. const AudioBusArray& ioBuses = getFilterBus (isInput);
  82. for (int i = busOffset; i < ioBuses.size(); ++i)
  83. total += ioBuses.getReference (i).channels.size();
  84. return total;
  85. }
  86. //==============================================================================
  87. void restoreBusArrangement (const AudioProcessor::AudioBusArrangement& original) const
  88. {
  89. const int numInputBuses = getBusCount (true);
  90. const int numOutputBuses = getBusCount (false);
  91. jassert (original.inputBuses. size() == numInputBuses);
  92. jassert (original.outputBuses.size() == numOutputBuses);
  93. for (int busNr = 0; busNr < numInputBuses; ++busNr)
  94. processor.setPreferredBusArrangement (true, busNr, original.inputBuses.getReference (busNr).channels);
  95. for (int busNr = 0; busNr < numOutputBuses; ++busNr)
  96. processor.setPreferredBusArrangement (false, busNr, original.outputBuses.getReference (busNr).channels);
  97. }
  98. //==============================================================================
  99. Array<SupportedBusLayouts>& getSupportedLayouts (bool isInput) noexcept { return isInput ? inputLayouts : outputLayouts; }
  100. const Array<SupportedBusLayouts>& getSupportedLayouts (bool isInput) const noexcept { return isInput ? inputLayouts : outputLayouts; }
  101. SupportedBusLayouts& getSupportedBusLayouts (bool isInput, int busNr) noexcept { return getSupportedLayouts (isInput).getReference (busNr); }
  102. const SupportedBusLayouts& getSupportedBusLayouts (bool isInput, int busNr) const noexcept { return getSupportedLayouts (isInput).getReference (busNr); }
  103. bool busIgnoresLayout (bool inp, int bus) const noexcept
  104. {
  105. return isPositiveAndBelow (bus, getSupportedLayouts (inp).size()) ? getSupportedBusLayouts (inp, bus).busIgnoresLayout : true;
  106. }
  107. const AudioChannelSet& getDefaultLayoutForBus (bool isInput, int busIdx) const noexcept { return getSupportedBusLayouts (isInput, busIdx).getDefault(); }
  108. bool hasDynamicInBuses() const noexcept { return dynamicInBuses; }
  109. bool hasDynamicOutBuses() const noexcept { return dynamicOutBuses; }
  110. void clear (int inputCount, int outputCount)
  111. {
  112. inputLayouts.clear();
  113. inputLayouts.resize (inputCount);
  114. outputLayouts.clear();
  115. outputLayouts.resize (outputCount);
  116. }
  117. //==============================================================================
  118. AudioChannelSet getDefaultLayoutForChannelNumAndBus (bool isInput, int busIdx, int channelNum) const noexcept
  119. {
  120. if (const AudioChannelSet* set = getSupportedBusLayouts (isInput, busIdx).getDefaultLayoutForChannelNum (channelNum))
  121. return *set;
  122. return AudioChannelSet::canonicalChannelSet (channelNum);
  123. }
  124. void findAllCompatibleLayouts()
  125. {
  126. {
  127. ScopedBusRestorer restorer (*this);
  128. clear (getBusCount (true), getBusCount (false));
  129. for (int i = 0; i < getBusCount (true); ++i) findAllCompatibleLayoutsForBus (true, i);
  130. for (int i = 0; i < getBusCount (false); ++i) findAllCompatibleLayoutsForBus (false, i);
  131. }
  132. // find the defaults
  133. for (int i = 0; i < getBusCount (true); ++i)
  134. updateDefaultLayout (true, i);
  135. for (int i = 0; i < getBusCount (false); ++i)
  136. updateDefaultLayout (false, i);
  137. // can any of the buses be disabled/enabled
  138. dynamicInBuses = doesPlugInHaveDynamicBuses (true);
  139. dynamicOutBuses = doesPlugInHaveDynamicBuses (false);
  140. }
  141. //==============================================================================
  142. void enableAllBuses()
  143. {
  144. for (int busIdx = 1; busIdx < getBusCount (true); ++busIdx)
  145. if (getChannelSet (true, busIdx) == AudioChannelSet::disabled())
  146. processor.setPreferredBusArrangement (true, busIdx, getDefaultLayoutForBus (true, busIdx));
  147. for (int busIdx = 1; busIdx < getBusCount (false); ++busIdx)
  148. if (getChannelSet (false, busIdx) == AudioChannelSet::disabled())
  149. processor.setPreferredBusArrangement (false, busIdx, getDefaultLayoutForBus (false, busIdx));
  150. }
  151. //==============================================================================
  152. // Helper class which restores the original arrangement when it leaves scope
  153. class ScopedBusRestorer
  154. {
  155. public:
  156. ScopedBusRestorer (PluginBusUtilities& bUtils)
  157. : busUtils (bUtils),
  158. originalArr (bUtils.processor.busArrangement),
  159. shouldRestore (true)
  160. {}
  161. ~ScopedBusRestorer()
  162. {
  163. if (shouldRestore)
  164. busUtils.restoreBusArrangement (originalArr);
  165. }
  166. void release() noexcept { shouldRestore = false; }
  167. private:
  168. PluginBusUtilities& busUtils;
  169. const AudioProcessor::AudioBusArrangement originalArr;
  170. bool shouldRestore;
  171. JUCE_DECLARE_NON_COPYABLE (ScopedBusRestorer)
  172. };
  173. //==============================================================================
  174. AudioProcessor& processor;
  175. private:
  176. friend class ScopedBusRestorer;
  177. //==============================================================================
  178. Array<SupportedBusLayouts> inputLayouts, outputLayouts;
  179. bool dynamicInBuses, dynamicOutBuses, addDiscreteLayouts;
  180. //==============================================================================
  181. bool busIgnoresLayoutForChannelNum (bool isInput, int busNr, int channelNum)
  182. {
  183. AudioChannelSet set;
  184. // If the plug-in does not complain about setting it's layout to an undefined layout
  185. // then we assume that the plug-in ignores the layout alltogether
  186. for (int i = 0; i < channelNum; ++i)
  187. set.addChannel (static_cast<AudioChannelSet::ChannelType> (SupportedBusLayouts::pseudoChannelBitNum + i));
  188. return processor.setPreferredBusArrangement (isInput, busNr, set);
  189. }
  190. void findAllCompatibleLayoutsForBus (bool isInput, int busNr)
  191. {
  192. const int maxNumChannels = 9;
  193. SupportedBusLayouts& layouts = getSupportedBusLayouts (isInput, busNr);
  194. layouts.supportedLayouts.clear();
  195. // check if the plug-in bus can be disabled
  196. layouts.canBeDisabled = processor.setPreferredBusArrangement (isInput, busNr, AudioChannelSet());
  197. layouts.busIgnoresLayout = true;
  198. for (int i = 1; i <= maxNumChannels; ++i)
  199. {
  200. const bool ignoresLayoutForChannel = busIgnoresLayoutForChannelNum (isInput, busNr, i);
  201. Array<AudioChannelSet> sets = layoutListCompatibleWithChannelCount (addDiscreteLayouts, i);
  202. for (int j = 0; j < sets.size(); ++j)
  203. {
  204. const AudioChannelSet& layout = sets.getReference (j);
  205. if (processor.setPreferredBusArrangement (isInput, busNr, layout))
  206. {
  207. if (! ignoresLayoutForChannel)
  208. layouts.busIgnoresLayout = false;
  209. layouts.supportedLayouts.add (layout);
  210. }
  211. }
  212. }
  213. // You cannot add a bus in your processor wich does not support any layouts! It must at least support one.
  214. jassert (layouts.supportedLayouts.size() > 0);
  215. }
  216. bool doesPlugInHaveDynamicBuses (bool isInput) const
  217. {
  218. for (int i = 0; i < getBusCount (isInput); ++i)
  219. if (getSupportedBusLayouts (isInput, i).canBeDisabled)
  220. return true;
  221. return false;
  222. }
  223. void updateDefaultLayout (bool isInput, int busIdx)
  224. {
  225. SupportedBusLayouts& layouts = getSupportedBusLayouts (isInput, busIdx);
  226. AudioChannelSet set = getChannelSet (isInput, busIdx);
  227. layouts.isEnabledByDefault = (set.size() > 0);
  228. if (layouts.isEnabledByDefault)
  229. layouts.supportedLayouts.add (set);
  230. // If you hit this assertion then you are disabling the main bus by default
  231. // which is unsupported
  232. jassert (layouts.isEnabledByDefault || busIdx >= 0);
  233. if (set == AudioChannelSet())
  234. {
  235. const bool mainBusHasInputs = hasInputs (0);
  236. const bool mainBusHasOutputs = hasOutputs (0);
  237. if (busIdx != 0 && (mainBusHasInputs || mainBusHasOutputs))
  238. {
  239. // the AudioProcessor does not give us any default layout
  240. // for an aux bus. Use the same number of channels as the
  241. // default layout on the main bus as a sensible default for
  242. // the aux bus
  243. const bool useInput = mainBusHasInputs && mainBusHasOutputs ? isInput : mainBusHasInputs;
  244. const AudioChannelSet& dfltLayout = getSupportedBusLayouts (useInput, 0).getDefault();
  245. if ((layouts.defaultLayoutIndex = layouts.supportedLayouts.indexOf (dfltLayout)) >= 0)
  246. return;
  247. // no exact match: try at least to match the number of channels
  248. for (int i = 0; i < layouts.supportedLayouts.size(); ++i)
  249. {
  250. if (layouts.supportedLayouts.getReference (i).size() == dfltLayout.size())
  251. {
  252. layouts.defaultLayoutIndex = i;
  253. return;
  254. }
  255. }
  256. }
  257. if (layouts.busIgnoresLayout)
  258. set = AudioChannelSet::discreteChannels (set.size());
  259. }
  260. layouts.updateDefaultLayout (set);
  261. }
  262. static Array<AudioChannelSet> layoutListCompatibleWithChannelCount (bool addDiscrete, const int channelCount) noexcept
  263. {
  264. jassert (channelCount > 0);
  265. Array<AudioChannelSet> sets;
  266. if (addDiscrete)
  267. sets.add (AudioChannelSet::discreteChannels (channelCount));
  268. switch (channelCount)
  269. {
  270. case 1:
  271. sets.add (AudioChannelSet::mono());
  272. break;
  273. case 2:
  274. sets.add (AudioChannelSet::stereo());
  275. break;
  276. case 3:
  277. sets.add (AudioChannelSet::createLCR());
  278. break;
  279. case 4:
  280. sets.add (AudioChannelSet::createLCRS());
  281. sets.add (AudioChannelSet::quadraphonic());
  282. sets.add (AudioChannelSet::ambisonic());
  283. break;
  284. case 5:
  285. sets.add (AudioChannelSet::pentagonal());
  286. sets.add (AudioChannelSet::create5point0());
  287. break;
  288. case 6:
  289. sets.add (AudioChannelSet::hexagonal());
  290. sets.add (AudioChannelSet::create5point1());
  291. sets.add (AudioChannelSet::create6point0());
  292. break;
  293. case 7:
  294. sets.add (AudioChannelSet::create6point1());
  295. sets.add (AudioChannelSet::create7point0());
  296. sets.add (AudioChannelSet::createFront7point0());
  297. break;
  298. case 8:
  299. sets.add (AudioChannelSet::octagonal());
  300. sets.add (AudioChannelSet::create7point1());
  301. sets.add (AudioChannelSet::createFront7point1());
  302. break;
  303. }
  304. return sets;
  305. }
  306. JUCE_DECLARE_NON_COPYABLE (PluginBusUtilities)
  307. };