/*
* Copyright 2006-2007 Columbia University.
*
* This file is part of MEAPsoft.
*
* MEAPsoft is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* MEAPsoft is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MEAPsoft; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* See the file "COPYING" for the text of the license.
*/
package com.meapsoft;
import gnu.getopt.Getopt;
import java.io.IOException;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Vector;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import com.meapsoft.featextractors.AvgMelSpec;
import com.meapsoft.featextractors.FeatureExtractor;
import com.meapsoft.featextractors.MetaFeatureExtractor;
/**
* Program that extracts features from the chunks listed in the input
* files.
*
* @author Ron Weiss (ronw@ee.columbia.edu)
*/
public class FeatExtractor extends MEAPUtil
{
// Files to use
private FeatFile[] featFiles;
private FeatFile outFile = null;
// all of our feature extractors...
private Vector featExts;
// and their associated weights
private Vector featExtWeights;
// names of our feature extractors
private String feat_names = "";
// should this FeatExtractor clear any non meta features?
private boolean clearNonMetaFeatures = true;
public static final int feSamplingRate = 22050;
// big enough to get good frequency resolution for AvgChroma
public static int nfft = 1024;
public static int nhop = 256;
// if this buffer is smaller than a chunk's length, that chunk's
// features not be calculated correctly
private int stftBufferSize = 2000;
/**
* FeatExtractor constructor. If no extractors is empty, defaults
* to AvgMelSpec.
*/
public FeatExtractor(String infile, String outfile, Vector extractors)
{
this(new FeatFile(infile), new FeatFile(outfile), extractors);
}
/**
* FeatExtractor constructor. If no extractors is empty, defaults
* to AvgMelSpec.
*/
public FeatExtractor(FeatFile infile, FeatFile outfile, Vector extractors)
{
this((FeatFile[])null, outfile, extractors);
featFiles = new FeatFile[1];
featFiles[0] = infile;
}
/**
* FeatExtractor constructor. If no extractors is empty, defaults
* to AvgMelSpec.
*/
public FeatExtractor(FeatFile[] infiles, FeatFile outfile, Vector extractors)
{
featFiles = infiles;
outFile = outfile;
featExts = extractors;
if(extractors.size() == 0)
extractors.add(new AvgMelSpec());
}
public void printUsageAndExit()
{
System.out.println("Usage: FeatExtractor [-options] file1.feat file2.feat ... \n\n" +
" where options include:\n" +
" -o output_file append features into output file (defaults to input file)\n" +
" -w winSize set STFT window size in seconds (defaults to "+getWindowSize()+")\n" +
" -o hopSize set STFT hop size in seconds (defaults to "+getHopSize()+")" +
"");
printCommandLineOptions('f');
System.out.println();
System.exit(0);
}
/**
* FeatExtractor constructor. Parses command line arguments
*/
public FeatExtractor(String[] args)
{
if(args.length == 0)
printUsageAndExit();
// Parse arguments
String argString = "f:o:w:h:";
featExts = parseFeatureExtractor(args, argString);
Getopt opt = new Getopt("FeatExtracter", args, argString);
opt.setOpterr(false);
int c = -1;
while ((c = opt.getopt()) != -1)
{
switch(c)
{
case 'o':
outFile = new FeatFile(opt.getOptarg());
break;
case 'h':
setHopSize(Double.parseDouble(opt.getOptarg()));
break;
case 'w':
setWindowSize(Double.parseDouble(opt.getOptarg()));
break;
case 'f': // already handled above
break;
case '?':
printUsageAndExit();
break;
default:
System.out.print("getopt() returned " + c + "\n");
}
}
// parse arguments
int ind = opt.getOptind();
if(ind > args.length)
printUsageAndExit();
featFiles = new FeatFile[args.length - ind];
for(int i=ind; i<args.length; i++)
featFiles[i-ind] = new FeatFile(args[i]);
// What are the feature names?
for(int i = 0; i < featExts.size(); i++)
feat_names += featExts.get(i).getClass().getName() + " ";
//chop off last space
feat_names = feat_names.substring(0, feat_names.length()-1);
}
public void setup() throws IOException, ParserException
{
for(int i = 0; i < featFiles.length; i++)
if(!featFiles[i].haveReadFile)
featFiles[i].readFile();
}
/**
* Where the magic happens. Extract features from featFiles.
*/
public FeatFile[] processFeatFiles() throws IOException, UnsupportedAudioFileException
{
for(int i = 0; i < featFiles.length; i++)
processFeatFile(featFiles[i]);
return featFiles;
}
/**
* Where the magic happens. Extract features from file.
*/
public FeatFile processFeatFile(FeatFile f) throws IOException, UnsupportedAudioFileException
{
FeatFile file = (FeatFile)f.clone();
// keep track of our progress in extracting features from this FeatFile:
progress.setMinimum(0);
progress.setMaximum(file.chunks.size()*featExts.size());
progress.setValue(0);
STFT stft = null;
boolean wroteFeatDesc = false;
String lastAudioFile = "";
file.chunks = new MinHeap(file.chunks);
((MinHeap)file.chunks).sort();
Iterator c = file.chunks.iterator();
//System.out.println("doing regular feature extractors...");
while(c.hasNext())
{
FeatChunk ch = (FeatChunk)c.next();
// let's get some new features
if(!ch.srcFile.equals(lastAudioFile))
{
AudioInputStream ais = openInputStream(ch.srcFile);
// resample to feSamplingRate
AudioFormat fmt = new AudioFormat(feSamplingRate,
format.getSampleSizeInBits(),
format.getChannels(),
MEAPUtil.signed,
format.isBigEndian());
ais = AudioSystem.getAudioInputStream(format, ais);
stft = new STFT(ais, nfft, nhop, stftBufferSize);
}
lastAudioFile = ch.srcFile;
// compute features from the STFT
ListIterator i = featExts.listIterator();
while(i.hasNext())
{
FeatureExtractor fe = (FeatureExtractor)i.next();
//we don't want to run meta feature extractors yet!
if (!(fe instanceof MetaFeatureExtractor))
{
long chunkStartFrame = stft.seconds2fr(ch.startTime);
int nframes = (int)stft.seconds2fr(ch.length);
long chunkEndFrame = chunkStartFrame + nframes;
// make sure stft contains valid data for us.
long lastFrame = stft.getLastFrameAddress();
if(chunkStartFrame > lastFrame)
stft.readFrames(chunkStartFrame - lastFrame + nframes + 1);
else if(chunkEndFrame > lastFrame)
stft.readFrames(chunkEndFrame - lastFrame + 1);
double[] feats = fe.features(stft, (int)chunkStartFrame, nframes);
ch.addFeature(feats);
// what features are we adding?
if(!wroteFeatDesc)
{
String featString = fe.getClass().getName() + "(" + feats.length + ") ";
if(featExtWeights != null)
{
int idx = i.nextIndex()-1;
if(idx < featExtWeights.size())
featString = featExtWeights.get(idx)+"*"+featString;
}
file.featureDescriptions.add(featString);
}
}
progress.setValue(progress.getValue()+1);
}
wroteFeatDesc = true;
}
//now do meta feature extractors
boolean descriptionsCleared = false;
ListIterator i = featExts.listIterator();
while(i.hasNext())
{
FeatureExtractor fe = (FeatureExtractor)i.next();
if (fe instanceof MetaFeatureExtractor)
{
if (!descriptionsCleared)
{
if(clearNonMetaFeatures)
file.featureDescriptions.clear();
// sk: remove normalization and feature weighting
//file.normalizeFeatures();
//file.applyFeatureWeights();
descriptionsCleared = true;
}
// this obliterates any other features
((MetaFeatureExtractor)fe).features(file, clearNonMetaFeatures);
// what features are we adding?
String featString = fe.getClass().getName() + "(" + 1 + ") ";
if(featExtWeights != null)
{
int idx = i.nextIndex()-1;
if(idx < featExtWeights.size())
featString = featExtWeights.get(idx)+"*"+featString;
}
file.featureDescriptions.add(featString);
progress.setValue(progress.getValue()+1);
}
}
stft.stop();
if(outFile != null)
{
outFile.chunks.addAll(file.chunks);
outFile.featureDescriptions = new Vector(file.featureDescriptions);
// outFile now contains some chunks.
outFile.haveReadFile = true;
}
return file;
}
/**
* Set everything up, process input, and write output.
*/
public void run()
{
try
{
setup();
}
catch(Exception e)
{
exceptionHandler.handleException(e);
}
FeatFile fn = outFile;
// process supplied files
for(int i = 0; i < featFiles.length; i++)
{
if(outFile == null)
fn = featFiles[i];
if(verbose)
System.out.println("Extracting features (" + feat_names +
") from " + featFiles[i].filename + " to "
+ fn.filename + ".");
long startTime = System.currentTimeMillis();
try
{
FeatFile f = processFeatFile(featFiles[i]);
if(writeMEAPFile)
f.writeFile(fn.filename);
}
catch(Exception e)
{
exceptionHandler.handleException(e);
}
if(verbose)
System.out.println("Done. Took " +
((System.currentTimeMillis() - startTime)/1000.0)
+ "s");
}
}
/**
* Set weights associated with the different FeatureExtractors
* used by this object.
*/
public void setFeatureExtractorWeights(Vector v)
{
featExtWeights = v;
}
/**
* Should this FeatExtractor clear any non meta features?
*/
public void setClearNonMetaFeatures(boolean clearNonMF)
{
clearNonMetaFeatures = clearNonMF;
}
/**
* Set the STFT window size for the feature extractors to use.
*
* @param winSize - window size in seconds
*/
public void setWindowSize(double winSize)
{
nfft = (int)(winSize*feSamplingRate);
nfft = (int)(winSize);
System.out.println("window size = " + winSize + ", nfft = " + nfft);
}
/**
* Set the STFT hop size for the feature extractors to use.
*
* @param hopSize - hop size in seconds
*/
public void setHopSize(double hopSize)
{
nhop = (int)(hopSize*feSamplingRate);
nhop = (int)(hopSize);
System.out.println("hop size = " + hopSize + ", nhop = " + nhop);
}
/**
* Get the STFT window size used by the feature extractors.
*/
public double getWindowSize()
{
return (double)nfft/feSamplingRate;
}
/**
* Get the STFT hop size used by the feature extractors.
*/
public double getHopSize()
{
return (double) nhop/feSamplingRate;
}
public static void main(String[] args)
{
FeatExtractor o2or = new FeatExtractor(args);
o2or.verbose = true;
o2or.run();
System.exit(0);
}
}