There are a number of design options to match more arbitrary frequency band specifications than the simple lowpass/bandpass/bandstop/hipass classification. The most common ones are :
The first two are readily available from scipy.signal
as scipy.firls
and scipy.remez
respectively, whereas the third can be done by taking the inverse FFT of the sampled frequency response curve.
Sample implementation using the least-square method:
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
bands = [0, fL1, fL2, fH1, fH2, fs/2]
desired = [Bl, Bl, 1, 1, Bh, Bh]
y = signal.firls(numtaps, bands, desired, fs=fs)
f,h = signal.freqz(y, fs=fs)
plt.plot(bands, desired, 'k', linewidth=2)
plt.plot(f, np.abs(h), 'r')
Sample implementation using the Parks-McClellan algorithm:
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
bands = [0, fL1, fL2, fH1, fH2, fs/2]
desired = [Bl, 1, Bh]
y = signal.remez(numtaps, bands, desired, fs=fs)
f,h = signal.freqz(y, fs=fs)
plt.plot(bands, desired, 'k', linewidth=2)
plt.plot(f, np.abs(h), 'b')
Sample implementation using the frequency sampling method:
from scipy import interpolate, fft
import matplotlib.pyplot as plt
import numpy as np
# interpolate desired response at equally spaced frequency points
interpolator = interpolate.interp1d(bands, desired)
N = 1024
fsampling = np.linspace(0, fs/2, N)
sampled = interpolator(fsampling)
# take the inverse FFT
y = fft.fftshift(fft.irfft(sampled))
# truncate response to keep numtaps coefficients
n1 = N-numtaps//2
n2 = n1 + numtaps
y = y[n1:n2]
For sake of illustration, using the following set of parameters
numtaps = 19
fL1 = 100
fL2 = 200
fH1 = 500
fH2 = 600
fs = 1600
Bl = 2
Bh = 2.3
would yield the corresponding designs:

Note that if your frequency specification curves are do-not-exceed values (rather than nominal ones), you can still use the above methods in an iterative fashion each time tweaking the bands
and desired
parameters until you converge to an acceptable response. With the initial set of parameters above, using the least-squares method, you could end up with tweaked parameters such as
bands = [0, 100, 180, 520, 600, fs/2]
desired = [1.966,1.966,0.980,0.980,2.225,2.225]
with the corresponding response:
