Chapter 19

Sound Manipulation

Sounds are represented in computer memory as a sequence of discrete samples of air pressure readings. CDs are recorded using 44,100 of these samples per second. With that many samples, it is possible to reconstruct the original sound waves in such a way that our ears have a difficult time hearing any difference between the reproduction and the original sound. Given access to those samples in a Python program, we can do some interesting things.

Listing 19.1: Reverse WAV

 1 # reversewav.py
 2 import array
 3 import contextlib
 4 import wave
 5
 6 def datatype(width):
 7 return "B" if width == 1 else "h"
 8
 9 def readwav(fname):
10 with contextlib.closing(wave.open(fname)) as f:
11  params = f.getparams()
12  frames = f.readframes(params[3])
13 return array.array(datatype(params[1]), frames), params
14
15 def writewav(fname, data, params):
16 with contextlib.closing(wave.open(fname, "w")) as f:
17  f.setparams(params)
18  f.writeframes(data.tostring())
19 print(fname, "written.")
20
21 def main():
22 fname = input("Enter the name of a .wav file: ")
23 data, params = readwav(fname)
24 outfname = "rev" + fname
25 writewav(outfname, data[::−1], params)
26
27 main()

Before running this program, put a WAV file with name ending in .wav in the same folder or directory as the program, and provide that file name when the program requests it. Then listen to the file that was created. See page 71 if you do not recognize the conditional expression on line 7.

WAV Files

The wave module provides one key function:

wave.open(file[, mode])

Open file in mode "r" (read, the default)

or "w" (write).

The wave.open() function returns either a Wave_read or Wave_write object. If f is a Wave_read object, then it has these methods:

f.getparams()

Get tuple of parameters describing f.

f.readframes(n)

Read n frames from f.

The parameters returned by .getparams() are described below. There is also a separate .get...() method for each of the individual parameters.

If f is a Wave_write object, then it has these corresponding methods:

f.setparams(params)

Set parameters for f to params.

f.writeframes(frames)

Write frames to f.

The argument to .setparams() may be a tuple or list. There is a separate .set...() method for each of the individual parameters. In Listing 19.1, the output file parameters are set to be the same as those that were used in the input file.

WAV File Parameters and Frames

Understanding the parameters that control WAV files and their relation to the frames returned is the key to using them correctly. A call to .getparams() returns these six values:

 (nchannels, sampwidth, framerate, nframes, comptype, compname)

nchannels: The number of channels per frame: 1 for mono recordings, 2 for stereo. If there are 2, the samples alternate left, right, left, right, etc.

sampwidth: The number of bytes per sample. One-byte samples are unsigned integers 0 to 255, whereas two-byte samples are signed integers −32, 768 to 32,767.

framerate: Also known as the sampling frequency, this is the number of frames per second (44,100 for CD quality).

nframes: The number of frames in the file.

For example, if nchannels = 2 and sampwidth = 2, then each frame is 4 bytes long and made up of 2 samples, left followed by right. If nchannels = 1 and sampwidth = 1, then each frame is a single sample made up of one byte.

The last two parameters refer to compression; we will not use them.

Tuples Are Immutable Lists

Both .getparams() and .setparams() use a tuple to communicate all six parameters at once. In Python, a tuple is exactly like a list except that it is immutable.

Tuples are written with parentheses instead of square brackets: for example, t = (1, 2, 3) is a tuple, whereas u = [1, 2, 3] is a list. Individual entries may be accessed by index, as in lines 12 and 13 of Listing 19.1.

It is sometimes convenient to unpack a tuple into its separate entries using multiple assignment:

<var1>, <var2>, ..., <varN> = <tuple>

Each variable on the left is assigned to the corresponding value from the tuple on the right, as long as the number of items match.

Recall our earlier discussion of multiple return values and multiple assignment (see page 95). In the terminology of this chapter, functions return more than one value by returning a tuple, and that tuple is often unpacked by the caller.

Data Arrays

The array module converts the raw bytes in a sound sample into an array that is easy to manipulate.

array.array(code, rawdata)

Array initialized with rawdata

according to code.

The type codes corresponding to data in WAV files are:

B

Unsigned 1-byte integer.

h

Signed 2-byte integer.

The array object returned by array.array() supports indexing, slicing, concatenation, repeated concatenation, and most of the same methods as lists. In other words, once you have sound data in an array, you can manipulate it just as you would a list. If data is an array, you can find out the type code it was created with like this:

data.typecode

Type code used to create data.

This is technically a field of the array object, which you will learn about in Chapters 21 and 23.

Context Managers

Finally, Listing 19.1 requires the contextlib module, which provides methods that objects need in order to be used inside with statements. In this case, we need a closing function because without it, Wave_read and Wave_write objects do not know to call their .close() method when the with block completes.

contextlib.closing(obj)

Manager to close obj when block finishes.

Exercises

  1. 19.1 Use Listing 19.1 to:
    1. (a) Identify where in the code the sound samples are reversed.
    2. (b) Explain the purpose of the datatype() function.
    3. (c) Identify the WAV parameter returned by params[3] in line 12.
    4. (d) Identify the WAV parameter returned by params[1] in line 13.
  2. 19.2 Suppose params refers to a tuple of WAV file parameters, as in line 11 of Listing 19.1. Write one line of code to unpack params into its six components.
  3. 19.3 Write one line of code to pack the variables nchannels, sampwidth, framerate, nframes, comptype, and compname into a tuple named params.
  4. 19.4 Explain why two sets of parentheses are necessary in this method call:
     f.setparams((nchannels, sampwidth, framerate,
       nframes, comptype, compname))

    What could the inner set of parentheses be replaced with?

  5. 19.5 Modify Listing 19.1 to use a separate reverse(data) function.
  6. 19.6 Write a function display(params) that prints the parameter list in a nice format. Include the duration of the WAV file in seconds.
  7. 19.7 Write a function silence(typecode, length) that returns a new data array containing all zeros of the given type code and length.
  8. 19.8 Write a function left(data) that returns the left channel of a two-channel stereo data array.
  9. 19.9 Write a function right(data) that returns the right channel of a two-channel stereo data array.
  10. 19.10 Write a function mix(left, right) that mixes left and right channel data arrays into one two-channel stereo data array. Do not assume the two channels have the same length.
  11. 19.11 Listing 19.1 does not handle stereo sounds correctly. Describe what it currently does and rewrite the program to work for both mono and stereo files. Exercises 19.7, Exercises 19.8, Exercises 19.9 and Exercises 19.10 will help.
  12. 19.12 Write a function clamp(x, a, b) that returns the value of x clamped to be in the interval [a, b]:

    clamp(x, a, b) = {aif x<=abif x>=bxotherwise

    Hint: it can be done in one line using min() and max().

  13. 19.13 Write a function clampsample(value, typecode) that uses the previous exercise to return value clamped into the appropriate range for type codes “B” and “h.” Because samples must be integers while value may be a float, have this function return an integer.
  14. 19.14 Write an amplify(data, factor) function that returns a copy of data with each sample multiplied by factor. Do not change the original data array, and use clamping to make sure each new sample has a valid value.
  15. 19.15 Write a function maxabs(data) that returns the maximum of the absolute value of the elements in data. Note that this is just the max() if data has type code “B.”
  16. 19.16 Write a function normalize(data) that uses amplification to normalize data. A sound file is normalized if it uses the full range of available values for its sample width. Use maxabs() from the previous exercise to help calculate the amplification factor.
  17. 19.17 Write a function addsamples(data1, data2) that creates a new data array containing the sum of the samples in the given arrays. Assume data1 and data2 have the same type code, but do not assume they are the same length. Clamp the new values.
  18. 19.18 Write a function echo(data, delay, factor) that returns a copy of the original sound with an echo. The delay parameter specifies the number of frames that the echo is delayed, and factor is the amplification factor for the echo (less than 1).

    The data array returned by this function will be longer than the original because of the delayed echo, so you will not be able to use the original file’s params when you write the new file. You will need to send a new set of parameters (which may be in a list) to write the new file.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.151.158