/*
* SmpSynDlg.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.gui;
import de.sciss.fscape.io.GenericFile;
import de.sciss.fscape.prop.Presets;
import de.sciss.fscape.prop.PropertyArray;
import de.sciss.fscape.session.ModulePanel;
import de.sciss.fscape.spect.SpectFrame;
import de.sciss.fscape.spect.SpectStream;
import de.sciss.fscape.spect.SpectralFile;
import de.sciss.fscape.util.Constants;
import de.sciss.fscape.util.Filter;
import de.sciss.fscape.util.Param;
import de.sciss.fscape.util.ParamSpace;
import de.sciss.fscape.util.SmpMap;
import de.sciss.fscape.util.SmpZone;
import de.sciss.gui.GUIUtil;
import de.sciss.gui.PathEvent;
import de.sciss.gui.PathListener;
import de.sciss.io.AudioFile;
import de.sciss.io.AudioFileDescr;
import de.sciss.io.Region;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Vector;
/**
* This processing module was inspired by a software
* called MetaSynth. It uses a pvoc file and instead
* of a sine oscillator bank uses a collection of sound
* files which get resampled and attenuated according
* to the pvoc data. Hell slow, rarely but sometimes
* interesting.
*/
public class SmpSynDlg
extends ModulePanel {
// -------- private variables --------
private static final int QUALITY_LOW = 0; // linear interp. + mono
private static final int QUALITY_MID = 1; // linear interp.
private static final int QUALITY_HIGH = 2; // bandlimited interp.
// Fehlermeldungen
private static final String ERR_NOZONES = "No sample zones defined";
private static final double MIDFREQ = 523.2511306; // C5 = Mittenfrequenz (Hz)
private static PropertyArray static_pr = null;
private static Presets static_presets = null;
private GUISupport sgui = null; // settings
// Properties (defaults)
private static final int PR_INPUTFILE = 0; // pr.text
private static final int PR_OUTPUTFILE = 1;
private static final int PR_SMPMAP = 2;
private static final int PR_OUTPUTTYPE = 0; // pr.intg
private static final int PR_OUTPUTRES = 1;
private static final int PR_OUTPUTRATE = 2;
private static final int PR_QUALITY = 3;
private static final int PR_NOISEFLOOR = 0; // pr.para
private static final int PR_TRIGTHRESH = 1;
private static final int PR_GAIN = 2;
private static final int PR_LENGTH = 3;
private static final String PRN_INPUTFILE = "InputFile";
private static final String PRN_OUTPUTFILE = "OutputFile";
private static final String PRN_SMPMAP = "SampleMap";
private static final String PRN_OUTPUTTYPE = "OutputType";
private static final String PRN_OUTPUTRES = "OutputReso";
private static final String PRN_OUTPUTRATE = "OutputRate";
private static final String PRN_QUALITY = "Quality";
private static final String PRN_NOISEFLOOR = "NoiseFloor";
private static final String PRN_TRIGTHRESH = "TrigThresh";
private static final String PRN_LENGTH = "Length";
private static final String prText[] = { "", "", "" };
private static final String prTextName[] = { PRN_INPUTFILE, PRN_OUTPUTFILE, PRN_SMPMAP };
private static final int prIntg[] = { 0, 0, 0, QUALITY_MID };
private static final String prIntgName[] = { PRN_OUTPUTTYPE, PRN_OUTPUTRES, PRN_OUTPUTRATE, PRN_QUALITY };
private static final Param prPara[] = { null, null, null, null };
private static final String prParaName[] = { PRN_NOISEFLOOR, PRN_TRIGTHRESH,
PRN_GAIN, PRN_LENGTH };
private static final int GG_INPUTFILE = GG_OFF_PATHFIELD + PR_INPUTFILE;
private static final int GG_OUTPUTFILE = GG_OFF_PATHFIELD + PR_OUTPUTFILE;
private static final int GG_OUTPUTTYPE = GG_OFF_CHOICE + PR_OUTPUTTYPE;
private static final int GG_OUTPUTRES = GG_OFF_CHOICE + PR_OUTPUTRES;
private static final int GG_OUTPUTRATE = GG_OFF_CHOICE + PR_OUTPUTRATE;
private static final int GGS_SMPMAP = GG_OFF_OTHER + 0;
private static final int GGS_NOISEFLOOR = GG_OFF_OTHER + 1;
private static final int GGS_TRIGTHRESH = GG_OFF_OTHER + 2;
private static final int GGS_SMPHIFREQ = GG_OFF_OTHER + 3;
private static final int GGS_SMPLOFREQ = GG_OFF_OTHER + 4;
private static final int GGS_SMPHIVEL = GG_OFF_OTHER + 5;
private static final int GGS_SMPLOVEL = GG_OFF_OTHER + 6;
private static final int GGS_SMPFILE = GG_OFF_OTHER + 7;
private static final int GGS_SMPPHASE = GG_OFF_OTHER + 8;
private static final int GGS_SMPGAIN = GG_OFF_OTHER + 9;
private static final int GGS_SMPBASE = GG_OFF_OTHER + 10;
private static final int GGS_SMPLOOP = GG_OFF_OTHER + 11;
private static final int GGS_SMPATK = GG_OFF_OTHER + 12;
private static final int GGS_SMPRLS = GG_OFF_OTHER + 13;
private static final int GGS_GAIN = GG_OFF_OTHER + 14;
private static final int GGS_LENGTH = GG_OFF_OTHER + 15;
private static final int GGS_QUALITY = GG_OFF_OTHER + 16;
private static final int GG_SETTINGS = GG_OFF_OTHER + 17;
// das "Instrument"
private Vector samples; // SmpZones
private SmpZone currentSmp = null; // angewaehltes Sample
// uebersetzung von SmpMapPanel's ACTION-Strings
private static final int CMD_UNKNOWN = -1;
private static final int CMD_SELECTED = 0;
private static final int CMD_DESELECTED = 1;
private static final int CMD_CREATED = 2;
private static final int CMD_DELETED = 3;
private static final int CMD_CHANGED = 4;
private static final int CMD_SPACE = 5;
// bandlimited interp
private float flt[];
private float fltD[];
private int fltSmpPerCrossing;
private SmpMapPanel ggSmpMap;
private ParamListener paramL;
private ItemListener il;
private PathListener pathL;
// -------- public methods --------
/**
* !! setVisible() bleibt dem Aufrufer ueberlassen
*/
public SmpSynDlg()
{
super( "Sample Synthesis" );
init2();
}
protected void buildGUI()
{
// einmalig PropertyArray initialisieren
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;
static_pr.para[ PR_NOISEFLOOR ] = new Param( -60.0, Param.DECIBEL_AMP );
static_pr.para[ PR_TRIGTHRESH ] = new Param( +12.0, Param.DECIBEL_AMP );
static_pr.para[ PR_GAIN ] = new Param( -6.0, Param.DECIBEL_AMP );
static_pr.para[ PR_LENGTH ] = new Param( 100.0, Param.FACTOR_TIME );
static_pr.paraName = prParaName;
// static_pr.superPr = DocumentFrame.static_pr;
fillDefaultAudioDescr( static_pr.intg, PR_OUTPUTTYPE, PR_OUTPUTRES, PR_OUTPUTRATE );
static_presets = new Presets( getClass(), static_pr.toProperties( true ));
}
presets = static_presets;
pr = (PropertyArray) static_pr.clone();
// -------- Var init --------
samples = new Vector();
// -------- build GUI --------
GridBagConstraints con;
PathField ggInputFile, ggOutputFile;
PathField[] ggInputs;
gui = new GUISupport();
con = gui.getGridBagConstraints();
con.insets = new Insets( 1, 2, 1, 2 );
paramL = new ParamListener() {
public void paramChanged( ParamEvent e )
{
int ID = gui.getItemID( e );
// String cmd = e.getActionCommand();
Component gg;
SmpZone smp;
ParamSpace hSpace;
switch( ID ) {
case -1: // -------------------- Settings GUI --------------------
if( sgui == null ) return;
ID = sgui.getItemID( e );
switch( ID ) {
case GGS_NOISEFLOOR:
gg = sgui.getItemObj( GGS_SMPMAP );
if( gg != null ) {
hSpace = new ParamSpace( ((SmpMapPanel) gg).getHSpace() );
// hSpace.min = ((ParamField) e.getSource()).getParam().value;
hSpace = new ParamSpace( ((ParamField) e.getSource()).getParam().value, hSpace.max, hSpace.inc, hSpace.unit );
((SmpMapPanel) gg).setSpaces( hSpace, null );
}
break;
case GGS_TRIGTHRESH:
// nothing
break;
case GGS_SMPHIFREQ:
case GGS_SMPLOFREQ:
case GGS_SMPHIVEL:
case GGS_SMPLOVEL:
case GGS_SMPBASE:
if( currentSmp != null ) {
smp = (SmpZone) currentSmp.clone();
switch( ID ) {
case GGS_SMPHIFREQ:
smp.freqHi = ((ParamField) e.getSource()).getParam();
break;
case GGS_SMPLOFREQ:
smp.freqLo = ((ParamField) e.getSource()).getParam();
break;
case GGS_SMPHIVEL:
smp.velHi = ((ParamField) e.getSource()).getParam();
break;
case GGS_SMPLOVEL:
smp.velLo = ((ParamField) e.getSource()).getParam();
break;
case GGS_SMPBASE:
smp.base = ((ParamField) e.getSource()).getParam();
currentSmp.base = smp.base;
break;
}
gg = sgui.getItemObj( GGS_SMPMAP );
if( gg != null ) {
if( !((SmpMapPanel) gg).setSample( smp )) { // invokes actionPerformed() if successfull
((SmpMapPanel) gg).setSample( currentSmp ); // restore old values if not successfull
}
}
}
break;
case GGS_SMPGAIN:
if( currentSmp != null ) {
currentSmp.gain = ((ParamField) e.getSource()).getParam();
}
break;
case GGS_SMPATK:
if( currentSmp != null ) {
currentSmp.atk = ((ParamField) e.getSource()).getParam();
}
break;
case GGS_SMPRLS:
if( currentSmp != null ) {
currentSmp.rls = ((ParamField) e.getSource()).getParam();
}
break;
}
break;
}
}
};
pathL = new PathListener() {
public void pathChanged( PathEvent e )
{
int ID = gui.getItemID( e );
Component gg;
switch( ID ) {
case -1: // -------------------- Settings GUI --------------------
if( sgui == null ) return;
ID = sgui.getItemID( e );
switch( ID ) {
case GGS_SMPFILE:
if( currentSmp != null ) {
currentSmp.fileName = ((PathField) e.getSource()).getPath().getPath();
setSample( currentSmp.fileName );
gg = sgui.getItemObj( GGS_SMPMAP );
if( gg != null ) {
((SmpMapPanel) gg).setSample( currentSmp );
}
}
break;
}
break;
}
}
};
ActionListener al = new ActionListener() {
public void actionPerformed( ActionEvent e )
{
int ID = gui.getItemID( e );
String cmd = e.getActionCommand();
int cmdID;
ParamField pf;
Component gg;
SmpZone smp;
int smpID = -1;
boolean fullRefresh;
ParamSpace hSpaces[], vSpaces[];
SmpMap smpMap;
Enumeration smpEnum;
SmpZone newSmp;
Vector newSamples;
switch( ID ) {
case -1: // -------------------- Settings GUI --------------------
if( sgui == null ) return;
ID = sgui.getItemID( e );
switch( ID ) {
case GGS_SMPMAP:
cmdID = CMD_UNKNOWN;
if( cmd.startsWith( SmpMapPanel.ACTION_BOXSELECTED )) {
cmdID = CMD_SELECTED;
} else if( cmd.startsWith( SmpMapPanel.ACTION_BOXCHANGED )) {
cmdID = CMD_CHANGED;
} else if( cmd.startsWith( SmpMapPanel.ACTION_BOXCREATED )) {
cmdID = CMD_CREATED;
} else if( cmd.startsWith( SmpMapPanel.ACTION_BOXDESELECTED )) {
cmdID = CMD_DESELECTED;
} else if( cmd.startsWith( SmpMapPanel.ACTION_BOXDELETED )) {
cmdID = CMD_DELETED;
} else if( cmd.startsWith( SmpMapPanel.ACTION_SPACECHANGED )) {
cmdID = CMD_SPACE;
}
try {
smpID = Integer.parseInt( cmd.substring( 3 ));
} catch( NumberFormatException e99 ) {
cmdID = CMD_UNKNOWN;
}
switch( cmdID ) {
case CMD_SELECTED:
case CMD_CHANGED:
case CMD_CREATED:
// ---- load current SmpZone ----
smp = ((SmpMapPanel) e.getSource()).getSample( smpID );
if( smp == null ) {
GUIUtil.displayError( getComponent(), new IllegalStateException( ERR_CORRUPTED ), String.valueOf( smpID ));
return;
}
fullRefresh = (cmdID != CMD_CHANGED);
if( cmdID == CMD_CREATED ) {
currentSmp = smp; // smp is already a clone, we can keep it
currentSmp.fileName = ""; // sonst NullPtrExc
samples.addElement( currentSmp );
}
// find the sample
if( (currentSmp == null) || (currentSmp.uniqueID != smp.uniqueID) ) {
for( int i = 0; i < samples.size(); i++ ) {
currentSmp = (SmpZone) samples.elementAt( i );
if( currentSmp.uniqueID == smpID ) break;
}
if( (currentSmp == null) || (currentSmp.uniqueID != smpID) ) {
GUIUtil.displayError( getComponent(), new NoSuchElementException( ERR_CORRUPTED ), String.valueOf( smpID ));
return;
}
fullRefresh = true;
}
// copy changed values
if( cmdID == CMD_CHANGED ) { // means Freq/Vel-Range
currentSmp.freqLo = smp.freqLo; // Params have been cloned already
currentSmp.freqHi = smp.freqHi;
currentSmp.velLo = smp.velLo;
currentSmp.velHi = smp.velHi;
}
// ---- display new values ----
pf = (ParamField) sgui.getItemObj( GGS_SMPHIFREQ );
if( pf != null ) pf.setParam( currentSmp.freqHi );
pf = (ParamField) sgui.getItemObj( GGS_SMPLOFREQ );
if( pf != null ) pf.setParam( currentSmp.freqLo );
pf = (ParamField) sgui.getItemObj( GGS_SMPHIVEL );
if( pf != null ) pf.setParam( currentSmp.velHi );
pf = (ParamField) sgui.getItemObj( GGS_SMPLOVEL );
if( pf != null ) pf.setParam( currentSmp.velLo );
// these only when switched to a different zone
if( fullRefresh ) {
gg = sgui.getItemObj( GGS_SMPFILE );
if( gg != null ) ((PathField) gg).setPath( new File( currentSmp.fileName ));
gg = sgui.getItemObj( GGS_SMPLOOP );
if( gg != null ) ((JCheckBox) gg).setSelected( (currentSmp.flags & SmpZone.LOOP) != 0 );
gg = sgui.getItemObj( GGS_SMPPHASE );
if( gg != null ) ((JComboBox) gg).setSelectedIndex( currentSmp.flags & SmpZone.PHASEMASK );
pf = (ParamField) sgui.getItemObj( GGS_SMPGAIN );
if( pf != null ) pf.setParam( currentSmp.gain );
pf = (ParamField) sgui.getItemObj( GGS_SMPBASE );
if( pf != null ) pf.setParam( currentSmp.base );
pf = (ParamField) sgui.getItemObj( GGS_SMPATK );
if( pf != null ) pf.setParam( currentSmp.atk );
pf = (ParamField) sgui.getItemObj( GGS_SMPRLS );
if( pf != null ) pf.setParam( currentSmp.rls );
}
break;
case CMD_DELETED:
for( int i = 0; i < samples.size(); i++ ) {
currentSmp = (SmpZone) samples.elementAt( i );
if( currentSmp.uniqueID == smpID ) break;
}
if( (currentSmp == null) || (currentSmp.uniqueID != smpID) ) {
GUIUtil.displayError( getComponent(), new NoSuchElementException( ERR_CORRUPTED ), String.valueOf( ID ));
return;
}
samples.removeElement( currentSmp );
// THRU
case CMD_DESELECTED:
if( currentSmp != null ) {
currentSmp = null;
gg = sgui.getItemObj( GGS_SMPFILE );
if( gg != null ) ((PathField) gg).setPath( new File( "(no sample zone selected)" ));
}
break;
case CMD_SPACE:
smpMap = ((SmpMapPanel) e.getSource()).getSmpMap();
smpEnum = smpMap.getSamples();
newSamples = new Vector();
currentSmp = null;
while( smpEnum.hasMoreElements() ) {
smp = (SmpZone) smpEnum.nextElement();
for( int i = 0; i < samples.size(); i++ ) {
newSmp = (SmpZone) samples.elementAt( i );
if( newSmp.uniqueID == smp.uniqueID ) {
newSmp.freqLo = (Param) smp.freqLo.clone();
newSmp.freqHi = (Param) smp.freqHi.clone();
newSmp.velLo = (Param) smp.velLo.clone();
newSmp.velHi = (Param) smp.velHi.clone();
newSamples.addElement( newSmp );
break;
}
}
}
samples = newSamples;
vSpaces = new ParamSpace[ 1 ];
vSpaces[ 0 ]= ((SmpMapPanel) e.getSource()).getVSpace();
hSpaces = new ParamSpace[ 1 ];
hSpaces[ 0 ]= ((SmpMapPanel) e.getSource()).getHSpace();
gg = sgui.getItemObj( GGS_SMPHIFREQ );
if( gg != null ) {
((ParamField) gg).setSpaces( vSpaces );
}
gg = sgui.getItemObj( GGS_SMPLOFREQ );
if( gg != null ) {
((ParamField) gg).setSpaces( vSpaces );
}
gg = sgui.getItemObj( GGS_SMPLOVEL );
if( gg != null ) {
((ParamField) gg).setSpaces( hSpaces );
}
gg = sgui.getItemObj( GGS_SMPHIVEL );
if( gg != null ) {
((ParamField) gg).setSpaces( hSpaces );
}
break;
case CMD_UNKNOWN:
GUIUtil.displayError( getComponent(), new NoSuchMethodException( ERR_CORRUPTED ), String.valueOf( ID ));
return;
}
break;
}
break;
}
}
};
il = new ItemListener() {
public void itemStateChanged( ItemEvent e )
{
// Component associate;
int ID = gui.getItemID( e );
// int ID2;
// String name;
// PropertyArray pa;
// Properties preset;
switch( ID ) {
case -1: // -------------------- Settings GUI --------------------
if( sgui == null ) return;
ID = sgui.getItemID( e );
switch( ID ) {
case GGS_SMPPHASE:
if( currentSmp != null ) {
currentSmp.flags &= ~SmpZone.PHASEMASK;
currentSmp.flags |= ((JComboBox) e.getSource()).getSelectedIndex() & SmpZone.PHASEMASK;
}
break;
case GGS_SMPLOOP:
if( currentSmp != null ) {
if( ((JCheckBox) e.getSource()).isSelected() ) {
currentSmp.flags |= SmpZone.LOOP;
} else {
currentSmp.flags &= ~SmpZone.LOOP;
}
}
break;
}
break;
}
}
};
// -------- I/O-Gadgets --------
con.fill = GridBagConstraints.BOTH;
con.gridwidth = GridBagConstraints.REMAINDER;
gui.addLabel( new GroupLabel( "Waveform I/O", GroupLabel.ORIENT_HORIZONTAL,
GroupLabel.BRACE_NONE ));
ggInputFile = new PathField( PathField.TYPE_INPUTFILE + PathField.TYPE_FORMATFIELD,
"Select spectral file" );
ggInputFile.handleTypes( GenericFile.TYPES_SPECT );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Input file", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggInputFile, GG_INPUTFILE, pathL );
ggOutputFile = new PathField( PathField.TYPE_OUTPUTFILE + PathField.TYPE_FORMATFIELD +
PathField.TYPE_RESFIELD + PathField.TYPE_RATEFIELD,
"Select output file" );
ggOutputFile.handleTypes( GenericFile.TYPES_SOUND );
ggInputs = new PathField[ 1 ];
ggInputs[ 0 ] = ggInputFile;
ggOutputFile.deriveFrom( ggInputs, "$D0$F0Syn$E" );
con.gridwidth = 1;
con.weightx = 0.1;
gui.addLabel( new JLabel( "Output file", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 0.9;
gui.addPathField( ggOutputFile, GG_OUTPUTFILE, pathL );
gui.registerGadget( ggOutputFile.getTypeGadget(), GG_OUTPUTTYPE );
gui.registerGadget( ggOutputFile.getResGadget(), GG_OUTPUTRES );
gui.registerGadget( ggOutputFile.getRateGadget(), GG_OUTPUTRATE );
// -------- Synthesis-Parameter --------
gui.addLabel( new JLabel( "Synthesis settings", SwingConstants.CENTER ));
con.fill = GridBagConstraints.BOTH;
// ggSettings = new JScrollPane(); // ( ScrollPane.SCROLLBARS_AS_NEEDED );
con.gridwidth = GridBagConstraints.REMAINDER;
con.weightx = 1.0;
con.weighty = 1.0;
sgui = createGUI();
// ggSettings.setViewportView( sgui );
// gui.addScrollPane( ggSettings, GG_SETTINGS, this );
gui.addGadget( sgui, GG_SETTINGS );
initGUI( this, FLAGS_PRESETS | FLAGS_PROGBAR, gui );
ggSmpMap.addActionListener( al );
}
/*
* Settings-GUI
*/
protected GUISupport createGUI()
{
GUISupport sg;
GridBagConstraints con;
ParamField ggNoiseFloor, ggTriggerThresh;
ParamField ggSmpLoFreq, ggSmpHiFreq, ggSmpLoVel, ggSmpHiVel; // Freq + Velocity bounds
PathField ggSmpFile;
JCheckBox ggSmpLoop;
JComboBox ggSmpPhase, ggQuality;
ParamField ggSmpGain, ggSmpBase, ggSmpAtk, ggSmpRls;
ParamField ggGain, ggLength;
ParamSpace ampSpace, noiseSpace, trigSpace;
ParamSpace freqSpace;
ParamSpace lengthSpaces[] = { Constants.spaces[ Constants.absMsSpace ],
Constants.spaces[ Constants.absBeatsSpace ],
Constants.spaces[ Constants.factorTimeSpace ]};
SmpMap smpMap;
sg = new GUISupport();
con = sg.getGridBagConstraints();
con.insets = new Insets( 1, 2, 1, 2 );
con.fill = GridBagConstraints.BOTH;
ggSmpMap = new SmpMapPanel();
con.gridwidth = 3;
con.gridheight = 8;
con.weightx = 1.0;
con.weighty = 1.0;
sg.addGadget( ggSmpMap, GGS_SMPMAP );
ampSpace = new ParamSpace( Constants.spaces[ Constants.decibelAmpSpace ]);
noiseSpace = new ParamSpace( ampSpace );
trigSpace = new ParamSpace( ampSpace );
// ampSpace.min = pr.para[ PR_NOISEFLOOR ].value;
// ampSpace.max = 0.0;
ampSpace = new ParamSpace( pr.para[ PR_NOISEFLOOR ].value, 0.0, ampSpace.inc, ampSpace.unit );
// noiseSpace.max = -12.0;
noiseSpace = new ParamSpace( noiseSpace.min, -12.0, noiseSpace.inc, noiseSpace.unit );
// trigSpace.min = 1.0;
// trigSpace.max = 96.0;
trigSpace = new ParamSpace( 1.0, 96.0, trigSpace.inc, trigSpace.unit );
freqSpace = new ParamSpace( Constants.spaces[ Constants.absHzSpace ]);
// freqSpace.max = freqSpace.fitValue( 22050.0 );
// freqSpace.min = freqSpace.fitValue( MIDFREQ*MIDFREQ / freqSpace.max );
final double maxNew = freqSpace.fitValue( 22050.0 );
freqSpace = new ParamSpace( freqSpace.fitValue( MIDFREQ*MIDFREQ / maxNew ), maxNew, freqSpace.inc, freqSpace.unit );
smpMap = new SmpMap( ampSpace, freqSpace );
ggSmpMap.setSmpMap( smpMap );
ggNoiseFloor = new ParamField( noiseSpace );
ggTriggerThresh = new ParamField( trigSpace );
ggSmpLoFreq = new ParamField( Constants.spaces[ Constants.absHzSpace ]);
ggSmpHiFreq = new ParamField( Constants.spaces[ Constants.absHzSpace ]);
ggSmpLoVel = new ParamField( ampSpace );
ggSmpHiVel = new ParamField( ampSpace );
con.fill = GridBagConstraints.HORIZONTAL;
con.gridwidth = GridBagConstraints.REMAINDER;
con.gridheight = 1;
con.weighty = 0.0;
con.weightx = 0.0;
sg.addLabel( new GroupLabel( "General Sensitivity", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_BOTTOM ));
con.gridwidth = GridBagConstraints.RELATIVE;
sg.addLabel( new JLabel( "Noisefloor", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggNoiseFloor, GGS_NOISEFLOOR, paramL );
con.gridwidth = GridBagConstraints.RELATIVE;
sg.addLabel( new JLabel( "Trigger", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggTriggerThresh, GGS_TRIGTHRESH, paramL );
con.fill = GridBagConstraints.BOTH;
sg.addLabel( new GroupLabel( "Sample Zone", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_BOTTOM ));
con.fill = GridBagConstraints.HORIZONTAL;
con.gridwidth = GridBagConstraints.RELATIVE;
sg.addLabel( new JLabel( "High freq", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggSmpHiFreq, GGS_SMPHIFREQ, paramL );
con.gridwidth = GridBagConstraints.RELATIVE;
sg.addLabel( new JLabel( "Low freq", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggSmpLoFreq, GGS_SMPLOFREQ, paramL );
con.gridwidth = GridBagConstraints.RELATIVE;
sg.addLabel( new JLabel( "High vel.", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggSmpHiVel, GGS_SMPHIVEL, paramL );
con.gridwidth = GridBagConstraints.RELATIVE;
sg.addLabel( new JLabel( "Low vel.", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggSmpLoVel, GGS_SMPLOVEL, paramL );
ggSmpFile = new PathField( PathField.TYPE_INPUTFILE, "Select sample" );
con.gridwidth = 1;
con.weightx = 0.0;
sg.addLabel( new JLabel( "Sample", SwingConstants.RIGHT ));
con.weightx = 0.9;
sg.addPathField( ggSmpFile, GGS_SMPFILE, pathL );
ggSmpLoop = new JCheckBox( "Loop" );
con.weightx = 0.1;
sg.addCheckbox( ggSmpLoop, GGS_SMPLOOP, il );
ggSmpPhase = new JComboBox();
ggSmpPhase.addItem( "Continuous" );
ggSmpPhase.addItem( "Zero @trig" );
ggSmpPhase.addItem( "Spect phase @trig" );
con.weightx = 0.0;
sg.addLabel( new JLabel( "Phase", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addChoice( ggSmpPhase, GGS_SMPPHASE, il );
ggSmpGain = new ParamField( Constants.spaces[ Constants.decibelAmpSpace ]);
con.gridwidth = 1;
con.weightx = 0.0;
sg.addLabel( new JLabel( "Gain", SwingConstants.RIGHT ));
con.gridwidth = 2;
con.weightx = 1.0;
sg.addParamField( ggSmpGain, GGS_SMPGAIN, paramL );
ggSmpAtk = new ParamField( Constants.spaces[ Constants.absMsSpace ]);
con.gridwidth = 1;
con.weightx = 0.0;
sg.addLabel( new JLabel( "Attack", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggSmpAtk, GGS_SMPATK, paramL );
ggSmpBase = new ParamField( Constants.spaces[ Constants.absHzSpace ]);
con.gridwidth = 1;
sg.addLabel( new JLabel( "Base freq", SwingConstants.RIGHT ));
con.gridwidth = 2;
con.weightx = 1.0;
sg.addParamField( ggSmpBase, GGS_SMPBASE, paramL );
ggSmpRls = new ParamField( Constants.spaces[ Constants.absMsSpace ]);
con.gridwidth = 1;
con.weightx = 0.0;
sg.addLabel( new JLabel( "Release", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggSmpRls, GGS_SMPRLS, paramL );
con.weightx = 1.0;
sg.addLabel( new GroupLabel( "Total", GroupLabel.ORIENT_HORIZONTAL, GroupLabel.BRACE_BOTTOM ));
ggGain = new ParamField( Constants.spaces[ Constants.decibelAmpSpace ]);
con.gridwidth = 1;
con.weightx = 0.0;
sg.addLabel( new JLabel( "Gain", SwingConstants.RIGHT ));
con.gridwidth = 2;
con.weightx = 1.0;
sg.addParamField( ggGain, GGS_GAIN, paramL );
ggLength = new ParamField( lengthSpaces );
con.gridwidth = 1;
con.weightx = 0.0;
sg.addLabel( new JLabel( "Length", SwingConstants.RIGHT ));
con.gridwidth = GridBagConstraints.REMAINDER;
sg.addParamField( ggLength, GGS_LENGTH, paramL );
ggQuality = new JComboBox();
ggQuality.addItem( "Low: linear+mono" );
ggQuality.addItem( "Medium: linear" );
ggQuality.addItem( "High: filtered" );
con.gridwidth = 1;
con.weightx = 0.0;
sg.addLabel( new JLabel( "Quality", SwingConstants.RIGHT ));
con.gridwidth = 2;
con.weightx = 1.0;
sg.addChoice( ggQuality, GGS_QUALITY, il );
return sg;
}
/**
* Transfer values from prop-array to GUI
*/
public void fillGUI()
{
super.fillGUI();
super.fillGUI( gui );
Component gg;
String val;
SmpMap smpMap;
Enumeration smpEnum;
// top level
gg = sgui.getItemObj( GGS_QUALITY );
if( gg != null ) {
((JComboBox) gg).setSelectedIndex( pr.intg[ PR_QUALITY ]);
}
gg = sgui.getItemObj( GGS_NOISEFLOOR );
if( gg != null ) {
((ParamField) gg).setParam( pr.para[ PR_NOISEFLOOR ]);
}
gg = sgui.getItemObj( GGS_TRIGTHRESH );
if( gg != null ) {
((ParamField) gg).setParam( pr.para[ PR_TRIGTHRESH ]);
}
gg = sgui.getItemObj( GGS_GAIN );
if( gg != null ) {
((ParamField) gg).setParam( pr.para[ PR_GAIN ]);
}
gg = sgui.getItemObj( GGS_LENGTH );
if( gg != null ) {
((ParamField) gg).setParam( pr.para[ PR_LENGTH ]);
}
gg = sgui.getItemObj( GGS_SMPMAP );
if( gg != null ) {
samples.setSize( 0 ); // "clear"
currentSmp = null;
val = pr.text[ PR_SMPMAP ];
if( val.length() != 0 ) {
smpMap = SmpMap.valueOf( val );
smpEnum = smpMap.getSamples();
while( smpEnum.hasMoreElements() ) {
samples.addElement( smpEnum.nextElement() );
}
((SmpMapPanel) gg).setSmpMap( smpMap );
} else {
((SmpMapPanel) gg).clear();
}
}
}
/**
* Transfer values from GUI to prop-array
*/
public void fillPropertyArray()
{
super.fillPropertyArray();
super.fillPropertyArray( gui );
Component gg;
SmpMap smpMap;
// top level
gg = sgui.getItemObj( GGS_QUALITY );
if( gg != null ) {
pr.intg[ PR_QUALITY ] = ((JComboBox) gg).getSelectedIndex();
}
gg = sgui.getItemObj( GGS_NOISEFLOOR );
if( gg != null ) {
pr.para[ PR_NOISEFLOOR ] = ((ParamField) gg).getParam();
}
gg = sgui.getItemObj( GGS_TRIGTHRESH );
if( gg != null ) {
pr.para[ PR_TRIGTHRESH ] = ((ParamField) gg).getParam();
}
gg = sgui.getItemObj( GGS_GAIN );
if( gg != null ) {
pr.para[ PR_GAIN ] = ((ParamField) gg).getParam();
}
gg = sgui.getItemObj( GGS_LENGTH );
if( gg != null ) {
pr.para[ PR_LENGTH ] = ((ParamField) gg).getParam();
}
gg = sgui.getItemObj( GGS_SMPMAP );
if( gg != null ) {
smpMap = ((SmpMapPanel) gg).getSmpMap();
while( smpMap.size() > 0 ) {
smpMap.removeSample( 0 ); // "its" SmpZones contain only correct margins
}
for( int i = 0; i < samples.size(); i++ ) {
smpMap.addSample( (SmpZone) samples.elementAt( i )); // "our" SmpZones contain the correct data
}
pr.text[ PR_SMPMAP ] = smpMap.toString();
}
}
/**
* Set new input file
*/
public void setInput( String fname )
{
SpectralFile f = null;
SpectStream stream = null;
Component gg;
ParamSpace vSpace;
// ---- Header lesen ----
try {
f = new SpectralFile( fname, GenericFile.MODE_INPUT );
stream = f.getDescr();
f.close();
} catch( IOException e1 ) {
GUIUtil.displayError( getComponent(), e1, getTitle() );
return;
}
// ---- Typ + Format ausgeben ----
// ---- Kommunikation mit Settings-GUI ----
gg = sgui.getItemObj( GGS_LENGTH );
if( gg != null ) {
((ParamField) gg).setReference( new Param( SpectStream.framesToMillis( stream, stream.frames ),
Param.ABS_MS ));
}
gg = sgui.getItemObj( GGS_SMPMAP );
if( gg != null ) {
// vSpace = (ParamSpace) ((SmpMapPanel) gg).getVSpace().clone();
vSpace = new ParamSpace( ((SmpMapPanel) gg).getVSpace() );
// vSpace.max = stream.hiFreq;
// vSpace.min = vSpace.fitValue( MIDFREQ*MIDFREQ / vSpace.max );
vSpace = new ParamSpace( vSpace.fitValue( MIDFREQ*MIDFREQ / stream.hiFreq ), vSpace.max, vSpace.inc, vSpace.unit );
((SmpMapPanel) gg).setSpaces( null, vSpace );
}
}
/**
* Sample-Filename geaendert => Datei wird ueberprueft
* und ggf. Gadgets angepasst
*/
public void setSample( String fname )
{
AudioFile f = null;
// ---- Header lesen ----
try {
f = AudioFile.openAsRead( new File( fname ));
// stream = f.getDescr();
f.close();
} catch( IOException e1 ) {
GUIUtil.displayError( getComponent(), e1, getTitle() );
return;
}
// ---- Kommunikation mit Settings-GUI ----
// XXX check Loop and Base Freq!
}
// -------- Processor Interface --------
protected void process()
{
int i, j, k;
int off, len;
float progOff, progWeight;
// io
SpectralFile inF = null;
AudioFile outF = null;
SpectStream inStream = null;
AudioFileDescr outStream = null;
SpectFrame pfr = null; // "previous" frame
SpectFrame cfr = null; // current frame
SpectFrame nfr = null; // "next" frame
PathField ggOutput;
int framesRead;
int smpPerFrame;
int frameSize; // = smpPerFrame * chanNum
int overhead;
float[] outData;
float[][] tempBuf;
int chanNum;
int numSmp = samples.size();
int[] smpFindex = null; // this will point to the indices in smpF, smpStream, smpData
AudioFile[] smpF = null; // might be null if smpFindex != index
AudioFileDescr[] smpStream = null; // containts correct value if smpFindex != index
float[][] smpData = null; // containts correct value if smpFindex != index
// Synthesize
SmpZoneGlobal[] szgs;
SmpZoneLocal[][] szls;
SmpZoneLocal szl;
float noise; // noise floor abs amp
float gain; // global gain abs amp
float trig; // trigger thresh abs amp
float length; // output in abs ms
Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz
float srcAmp;
float srcPhase;
double srcReal;
double srcImg;
float destAmp;
float floatPI2 = (float) Constants.PI2;
boolean triggered = false;
int band;
int ch;
// Smp Init
SmpZone smp, smp2;
int totalSamples = 0; // reichen 32 bit?
int samplesRead;
int maxRlsFrames;
float maxAmp = 0.0f;
topLevel: try {
// ---- open input ----
if( numSmp == 0 ) {
throw new IOException( ERR_NOZONES );
}
inF = new SpectralFile( pr.text[ PR_INPUTFILE ], GenericFile.MODE_INPUT );
inStream = inF.getDescr();
// this helps to prevent errors from empty files!
if( inStream.frames <= 1 ) throw new EOFException( ERR_EMPTY );
// .... check running ....
if( !threadRunning ) break topLevel;
chanNum = inStream.chanNum;
if( pr.intg[ PR_QUALITY ] == QUALITY_LOW ) chanNum = 1;
// ---- Filter berechnen ----
if( pr.intg[ PR_QUALITY ] == QUALITY_HIGH ) {
fltSmpPerCrossing = Filter.FLTSMPPERCROSSING;
len = fltSmpPerCrossing * 6;
flt = new float[ len ];
fltD = new float[ len ]; // 15% oversamp.; kaiser 6.0
gain = Filter.createAntiAliasFilter( flt, fltD, len, 0.85f, 6.0f );
} else {
flt = null;
fltD = null;
gain = 1.0f;
}
// ---- Open and prepare samples ----
smpF = new AudioFile[ numSmp ];
smpStream = new AudioFileDescr[ numSmp ];
smpData = new float[ numSmp ][];
smpFindex = new int[ numSmp ];
for( i = 0; i < numSmp; i++ ) {
smpF[ i ] = null; // allow save cleanup
smpStream[ i ] = null;
smpData[ i ] = null;
smpFindex[ i ] = -1;
}
smpInit: for( i = 0, k = 1; (i < numSmp) && threadRunning; i++ ) {
smp = (SmpZone) samples.elementAt( i );
for( j = 0; j < i; j++ ) { // Sample mehrmals benutzt?
smp2 = (SmpZone) samples.elementAt( j );
if( smp.fileName.equals( smp2.fileName )) {
smpFindex[ i ] = j;
smpStream[ i ] = smpStream[ j ];
smpData[ i ] = smpData[ j ];
continue smpInit;
}
}
smpFindex[ i ] = i;
smpF[ i ] = AudioFile.openAsRead( new File( smp.fileName ));
smpStream[ i ] = smpF[ i ].getDescr();
if( smpStream[ i ].length <= 0 ) throw new EOFException( ERR_EMPTY );
totalSamples += smpStream[ i ].length * smpStream[ i ].channels;
k = Math.max( k, smpStream[ i ].channels );
}
// .... check running ....
if( !threadRunning ) break topLevel;
// --- open + prepare output ----
ggOutput = (PathField) gui.getItemObj( GG_OUTPUTFILE );
if( ggOutput == null ) throw new IOException( ERR_MISSINGPROP );
outStream = new AudioFileDescr();
ggOutput.fillStream( outStream );
outStream.channels = chanNum;
outF = AudioFile.openAsWrite( outStream );
// .... check running ....
if( !threadRunning ) break topLevel;
gain *= (float) (Param.transform( pr.para[ PR_GAIN ], Param.ABS_AMP, ampRef, inStream )).value;
noise = (float) (Param.transform( pr.para[ PR_NOISEFLOOR ], Param.ABS_AMP, ampRef, inStream )).value;
trig = (float) (Param.transform( pr.para[ PR_TRIGTHRESH ], Param.ABS_AMP, ampRef, inStream )).value;
length = (float) (Param.transform( pr.para[ PR_LENGTH ], Param.ABS_MS,
new Param( SpectStream.framesToMillis( inStream, inStream.frames ), Param.ABS_MS ),
inStream )).value / 1000.f;
smpPerFrame = (int) (length / inStream.frames * outStream.rate + 0.5);
frameSize = smpPerFrame * chanNum;
szgs = new SmpZoneGlobal[ numSmp ]; // MAIN INITIALISATION !
szls = SmpZoneLocal.init( inStream, outStream, samples, smpStream, smpData, gain, szgs,
smpFindex, (flt != null) ? flt.length : 0 );
for( i = 0, maxRlsFrames = 0; i < numSmp; i++ ) {
maxRlsFrames = Math.max( maxRlsFrames, szgs[ i ].rls );
}
outData = new float[ (Math.max( smpPerFrame, maxRlsFrames ) + 2) * chanNum ];
overhead = outData.length - frameSize;
tempBuf = new float[ k ][ Math.max( 8192, smpPerFrame )];
// ---- preload all samples ----
for( i = 0; (i < numSmp) && (smpFindex[ i ] != i); i++ ) ; // next "physical" sample
if( i == numSmp ) throw new IOException( "Missing "+ (totalSamples) +" samples?!" );
samplesRead = 0;
off = 0;
progWeight = 0.01f;
while( (samplesRead < totalSamples) && threadRunning ) {
// XXX use prefs-buffersize
len = (int) Math.min( 8192 - 8192 % smpStream[ i ].channels,
(smpStream[ i ].length * smpStream[ i ].channels) - off );
if( len > 0 ) {
len += readInterleaved( smpF[ i ], smpData[ i ], off + szgs[ i ].dataOff, len, tempBuf,
(int) (smpStream[ i ].length * smpStream[ i ].channels) );
off += len;
samplesRead += len;
} else { // next one
do i++; while( (i < numSmp) && (smpFindex[ i ] != i) );
if( i == numSmp ) throw new IOException( "Missing "+ (totalSamples - samplesRead) +" samples?!" );
off = 0;
}
// .... progress ....
setProgression( ((float) samplesRead / (float) totalSamples) * progWeight );
}
// brauchen die Dateien nicht mehr
for( i = 0; i < numSmp; i++ ) {
if( smpF[ i ] != null ) {
smpF[ i ].close();
smpF[ i ] = null;
}
}
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- Conversion Loop ----
progOff = getProgression();
progWeight = 1.0f - progOff;
cfr = inF.allocFrame(); // => previous frame
nfr = inF.readFrame(); // => current frame
SpectFrame.clear( cfr );
for( framesRead = 1; (framesRead < inStream.frames) && threadRunning; ) {
pfr = cfr; // shift frames backwards
cfr = nfr;
nfr = inF.readFrame(); // read spect frame
for( band = 0; band < inStream.bands; band++ ) {
if( szls[ band ].length == 0 ) continue; // eh keine zonen hier
if( chanNum == 1 ) { // faster if mono
srcAmp = cfr.data[ 0 ][ (band<<1) + SpectFrame.AMP ];
srcPhase = cfr.data[ 0 ][ (band<<1) + SpectFrame.PHASE ];
while( srcPhase < 0 ) srcPhase += floatPI2;
triggered = (srcAmp / pfr.data[ 0 ][ (band<<1) + SpectFrame.AMP ]) >= trig;
} else { // else sum channels
srcImg = 0.0;
srcReal = 0.0;
triggered = false;
for( ch = 0; ch < chanNum; ch++ ) {
srcAmp = cfr.data[ ch ][ (band<<1) + SpectFrame.AMP ];
srcPhase = cfr.data[ ch ][ (band<<1) + SpectFrame.PHASE ];
srcImg += srcAmp * Math.sin( srcPhase );
srcReal += srcAmp * Math.cos( srcPhase );
if( (srcAmp / pfr.data[ ch ][ (band<<1) + SpectFrame.AMP ]) >= trig ) {
triggered = true;
}
}
srcAmp = (float) Math.sqrt( srcImg*srcImg + srcReal*srcReal ) / chanNum;
srcPhase = (float) Math.atan2( srcImg, srcReal ) + floatPI2;
}
// ---------------- Noiselevel ueberschritten ----------------
if( srcAmp > noise ) {
for( i = szls[ band ].length - 1; i >=0; i-- ) {
szl = szls[ band ][ i ]; // ------------ old triggaz ------------
if( szl.triggered ) { // maybe the velocity is invalid now?
if( (szl.basic.velLo >= srcAmp) || (szl.basic.velHi < srcAmp) ) { // ...release
szl.triggered = false;
szl.lvlTarget = 0.0f;
szl.lvlIncr = -szl.lvlCurrent / szl.basic.rls;
for( ch = 0; ch < chanNum; ch++ ) {
srcAmp = cfr.data[ ch ][ (band<<1) + SpectFrame.AMP ];
if( flt != null ) {
resampleFlt( szl, ch, chanNum, srcAmp, srcAmp, outData, 0, szl.basic.rls );
} else {
resampleLin( szl, ch, chanNum, srcAmp, srcAmp, outData, 0, szl.basic.rls );
}
}
} else { // ...continue oscillation
for( ch = 0; ch < chanNum; ch++ ) {
srcAmp = cfr.data[ ch ][ (band<<1) + SpectFrame.AMP ];
destAmp = nfr.data[ ch ][ (band<<1) + SpectFrame.AMP ];
if( flt != null ) {
resampleFlt( szl, ch, chanNum, srcAmp, destAmp, outData, 0, smpPerFrame );
} else {
resampleLin( szl, ch, chanNum, srcAmp, destAmp, outData, 0, smpPerFrame );
}
}
}
} else if( triggered ) { // ------------ nu triggaz ------------
if( (szl.basic.velLo < srcAmp) && (szl.basic.velHi >= srcAmp) ) { // ...start the RIOT
szl.triggered = true;
szl.lvlCurrent = 0.0f;
szl.lvlTarget = szl.basic.gain;
szl.lvlIncr = szl.lvlTarget / szl.basic.atk;
switch( szl.basic.phaseType ) {
case SmpZone.PHASE_LINEAR:
szl.phase = outStream.length * szl.smpIncr;
break;
case SmpZone.PHASE_TRIGZERO:
szl.phase = 0.0f;
break;
case SmpZone.PHASE_TRIGSPECT:
szl.phase = ((srcPhase / floatPI2) % 1.0f) * szl.basic.stream.length;
break;
}
for( ch = 0; ch < chanNum; ch++ ) {
srcAmp = cfr.data[ ch ][ (band<<1) + SpectFrame.AMP ];
destAmp = nfr.data[ ch ][ (band<<1) + SpectFrame.AMP ];
if( flt != null ) {
resampleFlt( szl, ch, chanNum, srcAmp, destAmp, outData, 0, smpPerFrame );
} else {
resampleLin( szl, ch, chanNum, srcAmp, destAmp, outData, 0, smpPerFrame );
}
}
}
}
}
// ---------------- Noiselevel unterschritten ----------------
} else {
for( i = szls[ band ].length - 1; i >=0; i-- ) {
szl = szls[ band ][ i ]; // ------------ old triggaz ------------
if( szl.triggered ) { // ...shut 'em down
szl.triggered = false;
szl.lvlTarget = 0.0f;
szl.lvlIncr = -szl.lvlCurrent / szl.basic.rls;
for( ch = 0; ch < chanNum; ch++ ) {
srcAmp = cfr.data[ ch ][ (band<<1) + SpectFrame.AMP ];
if( flt != null ) {
resampleFlt( szl, ch, chanNum, srcAmp, srcAmp, outData, 0, szl.basic.rls );
} else {
resampleLin( szl, ch, chanNum, srcAmp, srcAmp, outData, 0, szl.basic.rls );
}
}
}
}
}
} // for( bands )
// outF.writeSamples( outData, 0, frameSize );
writeInterleaved( outF, outData, 0, frameSize, tempBuf, chanNum );
inF.freeFrame( pfr );
for( i = 0; i < frameSize; i++ ) {
if( Math.abs( outData[ i ]) > maxAmp ) {
maxAmp = Math.abs( outData[ i ]);
}
}
// shift data and clear new frame space
System.arraycopy( outData, frameSize, outData, 0, overhead );
for( i = overhead; i < outData.length; i++ ) {
outData[ i ] = 0.0f;
}
framesRead++;
// .... progress ....
setProgression( ((float) framesRead / (float) inStream.frames) * progWeight + progOff );
} // while( not all read )
// .... check running ....
if( !threadRunning ) break topLevel;
// ---- Finish ----
inF.freeFrame( cfr );
inF.freeFrame( nfr );
outF.close();
outF = null;
inF.close();
inF = null;
handleClipping( maxAmp );
}
catch( IOException e1 ) {
setError( e1 );
}
catch( OutOfMemoryError e2 ) {
smpStream = null;
smpData = null;
szgs = null;
szls = null;
szl = null;
System.gc();
setError( new Exception( ERR_MEMORY ));;
}
// ---- cleanup (topLevel) ----
smpStream = null;
smpData = null;
szgs = null;
szls = null;
szl = null;
flt = null;
fltD = null;
if( inF != null ) {
inF.cleanUp();
inF = null;
}
if( outF != null ) {
outF.cleanUp();
outF = null;
}
if( smpF != null ) {
for( i = 0; i < smpF.length; i++ ) {
if( smpF[ i ] != null ) {
smpF[ i ].cleanUp();
smpF[ i ] = null;
}
}
}
} // process()
// -------- private methods --------
/**
* Daten resamplen; einfache Version (linear interpoliert, kein Filter)
* Phase und Gain werden in der szl geaendert, wenn der Kanal dem letzten output-Kanal entspricht!
*
* @param szl Struktur des aktuellen Bandes
* @param ch Kanal (in outData); 0...chanNum-1
* @param chanNum gesamt in outData
* @param startAmp Startwert gain-Faktor
* @param endAmp Endwert gain-Faktor
* @param outData wohin das Resamplete gespeichert werden soll; die Daten werden *addiert* zum bisherigen
* @param dataOff SampleWords in outData
* @param dataLen SampleWords in outData; mindestens 2!
*/
protected void resampleLin( SmpZoneLocal szl, int ch, int chanNum, float startAmp, float endAmp,
float outData[], int dataOffStart, int dataLenStart )
{
int srcChanNum = szl.basic.stream.channels;
int srcCh = ch % srcChanNum;
int srcOff;
float q;
float srcData[] = szl.basic.data;
float startPhase = szl.phase;
float phase;
float smpIncr = szl.smpIncr;
int lvlIncrNum;
float gain;
float startGain;
float gainIncr;
int pass;
Region loop;
startGain = szl.lvlCurrent * startAmp;
int dataOff = dataOffStart * chanNum + ch;
int dataLen = dataLenStart;
// ---- Gain-Zunahme pro Output-Sample (gainIncr) berechnen ----
if( szl.lvlIncr != 0.0f ) { // Atk/Rls Phase
lvlIncrNum = (int) ((szl.lvlTarget - szl.lvlCurrent) / szl.lvlIncr);
if( lvlIncrNum <= dataLen ) { // target level will be reached
if( lvlIncrNum < 1 ) {
lvlIncrNum = 1;
}
gain = szl.lvlTarget * (startAmp + (float) lvlIncrNum / (float) dataLen * (endAmp - startAmp));
gainIncr = (gain - startGain) / lvlIncrNum;
if( ch == chanNum - 1 ) { // write back
szl.lvlCurrent = szl.lvlTarget;
szl.lvlIncr = 0.0f;
}
} else { // target level will not be reached, interpolate
gain = endAmp * (szl.lvlCurrent + dataLen * szl.lvlIncr);
gainIncr = (gain - startGain) / dataLen;
if( ch == chanNum - 1 ) { // write back
szl.lvlCurrent += szl.lvlIncr * dataLen;
szl.lvlIncr = (szl.lvlTarget - szl.lvlCurrent) / (lvlIncrNum - dataLen);
}
}
} else { // sustain phase
gain = endAmp * szl.lvlCurrent;
gainIncr = (gain - startGain) / dataLen;
lvlIncrNum = 0;
}
// ---- Samples pro "Durchgang" berechnen (pass) ----
do {
loop = (Region) szl.basic.stream.getProperty( AudioFileDescr.KEY_LOOP );
if( loop != null ) {
while( startPhase >= loop.span.getStop() ) {
startPhase -= loop.span.getLength();
}
pass = Math.min( dataLen, (int) Math.ceil( (loop.span.getStop() - startPhase) / smpIncr ));
if( pass <= 0 ) {
startPhase = loop.span.getStart();
pass = Math.min( dataLen, (int) Math.ceil( (loop.span.getStop() - startPhase) / smpIncr ));
if( pass <= 0 ) return; // Loop zu klein
}
} else {
pass = Math.min( dataLen, (int) Math.ceil( (szl.basic.stream.length - startPhase) / smpIncr ));
dataLen = pass; // no loop => only one pass
}
// ---- eigentliches Resampling ----
phase = startPhase;
gain = startGain;
for( int i = 0; i < pass; i++,
phase = startPhase + i * smpIncr,
gain = startGain + i * gainIncr ) {
q = phase % 1.0f;
srcOff = ((int) phase + szl.basic.dataOff) * srcChanNum + srcCh;
outData[ dataOff ] += gain * (srcData[ srcOff ] * (1.0f-q) +
srcData[ srcOff + srcChanNum ] * q);
dataOff += chanNum;
// gain += gainIncr;
// phase += smpIncr; // WARNUNG: HIER TRETEN MASSIVE RUNDUNGSFEHLER AUF, DIE ZU ARRAY-FEHLERN FUEHREN
if( lvlIncrNum > 0 ) { // ...amp-env
if( --lvlIncrNum == 0 ) { // Ende der Envelope, gainIncr muss neu berechnet werden
gainIncr = (endAmp * szl.lvlTarget - gain) / (dataLen - i);
startGain = gain - i * gainIncr;
}
}
}
startPhase = phase;
startGain = phase;
dataLen -= pass;
} while( dataLen > 0 );
if( ch == chanNum - 1 ) { // write back
szl.phase = phase;
}
}
/**
* Daten resamplen; "bandlimited interpolation"
* Phase und Gain werden in der szl geaendert, wenn der Kanal dem letzten output-Kanal entspricht!
*
* @param szl Struktur des aktuellen Bandes
* @param ch Kanal (in outData); 0...chanNum-1
* @param chanNum gesamt in outData
* @param startAmp Startwert gain-Faktor
* @param endAmp Endwert gain-Faktor
* @param outData wohin das Resamplete gespeichert werden soll; die Daten werden *addiert* zum bisherigen
* @param dataOff SampleWords in outData
* @param dataLen SampleWords in outData; mindestens 2!
*/
protected void resampleFlt( SmpZoneLocal szl, int ch, int chanNum, float startAmp, float endAmp,
float outData[], int dataOffStart, int dataLenStart )
{
int srcChanNum = szl.basic.stream.channels;
int srcCh = ch % srcChanNum;
int srcOff;
float q;
float srcData[] = szl.basic.data;
float startPhase = szl.phase;
float phase;
float smpIncr = szl.smpIncr;
int lvlIncrNum;
float gain;
float startGain;
float gainIncr;
int pass;
// specific to the filtering
float fltIncr;
float fltOff;
int fltOffI;
int j, k;
float r;
float val;
Region loop;
if( smpIncr > 1.0f ) {
fltIncr = fltSmpPerCrossing / smpIncr;
startAmp /= smpIncr;
endAmp /= smpIncr;
} else {
fltIncr = fltSmpPerCrossing;
}
startGain = szl.lvlCurrent * startAmp;
int dataOff = dataOffStart * chanNum + ch;
int dataLen = dataLenStart;
// ---- Gain-Zunahme pro Output-Sample (gainIncr) berechnen ----
if( szl.lvlIncr != 0.0f ) { // Atk/Rls Phase
lvlIncrNum = (int) ((szl.lvlTarget - szl.lvlCurrent) / szl.lvlIncr);
if( lvlIncrNum <= dataLen ) { // target level will be reached
if( lvlIncrNum < 1 ) {
lvlIncrNum = 1;
}
gain = szl.lvlTarget * (startAmp + (float) lvlIncrNum / (float) dataLen * (endAmp - startAmp));
gainIncr = (gain - startGain) / lvlIncrNum;
if( ch == chanNum - 1 ) { // write back
szl.lvlCurrent = szl.lvlTarget;
szl.lvlIncr = 0.0f;
}
} else { // target level will not be reached, interpolate
gain = endAmp * (szl.lvlCurrent + dataLen * szl.lvlIncr);
gainIncr = (gain - startGain) / dataLen;
if( ch == chanNum - 1 ) { // write back
szl.lvlCurrent += szl.lvlIncr * dataLen;
szl.lvlIncr = (szl.lvlTarget - szl.lvlCurrent) / (lvlIncrNum - dataLen);
}
}
} else { // sustain phase
gain = endAmp * szl.lvlCurrent;
gainIncr = (gain - startGain) / dataLen;
lvlIncrNum = 0;
}
// ---- Samples pro "Durchgang" berechnen (pass) ----
do {
loop = (Region) szl.basic.stream.getProperty( AudioFileDescr.KEY_LOOP );
if( loop != null ) {
while( startPhase >= loop.span.getStop() ) {
startPhase -= loop.span.getLength();
}
pass = Math.min( dataLen, (int) Math.ceil( (loop.span.getStop() - startPhase) / smpIncr ));
if( pass <= 0 ) {
startPhase = loop.span.getStart();
pass = Math.min( dataLen, (int) Math.ceil( (loop.span.getStop() - startPhase) / smpIncr ));
if( pass <= 0 ) return; // Loop zu klein
}
} else {
pass = Math.min( dataLen, (int) Math.ceil( (szl.basic.stream.length - startPhase) / smpIncr ));
dataLen = pass; // no loop => only one pass
}
// ---- eigentliches Resampling ----
phase = startPhase;
gain = startGain;
for( int i = 0; i < pass; i++,
phase = startPhase + i * smpIncr,
gain = startGain + i * gainIncr ) {
q = phase % 1.0f;
val = 0.0f;
k = -srcChanNum; // src-smpIncr
for( j = 0; j < 2; j++ ) { // 0 = leftwing, 1 = rightwing
srcOff = ((int) phase + szl.basic.dataOff + j) * srcChanNum + srcCh;
fltOff = q * fltIncr;
fltOffI = (int) fltOff;
while( fltOffI < flt.length ) {
r = fltOff % 1.0f; // 0...1 for interpol.
val += srcData[ srcOff ] * (flt[ fltOffI ] + fltD[ fltOffI ] * r);
srcOff += k;
fltOff += fltIncr;
fltOffI = (int) fltOff;
}
q = 1.0f - q;
k = -k;
}
outData[ dataOff ] += val * gain;
dataOff += chanNum;
if( lvlIncrNum > 0 ) { // ...amp-env
if( --lvlIncrNum == 0 ) { // Ende der Envelope, gainIncr muss neu berechnet werden
gainIncr = (endAmp * szl.lvlTarget - gain) / (dataLen - i);
startGain = gain - i * gainIncr;
}
}
}
startPhase = phase;
startGain = phase;
dataLen -= pass;
} while( dataLen > 0 );
if( ch == chanNum - 1 ) { // write back
szl.phase = phase;
}
}
private static int readInterleaved( AudioFile f, float[] iBuf, int iOff, int iLen, float[][] tempBuf, int numCh )
throws IOException
{
assert (iLen % numCh) == 0 : "invalid interleaved length";
int ch, i, j, len = iLen / numCh;
f.readFrames( tempBuf, 0, len );
for( ch = 0; ch < numCh; ch++ ) {
for( i = iOff + ch, j = 0; j < iLen; i++, j += numCh ) {
iBuf[ i ] = tempBuf[ ch ][ j ];
}
}
return iLen;
}
private static void writeInterleaved( AudioFile f, float[] iBuf, int iOff, int iLen, float[][] tempBuf, int numCh )
throws IOException
{
assert (iLen % numCh) == 0 : "invalid interleaved length";
int ch, i, j, len = iLen / numCh;
for( ch = 0; ch < numCh; ch++ ) {
for( i = iOff + ch, j = 0; j < iLen; i++, j += numCh ) {
tempBuf[ ch ][ j ] = iBuf[ i ];
}
}
f.writeFrames( tempBuf, 0, len );
}
}
// class SmpSynDlg
/**
* internal class fuer die Synthese
* nur einmal pro SmpZone vorhanden
*/
class SmpZoneGlobal
{
public float velHi, velLo; // abs amp
public float gain; // abs amp multiplied by global gain
public float base; // abs Hz
public int atk, rls; // output-samplewords
public int phaseType; // SmpZone.flags & PHASEMASK
public AudioFileDescr stream;
public float data[] = null; // sampledata
public int dataOff = 0; // noetiger Offset wegen Filter-Convolution;
// von SmpLocal.init() initialisiert; noch nicht mit chanNum multipliziert!
/**
* @param basic zugrundeliegende SmpZone
* @param smpStream initialisierter AudioFileDescr
* @param outStream Synthese-Ausgabe; outStream.samples muss tatsaechlicher Zielgroesse entsprechen!
* @param data Sample-Daten
* @param gain globales Gain als abs amp
*/
public SmpZoneGlobal( SmpZone basic, AudioFileDescr smpStream, AudioFileDescr outStream, float gain )
{
Param ampRef = new Param( 1.0, Param.ABS_AMP ); // transform-Referenz
Param timeRef = new Param( (float) outStream.length / outStream.rate * 1000.0f, Param.ABS_MS );
this.stream = smpStream;
velHi = (float) (Param.transform( basic.velHi, Param.ABS_AMP, ampRef, null )).value;
velLo = (float) (Param.transform( basic.velLo, Param.ABS_AMP, ampRef, null )).value;
base = (float) basic.base.value;
this.gain = (float) (Param.transform( basic.gain, Param.ABS_AMP, ampRef, null )).value * gain;
phaseType = basic.flags & SmpZone.PHASEMASK;
atk = (int) ((Param.transform( basic.atk, Param.ABS_MS, timeRef, null )).value / 1000.0 *
outStream.rate + 0.5);
rls = (int) ((Param.transform( basic.rls, Param.ABS_MS, timeRef, null )).value / 1000.0 *
outStream.rate + 0.5);
}
} // class SmpZoneGlobal
/**
* internal class fuer die Synthese
* pro Verwendung der SmpZone (Frequenz-Band) vorhanden
*/
class SmpZoneLocal
{
public SmpZoneGlobal basic;
public float phase = 0.0f;
public boolean triggered = false;
public float lvlCurrent = 0.0f; // current Level
public float lvlTarget = 0.0f; // target Level
public float lvlIncr = 0.0f; // lvlCurrent-increment per output-Sample
public float smpIncr; // sample-increment = 1 / resmp-factor
/**
* @param freq Frequenz des Bandes, das diese szl enthaelt
*/
public SmpZoneLocal( SmpZoneGlobal basic, AudioFileDescr outStream, float freq )
{
this.basic = basic;
smpIncr = (float) ((basic.stream.rate * freq) / (outStream.rate * basic.base));
}
/**
* Init Array of this Struct
*
* @param smpStreams Indices muessen mit smpZones uebereinstimmen
* @param smpData dito; diese Methode fuellt die Felder aus, d.h. legt selbstaendig die Audiobuffer an!!!
* @param szgs Groesse muss mit smpZones uebereinstimmen; wird gefuellt und
* kann z.B. zur Ermittelung der Release-Zeiten benutzt werden!
* @return Array-Index entspricht Frequenz-Band im inStream
*/
public static SmpZoneLocal[][] init( SpectStream inStream, AudioFileDescr outStream, Vector smpZones,
AudioFileDescr[] smpStreams, float[][] smpData, float gain,
SmpZoneGlobal[] szgs, int smpFindex[], int Nwing )
{
int[] ID = new int[ smpZones.size() ];
int num;
float fNum;
SmpZoneLocal[][] szls = new SmpZoneLocal[ inStream.bands ][];
float freq;
float maxIncr[] = new float[ smpZones.size() ];
SmpZone smp;
// transform SmpZones to SmpZoneGlobals
for( int i = 0; i < szgs.length; i++ ) {
szgs[ i ] = new SmpZoneGlobal( (SmpZone) smpZones.elementAt( i ), smpStreams[ i ], outStream, gain );
maxIncr[ i ] = 0.0f;
}
for( int band = 0; band < inStream.bands; band++ ) {
// replace by SpectStream.getFrequencies()! XXX
freq = ((inStream.hiFreq - inStream.loFreq) * band / (inStream.bands - 1)) + inStream.loFreq;
num = 0;
if( freq > 0 ) {
for( int i = 0; i < smpZones.size(); i++ ) {
smp = (SmpZone) smpZones.elementAt( i );
if( (smp.freqLo.value <= freq) && (smp.freqHi.value > freq) ) {
ID[ num++ ] = i;
}
}
}
szls[ band ] = new SmpZoneLocal[ num ];
for( int i = 0; i < num; i++ ) {
szls[ band ][ i ] = new SmpZoneLocal( szgs[ ID[ i ]], outStream, freq );
maxIncr[ ID[ i ]] = Math.max( maxIncr[ ID[ i ]], szls[ band ][ i ].smpIncr );
}
}
// transform maxIncr to SmpGlobal.dataOff, alloc SmpGlobal.data
for( int i = 0; i < szgs.length; i++ ) {
if( smpFindex[ i ] != i ) continue;
fNum = 1.0f;
for( int j = 0; j < szgs.length; j++ ) {
if( smpFindex[ j ] == i ) {
fNum = Math.max( fNum, maxIncr[ j ]);
}
}
num = (int) (Nwing * fNum) + 10;
szgs[ i ].dataOff = num;
smpData[ i ] = new float[ (int) (((num << 1) + smpStreams[ i ].length) *
smpStreams[ i ].channels) ];
szgs[ i ].data = smpData[ i ];
// clear overheads
num *= smpStreams[ i ].channels;
for( int j = 0; j < num; j++ ) {
smpData[ i ][ j ] = 0.0f;
smpData[ i ][ smpData[ i ].length - j - 1 ] = 0.0f;
}
// System.out.println( i+": maxIncr="+fNum+" => dataOff="+szgs[ i ].dataOff );
for( int j = 0; j < szgs.length; j++ ) { // copy to all zones that refer to the same file
if( (i == j) || (smpFindex[ j ] != i) ) continue;
szgs[ j ].dataOff = szgs[ i ].dataOff;
smpData[ j ] = smpData[ i ];
szgs[ j ].data = smpData[ i ];
}
}
return szls;
}
}