Main Page   Packages   Class Hierarchy   Compound List   File List   Compound Members  

Synthesizer.java

00001 /*
00002  *  Copyright 2006-2007 Columbia University.
00003  *
00004  *  This file is part of MEAPsoft.
00005  *
00006  *  MEAPsoft is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License version 2 as
00008  *  published by the Free Software Foundation.
00009  *
00010  *  MEAPsoft is distributed in the hope that it will be useful, but
00011  *  WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  *  General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU General Public License
00016  *  along with MEAPsoft; if not, write to the Free Software
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
00018  *  02110-1301 USA
00019  *
00020  *  See the file "COPYING" for the text of the license.
00021  */
00022 
00023 package com.meapsoft;
00024 
00025 import gnu.getopt.Getopt;
00026 
00027 import java.io.IOException;
00028 import java.util.Arrays;
00029 import java.util.Iterator;
00030 import javax.sound.sampled.AudioFormat;
00031 import javax.sound.sampled.LineUnavailableException;
00032 import javax.sound.sampled.UnsupportedAudioFileException;
00033 
00041 public class Synthesizer extends MEAPUtil
00042 {
00043     EDLFile edlFile;
00044     // if outFile is null, sound will be output to the first available
00045     // audio mixer
00046     String outFile = null;
00047     AudioWriter audioWriter;
00048 
00049     // output buffer
00050     double[] outSamples;
00051     int outSamplesLength;
00052     
00053     // we want to synthesize cd quality audio
00054     AudioFormat format = new AudioFormat(44100, bitsPerSamp, 
00055                                          numChannels, signed, bigEndian);
00056 
00057     public Synthesizer(String infile, String outfile)
00058     {
00059         this(new EDLFile(infile), outfile); 
00060     }
00061 
00062     public Synthesizer(EDLFile edl, String outfile)
00063     {
00064         edlFile = edl;
00065         outFile = outfile; 
00066     }
00067     
00068     // \todo{test output to line out}
00069     public void printUsageAndExit() 
00070     {
00071         System.out.println("Usage: Synthesizer [-options] file.edl \n\n" + 
00072                            "  where options include:\n" + 
00073                            "    -o output_file  output sound file (defaults to line out)\n" +
00074                            "");
00075         System.exit(0);
00076     }
00077 
00081     public Synthesizer(String[] args) 
00082     {
00083         if(args.length == 0)
00084             printUsageAndExit();
00085 
00086         // Parse arguments
00087         Getopt opt = new Getopt("Synthesizer", args, "o:");
00088         opt.setOpterr(false);
00089         
00090         int c = -1;
00091         while ((c = opt.getopt()) != -1) 
00092         {
00093             switch(c) 
00094             {
00095             case 'o':
00096                 outFile = opt.getOptarg();
00097                 break;
00098             case '?':
00099                 printUsageAndExit();
00100                 break;
00101             default:
00102                 System.out.print("getopt() returned " + c + "\n");
00103             }
00104         }
00105         
00106         // parse arguments
00107         int ind = opt.getOptind();
00108         if(ind > args.length)
00109             printUsageAndExit();
00110         
00111         edlFile = new EDLFile(args[ind]);
00112 
00113         System.out.println("Synthesizing " + outFile + " from " + args[ind] + ".");
00114     }
00115 
00116     public void setup() throws IOException, ParserException
00117     {
00118         if(!edlFile.haveReadFile)
00119             edlFile.readFile();
00120 
00121         if(edlFile.chunks.size() == 0)
00122             throw new ParserException(edlFile.filename, "No chunks found");
00123 
00124         // keep track of our progress in synthesizing this EDLFile:
00125         progress.setMinimum(0);
00126         progress.setMaximum(edlFile.chunks.size());
00127         progress.setValue(0);
00128     }
00129 
00130     public void processEDL() throws IOException, UnsupportedAudioFileException
00131     {
00132         // create output samples
00133         EDLChunk ch = (EDLChunk)edlFile.chunks.get(0); 
00134         outSamplesLength = (int)(format.getSampleRate()*(ch.dstTime + ch.length));
00135 
00136         outSamples = new double[outSamplesLength];
00137         // initialize outSamples properly
00138         Arrays.fill(outSamples, 0); 
00139 
00140         // we need to go through the chunks in ascending order of dstTime:
00141         ((Heap)(edlFile.chunks)).sort();
00142 
00143         Iterator i = edlFile.chunks.iterator();
00144         int overlapAccumulator = 0;
00145         while(i.hasNext())
00146         {
00147             EDLChunk currChunk = (EDLChunk)i.next();
00148 
00149             double[] chunkSamples = currChunk.getSamples(format);
00150             int offset = (int)(currChunk.dstTime*format.getSampleRate()) - overlapAccumulator;
00151             
00152             // \todo {should commands be parsed in Chunk.getSamples()?}
00153             for(int c = 0; c < currChunk.commands.size(); c++)
00154             {
00155                 String cmd = (String)currChunk.commands.get(c);
00156                 if(cmd.equalsIgnoreCase("reverse"))
00157                 {
00158                     // reverse the current chunk in time
00159                     for(int x = 0; x < chunkSamples.length/2; x++)
00160                     {
00161                         double tmp = chunkSamples[chunkSamples.length-x-1];
00162                         chunkSamples[chunkSamples.length-x-1] = chunkSamples[x];
00163                         chunkSamples[x] = tmp;
00164                     }
00165                 }
00166                 // crossfade(time) (time in seconds) - add fade on
00167                 // both ends of chunk (like the "fade" command) but
00168                 // also overlaps it with the previous segment.
00169                 // default overlap is 1ms;
00170                 else if(cmd.toLowerCase().startsWith("crossfade"))
00171                 {
00172                     int overlap = (int)(.005*format.getSampleRate());
00173 
00174                     String[] overlapTime = cmd.split("[(),\\s]");
00175 
00176                     if(overlapTime.length >= 2)
00177                         overlap = (int)(format.getSampleRate()*Double.parseDouble(overlapTime[1]));
00178 
00179                     if(offset - overlap > 0)
00180                     {
00181                         offset -= overlap;
00182                         overlapAccumulator += overlap;
00183                     }
00184                 }
00185                 // fade|crossfade(fadeInTime,fadeOutTime) (times in seconds)
00186                 // if no arguments present defaults to 1ms fade in/out time
00187                 // if 1 argument fadeInTime = fadeOutTime
00188                 // \todo{crossfade is only allowed to have one argument but this is not enforced}
00189                 else if(cmd.toLowerCase().startsWith("crossfade") || cmd.toLowerCase().startsWith("fade"))
00190                 {
00191                     double fadeInTime = .005;
00192                     double fadeOutTime = .005;
00193 
00194                     String[] fadeTimes = cmd.split("[(),\\s]");
00195 
00196                     if(fadeTimes.length == 2)
00197                     {
00198                         fadeInTime = Double.parseDouble(fadeTimes[1]);
00199                         fadeOutTime = fadeInTime;
00200                     }
00201                     else if(fadeTimes.length > 2)
00202                     {
00203                         fadeInTime = Double.parseDouble(fadeTimes[1]);
00204                         fadeOutTime = Double.parseDouble(fadeTimes[2]);
00205                     }
00206 
00207                     // Smooth out any rough edges to the data with a
00208                     // simple triangular window on each end.
00209                     // \todo{Add support for fancier fade curves?}
00210                     int fadeInWinSize = (int)(fadeInTime*format.getSampleRate());
00211                     for(int x = 0; x < fadeInWinSize; x++) 
00212                     {
00213                         if(x < chunkSamples.length)
00214                             chunkSamples[x] *= (double)x / (double)fadeInWinSize;
00215                     }
00216 
00217                     int fadeOutWinSize = (int)(fadeOutTime*format.getSampleRate());
00218                     for(int x = 0; x < fadeOutWinSize; x++)
00219                     {
00220                         if(chunkSamples.length - 1 - x > 0)
00221                             chunkSamples[chunkSamples.length - 1 - x] 
00222                                 *= (double)x / (double)fadeOutWinSize;
00223                     }
00224                 }
00225                 // gain(A) - scale the waveform by A.
00226                 else if(cmd.toLowerCase().startsWith("gain"))
00227                 {
00228                                         //scale the amplitudes
00229                     String[] gainString = cmd.split("[(),\\s]");
00230                     double gain = Double.parseDouble(gainString[1]);
00231 
00232                     for(int x = 0; x < chunkSamples.length; x++)
00233                         chunkSamples[x] *= gain;
00234                 }
00235                 else
00236                     System.out.println("Ignored unknown command: " + cmd);
00237             }
00238             // copy the current chunk's samples into the output buffer
00239             for(int x = 0; x < chunkSamples.length; x++)
00240                 if(offset + x < outSamples.length)
00241                     outSamples[offset + x] += chunkSamples[x];
00242 
00243             progress.setValue(progress.getValue()+1);
00244         }
00245 
00246         outSamplesLength -= overlapAccumulator;
00247     }
00248     
00252     public void run()
00253     {
00254         try 
00255         {
00256             doSynthesizer();
00257         }
00258         catch(Exception e) 
00259         { 
00260             exceptionHandler.handleException(e);
00261         }
00262     } 
00263     
00267     public void stop()
00268     {
00269         try
00270         {
00271             audioWriter.close();
00272         }
00273         catch(IOException e) 
00274         { 
00275             exceptionHandler.handleException(e);
00276         }
00277 
00278         audioWriter = null;
00279     }
00280 
00281     public void doSynthesizer() throws IOException, ParserException, UnsupportedAudioFileException, LineUnavailableException
00282     {
00283         setup();
00284         processEDL();
00285         
00286         // write out the audio data
00287         //AudioWriter aw = new AudioWriter(outFile, format,  AudioFileFormat.Type.WAVE);
00288         audioWriter = openAudioWriter(outFile);
00289         
00290         audioWriter.write(outSamples, outSamplesLength);
00291         audioWriter.close();
00292     }
00293 
00294     public static void main(String[] args) 
00295     {
00296         Synthesizer o2or = new Synthesizer(args);
00297         long startTime = System.currentTimeMillis();
00298         o2or.verbose = true;
00299         o2or.run();
00300         System.out.println("Done.  Took " + 
00301                            ((System.currentTimeMillis() - startTime)/1000.0)
00302                            + "s");
00303         System.exit(0);
00304     }
00305 }

Generated on Tue Feb 6 19:02:27 2007 for MEAPsoft by doxygen1.2.18