Python SoundTouch Wrapper

SoundTouch is a very useful set of audio manipulation tools, with three powerful features:

  • Adjusting the pitch of a segment, without changing its tempo
  • Adjusting the tempo of a segment, without changing its pitch
  • Detecting the tempo of a segment, using beat detection

I used SoundTouch when I was developing CantoVario under the direction of Diana Dabby and using her algorithms for generating new music from existing music, using Lorenz attractors.  SoundTouch is a C++ library, but CantoVario was in python, so I built a wrapper for it.

Now you can use it too!  PySoundTouch, a python wrapper for the SoundTouch library is available on github!  It’s easy to use, especially with the super-cool AudioReader abstraction that I made with it.

AudioReader provides a single interface to any audio file (currently MP3, WAV, AIF, and AU files are supported).  Here’s an example of using AudioReader with the SoundTouch library:

# Open the file and convert it to have SoundTouch's required 2-byte samples
reader = AudioReader.open(srcpath)
reader2 = ConvertReader(reader, set_raw_width=2)

# Create the SoundTouch object and set the given shift
st = soundtouch.SoundTouch(reader2.sampling_rate(), reader2.channels())
st.set_pitch_shift(shift)

# Create the .WAV file to write the result to
writer = wave.open(dstpath, 'w')
writer.setnchannels(reader2.channels())
writer.setframerate(reader2.sampling_rate())
writer.setsampwidth(reader2.raw_width())

# Read values and feed them into SoundTouch
while True:
    data = reader2.raw_read()
    if not data:
        break

    print len(data)
    st.put_samples(data)

    while st.ready_count() > 0:
        writer.writeframes(st.get_samples(11025))

# Flush any remaining samples
waiting = st.waiting_count()
ready = st.ready_count()
flushed = ""

# Add silence until another chunk is pushed out
silence = array('h', [0] * 64)
while st.ready_count() == ready:
    st.put_samples(silence)

# Get all of the additional samples
while st.ready_count() > 0:
    flushed += st.get_samples(4000)

st.clear()

if len(flushed) > 2 * reader2.getnchannels() * waiting:
    flushed = flushed[0:(2 * reader2.getnchannels() * waiting)]

writer.writeframes(flushed)

# Clean up
writer.close()
reader2.close()

Leave a Reply

Your email address will not be published. Required fields are marked *