8.3. Changing the Volume of SoundsEarlier, we said that the amplitude of a sound is the main factor in the volume. This means that if we increase the amplitude, we increase the volume. Or if we decrease the amplitude, we decrease the volume. Don't get confused herechanging the amplitude doesn't reach out and twist up the volume knob on your speakers. If your speaker's volume (or computer's volume) is turned down, the sound will never get very loud. The point is getting the sound itself louder. Have you ever watched a movie on TV where, without changing the volume on the TV, sound becomes so low that you can hardly hear it? (Marlon Brando's dialogue in the movie The Godfather comes to mind.) That's what we're doing here. We can make sounds shout or whisper by tweaking the amplitude. 8.3.1. Increasing VolumeHere's a method that doubles the amplitude of an input sound. Program 65. Increase an Input Sound's Volume
Go ahead and type the above into your DrJava definitions pane before the last curly brace in the Sound.java class. Click COMPILE ALL to get DrJava to compile it. Follow along the example below to get a better idea of how this all works. To use this program, you have to create a sound first and invoke this method on it. Don't forget that you can't type this code in and have it work as-is: Your path names may be different than what is shown here! > String f = "c:/intro-prog-java/mediasources/gettysburg10.wav"; > Sound s = new Sound(f); > s.play(); > s.explore(); > s.increaseVolume(); > s.play(); > s.explore(); In the interactions pane we create a variable f which refers to a String object that holds the name of a file. We create the variable s which refers to a Sound object created from the file using new Sound(f). We ask this Sound object to play using s.play(). We then open an explorer on the sound to see what it looks like graphically using s.explore(). We next increase its volume using s.increaseVolume(). This implicitly passes the Sound object to the method increaseVolume(). So the code this.getSamples() in the method increaseVolume() means to get them from the implicitly passed Sound object (the one referred to by variable s).
8.3.2. Did that Really Work?Now, is it really louder, or does it just seem that way? We can check it in several ways. You could always make the sound even louder by evaluating increaseVolume on our sound a few more timeseventually, you'll be totally convinced that the sound is louder. But there are ways to test even more subtle effects. If you compare graphs of the two sounds using the sound explorer, you will find that the graph of the sound does have greater amplitude after increasing it using our method. Check it out in Figure 8.17. Figure 8.17. Comparing the graphs of the original sound (left) and the louder one (right).Maybe you're unsure that you're really seeing a larger wave in the second picture. You can use a sound explorer to check the individual sample values. You can actually already see that in Figure 8.17see that the first value (index number 0) is 59 in the original sound and 118 in the second sound. You can also check the value at any index using the sound explorer. Just click on a location and the value will be displayed for that location. To check the same location in the second explorer just type in the desired current index and it will show the value at that index. You'll see that the louder sound really does have double the value of the same sample in the original sound (Figure 8.18). Figure 8.18. Comparing specific samples in the original sound (left) and the louder one (right). |
/** * Method to halve the volume (amplitude) of the sound. */ public void decreaseVolume() { SoundSample[] sampleArray = this.getSamples(); SoundSample sample = null; int value = 0; int index = 0; // loop through all the samples in the array while (index < sampleArray.length) { sample = sampleArray[index]; value = sample.getValue(); sample.setValue((int) (value * 0.5)); index++; } } |
Our method is called on a Sound object. The Sound object is implicitly passed to the method and is accessed using the keyword this. You can leave off the this on this.getSamples() since it is understood to be invoked on the current object.
The variable sample will refer to a different SoundSample object each time through the loop.
Each time sample refers to a new SoundSample object, we will get the value of that SoundSample object. We put that in the variable value.
We then set the value held by the SoundSample object to 50% of its current value, by multiplying value by 0.5, and setting the sample value to that. However, because the value is an integer and the result of a computation with a floating point value (0.5) is a floating point number we must cast to integer using (int) (value * 0.5) to let the compiler know we realize that we will be throwing away the fractional part.
We can use it like this.
> String f = FileChooser.pickAFile(); > System.out.println(f); C:\intro-prog-java\mediasources\gettysburg10-louder.wav > Sound sound1 = new Sound(f); > System.out.println(sound1); Sound file: C:\intro-prog-java\mediasources\gettysburg10-louder.wav number of samples: 220568 > sound1.play(); > sound1.decreaseVolume(); > sound1.play();
We can even do it again, and lower the volume even further.
> sound1.decreaseVolume(); > sound1.play();
Have you ever forgotten to declare the variable index? If you did the method wouldn't compile. Did you ever forget to increment the variable index? If you did the loop would never end until you hit RESET. Because of these problems, programmers typically use a for loop instead of a while loop when they want to execute a block of commands a set number of times. A for loop is equivalent to a while loop (means the same thing to the computer). The for loop is just less error prone for a programmer (though it can be harder for a beginner to understand). We introduced for loops in Section 4.3.7.
A for loop looks like this: for (initialization; test; change). The initialization area lets you declare and initialize variables for use in the loop, the test is where you test if the loop should continue, and the change area is where you change the value of counters or indices used in the loop. For example, see the following new version of the method decreaseVolume which has been modified to use a for loop instead of a while loop.
/** * Method to halve the volume (amplitude) of the sound. */ public void decreaseVolume2() { |
We have used the to-end-of-line comment '//' to comment out some lines of code to show the difference between the while and for loops. Notice that what is different is that we don't declare and initialize the index before the loop, it is done in the initialization part of the for statement. We also don't increment the index as the last statement in the loop. This is moved to the change area in the for statement. So we have replaced three lines of code with one and made it more likely that we will remember to declare variables for use in the loop and change them. However, what really happens during execution is the same thing as what happened during the while loop. The declarations and initializations done in the initialization part of the for loop will actually take place before the first test. The change of the loop variables will actually take place after each execution of the loop body and before the next test.
The lessons that we learned when writing picture methods (from Section 4.3.5) apply to sound methods as well. We want to write methods that do one and only one thing. We want to write methods that can be reused.
We can write methods that take an input value. For example, here's a program to changeVolume. It accepts a factor that is multiplied by each sample value. This method can be used to increase or decrease the amplitude (and thus, the volume).
/** * Method to change the volume (amplitude) of the sound * by multiplying the current values in the sound by * the passed factor. * @param factor the factor to multiply by */ public void changeVolume(double factor) { SoundSample[] sampleArray = this.getSamples(); |
This program is clearly more flexible than increaseVolume(). Does that make it better? Certainly it is for some purposes (e.g., if you were writing software to do general audio processing), but for other purposes, having separate and clearly named methods for increasing and decreasing volume may be better. Of course, you could modify increaseVolume() and decreaseVolume() to call changeVolume() with the appropriate factor. Remember that software is written for humanswrite software that is understandable for the people who will be reading and using your software.
We are reusing the name sample a lot. We have used it in several methods in the Sound class. That's okay. Names can have different meanings depending on their context. Variables declared in a method have meaning only inside that method. Methods can even use the same variable names as other methods. You can even use the same variable names that you use in your methods in the interactions pane. This is a different context. If you create a variable in a method context (like value in Program 68 above), then that variable won't exist when you get back out to the interactions pane. We can return values from a method context back out to the interactions pane (or a calling method) by using return, which we'll talk more about later.