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.

331 lines
10KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For the technical preview this file cannot be licensed commercially.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #pragma once
  14. #include "../Plugins/IOConfigurationWindow.h"
  15. inline String getFormatSuffix (const AudioProcessor* plugin)
  16. {
  17. const auto format = [plugin]()
  18. {
  19. if (auto* instance = dynamic_cast<const AudioPluginInstance*> (plugin))
  20. return instance->getPluginDescription().pluginFormatName;
  21. return String();
  22. }();
  23. return format.isNotEmpty() ? (" (" + format + ")") : format;
  24. }
  25. class PluginGraph;
  26. /**
  27. A window that shows a log of parameter change messages sent by the plugin.
  28. */
  29. class PluginDebugWindow : public AudioProcessorEditor,
  30. public AudioProcessorParameter::Listener,
  31. public ListBoxModel,
  32. public AsyncUpdater
  33. {
  34. public:
  35. PluginDebugWindow (AudioProcessor& proc)
  36. : AudioProcessorEditor (proc), audioProc (proc)
  37. {
  38. setSize (500, 200);
  39. addAndMakeVisible (list);
  40. for (auto* p : audioProc.getParameters())
  41. p->addListener (this);
  42. log.add ("Parameter debug log started");
  43. }
  44. ~PluginDebugWindow() override
  45. {
  46. for (auto* p : audioProc.getParameters())
  47. p->removeListener (this);
  48. }
  49. void parameterValueChanged (int parameterIndex, float newValue) override
  50. {
  51. auto* param = audioProc.getParameters()[parameterIndex];
  52. auto value = param->getCurrentValueAsText().quoted() + " (" + String (newValue, 4) + ")";
  53. appendToLog ("parameter change", *param, value);
  54. }
  55. void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) override
  56. {
  57. auto* param = audioProc.getParameters()[parameterIndex];
  58. appendToLog ("gesture", *param, gestureIsStarting ? "start" : "end");
  59. }
  60. private:
  61. void appendToLog (StringRef action, AudioProcessorParameter& param, StringRef value)
  62. {
  63. String entry (action + " " + param.getName (30).quoted() + " [" + String (param.getParameterIndex()) + "]: " + value);
  64. {
  65. ScopedLock lock (pendingLogLock);
  66. pendingLogEntries.add (entry);
  67. }
  68. triggerAsyncUpdate();
  69. }
  70. void resized() override
  71. {
  72. list.setBounds(getLocalBounds());
  73. }
  74. int getNumRows() override
  75. {
  76. return log.size();
  77. }
  78. void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool) override
  79. {
  80. g.setColour (getLookAndFeel().findColour (TextEditor::textColourId));
  81. if (isPositiveAndBelow (rowNumber, log.size()))
  82. g.drawText (log[rowNumber], Rectangle<int> { 0, 0, width, height }, Justification::left, true);
  83. }
  84. void handleAsyncUpdate() override
  85. {
  86. if (log.size() > logSizeTrimThreshold)
  87. log.removeRange (0, log.size() - maxLogSize);
  88. {
  89. ScopedLock lock (pendingLogLock);
  90. log.addArray (pendingLogEntries);
  91. pendingLogEntries.clear();
  92. }
  93. list.updateContent();
  94. list.scrollToEnsureRowIsOnscreen (log.size() - 1);
  95. }
  96. constexpr static const int maxLogSize = 300;
  97. constexpr static const int logSizeTrimThreshold = 400;
  98. ListBox list { "Log", this };
  99. StringArray log;
  100. StringArray pendingLogEntries;
  101. CriticalSection pendingLogLock;
  102. AudioProcessor& audioProc;
  103. };
  104. //==============================================================================
  105. /**
  106. A desktop window containing a plugin's GUI.
  107. */
  108. class PluginWindow : public DocumentWindow
  109. {
  110. public:
  111. enum class Type
  112. {
  113. normal = 0,
  114. generic,
  115. programs,
  116. audioIO,
  117. debug,
  118. numTypes
  119. };
  120. PluginWindow (AudioProcessorGraph::Node* n, Type t, OwnedArray<PluginWindow>& windowList)
  121. : DocumentWindow (n->getProcessor()->getName() + getFormatSuffix (n->getProcessor()),
  122. LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId),
  123. DocumentWindow::minimiseButton | DocumentWindow::closeButton),
  124. activeWindowList (windowList),
  125. node (n), type (t)
  126. {
  127. setSize (400, 300);
  128. if (auto* ui = createProcessorEditor (*node->getProcessor(), type))
  129. {
  130. setContentOwned (ui, true);
  131. setResizable (ui->isResizable(), false);
  132. }
  133. #if JUCE_IOS || JUCE_ANDROID
  134. auto screenBounds = Desktop::getInstance().getDisplays().getTotalBounds (true).toFloat();
  135. auto scaleFactor = jmin ((screenBounds.getWidth() - 50) / getWidth(), (screenBounds.getHeight() - 50) / getHeight());
  136. if (scaleFactor < 1.0f)
  137. setSize ((int) (getWidth() * scaleFactor), (int) (getHeight() * scaleFactor));
  138. setTopLeftPosition (20, 20);
  139. #else
  140. setTopLeftPosition (node->properties.getWithDefault (getLastXProp (type), Random::getSystemRandom().nextInt (500)),
  141. node->properties.getWithDefault (getLastYProp (type), Random::getSystemRandom().nextInt (500)));
  142. #endif
  143. node->properties.set (getOpenProp (type), true);
  144. setVisible (true);
  145. }
  146. ~PluginWindow() override
  147. {
  148. clearContentComponent();
  149. }
  150. void moved() override
  151. {
  152. node->properties.set (getLastXProp (type), getX());
  153. node->properties.set (getLastYProp (type), getY());
  154. }
  155. void closeButtonPressed() override
  156. {
  157. node->properties.set (getOpenProp (type), false);
  158. activeWindowList.removeObject (this);
  159. }
  160. static String getLastXProp (Type type) { return "uiLastX_" + getTypeName (type); }
  161. static String getLastYProp (Type type) { return "uiLastY_" + getTypeName (type); }
  162. static String getOpenProp (Type type) { return "uiopen_" + getTypeName (type); }
  163. OwnedArray<PluginWindow>& activeWindowList;
  164. const AudioProcessorGraph::Node::Ptr node;
  165. const Type type;
  166. BorderSize<int> getBorderThickness() override
  167. {
  168. #if JUCE_IOS || JUCE_ANDROID
  169. const int border = 10;
  170. return { border, border, border, border };
  171. #else
  172. return DocumentWindow::getBorderThickness();
  173. #endif
  174. }
  175. private:
  176. float getDesktopScaleFactor() const override { return 1.0f; }
  177. static AudioProcessorEditor* createProcessorEditor (AudioProcessor& processor,
  178. PluginWindow::Type type)
  179. {
  180. if (type == PluginWindow::Type::normal)
  181. {
  182. if (processor.hasEditor())
  183. if (auto* ui = processor.createEditorIfNeeded())
  184. return ui;
  185. type = PluginWindow::Type::generic;
  186. }
  187. if (type == PluginWindow::Type::generic) return new GenericAudioProcessorEditor (processor);
  188. if (type == PluginWindow::Type::programs) return new ProgramAudioProcessorEditor (processor);
  189. if (type == PluginWindow::Type::audioIO) return new IOConfigurationWindow (processor);
  190. if (type == PluginWindow::Type::debug) return new PluginDebugWindow (processor);
  191. jassertfalse;
  192. return {};
  193. }
  194. static String getTypeName (Type type)
  195. {
  196. switch (type)
  197. {
  198. case Type::normal: return "Normal";
  199. case Type::generic: return "Generic";
  200. case Type::programs: return "Programs";
  201. case Type::audioIO: return "IO";
  202. case Type::debug: return "Debug";
  203. case Type::numTypes:
  204. default: return {};
  205. }
  206. }
  207. //==============================================================================
  208. struct ProgramAudioProcessorEditor : public AudioProcessorEditor
  209. {
  210. ProgramAudioProcessorEditor (AudioProcessor& p) : AudioProcessorEditor (p)
  211. {
  212. setOpaque (true);
  213. addAndMakeVisible (panel);
  214. Array<PropertyComponent*> programs;
  215. auto numPrograms = p.getNumPrograms();
  216. int totalHeight = 0;
  217. for (int i = 0; i < numPrograms; ++i)
  218. {
  219. auto name = p.getProgramName (i).trim();
  220. if (name.isEmpty())
  221. name = "Unnamed";
  222. auto pc = new PropertyComp (name, p);
  223. programs.add (pc);
  224. totalHeight += pc->getPreferredHeight();
  225. }
  226. panel.addProperties (programs);
  227. setSize (400, jlimit (25, 400, totalHeight));
  228. }
  229. void paint (Graphics& g) override
  230. {
  231. g.fillAll (Colours::grey);
  232. }
  233. void resized() override
  234. {
  235. panel.setBounds (getLocalBounds());
  236. }
  237. private:
  238. struct PropertyComp : public PropertyComponent,
  239. private AudioProcessorListener
  240. {
  241. PropertyComp (const String& name, AudioProcessor& p) : PropertyComponent (name), owner (p)
  242. {
  243. owner.addListener (this);
  244. }
  245. ~PropertyComp() override
  246. {
  247. owner.removeListener (this);
  248. }
  249. void refresh() override {}
  250. void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {}
  251. void audioProcessorParameterChanged (AudioProcessor*, int, float) override {}
  252. AudioProcessor& owner;
  253. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyComp)
  254. };
  255. PropertyPanel panel;
  256. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramAudioProcessorEditor)
  257. };
  258. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginWindow)
  259. };