1
votes

I'm trying to open a stereo stream and convert it to mono, using the wave module in python. So far I was able to write a single (left or right) channel from a 16bit stereo little endian file:

LEFT, RIGHT = 0, 1
def mono_single(cont, chan=LEFT):
    a = iter(cont)
    mono_cont = ''
    if chan:
        a.next(); a.next()
    while True:
        try:                          
            mono_cont += a.next() + a.next()
            a.next(); a.next()
        except StopIteration:
            return mono_cont

stereo = wave.open('stereofile.wav', 'rb')
mono = wave.open('monofile.wav', 'wb')
mono.setparams(stereo.getparams())
mono.setnchannels(1)
mono.writeframes(mono_single(stereo.readframes(stereo.getnframes())))
mono.close()

This works as expected. The problem comes when I try to downmix the two stereo channels to a single mono channel. I thought that a simple average between left and right would have been enough, and this is what I tried so far:

def mono_mix(cont):
    a = iter(cont)
    mono_cont = ''
    while True:
        try:
            left = ord(a.next()) + (ord(a.next()) << 8)
            right = ord(a.next()) + (ord(a.next()) << 8)
            value = (left + right) / 2
            mono_cont += chr(value & 255) + chr(value >> 8)
        except StopIteration:
            return mono_cont

stereo = wave.open('stereofile.wav', 'rb')
mono = wave.open('monofile.wav', 'wb')
mono.setparams(stereo.getparams())
mono.setnchannels(1)
mono.writeframes(mono_mix(stereo.readframes(stereo.getnframes())))
mono.close()

What I get from this is a "crackled" version of the source. I tried different combinations (I might have misunderstood the whole endianness thing), but with no luck so far.

2

2 Answers

2
votes

Are you open to using external libraries?
if yes, you can do this easily with pydub very few lines as follows,

from pydub import AudioSegment
mysound = AudioSegment.from_wav("/input_path/infile.wav")
mysound = mysound.set_channels(1)
mysound.export("/output_path/outfile.wav", format="wav")
2
votes

Turns out like I didn't know about the audioop built-in module (thanks to Anil_M's answer. Also, I was wrong on both converting the stereo format and writing (I should have used struct).

This makes everything absolutely easier:

stereo = wave.open('stereofile.wav', 'rb')
mono = wave.open('monofile.wav', 'wb')
mono.setparams(stereo.getparams())
mono.setnchannels(1)
mono.writeframes(audioop.tomono(stereo.readframes(float('inf')), stereo.getsampwidth(), 1, 1))
mono.close()

Then you can select a single channel by modifying the latest 2 parameters (1, 0 for left, 0, 1 for right) or even using 1.414 for equal power instead of equal amplitude