/* ============================================================================== PluginProcessor.cpp Created: 5 Sep 2023 Author: binya ============================================================================== */ #pragma once #include #include "PluginProcessor.h" #include "CustomLookAndFeel.h" #include "Components/Buttons.h" #include "Components/InputDeviceSelector.h" #include "Components/SampleEditor.h" #include "Components/FxChain.h" #include "Components/Prompt.h" #include "Components/SampleNavigator.h" /** Painting ordering requires a separate component... */ class EditorOverlay final : public CustomComponent { public: void setWaveformMode(bool isWaveformMode) { if (waveformModeAvailable != isWaveformMode) { waveformModeAvailable = isWaveformMode; repaint(); } } void setWaveformModeDisabled(bool disabled) { if (waveformModeDisabled != disabled) { waveformModeDisabled = disabled; repaint(); } } private: void paint(juce::Graphics& g) override; void resized() override; void lookAndFeelChanged() override; float scale(float value) const { return value * getWidth() / Layout::figmaWidth; } float scale(int value) const { return scale(float(value)); } //============================================================================== melatonin::DropShadow sampleControlShadow{ defaultTheme.slate.withAlpha(0.125f), 3, {2, 2} }; melatonin::DropShadow navControlShadow{ defaultTheme.slate.withAlpha(0.125f), 3, {-2, -2} }; bool waveformModeAvailable{ false }; bool waveformModeDisabled{ false }; }; //============================================================================== class JustaSampleAudioProcessorEditor final : public juce::AudioProcessorEditor, public juce::Timer, public juce::FileDragAndDropTarget, public juce::FilenameComponentListener, public CustomHelpTextDisplay, public ThemeProvider { public: explicit JustaSampleAudioProcessorEditor(JustaSampleAudioProcessor& audioProcessor); ~JustaSampleAudioProcessorEditor() override; private: void timerCallback() override; void paint(juce::Graphics&) override; void resized() override; void lookAndFeelChanged() override; void mouseDown(const juce::MouseEvent& event) override; void mouseUp(const juce::MouseEvent& event) override; void mouseDrag(const juce::MouseEvent& event) override; void mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) override; void helpTextChanged(const juce::String&) override {} void setTheme(Colors newTheme) override; Colors getTheme() const override; juce::Rectangle getConstrainedBounds() const; //============================================================================== /** Update the Editor to fit with the processor's sample. On the initial load, the SampleNavigator will not update the viewing bounds. */ void loadSample(); /** Handles the incoming recording messages from the recorder queue */ void handleActiveRecording(); /** If permitted, toggles whether the plugin state will use a file reference or store the samples directly. If necessary, this will prompt the user to save the current sample. */ void toggleLinkSample(); /** Signals to the processor to start recording, or first opens the device settings prompt if no valid input device is selected. Note that no editor changes occur here, instead it polls for changes in the timer callback (simpler than registering as a listener to the recorder). */ void startRecording(bool promptSettings = true); /** Starts a pitch detection prompt */ void promptPitchDetection(); /** Opens the device settings prompt */ void promptDeviceSettings(bool recordOnClose = false); /** We use this as a place to update the enablement and display of our plugin controls. */ void enablementChanged() override; void fxEnablementChanged(); //============================================================================== /** Whether the editor is interested in a file */ bool isInterestedInFile(const juce::String& file) const; bool isInterestedInFileDrag(const juce::StringArray& files) override; void filesDropped(const juce::StringArray& files, int x, int y) override; void filenameComponentChanged(juce::FilenameComponent* fileComponentThatHasChanged) override; void fileDragEnter(const juce::StringArray& files, int x, int y) override; void fileDragExit(const juce::StringArray& files) override; void updateLabel(const juce::String& text = ""); //============================================================================== /** Scaling the sizes in our Figma demo to percentages of width. This rounding operation is important to ensure consistent spacing with JUCE's integer component bounds. Otherwise, we'd just do a simple division. */ float scalei(int value) const { return scalei(float(value)); } float scalei(float value) const { return std::roundf(value * prompt.getWidth() / Layout::figmaWidth); } float scalef(float value) const { return value * prompt.getWidth() / Layout::figmaWidth; } //============================================================================== JustaSampleAudioProcessor& p; PluginParameters::State& pluginState; UIDummyParam dummyParam; const juce::OwnedArray& synthVoices; bool currentlyPlaying{ false }; /** Some thought is needed to keep the editor synchronized when changes to the sample occur or files are loaded. I decided the easiest way is to have the processor and editor both keep track of the buffer's hash. */ juce::String expectedHash{ 0 }; bool userDraggedSample{ false }; // We use this to reset the UI only when a sample loads as result of a user selection //============================================================================== juce::AudioBuffer pendingRecordingBuffer; int recordingBufferSize{ 0 }; //============================================================================== // Modules juce::Label tuningLabel, attackLabel, releaseLabel, playbackLabel, loopingLabel, masterLabel; juce::Array rotaries; // Tuning module CustomRotary semitoneRotary, centRotary, waveformSemitoneRotary, waveformCentRotary; CustomRotaryAttachment semitoneRotaryAttachment, centRotaryAttachment, waveformSemitoneRotaryAttachment, waveformCentRotaryAttachment; juce::Label tuningDetectLabel; CustomShapeButton tuningDetectButton; // Attack and release modules CustomRotary attackTimeRotary, attackCurve, releaseTimeRotary, releaseCurve; CustomRotaryAttachment attackTimeAttachment, attackCurveAttachment, releaseTimeAttachment, releaseCurveAttachment; EnvelopeSliderLookAndFeel attackCurveLNF; EnvelopeSliderLookAndFeel releaseCurveLNF; // Playback module CustomToggleableButton lofiModeButton; APVTS::ButtonAttachment lofiModeAttachment; CustomChoiceButton playbackModeButton; juce::ParameterAttachment playbackModeAttachment; CustomRotary playbackSpeedRotary; CustomRotaryAttachment playbackSpeedAttachment; // Loop module CustomToggleableButton loopButton, loopStartButton, loopEndButton; APVTS::ButtonAttachment loopAttachment, loopStartAttachment, loopEndAttachment; // Mixing module CustomToggleableButton monoOutputButton; APVTS::ButtonAttachment monoOutputAttachment; CustomRotary gainSlider; CustomRotaryAttachment gainSliderAttachment; VolumeSliderLookAndFeel gainSliderLNF; // Sample controls EditorOverlay editorOverlay; juce::FilenameComponent filenameComponent; CustomToggleableButton linkSampleToggle; // Whether the sample should be stored in the plugin state CustomToggleableButton waveformModeLabel; juce::ButtonParameterAttachment waveformModeLabelAttachment; CustomShapeButton playStopButton; juce::Path playPath, stopPath; const juce::String playHelpText{ "Play sample" }, stopHelpText{ "Halt voices" }; CustomShapeButton recordButton; CustomShapeButton deviceSettingsButton; InputDeviceSelector audioDeviceSettings; // Nav controls CustomShapeButton fitButton; CustomToggleableButton pinButton; ToggleButtonAttachment pinButtonAttachment; // Main components SampleEditor sampleEditor; SampleNavigator sampleNavigator; // Note that SampleNavigator manages ViewStart and ViewEnd FxChain fxChain; juce::Label statusLabel; bool fileDragging{ false }; // Some variables to help detect scroll gestures const int maxCallbacksSinceInZoomGesture{ 20 }; int zoomGestureEndCallbacks{ 0 }; bool mouseWheelDetected{ false }; int lastViewStart{ 0 }, lastViewEnd{ 0 }; // Footer juce::Label versionInfo; CustomToggleableButton preFXButton; APVTS::ButtonAttachment preFXAttachment; CustomToggleableButton showFXButton; ToggleButtonAttachment showFXAttachment; juce::ParameterAttachment eqEnablementAttachment, reverbEnablementAttachment, distortionEnablementAttachment, chorusEnablementAttachment; bool eqEnabled{ false }, reverbEnabled{ false }, distortionEnabled{ false }, chorusEnabled{ false }; Prompt prompt; CustomLookAndFeel& lnf; Colors theme{ defaultTheme }; juce::OpenGLContext openGLContext; juce::PluginHostType hostType; #if JUCE_DEBUG melatonin::Inspector inspector{ *this, false }; #endif JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JustaSampleAudioProcessorEditor) };