/*
* SpectralFile.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.spect;
import de.sciss.fscape.io.GenericFile;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class SpectralFile
extends GenericFile {
// -------- public variables --------
/**
* OR this to remove DC offset
*/
public static final int MODE_REMOVEDC = 0x010000;
// values for freqFormat field
public static final int PVA_LIN = 1; // linearly spaced frequency bins
public static final int PVA_EXP = 2; // exponentially spaced frequency bins
// values for frameFormat field
public static final int PVA_MAG = 1; // magnitude only
public static final int PVA_PHASE = 2; // phase only (!)
public static final int PVA_POLAR = 3; // mag, phase pairs
public static final int PVA_REAL = 4; // real only
public static final int PVA_IMAG = 5; // imaginary only
public static final int PVA_RECT = 6; // real, imag pairs
public static final int PVA_PVOC = 7; // weirdo mag, phi-dot format for phase vocoder
public static final int PVA_CQ = 32; // ORed with previous to indicate one frame per 8ve
public static final String ERR_ILLEGALFREQFORMAT = "Unsupported frequency format";
public static final String ERR_ILLEGALFRAMEFORMAT = "Unsupported frame format";
// -------- private variables --------
private int hdr[];
// hdr[]-Aufbau; adapted from SpectralAssistant by Tom Erbe
protected static final int hdr_magic = 0; // CSA_MAGIC or SHA_MAGIC to identify
protected static final int hdr_headBsize = 1; // byte offset from start to data
protected static final int hdr_dataBsize = 2; // number of bytes of data
protected static final int hdr_dataFormat = 3; // format specifier
protected static final int hdr_smpRate = 4; // FLOAT
protected static final int hdr_chanNum = 5;
protected static final int hdr_frameSize = 6; // size of FFT frames (2^n)
protected static final int hdr_smpPerFrame = 7; // number of new samples each frame (overlap)
protected static final int hdr_frameBsize = 8; // bytes in each file frame, (don't trust this value,
// use (frameSize + 2) * sizeof(float) instead
protected static final int hdr_frameFormat = 9; // how words are org'd in frms
protected static final int hdr_loFreq = 10; // FLOAT freq in Hz of lowest bin (exists)
protected static final int hdr_hiFreq = 11; // FLOAT freq in Hz of highest (or next)
protected static final int hdr_freqMode = 12; // flag for log/lin frq
protected static final int hdr_info = 13; // ?
protected static final int hdr_sizeof = 14;
protected static final int CSA_UNK_LEN = -1; // flag if dataBsize unknown in hdr
// values for dataFormat field
protected static final int PVA_SHORT = 2; // 16 bit linear data
protected static final int PVA_LONG = 4; // 32 bit linear data
protected static final int PVA_FLOAT = (4+32); // 32 bit float data
protected static final int PVA_DOUBLE = (8+32); // 64 bit float data
protected int bands; // frameSize / datanum + 1; where datanum = 2 for polar+rect, else 1
protected long frames; // total number
protected int dataOffset; // MAG, POLAR, RECT = 0, PHASE = 1
protected int dataNum; // MAG, PHASE = 1, POLAR, RECT = 2
protected SpectStream stream = null;
protected byte buffer[];
// -------- public methods --------
/**
* Datei, die spektrale Daten enthaelt bzw. enthalten soll
*
* @param spectF entsprechende Datei
* @param mode MODE_INPUT zum Lesen, MODE_OUTPUT zum Schreiben
*/
public SpectralFile( File spectF, int mode )
throws IOException
{
super( spectF, (mode & ~MODE_TYPEMASK) | MODE_SPECT );
hdr = new int[ hdr_sizeof ];
}
public SpectralFile( String fname, int mode )
throws IOException
{
this( new File( fname ), mode );
}
/**
* erzeugt einen SpectStream, in den das File eingelesen werden kann;
* der Stream ist "passiv", es werden keine Writer/Reader eingetragen;
* er dient nur zum Auslesen des Headers fuer den Operator;
* DENNOCH KEINE AENDERUNGEN MEHR AM SPECTSTREAM VORNEHMEN!
*/
public SpectStream getDescr()
throws IOException
{
float hiFreq, smpRate;
if( stream == null ) {
stream = new SpectStream();
readHeader();
// SND HACK BUG FIX XXXX
smpRate = Float.intBitsToFloat( hdr[ hdr_smpRate ]);
hiFreq = Float.intBitsToFloat( hdr[ hdr_hiFreq ]);
if( hiFreq > smpRate/2 ) { // SndHck writes "Infinity" sometimes
hiFreq = smpRate/2;
}
stream.setChannels( hdr[ hdr_chanNum ]);
stream.setBands( Float.intBitsToFloat( hdr[ hdr_loFreq ]), hiFreq, bands, hdr[ hdr_freqMode ] );
stream.setRate( smpRate, hdr[ hdr_smpPerFrame ] );
stream.setEstimatedLength( frames );
buffer = new byte[ (stream.bands * dataNum * stream.chanNum) << 2 ];
}
seekFrame( 0 );
return stream;
}
/**
* Meldet einen SpectStream zum Schreiben in das File an;
* der Stream bleibt "passiv", vgl. getDescr()!
*
* @param format PVA_MAG, PVA_PHASE oder PVA_POLAR
*/
public void initWriter( SpectStream strm, int format )
throws IOException
{
dataNum = (format == PVA_POLAR) ? 2 : 1;
dataOffset = (format == PVA_PHASE) ? 1 : 0;
bands = strm.bands;
frames = strm.frames;
hdr[ hdr_magic ] = SHA_MAGIC;
hdr[ hdr_headBsize ] = hdr_sizeof << 2;
hdr[ hdr_dataBsize ] = CSA_UNK_LEN; // am Schluss korrigiert
hdr[ hdr_dataFormat ] = PVA_FLOAT;
hdr[ hdr_smpRate ] = Float.floatToIntBits( strm.smpRate );
hdr[ hdr_chanNum ] = strm.chanNum;
hdr[ hdr_frameSize ] = (strm.bands - 1) * dataNum;
hdr[ hdr_smpPerFrame ] = strm.smpPerFrame;
hdr[ hdr_frameBsize ] = hdr[ hdr_frameSize ] << 1; // correct? XXX
hdr[ hdr_frameFormat ] = format;
hdr[ hdr_loFreq ] = Float.floatToIntBits( strm.loFreq );
hdr[ hdr_hiFreq ] = Float.floatToIntBits( strm.hiFreq );
hdr[ hdr_freqMode ] = strm.freqMode;
hdr[ hdr_info ] = 0x46534350; // 'FSCP'
for( int i = 0; i < hdr.length; i++ ) {
writeInt( hdr[ i ]);
}
// SND HACK BUG FIX XXXX
if( hdr[ hdr_chanNum ] == 2 ) {
byte[] foo = new byte[ (strm.bands * dataNum) << 2 ];
for( int i = 0; i < foo.length; i++ ) foo[ i ] = 0;
write( foo );
}
buffer = new byte[ (strm.bands * dataNum * strm.chanNum) << 2 ];
this.stream = strm;
}
/**
* Springt zu einem bestimmten Frame
* (nachfolgende readFrame() und writeFrame()s sind betroffen)
*/
public void seekFrame( long frame )
throws IOException
{
seek( hdr[ hdr_headBsize ] + frame * hdr[ hdr_frameBsize ] * hdr[ hdr_chanNum ]);
}
/**
* Liest einen Frame aus der Datei ein
*/
public SpectFrame readFrame()
throws IOException
{
int i, j, k;
SpectFrame fr = stream.allocFrame();
// frame.length * dataNum/2 * sizeof(data)
readFully( buffer, 0,
fr.data.length * (fr.data[ 0 ].length >> 1) * dataNum * (hdr[ hdr_dataFormat ] & 0x0F) );
switch( hdr[ hdr_dataFormat ] ) {
case PVA_FLOAT: // -------------------------- FLOAT ---------------------
for( i = 0, k = 0; i < fr.data.length; i++ ) { // ch
for( j = 0; j < fr.data[ i ].length; j += 2 ) {
if( dataOffset == 0 ) {
fr.data[ i ][ j + SpectFrame.AMP ] =
Float.intBitsToFloat( ((((int) buffer[ k ]) & 0xFF) << 24) |
((((int) buffer[ k+1 ]) & 0xFF) << 16) |
((((int) buffer[ k+2 ]) & 0xFF) << 8) |
(((int) buffer[ k+3 ]) & 0xFF) );
k += 4;
} else {
fr.data[ i ][ j + SpectFrame.AMP ] = 0.0f;
}
if( dataOffset + dataNum == 2 ) {
fr.data[ i ][ j + SpectFrame.PHASE ] =
Float.intBitsToFloat( ((((int) buffer[ k ]) & 0xFF) << 24) |
((((int) buffer[ k+1 ]) & 0xFF) << 16) |
((((int) buffer[ k+2 ]) & 0xFF) << 8) |
(((int) buffer[ k+3 ]) & 0xFF) );
k += 4;
} else {
fr.data[ i ][ j + SpectFrame.PHASE ] = 0.0f;
}
}
}
break;
case PVA_LONG: // -------------------------- LONG ---------------------
for( i = 0, k = 0; i < fr.data.length; i++ ) { // ch
for( j = 0; j < fr.data[ i ].length; j += 2 ) {
if( dataOffset == 0 ) {
fr.data[ i ][ j + SpectFrame.AMP ] =
(float) (((((int) buffer[ k ]) & 0xFF) << 24) |
((((int) buffer[ k+1 ]) & 0xFF) << 16) |
((((int) buffer[ k+2 ]) & 0xFF) << 8) |
(((int) buffer[ k+3 ]) & 0xFF));
k += 4;
} else {
fr.data[ i ][ j + SpectFrame.AMP ] = 0.0f;
}
if( dataOffset + dataNum == 2 ) {
fr.data[ i ][ j + SpectFrame.PHASE ] =
(float) (((((int) buffer[ k ]) & 0xFF) << 24) |
((((int) buffer[ k+1 ]) & 0xFF) << 16) |
((((int) buffer[ k+2 ]) & 0xFF) << 8) |
(((int) buffer[ k+3 ]) & 0xFF));
k += 4;
} else {
fr.data[ i ][ j + SpectFrame.PHASE ] = 0.0f;
}
}
}
break;
case PVA_DOUBLE: // -------------------------- DOUBLE ---------------------
for( i = 0, k = 0; i < fr.data.length; i++ ) { // ch
for( j = 0; j < fr.data[ i ].length; j += 2 ) {
if( dataOffset == 0 ) {
fr.data[ i ][ j + SpectFrame.AMP ] = (float)
Double.longBitsToDouble ( ((((long) buffer[ k ]) & 0xFF) << 56) |
((((long) buffer[ k+1 ]) & 0xFF) << 48) |
((((long) buffer[ k+2 ]) & 0xFF) << 40) |
((((long) buffer[ k+3 ]) & 0xFF) << 32) |
((((long) buffer[ k+4 ]) & 0xFF) << 24) |
((((long) buffer[ k+5 ]) & 0xFF) << 16) |
((((long) buffer[ k+6 ]) & 0xFF) << 8) |
(((long) buffer[ k+7 ]) & 0xFF) );
k += 8;
} else {
fr.data[ i ][ j + SpectFrame.AMP ] = 0.0f;
}
if( dataOffset + dataNum == 2 ) {
fr.data[ i ][ j + SpectFrame.PHASE ]= (float)
Double.longBitsToDouble ( ((((long) buffer[ k ]) & 0xFF) << 56) |
((((long) buffer[ k+1 ]) & 0xFF) << 48) |
((((long) buffer[ k+2 ]) & 0xFF) << 40) |
((((long) buffer[ k+3 ]) & 0xFF) << 32) |
((((long) buffer[ k+4 ]) & 0xFF) << 24) |
((((long) buffer[ k+5 ]) & 0xFF) << 16) |
((((long) buffer[ k+6 ]) & 0xFF) << 8) |
(((long) buffer[ k+7 ]) & 0xFF) );
k += 8;
} else {
fr.data[ i ][ j + SpectFrame.PHASE ] = 0.0f;
}
}
}
break;
case PVA_SHORT: // -------------------------- SHORT ---------------------
for( i = 0, k = 0; i < fr.data.length; i++ ) { // ch
for( j = 0; j < fr.data[ i ].length; j += 2 ) {
if( dataOffset == 0 ) {
fr.data[ i ][ j + SpectFrame.AMP ] =
(float) (((((int) buffer[ k ]) & 0xFF) << 8) |
(((int) buffer[ k+1 ]) & 0xFF));
k += 2;
} else {
fr.data[ i ][ j + SpectFrame.AMP ] = 0.0f;
}
if( dataOffset + dataNum == 2 ) {
fr.data[ i ][ j + SpectFrame.PHASE ] =
(float) (((((int) buffer[ k ]) & 0xFF) << 8) |
(((int) buffer[ k+1 ]) & 0xFF));
k += 2;
} else {
fr.data[ i ][ j + SpectFrame.PHASE ] = 0.0f;
}
}
}
break;
default:
break;
}
// Rechteck in Polar-Format konviertieren
if( hdr[ hdr_frameFormat ] == PVA_RECT ) {
float srcReal, srcImg;
for( i = 0; i < fr.data.length; i++ ) { // ch
for( j = 0; j < fr.data[ i ].length; j += 2 ) {
srcReal = fr.data[ i ][ j + SpectFrame.AMP ];
srcImg = fr.data[ i ][ j + SpectFrame.PHASE ];
fr.data[ i ][ j + SpectFrame.AMP ] = (float)
Math.sqrt( srcReal*srcReal + srcImg*srcImg );
fr.data[ i ][ j + SpectFrame.PHASE ] = (float)
Math.atan2( srcImg, srcReal );
}
}
}
// lockere Auslegung von DC-Offset: alles unter 10 Hz wegschneiden
if( ((mode & MODE_REMOVEDC) != 0) && (stream.loFreq < 10.0f) ) {
float freqSpacing = (stream.hiFreq - stream.loFreq) / stream.bands;
for( i = 0; i * freqSpacing + stream.loFreq < 10.0f; i++ ) {
for( j = 0; j < fr.data.length; j++ ) { // ch
fr.data[ j ][ (i << 1) + SpectFrame.AMP ] = 0.0f;
fr.data[ j ][ (i << 1) + SpectFrame.PHASE ] = 0.0f;
}
}
}
return fr;
}
/**
* Schreibt einen Frame in die Datei
*/
public void writeFrame( SpectFrame fr )
throws IOException
{
int i, j, k, l;
for( i = 0, k = 0; i < fr.data.length; i++ ) { // ch
for( j = 0; j < fr.data[ i ].length; j += 2 ) {
if( dataOffset == 0 ) {
l = Float.floatToIntBits( fr.data[ i ][ j + SpectFrame.AMP ]);
buffer[ k ] = (byte) ((l & 0xFF000000) >> 24);
buffer[ k+1 ] = (byte) (((l << 8) & 0xFF000000) >> 24);
buffer[ k+2 ] = (byte) (((l << 16) & 0xFF000000) >> 24);
buffer[ k+3 ] = (byte) ((l << 24) >> 24);
k += 4;
}
if( dataOffset + dataNum == 2 ) {
l = Float.floatToIntBits( fr.data[ i ][ j + SpectFrame.PHASE ]);
buffer[ k ] = (byte) ((l & 0xFF000000) >> 24);
buffer[ k+1 ] = (byte) (((l << 8) & 0xFF000000) >> 24);
buffer[ k+2 ] = (byte) (((l << 16) & 0xFF000000) >> 24);
buffer[ k+3 ] = (byte) ((l << 24) >> 24);
k += 4;
}
}
}
// lockere Auslegung von DC-Offset: alles unter 10 Hz wegschneiden
if( ((mode & MODE_REMOVEDC) != 0) && (stream.loFreq < 10.0f) ) {
float freqSpacing = (stream.hiFreq - stream.loFreq) / stream.bands;
for( i = 0, k = 0; i < stream.chanNum; i++ ) { // ch
for( j = 0, l = k; j * freqSpacing + stream.loFreq < 10.0f; j++ ) {
buffer[ l++ ] = 0;
buffer[ l++ ] = 0;
if( dataNum == 2 ) {
buffer[ l++ ] = 0;
buffer[ l++ ] = 0;
}
}
k += stream.bands * (dataNum << 1); // next channel
}
}
// frame.length * dataNum/2 * sizeof(data)
write( buffer, 0, fr.data.length * (fr.data[ 0 ].length << 1) * dataNum );
}
/**
* Erzeugt ein fuer diese Objekt zum Schreiben geeignetes Frame
*/
public SpectFrame allocFrame()
{
return stream.allocFrame();
}
/**
* Gibt einen ueber dieses Objekt erzeugtes Frame frei
*/
public void freeFrame( SpectFrame fr )
{
stream.freeFrame( fr );
}
/**
* Datei schliessen
*/
public void close()
throws IOException
{
stream = null;
if( (mode & MODE_FILEMASK) == MODE_OUTPUT ) {
try {
if( length() >= (hdr_sizeof << 2) ) { // haben wir schon was geschrieben? (= gueltiger hdr)
seek( hdr_dataBsize << 2 ); // ...dann update file length
writeInt( (int) length() - hdr[ hdr_headBsize ]);
}
}
catch( IOException e1 ) {} // egal, man kann auch mit CSA_UNK_LEN leben...
}
super.close();
}
/**
* Format string besorgen
*/
public String getFormat()
throws IOException
{
SpectStream tmpStream;
tmpStream = stream;
if( tmpStream == null ) {
tmpStream = getDescr();
}
return( SpectStream.getFormat( tmpStream ));
}
// -------- private methods --------
/*
* CSound/SoundHack Header einlesen
*/
private void readHeader()
throws IOException
{
seek( 0L );
hdr[ hdr_magic ] = readInt();
// only SoundHack supported at the moment
if( (hdr[ hdr_magic ] != SHA_MAGIC) && (hdr[ hdr_magic ] != CSA_MAGIC) ) {
throw new UnsupportedEncodingException( ERR_ILLEGALFILE );
}
mode = (mode & ~MODE_TYPEMASK) | MODE_SPECT;
for( int i = 1; i < hdr.length; i++ ) {
hdr[ i ] = readInt();
}
// only logarithmic + linear scaling supported
if( (hdr[ hdr_freqMode ] != PVA_LIN) && (hdr[ hdr_freqMode ] != PVA_EXP) ) {
throw new UnsupportedEncodingException( ERR_ILLEGALFREQFORMAT );
}
// supported only mag, phase, polar, rect
switch( hdr[ hdr_frameFormat ] &= 0x0F ) {
case PVA_MAG:
dataOffset = 0;
dataNum = 1;
break;
case PVA_PHASE:
dataOffset = 1;
dataNum = 1;
break;
case PVA_POLAR:
dataOffset = 0;
dataNum = 2;
break;
case PVA_RECT:
dataOffset = 0;
dataNum = 2;
break;
default:
throw new UnsupportedEncodingException( ERR_ILLEGALFRAMEFORMAT );
}
// calc number of frames
if( hdr[ hdr_dataBsize ] <= 0 ) {
hdr[ hdr_dataBsize ] = (int) this.length() - hdr[ hdr_headBsize ];
}
bands = (hdr[ hdr_frameSize ] / dataNum) + 1;
hdr[ hdr_frameBsize ] = (bands * dataNum) * (hdr[ hdr_dataFormat ] & 0x0F); // * bytes per word
frames = hdr[ hdr_dataBsize ] / (hdr[ hdr_frameBsize ] * hdr[ hdr_chanNum ]);
// SND HACK BUG FIX XXXX
if( hdr[ hdr_chanNum ] == 2 ) {
hdr[ hdr_headBsize ] += hdr[ hdr_frameBsize ]; // adjust correct channel offset; loosing 1 frame...
}
}
}
// class SpectralFile