Section 8.2. Manipulating Sounds


[Page 262 (continued)]

8.2. Manipulating Sounds

Now that we know how sounds are encoded, we can manipulate sounds using Java programs. Here's what we'll need to do.

  1. We'll need to get a filename of a WAV file, and make a Sound object from it. You already saw how to do that in Section 3.6.5.

  2. You will often get the samples of the sound as an array. Sample objects are easy to manipulate, and they know that when you change them, they should automatically change the original sound. You'll read first about manipulating the samples to start with, then about how to manipulate the sound samples from within the sound itself.


    [Page 263]

  3. Whether you get the sample objects out of a sound or just deal with the samples in the sound object, you will then want to do something to the value at the sample.

  4. You may then want to write the sound back out to a new file, to use elsewhere. (Most sound editing programs know how to deal with audio.)

8.2.1. Opening Sounds and Manipulating Samples

You have already seen how to pick a file with FileChooser.pickAFile() and then make a Sound object (object of the class Sound) with new Sound(fileName). Here's an example of doing that in DrJava.

> String fileName = FileChooser.pickAFile(); > Sound sound1 = new Sound(fileName); > System.out.println(sound1); Sound file: preamble.wav number of samples: 421110


What new Sound(fileName) does is to scoop up all the bytes from the file name provided as input, dump them into memory, and place a big sign on them saying, "This is a sound object (an object of the class Sound)!" When you execute Sound sound1 = new Sound(fileName), you are saying, "Create an object variable called sound1 that refers to the Sound object created from the information in the file with a file name given by the variable fileName." When you use sound1 as input to methods, you are saying "Use that sound object over there (yeah, the one referred to by the variable sound1) as input to this method."

You can get the samples from a sound using getSamples(). The method getSamples() must be invoked on a Sound object and returns an array of all the samples as SoundSample objects (objects of the class SoundSample). When you execute this method, it may take quite a while before it finisheslonger for longer sounds, shorter for shorter sounds.

> SoundSample[] sampleArray = sound1.getSamples(); > System.out.println(sampleArray.length); 421110


The method getSamples() is making an array of SoundSample objects out of the samples in the Sound object. An object is more than just a simple value-for example, a SoundSample object knows what Sound object it came from and what its index is. You will read more about objects later, but take it at face value now that getSamples() provides you with a bunch of sample objects that you can manipulateand, in fact, makes manipulation pretty easy. You can get the value of a SoundSample object by using getValue(), and you set the value of a SoundSample object with setValue(value).

But before we get to the manipulations, let's look at some other ways to get and set samples. We can ask the sound to give us the value of a specific sample at a specific index, by using the method getSampleValueAt(index) on a Sound object.


[Page 264]

> System.out.println(sound1.getSampleValueAt(0)); 36 > System.out.println(sound1.getSampleValueAt(1)); 29


What numbers can we use as index values? We can use anything between 0 and the number of samples minus 1. We can get the number of samples using getLength(). Notice the error that we get below if we try to get a sample past the end of the array.

> System.out.println(sound1.getLength()); 421110 > sound1.getSampleValueAt(500000); You are trying to access the sample at index: 500000, but the last valid index is at 421109


We can similarly change sample values in a Sound object with

setSampleValueAt(index)


This method changes the value of the sample at the passed index. We can then check it again with getSampleValueAt().

> System.out.println(sound1.getSampleValueAt(0)); 36 > sound1.setSampleValueAt(0,12); > System.out.println(sound1.getSampleValueAt(0)); 12


What do you think would happen if we then played this sound? Would it really sound different than it did before, now that we've turned the first sample from the number 36 to the number 12? Not really. To explain why not, let's find out what the sampling rate is for this sound, by using the method getSamplingRate().

> String fileName = FileChooser.getMediaPath("preamble.wav"); > Sound aSound = new Sound(fileName); > System.out.println(aSound.getSamplingRate()); 22050.0


To make some of our manipulations easier, we're going to be using

FileChooser.setMediaPath(String directory)


and

FileChooser.getMediaPath(String baseFileName)


Using setMediaPath(String directory) will set a media directory (folder), and then getMediaPath(String baseFileName) will reference media files within that directory. This makes it much easier to reference media filesyou don't have to spell out the whole path. The method getMediaPath takes a base file name as an argument, and will return the directory set by setMediaPath with the passed file name added to the end of the directory name. The default for the media directory is c:/intro-prog-java/mediasources/. If you have your media in another directory, you will need to use setMediaPath before you can use getMediaPath.


[Page 265]

> FileChooser.setMediaPath("c:/intro-prog-java/mediasources/"); The media directory is now c:/intro-prog-java/mediasources/ > System.out.println(FileChooser.getMediaPath("barbara.jpg")); c:/intro-prog-java/mediasources/barbara.jpg > System.out.println(FileChooser.getMediaPath("croak.wav")); c:/intro-prog-java/mediasources/croak.wav


Common Bug: It's not a File, it's a String

Just because getMediaPath returns something that looks like a path doesn't mean that a file really exists at that location. You have to know the right base name and enter it correctly. If you put in a base file name of a file that doesn't exist you'll get a path to a non-existent file. The method getMediaPath will warn you if the file doesn't exist.

> FileChooser.getMediaPath("blahblah.wav"); There is no file named blahblah.wav in directory c:/intro-prog-java/mediasources/



The sound that we're manipulating in this example (a recording of Mark reading part of the U.S. Constitution's preamble) has a sampling rate of 22,050 samples per second. Changing one sample changes 1/22,050 of the first second of that sound. If you can hear that, you have amazingly good hearingand we will have some doubts about your truthfulness!

Obviously, to make a significant manipulation to the sound, we have to manipulate hundreds if not thousands of samples. We're certainly not going to do that by typing thousands of lines like this:

> aSound.setSampleValueAt(0,12); > aSound.setSampleValueAt(1,24); > aSound.setSampleValueAt(2,100); > aSound.setSampleValueAt(3,99); > aSound.setSampleValueAt(4,-1);


We need to take advantage of the computer executing our program, by telling it to go do something hundreds or thousands of times. That's the topic for the next section.

But we will end this section by talking about how to write your results back out to a file. Once you've manipulated your sound and want to save it out to use elsewhere, you use write(String fileName) which takes a filename as input. Be sure that your file ends with the extension ".wav" if you're saving a sound so that your operating system knows what to do with it (what type of data is in it)!

> Sound aSound = new Sound(FileChooser.pickAFile()); > System.out.println(aSound.getFileName()); c:\intro-prog-java\mediasources\preamble.wav > aSound.write("c:\\intro-prog-java\\mediasources\\preamble.wav");



[Page 266]

Common Bug: Saving a File Quicklyand How to Find it Again!

What if you don't know the whole path to a directory of your choosing? You don't have to specify anything more than the base name.

> aSound.write("new-preamble.wav")


The problem is finding the file again! In what directory did it get saved? This is a pretty simple bug to resolve. The default directory (the one you get if you don't specify a path) is wherever DrJava is. You can also use

FileChooser.getMediaPath("new-preamble.wav");


to get the full name of the media directory and base file name.


You'll probably figure out when playing sounds a lot that if you use play() a couple times in quick succession, you'll mix the sounds. How do you make sure that the computer plays only a single sound and then waits for that sound to end? You use something called blockingPlay(). That works the same as play(), but it waits for the sound to end so that no other sound can interfere while it's playing.

8.2.2. Using MediaTools for Looking at Sounds

The MediaTools for manipulating sounds that you read about earlier can also be used to study sound files. Any WAV file on your computer can be opened and studied within the sound tools.

Using the MediaTools Application

Your CD contains a mediasources directory on it. Most of the examples in the book use the media in this directory. You'll probably want to drag the mediasources folder onto your hard disk so that you can use it there. The default media directory is c:/intro-prog-java/mediasources so that would be a good place to put it. If you put it somewhere else just use

FileChooser.setMediaPath(directory)


in the DrJava Interactions Pane to set the directory to use a different directory.

From the basic sound editor tool in the MediaTools application, click on File to get the option to open a WAV file (Figure 8.13). The MediaTools' open file dialog will then appear. Find a WAV file by clicking on the directories on the left until you find one that contains the WAV files you want on the right (Figure 8.14), then click OK.

Figure 8.13. The sound editor open menu in MediaTools application.
(This item is displayed on page 267 in the print version)


Figure 8.14. MediaTools application open file dialog.
(This item is displayed on page 267 in the print version)


You will then be shown the file in the sound editor view (Figure 8.15). The sound editor lets you explore a sound in many ways (Figure 8.16). As you scroll through the sound and change the sound cursor (the red/blue line in the graph) position, the index changes to show you which sound array element you're currently looking at, and the VALUE shows you the value at that index. You can also fit the whole sound into the graph to get an overall view (COMPRESS WAVE VIEW button) which necessarily "skips" some samples in the view, or get back to the expanded view where every sample is visible with the EXPAND WAVE VIEW button. You can set the cursor (via the scrollbar or by dragging in the graph window), then play the sound before (PLAY BEFORE button) or after the cursor (PLAY AFTER)a good way to hear what part of the sound corresponds to what index positions. You can even "play" your recorded sound as if it were an instrumenttry pressing the piano keys across the bottom of the editor. Keys to the right shift the sound to a higher frequencysomething we'll be learning to do a bit later. Clicking the SHOW FFT AT CURSOR button presents an FFT view of the sound.


[Page 268]

Figure 8.15. A sound opened in the editor in MediaTools application.
(This item is displayed on page 267 in the print version)


Figure 8.16. Exploring the sound in the editor in MediaTools application.


8.2.3. Introducing Loops

The problem of wanting to do something similar a great many times is a common one in computing: How do we get the computer to do something over-and-over again? We need to get the computer to loop or iterate. Java has commands especially for looping (iterating).

Starting with Java version 5.0 (1.5) there is a new way of looping through all members of an array using a for-each loop. We first introduced this loop in Section 4.3.1. The syntax is

for (Type variableName : array )


You can read this as, "for each element in the array execute the body of the loop." The first time through the loop the variableName will refer to the first element of the array (the one at index 0). The second time through the loop the variableName will refer to the second element of the array (the one at index 1). The last time through the loop the variableName will refer to the last element of the array (the one at index (length -1)). The code to loop through all the SoundSample objects in an array of SoundSample objects and set the value of each sample to its original value (no change) is below:

public void doNothing() {   SoundSample[] sampleArray = this.getSamples();   int value = 0; 
[Page 269]
// loop through all the samples in the array for (SoundSample sample : sampleArray) { value = sample.getValue(); sample.setValue(value); } }


As you can see, the for-each loop makes it easy to loop through all the elements of an array. You can use the for-each loop whenever you want to process all the elements of an array and you don't need to know the current index in the body of the loop.

If you are not using Java 1.5, you may want to start with a while loop. Even if you are using Java 1.5, there are things you can do with a while loop that you can't do with a for-each loop. A while loop executes some commands while a test returns true. In order for the loop to stop there must be some way for the test to end up false. We introduced the while loop in Section 4.3.2.

The way that we will manipulate a sound is to change the values in the samples that make up the sound. We want to loop through all the samples in a sound and do something to each value. One way to do that is to loop through all the elements of the array of samples. We are going to use the getSamples() method we saw earlier to provide our array.

For example, here is the while loop that simply sets each sample to its own value (a particularly useless exercise, but it'll get more interesting in just a couple pages).

public void doNothing() {   SoundSample[] sampleArray = this.getSamples();   SoundSample sample = null;   int index = 0;   int value = 0;   // loop through all the samples in the array   while (index < sampleArray.length)   {     sample = sampleArray[index];     value = sample.getValue();     sample.setValue(value);     index++;   } }


Let's talk through this code.

  • The first statement gets the array of SoundSample objects from the current Sound object using the method getSamples() and declares a variable sampleArray which refers to it. The this means the current Sound object that was implicitly passed to the method. You could leave off the this and the compiler would add it.


  • [Page 270]
  • The next statement declares a variable sample that can refer to a SoundSample object but is set to null to show that it doesn't reference any object yet.

  • Next a primitive variable index is declared and initialized to 0.

  • The primitive variable value is declared and initialized to 0.

  • The code while (index < sampleArray.length) tests if the value of index is less than the length of the array of SoundSample objects. If it is, the body of the loop will be executed. If not, execution will continue with the first statement following the body of the loop.

  • The first statement in the body of the loop sets the variable sample to refer to the SoundSample object at the value of index in the array sampleArray. Since index starts off with a value of 0 this will refer to the first SoundSample object in the array the first time through the loop.

  • The space reserved for the variable value is set to the value of the SoundSample object referred to by sample.

  • Next, the value of the SoundSample object referred to by sample is set to the contents of value. Since the contents of the variable value is the value for this SoundSample object there will be no change. It just sets the sample value to the original value.

  • Finally, the value in index is incremented by one. Then execution will jump back to the while test again.

Here's the exact same code (it would work exactly the same), but with different variable names.

SoundSample[] a = this.getSamples(); SoundSample s = null; int i = 0; int v = 0; // loop through all the samples in the array while (i < a.length) {   s = a[i];   v = s.getValue();   s.setValue(v);   i++; }


What's the difference? These are slightly easier to confuse variable names. a and s are not as obvious as to what they are naming as sampleArray and sample. Java doesn't care which we use, and the single-character variable names are clearly easier to type. But the longer variable names make it easier to understand your code. It is best to try to make your code easier for humans to understand.

You may have wondered do we need the variable v? We could combine the two statements into one.

SoundSample[] a = this.getSamples(); SoundSample s = null; 
[Page 271]
int i = 0; // loop through all the samples in the array while (i < a.length) { s = a[i]; s.setValue(s.getValue()); i++; }


Now that we see how to get the computer to do thousands of commands without writing thousands of individual lines, let's do something useful with this.

Making it Work Tip: Keep Sounds Short

Longer sounds take up more memory and will process more slowly.


Common Bug: Windows and WAV Files

The world of WAV files isn't as compatible and smooth as one might like. WAV files created with other applications (such as Windows Recorder) may not play in DrJava, and DrJava WAV files may not play in all other applications (e.g., WinAmp 2). Some tools like Apple QuickTime Player Pro (http://www.apple.com/quicktime) are good at reading any WAV file and being able to export a new one that most any other application can read. Some WAV files are encoded using MP3, which means they are really MP3 files. You can convert these using Sound.convert(origFileName, convertedFileName) where origFileName and convertedFileName are the full names (include path information).




Introduction to Computing & Programming Algebra in Java(c) A Multimedia Approach
Introduction to Computing & Programming Algebra in Java(c) A Multimedia Approach
ISBN: N/A
EAN: N/A
Year: 2007
Pages: 191

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net