/* Copyright (C) 2006 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad is free software; you can redistribute it and/or modify
* it 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.
*
* Nomad 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 Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.sf.nmedit.jtheme.clavia.nordmodular;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.sf.nmedit.jtheme.JTContext;
import net.sf.nmedit.jtheme.component.JTControlAdapter;
import net.sf.nmedit.jtheme.component.JTDisplay;
import net.sf.nmedit.jtheme.store2.BindParameter;
/*
* Created on Jul 24, 2006
*/
public class JTEnvelopeDisplay extends JTDisplay implements ChangeListener
{
// adsr / ad / ahd
/**
*
*/
private static final long serialVersionUID = 6469770227264863413L;
// sustain-release enabled
private boolean srEnabled = true;
// hold enabled
private boolean hEnabled = true;
private double va = 0;
private double vd = 0;
private double vh = 0;
private double vs = 0;
private double vr = 0;
public final static int LOG = 0;
public final static int LIN = 1;
public final static int EXP = 2;
private int attackType = LOG;
private boolean inverse = false;
private JTControlAdapter attackAdapter;
private JTControlAdapter attackTypeAdapter;
private JTControlAdapter decayAdapter;
private JTControlAdapter holdAdapter;
private JTControlAdapter sustainAdapter;
private JTControlAdapter releaseAdapter;
private JTControlAdapter inverseAdapter;
private boolean modified = true;
public JTEnvelopeDisplay(JTContext context)
{
super(context);
setAttack(1);
setDecay(1);
setHold(1);
setSustain(0.5);
setRelease(1);
configure();
}
protected void setModified(boolean modified)
{
this.modified = modified;
}
protected void paintComponent(Graphics g)
{
if (modified)
{
modified = false;
setDoubleBufferNeedsUpdate();
}
super.paintComponentWithDoubleBuffer(g);
}
protected void configure()
{
// no op
}
public JTControlAdapter getAttackAdapter()
{
return attackAdapter;
}
public JTControlAdapter getAttackTypeAdapter()
{
return attackTypeAdapter;
}
public JTControlAdapter getDecayAdapter()
{
return decayAdapter;
}
public JTControlAdapter getHoldAdapter()
{
return holdAdapter;
}
public JTControlAdapter getSustainAdapter()
{
return sustainAdapter;
}
public JTControlAdapter getReleaseAdapter()
{
return releaseAdapter;
}
public JTControlAdapter getInverseAdapter()
{
return inverseAdapter;
}
@BindParameter(name="attack")
public void setAttackAdapter(JTControlAdapter adapter)
{
JTControlAdapter oldAdapter = this.attackAdapter;
if (oldAdapter != adapter)
{
if (oldAdapter != null)
oldAdapter.setChangeListener(null);
this.attackAdapter = adapter;
if (adapter != null)
adapter.setChangeListener(this);
updateAttack();
}
}
@BindParameter(name="attack-type")
public void setAttackTypeAdapter(JTControlAdapter adapter)
{
JTControlAdapter oldAdapter = this.attackTypeAdapter;
if (oldAdapter != adapter)
{
if (oldAdapter != null)
oldAdapter.setChangeListener(null);
this.attackTypeAdapter = adapter;
if (adapter != null)
adapter.setChangeListener(this);
updateAttackType();
}
}
@BindParameter(name="decay")
public void setDecayAdapter(JTControlAdapter adapter)
{
JTControlAdapter oldAdapter = this.decayAdapter;
if (oldAdapter != adapter)
{
if (oldAdapter != null)
oldAdapter.setChangeListener(null);
this.decayAdapter = adapter;
if (adapter != null)
adapter.setChangeListener(this);
updateDecay();
}
}
@BindParameter(name="hold")
public void setHoldAdapter(JTControlAdapter adapter)
{
JTControlAdapter oldAdapter = this.holdAdapter;
if (oldAdapter != adapter)
{
if (oldAdapter != null)
oldAdapter.setChangeListener(null);
this.holdAdapter = adapter;
if (adapter != null)
adapter.setChangeListener(this);
updateHold();
}
}
@BindParameter(name="sustain")
public void setSustainAdapter(JTControlAdapter adapter)
{
JTControlAdapter oldAdapter = this.sustainAdapter;
if (oldAdapter != adapter)
{
if (oldAdapter != null)
oldAdapter.setChangeListener(null);
this.sustainAdapter = adapter;
if (adapter != null)
adapter.setChangeListener(this);
updateSustain();
}
}
@BindParameter(name="release")
public void setReleaseAdapter(JTControlAdapter adapter)
{
JTControlAdapter oldAdapter = this.releaseAdapter;
if (oldAdapter != adapter)
{
if (oldAdapter != null)
oldAdapter.setChangeListener(null);
this.releaseAdapter = adapter;
if (adapter != null)
adapter.setChangeListener(this);
updateRelease();
}
}
@BindParameter(name="inverse")
public void setInverseAdapter(JTControlAdapter adapter)
{
JTControlAdapter oldAdapter = this.inverseAdapter;
if (oldAdapter != adapter)
{
if (oldAdapter != null)
oldAdapter.setChangeListener(null);
this.inverseAdapter = adapter;
if (adapter != null)
adapter.setChangeListener(this);
updateInverse();
}
}
protected void updateAttack()
{
if (attackAdapter != null)
setAttack(attackAdapter.getNormalizedValue());
}
protected void updateAttackType()
{
if (attackTypeAdapter != null)
setAttackType(attackTypeAdapter.getValue());
}
protected void updateDecay()
{
if (decayAdapter != null)
setDecay(decayAdapter.getNormalizedValue());
}
protected void updateHold()
{
if (holdAdapter != null)
setHold(holdAdapter.getNormalizedValue());
}
protected void updateSustain()
{
if (sustainAdapter != null)
setSustain(sustainAdapter.getNormalizedValue());
}
protected void updateRelease()
{
if (releaseAdapter != null)
setRelease(releaseAdapter.getNormalizedValue());
}
protected void updateInverse()
{
if (inverseAdapter != null)
setInverse(partitionValue(inverseAdapter,2) == 1);
}
public void stateChanged(ChangeEvent e)
{
if (e.getSource() == attackAdapter)
{
updateAttack();
return;
}
if (e.getSource() == attackTypeAdapter)
{
updateAttackType();
return;
}
if (e.getSource() == decayAdapter)
{
updateDecay();
return;
}
if (e.getSource() == holdAdapter)
{
updateHold();
return;
}
if (e.getSource() == sustainAdapter)
{
updateSustain();
return;
}
if (e.getSource() == releaseAdapter)
{
updateRelease();
return;
}
if (e.getSource() == inverseAdapter)
{
updateInverse();
return;
}
}
protected int partitionValue(JTControlAdapter adapter, int partitions)
{
return partitionValue(adapter.getValue(), adapter.getMinValue(), adapter.getMaxValue(), partitions);
}
protected int partitionValue(int value, int minValue, int maxValue, int partitions)
{
value -= minValue;
maxValue -= minValue;
if (maxValue == 0)
return 0;
return ((partitions -1)*value) / maxValue;
}
public int getAttackType()
{
return attackType;
}
public void setAttackType(int t)
{
switch (t)
{
case LOG:break;
case LIN:break;
case EXP:break;
default :
throw new IllegalArgumentException("Invalid attack type");
}
if (attackType!=t)
{
setModified(true);
attackType=t;
repaint();
}
}
public void setInverse(boolean e)
{
if (inverse!=e)
{
setModified(true);
this.inverse = e;
repaint();
}
}
public boolean isInverse()
{
return inverse;
}
public boolean isSREnabled()
{
return srEnabled;
}
public void setSREnabled(boolean e)
{
if (e!=srEnabled)
{
setModified(true);
srEnabled = e;
repaint();
}
}
public boolean isHoldEnabled()
{
return hEnabled;
}
public void setHoldEnabled(boolean e)
{
if (e!=hEnabled)
{
setModified(true);
hEnabled = e;
repaint();
}
}
public void configureADSR()
{
setHoldEnabled(false);
setSREnabled(true);
setInverse(false);
setAttackType(LOG);
}
public void configureAD()
{
setHoldEnabled(false);
setSREnabled(false);
setInverse(false);
setAttackType(LIN);
}
public void configureAHD()
{
setHoldEnabled(true);
setSREnabled(false);
setInverse(false);
setAttackType(LIN);
}
public void paintDynamicLayer(Graphics2D g2)
{
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
final double segments =
2 + (hEnabled?1:0) + (srEnabled?2:0);
// each segment has space (0,+1) => scale by 1/segments
GeneralPath gp = new GeneralPath();
gp.moveTo(0, 0); // always start at origin
float left = 0; // left offset
{ // attack
final float ax = (float)va;
final float ay = 1;
left+=ax;
switch(attackType)
{
case LOG:
gp.curveTo(
0, 0.25f,
0, 1f,
left, ay);
break;
case LIN:
gp.lineTo(left, ay);
break;
case EXP:
gp.curveTo(
(float)va, 0,
(float)va, 1,
left, ay);
break;
}
}
if (hEnabled)
{ // hold
final float hx = (float) vh;
final float hy = 1;
left+=hx;
gp.lineTo(left, hy);
}
{ // decay
final float dx = (float) (srEnabled ? (vd*(1-vs)) : vd);
final float dy = srEnabled ? (float) vs : 0;
final float l= left;
left+=dx;
//gp.lineTo(left, dy);
/*gp.curveTo(
l, dy-dx, // (1-dy)*0.25f,
l, dy,
left, dy);*/
gp.curveTo(
l, (1-dy)*0.5f+dy,
(left-l)*0.5f+l, dy,
left, dy);
}
if (srEnabled)
{
{ // sustain+(1-vr)+(1-vd)+
final float sx = (float)(1+(1-vd*(1-vs))+(1-vr*vs)+(1-va)+(hEnabled?(1-vh):0));
final float sy = (float) vs;
left+=sx;
gp.lineTo(left, sy);
}
{ // release
final float rx = (float)(vr*vs);
final float ry = 0;
final float l = left;
left+=rx;
//gp.lineTo(left, ry);
gp.curveTo(
l, left-l,
l, 0,
left, ry);
}
}
AffineTransform at = new AffineTransform();
Insets insets = getInsets();
int w = getWidth()-insets.left-insets.right;
int h = getHeight()-insets.top-insets.bottom;
if (!inverse)
{
at.scale(1, -1);
at.translate(insets.left, -insets.top-(h-1));
}
at.scale((w-1)/segments, h-1);
gp.transform(at);
g2.setColor(getForeground());
g2.draw(gp);
}
public void setAttack(double v)
{
v = bounded(v);
if (va!=v)
{
setModified(true);
this.va = v;
repaint();
}
}
public void setDecay(double v)
{
v = bounded(v);
if (vd!=v)
{
setModified(true);
this.vd = v;
repaint();
}
}
public void setHold(double v)
{
v = bounded(v);
if (vh!=v)
{
setModified(true);
this.vh = v;
repaint();
}
}
public void setSustain(double v)
{
v = bounded(v);
if (vs!=v)
{
setModified(true);
this.vs = v;
repaint();
}
}
public void setRelease(double v)
{
v = bounded(v);
if (vr!=v)
{
setModified(true);
this.vr = v;
repaint();
}
}
private double bounded(double v)
{
return Math.max(0, Math.min(v, 1.0d));
}
}