Package de.sciss.eisenkraut.math

Source Code of de.sciss.eisenkraut.math.ConstQ$Kernel

/*
*  ConstQ.java
*  Eisenkraut
*
*  Copyright (c) 2004-2014 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
*
*
*  Changelog:
*/

package de.sciss.eisenkraut.math;

import java.util.prefs.Preferences;

import de.sciss.util.Param;
import de.sciss.util.ParamSpace;

/**
@version  0.70, 25-Apr-08
@author    Hanns Holger Rutz
*/
public class ConstQ
{
  public static final String KEY_MINFREQ    = "minfreq";    // Param (Hz)
  public static final String KEY_MAXFREQ    = "maxfreq";    // Param (Hz)
  public static final String KEY_BANDSPEROCT  = "bandsperoct"// Param (none)
  public static final String KEY_MAXTIMERES  = "maxtimeres" // Param (ms)
  public static final String KEY_MAXFFTSIZE  = "maxfftsize";    // Param (String fftSize)
 
  private Kernel[]  kernels;
  private int      numKernels;
  private int      fftSize;
  private float[]    fftBuf;

  // rather moderate defaults with 55 Hz, 8ms spacing, 4096 FFT...
  private float    minFreq    = 55f// 27.5f;
  private float    maxFreq    = 20000f;
  private float    maxTimeRes  = 8f// 5f;
  private int      bandsPerOct  = 24// 24
  private int      maxFFTSize  = 4096// 8192;
 
  private double    fs;
 
//  private double    LNKORR_ADD; // = -2 * Math.log( fftSize );

//  private static final double  TENBYLOG10        = 10 / MathUtil.LN10;

  public ConstQ() { /* empty */ }
 
  public int getNumKernels()
  {
    return numKernels;
  }

  public int getFFTSize()
  {
    return fftSize;
  }
 
  public float[] getFFTBuffer()
  {
    return fftBuf;
  }

  public void setSampleRate( double fs )
  {
    this.fs = fs;
  }
 
  public double getSampleRate()
  {
    return fs;
  }
 
  public void setMinFreq( float minFreq )
  {
    this.minFreq  = minFreq;
  }
 
  public float getMinFreq()
  {
    return minFreq;
  }
 
  public void setMaxFreq( float maxFreq )
  {
    this.maxFreq = maxFreq;
  }
 
  public float getMaxFreq()
  {
    return maxFreq;
  }
 
  public void setMaxTimeRes( float maxTimeRes )
  {
    this.maxTimeRes  = maxTimeRes;
  }
 
  public float getMaxTimeRes()
  {
    return maxTimeRes;
  }
 
  public void setMaxFFTSize( int maxFFTSize )
  {
    this.maxFFTSize  = maxFFTSize;
  }
 
  public float getMaxFFTSize()
  {
    return maxFFTSize;
  }
 
  public void setBandsPerOct( int bpo )
  {
    this.bandsPerOct = bpo;
  }
 
  public int getBandsPerOct()
  {
    return bandsPerOct;
  }
 
  public void readPrefs( Preferences prefs )
  {
    Param p;
    String s;
   
    p = Param.fromPrefs( prefs, KEY_MINFREQ, null );
    if( p != null ) minFreq = Math.max( 1, (float) p.val );
    p = Param.fromPrefs( prefs, KEY_MAXFREQ, null );
    if( p != null ) maxFreq = Math.max( minFreq, (float) p.val );
    p = Param.fromPrefs( prefs, KEY_BANDSPEROCT, null );
    if( p != null ) bandsPerOct = Math.max( 1, (int) p.val );
    p = Param.fromPrefs( prefs, KEY_MAXTIMERES, null );
    if( p != null ) maxTimeRes = (float) p.val;
    s = prefs.get( KEY_MAXFFTSIZE, null );
    if( s != null ) {
      try {
        int i = Integer.parseInt( s );
        for( maxFFTSize = 256; maxFFTSize < i; maxFFTSize <<= 1 );
      }
      catch( NumberFormatException e1 ) { /* ignore */ }
    }
  }
 
  public void writePrefs( Preferences prefs )
  {
    prefs.put( KEY_MINFREQ, new Param( minFreq, ParamSpace.FREQ | ParamSpace.HERTZ ).toString() );
    prefs.put( KEY_MAXFREQ, new Param( maxFreq, ParamSpace.FREQ | ParamSpace.HERTZ ).toString() );
    prefs.put( KEY_BANDSPEROCT, new Param( bandsPerOct, ParamSpace.NONE ).toString() );
    prefs.put( KEY_MAXTIMERES, new Param( maxTimeRes, ParamSpace.TIME | ParamSpace.MILLI | ParamSpace.SECS ).toString() );
    prefs.put( KEY_MAXFFTSIZE, String.valueOf( maxFFTSize ));
  }

  public float getFrequency( int kernel )
  {
    return kernels[ kernel ].freq;
  }
 
  public void createKernels()
  {
    final float    threshSqr, q;
    final double  maxKernLen;
    final int    fftSizeC;

    int        kernelLen, kernelLenE, specStart, specStop;
    float[]      win;
    float      f1, f2;
    double      theorKernLen, centerFreq, centerFreqN, weight, d1, cos, sin;

//    System.out.println( "Calculating sparse kernel matrices" );
   
    maxFreq    = (float) Math.min( maxFreq, fs/2 );
    q      = (float) (1 / (Math.pow( 2, 1.0/bandsPerOct ) - 1));
    numKernels  = (int) Math.ceil( bandsPerOct * MathUtil.log2( maxFreq / minFreq ));
    kernels    = new Kernel[ numKernels ];
//    cqKernels  = new float[ cqKernelNum ][];
//    cqKernelOffs= new int[ cqKernelNum ];
    maxKernLen  = q * fs / minFreq;
   
//    System.out.println( "ceil " + ((int) Math.ceil( maxKernLen )) + "; nextPower " + (MathUtil.nextPowerOfTwo( (int) Math.ceil( maxKernLen )))) ;
   
    fftSize    = Math.min( maxFFTSize,
        MathUtil.nextPowerOfTwo( (int) Math.ceil( maxKernLen )));
//    LNKORR_ADD  = -2 * Math.log( fftSize );
    fftSizeC  = fftSize << 1;
    fftBuf    = new float[ fftSizeC ];
//    thresh    = 0.0054f / fftLen; // for Hamming window
    // weird observation : lowering the threshold will _increase_ the
    // spectral noise, not improve analysis! so the truncating of the
    // kernel is essential for a clean analysis (why??). the 0.0054
    // seems to be a good choice, so don't touch it.
//    threshSqr  = 2.916e-05f / (fftSize * fftSize); // for Hamming window (squared!)
//    threshSqr  = 2.916e-05f / fftSize; // for Hamming window (squared!)
    // (0.0054 * 3).squared
    threshSqr  = 2.6244e-4f / (fftSize * fftSize); // for Hamming window (squared!)
    // tempKernel= zeros(fftLen, 1);
//    sparKernel= [];
   
//    System.out.println( "cqKernelNum = " + cqKernelNum + "; maxKernLen = " + maxKernLen + "; fftSize = " + fftSize + "; threshSqr " + threshSqr );
   
    for( int k = 0; k < numKernels; k++ ) {
      theorKernLen = maxKernLen * (float) Math.pow( 2, (double) -k / bandsPerOct );
      kernelLen  = Math.min( fftSize, (int) Math.ceil( theorKernLen ));
      kernelLenE  = kernelLen & ~1;
      win      = Filter.createFullWindow( kernelLen, Filter.WIN_HAMMING );
     
      centerFreq  = minFreq * Math.pow( 2, (float) k / bandsPerOct );
      centerFreqN  = centerFreq * -MathUtil.PI2 / fs;

//      weight    = 1.0f / (kernelLen * fftSize);
      // this is a good approximation in the kernel len truncation case
      // (tested with pink noise, where it will appear pretty much
      // with the same brightness over all frequencies)
      weight    = 6 / ((theorKernLen + kernelLen) * fftSize);
//      weight    = 2 / ((theorKernLen + kernelLen) * Math.sqrt( fftSize ));
      for( int m = kernelLenE, n = fftSizeC - kernelLenE; m < n; m++ ) {
        fftBuf[ m ] = 0f;
      }
                 
      // note that we calculate the complex conjugation of
      // the temporalKernal and reverse its time, so the resulting
      // FFT can be immediately used for the convolution and does not
      // need to be conjugated; this is due to the Fourier property
      // h*(-x) <-> H*(f). time reversal is accomplished by
      // having iteration variable j run....

      // XXX NO! we don't take the reversed conjugate since
      // there seems to be a bug with Fourier.complexTransform
      // that already computes the conjugate spectrum (why??)
//      for( int i = kernelLen - 1, j = fftSizeC - kernelLenE; i >= 0; i-- ) { ... }
//      for( int i = 0, j = 0; /* fftSizeC - kernelLenE; */ i < kernelLen; i++ ) { ... }
      for( int i = 0, j = fftSizeC - kernelLenE; i < kernelLen; i++ ) {
        // complex exponential of a purely imaginary number
        // is cos( imag( n )) + i sin( imag( n ))
        d1      = centerFreqN * i;
//        d1      = centerFreqN * (i - kernelLenE);
        cos      = Math.cos( d1 );
        sin      = Math.sin( d1 ); // Math.sqrt( 1 - cos*cos );
        d1      = win[ i ] * weight;
        fftBuf[ j++ ] = (float) (d1 * cos);
//        fftBuf[ j++ ] = -f1 * sin;  // conj!
        fftBuf[ j++ ] = (float) (d1 * sin)// NORM!
        if( j == fftSizeC ) j = 0;
      }
     
      // XXX to be honest, i don't get the point
      // of calculating the fft here, since we
      // have an analytic description of the kernel
      // function, it should be possible to calculate
      // the spectral coefficients directly
      // (the fft of a hamming is a gaussian,
      // isn't it?)
     
      Fourier.complexTransform( fftBuf, fftSize, Fourier.FORWARD );
      // with a "high" threshold like 0.0054, the
      // point it _not_ to create a sparse matrix by
      // gating the values. in fact we can locate
      // the kernal spectrally, so all we need to do
      // is to find the lower and upper frequency
      // of the transformed kernel! that makes things
      // a lot easier anyway since we don't need
      // to employ a special sparse matrix library.
      for( specStart = 0; specStart <= fftSize; specStart += 2 ) {
        f1 = fftBuf[ specStart ];
        f2 = fftBuf[ specStart+1 ];
        if( (f1 * f1 + f2 * f2) > threshSqr ) break;
      }
      // final matrix product:
      // input chunk (fft'ed) is a row vector with n = fftSize
      // kernel is a matrix mxn with m = fftSize, n = numKernels
      // result is a row vector with = numKernels
      // ; note that since the input is real and hence we
      // calculate only the positive frequencies (up to pi),
      // we might need to mirror the input spectrum to the
      // negative frequencies. however it can be observed that
      // for practically all kernels their frequency bounds
      // lie in the positive side of the spectrum (only the
      // high frequencies near nyquest blur accross the pi boundary,
      // and we will cut the overlap off by limiting specStop
      // to fftSize instead of fftSize<<1 ...).

      for( specStop = specStart; specStop <= fftSize; specStop += 2 ) {
        f1 = fftBuf[ specStop ];
        f2 = fftBuf[ specStop+1 ];
        if( (f1 * f1 + f2 * f2) <= threshSqr ) break;
      }

//System.out.println( "Kernel k : specStart " + specStart + "; specStop " + specStop + "; centerFreq " + centerFreq );
      kernels[ k ] = new Kernel( specStart, new float[ specStop - specStart ], (float) centerFreq );
      System.arraycopy( fftBuf, specStart, kernels[ k ].data, 0, specStop - specStart );
    }
  }

//  /**
//   *   Helper method for SwingOSC
//   */
//  public float[] createFloatArray( int size )
//  {
//    return new float[ size ];
//  }
//
//  /**
//   *   Helper method for SwingOSC
//   */
//  public float[][] createFloatArray( int size1, int size2 )
//  {
//    return new float[ size1 ][ size2 ];
//  }
// 
//  /**
//   *   Helper method for SwingOSC
//   */
//  public float[] getFloatArray( float[][] array2D, int dim )
//  {
//    return array2D[ dim ];
//  }
 
  /**
   *   Helper method for SwingOSC
   */
  public float[] castToFloatArray( Object[] data )
  {
    final float[] buf = new float[ data.length ];
    for( int i = 0; i < buf.length; i++ ) {
      buf[ i ] = ((Number) data[ i ]).floatValue();
    }
    return buf;
  }
 
  /**
   *   Helper method for SwingOSC
   */
  public float getArrayElement( float[] data, int idx )
  {
    return data[ idx ];
  }

  /**
   *   Assumes that the input was already successfully transformed
   *   into the Fourier domain (namely into fftBuf as returned by
   *   getFFTBuffer()). From this FFT, the method calculates the
   *   convolutions with the filter kernels, returning the magnitudes
   *   of the filter outputs.
   *
   *  @param  output
   *  @param  outOff
   *  @return
   */
  public float[] convolve( float[] output, int outOff )
  {
    if( output == null ) output = new float[ numKernels ];
   
    float[]  kern;
    float  f1, f2;
       
    for( int k = 0; k < numKernels; k++, outOff++ ) {
      kern  = kernels[ k ].data;
      f1    = 0f;
      f2    = 0f;
      for( int i = kernels[ k ].offset, j = 0; j < kern.length; i += 2, j += 2 ) {
        // complex mult: a * b =
        // (re(a)re(b)-im(a)im(b))+i(re(a)im(b)+im(a)re(b))
        // ; since we left out the conjugation of the kernel(!!)
        // this becomes (assume a is input and b is kernel):
        // (re(a)re(b)+im(a)im(b))+i(im(a)re(b)-re(a)im(b))
        // ; in fact this conjugation is unimportant for the
        // calculation of the magnitudes...
        f1 += fftBuf[ i ] * kern[ j ] - fftBuf[ i+1 ] * kern[ j+1 ];
        f2 += fftBuf[ i ] * kern[ j+1 ] + fftBuf[ i+1 ] * kern[ j ];
      }
//      cBuf[ k ] = ;  // squared magnitude
//      f1 = (float) ((Math.log( f1 * f1 + f2 * f2 ) + LNKORR_ADD) * gain);
//f1 = Math.max( -160, f1 + 90 );
     
      // since we use constQ to decimate spectra, we actually
      // are going to store a "mean square" of the amplitudes
      // so at the end of the decimation we'll have one squareroot,
      // or even easier, when calculating the logarithm, the
      // multiplicator of the log just has to be divided by two.
      // hence we don't calc abs( f ) but abs( f )^2 !
     
      output[ outOff ] = (f1 * f1 + f2 * f2);
    }
   
    return output;
  }
 
  public float[] transform( float[] input, int inOff, int inLen, float output[], int outOff )
  {
    if( output == null ) output = new float[ numKernels ];
   
    final int off, num, num2;

//    gain *= TENBYLOG10;
    off = fftSize >> 1;
    num = Math.min( fftSize - off, inLen );
   
//System.out.println( "inOff " + inOff + "; num  " + num + " ; inOff + num " + (inOff + num) + "; input.length " + input.length + "; fftBuf.length " + fftBuf.length );
   
    System.arraycopy( input, inOff, fftBuf, off, num );
    for( int i = off + num; i < fftSize; i++ ) {
      fftBuf[ i ] = 0f;
    }
    num2 = Math.min( fftSize - num, inLen - num );
    System.arraycopy( input, inOff + num, fftBuf, 0, num2 );
    for( int i = num2; i < off; i++ ) {
      fftBuf[ i ] = 0f;
    }
   
    // XXX evtl., wenn inpWin weggelassen werden kann,
    // optimierte overlap-add fft
    Fourier.realTransform( fftBuf, fftSize, Fourier.FORWARD );
   
    return convolve( output, outOff );
  }
   
  private static class Kernel
  {
    protected final int      offset;
    protected final float[]    data;
    protected final float    freq;
   
    protected Kernel( int offset, float[] data, float freq )
    {
      this.offset  = offset;
      this.data  = data;
      this.freq  = freq;
    }
  }
}
TOP

Related Classes of de.sciss.eisenkraut.math.ConstQ$Kernel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.