/*Modified by Tommy Montgomery (1/07) to read in a file containing frequencies and create .au files for each of the tones */ /*File AudioSynth01.java Copyright 2003, R.G.Baldwin This program demonstrates the ability to create synthetic audio data, and to play it back immediately, or to store it in an AU file for later playback. A GUI appears on the screen containing the following components in the North position: Generate button Play/File button Elapsed time meter (JTextField) Several radio buttons appear in the Center position of the GUI. Each radio button selects a different format for synthetic audio data. The South position of the GUI contains the following components: Listen radio button File radio button File Name text field Select a radio button from the Center and click the Generate button. A short segment of synthetic audio data will be generated and saved in memory. The segment length is two seconds for monaural data and one second for stereo data, at 16000 samp/sec and 16 bits per sample. To listen to the audio data, select the Listen radio button in the South position and click the Play/File button. You can listen to the data repeatedly if you so choose. In addition to listening to the data, you can also save it in an audio file. To save the audio data in an audio file of type AU, enter a file name (without extension) in the text field in the South position, select the File radio button in the South position, and click the Play/File button. You should be able to play the audio file back with any standard media player that can handle the AU file type, or with a program written in Java, such as the program named AudioPlayer02 that was discussed in an earlier lesson. Tested using SDK 1.4.0 under Win2000 ************************************************/ import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.sound.sampled.*; import java.io.*; import java.nio.channels.*; import java.nio.*; import java.util.*; public class sound extends JFrame{ //The following are general instance variables // used to create a SourceDataLine object. AudioFormat audioFormat; AudioInputStream audioInputStream; SourceDataLine sourceDataLine; //The following are audio format parameters. // They may be modified by the signal generator // at runtime. Values allowed by Java // SDK 1.4.1 are shown in comments. float sampleRate = 16000.0F; //Allowable 8000,11025,16000,22050,44100 int sampleSizeInBits = 16; //Allowable 8,16 int channels = 1; //Allowable 1,2 boolean signed = true; //Allowable true,false boolean bigEndian = true; //Allowable true,false //--------String used to denote the file to write to--------// String writeFile = ""; double frequency = 0.0; //A buffer to hold two seconds monaural and one // second stereo data at 16000 samp/sec for // 16-bit samples byte audioData[] = new byte[16000*4]; //Following components appear in the North // position of the GUI. final JButton generateBtn = new JButton("Generate"); final JButton playOrFileBtn = new JButton("Play/File"); final JLabel elapsedTimeMeter = new JLabel("0000"); //Following radio buttons select a synthetic // data type. Add more buttons if you add // more synthetic data types. They appear in // the center position of the GUI. final JRadioButton tones = new JRadioButton("Tones",true); final JRadioButton stereoPanning = new JRadioButton("Stereo Panning"); final JRadioButton stereoPingpong = new JRadioButton("Stereo Pingpong"); final JRadioButton fmSweep = new JRadioButton("FM Sweep"); final JRadioButton decayPulse = new JRadioButton("Decay Pulse"); final JRadioButton echoPulse = new JRadioButton("Echo Pulse"); final JRadioButton waWaPulse = new JRadioButton("WaWa Pulse"); //Following components appear in the South // position of the GUI. final JRadioButton listen = new JRadioButton("Listen",true); final JRadioButton file = new JRadioButton("File"); final JTextField fileName = new JTextField("junk",10); //-------------------------------------------// public static void main( String args[]){ new sound(); }//end main //-------------------------------------------// public sound(){//constructor //A panel for the North position. Note the // etched border. final JPanel controlButtonPanel = new JPanel(); controlButtonPanel.setBorder( BorderFactory.createEtchedBorder()); //A panel and button group for the radio // buttons in the Center position. final JPanel synButtonPanel = new JPanel(); final ButtonGroup synButtonGroup = new ButtonGroup(); //This panel is used for cosmetic purposes // only, to cause the radio buttons to be // centered horizontally in the Center // position. final JPanel centerPanel = new JPanel(); //A panel for the South position. Note the // etched border. final JPanel outputButtonPanel = new JPanel(); outputButtonPanel.setBorder( BorderFactory.createEtchedBorder()); final ButtonGroup outputButtonGroup = new ButtonGroup(); //Disable the Play button initially to force // the user to generate some data before // trying to listen to it or write it to a // file. playOrFileBtn.setEnabled(false); //Register anonymous listeners on the // Generate button and the Play/File button. generateBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //Don't allow Play during generation playOrFileBtn.setEnabled(false); //Generate synthetic data try { String readFile = "freq.txt"; FileReader f = new FileReader(readFile); BufferedReader reader = new BufferedReader(f); String line = reader.readLine(); while (line != null) { writeFile = line; frequency = Double.parseDouble(line); System.out.println(line); new SynGen().getSyntheticData(audioData); line = reader.readLine(); } } catch (IOException error) { System.out.println(error.getMessage()); } //Now it is OK for the user to listen // to or file the synthetic audio data. playOrFileBtn.setEnabled(true); }//end actionPerformed }//end ActionListener );//end addActionListener() playOrFileBtn.addActionListener( new ActionListener(){ public void actionPerformed( ActionEvent e){ //Play or file the data synthetic data playOrFileData(); }//end actionPerformed }//end ActionListener );//end addActionListener() //Add two buttons and a text field to a // physical group in the North of the GUI. controlButtonPanel.add(generateBtn); controlButtonPanel.add(playOrFileBtn); controlButtonPanel.add(elapsedTimeMeter); //Add radio buttons to a mutually exclusive // group in the Center of the GUI. Make // additions here if you add new synthetic // generator methods. synButtonGroup.add(tones); synButtonGroup.add(stereoPanning); synButtonGroup.add(stereoPingpong); synButtonGroup.add(fmSweep); synButtonGroup.add(decayPulse); synButtonGroup.add(echoPulse); synButtonGroup.add(waWaPulse); //Add radio buttons to a physical group and // center it in the Center of the GUI. Make // additions here if you add new synthetic // generator methods. synButtonPanel.setLayout( new GridLayout(0,1)); synButtonPanel.add(tones); synButtonPanel.add(stereoPanning); synButtonPanel.add(stereoPingpong); synButtonPanel.add(fmSweep); synButtonPanel.add(decayPulse); synButtonPanel.add(echoPulse); synButtonPanel.add(waWaPulse); //Note that the centerPanel has center // alignment by default. centerPanel.add(synButtonPanel); //Add radio buttons to a mutually exclusive // group in the South of the GUI. outputButtonGroup.add(listen); outputButtonGroup.add(file); //Add radio buttons to a physical group in // the South of the GUI. outputButtonPanel.add(listen); outputButtonPanel.add(file); outputButtonPanel.add(fileName); //Add the panels containing components to the // content pane of the GUI in the appropriate // positions. getContentPane().add( controlButtonPanel,BorderLayout.NORTH); getContentPane().add(centerPanel, BorderLayout.CENTER); getContentPane().add(outputButtonPanel, BorderLayout.SOUTH); //Finish the GUI. If you add more radio // buttons in the center, you may need to // modify the call to setSize to increase // the vertical component of the GUI size. setTitle("Copyright 2003, R.G.Baldwin"); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(250,275); setVisible(true); }//end constructor //-------------------------------------------// //This method plays or files the synthetic // audio data that has been generated and saved // in an array in memory. private void playOrFileData() { try{ //Get an input stream on the byte array // containing the data InputStream byteArrayInputStream = new ByteArrayInputStream( audioData); //Get the required audio format audioFormat = new AudioFormat( sampleRate, sampleSizeInBits, channels, signed, bigEndian); //Get an audio input stream from the // ByteArrayInputStream audioInputStream = new AudioInputStream( byteArrayInputStream, audioFormat, audioData.length/audioFormat. getFrameSize()); //Get info on the required data line DataLine.Info dataLineInfo = new DataLine.Info( SourceDataLine.class, audioFormat); //Get a SourceDataLine object sourceDataLine = (SourceDataLine) AudioSystem.getLine( dataLineInfo); //Decide whether to play the synthetic // data immediately, or to write it into // an audio file, based on the user // selection of the radio buttons in the // South of the GUI.. /* if(listen.isSelected()){ //Create a thread to play back the data and // start it running. It will run until all // the data has been played back new ListenThread().start(); }else{*/ //Disable buttons until existing data // is written to the file. generateBtn.setEnabled(false); playOrFileBtn.setEnabled(false); //Write the data to an output file with // the name provided by the text field // in the South of the GUI. try{ AudioSystem.write( audioInputStream, AudioFileFormat.Type.AU, new File(writeFile + ".au")); }catch (Exception e) { e.printStackTrace(); System.exit(0); }//end catch //Enable buttons for another operation generateBtn.setEnabled(true); playOrFileBtn.setEnabled(true); //}//end else }catch (Exception e) { e.printStackTrace(); System.exit(0); }//end catch }//end playOrFileData //=============================================// //Inner class to play back the data that was // saved. class ListenThread extends Thread{ //This is a working buffer used to transfer // the data between the AudioInputStream and // the SourceDataLine. The size is rather // arbitrary. byte playBuffer[] = new byte[16384]; public void run(){ try{ //Disable buttons while data is being // played. generateBtn.setEnabled(false); playOrFileBtn.setEnabled(false); //Open and start the SourceDataLine sourceDataLine.open(audioFormat); sourceDataLine.start(); int cnt; //Get beginning of elapsed time for // playback long startTime = new Date().getTime(); //Transfer the audio data to the speakers while((cnt = audioInputStream.read( playBuffer, 0, playBuffer.length)) != -1){ //Keep looping until the input read // method returns -1 for empty stream. if(cnt > 0){ //Write data to the internal buffer of // the data line where it will be // delivered to the speakers in real // time sourceDataLine.write( playBuffer, 0, cnt); }//end if }//end while //Block and wait for internal buffer of the // SourceDataLine to become empty. sourceDataLine.drain(); //Get and display the elapsed time for // the previous playback. int elapsedTime = (int)(new Date().getTime() - startTime); elapsedTimeMeter.setText("" + elapsedTime); //Finish with the SourceDataLine sourceDataLine.stop(); sourceDataLine.close(); //Re-enable buttons for another operation generateBtn.setEnabled(true); playOrFileBtn.setEnabled(true); }catch (Exception e) { e.printStackTrace(); System.exit(0); }//end catch }//end run }//end inner class ListenThread //=============================================// //Inner signal generator class. //An object of this class can be used to // generate a variety of different synthetic // audio signals. Each time the getSyntheticData // method is called on an object of this class, // the method will fill the incoming array with // the samples for a synthetic signal. class SynGen{ //Note: Because this class uses a ByteBuffer // asShortBuffer to handle the data, it can // only be used to generate signed 16-bit // data. ByteBuffer byteBuffer; ShortBuffer shortBuffer; int byteLength; void getSyntheticData(byte[] synDataBuffer){ //Prepare the ByteBuffer and the shortBuffer // for use byteBuffer = ByteBuffer.wrap(synDataBuffer); shortBuffer = byteBuffer.asShortBuffer(); byteLength = synDataBuffer.length; //Decide which synthetic data generator // method to invoke based on which radio // button the user selected in the Center of // the GUI. If you add more methods for // other synthetic data types, you need to // add corresponding radio buttons to the // GUI and add statements here to test the // new radio buttons. Make additions here // if you add new synthetic generator // methods. if(tones.isSelected()) tones(); if(stereoPanning.isSelected()) stereoPanning(); if(stereoPingpong.isSelected()) stereoPingpong(); if(fmSweep.isSelected()) fmSweep(); if(decayPulse.isSelected()) decayPulse(); if(echoPulse.isSelected()) echoPulse(); if(waWaPulse.isSelected()) waWaPulse(); }//end getSyntheticData method //-------------------------------------------// //This method generates a monaural tone // consisting of the sum of three sinusoids. void tones(){ //-----------------// //code to generate the tone and then write it to an .au file channels = 1;//Java allows 1 or 2 //Each channel requires two 8-bit bytes per // 16-bit sample. int bytesPerSamp = 2; sampleRate = 16000.0F; // Allowable 8000,11025,16000,22050,44100 int sampLength = byteLength/bytesPerSamp; sampLength = 5000; for(int cnt = 0; cnt < sampLength; cnt++){ double time = cnt/sampleRate; //double freq = 440.0;//arbitrary frequency double sinValue = Math.sin(2*Math.PI*frequency*time); shortBuffer.put((short)(16000*sinValue)); }//end for loop playOrFileData(); }//end method tones //-------------------------------------------// //This method generates a stereo speaker sweep, // starting with a relatively high frequency // tone on the left speaker and moving across // to a lower frequency tone on the right // speaker. void stereoPanning(){ channels = 2;//Java allows 1 or 2 int bytesPerSamp = 4;//Based on channels sampleRate = 16000.0F; // Allowable 8000,11025,16000,22050,44100 int sampLength = byteLength/bytesPerSamp; for(int cnt = 0; cnt < sampLength; cnt++){ //Calculate time-varying gain for each // speaker double rightGain = 16000.0*cnt/sampLength; double leftGain = 16000.0 - rightGain; double time = cnt/sampleRate; double freq = 600;//An arbitrary frequency //Generate data for left speaker double sinValue = Math.sin(2*Math.PI*(freq)*time); shortBuffer.put( (short)(leftGain*sinValue)); //Generate data for right speaker sinValue = Math.sin(2*Math.PI*(freq*0.8)*time); shortBuffer.put( (short)(rightGain*sinValue)); }//end for loop }//end method stereoPanning //-------------------------------------------// //This method uses stereo to switch a sound // back and forth between the left and right // speakers at a rate of about eight switches // per second. On my system, this is a much // better demonstration of the sound separation // between the two speakers than is the // demonstration produced by the stereoPanning // method. Note also that because the sounds // are at different frequencies, the sound // produced is similar to that of U.S. // emergency vehicles. void stereoPingpong(){ channels = 2;//Java allows 1 or 2 int bytesPerSamp = 4;//Based on channels sampleRate = 16000.0F; // Allowable 8000,11025,16000,22050,44100 int sampLength = byteLength/bytesPerSamp; double leftGain = 0.0; double rightGain = 16000.0; for(int cnt = 0; cnt < sampLength; cnt++){ //Calculate time-varying gain for each // speaker if(cnt % (sampLength/8) == 0){ //swap gain values double temp = leftGain; leftGain = rightGain; rightGain = temp; }//end if double time = cnt/sampleRate; double freq = 600;//An arbitrary frequency //Generate data for left speaker double sinValue = Math.sin(2*Math.PI*(freq)*time); shortBuffer.put( (short)(leftGain*sinValue)); //Generate data for right speaker sinValue = Math.sin(2*Math.PI*(freq*0.8)*time); shortBuffer.put( (short)(rightGain*sinValue)); }//end for loop }//end stereoPingpong method //-------------------------------------------// //This method generates a monaural linear // frequency sweep from 100 Hz to 1000Hz. void fmSweep(){ channels = 1;//Java allows 1 or 2 int bytesPerSamp = 2;//Based on channels sampleRate = 16000.0F; // Allowable 8000,11025,16000,22050,44100 int sampLength = byteLength/bytesPerSamp; double lowFreq = 100.0; double highFreq = 1000.0; for(int cnt = 0; cnt < sampLength; cnt++){ double time = cnt/sampleRate; double freq = lowFreq + cnt*(highFreq-lowFreq)/sampLength; double sinValue = Math.sin(2*Math.PI*freq*time); shortBuffer.put((short)(16000*sinValue)); }//end for loop }//end method fmSweep //-------------------------------------------// //This method generates a monaural triple- // frequency pulse that decays in a linear // fashion with time. void decayPulse(){ channels = 1;//Java allows 1 or 2 int bytesPerSamp = 2;//Based on channels sampleRate = 16000.0F; // Allowable 8000,11025,16000,22050,44100 int sampLength = byteLength/bytesPerSamp; for(int cnt = 0; cnt < sampLength; cnt++){ //The value of scale controls the rate of // decay - large scale, fast decay. double scale = 2*cnt; if(scale > sampLength) scale = sampLength; double gain = 16000*(sampLength-scale)/sampLength; double time = cnt/sampleRate; double freq = 499.0;//an arbitrary freq double sinValue = (Math.sin(2*Math.PI*freq*time) + Math.sin(2*Math.PI*(freq/1.8)*time) + Math.sin(2*Math.PI*(freq/1.5)*time))/3.0; shortBuffer.put((short)(gain*sinValue)); }//end for loop }//end method decayPulse //-------------------------------------------// //This method generates a monaural triple- // frequency pulse that decays in a linear // fashion with time. However, three echoes // can be heard over time with the amplitude // of the echoes also decreasing with time. void echoPulse(){ channels = 1;//Java allows 1 or 2 int bytesPerSamp = 2;//Based on channels sampleRate = 16000.0F; // Allowable 8000,11025,16000,22050,44100 int sampLength = byteLength/bytesPerSamp; int cnt2 = -8000; int cnt3 = -16000; int cnt4 = -24000; for(int cnt1 = 0; cnt1 < sampLength; cnt1++,cnt2++,cnt3++,cnt4++){ double val = echoPulseHelper( cnt1,sampLength); if(cnt2 > 0){ val += 0.7 * echoPulseHelper( cnt2,sampLength); }//end if if(cnt3 > 0){ val += 0.49 * echoPulseHelper( cnt3,sampLength); }//end if if(cnt4 > 0){ val += 0.34 * echoPulseHelper( cnt4,sampLength); }//end if shortBuffer.put((short)val); }//end for loop }//end method echoPulse //-------------------------------------------// double echoPulseHelper(int cnt,int sampLength){ //The value of scale controls the rate of // decay - large scale, fast decay. double scale = 2*cnt; if(scale > sampLength) scale = sampLength; double gain = 16000*(sampLength-scale)/sampLength; double time = cnt/sampleRate; double freq = 499.0;//an arbitrary freq double sinValue = (Math.sin(2*Math.PI*freq*time) + Math.sin(2*Math.PI*(freq/1.8)*time) + Math.sin(2*Math.PI*(freq/1.5)*time))/3.0; return(short)(gain*sinValue); }//end echoPulseHelper //-------------------------------------------// //This method generates a monaural triple- // frequency pulse that decays in a linear // fashion with time. However, three echoes // can be heard over time with the amplitude // of the echoes also decreasing with time. //Note that this method is identical to the // method named echoPulse, except that the // algebraic sign was switched on the amplitude // of two of the echoes before adding them to // the composite synthetic signal. This // resulted in a difference in the // sound. void waWaPulse(){ channels = 1;//Java allows 1 or 2 int bytesPerSamp = 2;//Based on channels sampleRate = 16000.0F; // Allowable 8000,11025,16000,22050,44100 int sampLength = byteLength/bytesPerSamp; int cnt2 = -8000; int cnt3 = -16000; int cnt4 = -24000; for(int cnt1 = 0; cnt1 < sampLength; cnt1++,cnt2++,cnt3++,cnt4++){ double val = waWaPulseHelper( cnt1,sampLength); if(cnt2 > 0){ val += -0.7 * waWaPulseHelper( cnt2,sampLength); }//end if if(cnt3 > 0){ val += 0.49 * waWaPulseHelper( cnt3,sampLength); }//end if if(cnt4 > 0){ val += -0.34 * waWaPulseHelper( cnt4,sampLength); }//end if shortBuffer.put((short)val); }//end for loop }//end method waWaPulse //-------------------------------------------// double waWaPulseHelper(int cnt,int sampLength){ //The value of scale controls the rate of // decay - large scale, fast decay. double scale = 2*cnt; if(scale > sampLength) scale = sampLength; double gain = 16000*(sampLength-scale)/sampLength; double time = cnt/sampleRate; double freq = 499.0;//an arbitrary freq double sinValue = (Math.sin(2*Math.PI*freq*time) + Math.sin(2*Math.PI*(freq/1.8)*time) + Math.sin(2*Math.PI*(freq/1.5)*time))/3.0; return(short)(gain*sinValue); }//end waWaPulseHelper //-------------------------------------------// }//end SynGen class //=============================================// }//end outer class AudioSynth01.java