A Hopf, Skip and a Jump
Python examples

Some short Python examples to illustrate basic use of the getZ and absZ methods, the DetectorCache, the OnsetDetector and the FrequencyShifter.

Simple Example

''' Simple example to read in a file, create a DetectorBank with two octaves
of detectors and plot the abs(z) values.
'''
import numpy as np
import scipy.io.wavfile
from detectorbank import DetectorBank
import matplotlib.pyplot as plt
# read audio from file
# the sample rate should be 48000 or 44100
sr, audio = scipy.io.wavfile.read('file.wav')
if audio.dtype == 'int16':
audio = audio / 2**15
# array of frequencies (two octaves around A4)
f = np.array(list(440 * 2**(k/12) for k in range(-12, 13)))
# minimum bandwidth detectors
bandwidth = np.zeros(len(f))
# make 2D array of frequencies and bandwidths
det_char = np.array(list(zip(f, bandwidth)))
# detectorbank parameters
method = DetectorBank.runge_kutta
f_norm = DetectorBank.freq_unnormalized
a_norm = DetectorBank.amp_normalized
damping = 0.0001
gain = 25
# create DetectorBank object with sample rate sr, 32-bit float input audio,
# 4 threads, detector characteristics det_char, using Runge-Kutta, without
# frequency normalisation and with amplitude normalisation, damping = 0.0001
# and with a gain of 25
det = DetectorBank(sr, audio.astype(np.float32), 4, det_char,
method|f_norm|a_norm, damping, gain)
# create empty output array
z = np.zeros((len(f),len(audio)), dtype=np.complex128)
# fill z with detector output
det.getZ(z)
# fill new array r with the absolute values of the z array
r = np.zeros(z.shape)
m = det.absZ(r, z)
print('Max: {}'.format(m))
# plot the absZ values
t = np.linspace(0, r.shape[1]/sr, r.shape[1])
for k in range(r.shape[0]):
line, = plt.plot(t,r[k])
plt.show()

Using the DetectorCache

The previous example required all of the z values to be kept in RAM. For long audio files and/or many detectors, this is impractical. The DetectorCache enables you to store a number of short segments of abs(z) values. If you request a value not currently in the cache, old segments will be deleted and new ones created until the requested sample is reached. This means the DetectorCache can only go forwards: once a segment has been deleted, attempting to access values from it will cause an exception to be thrown. (For more information, please see SlidingBuffer.)

''' DetectorCache example: create a DetectorBank and use a DetectorCache to
access abs(z) values on demand.
'''
import numpy as np
import scipy.io.wavfile
from detectorbank import DetectorBank, Producer, DetectorCache
# read audio from file
# the sample rate should be 48000 or 44100
sr, audio = scipy.io.wavfile.read('file.wav')
if audio.dtype == 'int16':
audio = audio / 2**15
# array of frequencies (Hertz)
f = np.array(list(440 * 2 ** (k/12) for k in range(-5,5)))
bandwidth = np.zeros(len(f))
# change the bandwidth of the last detector
bandwidth[2] = 7
# make 2D array of frequencies and bandwidths
det_char = np.array(list(zip(f, bandwidth)))
# detectorbank parameters
method = DetectorBank.runge_kutta
f_norm = DetectorBank.freq_unnormalized
a_norm = DetectorBank.amp_normalized
damping = 0.0001
gain = 25
# construct DetectorBank
det = DetectorBank(sr, audio.astype(np.float32), 0, det_char,
method|f_norm|a_norm, damping, gain)
# make a producer
p = Producer(det)
# 2 segments, each containing sr (48000) abs(z) values
cache = DetectorCache(p, 2, sr)
# get the first abs(z) value from channel five
result = cache[5,0]
print(result)
# get the 100000th value from channel seven
# this causes a new segment to be generated (100000 > 2*sr)
result = cache[7,100000]
print(result)
# attempting to access a value from the (now discarded) first segment will
# cause the following exception to be thrown:
# IndexError: SlidingBuffer: Indexed item no longer available (underflow)
try:
result = cache[3,1000]
except IndexError as err:
print('Attempting to access a value from a discarded segment throws an exception:')
print('IndexError: {}'.format(err))

Saving and Loading DetectorBank Profiles

The DetectorBank has a facility to save itself, so it can be reloaded, instead of constructing a new DetectorBank with the same parameters.

import numpy as np
from detectorbank import DetectorBank
# array of frequencies (corresponding to 88 key piano)
f = np.array(list(440 * 2**(k/12) for k in range(-48, 40)))
bandwidth = np.zeros(len(f))
det_char = np.array(list(zip(f, bandwidth)))
# detectorbank parameters
sr = 48000
method = DetectorBank.runge_kutta
f_norm = DetectorBank.freq_unnormalized
a_norm = DetectorBank.amp_normalized
damping = 0.0001
gain = 25
# construct a DetectorBank, giving an empty array as input
det = DetectorBank(sr, np.zeros(1).astype(np.float32), 0, det_char,
method|f_norm|a_norm, damping, gain)
# save the DetectorBank as '88 key piano, RK4, f un, a nrm'
det.saveProfile('88 key piano, RK4, f un, a nrm')

This profile can then be loaded into a new program thus:

# load new audio file
sr, audio = scipy.io.wavfile.read('file.wav')
if audio.dtype == 'int16':
audio = audio / 2**15
# construct a DetectorBank from the saved profile
det = db.DetectorBank('88 key piano, RK4, f un, a nrm', audio.astype(np.float32))

Using the FrequencyShifter

This package also contains a FrequencyShifter. You probably won't need to use it in conjunction with a DetectorBank (it will be applied automatically, if necessary), but it may be useful in other applications, so here's a short usage example. (Full explantion of the operation of the FrequencyShifter can be found here.)

''' FrequencyShifter example: create a sine tone at a given frequency, then
modulate it and plot the power spectral density of both signals.
'''
import numpy as np
from detectorbank import FrequencyShifter
import matplotlib.pyplot as plt
sr = 48000
# original signal frquency
f0 = 10000
# amount by which to shift the signal
f_shift = -7000
# create sine tone
t = np.linspace(0, 2*np.pi*f0, sr)
signal = np.sin(t)
signal = signal.astype(np.float32)
# create empty array to be filled with the shifted signal
shifted = np.zeros(signal.shape, dtype=np.float32)
# make FrequencyShifter which uses FIR
fs = FrequencyShifter(signal, sr, FrequencyShifter.fir)
# shift the signal by the stated frequency
fs.shift(f_shift, shifted)
# the FrequencyShifter can be reused to modulate the same signal multiple times
#shifted2 = np.zeros(signal.shape, dtype=np.float32)
#fs.shift(5000, shifted2)
# plot power spectral density curves for the original and shifted signals
f, (ax1, ax2) = plt.subplots(2, sharex=True)
ax1.psd(signal, Fs=sr)
ax1.set_xlim(xmax=sr//2)
ax1.set_ylim(-120, -20)
ax1.set_xlabel('')
ax1.set_ylabel('dB/Hz')
ax2.psd(shifted, Fs=sr)
ax2.set_xlim(xmax=sr//2)
ax2.set_ylim(-120, -20)
ax2.set_ylabel('dB/Hz')
plt.show()