191 lines
6.7 KiB
C++
191 lines
6.7 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE framework examples.
|
|
Copyright (c) Raw Material Software Limited
|
|
|
|
The code included in this file is provided under the terms of the ISC license
|
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
|
to use, copy, modify, and/or distribute this software for any purpose with or
|
|
without fee is hereby granted provided that the above copyright notice and
|
|
this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
class ADSRComponent final : public Component
|
|
{
|
|
public:
|
|
ADSRComponent()
|
|
: envelope { *this }
|
|
{
|
|
for (Slider* slider : { &adsrAttack, &adsrDecay, &adsrSustain, &adsrRelease })
|
|
{
|
|
if (slider == &adsrSustain)
|
|
{
|
|
slider->textFromValueFunction = [slider] (double value)
|
|
{
|
|
String text;
|
|
|
|
text << slider->getName();
|
|
|
|
const auto val = (int) jmap (value, 0.0, 1.0, 0.0, 100.0);
|
|
text << String::formatted (": %d%%", val);
|
|
|
|
return text;
|
|
};
|
|
}
|
|
else
|
|
{
|
|
slider->textFromValueFunction = [slider] (double value)
|
|
{
|
|
String text;
|
|
|
|
text << slider->getName();
|
|
|
|
text << ": " << ((value < 0.4f) ? String::formatted ("%dms", (int) std::round (value * 1000))
|
|
: String::formatted ("%0.2lf Sec", value));
|
|
|
|
return text;
|
|
};
|
|
|
|
slider->setSkewFactor (0.3);
|
|
}
|
|
|
|
slider->setRange (0, 1);
|
|
slider->setTextBoxStyle (Slider::TextBoxBelow, true, 300, 25);
|
|
slider->onValueChange = [this]
|
|
{
|
|
NullCheckedInvocation::invoke (onChange);
|
|
repaint();
|
|
};
|
|
|
|
addAndMakeVisible (slider);
|
|
}
|
|
|
|
adsrAttack.setName ("Attack");
|
|
adsrDecay.setName ("Decay");
|
|
adsrSustain.setName ("Sustain");
|
|
adsrRelease.setName ("Release");
|
|
|
|
adsrAttack.setValue (0.1, dontSendNotification);
|
|
adsrDecay.setValue (0.3, dontSendNotification);
|
|
adsrSustain.setValue (0.3, dontSendNotification);
|
|
adsrRelease.setValue (0.2, dontSendNotification);
|
|
|
|
addAndMakeVisible (envelope);
|
|
}
|
|
|
|
std::function<void()> onChange;
|
|
|
|
ADSR::Parameters getParameters() const
|
|
{
|
|
return
|
|
{
|
|
(float) adsrAttack.getValue(),
|
|
(float) adsrDecay.getValue(),
|
|
(float) adsrSustain.getValue(),
|
|
(float) adsrRelease.getValue(),
|
|
};
|
|
}
|
|
|
|
void resized() final
|
|
{
|
|
auto bounds = getLocalBounds();
|
|
|
|
const auto knobWidth = bounds.getWidth() / 4;
|
|
auto knobBounds = bounds.removeFromBottom (bounds.getHeight() / 2);
|
|
{
|
|
adsrAttack.setBounds (knobBounds.removeFromLeft (knobWidth));
|
|
adsrDecay.setBounds (knobBounds.removeFromLeft (knobWidth));
|
|
adsrSustain.setBounds (knobBounds.removeFromLeft (knobWidth));
|
|
adsrRelease.setBounds (knobBounds.removeFromLeft (knobWidth));
|
|
}
|
|
|
|
envelope.setBounds (bounds);
|
|
}
|
|
|
|
Slider adsrAttack { Slider::RotaryVerticalDrag, Slider::TextBoxBelow };
|
|
Slider adsrDecay { Slider::RotaryVerticalDrag, Slider::TextBoxBelow };
|
|
Slider adsrSustain { Slider::RotaryVerticalDrag, Slider::TextBoxBelow };
|
|
Slider adsrRelease { Slider::RotaryVerticalDrag, Slider::TextBoxBelow };
|
|
|
|
private:
|
|
class Envelope final : public Component
|
|
{
|
|
public:
|
|
Envelope (ADSRComponent& adsr) : parent { adsr } {}
|
|
|
|
void paint (Graphics& g) final
|
|
{
|
|
const auto env = parent.getParameters();
|
|
|
|
// sustain isn't a length but we use a fixed value here to give
|
|
// sustain some visual width in the envelope
|
|
constexpr auto sustainLength = 0.1;
|
|
|
|
const auto adsrLength = env.attack
|
|
+ env.decay
|
|
+ sustainLength
|
|
+ env.release;
|
|
|
|
auto bounds = getLocalBounds().toFloat();
|
|
|
|
const auto attackWidth = bounds.proportionOfWidth (env.attack / adsrLength);
|
|
const auto decayWidth = bounds.proportionOfWidth (env.decay / adsrLength);
|
|
const auto sustainWidth = bounds.proportionOfWidth (sustainLength / adsrLength);
|
|
const auto releaseWidth = bounds.proportionOfWidth (env.release / adsrLength);
|
|
const auto sustainHeight = bounds.proportionOfHeight (1 - env.sustain);
|
|
|
|
const auto attackBounds = bounds.removeFromLeft (attackWidth);
|
|
const auto decayBounds = bounds.removeFromLeft (decayWidth);
|
|
const auto sustainBounds = bounds.removeFromLeft (sustainWidth);
|
|
const auto releaseBounds = bounds.removeFromLeft (releaseWidth);
|
|
|
|
g.setColour (Colours::black.withAlpha (0.1f));
|
|
g.fillRect (bounds);
|
|
|
|
const auto alpha = 0.4f;
|
|
|
|
g.setColour (Colour (246, 98, 92).withAlpha (alpha));
|
|
g.fillRect (attackBounds);
|
|
|
|
g.setColour (Colour (242, 187, 60).withAlpha (alpha));
|
|
g.fillRect (decayBounds);
|
|
|
|
g.setColour (Colour (109, 234, 166).withAlpha (alpha));
|
|
g.fillRect (sustainBounds);
|
|
|
|
g.setColour (Colour (131, 61, 183).withAlpha (alpha));
|
|
g.fillRect (releaseBounds);
|
|
|
|
Path envelopePath;
|
|
envelopePath.startNewSubPath (attackBounds.getBottomLeft());
|
|
envelopePath.lineTo (decayBounds.getTopLeft());
|
|
envelopePath.lineTo (sustainBounds.getX(), sustainHeight);
|
|
envelopePath.lineTo (releaseBounds.getX(), sustainHeight);
|
|
envelopePath.lineTo (releaseBounds.getBottomRight());
|
|
|
|
const auto lineThickness = 4.0f;
|
|
|
|
g.setColour (Colours::white);
|
|
g.strokePath (envelopePath, PathStrokeType { lineThickness });
|
|
}
|
|
|
|
private:
|
|
ADSRComponent& parent;
|
|
};
|
|
|
|
Envelope envelope;
|
|
};
|