How to manipulate wav file data in Python?

JVE999 picture JVE999 · Sep 5, 2013 · Viewed 40.4k times · Source

I'm trying to read a wav file, then manipulate its contents, sample by sample

Here's what I have so far:

import scipy.io.wavfile
import math

rate, data = scipy.io.wavfile.read('xenencounter_23.wav')

for i in range(len(data)):
    data[i][0] = math.sin(data[i][0])
    print data[i][0]

The result I get is:

0
0
0
0
0
0

etc

It is reading properly, because if I write print data[i] instead I get usually non-zero arrays of size 2.

Answer

Warren Weckesser picture Warren Weckesser · Sep 5, 2013

The array data returned by wavfile.read is a numpy array with an integer data type. The data type of a numpy array can not be changed in place, so this line:

data[i][0] = math.sin(data[i][0])

casts the result of math.sin to an integer, which will always be 0.

Instead of that line, create a new floating point array to store your computed result.

Or use numpy.sin to compute the sine of all the elements in the array at once:

import numpy as np
import scipy.io.wavfile

rate, data = scipy.io.wavfile.read('xenencounter_23.wav')

sin_data = np.sin(data)

print sin_data

From your additional comments, it appears that you want to take the sine of each value and write out the result as a new wav file.

Here is an example that (I think) does what you want. I'll use the file 'M1F1-int16-AFsp.wav' from here: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Samples.html. The function show_info is just a convenient way to illustrate the results of each step. If you are using an interactive shell, you can use it to inspect the variables and their attributes.

import numpy as np
from scipy.io import wavfile

def show_info(aname, a):
    print "Array", aname
    print "shape:", a.shape
    print "dtype:", a.dtype
    print "min, max:", a.min(), a.max()
    print

rate, data = wavfile.read('M1F1-int16-AFsp.wav')

show_info("data", data)

# Take the sine of each element in `data`.
# The np.sin function is "vectorized", so there is no need
# for a Python loop here.
sindata = np.sin(data)

show_info("sindata", sindata)

# Scale up the values to 16 bit integer range and round
# the value.
scaled = np.round(32767*sindata)

show_info("scaled", scaled)

# Cast `scaled` to an array with a 16 bit signed integer data type.
newdata = scaled.astype(np.int16)

show_info("newdata", newdata)

# Write the data to 'newname.wav'
wavfile.write('newname.wav', rate, newdata)

Here's the output. (The initial warning means there is perhaps some metadata in the file that is not understood by scipy.io.wavfile.read.)

<snip>/scipy/io/wavfile.py:147: WavFileWarning: Chunk (non-data) not understood, skipping it.
  WavFileWarning)
Array 'data'
shape: (23493, 2)
dtype: int16
min, max: -7125 14325

Array 'sindata'
shape: (23493, 2)
dtype: float32
min, max: -0.999992 0.999991

Array 'scaled'
shape: (23493, 2)
dtype: float32
min, max: -32767.0 32767.0

Array 'newdata'
shape: (23493, 2)
dtype: int16
min, max: -32767 32767

The new file 'newname.wav' contains two channels of signed 16 bit values.