Write numpy array to wave file in buffers using wave (not scipy.io.wavfile) module
Question: This caused me a day's worth of headache, but since I've figured it out I
wanted to post it somewhere in case it's helpful.

I am using python's wave module to write data to a wave file. I'm NOT using
scipy.io.wavfile because the data can be a huge vector (hours of audio at
16kHz) that I don't want to / can't load into memory all at once. My
understanding is that scipy.io.wavfile only gives you full-file interface,
while wave can allow you to read and write in buffers. I'd love to be
corrected on that if I'm wrong.

The problem I was running into comes down to how to convert the float data
into bytes for the wave.writeframes function. My data were not being written
in the correct order. This is because I was using the numpy.getbuffer()
function to convert the data into bytes, which does not respect the
orientation of the data:

    
    
    x0 = np.array([[0,1],[2,3],[4,5]],dtype='int8')
    x1 = np.array([[0,2,4],[1,3,5]],dtype='int8').transpose()
    if np.array_equal(x0, x1):
        print "Data are equal"
    else:
        print "Data are not equal"
    b0 = np.getbuffer(x0)
    b1 = np.getbuffer(x1)
    

result:

    
    
    Data are equal
    
    In [453]: [b for b in b0]
    Out[453]: ['\x00', '\x01', '\x02', '\x03', '\x04', '\x05']
    
    In [454]: [b for b in b1]
    Out[454]: ['\x00', '\x02', '\x04', '\x01', '\x03', '\x05']
    

I assume the order of bytes is determined by the initial allocation in memory,
as numpy.transpose() does not rewrite data but just returns a view. However
since this fact is buried by the interface to numpy arrays, debugging this
before knowing that this was the issue was a doozy.

A solution is to use numpy's tostring() function:

    
    
    s0 = x0.tostring()
    s1 = x1.tostring()
    In [455]: s0
    Out[455]: '\x00\x01\x02\x03\x04\x05'
    
    In [456]: s1
    Out[456]: '\x00\x01\x02\x03\x04\x05'
    

This is probably obvious to anyone who say the tostring() function first, but
somehow my search did not dig up any good documentation on how to format an
entire numpy array for wave file writing other than to use scipy.io.wavfile.
So here it is. Just for completion (note that "features" is originally
n_channels x n_samples, which is why I had this data order issue to begin
with:

    
    
    outfile = wave.open(output_file, mode='w')
    outfile.setnchannels(features.shape[0])
    outfile.setframerate(fs)
    outfile.setsampwidth(2)
    bytes = (features*(2**15-1)).astype('i2').transpose().tostring()
    outfile.writeframes(bytes)
    outfile.close()
    


Answer: For me `tostring` works fine. Note that in WAVE an 8-bit file must be signed,
whereas others (16- or 32-bit) must be unsigned.

Some dirty demo code that works for me:

    
    
    import wave
    import numpy as np
    
    SAMPLERATE=44100
    BITWIDTH=8
    CHANNELS=2
    
    def gensine(freq, dur):
        t = np.linspace(0, dur, round(dur*SAMPLERATE))
        x = np.sin(2.0*np.pi*freq*t)
        if BITWIDTH==8:
            x = x+abs(min(x))
            x = np.array( np.round( (x/max(x)) * 255) , dtype=np.dtype('<u1'))
        else:
            x = np.array(np.round(x * ((2**(BITWIDTH-1))-1)), dtype=np.dtype('<i%d' % (BITWIDTH/8)))
    
        return np.repeat(x,CHANNELS).reshape((len(x),CHANNELS))
    
    output_file="test.wav"
    
    outfile = wave.open(output_file, mode='wb')
    outfile.setparams((CHANNELS, BITWIDTH/8, SAMPLERATE, 0, 'NONE', 'not compressed'))
    outfile.writeframes(gensine(440, 1).tostring())
    outfile.writeframes(gensine(880, 1).tostring())
    outfile.close()
    

