annotate utils/filters.py @ 19:890cfe424f4a tip

added annotations
author mitian
date Fri, 11 Dec 2015 09:47:40 +0000
parents 26838b1f560f
children
rev   line source
mi@0 1 # Copyright 2014 Jason Heeris, jason.heeris@gmail.com
mi@0 2 #
mi@0 3 # This file is part of the gammatone toolkit, and is licensed under the 3-clause
mi@0 4 # BSD license: https://github.com/detly/gammatone/blob/master/COPYING
mi@0 5 """
mi@0 6 This module contains functions for constructing sets of equivalent rectangular
mi@0 7 bandwidth gammatone filters.
mi@0 8 """
mi@0 9 from __future__ import division
mi@0 10 from collections import namedtuple
mi@0 11
mi@0 12 import numpy as np
mi@0 13 import scipy as sp
mi@0 14 from scipy import signal as sgn
mi@0 15
mi@0 16 DEFAULT_FILTER_NUM = 100
mi@0 17 DEFAULT_LOW_FREQ = 100
mi@0 18 DEFAULT_HIGH_FREQ = 44100/4
mi@0 19
mi@0 20
mi@0 21 def erb_point(low_freq, high_freq, fraction):
mi@0 22 """
mi@0 23 Calculates a single point on an ERB scale between ``low_freq`` and
mi@0 24 ``high_freq``, determined by ``fraction``. When ``fraction`` is ``1``,
mi@0 25 ``low_freq`` will be returned. When ``fraction`` is ``0``, ``high_freq``
mi@0 26 will be returned.
mi@0 27
mi@0 28 ``fraction`` can actually be outside the range ``[0, 1]``, which in general
mi@0 29 isn't very meaningful, but might be useful when ``fraction`` is rounded a
mi@0 30 little above or below ``[0, 1]`` (eg. for plot axis labels).
mi@0 31 """
mi@0 32 # Change the following three parameters if you wish to use a different ERB
mi@0 33 # scale. Must change in MakeERBCoeffs too.
mi@0 34 # TODO: Factor these parameters out
mi@0 35 ear_q = 9.26449 # Glasberg and Moore Parameters
mi@0 36 min_bw = 24.7
mi@0 37 order = 1
mi@0 38
mi@0 39 # All of the following expressions are derived in Apple TR #35, "An
mi@0 40 # Efficient Implementation of the Patterson-Holdsworth Cochlear Filter
mi@0 41 # Bank." See pages 33-34.
mi@0 42 erb_point = (
mi@0 43 -ear_q*min_bw
mi@0 44 + np.exp(
mi@0 45 fraction * (
mi@0 46 -np.log(high_freq + ear_q*min_bw)
mi@0 47 + np.log(low_freq + ear_q*min_bw)
mi@0 48 )
mi@0 49 ) *
mi@0 50 (high_freq + ear_q*min_bw)
mi@0 51 )
mi@0 52
mi@0 53 return erb_point
mi@0 54
mi@0 55
mi@0 56 def erb_space(
mi@0 57 low_freq=DEFAULT_LOW_FREQ,
mi@0 58 high_freq=DEFAULT_HIGH_FREQ,
mi@0 59 num=DEFAULT_FILTER_NUM):
mi@0 60 """
mi@0 61 This function computes an array of ``num`` frequencies uniformly spaced
mi@0 62 between ``high_freq`` and ``low_freq`` on an ERB scale.
mi@0 63
mi@0 64 For a definition of ERB, see Moore, B. C. J., and Glasberg, B. R. (1983).
mi@0 65 "Suggested formulae for calculating auditory-filter bandwidths and
mi@0 66 excitation patterns," J. Acoust. Soc. Am. 74, 750-753.
mi@0 67 """
mi@0 68 return erb_point(
mi@0 69 low_freq,
mi@0 70 high_freq,
mi@0 71 np.arange(1, num+1)/num
mi@0 72 )
mi@0 73
mi@0 74
mi@0 75 def centre_freqs(high_freq, num_freqs, cutoff):
mi@0 76 """
mi@0 77 Calculates an array of centre frequencies (for :func:`make_erb_filters`)
mi@0 78 from a sampling frequency, lower cutoff frequency and the desired number of
mi@0 79 filters.
mi@0 80
mi@0 81 :param fs: sampling rate
mi@0 82 :param num_freqs: number of centre frequencies to calculate
mi@0 83 :type num_freqs: int
mi@0 84 :param cutoff: lower cutoff frequency
mi@0 85 :return: same as :func:`erb_space`
mi@0 86 """
mi@0 87 # print 'center freq', erb_space(cutoff, fs/2, num_freqs)
mi@0 88 return erb_space(cutoff, high_freq, num_freqs)
mi@0 89
mi@0 90
mi@0 91 def make_erb_filters(fs, centre_freqs, width=1.0):
mi@0 92 """
mi@0 93 This function computes the filter coefficients for a bank of
mi@0 94 Gammatone filters. These filters were defined by Patterson and Holdworth for
mi@0 95 simulating the cochlea.
mi@0 96
mi@0 97 The result is returned as a :class:`ERBCoeffArray`. Each row of the
mi@0 98 filter arrays contains the coefficients for four second order filters. The
mi@0 99 transfer function for these four filters share the same denominator (poles)
mi@0 100 but have different numerators (zeros). All of these coefficients are
mi@0 101 assembled into one vector that the ERBFilterBank can take apart to implement
mi@0 102 the filter.
mi@0 103
mi@0 104 The filter bank contains "numChannels" channels that extend from
mi@0 105 half the sampling rate (fs) to "lowFreq". Alternatively, if the numChannels
mi@0 106 input argument is a vector, then the values of this vector are taken to be
mi@0 107 the center frequency of each desired filter. (The lowFreq argument is
mi@0 108 ignored in this case.)
mi@0 109
mi@0 110 Note this implementation fixes a problem in the original code by
mi@0 111 computing four separate second order filters. This avoids a big problem with
mi@0 112 round off errors in cases of very small cfs (100Hz) and large sample rates
mi@0 113 (44kHz). The problem is caused by roundoff error when a number of poles are
mi@0 114 combined, all very close to the unit circle. Small errors in the eigth order
mi@0 115 coefficient, are multiplied when the eigth root is taken to give the pole
mi@0 116 location. These small errors lead to poles outside the unit circle and
mi@0 117 instability. Thanks to Julius Smith for leading me to the proper
mi@0 118 explanation.
mi@0 119
mi@0 120 Execute the following code to evaluate the frequency response of a 10
mi@0 121 channel filterbank::
mi@0 122
mi@0 123 fcoefs = MakeERBFilters(16000,10,100);
mi@0 124 y = ERBFilterBank([1 zeros(1,511)], fcoefs);
mi@0 125 resp = 20*log10(abs(fft(y')));
mi@0 126 freqScale = (0:511)/512*16000;
mi@0 127 semilogx(freqScale(1:255),resp(1:255,:));
mi@0 128 axis([100 16000 -60 0])
mi@0 129 xlabel('Frequency (Hz)'); ylabel('Filter Response (dB)');
mi@0 130
mi@0 131 | Rewritten by Malcolm Slaney@Interval. June 11, 1998.
mi@0 132 | (c) 1998 Interval Research Corporation
mi@0 133 |
mi@0 134 | (c) 2012 Jason Heeris (Python implementation)
mi@0 135 """
mi@0 136 T = 1/fs
mi@0 137 # Change the followFreqing three parameters if you wish to use a different
mi@0 138 # ERB scale. Must change in ERBSpace too.
mi@0 139 # TODO: factor these out
mi@0 140 ear_q = 9.26449 # Glasberg and Moore Parameters
mi@0 141 min_bw = 24.7
mi@0 142 order = 1
mi@0 143
mi@0 144 erb = width*((centre_freqs/ear_q)**order + min_bw**order)**(1/order)
mi@0 145 B = 1.019*2*np.pi*erb
mi@0 146
mi@0 147 arg = 2*centre_freqs*np.pi*T
mi@0 148 vec = np.exp(2j*arg)
mi@0 149
mi@0 150 A0 = T
mi@0 151 A2 = 0
mi@0 152 B0 = 1
mi@0 153 B1 = -2*np.cos(arg)/np.exp(B*T)
mi@0 154 B2 = np.exp(-2*B*T)
mi@0 155
mi@0 156 rt_pos = np.sqrt(3 + 2**1.5)
mi@0 157 rt_neg = np.sqrt(3 - 2**1.5)
mi@0 158
mi@0 159 common = -T * np.exp(-(B * T))
mi@0 160
mi@0 161 # TODO: This could be simplified to a matrix calculation involving the
mi@0 162 # constant first term and the alternating rt_pos/rt_neg and +/-1 second
mi@0 163 # terms
mi@0 164 k11 = np.cos(arg) + rt_pos * np.sin(arg)
mi@0 165 k12 = np.cos(arg) - rt_pos * np.sin(arg)
mi@0 166 k13 = np.cos(arg) + rt_neg * np.sin(arg)
mi@0 167 k14 = np.cos(arg) - rt_neg * np.sin(arg)
mi@0 168
mi@0 169 A11 = common * k11
mi@0 170 A12 = common * k12
mi@0 171 A13 = common * k13
mi@0 172 A14 = common * k14
mi@0 173
mi@0 174 gain_arg = np.exp(1j * arg - B * T)
mi@0 175
mi@0 176 gain = np.abs(
mi@0 177 (vec - gain_arg * k11)
mi@0 178 * (vec - gain_arg * k12)
mi@0 179 * (vec - gain_arg * k13)
mi@0 180 * (vec - gain_arg * k14)
mi@0 181 * ( T * np.exp(B*T)
mi@0 182 / (-1 / np.exp(B*T) + 1 + vec * (1 - np.exp(B*T)))
mi@0 183 )**4
mi@0 184 )
mi@0 185
mi@0 186 allfilts = np.ones_like(centre_freqs)
mi@0 187
mi@0 188 fcoefs = np.column_stack([
mi@0 189 A0*allfilts, A11, A12, A13, A14, A2*allfilts,
mi@0 190 B0*allfilts, B1, B2,
mi@0 191 gain
mi@0 192 ])
mi@0 193
mi@0 194 return fcoefs
mi@0 195
mi@0 196
mi@0 197 def erb_filterbank(wave, coefs):
mi@0 198 """
mi@0 199 :param wave: input data (one dimensional sequence)
mi@0 200 :param coefs: gammatone filter coefficients
mi@0 201
mi@0 202 Process an input waveform with a gammatone filter bank. This function takes
mi@0 203 a single sound vector, and returns an array of filter outputs, one channel
mi@0 204 per row.
mi@0 205
mi@0 206 The fcoefs parameter, which completely specifies the Gammatone filterbank,
mi@0 207 should be designed with the :func:`make_erb_filters` function.
mi@0 208
mi@0 209 | Malcolm Slaney @ Interval, June 11, 1998.
mi@0 210 | (c) 1998 Interval Research Corporation
mi@0 211 | Thanks to Alain de Cheveigne' for his suggestions and improvements.
mi@0 212 |
mi@0 213 | (c) 2013 Jason Heeris (Python implementation)
mi@0 214 """
mi@0 215 output = np.zeros((coefs[:,9].shape[0], wave.shape[0]))
mi@0 216
mi@0 217 gain = coefs[:, 9]
mi@0 218 # A0, A11, A2
mi@0 219 As1 = coefs[:, (0, 1, 5)]
mi@0 220 # A0, A12, A2
mi@0 221 As2 = coefs[:, (0, 2, 5)]
mi@0 222 # A0, A13, A2
mi@0 223 As3 = coefs[:, (0, 3, 5)]
mi@0 224 # A0, A14, A2
mi@0 225 As4 = coefs[:, (0, 4, 5)]
mi@0 226 # B0, B1, B2
mi@0 227 Bs = coefs[:, 6:9]
mi@0 228
mi@0 229 # Loop over channels
mi@0 230 for idx in range(0, coefs.shape[0]):
mi@0 231 # These seem to be reversed (in the sense of A/B order), but that's what
mi@0 232 # the original code did...
mi@0 233 # Replacing these with polynomial multiplications reduces both accuracy
mi@0 234 # and speed.
mi@0 235 y1 = sgn.lfilter(As1[idx], Bs[idx], wave)
mi@0 236 y2 = sgn.lfilter(As2[idx], Bs[idx], y1)
mi@0 237 y3 = sgn.lfilter(As3[idx], Bs[idx], y2)
mi@0 238 y4 = sgn.lfilter(As4[idx], Bs[idx], y3)
mi@0 239 output[idx, :] = y4/gain[idx]
mi@0 240
mi@0 241 return output