/* * FlipFreqOp.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.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.SpectFrame; import de.sciss.fscape.spect.SpectStream; import de.sciss.fscape.spect.SpectStreamSlot; import de.sciss.fscape.util.Constants; import de.sciss.fscape.util.Envelope; import de.sciss.fscape.util.Modulator; import de.sciss.fscape.util.Param; import de.sciss.fscape.util.Slots; import java.io.EOFException; import java.io.IOException; /** * Operator zum Umkehren der Frequenzverteilung */ public class FlipFreqOp extends Operator { // -------- private variables -------- protected static final String defaultName = "Flip freq"; 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; protected static final int SLOT_OUTPUT = 1; // Properties (defaults) private static final int PR_FLIPFREQ = 0; // pr.bool private static final int PR_MIDMOD = 1; private static final int PR_SHIFTMOD = 2; private static final int PR_HIMOD = 3; private static final int PR_LOMOD = 4; // private static final int PR_CALCTYPE = 0; // pr.intg private static final int PR_MIDFREQ = 0; // pr.para private static final int PR_SHIFTFREQ = 1; private static final int PR_HIFREQ = 2; private static final int PR_LOFREQ = 3; private static final int PR_MIDMODDEPTH = 4; private static final int PR_SHIFTMODDEPTH = 5; private static final int PR_HIMODDEPTH = 6; private static final int PR_LOMODDEPTH = 7; private static final int PR_MIDMODENV = 0; // pr.envl private static final int PR_SHIFTMODENV = 1; private static final int PR_HIMODENV = 2; private static final int PR_LOMODENV = 3; private static final String PRN_FLIPFREQ = "FlipFreq"; private static final String PRN_MIDFREQ = "MidFreq"; private static final String PRN_SHIFTFREQ = "ShiftFreq"; private static final String PRN_HIFREQ = "HiFreq"; private static final String PRN_LOFREQ = "LoFreq"; private static final String PRN_MIDMOD = "MidMod"; private static final String PRN_SHIFTMOD = "ShiftMod"; private static final String PRN_HIMOD = "HiMod"; private static final String PRN_LOMOD = "LoMod"; private static final String PRN_MIDMODDEPTH = "MidModDepth"; private static final String PRN_SHIFTMODDEPTH = "ShiftModDepth"; private static final String PRN_HIMODDEPTH = "HiModDepth"; private static final String PRN_LOMODDEPTH = "LoModDepth"; private static final String PRN_MIDMODENV = "MidModEnv"; private static final String PRN_SHIFTMODENV = "ShiftModEnv"; private static final String PRN_HIMODENV = "HiModEnv"; private static final String PRN_LOMODENV = "LoModEnv"; private static final String PRN_CALCTYPE = "CalcType"; // private static final int PR_CALCTYPE_FACTOR = 0; private static final int PR_CALCTYPE_FREQRANGE = 1; private static final boolean prBool[] = { true, false, false, false, false }; private static final String prBoolName[] = { PRN_FLIPFREQ, PRN_MIDMOD, PRN_SHIFTMOD, PRN_HIMOD, PRN_LOMOD }; private static final int prIntg[] = { PR_CALCTYPE_FREQRANGE }; private static final String prIntgName[] = { PRN_CALCTYPE }; private static final Param prPara[] = { null, null, null, null, null, null, null, null }; private static final String prParaName[] = { PRN_MIDFREQ, PRN_SHIFTFREQ, PRN_HIFREQ, PRN_LOFREQ, PRN_MIDMODDEPTH, PRN_SHIFTMODDEPTH, PRN_HIMODDEPTH, PRN_LOMODDEPTH }; private static final Envelope prEnvl[] = { null, null, null, null }; private static final String prEnvlName[] = { PRN_MIDMODENV, PRN_SHIFTMODENV, PRN_HIMODENV, PRN_LOMODENV }; // -------- public methods -------- // public Container createGUI( int type ); public FlipFreqOp() { 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.bool = prBool; static_pr.boolName = prBoolName; static_pr.intg = prIntg; static_pr.intgName = prIntgName; static_pr.para = prPara; static_pr.para[ PR_MIDFREQ ] = new Param( 1760.0, Param.ABS_HZ ); static_pr.para[ PR_SHIFTFREQ ] = new Param( 0.0, Param.OFFSET_HZ ); static_pr.para[ PR_HIFREQ ] = new Param( 7040.0, Param.ABS_HZ ); static_pr.para[ PR_LOFREQ ] = new Param( 0.0, Param.ABS_HZ ); static_pr.para[ PR_MIDMODDEPTH ] = new Param( 12.0, Param.OFFSET_SEMITONES ); static_pr.para[ PR_SHIFTMODDEPTH ] = new Param( 12.0, Param.OFFSET_SEMITONES ); static_pr.para[ PR_HIMODDEPTH ] = new Param( 12.0, Param.OFFSET_SEMITONES ); static_pr.para[ PR_LOMODDEPTH ] = new Param( 12.0, Param.OFFSET_SEMITONES ); static_pr.paraName = prParaName; static_pr.envl = prEnvl; static_pr.envl[ PR_MIDMODENV ] = Envelope.createBasicEnvelope( Envelope.BASIC_TIME ); static_pr.envl[ PR_SHIFTMODENV ] = Envelope.createBasicEnvelope( Envelope.BASIC_TIME ); static_pr.envl[ PR_HIMODENV ] = Envelope.createBasicEnvelope( Envelope.BASIC_TIME ); static_pr.envl[ PR_LOMODENV ] = Envelope.createBasicEnvelope( Envelope.BASIC_TIME ); static_pr.envlName = prEnvlName; 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 = "FlipFreqOp"; prefs = static_prefs; presets = static_presets; pr = (PropertyArray) static_pr.clone(); // slots slots.addElement( new SpectStreamSlot( this, Slots.SLOTS_READER )); // SLOT_INPUT slots.addElement( new SpectStreamSlot( this, Slots.SLOTS_WRITER )); // SLOT_OUTPUT // icon icon = new OpIcon( this, OpIcon.ID_FLIPFREQ, defaultName ); } // -------- Runnable methods -------- public void run() { runInit(); // superclass // Haupt-Variablen fuer den Prozess SpectStreamSlot runInSlot; SpectStreamSlot runOutSlot; SpectStream runInStream = null; SpectStream runOutStream; SpectFrame runInFr = null; SpectFrame runOutFr = null; // Berechnungs-Grundlagen; in Hz Param midBase, shiftBase, hiBase, loBase; int phaseFactor; // beim Flipping muss die Phase gedreht werden! // Modulations-Variablen boolean recalc = true; // false, wenn sich die Werte nicht geaendert haben Param midFreq; Param shiftFreq; Param hiFreq; Param loFreq; Modulator midMod = null; Modulator shiftMod = null; Modulator hiMod = null; Modulator loMod = null; Param foo; // fuer Lookup-Berechnung double freqSpacing; // linear mode: bandwidth (Hz) double hiScaling; // Scalierung der Frequenzen oberhalb von midFreq double loScaling; // Scalierung der Frequenzen unterhalb von midFreq double loRange; double hiRange; double destFreq; double srcFreq; float srcBand; int srcBands[]; // Beschreibung siehe new()-Befehl unten float srcWeights[]; // Ziel-Frame Berechnung int srcFloorBand; int srcCeilBand; float srcFloorWeight; float srcCeilWeight; float srcAmp, srcPhase; double destReal, destImg; topLevel: try { // ------------------------------ 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; // srcBand[ x ] ist das Band, das fuer das x-te destBand ausgelesen werden muss srcBands = new int[ runInStream.bands + 1 ]; // ein Overhead-Band! // dazugehoerige Gewichtung 0...100%; zur Interpolation wird addiert srcWeights = new float[ runInStream.bands + 1 ]; // Frequency spacing (linear!) freqSpacing = (runInStream.hiFreq - runInStream.loFreq) / runInStream.bands; // ------------------------------ Output-Slot ------------------------------ runOutSlot = slots.elementAt( SLOT_OUTPUT ); runOutStream = new SpectStream( runInStream ); runOutSlot.initWriter( runOutStream ); // ------------------------------ Vorberechnungen ------------------------------ // Phasenumkehrung if( pr.bool[ PR_FLIPFREQ ]) { phaseFactor = -1; } else { phaseFactor = +1; } // constants midBase = Param.transform( pr.para[ PR_MIDFREQ ], Param.ABS_HZ, null, runInStream ); shiftBase = Param.transform( pr.para[ PR_SHIFTFREQ ], Param.ABS_HZ, midBase, runInStream ); hiBase = Param.transform( pr.para[ PR_HIFREQ ], Param.ABS_HZ, midBase, runInStream ); loBase = Param.transform( pr.para[ PR_LOFREQ ], Param.ABS_HZ, midBase, runInStream ); midFreq = midBase; // zunaechst unmoduliert shiftFreq = shiftBase; hiFreq = hiBase; loFreq = loBase; // Modulatoren erzeugen if( pr.bool[ PR_MIDMOD ]) { midMod = new Modulator( midBase, pr.para[ PR_MIDMODDEPTH ], pr.envl[ PR_MIDMODENV ], runInStream ); } if( pr.bool[ PR_SHIFTMOD ]) { shiftMod = new Modulator( shiftBase, pr.para[ PR_SHIFTMODDEPTH ], pr.envl[ PR_SHIFTMODENV ], runInStream ); } if( pr.bool[ PR_HIMOD ]) { hiMod = new Modulator( hiBase, pr.para[ PR_HIMODDEPTH ], pr.envl[ PR_HIMODENV ], runInStream ); } if( pr.bool[ PR_LOMOD ]) { loMod = new Modulator( loBase, pr.para[ PR_LOMODDEPTH ], pr.envl[ PR_LOMODENV ], runInStream ); } // ------------------------------ Hauptschleife ------------------------------ runSlotsReady(); mainLoop: while( !threadDead ) { // ---------- Process: (modulierte) Variablen ---------- if( pr.bool[ PR_MIDMOD ]) { foo = midMod.calc(); if( Math.abs( foo.value - midFreq.value) >= 0.1 ) { // 0.1 Hz treshhold improves speed midFreq = foo; recalc = true; } } if( pr.bool[ PR_SHIFTMOD ]) { foo = shiftMod.calc(); if( Math.abs( foo.value - shiftFreq.value) >= 0.1 ) { shiftFreq = foo; recalc = true; } } if( pr.bool[ PR_HIMOD ]) { foo = hiMod.calc(); if( Math.abs( foo.value - hiFreq.value) >= 0.1 ) { hiFreq = foo; recalc = true; } } if( pr.bool[ PR_LOMOD ]) { foo = loMod.calc(); if( Math.abs( foo.value - loFreq.value) >= 0.1 ) { loFreq = foo; recalc = true; } } // ---------- Frame einlesen ---------- for( boolean readDone = false; (!readDone) && !threadDead; ) { try { runInFr = runInSlot.readFrame(); // throws InterruptedException readDone = true; runOutFr = runOutStream.allocFrame(); } catch( InterruptedException ignored) {} catch( EOFException e ) { break mainLoop; } runCheckPause(); } if( threadDead ) break mainLoop; // ---------- Process: Lookup-Table berechnen ---------- if( recalc ) { // something's changed... // Scalierungen loRange = Math.max( 1, midFreq.value - loFreq.value); hiRange = Math.max( 1, hiFreq.value - midFreq.value); hiScaling = loRange / hiRange; loScaling = hiRange / loRange; // Lookup-Tabelle berechnen if( pr.bool[ PR_FLIPFREQ ]) { // flipped for( int band = 0; band <= runInStream.bands; band++ ) { // ein Overhead-Band! destFreq = band * freqSpacing + runOutStream.loFreq - shiftFreq.value; if( destFreq >= midFreq.value) { srcFreq = midFreq.value - (destFreq - midFreq.value) * hiScaling; } else { srcFreq = (midFreq.value - destFreq) * loScaling + midFreq.value; } srcBand = (float) ((srcFreq - runInStream.loFreq) / freqSpacing); srcBands[ band ] = (int) Math.floor( srcBand ); // Floor = srcBands[ x + 1 ] srcWeights[ band ] = srcBand - srcBands[ band ]; // System.out.println( "Band "+srcBands[band]+" ==> "+band+", weight "+srcWeights[band]*100+"%" ); } } else { // not flipped for( int band = 0; band <= runInStream.bands; band++ ) { destFreq = band * freqSpacing + runOutStream.loFreq + shiftFreq.value; if( destFreq >= midFreq.value) { srcFreq = (destFreq - midFreq.value) * loScaling + midFreq.value; } else { srcFreq = midFreq.value - (midFreq.value - destFreq) * hiScaling; } srcBand = (float) ((srcFreq - runInStream.loFreq) / freqSpacing); srcBands[ band ] = (int) Math.floor( srcBand ); // Ceil = srcBands[ x + 1 ] srcWeights[ band ] = srcBand - srcBands[ band ]; } } recalc = false; } // ---------- Process: Ziel-Frame berechnen ---------- bandLp: for( int band = 0; band < runInStream.bands; band++ ) { // unterstes + oberstes Band; Gewichtung srcFloorBand = srcBands[ band ]; srcCeilBand = srcBands[ band + 1 ]; if( srcFloorBand > srcCeilBand ) { // kleinstes zu unterst srcFloorBand = srcBands[ band + 1 ]; srcCeilBand = srcBands[ band ]; srcFloorWeight = 1.0f - srcWeights[ band + 1 ]; srcCeilWeight = srcWeights[ band ]; } else { srcFloorWeight = 1.0f - srcWeights[ band ]; srcCeilWeight = srcWeights[ band + 1 ]; } if( srcFloorBand < 0 ) { srcFloorBand = 0; if( srcFloorBand < srcCeilBand ) { srcFloorWeight = 1.0f; } else { // voellig ausserhalb; alles Null setzen geht schneller for( int ch = 0; ch < runInStream.chanNum; ch++ ) { runOutFr.data[ ch ][ (band << 1) + SpectFrame.AMP ] = 0.0f; runOutFr.data[ ch ][ (band << 1) + SpectFrame.PHASE ] = 0.0f; } continue bandLp; } } if( srcCeilBand >= runInStream.bands ) { srcCeilBand = runInStream.bands - 1; if( srcCeilBand > srcFloorBand ) { srcCeilWeight = 1.0f; } else { // voellig ausserhalb; alles Null setzen geht schneller for( int ch = 0; ch < runInStream.chanNum; ch++ ) { runOutFr.data[ ch ][ (band << 1) + SpectFrame.AMP ] = 0.0f; runOutFr.data[ ch ][ (band << 1) + SpectFrame.PHASE ] = 0.0f; } continue bandLp; } } if( srcFloorBand == srcCeilBand ) { srcFloorWeight = srcCeilWeight - (1.0f - srcFloorWeight); srcCeilWeight = 0.0f; // assert( srcFloorWeight >= 0f ); } for( int ch = 0; ch < runInStream.chanNum; ch++ ) { // alle Kanaele // unterstes Band berechnen srcAmp = runInFr.data[ ch ][ (srcFloorBand << 1) + SpectFrame.AMP ]; srcPhase = runInFr.data[ ch ][ (srcFloorBand << 1) + SpectFrame.PHASE ]; destImg = srcAmp * Math.sin( srcPhase ) * srcFloorWeight; destReal = srcAmp * Math.cos( srcPhase ) * srcFloorWeight; // Zwischenbaender addieren (sofern vorhanden) for( int i = srcFloorBand + 1; i < srcCeilBand; i++ ) { srcAmp = runInFr.data[ ch ][ (i << 1) + SpectFrame.AMP ]; srcPhase = runInFr.data[ ch ][ (i << 1) + SpectFrame.PHASE ]; destImg += srcAmp * Math.sin( srcPhase ); // Gewichtung 1.0 destReal += srcAmp * Math.cos( srcPhase ); } // oberstes Band addieren srcAmp = runInFr.data[ ch ][ (srcCeilBand << 1) + SpectFrame.AMP ]; srcPhase = runInFr.data[ ch ][ (srcCeilBand << 1) + SpectFrame.PHASE ]; destImg += srcAmp * Math.sin( srcPhase ) * srcCeilWeight; destReal += srcAmp * Math.cos( srcPhase ) * srcCeilWeight; runOutFr.data[ ch ][ (band << 1) + SpectFrame.AMP ] = (float) Math.sqrt( destImg*destImg + destReal*destReal ); runOutFr.data[ ch ][ (band << 1) + SpectFrame.PHASE ] = phaseFactor * (float) Math.atan2( destImg, destReal ); } } // calculation done runInSlot.freeFrame( runInFr ); for( boolean writeDone = false; (!writeDone) && !threadDead; ) { try { // Unterbrechung runOutSlot.writeFrame( runOutFr ); // throws InterruptedException writeDone = true; runFrameDone( runOutSlot, runOutFr ); runOutStream.freeFrame( runOutFr ); } catch( InterruptedException ignored) {} // mainLoop wird eh gleich verlassen runCheckPause(); } } // end of main loop runInStream.closeReader(); runOutStream.closeWriter(); } // break topLevel catch( IOException e ) { runQuit( e ); return; } catch( SlotAlreadyConnectedException e ) { runQuit( e ); return; } // catch( OutOfMemoryException e ) { // abort( e ); // return; // } runQuit( null ); } // -------- GUI methods -------- public PropertyGUI createGUI( int type ) { PropertyGUI gui; if( type != GUI_PREFS ) return null; gui = new PropertyGUI( "gl"+GroupLabel.NAME_GENERAL+"\n" + "cbFreq bands upside-down,pr"+PRN_FLIPFREQ+"\n" + "lbMiddle frequency;pf"+Constants.absHzSpace+",id1,pr"+PRN_MIDFREQ+"\n" + "lbPost freq shift;pf"+Constants.offsetFreqSpace+"|"+Constants.offsetHzSpace+ "|"+Constants.offsetSemitonesSpace+",re1,id2,pr"+PRN_SHIFTFREQ+"\n" + "glCalculation\n" + "lbScaling;ch,pr"+PRN_CALCTYPE+"," + "itby factor," + "itby freq range\n" + "lbHigh frequency;pf"+Constants.absHzSpace+"|"+Constants.offsetFreqSpace+"|"+Constants.offsetHzSpace+ "|"+Constants.offsetSemitonesSpace+",re1,id3,pr"+PRN_HIFREQ+"\n" + "lbLow frequency;pf"+Constants.absHzSpace+"|"+Constants.offsetFreqSpace+"|"+Constants.offsetHzSpace+ "|"+Constants.offsetSemitonesSpace+",re1,id4,pr"+PRN_LOFREQ+"\n" + "gl"+GroupLabel.NAME_MODULATION+"\n" + "cbMiddle freq,actrue|5|en|6|en,acfalse|5|di|6|di,pr"+PRN_MIDMOD+";" + "pf"+Constants.offsetFreqSpace+"|"+Constants.offsetHzSpace+ "|"+Constants.offsetSemitonesSpace+",re1,id5,pr"+PRN_MIDMODDEPTH+";en,id6,pr"+PRN_MIDMODENV+"\n" + "cbShift freq,actrue|7|en|8|en,acfalse|7|di|8|di,pr"+PRN_SHIFTMOD+";" + "pf"+Constants.offsetFreqSpace+"|"+Constants.offsetHzSpace+ "|"+Constants.offsetSemitonesSpace+",re2,id7,pr"+PRN_SHIFTMODDEPTH+";en,id8,pr"+PRN_SHIFTMODENV+"\n" + "cbHigh freq,actrue|9|en|10|en,acfalse|9|di|10|di,pr"+PRN_HIMOD+";" + "pf"+Constants.offsetFreqSpace+"|"+Constants.offsetHzSpace+ "|"+Constants.offsetSemitonesSpace+",re3,id9,pr"+PRN_HIMODDEPTH+";en,id10,pr"+PRN_HIMODENV+"\n" + "cbLow freq,actrue|11|en|12|en,acfalse|11|di|12|di,pr"+PRN_LOMOD+";" + "pf"+Constants.offsetFreqSpace+"|"+Constants.offsetHzSpace+ "|"+Constants.offsetSemitonesSpace+",re4,id11,pr"+PRN_LOMODDEPTH+";en,id12,pr"+PRN_LOMODENV ); return gui; } }