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.

278 lines
10KB

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