Home About Puzzles Math Programming Origami Japanese MSPaint

Don't worry, I'm not selling anything. Just giving a shoutout to one of my open-source side projects:

I created a programming language for beginners and am looking for people to test it out. If you have a spare moment (particularly if you haven't programmed before) and you're interested in making 2D games, please check it out.

Home > Programming > Modifying a WAVE File using Python

# Modifying a WAVE File using Python

You may find the Python snippet at the end of this post useful for simple sound editing and effects. It contains a few helper functions and a class called Sound. This allows you to open, modify, and save WAVE files.

The Sound class has a field called "samples". This is a list containing 1 or 2 lists of numbers. These lists of numbers are the samples for each channel (1 channel is mono, 2 channels is stereo). By modifying the samples, you can edit the sound.

Reversing a stereo sound:
snd = create_sound_from_file('foo.wav')
snd.samples[0] = snd.samples[0][::-1# reverse left channel
snd.samples[1] = snd.samples[1][::-1# reverse right channel

Double the speed:
snd = create_sound_from_file('foo.wav')
snd.samples[0] = snd.samples[0][::2# cut out every other sample
snd.samples[1] = snd.samples[1][::2]

Save back out to file:
snd.save_to_file('foo2.wav')

And here's the magic code that makes it happen:
def push_int(byte_list, value, byte_length):
while byte_length > 0:
byte_list.append(chr(value & 255))
value = value >> 8;
byte_length -= 1

def push_string(byte_list, value):
length = len(value)
i = 0
while i < length:
byte_list.append(value[i])
i += 1

class ByteStream:
def __init__(self, file):
self.i = 0
c = open(file, 'rb')
c.close()
self.length = len(self.data)

def has_next(self):
return self.i < self.length

def next_byte(self):
output = ord(self.data[self.i])
self.i += 1
return output

def next_string(self, length):
output = ''
while length > 0:
output = self.data[self.i]
self.i += 1
length -= 1
return output

def next_int(self, byte_length):
if byte_length == 1:
output = ord(self.data[self.i])
self.i += 1
elif byte_length == 2:
output = (ord(self.data[self.i]) +
(ord(self.data[self.i + 1]) << 8))
self.i += 2
elif byte_length == 4:
output = (ord(self.data[self.i]) +
(ord(self.data[self.i + 1]) << 8) +
(ord(self.data[self.i + 2]) << 16) +
(ord(self.data[self.i + 3]) << 24))
self.i += 4
else:
# how did this happen?
pass
return output

def create_sound_from_file(filename):
bs = ByteStream(filename)

ignore = bs.next_string(4# "RIFF"
ignore = bs.next_int(4# file size
ignore = bs.next_string(4# "WAVE"
ignore = bs.next_string(4# "fmt "
excess = bs.next_int(4) - 16 # file header size
ignore = bs.next_int(2# 1 for PCM

channels = bs.next_int(2# num channels

sps = bs.next_int(4# samples per second (e.g. 44100)

ignore = bs.next_int(4# bytes per second

ignore = bs.next_int(2# bytes per sample

bps = bs.next_int(2# bits per sample

while excess > 0:
ignore = bs.next_char()
excess -= 1

ignore = bs.next_string(4# "data"
ignore = bs.next_int(4# length of data

bytes_per_sample = bps // 8

samples = []
for channel in range(channels):
samples.append([])

while bs.has_next():
channel_i = 0
while channel_i < channels:
samples[channel_i].append(bs.next_int(bytes_per_sample))
channel_i += 1

s = Sound(len(samples[0]), sps, channels, bps)
s.samples = samples
return s

class Sound:

def __init__(self, sample_count, samples_per_second, channels, bits_per_sample):
self.sps = samples_per_second
self.bps = bits_per_sample
self.samples = []
for i in range(channels):
self.samples.append([0] * sample_count)

def save_to_file(self, filename):
output = []

push_string(output, "RIFF")

data = self.generate_data()

push_int(output, len(data) + 364)

output += data

c = open(filename, 'wb')
c.write(''.join(output))
c.close()

output = []

push_string(output, "WAVE")

push_string(output, "fmt ")

# Header size (16 bytes follow)
push_int(output, 164)

# 1 (to indicate PCM. Not sure what other valid values are)
push_int(output, 12)

# number of channels (stereo = 2, mono = 1)
push_int(output, len(self.samples), 2)

# samples per second
push_int(output, self.sps, 4)

# bytes per second (across all channels)
push_int(output, self.bps / 8 * len(self.samples) * self.sps, 4)

# bytes per sample across all channels
push_int(output, self.bps / 8 * len(self.samples), 2)

# bits per sample
push_int(output, self.bps, 2)

return output

def generate_data(self):
output = []

samples = self.samples
sample_i = 0
samples_length = len(self.samples[0])
num_channels = len(self.samples)
bps_total = self.bps / 8
while sample_i < samples_length:

channels_i = 0
while channels_i < num_channels:

push_int(output, self.samples[channels_i][sample_i], bps_total)

channels_i += 1

sample_i += 1

prefix = []
push_string(prefix, 'data')

# length of the rest of the data
push_int(prefix, len(output), 4)

return prefix + output

You are visitor #