#ifndef IAAEFFECTEDITOR_H_INCLUDED #define IAAEFFECTEDITOR_H_INCLUDED #include "../JuceLibraryCode/JuceHeader.h" #include "IAAEffectProcessor.h" #include "SimpleMeter.h" class IAAEffectEditor : public AudioProcessorEditor, private IAAEffectProcessor::MeterListener, private ButtonListener, private Timer { public: IAAEffectEditor (IAAEffectProcessor& p, AudioProcessorValueTreeState& vts) : AudioProcessorEditor (p), processor (p), parameters (vts) { // Register for meter value updates. processor.addMeterListener (*this); gainSlider.setSliderStyle (Slider::SliderStyle::LinearVertical); gainSlider.setTextBoxStyle (Slider::TextEntryBoxPosition::TextBoxAbove, false, 60, 20); addAndMakeVisible (gainSlider); for (auto& meter : meters) addAndMakeVisible (meter); // Configure all the graphics for the transport control. transportText.setColour (Label::textColourId, Colours::white); transportText.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain)); transportText.setJustificationType (Justification::topLeft); addChildComponent (transportText); Path rewindShape; rewindShape.addRectangle (0.0, 0.0, 5.0, buttonSize); rewindShape.addTriangle (0.0, buttonSize / 2, buttonSize, 0.0, buttonSize, buttonSize); rewindButton.setShape (rewindShape, true, true, false); rewindButton.addListener (this); addChildComponent (rewindButton); Path playShape; playShape.addTriangle (0.0, 0.0, 0.0, buttonSize, buttonSize, buttonSize / 2); playButton.setShape (playShape, true, true, false); playButton.addListener (this); addChildComponent (playButton); Path recordShape; recordShape.addEllipse (0.0, 0.0, buttonSize, buttonSize); recordButton.setShape (recordShape, true, true, false); recordButton.addListener (this); addChildComponent (recordButton); // Configure the switch to host button. switchToHostButtonLabel.setColour (Label::textColourId, Colours::white); switchToHostButtonLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain)); switchToHostButtonLabel.setJustificationType (Justification::centredRight); switchToHostButtonLabel.setText ("Switch to\nhost app:", dontSendNotification); addChildComponent (switchToHostButtonLabel); switchToHostButton.addListener (this); addChildComponent (switchToHostButton); Rectangle screenSize = Desktop::getInstance().getDisplays().getMainDisplay().userArea; setSize (screenSize.getWidth(), screenSize.getHeight()); resized(); startTimerHz (60); } //============================================================================== void paint (Graphics& g) override { g.fillAll (Colours::darkgrey); } void resized() override { auto area = getBounds().reduced (10); gainSlider.setBounds (area.removeFromLeft (60)); for (auto& meter : meters) { area.removeFromLeft (10); meter.setBounds (area.removeFromLeft (20)); } area.removeFromLeft (20); transportText.setBounds (area.removeFromTop (120)); auto navigationArea = area.removeFromTop (buttonSize); rewindButton.setTopLeftPosition (navigationArea.getPosition()); navigationArea.removeFromLeft (buttonSize + 10); playButton.setTopLeftPosition (navigationArea.getPosition()); navigationArea.removeFromLeft (buttonSize + 10); recordButton.setTopLeftPosition (navigationArea.getPosition()); area.removeFromTop (30); auto appSwitchArea = area.removeFromTop (buttonSize); switchToHostButtonLabel.setBounds (appSwitchArea.removeFromLeft (100)); appSwitchArea.removeFromLeft (5); switchToHostButton.setBounds (appSwitchArea.removeFromLeft (buttonSize)); } private: //============================================================================== // Called from the audio thread. void handleNewMeterValue (int channel, float value) override { meters[(size_t) channel].update (value); } //============================================================================== void timerCallback () override { auto timeInfoSuccess = processor.updateCurrentTimeInfoFromHost (lastPosInfo); transportText.setVisible (timeInfoSuccess); if (timeInfoSuccess) updateTransportTextDisplay(); updateTransportButtonsDisplay(); updateSwitchToHostDisplay(); } //============================================================================== void buttonClicked (Button* b) override { auto playHead = processor.getPlayHead(); if (playHead != nullptr && playHead->canControlTransport()) { if (b == &rewindButton) { playHead->transportRewind(); } else if (b == &playButton) { playHead->transportPlay(! lastPosInfo.isPlaying); } else if (b == &recordButton) { playHead->transportRecord (! lastPosInfo.isRecording); } else if (b == &switchToHostButton) { PluginHostType hostType; hostType.switchToHostApplication(); } } } //============================================================================== // quick-and-dirty function to format a timecode string String timeToTimecodeString (double seconds) { auto millisecs = roundToInt (seconds * 1000.0); auto absMillisecs = std::abs (millisecs); return String::formatted ("%02d:%02d:%02d.%03d", millisecs / 360000, (absMillisecs / 60000) % 60, (absMillisecs / 1000) % 60, absMillisecs % 1000); } // A quick-and-dirty function to format a bars/beats string. String quarterNotePositionToBarsBeatsString (double quarterNotes, int numerator, int denominator) { if (numerator == 0 || denominator == 0) return "1|1|000"; auto quarterNotesPerBar = (numerator * 4 / denominator); auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * numerator; auto bar = ((int) quarterNotes) / quarterNotesPerBar + 1; auto beat = ((int) beats) + 1; auto ticks = ((int) (fmod (beats, 1.0) * 960.0 + 0.5)); return String::formatted ("%d|%d|%03d", bar, beat, ticks); } void updateTransportTextDisplay() { MemoryOutputStream displayText; displayText << "[" << SystemStats::getJUCEVersion() << "]\n" << String (lastPosInfo.bpm, 2) << " bpm\n" << lastPosInfo.timeSigNumerator << '/' << lastPosInfo.timeSigDenominator << "\n" << timeToTimecodeString (lastPosInfo.timeInSeconds) << "\n" << quarterNotePositionToBarsBeatsString (lastPosInfo.ppqPosition, lastPosInfo.timeSigNumerator, lastPosInfo.timeSigDenominator) << "\n"; if (lastPosInfo.isRecording) displayText << "(recording)"; else if (lastPosInfo.isPlaying) displayText << "(playing)"; transportText.setText (displayText.toString(), dontSendNotification); } void updateTransportButtonsDisplay() { auto visible = processor.getPlayHead() != nullptr && processor.getPlayHead()->canControlTransport(); if (rewindButton.isVisible() != visible) { rewindButton.setVisible (visible); playButton.setVisible (visible); recordButton.setVisible (visible); } if (visible) { Colour playColour = lastPosInfo.isPlaying ? Colours::green : defaultButtonColour; playButton.setColours (playColour, playColour, playColour); playButton.repaint(); Colour recordColour = lastPosInfo.isRecording ? Colours::red : defaultButtonColour; recordButton.setColours (recordColour, recordColour, recordColour); recordButton.repaint(); } } void updateSwitchToHostDisplay() { PluginHostType hostType; const bool visible = hostType.isInterAppAudioConnected(); if (switchToHostButtonLabel.isVisible() != visible) { switchToHostButtonLabel.setVisible (visible); switchToHostButton.setVisible (visible); if (visible) { auto icon = hostType.getHostIcon (buttonSize); switchToHostButton.setImages(false, true, true, icon, 1.0, Colours::transparentBlack, icon, 1.0, Colours::transparentBlack, icon, 1.0, Colours::transparentBlack); } } } IAAEffectProcessor& processor; AudioProcessorValueTreeState& parameters; const int buttonSize = 30; const Colour defaultButtonColour = Colours::lightgrey; ShapeButton rewindButton {"Rewind", defaultButtonColour, defaultButtonColour, defaultButtonColour}; ShapeButton playButton {"Play", defaultButtonColour, defaultButtonColour, defaultButtonColour}; ShapeButton recordButton {"Record", defaultButtonColour, defaultButtonColour, defaultButtonColour}; Slider gainSlider; AudioProcessorValueTreeState::SliderAttachment gainAttachment = {parameters, "gain", gainSlider}; std::array meters; ImageButton switchToHostButton; Label transportText, switchToHostButtonLabel; Image hostImage; AudioPlayHead::CurrentPositionInfo lastPosInfo; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IAAEffectEditor) }; #endif // IAAEFFECTEDITOR_H_INCLUDED