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.

373 lines
16KB

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