/*
* Curve.java
* (FScape)
*
* Copyright (c) 2001-2016 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*/
package de.sciss.fscape.util;
import de.sciss.fscape.spect.*;
import java.util.*;
/**
* @version 0.71, 14-Nov-07
*/
public class Curve
implements Cloneable
{
// -------- public variables --------
public static final int TYPE_DIA = 0;
public static final int TYPE_ORTHO = 1;
/**
* READ-ONLY!
*/
public ParamSpace hSpace;
public ParamSpace vSpace;
public int type;
// -------- private variables --------
protected Vector<DoublePoint> points; // Element = DoublePoints
// -------- public methods --------
public Curve( ParamSpace hSpace, ParamSpace vSpace, int type )
{
this.hSpace = hSpace;
this.vSpace = vSpace;
this.type = type;
points = new Vector<DoublePoint>();
}
public Curve( ParamSpace hSpace, ParamSpace vSpace )
{
this( hSpace, vSpace, TYPE_DIA );
}
/**
* Clont vorgegebene Curve
*/
public Curve( Curve src )
{
this.hSpace = src.hSpace;
this.vSpace = src.vSpace;
this.type = src.type;
points = (Vector<DoublePoint>) src.points.clone();
}
public Object clone()
{
return new Curve( this );
}
/**
* Fuegt einen neuen Punkt in die Kurve ein
*
* @return Index in der Punkt-Kette; -1 wenn Punkt ausserhalb des Raumes lag
* oder bereits ein Punkt mit exakt demselben X-Wert existiert
*/
public int addPoint( double x, double y )
{
DoublePoint ptThis;
DoublePoint ptNeighbour;
double doubleIndex;
int index = -1;
int neighbourIndex = -1;
if( hSpace.contains( x ) && vSpace.contains( y ))
{
ptThis = new DoublePoint( hSpace.fitValue( x ), vSpace.fitValue( y ));
doubleIndex = indexOf( x );
if( doubleIndex < 0 ) {
index = 0;
neighbourIndex = 0;
} else if( doubleIndex > points.size() ) {
index = points.size();
neighbourIndex = index - 1;
} else {
index = (int) Math.ceil( doubleIndex );
neighbourIndex = (int) Math.rint( doubleIndex );
}
// Abstand ok?
ptNeighbour = getPoint( neighbourIndex );
if( ptNeighbour != null ) {
if( ((neighbourIndex >= index) &&
(ptNeighbour.x - hSpace.inc < ptThis.x - Constants.suckyDoubleError)) ||
((neighbourIndex < index) &&
(ptNeighbour.x + hSpace.inc > ptThis.x + Constants.suckyDoubleError)) )
return -1;
}
points.insertElementAt( ptThis, index );
}
return index;
}
public int addPoint( DoublePoint pt )
{
return addPoint( pt.x, pt.y );
}
/**
* Entfernt einen Punkt aus der Kurve
*
* @return false, wenn Punkt nicht existiert
*/
public boolean removePoint( int index )
{
try {
points.removeElementAt( index );
return true;
}
catch( IndexOutOfBoundsException e ) {
return false;
}
}
/**
* Besorgt einen Punkt
*/
public DoublePoint getPoint( int index )
{
DoublePoint pt = null;
try {
pt = points.elementAt( index );
}
catch( IndexOutOfBoundsException ignored) {}
return pt;
}
/**
* Besorgt eine Aufzaehlung aller Punkte
*/
public Enumeration getPoints()
{
return points.elements();
}
/**
* Ermittelt Anzahl der Punkte
*/
public int size()
{
return points.size();
}
/**
* Index eines (fiktiven) Punktes erfragen
* DER X-WERT WIRD NICHT GERASTERT
*
* @return der Index ist interpoliert, d.h. der Punkt liegt zwischen
* Math.floor( result ) und Math.ceil( result )!
* Double.NEGATIVE_INFINITY: Punkt liegt links der Kurvengrenze;
* Double.POSITIVE_INFINITY: Punkt liegt rechts der Kurzengrenze;
*/
public double indexOf( double x )
{
int size = points.size();
DoublePoint pt1, pt2, pt3;
int index1, index2, index3;
if( size == 0 ) return Double.POSITIVE_INFINITY; // keine Punkte
pt1 = points.firstElement();
pt2 = points.lastElement();
if( pt1.x > x ) return Double.NEGATIVE_INFINITY; // zu klein
if( pt2.x < x ) return Double.POSITIVE_INFINITY; // zu gross
index1 = 0;
index2 = size - 1;
// Suchverfahren: Strecke immer halbieren und diejenige
// weiterverfolgen, in der das gesuchte X liegt
while( index1 + 1 < index2 ) {
index3 = (index1 + index2) / 2; // "floor"
pt3 = points.elementAt( index3 );
if( pt3.x > x ) {
pt2 = pt3;
index2 = index3;
} else {
pt1 = pt3;
index1 = index3;
}
}
if( pt1.x != pt2.x ) {
return( (double) index1 + (x - pt1.x) / (pt2.x - pt1.x) );
} else {
return( (double) index1 );
}
}
/**
* Transformiert eine Kurve von einer Groesse/Masseinheit (zweidimensional)
* in eine andere (zweidimensional); vgl. auch Param.transform()!
*
* - bei gleichen ParamSpaces wird einfach src zurueckgeliefert.
* - ein ParamSpace.fitValue() wird auf jeden Punkt angewandt!
*
* @param hRef optionale (horizontale) Referenz, der ggf. fuer Umrechnungen von/in
* relative Werte benoetigt wird; kann null sein;
* WENN ER BENOETIGT WIRD, MUSS ER ENTWEDER IN DER FORM
* ABS_UNIT VORLIEGEN ODER (OHNE REFERENZ) IN DIESE UMWANDELBAR SEIN!
*
* @param vRef optionale vertikale Referenz
*
* @param stream optionaler Stream-Referrer, der ggf. fuer Umrechnungen
* von/in Beats oder Semitones benoetigt wird; darf null sein
* (ggf. wird dann auf die globale Geschwindigkeit/Scala
* zurueckgegriffen)
*/
public static Curve transform( Curve src, ParamSpace destHSpace, ParamSpace destVSpace,
Param hRef, Param vRef, SpectStream stream )
{
if( destHSpace.contains( src.hSpace ) &&
destVSpace.contains( src.vSpace )) return src;
Curve dest = new Curve( destHSpace, destVSpace, src.type );
DoublePoint pt;
Param newX, newY;
for( int i = 0; i < src.points.size(); i++ ) {
pt = src.points.elementAt( i );
newX = Param.transform( new Param( pt.x, src.hSpace.unit ), destHSpace.unit,
hRef, stream );
newY = Param.transform( new Param( pt.y, src.vSpace.unit ), destVSpace.unit,
vRef, stream );
if( (newX != null) && (newY != null) ) {
dest.addPoint( newX.value, newY.value);
}
}
return dest;
}
/**
* Integriert die Kurve ueber ein Intervall und teilt durch
* die Intervallgroesse
*
* @param start linke Intervall Grenze, darf "zu klein" sein
* @param end rechte Intervall Grenze, darf "zu gross" sein
*/
public static double average( Curve c, double start, double end )
{
double width = end - start;
int floor, ceil;
int firstIndex, lastIndex; // ceil( startIndex bzw. endIndex )
double startIndex, endIndex;
DoublePoint floorPt, ceilPt;
DoublePoint predPt, succPt, lastPt;
double integral;
if( width > 0 ) { // integrieren und teilen
startIndex = Math.min( (double) (c.points.size() - 1), Math.max( 0.0, c.indexOf( start )));
endIndex = Math.max( 0.0, Math.min( (double) (c.points.size() - 1), c.indexOf( end )));
// virtueller Startpunkt
floor = (int) Math.floor( startIndex );
ceil = (int) Math.ceil( startIndex );
floorPt = c.points.elementAt( floor );
ceilPt = c.points.elementAt( ceil );
predPt = new DoublePoint( start, floorPt.y + (startIndex - floor) *
(ceilPt.y - floorPt.y) );
firstIndex = ceil;
// virtueller Endpunkt
floor = (int) Math.floor( endIndex );
ceil = (int) Math.ceil( endIndex );
floorPt = c.points.elementAt( floor );
ceilPt = c.points.elementAt( ceil );
lastPt = new DoublePoint( end, floorPt.y + (endIndex - floor) *
(ceilPt.y - floorPt.y) );
lastIndex = floor;
// integrieren: punkt-zu-punkt flaeche = delta-x * (y0 + 1/2 delta-y) ; aufaddieren
integral = 0.0;
for( int i = firstIndex; i <= lastIndex; i++ ) {
succPt = c.points.elementAt( i );
integral += (succPt.x - predPt.x) * (predPt.y + (succPt.y - predPt.y) / 2);
predPt = succPt;
}
integral += (lastPt.x - predPt.x) * (predPt.y + (lastPt.y - predPt.y) / 2);
return( integral / width ); // average
} else { // einzelnen Y-Wert ermitteln
startIndex = Math.min( (double) c.points.size(), Math.max( 0.0, c.indexOf( start )));
floor = (int) Math.floor( startIndex );
ceil = (int) Math.ceil( startIndex );
floorPt = c.points.elementAt( floor );
ceilPt = c.points.elementAt( ceil );
// vvv 0...1 (quasi Gewichtung zwischen floor+ceil)
return( floorPt.y + (startIndex - floor) * (ceilPt.y - floorPt.y) );
}
}
// -------- StringComm methods --------
public String toString()
{
StringBuffer strBuf;
strBuf = new StringBuffer( hSpace.toString() + ';' + vSpace.toString() + ';' + type );
for( int i = 0; i < points.size(); i++ ) {
strBuf.append( ";" + points.elementAt( i ).toString() );
}
return( strBuf.toString() );
}
/**
* @param s MUST BE in the format as returned by Curve.toString()
*/
public static Curve valueOf( String s )
{
StringTokenizer strTok;
Curve c;
strTok = new StringTokenizer( s, ";" );
c = new Curve( ParamSpace.valueOf( strTok.nextToken() ), // hSpace
ParamSpace.valueOf( strTok.nextToken() ), // vSpace
Integer.parseInt( strTok.nextToken() )); // type
while( strTok.hasMoreElements() ) {
c.points.addElement( DoublePoint.valueOf( strTok.nextToken() ));
}
return c;
}
}
// class Curve