0

I am currently working on processing .wav files with python, using Pyaudio for streaming the audio, and the python wave library for loading the file data. I plan to later on include processing of the individual stereo channels, with regards to amplitude of the signal, and panning of the stereo signal, but for now i'm just trying to seperate the two channels of the wave file, and stitch them back together - Hopefully ending up with data that is identical to the input data.

Below is my code. The method getRawSample works perfectly fine, and i can stream audio through that function. The problem is my getSample method. Somewhere along the line, where i'm seperating the two channels of audio, and joining them back together, the audio gets distorted. I have even commented out the part where i do amplitude and panning adjustment, so in theory it's data in -> data out.
Below is an example of my code:

class Sample(threading.Thread) :

def __init__(self, filepath, chunk):
    super(Sample, self).__init__()
    self.CHUNK = chunk
    self.filepath = filepath
    self.wave = wave.open(self.filepath, 'rb')
    self.amp = 0.5 # varies from 0 to 1
    self.pan = 0 # varies from -pi to pi
    self.WIDTH = self.wave.getsampwidth()
    self.CHANNELS  = self.wave.getnchannels()
    self.RATE = self.wave.getframerate()
    self.MAXFRAMEFEEDS = self.wave.getnframes()/self.CHUNK  # maximum even number of chunks
    self.unpstr = '<{0}h'.format(self.CHUNK*self.WIDTH)  # format for unpacking the sample byte string
    self.pckstr = '<{0}h'.format(self.CHUNK*self.WIDTH)  # format for unpacking the sample byte string

    self.framePos = 0  # keeps track of how many chunks of data fed

#  panning and amplitude adjustment of input sample data

def panAmp(self, data, panVal, ampVal):  # when panning, using constant power panning
    [left, right] = self.getChannels(data)
    #left = np.multiply(0.5, left) #(np.sqrt(2)/2)*(np.cos(panVal) + np.sin(panVal))
    #right = np.multiply(0.5, right)  # (np.sqrt(2)/2)*(np.cos(panVal) - np.sin(panVal))
    outputList = self.combineChannels(left, right)
    dataResult = struct.pack(self.pckstr, *outputList)
    return dataResult

def getChannels(self, data):
    dataPrepare = list(struct.unpack(self.unpstr, data))
    left = dataPrepare[0::self.CHANNELS]
    right = dataPrepare[1::self.CHANNELS]
    return [left, right]

def combineChannels(self, left, right):
    stereoData = left
    for i in range(0, self.CHUNK/self.WIDTH):
        index = i*2+1
        stereoData = np.insert(stereoData, index, right[i*self.WIDTH:(i+1)*self.WIDTH])
    return stereoData

def getSample(self, panVal, ampVal):
    data = self.wave.readframes(self.CHUNK)
    self.framePos += 1
    if self.framePos > self.MAXFRAMEFEEDS:  # if no more audio samples to process
        self.wave.rewind()
        data = self.wave.readframes(self.CHUNK)
        self.framePos = 1
    return self.panAmp(data, panVal, ampVal)

def getRawSample(self):  # for debugging, bypasses pan and amp functions
    data = self.wave.readframes(self.CHUNK)
    self.framePos += 1
    if self.framePos > self.MAXFRAMEFEEDS:  # if no more audio samples to process
        self.wave.rewind()
        data = self.wave.readframes(self.CHUNK)
        self.framePos = 1
    return data

i am suspecting that the error is in the way that i stitch together the left and right channel, but not sure. I load the project with 16 bit 44100khz .wav files. Below is a link to an audio file so that you can hear the resulting audio output. The first part is running two files (both two channel) through the getSample method, while the next part is running those same files, through the getRawSample method.

https://dl.dropboxusercontent.com/u/24215404/pythonaudiosample.wav

Basing on the audio, as said earlier, it seems like the stereo file gets distorted. Looking at the waveform of above file, it seems as though the right and left channels are exactly the same after going through the getSample method.

If needed, i can also post my code including the main function. Hopefully my question isn't too vague, but i am grateful for any help or input!

Morten B.
  • 93
  • 8

1 Answers1

1

As it so often happens, i slept on it, and woke up the next day with a solution.
The problem was in the combineChannels function. Following is the working code:

   def combineChannels(self, left, right):
    stereoData = left
    for i in range(0, self.CHUNK):
        index = i*2+1
        stereoData = np.insert(stereoData, index, right[i:(i+1)])
    return stereoData

The changes are

  • For loop bounds: as i have 1024 items (the same as my chunk size) in the lists left and right, i ofcourse need to iterate through every one of those.
  • index: the index definition remains the same
  • stereoData: Again, here i remember that im working with lists, each containing a frame of audio. The code in the question assumes that my list is stored as a bytestring, but this is ofcourse not the case. And as you see, the resulting code is much simpler.
Morten B.
  • 93
  • 8