/* * Mono2StereoOp.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.Param; import de.sciss.fscape.util.Slots; import java.io.EOFException; import java.io.IOException; public class Mono2StereoOp extends Operator { // -------- private variables -------- protected static final String defaultName = "Mono\u2192Stereo"; 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_PHASEMOD = 0; // pr.bool private static final int PR_HIDEPTH = 0; // pr.para private static final int PR_LODEPTH = 1; private static final int PR_BANDWIDTH = 2; private static final int PR_PHASEMODFREQ = 3; private static final int PR_GAIN = 4; private static final String PRN_PHASEMOD = "PhaseMod"; private static final String PRN_HIDEPTH = "HiDepth"; private static final String PRN_LODEPTH = "LoDepth"; private static final String PRN_BANDWIDTH = "Bandwidth"; private static final String PRN_PHASEMODFREQ = "PhaseModFreq"; private static final String PRN_GAIN = "Gain"; private static final boolean prBool[] = { true }; private static final String prBoolName[] = { PRN_PHASEMOD }; private static final Param prPara[] = { null, null, null, null, null }; private static final String prParaName[] = { PRN_HIDEPTH, PRN_LODEPTH, PRN_BANDWIDTH, PRN_PHASEMODFREQ, PRN_GAIN }; protected static final float hiFreq = 16000.0f; // [Hz] corresponds to PR_HIDEPTH // -------- public methods -------- // public Container createGUI( int type ); public Mono2StereoOp() { 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.para = prPara; static_pr.para[ PR_HIDEPTH ] = new Param( 7.5, Param.DECIBEL_AMP ); static_pr.para[ PR_LODEPTH ] = new Param( 3.0, Param.DECIBEL_AMP ); static_pr.para[ PR_BANDWIDTH ] = new Param( 92.0, Param.OFFSET_HZ ); static_pr.para[ PR_PHASEMODFREQ ] = new Param( 0.66, Param.ABS_HZ ); static_pr.para[ PR_GAIN ] = 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 = "Mono2StereoOp"; 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_MONO2STEREO, 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 Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz double gain; int ch2; // Source-Kanal fuer rechten Zielkanal (0 oder 1) // Modulations-Variablen boolean recalc = true; // false, wenn sich die Werte nicht geaendert haben float phaseOffset = 0.0f; float foo; // fuer Lookup-Berechnung int loBand; // unterstes benutztes Band; i.d.R. 1 (DC produziert Division by 0) Param freq; Param freqFloorOffs; Param freqCeilOffs; Param freqFloor; Param freqCeil; float depthFactor[]; float depth[]; float freqPhase[]; double val; 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; // band-abhaengige Stereo-Breite depthFactor = new float[ runInStream.bands ]; depth = new float[ runInStream.bands ]; // frequency angle depending on bandwidth; freqPhase = new float[ runInStream.bands ]; // Source-Kanal fuer rechten Zielkanal ch2 = 1 % runInStream.chanNum; // ------------------------------ Output-Slot ------------------------------ runOutSlot = slots.elementAt( SLOT_OUTPUT ); runOutStream = new SpectStream( runInStream ); runOutStream.setChannels( 2 ); runOutSlot.initWriter( runOutStream ); // ------------------------------ Vorberechnungen ------------------------------ gain = (float) Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, runInStream ).value; // Winkelfrequenzen und Depth-Koeffizienten Tabelle (dL + (dH - dL) * f / fH) freq = new Param( 0.0, Param.ABS_HZ ); freqCeilOffs = pr.para[ PR_BANDWIDTH ]; freqFloorOffs = new Param( -freqCeilOffs.value, freqCeilOffs.unit ); // XXX loBand = 1; for( int i = loBand; i < runInStream.bands; i++ ) { // XXX freq.value = runInStream.freq[ i ]; freq.value = i * runInStream.hiFreq / (runInStream.bands - 1); freqCeil = Param.transform( freqCeilOffs, Param.ABS_HZ, freq, runInStream ); freqFloor = Param.transform( freqFloorOffs, Param.ABS_HZ, freq, runInStream ); freqPhase[ i ] = (float) ((freq.value / (freqCeil.value - freqFloor.value)) % 1.0); depthFactor[ i ]= (float) (pr.para[ PR_LODEPTH ].value + ((pr.para[ PR_HIDEPTH ].value - pr.para[ PR_LODEPTH ].value) * freq.value / hiFreq )); // System.out.println( (int) freq.value + " Hz: ceil = "+(int) freqCeil.value +"/floor = "+(int) freqFloor.value+"; phase = "+(int) (freqPhase[ i ]*360) ); } // ------------------------------ Hauptschleife ------------------------------ runSlotsReady(); mainLoop: while( !threadDead ) { // ---------- Process: (modulierte) Variablen ---------- if( pr.bool[ PR_PHASEMOD ]) { foo = (float) (pr.para[ PR_PHASEMODFREQ ].value * // "sawtooth" ((runInStream.getTime() / 1000.0) % (1.0 / pr.para[ PR_PHASEMODFREQ ].value))); if( Math.abs( foo - phaseOffset ) >= 0.01 ) { // 1.8 degrees treshhold improves speed phaseOffset = 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... // Depth-Tabelle berechnen for( int i = loBand; i < runInStream.bands; i++ ) { val = Math.sin( Math.PI * (freqPhase[ i ] + phaseOffset) ); val = (val * val - 0.5) * depthFactor[ i ]; depth[ i ] = (float) Math.exp( val / 20 * Constants.ln10 ); // System.out.println( i + ": " + (int) (depth[ i ] * 100) + "%" ); } recalc = false; } // ---------- Process: Ziel-Frame berechnen ---------- System.arraycopy( runInFr.data[ 0 ], 0, runOutFr.data[ 0 ], 0, runInFr.data[ 0 ].length ); System.arraycopy( runInFr.data[ ch2 ], 0, runOutFr.data[ 1 ], 0, runInFr.data[ ch2 ].length ); for( int band = loBand; band < runOutStream.bands; band++ ) { runOutFr.data[ 0 ][ (band << 1) + SpectFrame.AMP ] *= gain * depth[ band ]; runOutFr.data[ 1 ][ (band << 1) + SpectFrame.AMP ] *= gain / depth[ band ]; } // 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( OutOfMemoryError 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" + "lbHigh freq stereo depth;pf" + Constants.decibelAmpSpace + ",pr" + PRN_HIDEPTH + "\n" + "lbLow freq stereo depth;pf" + Constants.decibelAmpSpace + ",pr" + PRN_LODEPTH + "\n" + "lbBandwidth;pf" + Constants.offsetFreqSpace + "|" + Constants.offsetHzSpace + "|" + Constants.offsetSemitonesSpace + ",pr" + PRN_BANDWIDTH + "\n" + "lbTotal gain;pf" + Constants.decibelAmpSpace + ",pr" + PRN_GAIN + "\n" + "gl" + GroupLabel.NAME_MODULATION + "\n" + "cbPhase motion,actrue|1|en,acfalse|1|di,pr" + PRN_PHASEMOD + ";" + "pf" + Constants.lfoHzSpace + ",id1,pr" + PRN_PHASEMODFREQ); return gui; } }