/* * SynthesisOp.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.op; import de.sciss.fscape.gui.GroupLabel; import de.sciss.fscape.gui.OpIcon; import de.sciss.fscape.gui.PathField; import de.sciss.fscape.gui.PropertyGUI; import de.sciss.fscape.prop.OpPrefs; import de.sciss.fscape.prop.Prefs; import de.sciss.fscape.prop.Presets; import de.sciss.fscape.prop.PropertyArray; import de.sciss.fscape.spect.Fourier; import de.sciss.fscape.spect.SpectFrame; import de.sciss.fscape.spect.SpectStream; import de.sciss.fscape.spect.SpectStreamSlot; import de.sciss.fscape.util.Constants; import de.sciss.fscape.util.Filter; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.Slots; import de.sciss.fscape.util.Util; import de.sciss.io.AudioFile; import de.sciss.io.AudioFileDescr; import de.sciss.io.IOUtil; import java.awt.*; import java.io.EOFException; import java.io.File; import java.io.IOException; /** * Synthesis Operator */ public class SynthesisOp extends Operator { // -------- private variables -------- protected static final String defaultName = "Synthesize"; protected static Presets static_presets = null; protected static Prefs static_prefs = null; protected static PropertyArray static_pr = null; // Slots protected static final int SLOT_INPUT = 0; // es kann nur einen geben // Properties (defaults) private static final int PR_FILENAME = 0; // Array-Indices: pr.text private static final int PR_WINDOW = 0; // pr.intg private static final int PR_TYPE = 1; private static final int PR_ROTATE = 0; // pr.bool private static final int PR_LOFREQ = 0; // pr.para private static final int PR_HIFREQ = 1; private static final int PR_LORADIUS = 2; private static final int PR_HIRADIUS = 3; protected static final int TYPE_FFT = 0; protected static final int TYPE_CZT = 1; protected static final int TYPE_NONE = 2; private static final String PRN_FILENAME = "Filename"; // Property-Keynames private static final String PRN_WINDOW = "Window"; private static final String PRN_ROTATE = "Rotate"; private static final String PRN_TYPE = "Type"; private static final String PRN_LOFREQ = "LoFreq"; private static final String PRN_HIFREQ = "HiFreq"; private static final String PRN_LORADIUS = "LoRadius"; private static final String PRN_HIRADIUS = "HiRadius"; private static final String prText[] = { "" }; private static final String prTextName[] = { PRN_FILENAME }; private static final int prIntg[] = { 0, TYPE_FFT }; private static final String prIntgName[] = { PRN_WINDOW, PRN_TYPE }; private static final boolean prBool[] = { true }; private static final String prBoolName[] = { PRN_ROTATE }; private static final Param prPara[] = { null, null, null, null }; private static final String prParaName[] = { PRN_LOFREQ, PRN_HIFREQ, PRN_LORADIUS, PRN_HIRADIUS }; // Laufzeitfehler protected static final String ERR_NOOUTPUT = "No output file"; // -------- public methods -------- // public Container createGUI( int type ); public SynthesisOp() { super(); // initialize only in the first instance // preferences laden if( static_prefs == null ) { static_prefs = new OpPrefs( getClass(), getDefaultPrefs() ); } // propertyarray defaults if( static_pr == null ) { static_pr = new PropertyArray(); static_pr.text = prText; static_pr.textName = prTextName; static_pr.intg = prIntg; static_pr.intgName = prIntgName; static_pr.bool = prBool; static_pr.boolName = prBoolName; static_pr.para = prPara; // XXX static_pr.para[ PR_STARTSHIFT ] = new Param( 0.0, Param.ABS_MS ); // XXX static_pr.para[ PR_LENGTH ] = new Param( 5000.0, Param.ABS_MS ); static_pr.para[ PR_LOFREQ ] = new Param( 0.0, Param.ABS_HZ ); static_pr.para[ PR_HIFREQ ] = new Param( 22050.0, Param.ABS_HZ ); static_pr.para[ PR_LORADIUS ] = new Param( 0.0, Param.DECIBEL_AMP ); static_pr.para[ PR_HIRADIUS ] = new Param( 0.0, Param.DECIBEL_AMP ); static_pr.paraName = prParaName; static_pr.superPr = Operator.op_static_pr; } // default preset if( static_presets == null ) { static_presets = new Presets( getClass(), static_pr.toProperties( true )); } // superclass-Felder uebertragen opName = "SynthesisOp"; prefs = static_prefs; presets = static_presets; pr = (PropertyArray) static_pr.clone(); // slots slots.addElement( new SpectStreamSlot( this, Slots.SLOTS_READER )); // SLOT_INPUT // icon icon = new OpIcon( this, OpIcon.ID_OUTPUT, defaultName ); } // -------- Runnable methods -------- public void run() { runInit(); // superclass int i, j, k, m, ch; double d1; float f1; // Haupt-Variablen fuer den Prozess String runFileName; AudioFile runFile = null; SpectStreamSlot runInSlot; SpectStream runInStream = null; SpectStream tmpStream; AudioFileDescr runOutStream; SpectFrame runInFr = null; int winSize, winHalf, winStep, fftLength; float[] win; int inChanNum; float[] fftBuf, convBuf1; float[][] outBuf; int outBufOff; int samplesWritten; // sample *frames*! int chunkLength; // boolean chirp = pr.intg[ PR_TYPE ] == TYPE_CZT; // boolean fourier = pr.intg[ PR_TYPE ] == TYPE_FFT; // double loFreq, hiFreq, loRadius, hiRadius, chirpAngle, chirpRadius; // ------------------------------ Filename ------------------------------ if( (pr.text[ PR_FILENAME ] == null) || (pr.text[ PR_FILENAME ].length() == 0) ) { FileDialog fDlg; // ausnahmsweise nicht am methods-Anfang ;) String foName, foDir; final Component ownerF = owner.getModule().getComponent(); final boolean makeF = !(ownerF instanceof Frame); final Frame f = makeF ? new Frame() : (Frame) ownerF; fDlg = new FileDialog( f, ((OpIcon) getIcon()).getName() + ": Select output file", FileDialog.SAVE ); fDlg.setVisible( true ); if( makeF ) f.dispose(); foName = fDlg.getFile(); foDir = fDlg.getDirectory(); fDlg.dispose(); if( foDir == null) foDir = ""; if( foName == null) { // Cancel. Wir koennen folglich nichts mehr tun runQuit( new IOException( ERR_NOOUTPUT )); return; } runFileName = foDir + foName; } else { runFileName = pr.text[ PR_FILENAME ]; } topLevel: try { // no format field yet and normalization would be a problem // ; therefore just write a floating point ircam file IOUtil.createEmptyFile( new File( runFileName )); // ------------------------------ Input-Slot ------------------------------ runInSlot = slots.elementAt( SLOT_INPUT ); if( runInSlot.getLinked() == null ) { runStop(); // threadDead = true -> folgendes for() wird uebersprungen } // diese while Schleife ist noetig, da beim initReader ein Pause eingelegt werden kann // und die InterruptException ausgeloest wird; danach versuchen wir es erneut for( boolean initDone = false; !initDone && !threadDead; ) { try { runInStream = runInSlot.getDescr(); // throws InterruptedException initDone = true; } catch( InterruptedException ignored) {} runCheckPause(); } if( threadDead ) break topLevel; inChanNum = runInStream.chanNum; // ------------------------------ weitere init ------------------------------ tmpStream = new SpectStream(); tmpStream.smpRate = runInStream.smpRate; // this sucks like hell // loFreq = Param.transform( pr.para[ PR_LOFREQ ], Param.ABS_HZ, null, tmpStream ).value * // Constants.PI2 / tmpStream.smpRate; // hiFreq = Param.transform( pr.para[ PR_HIFREQ ], Param.ABS_HZ, null, tmpStream ).value * // Constants.PI2 / tmpStream.smpRate; // loRadius = Param.transform( pr.para[ PR_LORADIUS ], Param.ABS_AMP, new Param( 1.0, Param.ABS_AMP ), // null ).value; // hiRadius = Param.transform( pr.para[ PR_HIRADIUS ], Param.ABS_AMP, new Param( 1.0, Param.ABS_AMP ), // null ).value; winStep = runInStream.smpPerFrame; fftLength = (runInStream.bands - 1) << 1; winSize = fftLength; winHalf = winSize >> 1; fftBuf = new float[ fftLength + 2 ]; outBuf = new float[ inChanNum ][ winSize ]; Util.clear( outBuf ); win = Filter.createFullWindow( winSize, pr.intg[ PR_WINDOW ]); // normalize energy for( i = 0, d1 = 0.0; i < winSize; i++ ) { d1 += (double) win[ i ]; } f1 = (float) ((double) fftLength * (double) winStep / d1); // (1.0 / d1); for( i = 0; i < winSize; i++ ) { win[ i ] *= f1; } // ------------------------------ AudioFileDescr ------------------------------ runOutStream = new AudioFileDescr(); runOutStream.channels = inChanNum; runOutStream.rate = runInStream.smpRate; runOutStream.bitsPerSample = 32; runOutStream.sampleFormat = AudioFileDescr.FORMAT_FLOAT; runOutStream.type = AudioFileDescr.TYPE_IRCAM; runOutStream.file = new File( runFileName ); runFile = AudioFile.openAsWrite( runOutStream ); // ------------------------------ Hauptschleife ------------------------------ runSlotsReady(); outBufOff = 0; // rotate offset instead of buffer content shifting (faster) samplesWritten = 0; chunkLength = winStep; // System.out.println( "Sy: chunkLen "+winStep ); mainLoop: while( !threadDead ) { for( boolean readDone = false; (!readDone) && !threadDead; ) { try { runInFr = runInSlot.readFrame(); // throws InterruptedException readDone = true; } catch( InterruptedException ignored) {} catch( EOFException e ) { break mainLoop; } runCheckPause(); } if( threadDead ) break mainLoop; // transform for( ch = 0; ch < inChanNum; ch++ ) { convBuf1 = runInFr.data[ ch ]; Fourier.polar2Rect( convBuf1, 0, fftBuf, 0, fftLength + 2 ); // if( (runInStream.framesRead < 3) && (ch == 0) ) Debug.view( fftBuf, "sy IFFT "+runInStream.framesRead ); convBuf1 = outBuf[ ch ]; switch( pr.intg[ PR_TYPE ]) { case TYPE_CZT: // -------------------- CZT -------------------- throw new IOException( "CZT not yet implemented!" ); // break; case TYPE_FFT: // -------------------- FFT -------------------- Fourier.realTransform( fftBuf, fftLength, Fourier.INVERSE ); if( pr.bool[ PR_ROTATE ]) { // apply right wing window for( i = 0, j = winHalf; i < winHalf; i++, j++ ) { fftBuf[ i ] *= win[ j ]; } // zero pad oversampling range for( ; i < fftLength - winHalf; i++ ) { fftBuf[ i ] = 0.0f; } // apply left wing window for( j = 0; i < fftLength; i++, j++ ) { fftBuf[ i ] *= win[ j ]; } // we shift the origin so that the middle smp of inBuf becomes fftBuf[ 0 ], // this gives better phasograms // not compatible with soundhack; but we aren't anyways ;---) for( i = outBufOff, j = fftLength - winHalf, k = 0; k < winSize; ) { m = Math.min( winSize - k, Math.min( winSize - i, fftLength - j )); for( ; m > 0; m--, k++ ) { convBuf1[ i++ ] += fftBuf[ j++ ]; } i %= winSize; j %= fftLength; } } else { // mix windowed output portion for( i = outBufOff, j = 0, k = 0; k < winSize; ) { m = Math.min( winSize - k, winSize - i ); for( ; m > 0; m-- ) { convBuf1[ i++ ] += fftBuf[ j++ ] * win[ k++ ]; } i %= winSize; } } break; case TYPE_NONE: // -------------------- None -------------------- if( pr.bool[ PR_ROTATE ]) { // apply right wing window for( i = 0, j = winHalf; i < winHalf; i++, j++ ) { fftBuf[ i ] *= win[ j ]; } // zero pad oversampling range for( ; i < fftLength - winHalf; i++ ) { fftBuf[ i ] = 0.0f; } // apply left wing window for( j = 0; i < fftLength; i++, j++ ) { fftBuf[ i ] *= win[ j ]; } // we shift the origin so that the middle smp of inBuf becomes fftBuf[ 0 ], // this gives better phasograms // not compatible with soundhack; but we aren't anyways ;---) for( i = outBufOff, j = fftLength - winHalf, k = 0; k < winSize; ) { m = Math.min( winSize - k, Math.min( winSize - i, fftLength - j )); for( ; m > 0; m--, k++ ) { convBuf1[ i++ ] += fftBuf[ j++ ]; } i %= winSize; j %= fftLength; } } else { // mix windowed output portion for( i = outBufOff, j = 0, k = 0; k < winSize; ) { m = Math.min( winSize - k, winSize - i ); for( ; m > 0; m-- ) { convBuf1[ i++ ] += fftBuf[ j++ ] * win[ k++ ]; } i %= winSize; } } break; } // switch type } // for channels // if( (runInStream.framesRead < 3) && (ch == 0) ) Debug.view( outBuf[0], "sy win outbuf "+runInStream.framesRead ); // write sound chunk runFile.writeFrames( outBuf, outBufOff, chunkLength ); // zero obsolete portion for( ch = 0; ch < inChanNum; ch++ ) { convBuf1 = outBuf[ ch ]; for( i = outBufOff + chunkLength; i > outBufOff; ) { convBuf1[ --i ] = 0.0f; } } samplesWritten += chunkLength; outBufOff = (outBufOff + chunkLength) % winSize; runFrameDone( runInSlot, runInFr ); runInSlot.freeFrame( runInFr ); } // end of main loop runInStream.closeReader(); // flush overlap buf if( !threadDead ) { for( i = winStep; i < winSize; i += winStep ) { runFile.writeFrames( outBuf, outBufOff, chunkLength ); outBufOff = (outBufOff + chunkLength) % winSize; } } runFile.close(); } // break topLevel catch( IOException e ) { if( runFile != null ) { runFile.cleanUp(); } runQuit( e ); return; } // catch( OutOfMemoryException e ) { // if( runFile != null ) { // runFile.cleanUp(); // } // abort( e ); // return; // } runQuit( null ); } // -------- GUI methods -------- public PropertyGUI createGUI(int type) { PropertyGUI gui; String[] winNames = Filter.getWindowNames(); StringBuffer winChoise = new StringBuffer(); if( type != GUI_PREFS ) return null; for( int i = 0; i < winNames.length; i++ ) { winChoise.append( ",it" ); winChoise.append( winNames[ i ]); } gui = new PropertyGUI( "gl"+GroupLabel.NAME_GENERAL+"\n" + "lbFilename;io"+PathField.TYPE_OUTPUTFILE+"|Select output file,pr"+PRN_FILENAME+"\n" + "lbTransform;ch,ac0|3|di|4|di|5|di|6|di,ac1|3|en|4|en|5|en|6|en,pr"+PRN_TYPE+"," + "itDiscrete Fourier," + "itChirp Z," + "itNone\n" + "lbWindow;ch,pr"+PRN_WINDOW+winChoise.toString()+"\n"+ "cbRotate origin,pr"+PRN_ROTATE+ "\nglChirp transform specs\n" + "lbLow freq;pf"+Constants.absHzSpace+",id3,pr"+PRN_LOFREQ+"\n" + "lbHigh freq;pf"+Constants.absHzSpace+",id4,pr"+PRN_HIFREQ+"\n" + "lbLow radius;pf"+Constants.decibelAmpSpace+",id5,pr"+PRN_LORADIUS +"\n" + "lbHigh radius;pf"+Constants.decibelAmpSpace+",id6,pr"+PRN_HIRADIUS ); return gui; } }