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.

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