/* * Curve2.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; /** * @version 0.71, 15-Nov-07 */ public class Curve2 { // -------- public variables -------- public static final int INT_LINEAR = 0x000; public static final int INT_SPLINE = 0x001; public static final int INT_SAMPLEHOLD = 0x002; public static final int INTMASK = 0x00F; public static final int TYPE_BIPOLAR = 0x000; public static final int TYPE_UNIPOLAR = 0x010; public int flags; public int size; // in x, y ; arrays duerfen aber laenger sein; mind. 2! public float[] xs; // nur im Bereich 0...1 ; Randpunkte nicht loeschen! Keine doppelten x public float[] ys; public boolean looped = false; public float loopStart = 0.0f; public float loopEnd = 1.0f; public int loopCount = 1; // -------- private variables -------- private boolean valid = false; // private int clcLpStart, clcLpEnd; private float[] yDrv = null; // zweite Ableitung fuer Spline-Interpolation // -------- public methods -------- public Curve2( int flags ) { this.flags = flags; size = 2; xs = new float[ 2 ]; ys = new float[ 2 ]; xs[ 0 ] = 0.0f; ys[ 0 ] = 0.0f; xs[ 1 ] = 1.0f; ys[ 1 ] = 1.0f; } public Curve2() { this( INT_LINEAR + TYPE_BIPOLAR ); } /** * Clont vorgegebene Curve */ public Curve2( Curve2 src ) { this.flags = src.flags; this.size = src.size; this.looped = src.looped; this.loopStart = src.loopStart; this.loopEnd = src.loopEnd; this.loopCount = src.loopCount; this.xs = new float[ size + 16 ]; this.ys = new float[ size + 16 ]; System.arraycopy( src.xs, 0, this.xs, 0, size ); System.arraycopy( src.ys, 0, this.ys, 0, size ); } public Object clone() { return new Curve2( this ); } /** * Needs to be called when x, y, size or flags are changed! */ public void invalidate() { valid = false; } /** * Array-Position eines Abszissen-Wertes bestimmen * * @return x liegt zwischen diesem Index und dem diesem+1 (incl.) * -1 wenn out of range */ public int indexOf( float x ) { if( looped && (x > loopStart) && (x < loopEnd) ) { x = loopStart + (((x - loopStart) * loopCount) % (loopEnd - loopStart)); } if( (x < this.xs[ 0 ]) || (x > this.xs[ size-1 ])) return -1; // bisection; see NumericalRecipes 3.4 int idxHi, idxLo, idxMid; idxLo = 0; idxHi = size - 1; while( (idxHi - idxLo) > 1) { idxMid = (idxHi + idxLo) >> 1; if( x >= this.xs[ idxMid ]) idxLo = idxMid; else idxHi = idxMid; } return idxLo; } /** * Array-Position eines Abszissen-Wertes bestimmen * * @param x Wert * @param idxLo alter Index; damit ist die Suche schneller als obige Routine * @return x liegt zwischen diesem Index und dem diesem+1 (incl.) * -1 wenn out of range */ public int indexOf( float x, int idxLo ) { if( looped && (x > loopStart) && (x < loopEnd) ) { x = loopStart + (((x - loopStart) * loopCount) % (loopEnd - loopStart)); } if( (x < this.xs[ 0 ]) || (x > this.xs[ size-1 ])) return -1; // hunt; see NumericalRecipes 3.4 int idxHi, idxMid, inc; if( idxLo >= 0 ) { inc = 1; // Initial hunting increment. if( x >= this.xs[ idxLo ]) { // Hunt up idxHi = idxLo + 1; if( idxHi >= size - 1 ) return idxLo; while( x >= this.xs[ idxHi ]) { idxLo = idxHi; inc <<= 1; idxHi += inc; if( idxHi >= size - 1 ) { idxHi = size - 1; break; } } // Done hunting, value bracketed. } else { // Hunt down idxHi = idxLo; idxLo--; while( x < this.xs[ idxLo ]) { idxHi = idxLo; inc <<= 1; idxLo -= inc; if( idxLo < 0 ) { idxLo = 0; break; } } // Done hunting, value bracketed. } } else { idxLo = 0; idxHi = size - 1; } // System.out.println( "lo "+idxLo+"; hi "+idxHi ); // bisection phase: while( (idxHi - idxLo) > 1) { idxMid = (idxHi + idxLo) >> 1; if( x >= this.xs[ idxMid ]) idxLo = idxMid; else idxHi = idxMid; } return idxLo; } /** * Ggf. interpolierten Wert an der Stelle x berechnen */ public float calc( float x ) { return calc( x, indexOf( x )); } /** * len Werte im Bereich startX bis stopX berechnen und in a ab off speichern */ public void calc( float startX, float stopX, float[] a, int off, int len ) { len--; float stepX = (stopX - startX) / len; float x; int idx = indexOf( startX ); a[ off ] = calc( startX, idx ); // first value outside loop for( int i = 1, j = off; i < len; i++ ) { x = startX + i * stepX; idx = indexOf( x, idx ); a[ ++j ] = calc( x, idx ); } a[ off+len ] = calc( stopX, idx ); // last value outside loop } // -------- private methods -------- private void validate() { // if( looped ) { // clcLpStart = (int) Math.ceil( indexOf( loopStart )); // clcLpEnd = (int) indexOf( loopEnd ); // } else { // clcLpStart = 0; // clcLpEnd = size - 1; // } if( (flags & INT_SPLINE) != 0 ) { if( (yDrv == null) || (yDrv.length < size) || ((yDrv.length - 64) > size) ) { yDrv = new float[ size + 16 ]; } // spline preparation: second order derivates; see NumericalRecipes 3.3 float[] u = new float[ size - 1 ]; int i, j, k; float p, sig; yDrv[ 0 ] = 0.0f; // "natural" splines u[ 0 ] = 0.0f; yDrv[ size - 1 ] = 0.0f; for( i = 1, j = 0, k = 2; k < size; i++, j++, k++ ) { sig = (xs[ i ] - xs[ j ]) / (xs[ k ] - xs[ j ]); p = sig * yDrv[ j ] + 2.0f; yDrv[ i ] = (sig - 1.0f) / p; u[ i ] = (ys[ k ] - ys[ i ]) / (xs[ k ] - xs[ i ]) - (ys[ i ] - ys[ j ]) / (xs[ i ] - xs[ j ]); u[ i ] = (6.0f * u[ i ] / (xs[ k ] - xs[ j ]) - sig * u[ j ]) / p; } for( i = size - 2; i >= 0; i-- ) { yDrv[ i ] = yDrv[ i ] * yDrv[ i+1 ] + u[ i ]; } } valid = true; } private float calc( float x, int idxLo ) { if( !valid ) validate(); if( idxLo == -1 ) return 0.0f; switch( flags & INTMASK ) { case INT_SAMPLEHOLD: if( x < this.xs[ idxLo+1 ]) { return( this.ys[ idxLo ]); } else { return( this.ys[ idxLo+1 ]); } case INT_SPLINE: return spline( x, idxLo ); default: // INT_LINEAR return linear( x, idxLo ); } } private float linear( float x, int idxLo ) { int idxHi; float h, b, a; idxHi = idxLo + 1; h = this.xs[ idxHi ] - this.xs[ idxLo ]; a = (this.xs[ idxHi ] - x) / h; b = 1.0f - a; return( a * this.ys[ idxLo ] + b * this.ys[ idxHi ]); } // adapted from NumericalRecipes 3.3 private float spline( float x, int idxLo ) { int idxHi; float h, b, a; idxHi = idxLo + 1; h = this.xs[ idxHi ] - this.xs[ idxLo ]; a = (this.xs[ idxHi ] - x) / h; b = 1.0f - a; // (x - this.x[ idxLo ]) / h; return( a * this.ys[ idxLo ] + b * this.ys[ idxHi ] + ((a*a*a - a) * yDrv[ idxLo ] + (b*b*b - b) * yDrv[ idxHi ]) * (h*h) / 6.0f ); } // -------- 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( ";" + ((DoublePoint) points.elementAt( i )).toString() ); } return( strBuf.toString() ); } */ /** * @param s MUST BE in the format as returned by Curve2.toString() */ /* public static Curve2 valueOf( String s ) { StringTokenizer strTok; Curve2 c; strTok = new StringTokenizer( s, ";" ); c = new Curve2( 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 Curve2