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.

298 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #pragma once
  20. #include "../JuceLibraryCode/JuceHeader.h"
  21. #include "IAAEffectProcessor.h"
  22. #include "SimpleMeter.h"
  23. class IAAEffectEditor : public AudioProcessorEditor,
  24. private IAAEffectProcessor::MeterListener,
  25. private Button::Listener,
  26. private Timer
  27. {
  28. public:
  29. IAAEffectEditor (IAAEffectProcessor& p,
  30. AudioProcessorValueTreeState& vts)
  31. : AudioProcessorEditor (p),
  32. processor (p),
  33. parameters (vts)
  34. {
  35. // Register for meter value updates.
  36. processor.addMeterListener (*this);
  37. gainSlider.setSliderStyle (Slider::SliderStyle::LinearVertical);
  38. gainSlider.setTextBoxStyle (Slider::TextEntryBoxPosition::TextBoxAbove, false, 60, 20);
  39. addAndMakeVisible (gainSlider);
  40. for (auto& meter : meters)
  41. addAndMakeVisible (meter);
  42. // Configure all the graphics for the transport control.
  43. transportText.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain));
  44. transportText.setJustificationType (Justification::topLeft);
  45. addChildComponent (transportText);
  46. Path rewindShape;
  47. rewindShape.addRectangle (0.0, 0.0, 5.0, buttonSize);
  48. rewindShape.addTriangle (0.0, buttonSize / 2, buttonSize, 0.0, buttonSize, buttonSize);
  49. rewindButton.setShape (rewindShape, true, true, false);
  50. rewindButton.addListener (this);
  51. addChildComponent (rewindButton);
  52. Path playShape;
  53. playShape.addTriangle (0.0, 0.0, 0.0, buttonSize, buttonSize, buttonSize / 2);
  54. playButton.setShape (playShape, true, true, false);
  55. playButton.addListener (this);
  56. addChildComponent (playButton);
  57. Path recordShape;
  58. recordShape.addEllipse (0.0, 0.0, buttonSize, buttonSize);
  59. recordButton.setShape (recordShape, true, true, false);
  60. recordButton.addListener (this);
  61. addChildComponent (recordButton);
  62. // Configure the switch to host button.
  63. switchToHostButtonLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain));
  64. switchToHostButtonLabel.setJustificationType (Justification::centredRight);
  65. switchToHostButtonLabel.setText ("Switch to\nhost app:", dontSendNotification);
  66. addChildComponent (switchToHostButtonLabel);
  67. switchToHostButton.addListener (this);
  68. addChildComponent (switchToHostButton);
  69. Rectangle<int> screenSize = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
  70. setSize (screenSize.getWidth(), screenSize.getHeight());
  71. resized();
  72. startTimerHz (60);
  73. }
  74. //==============================================================================
  75. void paint (Graphics& g) override
  76. {
  77. g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  78. }
  79. void resized() override
  80. {
  81. auto area = getBounds().reduced (20);
  82. gainSlider.setBounds (area.removeFromLeft (60));
  83. for (auto& meter : meters)
  84. {
  85. area.removeFromLeft (10);
  86. meter.setBounds (area.removeFromLeft (20));
  87. }
  88. area.removeFromLeft (20);
  89. transportText.setBounds (area.removeFromTop (120));
  90. auto navigationArea = area.removeFromTop (buttonSize);
  91. rewindButton.setTopLeftPosition (navigationArea.getPosition());
  92. navigationArea.removeFromLeft (buttonSize + 10);
  93. playButton.setTopLeftPosition (navigationArea.getPosition());
  94. navigationArea.removeFromLeft (buttonSize + 10);
  95. recordButton.setTopLeftPosition (navigationArea.getPosition());
  96. area.removeFromTop (30);
  97. auto appSwitchArea = area.removeFromTop (buttonSize);
  98. switchToHostButtonLabel.setBounds (appSwitchArea.removeFromLeft (100));
  99. appSwitchArea.removeFromLeft (5);
  100. switchToHostButton.setBounds (appSwitchArea.removeFromLeft (buttonSize));
  101. }
  102. private:
  103. //==============================================================================
  104. // Called from the audio thread.
  105. void handleNewMeterValue (int channel, float value) override
  106. {
  107. meters[(size_t) channel].update (value);
  108. }
  109. //==============================================================================
  110. void timerCallback () override
  111. {
  112. auto timeInfoSuccess = processor.updateCurrentTimeInfoFromHost (lastPosInfo);
  113. transportText.setVisible (timeInfoSuccess);
  114. if (timeInfoSuccess)
  115. updateTransportTextDisplay();
  116. updateTransportButtonsDisplay();
  117. updateSwitchToHostDisplay();
  118. }
  119. //==============================================================================
  120. void buttonClicked (Button* b) override
  121. {
  122. auto playHead = processor.getPlayHead();
  123. if (playHead != nullptr && playHead->canControlTransport())
  124. {
  125. if (b == &rewindButton)
  126. {
  127. playHead->transportRewind();
  128. }
  129. else if (b == &playButton)
  130. {
  131. playHead->transportPlay(! lastPosInfo.isPlaying);
  132. }
  133. else if (b == &recordButton)
  134. {
  135. playHead->transportRecord (! lastPosInfo.isRecording);
  136. }
  137. else if (b == &switchToHostButton)
  138. {
  139. PluginHostType hostType;
  140. hostType.switchToHostApplication();
  141. }
  142. }
  143. }
  144. //==============================================================================
  145. // quick-and-dirty function to format a timecode string
  146. String timeToTimecodeString (double seconds)
  147. {
  148. auto millisecs = roundToInt (seconds * 1000.0);
  149. auto absMillisecs = std::abs (millisecs);
  150. return String::formatted ("%02d:%02d:%02d.%03d",
  151. millisecs / 360000,
  152. (absMillisecs / 60000) % 60,
  153. (absMillisecs / 1000) % 60,
  154. absMillisecs % 1000);
  155. }
  156. // A quick-and-dirty function to format a bars/beats string.
  157. String quarterNotePositionToBarsBeatsString (double quarterNotes, int numerator, int denominator)
  158. {
  159. if (numerator == 0 || denominator == 0)
  160. return "1|1|000";
  161. auto quarterNotesPerBar = (numerator * 4 / denominator);
  162. auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * numerator;
  163. auto bar = ((int) quarterNotes) / quarterNotesPerBar + 1;
  164. auto beat = ((int) beats) + 1;
  165. auto ticks = ((int) (fmod (beats, 1.0) * 960.0 + 0.5));
  166. return String::formatted ("%d|%d|%03d", bar, beat, ticks);
  167. }
  168. void updateTransportTextDisplay()
  169. {
  170. MemoryOutputStream displayText;
  171. displayText << "[" << SystemStats::getJUCEVersion() << "]\n"
  172. << String (lastPosInfo.bpm, 2) << " bpm\n"
  173. << lastPosInfo.timeSigNumerator << '/' << lastPosInfo.timeSigDenominator << "\n"
  174. << timeToTimecodeString (lastPosInfo.timeInSeconds) << "\n"
  175. << quarterNotePositionToBarsBeatsString (lastPosInfo.ppqPosition,
  176. lastPosInfo.timeSigNumerator,
  177. lastPosInfo.timeSigDenominator) << "\n";
  178. if (lastPosInfo.isRecording)
  179. displayText << "(recording)";
  180. else if (lastPosInfo.isPlaying)
  181. displayText << "(playing)";
  182. transportText.setText (displayText.toString(), dontSendNotification);
  183. }
  184. void updateTransportButtonsDisplay()
  185. {
  186. auto visible = processor.getPlayHead() != nullptr
  187. && processor.getPlayHead()->canControlTransport();
  188. if (rewindButton.isVisible() != visible)
  189. {
  190. rewindButton.setVisible (visible);
  191. playButton.setVisible (visible);
  192. recordButton.setVisible (visible);
  193. }
  194. if (visible)
  195. {
  196. Colour playColour = lastPosInfo.isPlaying ? Colours::green : defaultButtonColour;
  197. playButton.setColours (playColour, playColour, playColour);
  198. playButton.repaint();
  199. Colour recordColour = lastPosInfo.isRecording ? Colours::red : defaultButtonColour;
  200. recordButton.setColours (recordColour, recordColour, recordColour);
  201. recordButton.repaint();
  202. }
  203. }
  204. void updateSwitchToHostDisplay()
  205. {
  206. PluginHostType hostType;
  207. const bool visible = hostType.isInterAppAudioConnected();
  208. if (switchToHostButtonLabel.isVisible() != visible)
  209. {
  210. switchToHostButtonLabel.setVisible (visible);
  211. switchToHostButton.setVisible (visible);
  212. if (visible) {
  213. auto icon = hostType.getHostIcon (buttonSize);
  214. switchToHostButton.setImages(false, true, true,
  215. icon, 1.0, Colours::transparentBlack,
  216. icon, 1.0, Colours::transparentBlack,
  217. icon, 1.0, Colours::transparentBlack);
  218. }
  219. }
  220. }
  221. IAAEffectProcessor& processor;
  222. AudioProcessorValueTreeState& parameters;
  223. const int buttonSize = 30;
  224. const Colour defaultButtonColour = Colours::darkgrey;
  225. ShapeButton rewindButton {"Rewind", defaultButtonColour, defaultButtonColour, defaultButtonColour};
  226. ShapeButton playButton {"Play", defaultButtonColour, defaultButtonColour, defaultButtonColour};
  227. ShapeButton recordButton {"Record", defaultButtonColour, defaultButtonColour, defaultButtonColour};
  228. Slider gainSlider;
  229. AudioProcessorValueTreeState::SliderAttachment gainAttachment = {parameters, "gain", gainSlider};
  230. std::array<SimpleMeter, 2> meters;
  231. ImageButton switchToHostButton;
  232. Label transportText, switchToHostButtonLabel;
  233. Image hostImage;
  234. AudioPlayHead::CurrentPositionInfo lastPosInfo;
  235. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IAAEffectEditor)
  236. };