12

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()
Andriy Makukha
  • 7,580
  • 1
  • 38
  • 49
mm_
  • 1,566
  • 2
  • 18
  • 37
  • As a side note: since `data` is already a NumPy array, you don't need to write a list comprehension and build an array out of the result; just do `channel_1 = File.data[:,0]`. – abarnert Jul 11 '18 at 01:02
  • Meanwhile, can you tell us what WAV format the file is in? Rate, sampwidth, channels, whatever? Are you even sure it's an uncompressed PCM int WAV file? If you play the saved files in an audio player instead of via pyglet, do you get the same problem? – abarnert Jul 11 '18 at 01:09
  • If I play the file guitar_channel_1.wav and guitar_channel_2.wav in a regular player, I get the same: gaussian noise. Also, itś a non compressed wav file. – mm_ Jul 11 '18 at 02:43
  • I run your code and it produces valid WAVs. Try reinstalling `wavio` – igrinis Jul 18 '18 at 07:23
  • already re-installed wavio, python, etc. – mm_ Jul 18 '18 at 15:28

5 Answers5

14

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)
Andriy Makukha
  • 7,580
  • 1
  • 38
  • 49
  • did you check this script with the sound file I sent? still Gaussian noise. – mm_ Jul 13 '18 at 15:59
  • @mm_, I did. And it worked well for me. What is the size of the files that you get? – Andriy Makukha Jul 13 '18 at 16:07
  • Just confirmed: it works well with Python 2.x as well as Python 3. Numpy versions are 1.11.2 and 1.14.0. – Andriy Makukha Jul 13 '18 at 16:38
  • numpy version '1.14.5', python 2.7.15rc1, File.data.shape = (88471, 2), not working for me. – mm_ Jul 14 '18 at 02:42
  • @mm_, I mean, what are the sizes of resulting files? They are named `ch1.wav` and `ch2.wav` in my script. – Andriy Makukha Jul 14 '18 at 03:44
  • 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. – Andriy Makukha Jul 14 '18 at 04:06
  • 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_ Jul 14 '18 at 17:35
  • more importantly, how can I use md5sum to solve my problem? – mm_ Jul 14 '18 at 17:46
  • 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. – Andriy Makukha Jul 15 '18 at 07:36
  • I see. What can I do with this info? How can I produce the correct file? – mm_ Jul 16 '18 at 16:12
  • 3
    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. – Andriy Makukha Jul 16 '18 at 19:09
11

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
machnic
  • 2,304
  • 2
  • 17
  • 21
  • Just tested this method. It works of course. For my app I need both the arrays and files. – mm_ Jul 18 '18 at 21:00
  • 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 – machnic Jul 18 '18 at 21:10
  • you are right. That's how I implemented my solution. – mm_ Jul 21 '18 at 18:14
  • @machnic, how to send that channel as data to another function without saving? pls help – ERJAN Aug 19 '20 at 03:29
  • @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. – machnic Aug 19 '20 at 10:09
10

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)

Marichyasana
  • 2,966
  • 1
  • 19
  • 20
  • 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_ Jul 14 '18 at 17:45
  • 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_ Jul 16 '18 at 17:30
  • 1
    The output are sound files alright, but not uncompressed wav files. How can I indicate the output format? the documentation https://www.ffmpeg.org/ffmpeg.html#Video-and-Audio-grabbing is not clear on this point unfortunately. – mm_ Jul 16 '18 at 17:49
  • 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 Jul 16 '18 at 18:58
3

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

Agile_Eagle
  • 1,710
  • 3
  • 13
  • 32
2

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 !

mm_
  • 1,566
  • 2
  • 18
  • 37