How do you separate each channel of a two channel wav file into two different files using wavio? or another library?

14,498

Solution 1

I don't know what is wrong with the wavio code, but here's how you can separate WAV channel using standard Python module wave (which is also used by wavio) and numpy:

import wave
import numpy as np

def save_wav_channel(fn, wav, channel):
    '''
    Take Wave_read object as an input and save one of its
    channels into a separate .wav file.
    '''
    # Read data
    nch   = wav.getnchannels()
    depth = wav.getsampwidth()
    wav.setpos(0)
    sdata = wav.readframes(wav.getnframes())

    # Extract channel data (24-bit data not supported)
    typ = { 1: np.uint8, 2: np.uint16, 4: np.uint32 }.get(depth)
    if not typ:
        raise ValueError("sample width {} not supported".format(depth))
    if channel >= nch:
        raise ValueError("cannot extract channel {} out of {}".format(channel+1, nch))
    print ("Extracting channel {} out of {} channels, {}-bit depth".format(channel+1, nch, depth*8))
    data = np.fromstring(sdata, dtype=typ)
    ch_data = data[channel::nch]

    # Save channel to a separate file
    outwav = wave.open(fn, 'w')
    outwav.setparams(wav.getparams())
    outwav.setnchannels(1)
    outwav.writeframes(ch_data.tostring())
    outwav.close()

wav = wave.open(WAV_FILENAME)
save_wav_channel('ch1.wav', wav, 0)
save_wav_channel('ch2.wav', wav, 1)

Solution 2

Try ffmpeg on the command line:

Output each channel in stereo input to individual mono files:

ffmpeg -i stereo.wav -map_channel 0.0.0 left.wav -map_channel 0.0.1 right.wav

I tried it on your file and it seems to work.

If you need it to be in a python program just use os.system(cmd)

Solution 3

I think that you are trying to make it much more complicated than it needs to be. If you don't mind using scipy than separating the channels comes down to:

from scipy.io import wavfile

fs, data = wavfile.read('guitarup_full.wav')            # reading the file

wavfile.write('guitar_channel_1.wav', fs, data[:, 0])   # saving first column which corresponds to channel 1
wavfile.write('guitar_channel_2.wav', fs, data[:, 1])   # saving second column which corresponds to channel 2

Solution 4

Method 1 (Using ffmpeg)

You can use ffmpeg to split a n-channeled .wav like this:

ffmpeg -i input.wav -map 0:1 1.wav -map 0:2 2.wav -map 0:3 3.wav........

You can run this is python using:

import os
os.system(ffmpeg -i 14ch.mov -map 0:1 1.wav -map 0:2 2.wav -map 0:3 3.wav........)

Method 2(Using sox)

Install sox from here

Then try this:

sox infile.wav outfile_left.wav remix 1
sox infile.wav outfile_right.wav remix 2

Again you can run this in python using os.system

Solution 5

Thanks to everyone for taking the time to read and run my scripts. I finally found a solution. Follow the steps in this order:

First, uninstall wavio by:

pip uninstall wavio

Second, uninstall numpy too:

pip uninstall numpy

Finally, install wavio again. numpy will be installed as a dependency:

pip install wavio

numpy will be installed as a dependency.

Installing collected packages: numpy, wavio
Successfully installed numpy-1.14.5 wavio-0.0.4

Then my original script will deliver the solution needed.

The use of ffmpeg as @Marichyasana suggested is a good workaround. However python libraries (such as pyglet, python audio tools, etc) have problems reading these output files back.

I hope people find this solution helpful !

Share:
14,498

Related videos on Youtube

mm_
Author by

mm_

One part scientist, one part entrepreneur, one part programmer.

Updated on September 15, 2022

Comments

  • mm_
    mm_ almost 2 years

    The following script plays the original file ok. I try to separate each channel in the obvious way, but it does not work.

    import os
    import wavio
    import numpy
    import pyglet
    
    file_name = "guitarup_full.wav"
    
    # I get the file !
    File = wavio.read(file_name)
    rate = File.rate
    # it looks good
    print File.data.shape
    print rate
    # and then the channels:
    channel_1 = File.data[:,0]
    channel_2 = File.data[:,1]
    wavio.write("guitar_channel_1.wav", channel_1, rate )
    wavio.write("guitar_channel_2.wav", channel_2, rate )
    
    # now we try to play:
    source =  pyglet.resource.media(file_name, streaming=False)
    source_ch1 =  pyglet.resource.media("guitar_channel_1.wav", streaming=False)
    source_ch2 =  pyglet.resource.media("guitar_channel_2.wav", streaming=False)
    
    #uncomment the one you want to listen.
    source.play()
    #source_ch1.play()
    #source_ch2.play()
    
    pyglet.app.run()
    

    The first sounds like a guitar, the second and third like Gaussian noise. Can someone tell me what is wrong with it?

    The audio file I used is: https://www.freesounds.info/global/wav.php?fileid=11

    The shape of the data is: (88471, 2) rate is: 44100

    Also, if I play the file in another player, I get the same: Gaussian Noise.

    Note: The use of pyglet is superfluous for the problem. If you use it to investigate this issue, make sure the files are in a folder registered in the resources folder. To do that:

    pyglet.resource.path.append("your_sounds_location")
    pyglet.resource.reindex()
    
  • mm_
    mm_ almost 6 years
    did you check this script with the sound file I sent? still Gaussian noise.
  • Andriy Makukha
    Andriy Makukha almost 6 years
    @mm_, I did. And it worked well for me. What is the size of the files that you get?
  • Andriy Makukha
    Andriy Makukha almost 6 years
    Just confirmed: it works well with Python 2.x as well as Python 3. Numpy versions are 1.11.2 and 1.14.0.
  • mm_
    mm_ almost 6 years
    numpy version '1.14.5', python 2.7.15rc1, File.data.shape = (88471, 2), not working for me.
  • Andriy Makukha
    Andriy Makukha almost 6 years
    @mm_, I mean, what are the sizes of resulting files? They are named ch1.wav and ch2.wav in my script.
  • Andriy Makukha
    Andriy Makukha almost 6 years
    Oh, the sizes are actually not helpful because they are the same as produced by your script. md5sum's of the correct resulting files are a00c..5c75 and a934..ff63. md5sum of the input file is c98f..3a12. MD5 hashes of the files produced by your wavio code are 8c84..ee17 and 3827..c478. They are the same for both versions of Python on my machine.
  • mm_
    mm_ almost 6 years
    you might had hit the core of the problem. "md5sum's of the correct resulting files are a00c..5c75 and a934..ff63. md5sum of the input file is c98f..3a12. MD5 hashes of the files produced by your wavio code are 8c84..ee17 and 3827..c478." what do you mean?
  • mm_
    mm_ almost 6 years
    This is a great work around. However I ultimately want to manipulate the data or each channel. This method does not tell me how each channel is assembled from the original.
  • mm_
    mm_ almost 6 years
    more importantly, how can I use md5sum to solve my problem?
  • Andriy Makukha
    Andriy Makukha almost 6 years
    Your script writes file guitar_channel_1.wav to your machine. You can run md5sum guitar_channel_1.wav on Linux to find the MD5 hash. My script writes file ch1.wav. You can run md5sum ch1.wav to find the MD5 hash. This will show whether your machine produces the correct files and explain why this code doesn't work for you.
  • mm_
    mm_ almost 6 years
    I see. What can I do with this info? How can I produce the correct file?
  • mm_
    mm_ almost 6 years
    Thanks for your answer. Unfortunately, I can't open the left.wav and right.wav with pyglet, don't know why. Do you know if ffmpeg return compressed wav files? Can I specify the output format explicitly?
  • mm_
    mm_ almost 6 years
    The output are sound files alright, but not uncompressed wav files. How can I indicate the output format? the documentation ffmpeg.org/ffmpeg.html#Video-and-Audio-grabbing is not clear on this point unfortunately.
  • Andriy Makukha
    Andriy Makukha almost 6 years
    I also cannot play the resulting WAV files from the ffmpeg command with pyglet. I get en exception pyglet.media.sources.riff.WAVEFormatException: Not a WAVE file. I think, pyglet just cannot recognize the WAV file header because ffmpeg adds some metadata. But the files are certainly uncompressed .wav and can be read by Python's wave (as in my script). Maybe there is an option to avoid adding metadata to WAV?
  • Andriy Makukha
    Andriy Makukha almost 6 years
    It's a standard debugging process. We cannot tell why this Python code doesn't work for you if we don't know what's going on on your machine. Meanwhile I checked again: my script works correctly on another machine with three different Python versions.
  • mm_
    mm_ almost 6 years
    Just tested this method. It works of course. For my app I need both the arrays and files.
  • machnic
    machnic almost 6 years
    Then probably it's the simplest solution you can get. You have both channels stored as columns inside data (numpy array) and you can change them in any way you need - before of after saving
  • mm_
    mm_ almost 6 years
    you are right. That's how I implemented my solution.
  • ERJAN
    ERJAN almost 4 years
    @machnic, how to send that channel as data to another function without saving? pls help
  • machnic
    machnic almost 4 years
    @ERJAN Exactly the same way as in the example above. You can use variable data with any function you need. I'm not sure what's unclear about it.