/* Copyright (C) 2007 Julien Pauty
*
* 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
*/
/*
* Created on Nov 27, 2006
*/
package net.sf.nmedit.jtheme.clavia.nordmodular.graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class Envelope implements Shape
{
private boolean modified = true;
public void setModified(boolean modified)
{
this.modified = modified;
}
public boolean isModified()
{
return modified;
}
public static class ADEnvelope extends Envelope
{
protected ADEnvelope(int nbPoints)
{
super(nbPoints);
setNbSegment(2);
// attack
setTime(0, 0);
setLevel(0, RANGE_MAX);
// decay
setTime(1, RANGE_MAX);
setLevel(1, 0);
setTime(2, RANGE_MAX);
setLevel(2, RANGE_MAX);
setTime(3,RANGE_MAX);
setLevel(3, RANGE_MAX);
setAttackCurve(LIN);
setDecayCurve(LOG);
}
public ADEnvelope()
{
this( 4 );
}
public void setAttackCurve(int ct)
{
setCurveType(1, ct);
}
public int getAttackCurve()
{
return getCurveType(1);
}
public void setDecayCurve(int ct)
{
setCurveType(2, ct);
}
public int getDecayCurve()
{
return getCurveType(2);
}
public void setAttackTime(int f)
{
setTime(1, f);
setTime(2, getDecayTime());
}
public int getAttackTime()
{
return getTime(1);
}
public void setDecayTime(int f)
{
setTime(2, f);
}
public int getDecayTime()
{ return getTime(2); }
}
public static class ADSREnvelope extends ADEnvelope
{
protected ADSREnvelope(int nbPoints)
{
super(nbPoints);
setNbSegment(4);
setTime(0, 0);
setLevel(0, RANGE_MAX);
// decay
setTime(1, RANGE_MAX);
setLevel(1, 0);
setTime(2, RANGE_MAX);
setLevel(2,63);
// sustain
setTime(3, RANGE_MAX);
setLevel(3, 63);
// release
setTime(4, RANGE_MAX);
setLevel(4, RANGE_MAX);
setReleaseCurve(LOG);
}
public ADSREnvelope()
{
this( 5 );
}
public void setDecayTime(int f)
{
setTime(2, f);
}
public int getDecayTime()
{
return getTime(2);
}
public void setReleaseCurve(int ct)
{
setCurveType(4, ct);
}
public int getReleaseCurve()
{
return getCurveType(4);
}
public void setSustainValue(int v)
{
setLevel(3, v);
setLevel(2, v);
}
public float getSustainValue()
{
return getLevel(3);
}
public void setReleaseTime(int f)
{
/* to set the release time we actually set the time of the
* sustain time, which is 4 - attackTime - decayTime - releaseTime
* The principle here is to use the sustain segment to fill the
* space so that the release segment finishes in the bottom
* right corner*/
setTime(3, 4*RANGE_MAX - (getAttackTime()+getDecayTime()+f));
}
public float getReleaseTime()
{
//System.out.println(4 - (getTime(3)+getAttackTime()+getDecayTime()));
return 4*RANGE_MAX - (getTime(3)+getAttackTime()+getDecayTime());
}
}
public static class AHDEnvelope extends Envelope
{
protected AHDEnvelope(int nbPoints)
{
super(nbPoints);
setNbSegment(3);
// attack
setTime(0, 0);
setLevel(0, RANGE_MAX);
// hold
setTime(1, RANGE_MAX);
setLevel(1, 0);
// decay time
setTime(2, RANGE_MAX);
setLevel(2, 0);
setLevel(3, RANGE_MAX);
setTime(3,RANGE_MAX);
setDecayCurve(LOG);
setTime(4,RANGE_MAX);
setLevel(4, RANGE_MAX);
}
public AHDEnvelope()
{
this( 5 );
}
public void setAttackCurve(int ct)
{
setCurveType(0, ct);
}
public int getAttackCurve()
{
return getCurveType(0);
}
public void setDecayCurve(int ct)
{
setCurveType(3, ct);
}
public int getDecayCurve()
{
return getCurveType(2);
}
public void setAttackTime(int f)
{
setTime(1, f);
setHoldTime(getHoldTime());
}
public float getAttackTime()
{
return getTime(1);
}
public void setHoldTime(int f)
{
setTime(2, f);
setDecayTime(getDecayTime());
}
public int getHoldTime()
{
return getTime(2);
}
public void setDecayTime(int f)
{
setTime(3, f);
}
public int getDecayTime()
{
return getTime(3);
}
}
public final static int LIN = 0;
public final static int EXP = 1;
public final static int LOG = 2;
public final static int RANGE_MAX = 127;
private boolean inverse = false;
private Point[] points;
protected int nbSegment;
private boolean fill = false;
public Envelope(int nbPoints)
{
this.nbSegment = nbPoints-1;
points = new Point[nbPoints+1];
for ( int i= 0; i < nbPoints+1; i++ ) {
points[i]=new Point();
points[i].setPoint_type(EnvelopePathIterator.SEG_LINETO);
points[i].setCurve_type(LIN);
}
points[0].setPoint_type(EnvelopePathIterator.SEG_MOVETO);
points[0].setWeight(0);
points[0].setTime(0);
//final point to close the curve (needed for filling)
points[nbPoints].setWeight(1);
points[nbPoints].setTime(0);
points[nbPoints].setPoint_type(EnvelopePathIterator.SEG_MOVETO);
}
public void setFillEnabled(boolean enable)
{
this.fill = enable;
setModified(true);
}
public boolean isFillEnabled()
{
return fill;
}
protected Rectangle bounds = new Rectangle(0,0,1,1);
public void setBounds(int x, int y, int width, int height)
{
bounds.setBounds(x, y, width, height);
}
public Rectangle getBounds()
{
return new Rectangle(bounds);
}
public Rectangle2D getBounds2D()
{
return getBounds();
}
public boolean contains( double x, double y )
{
return bounds.contains(x, y);
}
public boolean contains( Point2D p )
{
return bounds.contains(p);
}
public boolean intersects( double x, double y, double w, double h )
{
return bounds.intersects(x, y, w, h);
}
public boolean intersects( Rectangle2D r )
{
return bounds.intersects(r);
}
public boolean contains( double x, double y, double w, double h )
{
return bounds.contains(x, y, w, h);
}
public boolean contains( Rectangle2D r )
{
return bounds.contains(r);
}
private void checkSegIndex(int segIndex, int max)
{
if (segIndex<0 || segIndex>max)
throw new IllegalArgumentException("no such segment: "+segIndex);
}
private int checkBounds(int v)
{
return v<0 ? 0 : (v>RANGE_MAX ? RANGE_MAX : v);
}
/*set the time of a segment.
* Basically this set up the actual x coordinate of the corresponding point
* The x coordinates is set up with respect to the x coordinate of the
* preceding point. This imply that when a point is modified, the following point
* should be updated. Updating a point consist in making a call such as:
* setTime(pointIndex, getTime(pointIndex));*/
public void setTime(int segIndex, int t)
{
setModified(true);
//System.out.println(segIndex +"=index" + points.length );
checkSegIndex(segIndex, points.length-1);
points[segIndex].setTime(t);
if (segIndex==0) {
points[segIndex].setX(t/((nbSegment)*(float)RANGE_MAX));
} else {
int previous = 0; //length of segment preceding the current segment
for(int i= 0 ; i< segIndex ; i++) {
previous += points[i].getTime();
}
points[segIndex].setX((t+ previous)/((nbSegment)*(float)RANGE_MAX) );
}
}
public void setX(int pointIndex, float t){
setModified(true);
points[pointIndex].setX(t/(nbSegment));
}
public float getX(int pointIndex){
setModified(true);
return points[pointIndex].getX();
}
public int getTime(int segIndex)
{
checkSegIndex(segIndex, points.length-1);
return points[segIndex].getTime();
}
public void setCurveType(int segIndex, int type)
{
setModified(true);
checkSegIndex(segIndex, points.length-1);
// if (type>=0 && type<3)
points[segIndex].setCurve_type(type);
if(type== LOG || type == EXP) {
points[segIndex].setPoint_type(PathIterator.SEG_CUBICTO);
} else {
points[segIndex].setPoint_type(EnvelopePathIterator.SEG_LINETO);
}
}
public int getCurveType(int segIndex)
{
checkSegIndex(segIndex, points.length-1);
return points[segIndex].getCurve_type();
}
public void setLevel(int segIndex, int weight)
{
setModified(true);
checkSegIndex(segIndex, points.length-1);
points[segIndex].setWeight(checkBounds(weight));
points[segIndex].setY(weight/(float)RANGE_MAX);
}
public int getLevel(int segIndex)
{
checkSegIndex(segIndex, points.length-1);
return points[segIndex].getWeight();
}
public int getSegmentCount()
{
return nbSegment;
}
public void setIsInverse(boolean inv)
{
setModified(true);
this.inverse = inv;
for(int i = 0; i < nbSegment; i++){
setLevel(i, RANGE_MAX - getLevel(i));
}
}
public boolean isInverse()
{
return inverse;
}
public PathIterator getPathIterator( final AffineTransform at )
{
return new EnvelopePathIterator(at);
}
public PathIterator getPathIterator( AffineTransform at, double flatness )
{
return new FlatteningPathIterator(getPathIterator(at), flatness);
}
private class EnvelopePathIterator implements PathIterator
{
// affine transformation
private AffineTransform at;
// current index
private int index = 0;
private int maxpoint = points.length - (fill ? 0 : 1);
public EnvelopePathIterator(AffineTransform at)
{
this.at = at;
}
public int getWindingRule()
{
return WIND_NON_ZERO;
}
public boolean isDone()
{
return index>=maxpoint;
}
public void next()
{
index ++;
}
private void exp(float srcX, float srcY, float destX, float destY, float coords[])
{
coords[0] = (destX-srcX)/2+srcX; // ctrl 1
coords[1] = srcY;
coords[2] = destX; // ctrl 2
coords[3] = (destY-srcY)/2+srcY;
coords[4] = destX; // destination
coords[5] = destY;
}
private void log(float srcX, float srcY, float destX, float destY, float coords[])
{
coords[0] = srcX; // ctrl 1
coords[1] = (srcY-destY)/2+destY;
coords[2] = (destX-srcX)/2+srcX; // ctrl 2
coords[3] = destY;
coords[4] = destX; // destination
coords[5] = destY;
}
private void applytransform( float[] coords, int seg )
{
if (at != null) {
at.transform(coords, 0, coords, 0, seg == SEG_CUBICTO ? 3 : 1);
}
}
public int currentSegment( float[] fcoords )
{
if(points[index].getCurve_type() == LIN) {
fcoords[0] = points[index].getX()*(bounds.width-1);
fcoords[1] = points[index].getY()*(bounds.height-1);
} else if(points[index].getCurve_type() == EXP) {
exp(points[index-1].getX(),points[index-1].getY(),
points[index].getX(),points[index].getY(),fcoords);
fcoords[0] = fcoords[0]*(bounds.width-1);
fcoords[1] = fcoords[1]*(bounds.height-1);
fcoords[2] = fcoords[2]*(bounds.width-1);
fcoords[3] = fcoords[3]*(bounds.height-1);
fcoords[4] = fcoords[4]*(bounds.width-1);
fcoords[5] = fcoords[5]*(bounds.height-1);
} else if(points[index].getCurve_type() == LOG) {
log(points[index-1].getX(),points[index-1].getY(),
points[index].getX(),points[index].getY(),fcoords);
fcoords[0] = fcoords[0]*(bounds.width-1);
fcoords[1] = fcoords[1]*(bounds.height-1);
fcoords[2] = fcoords[2]*(bounds.width-1);
fcoords[3] = fcoords[3]*(bounds.height-1);
fcoords[4] = fcoords[4]*(bounds.width-1);
fcoords[5] = fcoords[5]*(bounds.height-1);
}
applytransform(fcoords, points[index].getPoint_type());
return points[index].getPoint_type();
}
public int currentSegment( double[] dcoords )
{
dcoords[0] = (double) points[index].getX();
dcoords[1] = (double) points[index].getY();
//applytransform(dcoords, points[index].getCurve_type());
return points[index].getPoint_type();
}
}
public int getNbSegment() {
return nbSegment;
}
public void setNbSegment(int nbSegment) {
this.nbSegment = nbSegment;
}
}