Skip to content

Delayline Pitchshifter

March 2, 2012

his note discusses a delayline pitchshifter that can be used to transpose sounds with small amounts of shift and is quite effective. It is based on a circular delay line and two variable taps that read the buffer at different ‘speeds’ depending on the amount of pitchshift required.  For better results on monophonic pitched sounds, if we know the original pitch, we can set the delayline to match the fundamental period and this will reduce the artifacts introduced by the shift.

Again, we’ll be using Python and its scientific libraries, which provide a nice environment for studying these processes:

from pylab import *
from scipy.io import wavfile

The whole process is placed inside a function that takes in some input parameters, signal arrays for in and out; pitch; and delay time in samples. It produces an output signal array. This function sets a delay line with two variable taps, and uses a triangular (Bartlett) window to fade between the taps:

def pitchshifter(sigin,sigout,pitch,deltime):

size = deltime       # delay time in samples
delay = zeros(size) # delay line
env = bartlett(size)   # fade envelope table
tap1 = 0            # tap positions
tap2 = size/2

wp = 0              # write pos

The processing code then loops over the input signal, filling the delay and then reading it using two taps:

 for i in range(0, len(sigin)):

delay = sigin[i]   # fill the delay line

# first tap, linear interp readout
frac = tap1 – int(tap1)
if tap1 < size – 1 : delaynext = delay[tap1+1]
else: delaynext = delay[0]
sig1  =  delay[int(tap1)] + frac*(delaynext – delay[int(tap1)])

# second tap, linear interp readout
frac = tap2 – int(tap2)
if tap2  < size – 1 : delaynext = delay[tap2+1]
else: delaynext = delay[0]
sig2  =  delay[int(tap2)] + frac*(delaynext – delay[int(tap2)])

The tap signals are faded using the triangular window. The position of the fade envelopes is based on the difference between the tap and the delay write position (the envelope should be 0 at this position and max when the tap is furthest away from the write position). This envelope is crucial to avoid the discontinuity that happens when the taps ‘overtake or are overtaken by’ the write pointer.

    # fade envelope positions

ep1 = tap1 – wp
if ep1 < 0: ep1 += size
ep2 = tap2 – wp
if ep2 < 0: ep2 += size

# combine tap signals

sigout[i] = env[ep1]*sig1 + env[ep2]*sig2

Finally we increment the taps by the required pitch transposition ratio, and wrap around the circular delay buffer. At the end we also increment the writer pointer by 1 position:

    # increment tap pos according to pitch transposition

tap1 += pitch
tap2 = tap1 + size/2

# keep tap pos within the delay memory bounds

while tap1 >= size: tap1 -= size
while tap1 < 0: tap1 += size

while tap2 >= size: tap2 -= size
while tap2 < 0: tap2 += size

# increment write pos

wp += 1
if wp == size: wp = 0

The function returns the output signal array, now filled with the transposed audio.

 return sigout

A short harmoniser program can be written like this. We take an input file, a transposition pitch in 12TET semitones, create an array to hold the output and pitch shift the signal. The delay size is set to be twice the fundamental period of the input signal (this is set by hand, here we are setting the fundamental frequency to 131 Hz, C2). This is to make sure the taps are space by one fundamental period. The output is made up of a mix of the original and the transposed signals:

(sr,signalin) = wavfile.read(sys.argv[2])
pitch = 2.**(float(sys.argv[1])/12.)
signalout = zeros(len(signalin))
fund = 131.
dsize = int(sr/(fund*0.5))
signalout = pitchshifter(signalin,signalout,pitch,dsize)
wavfile.write(sys.argv[3],sr,array((signalout+signalin)/2., dtype=’int16′))

Finally, the program could be completed by an automatic pitch tracking element, setting the delays to accommodate changes in the input. That would make a nice exercise for anyone studying this code.

Here is the full program:


from pylab import *
from scipy.io import wavfile

def pitchshifter(sigin,sigout,pitch,deltime):

size = deltime       # delay time in samples
delay = zeros(size) # delay line
env = bartlett(size)   # fade envelope table
tap1 = 0            # tap positions
tap2 = size/2
wp = 0              # write pos


for i in range(0, len(sigin)):

delay = sigin[i]   # fill the delay line
# first tap, linear interp readout
frac = tap1 - int(tap1)
if tap1 < size - 1 : delaynext = delay[tap1+1]
else: delaynext = delay[0]
sig1  =  delay[int(tap1)] + frac*(delaynext - delay[int(tap1)])
# second tap, linear interp readout
frac = tap2 - int(tap2)
if tap2 < size - 1 : delaynext = delay[tap2+1]
else: delaynext = delay[0]
sig2  =  delay[int(tap2)] + frac*(delaynext - delay[int(tap2)])
# fade envelope positions
ep1 = tap1 - wp
if ep1 < 0: ep1 += size
ep2 = tap2 - wp
if ep2 <  0: ep2 += size


# combine tap signals
sigout[i] = env[ep1]*sig1 + env[ep2]*sig2
# increment tap pos according to pitch transposition
tap1 += pitch
tap2 = tap1 + size/2


# keep tap pos within the delay memory bounds
while tap1 >= size: tap1 -= size
while tap1 < 0: tap1 += size


while tap2 >= size: tap2 -= size
while tap2 < 0: tap2 += size


# increment write pos
wp += 1
if wp == size: wp = 0

return sigout

(sr,signalin) = wavfile.read(sys.argv[2])
pitch = 2.**(float(sys.argv[1])/12.)
signalout = zeros(len(signalin))


fund = 131.
dsize = int(sr/(fund*0.5))
print dsize
signalout = pitchshifter(signalin,signalout,pitch,dsize)
wavfile.write(sys.argv[3],sr,array((signalout+signalin)/2., dtype='int16'))

Advertisements
4 Comments
  1. kyle permalink

    File “pitchshift.py”, line 66, in
    signalout = pitchshifter(signalin,signalout,pitch,dsize)
    File “pitchshift.py”, line 16, in pitchshifter
    delay[wp] = sigin[i] # fill the delay line
    ValueError: setting an array element with a sequence.

    • you are probably trying to use a stereo file as input, I think. Try with a mono file. I should have mentioned this. Stereo files have pairs instead of single elements in the numpy arrays returned by the wav reading function.

  2. Have you ever considered publishing an e-book or
    guest authoring on other websites? I have a blog centered on the same ideas you discuss and would really like to have you share some stories/information.
    I know my viewers would enjoy your work. If you’re even remotely interested, feel free to send me an email.

    • well, yes, but that’s dependent on my time. As you can probably see, I am only blogging on/off, as time permits. But I am happy to talk, you can drop me an e-mail if you’d like (my address is easily googleable).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: