justasample/Source/PluginEditor.h
2026-07-23 23:40:48 +02:00

250 lines
9.7 KiB
C++

/*
==============================================================================
PluginProcessor.cpp
Created: 5 Sep 2023
Author: binya
==============================================================================
*/
#pragma once
#include <JuceHeader.h>
#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<int> 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<CustomSamplerVoice>& 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<float> pendingRecordingBuffer;
int recordingBufferSize{ 0 };
//==============================================================================
// Modules
juce::Label tuningLabel, attackLabel, releaseLabel, playbackLabel, loopingLabel, masterLabel;
juce::Array<juce::Slider*> 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<EnvelopeSlider::attack> attackCurveLNF;
EnvelopeSliderLookAndFeel<EnvelopeSlider::release> 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)
};