kopia lustrzana https://github.com/jameshball/osci-render
1866 wiersze
46 KiB
C++
1866 wiersze
46 KiB
C++
// $Id$
|
|
// $HeadURL$
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
This file is part of the UGEN++ library
|
|
Copyright 2008-11 The University of the West of England.
|
|
by Martin Robinson
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
UGEN++ can be redistributed and/or modified under the terms of the
|
|
GNU General Public License, as published by the Free Software Foundation;
|
|
either version 2 of the License, or (at your option) any later version.
|
|
either version 2 of the License, or (at your option) any later version.
|
|
|
|
UGEN++ is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with UGEN++; if not, visit www.gnu.org/licenses or write to the
|
|
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
|
Boston, MA 02111-1307 USA
|
|
|
|
The idea for this project and code in the UGen implementations is
|
|
derived from SuperCollider which is also released under the
|
|
GNU General Public License:
|
|
|
|
SuperCollider real time audio synthesis system
|
|
Copyright (c) 2002 James McCartney. All rights reserved.
|
|
http://www.audiosynth.com
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#ifndef UGEN_NOEXTGPL
|
|
|
|
#include "ugen_JuceEnvelopeComponent.h"
|
|
|
|
|
|
EnvelopeHandleComponentConstrainer::EnvelopeHandleComponentConstrainer(EnvelopeHandleComponent* handleOwner)
|
|
: leftLimit(0),
|
|
rightLimit(0xffffff),
|
|
handle(handleOwner)
|
|
{
|
|
}
|
|
|
|
void EnvelopeHandleComponentConstrainer::checkBounds (juce::Rectangle<int>& bounds,
|
|
const juce::Rectangle<int>& old, const juce::Rectangle<int>& limits,
|
|
bool isStretchingTop, bool isStretchingLeft,
|
|
bool isStretchingBottom, bool isStretchingRight)
|
|
{
|
|
|
|
ComponentBoundsConstrainer::checkBounds(bounds,
|
|
old,limits,
|
|
isStretchingTop,isStretchingLeft,
|
|
isStretchingBottom,isStretchingRight);
|
|
|
|
// prevent this handle moving before the previous point
|
|
// or after the next point
|
|
bounds.setPosition(juce::jlimit(leftLimit, rightLimit, bounds.getX()), bounds.getY());
|
|
|
|
|
|
// then use the handle to access the envelope to then quantise x+y..
|
|
|
|
// EnvelopeComponent* env = handle->getParentComponent();
|
|
//
|
|
// if(env)
|
|
// {
|
|
// double domain = env->convertPixelsToDomain(bounds.getX());
|
|
// double value = env->convertPixelsToValue(bounds.getY());
|
|
//
|
|
// domain = env->quantiseDomain(domain);
|
|
// value = env->quantiseValue(value);
|
|
//
|
|
// bounds.setPosition(env->convertDomainToPixels(domain),
|
|
// env->convertValueToPixels(value));
|
|
// }
|
|
}
|
|
|
|
|
|
void EnvelopeHandleComponentConstrainer::setAdjacentHandleLimits(int setLeftLimit, int setRightLimit)
|
|
{
|
|
leftLimit = setLeftLimit;
|
|
rightLimit = setRightLimit;
|
|
}
|
|
|
|
EnvelopeHandleComponent::EnvelopeHandleComponent()
|
|
: dontUpdateTimeAndValue(false),
|
|
lastX(-1),
|
|
lastY(-1),
|
|
resizeLimits(this),
|
|
shouldLockTime(false),
|
|
shouldLockValue(false),
|
|
shouldDraw(!shouldLockTime || !shouldLockValue),
|
|
ignoreDrag(false)
|
|
{
|
|
|
|
if (shouldDraw) {
|
|
setMouseCursor(juce::MouseCursor::DraggingHandCursor);
|
|
}
|
|
resetOffsets();
|
|
}
|
|
|
|
EnvelopeComponent* EnvelopeHandleComponent::getParentComponent() const
|
|
{
|
|
return (EnvelopeComponent*)Component::getParentComponent();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::updateTimeAndValue()
|
|
{
|
|
bool envChanged = false;
|
|
|
|
if (shouldLockTime)
|
|
{
|
|
setTopLeftPosition(getParentComponent()->convertDomainToPixels(time),
|
|
getY());
|
|
}
|
|
else {
|
|
envChanged = true;
|
|
time = getParentComponent()->convertPixelsToDomain(getX());
|
|
}
|
|
|
|
if (shouldLockValue)
|
|
{
|
|
setTopLeftPosition(getX(),
|
|
getParentComponent()->convertValueToPixels(value));
|
|
}
|
|
else {
|
|
envChanged = true;
|
|
value = getParentComponent()->convertPixelsToValue(getY());
|
|
}
|
|
|
|
if (envChanged == true) {
|
|
((EnvelopeComponent*)getParentComponent())->sendChangeMessage();
|
|
}
|
|
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::updateTimeAndValue(%f, %f)\n", time, value);
|
|
#endif
|
|
}
|
|
|
|
void EnvelopeHandleComponent::updateLegend()
|
|
{
|
|
EnvelopeComponent *env = getParentComponent();
|
|
EnvelopeLegendComponent* legend = env->getLegend();
|
|
|
|
if(legend == 0) return;
|
|
|
|
juce::String text;
|
|
|
|
int width = getParentWidth();
|
|
int places;
|
|
|
|
if (width >= 115) {
|
|
places = 3;
|
|
} else if (width >= 100) {
|
|
places = 2;
|
|
} else {
|
|
places = 1;
|
|
}
|
|
|
|
if (env->getAdsrMode()) {
|
|
int index = env->getHandleIndex(this);
|
|
Env envelope = env->getEnv();
|
|
|
|
double envTime = envelope.getTimes()[index - 1];
|
|
|
|
if (index == 1) {
|
|
text = "Attack time (s): " + juce::String(legend->mapTime(envTime), places);
|
|
text << ", Attack level: " << juce::String(legend->mapValue(value), places);
|
|
} else if (index == 2) {
|
|
text = "Decay time (s): " + juce::String(legend->mapTime(envTime), places);
|
|
text << ", Sustain level: " << juce::String(legend->mapValue(value), places);
|
|
} else {
|
|
text = "Release time (s): " + juce::String(legend->mapTime(envTime), places);
|
|
}
|
|
} else {
|
|
if (width >= 165) {
|
|
if (env && env->isLoopNode(this))
|
|
text << "(Loop) ";
|
|
else if (env && env->isReleaseNode(this))
|
|
text << "(Release) ";
|
|
else
|
|
text << "Point ";
|
|
} else if (width >= 140) {
|
|
text << "Point ";
|
|
} else if (width >= 115) {
|
|
text << "Pt ";
|
|
} else if (width >= 100) {
|
|
text << "Pt ";
|
|
} else if (width >= 85) {
|
|
text << "Pt ";
|
|
} else if (width >= 65) {
|
|
text << "P ";
|
|
}
|
|
|
|
text << (getHandleIndex())
|
|
<< ": "
|
|
<< juce::String(legend->mapTime(time), places) << legend->getTimeUnits()
|
|
<< ", "
|
|
<< juce::String(legend->mapValue(value), places) << legend->getValueUnits();
|
|
}
|
|
|
|
getParentComponent()->setLegendText(text);
|
|
}
|
|
|
|
void EnvelopeHandleComponent::paint(juce::Graphics& g)
|
|
{
|
|
if (shouldDraw) {
|
|
EnvelopeComponent *env = getParentComponent();
|
|
juce::Colour handleColour;
|
|
|
|
if(env == 0)
|
|
{
|
|
handleColour = juce::Colour(0xFF69B4FF);
|
|
}
|
|
else if(env->isReleaseNode(this))
|
|
{
|
|
handleColour = findColour(EnvelopeComponent::ReleaseNode);
|
|
}
|
|
else if(env->isLoopNode(this))
|
|
{
|
|
handleColour = findColour(EnvelopeComponent::LoopNode);
|
|
}
|
|
else
|
|
{
|
|
handleColour = findColour(EnvelopeComponent::Node);
|
|
}
|
|
|
|
g.setColour(handleColour);
|
|
g.fillEllipse(1, 1, getWidth() - 2, getHeight() - 2);
|
|
g.setColour(findColour(EnvelopeComponent::NodeOutline));
|
|
g.drawEllipse(1, 1, getWidth() - 2, getHeight() - 2, 1.0f);
|
|
}
|
|
}
|
|
|
|
void EnvelopeHandleComponent::moved()
|
|
{
|
|
if(dontUpdateTimeAndValue == false)
|
|
updateTimeAndValue();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::mouseMove(const juce::MouseEvent& e)
|
|
{
|
|
(void)e;
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::mouseMove\n");
|
|
#endif
|
|
}
|
|
|
|
void EnvelopeHandleComponent::mouseEnter(const juce::MouseEvent& e)
|
|
{
|
|
(void)e;
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::mouseEnter\n");
|
|
#endif
|
|
|
|
if (shouldDraw) {
|
|
setMouseCursor(juce::MouseCursor::DraggingHandCursor);
|
|
updateLegend();
|
|
} else {
|
|
setMouseCursor(juce::MouseCursor::NormalCursor);
|
|
}
|
|
}
|
|
|
|
void EnvelopeHandleComponent::mouseExit(const juce::MouseEvent& e)
|
|
{
|
|
(void)e;
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::mouseExit\n");
|
|
#endif
|
|
|
|
getParentComponent()->setLegendTextToDefault();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::mouseDown(const juce::MouseEvent& e)
|
|
{
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::mouseDown (%d, %d)\n", e.x, e.y);
|
|
#endif
|
|
|
|
if (shouldDraw) {
|
|
setMouseCursor(juce::MouseCursor::NoCursor);
|
|
|
|
if(e.mods.isShiftDown()) {
|
|
|
|
if(!shouldLockTime && !shouldLockValue)
|
|
{
|
|
getParentComponent()->setLegendTextToDefault();
|
|
removeThisHandle();
|
|
}
|
|
|
|
return; // dont send drag msg
|
|
|
|
}
|
|
//else if(e.mods.isCtrlDown())
|
|
//{
|
|
// if(getParentComponent()->getAllowNodeEditing())
|
|
// {
|
|
// ignoreDrag = true;
|
|
//
|
|
// if(PopupComponent::getActivePopups() < 1)
|
|
// {
|
|
// EnvelopeNodePopup::create(this, getScreenX()+e.x, getScreenY()+e.y);
|
|
// }
|
|
// }
|
|
//}
|
|
else
|
|
{
|
|
|
|
offsetX = e.x;
|
|
offsetY = e.y;
|
|
|
|
resizeLimits.setMinimumOnscreenAmounts(HANDLESIZE,HANDLESIZE,HANDLESIZE,HANDLESIZE);
|
|
|
|
EnvelopeHandleComponent* previousHandle = getPreviousHandle();
|
|
EnvelopeHandleComponent* nextHandle = getNextHandle();
|
|
|
|
int leftLimit = previousHandle == 0 ? 0 : previousHandle->getX()+2;
|
|
int rightLimit = nextHandle == 0 ? getParentWidth()-HANDLESIZE : nextHandle->getX()-2;
|
|
// int leftLimit = previousHandle == 0 ? 0 : previousHandle->getX();
|
|
// int rightLimit = nextHandle == 0 ? getParentWidth()-HANDLESIZE : nextHandle->getX();
|
|
|
|
|
|
resizeLimits.setAdjacentHandleLimits(leftLimit, rightLimit);
|
|
|
|
dragger.startDraggingComponent(this, e);//&resizeLimits);
|
|
|
|
}
|
|
|
|
getParentComponent()->sendStartDrag();
|
|
}
|
|
}
|
|
|
|
void EnvelopeHandleComponent::mouseDrag(const juce::MouseEvent& e)
|
|
{
|
|
if(ignoreDrag || !shouldDraw) return;
|
|
|
|
dragger.dragComponent(this, e, &resizeLimits);
|
|
|
|
updateLegend();
|
|
getParentComponent()->repaint();
|
|
|
|
if(lastX == getX() && lastY == getY()) {
|
|
setMousePositionToThisHandle();
|
|
#ifdef MYDEBUG
|
|
int x, y;
|
|
Desktop::getMousePosition(x, y);
|
|
printf("screen pos (and mouse pos): %d (%d) %d (%d)\n", getScreenX(), x, getScreenY(), y);
|
|
#endif
|
|
}
|
|
|
|
lastX = getX();
|
|
lastY = getY();
|
|
|
|
#ifdef MYDEBUG
|
|
double domain = getParentComponent()->convertPixelsToDomain(getX());
|
|
double value = getParentComponent()->convertPixelsToValue(getY());
|
|
printf("MyEnvelopeHandleComponent::mouseDrag(%d, %d) [%f, %f] (%d, %d)\n", e.x, e.y,
|
|
domain,
|
|
value,
|
|
getParentComponent()->convertDomainToPixels(domain),
|
|
getParentComponent()->convertValueToPixels(value));
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
void EnvelopeHandleComponent::mouseUp(const juce::MouseEvent& e)
|
|
{
|
|
(void)e;
|
|
EnvelopeComponent *env = getParentComponent();
|
|
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::mouseUp\n");
|
|
#endif
|
|
|
|
if (!shouldDraw) {
|
|
goto exit;
|
|
}
|
|
|
|
if(ignoreDrag)
|
|
{
|
|
ignoreDrag = false;
|
|
goto exit;
|
|
}
|
|
|
|
// if(e.mods.isCtrlDown() == false)
|
|
// {
|
|
env->quantiseHandle(this);
|
|
// }
|
|
|
|
setMouseCursor(juce::MouseCursor::DraggingHandCursor);
|
|
setMousePositionToThisHandle();
|
|
|
|
offsetX = 0;
|
|
offsetY = 0;
|
|
|
|
exit:
|
|
getParentComponent()->sendEndDrag();
|
|
}
|
|
|
|
|
|
EnvelopeHandleComponent* EnvelopeHandleComponent::getPreviousHandle() const
|
|
{
|
|
return getParentComponent()->getPreviousHandle(this);
|
|
}
|
|
|
|
EnvelopeHandleComponent* EnvelopeHandleComponent::getNextHandle() const
|
|
{
|
|
return getParentComponent()->getNextHandle(this);
|
|
}
|
|
|
|
void EnvelopeHandleComponent::removeThisHandle()
|
|
{
|
|
getParentComponent()->removeHandle(this);
|
|
}
|
|
|
|
void EnvelopeHandleComponent::setMousePositionToThisHandle()
|
|
{
|
|
juce::Desktop::setMousePosition(juce::Point<int>(getScreenX()+offsetX, getScreenY()+offsetY));
|
|
}
|
|
|
|
int EnvelopeHandleComponent::getHandleIndex() const
|
|
{
|
|
return getParentComponent()->getHandleIndex(const_cast<EnvelopeHandleComponent*>(this));
|
|
}
|
|
|
|
void EnvelopeHandleComponent::setTime(double timeToSet)
|
|
{
|
|
bool oldDontUpdateTimeAndValue = dontUpdateTimeAndValue;
|
|
dontUpdateTimeAndValue = true;
|
|
|
|
time = constrainDomain(timeToSet);
|
|
|
|
setTopLeftPosition(getParentComponent()->convertDomainToPixels(time),
|
|
getY());
|
|
|
|
dontUpdateTimeAndValue = oldDontUpdateTimeAndValue;
|
|
|
|
getParentComponent()->repaint();
|
|
((EnvelopeComponent*)getParentComponent())->sendChangeMessage();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::setValue(double valueToSet)
|
|
{
|
|
bool oldDontUpdateTimeAndValue = dontUpdateTimeAndValue;
|
|
dontUpdateTimeAndValue = true;
|
|
|
|
value = constrainValue(valueToSet);
|
|
|
|
setTopLeftPosition(getX(),
|
|
getParentComponent()->convertValueToPixels(value));
|
|
|
|
dontUpdateTimeAndValue = oldDontUpdateTimeAndValue;
|
|
|
|
getParentComponent()->repaint();
|
|
((EnvelopeComponent*)getParentComponent())->sendChangeMessage();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::setCurve(EnvCurve curveToSet)
|
|
{
|
|
curve = curveToSet;
|
|
getParentComponent()->repaint();
|
|
((EnvelopeComponent*)getParentComponent())->sendChangeMessage();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::setTimeAndValue(double timeToSet, double valueToSet, double quantise)
|
|
{
|
|
bool oldDontUpdateTimeAndValue = dontUpdateTimeAndValue;
|
|
dontUpdateTimeAndValue = true;
|
|
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::setTimeAndValue original (%f, %f)\n", timeToSet, valueToSet);
|
|
#endif
|
|
|
|
if(quantise > 0.0) {
|
|
int steps;
|
|
|
|
steps = valueToSet / quantise;
|
|
valueToSet = steps * quantise;
|
|
steps = timeToSet / quantise;
|
|
timeToSet = steps * quantise;
|
|
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeHandleComponent::setTimeAndValue quantised (%f, %f)\n", timeToSet, valueToSet);
|
|
#endif
|
|
}
|
|
|
|
// valueToSet = getParentComponent()->quantiseValue(valueToSet);
|
|
// timeToSet = getParentComponent()->quantiseDomain(timeToSet);
|
|
|
|
value = constrainValue(valueToSet);
|
|
time = constrainDomain(timeToSet);
|
|
|
|
setTopLeftPosition(getParentComponent()->convertDomainToPixels(time),
|
|
getParentComponent()->convertValueToPixels(value));
|
|
|
|
dontUpdateTimeAndValue = oldDontUpdateTimeAndValue;
|
|
|
|
getParentComponent()->repaint();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::offsetTimeAndValue(double offsetTime, double offsetValue, double quantise)
|
|
{
|
|
setTimeAndValue(time+offsetTime, value+offsetValue, quantise);
|
|
}
|
|
|
|
|
|
double EnvelopeHandleComponent::constrainDomain(double domainToConstrain) const
|
|
{
|
|
EnvelopeHandleComponent* previousHandle = getPreviousHandle();
|
|
EnvelopeHandleComponent* nextHandle = getNextHandle();
|
|
|
|
int leftLimit = previousHandle == 0 ? 0 : previousHandle->getX();
|
|
int rightLimit = nextHandle == 0 ? getParentWidth()-HANDLESIZE : nextHandle->getX();
|
|
|
|
double left = getParentComponent()->convertPixelsToDomain(leftLimit);
|
|
double right = getParentComponent()->convertPixelsToDomain(rightLimit);
|
|
|
|
if(previousHandle != 0) left += FINETUNE;
|
|
if(nextHandle != 0) right -= FINETUNE;
|
|
|
|
return juce::jlimit(juce::jmin(left, right), juce::jmax(left, right), shouldLockTime ? time : domainToConstrain);
|
|
}
|
|
|
|
double EnvelopeHandleComponent::constrainValue(double valueToConstrain) const
|
|
{
|
|
return getParentComponent()->constrainValue(shouldLockValue ? value : valueToConstrain);
|
|
}
|
|
|
|
void EnvelopeHandleComponent::lockTime(double timeToLock)
|
|
{
|
|
setTime(timeToLock);
|
|
shouldLockTime = true;
|
|
recalculateShouldDraw();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::lockValue(double valueToLock)
|
|
{
|
|
setValue(valueToLock);
|
|
shouldLockValue = true;
|
|
recalculateShouldDraw();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::unlockTime()
|
|
{
|
|
shouldLockTime = false;
|
|
recalculateShouldDraw();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::unlockValue()
|
|
{
|
|
shouldLockValue = false;
|
|
recalculateShouldDraw();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::recalculatePosition()
|
|
{
|
|
bool oldDontUpdateTimeAndValue = dontUpdateTimeAndValue;
|
|
dontUpdateTimeAndValue = true;
|
|
setTopLeftPosition(getParentComponent()->convertDomainToPixels(time),
|
|
getParentComponent()->convertValueToPixels(value));
|
|
dontUpdateTimeAndValue = oldDontUpdateTimeAndValue;
|
|
getParentComponent()->repaint();
|
|
}
|
|
|
|
void EnvelopeHandleComponent::recalculateShouldDraw() {
|
|
shouldDraw = !shouldLockTime || !shouldLockValue;
|
|
}
|
|
|
|
|
|
EnvelopeComponent::EnvelopeComponent()
|
|
: minNumHandles(0),
|
|
maxNumHandles(0xffffff),
|
|
domainMin(0.0),
|
|
domainMax(1.0),
|
|
valueMin(0.0),
|
|
valueMax(1.0),
|
|
valueGrid((valueMax-valueMin) / 10),
|
|
domainGrid((domainMax-domainMin) / 16),
|
|
gridDisplayMode(GridNone),
|
|
gridQuantiseMode(GridNone),
|
|
draggingHandle(0),
|
|
adjustingHandle(nullptr),
|
|
curvePoints(64),
|
|
releaseNode(-1),
|
|
loopNode(-1),
|
|
allowCurveEditing(true),
|
|
allowNodeEditing(true)
|
|
{
|
|
setMouseCursor(juce::MouseCursor::NormalCursor);
|
|
setBounds(0, 0, 200, 200); // non-zero size to start with
|
|
}
|
|
|
|
EnvelopeComponent::~EnvelopeComponent()
|
|
{
|
|
deleteAllChildren();
|
|
}
|
|
|
|
void EnvelopeComponent::setDomainRange(const double min, const double max)
|
|
{
|
|
bool changed = false;
|
|
|
|
if(domainMin != min)
|
|
{
|
|
changed = true;
|
|
domainMin = min;
|
|
}
|
|
|
|
if(domainMax != max)
|
|
{
|
|
changed = true;
|
|
domainMax = max;
|
|
}
|
|
|
|
if(changed == true)
|
|
{
|
|
recalculateHandles();
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::getDomainRange(double& min, double& max) const
|
|
{
|
|
min = domainMin;
|
|
max = domainMax;
|
|
}
|
|
|
|
void EnvelopeComponent::setValueRange(const double min, const double max)
|
|
{
|
|
bool changed = false;
|
|
|
|
if(valueMin != min)
|
|
{
|
|
changed = true;
|
|
valueMin = min;
|
|
}
|
|
|
|
if(valueMax != max)
|
|
{
|
|
changed = true;
|
|
valueMax = max;
|
|
}
|
|
|
|
if(changed == true)
|
|
{
|
|
recalculateHandles();
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::getValueRange(double& min, double& max) const
|
|
{
|
|
min = valueMin;
|
|
max = valueMax;
|
|
}
|
|
|
|
void EnvelopeComponent::recalculateHandles()
|
|
{
|
|
for(int i = 0; i < handles.size(); i++)
|
|
{
|
|
handles.getUnchecked(i)->recalculatePosition();
|
|
}
|
|
}
|
|
|
|
EnvelopeHandleComponent* EnvelopeComponent::findHandle(double time) {
|
|
EnvelopeHandleComponent* handle = nullptr;
|
|
|
|
for (int i = 0; i < handles.size(); i++) {
|
|
handle = handles.getUnchecked(i);
|
|
if (handle->getTime() > time)
|
|
break;
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
void EnvelopeComponent::setGrid(const GridMode display, const GridMode quantise, const double domainQ, const double valueQ)
|
|
{
|
|
if(quantise != GridLeaveUnchanged)
|
|
gridQuantiseMode = quantise;
|
|
|
|
if((display != GridLeaveUnchanged) && (display != gridDisplayMode))
|
|
{
|
|
gridDisplayMode = display;
|
|
repaint();
|
|
}
|
|
|
|
if((domainQ > 0.0) && (domainQ != domainGrid))
|
|
{
|
|
domainGrid = domainQ;
|
|
repaint();
|
|
}
|
|
|
|
if((valueQ > 0.0) && (valueQ != valueGrid))
|
|
{
|
|
valueGrid = valueQ;
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::getGrid(GridMode& display, GridMode& quantise, double& domainQ, double& valueQ) const
|
|
{
|
|
display = gridDisplayMode;
|
|
quantise = gridQuantiseMode;
|
|
domainQ = domainGrid;
|
|
valueQ = valueGrid;
|
|
}
|
|
|
|
void EnvelopeComponent::paint(juce::Graphics& g)
|
|
{
|
|
paintBackground(g);
|
|
|
|
if(handles.size() > 0)
|
|
{
|
|
juce::Path path;
|
|
Env env = getEnv();
|
|
|
|
EnvelopeHandleComponent* handle = handles.getUnchecked(0);
|
|
path.startNewSubPath((handle->getX() + handle->getRight()) * 0.5f,
|
|
(handle->getY() + handle->getBottom()) * 0.5f);
|
|
|
|
const float firstTime = handle->getTime();
|
|
float time = firstTime;
|
|
|
|
for(int i = 1; i < handles.size(); i++)
|
|
{
|
|
handle = handles.getUnchecked(i);
|
|
float halfWidth = handle->getWidth()*0.5f;
|
|
float halfHeight = handle->getHeight()*0.5f;
|
|
|
|
float nextTime = handle->getTime();
|
|
float handleTime = nextTime - time;
|
|
float timeInc = handleTime / curvePoints;
|
|
|
|
for(int j = 0; j < curvePoints; j++)
|
|
{
|
|
float value = env.lookup(time - firstTime);
|
|
path.lineTo(convertDomainToPixels(time) + halfWidth,
|
|
convertValueToPixels(value) + halfHeight);
|
|
time += timeInc;
|
|
}
|
|
|
|
path.lineTo((handle->getX() + handle->getRight()) * 0.5f,
|
|
(handle->getY() + handle->getBottom()) * 0.5f);
|
|
|
|
time = nextTime;
|
|
}
|
|
|
|
g.setColour(findColour(Line));
|
|
g.strokePath (path, juce::PathStrokeType(2.0f));
|
|
|
|
path.lineTo(handle->getRight(), getHeight());
|
|
path.lineTo(0, getHeight());
|
|
|
|
// gradient fill the path
|
|
juce::ColourGradient gradient(findColour(LoopLine), 0, 0, juce::Colours::transparentWhite, 0, getHeight(), false);
|
|
g.setGradientFill(gradient);
|
|
g.fillPath(path);
|
|
|
|
if((loopNode >= 0) && (releaseNode >= 0) && (releaseNode > loopNode))
|
|
{
|
|
EnvelopeHandleComponent* releaseHandle = handles[releaseNode];
|
|
EnvelopeHandleComponent* loopHandle = handles[loopNode];
|
|
|
|
if((releaseHandle != 0) && (loopHandle != 0))
|
|
{
|
|
// draw a horizontal line from release
|
|
g.setColour(findColour(LoopLine));
|
|
|
|
const float loopY = (loopHandle->getY() + loopHandle->getBottom()) * 0.5f;
|
|
const float releaseY = (releaseHandle->getY() + releaseHandle->getBottom()) * 0.5f;
|
|
const float loopX = (loopHandle->getX() + loopHandle->getRight()) * 0.5f;
|
|
const float releaseX = (releaseHandle->getX() + releaseHandle->getRight()) * 0.5f;
|
|
|
|
float dashes[] = { 5, 3 };
|
|
juce::Line<float> line(loopX, releaseY, loopX, loopY);
|
|
g.drawDashedLine(line, dashes, 2, 0.5f);
|
|
|
|
const int arrowLength = HANDLESIZE*2;
|
|
|
|
g.drawLine(releaseX, releaseY,
|
|
loopX + arrowLength, releaseY,
|
|
0.5f);
|
|
|
|
if(loopY == releaseY)
|
|
g.setColour(findColour(LoopNode));
|
|
|
|
// g.drawArrow(loopX + arrowLength, releaseY,
|
|
// loopX, releaseY,
|
|
// 0.5f, HANDLESIZE, arrowLength);
|
|
g.drawArrow(juce::Line<float>((float)(loopX + arrowLength), releaseY, loopX, releaseY),
|
|
0.5f, HANDLESIZE, arrowLength);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
inline double round(double a, double b) throw()
|
|
{
|
|
double offset = a < 0 ? -0.5 : 0.5;
|
|
int n = (int)(a / b + offset);
|
|
return b * (double)n;
|
|
}
|
|
|
|
void EnvelopeComponent::paintBackground(juce::Graphics& g)
|
|
{
|
|
g.setColour(findColour(Background));
|
|
g.fillRect(0, 0, getWidth(), getHeight());
|
|
|
|
g.setColour(findColour(GridLine));
|
|
|
|
if((gridDisplayMode & GridValue) && (valueGrid > 0.0))
|
|
{
|
|
double value = valueMin;
|
|
|
|
while(value <= valueMax)
|
|
{
|
|
//g.drawHorizontalLine(convertValueToPixels(value) + HANDLESIZE/2, 0, getWidth());
|
|
float y = convertValueToPixels(value) + HANDLESIZE/2;
|
|
y = round(y, 1.f) + 0.5f;
|
|
g.drawLine(0, y, getWidth(), y, 1.0f);
|
|
value += valueGrid;
|
|
}
|
|
}
|
|
|
|
if((gridDisplayMode & GridDomain) && (domainGrid > 0.0))
|
|
{
|
|
double domain = domainMin;
|
|
|
|
while(domain <= domainMax)
|
|
{
|
|
g.drawVerticalLine(convertDomainToPixels(domain) + HANDLESIZE/2, 0, getHeight());
|
|
domain += domainGrid;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::resized()
|
|
{
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeComponent::resized(%d, %d)\n", getWidth(), getHeight());
|
|
#endif
|
|
if(handles.size() != 0) {
|
|
for(int i = 0; i < handles.size(); i++) {
|
|
EnvelopeHandleComponent* handle = handles.getUnchecked(i);
|
|
|
|
bool oldDontUpdateTimeAndValue = handle->dontUpdateTimeAndValue;
|
|
handle->dontUpdateTimeAndValue = true;
|
|
|
|
handle->setTopLeftPosition(convertDomainToPixels(handle->getTime()),
|
|
convertValueToPixels(handle->getValue()));
|
|
|
|
handle->dontUpdateTimeAndValue = oldDontUpdateTimeAndValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void EnvelopeComponent::mouseEnter(const juce::MouseEvent& e)
|
|
{
|
|
EnvelopeHandleComponent* handle = findHandle(convertPixelsToDomain(e.x));
|
|
EnvelopeHandleComponent* prevHandle = handle->getPreviousHandle();
|
|
|
|
if (handle == nullptr || prevHandle == nullptr) {
|
|
adjustable = false;
|
|
setMouseCursor(juce::MouseCursor::NormalCursor);
|
|
return;
|
|
}
|
|
|
|
auto handleBounds = handle->getBoundsInParent();
|
|
auto prevHandleBounds = prevHandle->getBoundsInParent();
|
|
|
|
auto minX = juce::jmin(handleBounds.getX(), prevHandleBounds.getX());
|
|
auto maxX = juce::jmax(handleBounds.getRight(), prevHandleBounds.getRight());
|
|
auto minY = juce::jmin(handleBounds.getY(), prevHandleBounds.getY());
|
|
auto maxY = juce::jmax(handleBounds.getBottom(), prevHandleBounds.getBottom());
|
|
|
|
auto rect = juce::Rectangle<int>(minX, minY, maxX - minX, maxY - minY);
|
|
|
|
if (rect.contains(e.getPosition())) {
|
|
adjustable = true;
|
|
setMouseCursor(juce::MouseCursor::UpDownResizeCursor);
|
|
} else {
|
|
adjustable = false;
|
|
setMouseCursor(juce::MouseCursor::NormalCursor);
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::mouseMove(const juce::MouseEvent& e)
|
|
{
|
|
mouseEnter(e);
|
|
}
|
|
|
|
void EnvelopeComponent::mouseDown(const juce::MouseEvent& e)
|
|
{
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeComponent::mouseDown(%d, %d)\n", e.x, e.y);
|
|
#endif
|
|
|
|
if (adjustable) {
|
|
adjustingHandle = findHandle(convertPixelsToDomain(e.x));
|
|
prevCurveValue = adjustingHandle->getCurve().getCurve();
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::mouseDrag(const juce::MouseEvent& e)
|
|
{
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeComponent::mouseDrag(%d, %d)\n", e.x, e.y);
|
|
#endif
|
|
|
|
if (adjustable && adjustingHandle != nullptr) {
|
|
EnvCurve curve = adjustingHandle->getCurve();
|
|
EnvelopeHandleComponent* prevHandle = adjustingHandle->getPreviousHandle();
|
|
// get distance as proportion of height
|
|
double originalValue = e.getDistanceFromDragStartY() / (double) getHeight();
|
|
double value = originalValue * 5;
|
|
value = value * value;
|
|
if (originalValue < 0) {
|
|
value = -value;
|
|
}
|
|
// make the value change the prevValue rather than overwriting it
|
|
if (prevHandle != nullptr && prevHandle->getValue() > adjustingHandle->getValue()) {
|
|
value = -value;
|
|
}
|
|
value = juce::jmin(prevCurveValue + value, 50.0);
|
|
curve.setCurve(value);
|
|
adjustingHandle->setCurve(curve);
|
|
} else if (draggingHandle != nullptr) {
|
|
draggingHandle->mouseDrag(e.getEventRelativeTo(draggingHandle));
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::mouseUp(const juce::MouseEvent& e)
|
|
{
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeComponent::mouseUp\n");
|
|
#endif
|
|
|
|
adjustingHandle = nullptr;
|
|
|
|
if(draggingHandle != 0)
|
|
{
|
|
if(e.mods.isCtrlDown() == false)
|
|
quantiseHandle(draggingHandle);
|
|
|
|
setMouseCursor(juce::MouseCursor::DraggingHandCursor);
|
|
draggingHandle->setMousePositionToThisHandle();
|
|
draggingHandle->resetOffsets();
|
|
draggingHandle = 0;
|
|
sendEndDrag();
|
|
} else {
|
|
setMouseCursor(juce::MouseCursor::NormalCursor);
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::addListener (EnvelopeComponentListener* const listener)
|
|
{
|
|
if (listener != 0)
|
|
listeners.add (listener);
|
|
}
|
|
|
|
void EnvelopeComponent::removeListener (EnvelopeComponentListener* const listener)
|
|
{
|
|
listeners.removeValue(listener);
|
|
}
|
|
|
|
void EnvelopeComponent::sendChangeMessage()
|
|
{
|
|
for (int i = listeners.size(); --i >= 0;)
|
|
{
|
|
((EnvelopeComponentListener*) listeners.getUnchecked (i))->envelopeChanged (this);
|
|
i = juce::jmin (i, listeners.size());
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::sendStartDrag()
|
|
{
|
|
for (int i = listeners.size(); --i >= 0;)
|
|
{
|
|
((EnvelopeComponentListener*) listeners.getUnchecked (i))->envelopeStartDrag (this);
|
|
i = juce::jmin (i, listeners.size());
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::sendEndDrag()
|
|
{
|
|
for (int i = listeners.size(); --i >= 0;)
|
|
{
|
|
((EnvelopeComponentListener*) listeners.getUnchecked (i))->envelopeEndDrag (this);
|
|
i = juce::jmin (i, listeners.size());
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::clear()
|
|
{
|
|
int i = getNumHandles();
|
|
|
|
while (i > 0)
|
|
removeHandle(handles[--i]);
|
|
}
|
|
|
|
EnvelopeLegendComponent* EnvelopeComponent::getLegend()
|
|
{
|
|
EnvelopeContainerComponent* container =
|
|
dynamic_cast <EnvelopeContainerComponent*> (getParentComponent());
|
|
|
|
if(container == 0) return 0;
|
|
|
|
return container->getLegendComponent();
|
|
}
|
|
|
|
void EnvelopeComponent::setLegendText(juce::String legendText)
|
|
{
|
|
EnvelopeLegendComponent* legend = getLegend();
|
|
|
|
if(legend == 0) return;
|
|
|
|
legend->setText(legendText);
|
|
}
|
|
|
|
void EnvelopeComponent::setLegendTextToDefault()
|
|
{
|
|
EnvelopeLegendComponent* legend = getLegend();
|
|
|
|
if(legend == 0) return;
|
|
|
|
legend->setText();
|
|
}
|
|
|
|
int EnvelopeComponent::getHandleIndex(EnvelopeHandleComponent* thisHandle) const
|
|
{
|
|
return handles.indexOf(thisHandle);
|
|
}
|
|
|
|
EnvelopeHandleComponent* EnvelopeComponent::getHandle(const int index) const
|
|
{
|
|
return handles[index];
|
|
}
|
|
|
|
EnvelopeHandleComponent* EnvelopeComponent::getPreviousHandle(const EnvelopeHandleComponent* thisHandle) const
|
|
{
|
|
int thisHandleIndex = handles.indexOf(const_cast<EnvelopeHandleComponent*>(thisHandle));
|
|
|
|
if(thisHandleIndex <= 0)
|
|
return 0;
|
|
else
|
|
return handles.getUnchecked(thisHandleIndex-1);
|
|
}
|
|
|
|
EnvelopeHandleComponent* EnvelopeComponent::getNextHandle(const EnvelopeHandleComponent* thisHandle) const
|
|
{
|
|
int thisHandleIndex = handles.indexOf(const_cast<EnvelopeHandleComponent*>(thisHandle));
|
|
|
|
if(thisHandleIndex == -1 || thisHandleIndex >= handles.size()-1)
|
|
return 0;
|
|
else
|
|
return handles.getUnchecked(thisHandleIndex+1);
|
|
}
|
|
|
|
|
|
EnvelopeHandleComponent* EnvelopeComponent::addHandle(int newX, int newY, EnvCurve curve)
|
|
{
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeComponent::addHandle(%d, %d)\n", newX, newY);
|
|
#endif
|
|
|
|
return addHandle(convertPixelsToDomain(newX), convertPixelsToValue(newY), curve);
|
|
}
|
|
|
|
|
|
EnvelopeHandleComponent* EnvelopeComponent::addHandle(double newDomain, double newValue, EnvCurve curve)
|
|
{
|
|
#ifdef MYDEBUG
|
|
printf("MyEnvelopeComponent::addHandle(%f, %f)\n", (float)newDomain, (float)newValue);
|
|
#endif
|
|
|
|
// newDomain = quantiseDomain(newDomain);
|
|
// newValue = quantiseValue(newValue);
|
|
|
|
if(handles.size() < maxNumHandles) {
|
|
int i;
|
|
for(i = 0; i < handles.size(); i++) {
|
|
EnvelopeHandleComponent* handle = handles.getUnchecked(i);
|
|
if(handle->getTime() > newDomain)
|
|
break;
|
|
}
|
|
|
|
if(releaseNode >= i) releaseNode++;
|
|
if(loopNode >= i) loopNode++;
|
|
|
|
EnvelopeHandleComponent* handle;
|
|
addAndMakeVisible(handle = new EnvelopeHandleComponent());
|
|
handle->setSize(HANDLESIZE, HANDLESIZE);
|
|
handle->setTimeAndValue(newDomain, newValue, 0.0);
|
|
handle->setCurve(curve);
|
|
handles.insert(i, handle);
|
|
if (adsrMode) {
|
|
if (i == 0) {
|
|
handle->lockTime(0);
|
|
}
|
|
if (i == 0 || i == 3) {
|
|
handle->lockValue(0);
|
|
}
|
|
}
|
|
// sendChangeMessage();
|
|
return handle;
|
|
}
|
|
else return 0;
|
|
}
|
|
|
|
|
|
void EnvelopeComponent::removeHandle(EnvelopeHandleComponent* thisHandle)
|
|
{
|
|
if(handles.size() > minNumHandles) {
|
|
int index = handles.indexOf(thisHandle);
|
|
|
|
if(releaseNode >= 0)
|
|
{
|
|
if(releaseNode == index)
|
|
releaseNode = -1;
|
|
else if(releaseNode > index)
|
|
releaseNode--;
|
|
}
|
|
|
|
if(loopNode >= 0)
|
|
{
|
|
if(loopNode == index)
|
|
loopNode = -1;
|
|
else if(loopNode > index)
|
|
loopNode--;
|
|
}
|
|
|
|
handles.removeFirstMatchingValue(thisHandle);
|
|
removeChildComponent(thisHandle);
|
|
delete thisHandle;
|
|
sendChangeMessage();
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::quantiseHandle(EnvelopeHandleComponent* thisHandle)
|
|
{
|
|
if((gridQuantiseMode & GridDomain) && (domainGrid > 0.0))
|
|
{
|
|
double domain = round(thisHandle->getTime(), domainGrid);
|
|
thisHandle->setTime(domain);
|
|
|
|
}
|
|
|
|
if((gridQuantiseMode & GridValue) && (valueGrid > 0.0))
|
|
{
|
|
double value = round(thisHandle->getValue(), valueGrid);
|
|
thisHandle->setValue(value);
|
|
}
|
|
}
|
|
|
|
bool EnvelopeComponent::isReleaseNode(EnvelopeHandleComponent* thisHandle) const
|
|
{
|
|
int index = handles.indexOf(thisHandle);
|
|
|
|
if(index < 0)
|
|
return false;
|
|
else
|
|
return index == releaseNode;
|
|
}
|
|
|
|
bool EnvelopeComponent::isLoopNode(EnvelopeHandleComponent* thisHandle) const
|
|
{
|
|
int index = handles.indexOf(thisHandle);
|
|
|
|
if(index < 0)
|
|
return false;
|
|
else
|
|
return index == loopNode;
|
|
}
|
|
|
|
void EnvelopeComponent::setReleaseNode(const int index)
|
|
{
|
|
if((index >= -1) && index < handles.size())
|
|
{
|
|
releaseNode = index;
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
int EnvelopeComponent::getReleaseNode() const
|
|
{
|
|
return releaseNode;
|
|
}
|
|
|
|
void EnvelopeComponent::setLoopNode(const int index)
|
|
{
|
|
if((index >= -1) && index < handles.size())
|
|
{
|
|
loopNode = index;
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
int EnvelopeComponent::getLoopNode() const
|
|
{
|
|
return loopNode;
|
|
}
|
|
|
|
void EnvelopeComponent::setAllowCurveEditing(const bool flag)
|
|
{
|
|
allowCurveEditing = flag;
|
|
}
|
|
|
|
bool EnvelopeComponent::getAllowCurveEditing() const
|
|
{
|
|
return allowCurveEditing;
|
|
}
|
|
|
|
void EnvelopeComponent::setAllowNodeEditing(const bool flag)
|
|
{
|
|
allowNodeEditing = flag;
|
|
}
|
|
|
|
bool EnvelopeComponent::getAllowNodeEditing() const
|
|
{
|
|
return allowNodeEditing;
|
|
}
|
|
|
|
void EnvelopeComponent::setAdsrMode(const bool adsrMode) {
|
|
this->adsrMode = adsrMode;
|
|
}
|
|
|
|
bool EnvelopeComponent::getAdsrMode() const {
|
|
return adsrMode;
|
|
}
|
|
|
|
void EnvelopeComponent::setReleaseNode(EnvelopeHandleComponent* thisHandle)
|
|
{
|
|
setReleaseNode(handles.indexOf(thisHandle));
|
|
}
|
|
|
|
void EnvelopeComponent::setLoopNode(EnvelopeHandleComponent* thisHandle)
|
|
{
|
|
setLoopNode(handles.indexOf(thisHandle));
|
|
}
|
|
|
|
double EnvelopeComponent::convertPixelsToDomain(int pixelsX, int pixelsXMax) const
|
|
{
|
|
if(pixelsXMax < 0)
|
|
pixelsXMax = getWidth()-HANDLESIZE;
|
|
|
|
return (double)pixelsX / pixelsXMax * (domainMax-domainMin) + domainMin;
|
|
|
|
}
|
|
|
|
double EnvelopeComponent::convertPixelsToValue(int pixelsY, int pixelsYMax) const
|
|
{
|
|
if(pixelsYMax < 0)
|
|
pixelsYMax = getHeight()-HANDLESIZE;
|
|
|
|
return (double)(pixelsYMax - pixelsY) / pixelsYMax * (valueMax-valueMin) + valueMin;
|
|
}
|
|
|
|
double EnvelopeComponent::convertDomainToPixels(double domainValue) const
|
|
{
|
|
return (domainValue - domainMin) / (domainMax - domainMin) * (getWidth() - HANDLESIZE);
|
|
}
|
|
|
|
double EnvelopeComponent::convertValueToPixels(double value) const
|
|
{
|
|
double pixelsYMax = getHeight()-HANDLESIZE;
|
|
return pixelsYMax-((value- valueMin) / (valueMax - valueMin) * pixelsYMax);
|
|
}
|
|
|
|
Env EnvelopeComponent::getEnv() const
|
|
{
|
|
if (handles.size() < 1) return Env({ 0.0, 0.0 }, { 0.0 });
|
|
if (handles.size() < 2) return Env({ 0.0, 0.0 }, { handles.getUnchecked(0)->getValue() });
|
|
|
|
EnvelopeHandleComponent* startHandle = handles.getUnchecked(0);
|
|
double currentLevel = startHandle->getValue();
|
|
double currentTime = startHandle->getTime();
|
|
|
|
std::vector<double> levels(handles.size(), 0.0);
|
|
std::vector<double> times(handles.size() - 1, 0.0);
|
|
EnvCurveList curves = EnvCurveList(EnvCurve::Empty, handles.size()-1);
|
|
|
|
levels[0] = currentLevel;
|
|
|
|
for(int i = 1; i < handles.size(); i++)
|
|
{
|
|
EnvelopeHandleComponent* handle = handles.getUnchecked(i);
|
|
|
|
currentLevel = handle->getValue();
|
|
double time = handle->getTime();
|
|
|
|
levels[i] = currentLevel;
|
|
times[i-1] = time-currentTime;
|
|
curves[i-1] = handle->getCurve();
|
|
|
|
currentTime = time;
|
|
}
|
|
|
|
return Env(levels, times, curves, releaseNode, loopNode);
|
|
}
|
|
|
|
void EnvelopeComponent::setEnv(Env const& env)
|
|
{
|
|
if (env.getLevels().size() == handles.size()) {
|
|
double time = 0.0;
|
|
|
|
std::vector<double> levels = env.getLevels();
|
|
std::vector<double> times = env.getTimes();
|
|
const EnvCurveList& curves = env.getCurves();
|
|
|
|
for (int i = 0; i < handles.size(); i++) {
|
|
EnvelopeHandleComponent* handle = handles.getUnchecked(i);
|
|
handle->setTimeAndValue(time, (double)levels[i], 0.0);
|
|
if (i > 0) {
|
|
handle->curve = curves[i - 1];
|
|
}
|
|
if (i < times.size()) {
|
|
time += times[i];
|
|
}
|
|
}
|
|
} else {
|
|
clear();
|
|
|
|
double time = 0.0;
|
|
|
|
std::vector<double> levels = env.getLevels();
|
|
std::vector<double> times = env.getTimes();
|
|
const EnvCurveList& curves = env.getCurves();
|
|
|
|
EnvelopeHandleComponent* handle = addHandle(time, (double)levels[0], EnvCurve::Linear);
|
|
quantiseHandle(handle);
|
|
|
|
for(int i = 0; i < times.size(); i++)
|
|
{
|
|
time += times[i];
|
|
handle = addHandle(time, (double)levels[i+1], curves[i]);
|
|
quantiseHandle(handle);
|
|
}
|
|
}
|
|
|
|
releaseNode = env.getReleaseNode();
|
|
loopNode = env.getLoopNode();
|
|
}
|
|
|
|
float EnvelopeComponent::lookup(const float time) const
|
|
{
|
|
if(handles.size() < 1)
|
|
{
|
|
return 0.f;
|
|
}
|
|
else
|
|
{
|
|
EnvelopeHandleComponent *first = handles.getUnchecked(0);
|
|
EnvelopeHandleComponent *last = handles.getUnchecked(handles.size()-1);
|
|
|
|
if(time <= first->getTime())
|
|
{
|
|
return first->getValue();
|
|
}
|
|
else if(time >= last->getTime())
|
|
{
|
|
return last->getValue();
|
|
}
|
|
else
|
|
{
|
|
return getEnv().lookup(time - first->getTime());
|
|
}
|
|
}
|
|
}
|
|
|
|
void EnvelopeComponent::setMinMaxNumHandles(int min, int max)
|
|
{
|
|
if(min <= max) {
|
|
minNumHandles = min;
|
|
maxNumHandles = max;
|
|
} else {
|
|
minNumHandles = max;
|
|
maxNumHandles = min;
|
|
}
|
|
|
|
juce::Random rand(juce::Time::currentTimeMillis());
|
|
|
|
if(handles.size() < minNumHandles) {
|
|
int num = minNumHandles-handles.size();
|
|
|
|
for(int i = 0; i < num; i++) {
|
|
double randX = rand.nextDouble() * (domainMax-domainMin) + domainMin;
|
|
double randY = rand.nextDouble() * (valueMax-valueMin) + valueMin;
|
|
|
|
addHandle(randX, randY, EnvCurve::Linear);
|
|
}
|
|
|
|
} else if(handles.size() > maxNumHandles) {
|
|
int num = handles.size()-maxNumHandles;
|
|
|
|
for(int i = 0; i < num; i++) {
|
|
removeHandle(handles.getLast());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
double EnvelopeComponent::constrainDomain(double domainToConstrain) const
|
|
{
|
|
return juce::jlimit(domainMin, domainMax, domainToConstrain);
|
|
}
|
|
|
|
double EnvelopeComponent::constrainValue(double valueToConstrain) const
|
|
{
|
|
return juce::jlimit(valueMin, valueMax, valueToConstrain);
|
|
}
|
|
|
|
|
|
//double EnvelopeComponent::quantiseDomain(double value)
|
|
//{
|
|
// if((gridQuantiseMode & GridDomain) && (domainGrid > 0.0))
|
|
// return round(value, domainGrid);
|
|
// else
|
|
// return value;
|
|
//}
|
|
//
|
|
//double EnvelopeComponent::quantiseValue(double value)
|
|
//{
|
|
// if((gridQuantiseMode & GridValue) && (valueGrid > 0.0))
|
|
// return round(value, valueGrid);
|
|
// else
|
|
// return value;
|
|
//}
|
|
|
|
EnvelopeLegendComponent::EnvelopeLegendComponent(juce::String _defaultText)
|
|
: defaultText(_defaultText)
|
|
{
|
|
addAndMakeVisible(text = new juce::Label("legend", defaultText));
|
|
setBounds(0, 0, 100, 16); // the critical thing is the height which should stay constant
|
|
}
|
|
|
|
EnvelopeLegendComponent::~EnvelopeLegendComponent()
|
|
{
|
|
deleteAllChildren();
|
|
}
|
|
|
|
EnvelopeComponent* EnvelopeLegendComponent::getEnvelopeComponent() const
|
|
{
|
|
EnvelopeContainerComponent* parent = dynamic_cast<EnvelopeContainerComponent*> (getParentComponent());
|
|
|
|
if(parent == 0) return 0;
|
|
|
|
return parent->getEnvelopeComponent();
|
|
}
|
|
|
|
void EnvelopeLegendComponent::resized()
|
|
{
|
|
text->setBounds(0, 0, getWidth(), getHeight());
|
|
}
|
|
|
|
void EnvelopeLegendComponent::paint(juce::Graphics& g)
|
|
{
|
|
EnvelopeComponent *env = getEnvelopeComponent();
|
|
juce::Colour backColour = findColour(EnvelopeComponent::LegendBackground);
|
|
|
|
g.setColour(backColour);
|
|
g.fillRect(0, 0, getWidth(), getHeight());
|
|
|
|
juce::Colour textColour = findColour(EnvelopeComponent::LegendText);
|
|
text->setColour(juce::Label::textColourId, textColour);
|
|
}
|
|
|
|
void EnvelopeLegendComponent::setText(juce::String legendText)
|
|
{
|
|
text->setText(legendText, juce::dontSendNotification);
|
|
repaint();
|
|
}
|
|
|
|
void EnvelopeLegendComponent::setText()
|
|
{
|
|
text->setText(defaultText, juce::dontSendNotification);
|
|
repaint();
|
|
}
|
|
|
|
double EnvelopeLegendComponent::mapValue(double value)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
double EnvelopeLegendComponent::mapTime(double time)
|
|
{
|
|
return time;
|
|
}
|
|
|
|
EnvelopeContainerComponent::EnvelopeContainerComponent(juce::String defaultText)
|
|
{
|
|
addAndMakeVisible(legend = new EnvelopeLegendComponent(defaultText));
|
|
addAndMakeVisible(envelope = new EnvelopeComponent());
|
|
}
|
|
|
|
EnvelopeContainerComponent::~EnvelopeContainerComponent()
|
|
{
|
|
deleteAllChildren();
|
|
}
|
|
|
|
void EnvelopeContainerComponent::resized()
|
|
{
|
|
int legendHeight = legend->getHeight();
|
|
|
|
envelope->setBounds(0,
|
|
0,
|
|
getWidth(),
|
|
getHeight()-legendHeight);
|
|
|
|
legend->setBounds(0,
|
|
getHeight()-legendHeight,
|
|
getWidth(),
|
|
legend->getHeight());
|
|
}
|
|
|
|
void EnvelopeContainerComponent::setLegendComponent(EnvelopeLegendComponent* newLegend)
|
|
{
|
|
deleteAndZero(legend);
|
|
addAndMakeVisible(legend = newLegend);
|
|
}
|
|
|
|
EnvelopeCurvePopup::EnvelopeCurvePopup(EnvelopeHandleComponent* handleToEdit)
|
|
: handle(handleToEdit),
|
|
initialised(false)
|
|
{
|
|
resetCounter();
|
|
|
|
EnvCurve curve = handle->getCurve();
|
|
EnvCurve::CurveType type = curve.getType();
|
|
float curveValue = curve.getCurve();
|
|
|
|
addChildComponent(slider = new CurveSlider());
|
|
slider->setSliderStyle(juce::Slider::LinearBar);
|
|
//slider->setTextBoxStyle(Slider::NoTextBox, false, 0,0);
|
|
slider->setRange(-1, 1, 0.0);
|
|
|
|
slider->setValue(curveValue, juce::dontSendNotification);
|
|
|
|
|
|
addAndMakeVisible(combo = new juce::ComboBox("combo"));
|
|
combo->addItem("Empty", idOffset + (int)EnvCurve::Empty);
|
|
combo->addItem("Numerical...", idOffset + (int)EnvCurve::Numerical);
|
|
combo->addItem("Step", idOffset + (int)EnvCurve::Step);
|
|
combo->addItem("Linear", idOffset + (int)EnvCurve::Linear);
|
|
|
|
EnvelopeComponent *parent = handleToEdit->getParentComponent();
|
|
double min, max;
|
|
parent->getValueRange(min, max);
|
|
|
|
if(((min > 0.0) && (max > 0.0)) || ((min < 0.0) && (min < 0.0)))
|
|
{
|
|
// exponential can't cross zero
|
|
combo->addItem("Exponential", idOffset + (int)EnvCurve::Exponential);
|
|
}
|
|
|
|
combo->addItem("Sine", idOffset + (int)EnvCurve::Sine);
|
|
combo->addItem("Welch", idOffset + (int)EnvCurve::Welch);
|
|
|
|
combo->addListener(this);
|
|
|
|
switch(type)
|
|
{
|
|
case EnvCurve::Empty: combo->setSelectedId(idOffset + (int)EnvCurve::Empty, juce::dontSendNotification); break;
|
|
case EnvCurve::Numerical: combo->setSelectedId(idOffset + (int)EnvCurve::Numerical, juce::dontSendNotification); break;
|
|
case EnvCurve::Step: combo->setSelectedId(idOffset + (int)EnvCurve::Step, juce::dontSendNotification); break;
|
|
case EnvCurve::Linear: combo->setSelectedId(idOffset + (int)EnvCurve::Linear, juce::dontSendNotification); break;
|
|
case EnvCurve::Exponential: combo->setSelectedId(idOffset + (int)EnvCurve::Exponential, juce::dontSendNotification); break;
|
|
case EnvCurve::Sine: combo->setSelectedId(idOffset + (int)EnvCurve::Sine, juce::dontSendNotification); break;
|
|
case EnvCurve::Welch: combo->setSelectedId(idOffset + (int)EnvCurve::Welch, juce::dontSendNotification); break;
|
|
}
|
|
|
|
slider->addListener(this);
|
|
}
|
|
|
|
void EnvelopeCurvePopup::create(EnvelopeHandleComponent* handle, int x, int y)
|
|
{
|
|
EnvelopeCurvePopup *popup = new EnvelopeCurvePopup(handle);
|
|
popup->addToDesktop(juce::ComponentPeer::windowIsTemporary);
|
|
|
|
if(x > 20) x -= 20;
|
|
//if(y > 20) y -= 20;
|
|
|
|
const int w = 200;
|
|
const int h = 50;
|
|
const int r = x+w;
|
|
const int b = y+h;
|
|
|
|
juce::Rectangle<int> monitorArea = popup->getParentMonitorArea();
|
|
|
|
if(r > monitorArea.getRight())
|
|
x = monitorArea.getRight() - w;
|
|
|
|
if(b > monitorArea.getBottom())
|
|
y = monitorArea.getBottom() - h;
|
|
|
|
popup->setBounds(x, y, w, h);
|
|
popup->setVisible(true);
|
|
popup->getPeer()->setAlwaysOnTop(true);
|
|
}
|
|
|
|
EnvelopeCurvePopup::~EnvelopeCurvePopup()
|
|
{
|
|
deleteAllChildren();
|
|
}
|
|
|
|
void EnvelopeCurvePopup::resized()
|
|
{
|
|
combo->setBoundsRelative(0.05f, 0.1f, 0.9f, 0.4f);
|
|
slider->setBoundsRelative(0.05f, 0.50f, 0.9f, 0.4f);
|
|
}
|
|
|
|
inline double linlin(const double input,
|
|
const double inLow, const double inHigh,
|
|
const double outLow, const double outHigh) throw()
|
|
{
|
|
double inRange = inHigh - inLow;
|
|
double outRange = outHigh - outLow;
|
|
return (input - inLow) * outRange / inRange + outLow;
|
|
}
|
|
|
|
void EnvelopeCurvePopup::sliderValueChanged(juce::Slider* sliderThatChanged)
|
|
{
|
|
resetCounter();
|
|
|
|
if(sliderThatChanged == slider)
|
|
{
|
|
EnvCurve curve = handle->getCurve();
|
|
double value = slider->getValue();
|
|
value = value * value * value;
|
|
value = linlin(value, -1.0, 1.0, -50.0, 50.0);
|
|
curve.setCurve(value);
|
|
handle->setCurve(curve);
|
|
}
|
|
}
|
|
|
|
void EnvelopeCurvePopup::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged)
|
|
{
|
|
resetCounter();
|
|
|
|
if(comboBoxThatHasChanged == combo)
|
|
{
|
|
EnvCurve::CurveType type = (EnvCurve::CurveType)(combo->getSelectedId() - idOffset);
|
|
bool showSlider = type == EnvCurve::Numerical;
|
|
slider->setVisible(showSlider);
|
|
|
|
EnvCurve curve = handle->getCurve();
|
|
curve.setType(type);
|
|
handle->setCurve(curve);
|
|
|
|
if(!showSlider && initialised) expire();
|
|
|
|
initialised = true;
|
|
}
|
|
}
|
|
|
|
void EnvelopeCurvePopup::expired()
|
|
{
|
|
handle->getParentComponent()->sendEndDrag();
|
|
}
|
|
|
|
const int EnvelopeCurvePopup::idOffset = 1000;
|
|
|
|
|
|
EnvelopeNodePopup::EnvelopeNodePopup(EnvelopeHandleComponent* handleToEdit)
|
|
: handle(handleToEdit),
|
|
initialised(false)
|
|
{
|
|
resetCounter();
|
|
|
|
addChildComponent(setLoopButton = new juce::TextButton("Set Y to Loop"));
|
|
addChildComponent(setReleaseButton = new juce::TextButton("Set Y to Release"));
|
|
setLoopButton->addListener(this);
|
|
setReleaseButton->addListener(this);
|
|
|
|
addAndMakeVisible(combo = new juce::ComboBox("combo"));
|
|
combo->addItem("Normal", idOffset + (int)Normal);
|
|
combo->addItem("Release", idOffset + (int)Release);
|
|
combo->addItem("Loop", idOffset + (int)Loop);
|
|
combo->addItem("Release & Loop", idOffset + (int)ReleaseAndLoop);
|
|
combo->addListener(this);
|
|
|
|
EnvelopeComponent* env = handle->getParentComponent();
|
|
|
|
if(env != 0)
|
|
{
|
|
if(env->isLoopNode(handle) && env->isReleaseNode(handle))
|
|
combo->setSelectedId(idOffset + (int)ReleaseAndLoop, false);
|
|
else if(env->isLoopNode(handle))
|
|
combo->setSelectedId(idOffset + (int)Loop, false);
|
|
else if(env->isReleaseNode(handle))
|
|
combo->setSelectedId(idOffset + (int)Release, false);
|
|
else
|
|
combo->setSelectedId(idOffset + (int)Normal, false);
|
|
}
|
|
|
|
}
|
|
|
|
void EnvelopeNodePopup::create(EnvelopeHandleComponent* handle, int x, int y)
|
|
{
|
|
EnvelopeNodePopup *popup = new EnvelopeNodePopup(handle);
|
|
popup->addToDesktop(juce::ComponentPeer::windowIsTemporary);
|
|
|
|
if(x > 20) x -= 20;
|
|
if(y > 20) y -= 20;
|
|
|
|
const int w = 200;
|
|
const int h = 50;
|
|
const int r = x+w;
|
|
const int b = y+h;
|
|
|
|
juce::Rectangle<int> monitorArea = popup->getParentMonitorArea();
|
|
|
|
if(r > monitorArea.getRight())
|
|
x = monitorArea.getRight() - w;
|
|
|
|
if(b > monitorArea.getBottom())
|
|
y = monitorArea.getBottom() - h;
|
|
|
|
popup->setBounds(x, y, w, h);
|
|
popup->setVisible(true);
|
|
popup->getPeer()->setAlwaysOnTop(true);
|
|
}
|
|
|
|
EnvelopeNodePopup::~EnvelopeNodePopup()
|
|
{
|
|
deleteAllChildren();
|
|
}
|
|
|
|
void EnvelopeNodePopup::resized()
|
|
{
|
|
combo->setBoundsRelative(0.05f, 0.1f, 0.9f, 0.4f);
|
|
|
|
// the same pos but we should see only one at a time
|
|
setLoopButton->setBoundsRelative(0.05f, 0.50f, 0.9f, 0.4f);
|
|
setReleaseButton->setBoundsRelative(0.05f, 0.50f, 0.9f, 0.4f);
|
|
}
|
|
|
|
void EnvelopeNodePopup::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged)
|
|
{
|
|
resetCounter();
|
|
|
|
if(comboBoxThatHasChanged == combo)
|
|
{
|
|
EnvelopeComponent* env = handle->getParentComponent();
|
|
|
|
if(env != 0)
|
|
{
|
|
NodeType type = (NodeType)(combo->getSelectedId() - idOffset);
|
|
|
|
switch(type)
|
|
{
|
|
case Normal: {
|
|
|
|
if(env->isLoopNode(handle))
|
|
env->setLoopNode(-1);
|
|
|
|
if(env->isReleaseNode(handle))
|
|
env->setReleaseNode(-1);
|
|
|
|
setLoopButton->setVisible(false);
|
|
setReleaseButton->setVisible(false);
|
|
|
|
} break;
|
|
|
|
case Release: {
|
|
|
|
if(env->isLoopNode(handle))
|
|
env->setLoopNode(-1);
|
|
|
|
env->setReleaseNode(handle);
|
|
|
|
setReleaseButton->setVisible(false);
|
|
|
|
if(env->getLoopNode() != -1)
|
|
setLoopButton->setVisible(true);
|
|
|
|
} break;
|
|
|
|
case Loop: {
|
|
|
|
env->setLoopNode(handle);
|
|
|
|
if(env->isReleaseNode(handle))
|
|
env->setReleaseNode(-1);
|
|
|
|
if(env->getReleaseNode() != -1)
|
|
setReleaseButton->setVisible(true);
|
|
|
|
setLoopButton->setVisible(false);
|
|
|
|
} break;
|
|
|
|
case ReleaseAndLoop: {
|
|
|
|
env->setLoopNode(handle);
|
|
env->setReleaseNode(handle);
|
|
|
|
setLoopButton->setVisible(false);
|
|
setReleaseButton->setVisible(false);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if(initialised) expire();
|
|
|
|
initialised = true;
|
|
}
|
|
}
|
|
|
|
void EnvelopeNodePopup::buttonClicked(juce::Button *button)
|
|
{
|
|
EnvelopeComponent* env = handle->getParentComponent();
|
|
|
|
if(env != 0)
|
|
{
|
|
if(button == setLoopButton)
|
|
{
|
|
int loopNode = env->getLoopNode();
|
|
|
|
if(loopNode >= 0)
|
|
{
|
|
EnvelopeHandleComponent *loopHandle = env->getHandle(loopNode);
|
|
|
|
if(loopHandle != 0)
|
|
{
|
|
float value = loopHandle->getValue();
|
|
handle->setValue(value);
|
|
expire();
|
|
}
|
|
}
|
|
}
|
|
else if(button == setReleaseButton)
|
|
{
|
|
int releaseNode = env->getReleaseNode();
|
|
|
|
if(releaseNode >= 0)
|
|
{
|
|
EnvelopeHandleComponent *releaseHandle = env->getHandle(releaseNode);
|
|
|
|
if(releaseHandle != 0)
|
|
{
|
|
float value = releaseHandle->getValue();
|
|
handle->setValue(value);
|
|
expire();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void EnvelopeNodePopup::expired()
|
|
{
|
|
}
|
|
|
|
const int EnvelopeNodePopup::idOffset = 2000;
|
|
|
|
#endif // gpl
|