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.

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