Main Page   Class Hierarchy   Compound List   File List   Compound Members  

Synthesizer.java

00001 /*
00002  *  Copyright 2006 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 
00031 import javax.sound.sampled.AudioFormat;
00032 
00039 public class Synthesizer extends MEAPUtil
00040 {
00041     EDLFile edlFile;
00042     String outFile = null;
00043     AudioWriter output;
00044 
00045     // output buffer
00046     double[] outSamples;
00047     int outSamplesLength;
00048 
00049     public Synthesizer(String infile, String outfile)
00050     {
00051         this(new EDLFile(infile), outfile); 
00052     }
00053 
00054     public Synthesizer(EDLFile edl, String outfile)
00055     {
00056         edlFile = edl;
00057         outFile = outfile; 
00058     }
00059     
00060     // \todo{test output to line out}
00061     public void printUsageAndExit() 
00062     {
00063         System.out.println("Usage: Synthesizer [-options] file.edl \n\n" + 
00064                            "  where options include:\n" + 
00065                            "    -o output_file  output sound file (defaults to line out)\n" +
00066                            "");
00067         System.exit(0);
00068     }
00069 
00073     public Synthesizer(String[] args) 
00074     {
00075         if(args.length == 0)
00076             printUsageAndExit();
00077 
00078         // Parse arguments
00079         Getopt opt = new Getopt("Synthesizer", args, "o:");
00080         opt.setOpterr(false);
00081         
00082         int c = -1;
00083         while ((c = opt.getopt()) != -1) 
00084         {
00085             switch(c) 
00086             {
00087             case 'o':
00088                 outFile = opt.getOptarg();
00089                 break;
00090             case '?':
00091                 printUsageAndExit();
00092                 break;
00093             default:
00094                 System.out.print("getopt() returned " + c + "\n");
00095             }
00096         }
00097         
00098         // parse arguments
00099         int ind = opt.getOptind();
00100         if(ind > args.length)
00101             printUsageAndExit();
00102         
00103         edlFile = new EDLFile(args[ind]);
00104 
00105         System.out.println("Synthesizing " + outFile + " from " + args[ind] + ".");
00106     }
00107 
00108 
00109     public void setup() throws IOException, ParserException
00110     {
00111         if(!edlFile.haveReadFile)
00112             edlFile.readFile();
00113 
00114         if(edlFile.chunks.size() == 0)
00115             throw new ParserException(edlFile.filename, "No chunks found");
00116     }
00117 
00118     public void processEDL() throws IOException
00119     {
00120         // create output samples
00121         EDLChunk ch = (EDLChunk)edlFile.chunks.get(0); 
00122         outSamples = new double[(int)(samplingRate*(ch.dstTime + ch.length))];
00123         outSamplesLength = outSamples.length;
00124         // initialize outSamples properly
00125         Arrays.fill(outSamples, 0); 
00126 
00127         // we need to go through the chunks in ascending order of dstTime:
00128         ((Heap)(edlFile.chunks)).sort();
00129 
00130         Iterator i = edlFile.chunks.iterator();
00131         int overlapAccumulator = 0;
00132         while(i.hasNext())
00133         {
00134             EDLChunk currChunk = (EDLChunk)i.next();
00135             double[] chunkSamples = currChunk.getSamples(format);
00136             int offset = (int)(currChunk.dstTime*samplingRate) - overlapAccumulator;
00137             
00138             // \todo {should commands be parsed in Chunk.getSamples()?}
00139             for(int c = 0; c < currChunk.commands.size(); c++)
00140             {
00141                 String cmd = (String)currChunk.commands.get(c);
00142                 if(cmd.equalsIgnoreCase("reverse"))
00143                 {
00144                     // reverse the current chunk in time
00145                     for(int x = 0; x < chunkSamples.length/2; x++)
00146                     {
00147                         double tmp = chunkSamples[chunkSamples.length-x-1];
00148                         chunkSamples[chunkSamples.length-x-1] = chunkSamples[x];
00149                         chunkSamples[x] = tmp;
00150                     }
00151                 }
00152                 // crossfade(time) (time in seconds) - add fade on
00153                 // both ends of chunk (like the "fade" command) but
00154                 // also overlaps it with the previous segment.
00155                 // default overlap is 1ms;
00156                 else if(cmd.toLowerCase().startsWith("crossfade"))
00157                 {
00158                     int overlap = (int)(.001*samplingRate);
00159 
00160                     String[] overlapTime = cmd.split("[(),\\s]");
00161 
00162                     if(overlapTime.length >= 2)
00163                         overlap = (int)(samplingRate*Double.parseDouble(overlapTime[1]));
00164 
00165                     if(offset - overlap > 0)
00166                     {
00167                         offset -= overlap;
00168                         overlapAccumulator += overlap;
00169                     }
00170                 }
00171                 // fade|crossfade(fadeInTime,fadeOutTime) (times in seconds)
00172                 // if no arguments present defaults to 1ms fade in/out time
00173                 // if 1 argument fadeInTime = fadeOutTime
00174                 // \todo{crossfade is only allowed to have one argument but this is not enforced}
00175                 else if(cmd.toLowerCase().startsWith("crossfade") || cmd.toLowerCase().startsWith("fade"))
00176                 {
00177                     double fadeInTime = .001;
00178                     double fadeOutTime = .001;
00179 
00180                     String[] fadeTimes = cmd.split("[(),\\s]");
00181 
00182                     if(fadeTimes.length == 2)
00183                     {
00184                         fadeInTime = Double.parseDouble(fadeTimes[1]);
00185                         fadeOutTime = fadeInTime;
00186                     }
00187                     else if(fadeTimes.length > 2)
00188                     {
00189                         fadeInTime = Double.parseDouble(fadeTimes[1]);
00190                         fadeOutTime = Double.parseDouble(fadeTimes[2]);
00191                     }
00192 
00193                     // Smooth out any rough edges to the data with a
00194                     // simple triangular window on each end.
00195                     // \todo{Add support for fancier fade curves?}
00196                     int fadeInWinSize = (int)(fadeInTime*samplingRate);
00197                     for(int x = 0; x < fadeInWinSize; x++) 
00198                     {
00199                         if(x < chunkSamples.length)
00200                             chunkSamples[x] *= (double)x / (double)fadeInWinSize;
00201                     }
00202 
00203                     int fadeOutWinSize = (int)(fadeOutTime*samplingRate);
00204                     for(int x = 0; x < fadeOutWinSize; x++)
00205                     {
00206                         if(chunkSamples.length - 1 - x > 0)
00207                             chunkSamples[chunkSamples.length - 1 - x] 
00208                                 *= (double)x / (double)fadeOutWinSize;
00209                     }
00210                 }
00211                 else
00212                     System.out.println("Ignored unknown command: " + cmd);
00213             }
00214             // copy the current chunk's samples into the output buffer
00215             for(int x = 0; x < chunkSamples.length; x++)
00216                 if(offset + x < outSamples.length)
00217                     outSamples[offset + x] += chunkSamples[x];
00218         }
00219 
00220         outSamplesLength -= overlapAccumulator;
00221     }
00222     
00226     public void go()
00227     {
00228         format = new AudioFormat(samplingRate, bitsPerSamp, numChannels, 
00229                                  signed, bigEndian);
00230         try 
00231         {
00232             setup();
00233             processEDL();
00234             
00235             // write out the audio data
00236             //AudioWriter aw = new AudioWriter(outFile, format,  AudioFileFormat.Type.WAVE);
00237             AudioWriter aw = openAudioWriter(outFile);
00238 
00239             aw.write(outSamples, outSamplesLength);
00240             aw.close();
00241         }
00242         catch(Exception e) 
00243         { 
00244             e.printStackTrace(); 
00245         }
00246     } 
00247 
00248     public static void main(String[] args) 
00249     {
00250         Synthesizer o2or = new Synthesizer(args);
00251         long startTime = System.currentTimeMillis();
00252         o2or.go();
00253         System.out.println("Done.  Took " + 
00254                            ((System.currentTimeMillis() - startTime)/1000.0)
00255                            + "s");
00256         System.exit(0);
00257     }
00258 }

Generated on Thu May 11 15:04:11 2006 for MEAPsoft by doxygen1.2.18