├── pyfilterbank ├── docs │ ├── source │ │ ├── examples.rst │ │ ├── introduction.rst │ │ ├── glossary.rst │ │ ├── melbank.rst │ │ ├── gammatone.rst │ │ ├── octbank.rst │ │ ├── splweighting.rst │ │ ├── sosfiltering.rst │ │ ├── index.rst │ │ └── conf.py │ ├── filtbank_logo.png │ ├── Makefile │ └── filtbank_logo.svg ├── stft.py ├── sosfilt.so ├── sosfilt32.dll ├── sosfilt64.dll ├── tests │ ├── check_filterbank_filtering.py │ ├── test_sosfiltering.py │ ├── test_butterworth.py │ └── test_octbank.py ├── __init__.py ├── blockprocessing.py ├── sosfilt.c ├── butterworth.py ├── melbank.py ├── rbj_audio_eq.py ├── splweighting.py ├── gammatone.py ├── sosfiltering.py └── octbank.py ├── .gitignore ├── setup.py └── README.md /pyfilterbank/docs/source/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | -------------------------------------------------------------------------------- /pyfilterbank/docs/source/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | -------------------------------------------------------------------------------- /pyfilterbank/stft.py: -------------------------------------------------------------------------------- 1 | def stft(): 2 | pass 3 | 4 | def istft(): 5 | pass 6 | -------------------------------------------------------------------------------- /pyfilterbank/sosfilt.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetdog/pyfilterbank/master/pyfilterbank/sosfilt.so -------------------------------------------------------------------------------- /pyfilterbank/sosfilt32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetdog/pyfilterbank/master/pyfilterbank/sosfilt32.dll -------------------------------------------------------------------------------- /pyfilterbank/sosfilt64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetdog/pyfilterbank/master/pyfilterbank/sosfilt64.dll -------------------------------------------------------------------------------- /pyfilterbank/docs/filtbank_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetdog/pyfilterbank/master/pyfilterbank/docs/filtbank_logo.png -------------------------------------------------------------------------------- /pyfilterbank/docs/source/glossary.rst: -------------------------------------------------------------------------------- 1 | Indices and tables 2 | ================== 3 | 4 | * :ref:`genindex` 5 | * :ref:`modindex` 6 | * :ref:`search` 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bat 2 | *.o 3 | *.pyc 4 | env/ 5 | __pycache__/ 6 | kernel*.json 7 | _foreign/ 8 | _papers/ 9 | build/ 10 | dist/ 11 | *.egg-info 12 | -------------------------------------------------------------------------------- /pyfilterbank/docs/source/melbank.rst: -------------------------------------------------------------------------------- 1 | Mel Filter Bank 2 | =============== 3 | 4 | Module name: :mod:`melbank` 5 | 6 | .. automodule:: melbank 7 | :members: 8 | -------------------------------------------------------------------------------- /pyfilterbank/docs/source/gammatone.rst: -------------------------------------------------------------------------------- 1 | Gammatone Filter Bank 2 | ===================== 3 | 4 | Module name: :mod:`gammatone` 5 | 6 | .. automodule:: gammatone 7 | :members: 8 | -------------------------------------------------------------------------------- /pyfilterbank/docs/source/octbank.rst: -------------------------------------------------------------------------------- 1 | Fractional Octave Filter Bank 2 | ============================= 3 | 4 | Module name: :mod:`octbank` 5 | 6 | .. automodule:: octbank 7 | :members: 8 | -------------------------------------------------------------------------------- /pyfilterbank/docs/source/splweighting.rst: -------------------------------------------------------------------------------- 1 | Spectral Weighting Filters 2 | ============================= 3 | 4 | Module name: :mod:`splweighting` 5 | 6 | .. automodule:: splweighting 7 | :members: 8 | -------------------------------------------------------------------------------- /pyfilterbank/docs/source/sosfiltering.rst: -------------------------------------------------------------------------------- 1 | Second Order Section Filtering 2 | ============================== 3 | 4 | Module name: :mod:`sosfiltering` 5 | 6 | 7 | .. automodule:: sosfiltering 8 | :members: 9 | 10 | 11 | :mod:`butterworth` 12 | ------------------ 13 | .. automodule:: butterworth 14 | :members: 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -- coding: utf-8 -- 2 | 3 | from setuptools import setup 4 | 5 | settings = { 6 | 'name': 'pyfilterbank', 7 | 'version': '0.0.0', 8 | 'description': 'Filterbanks and filtering for the acoustician and audiologists in python.', 9 | 'url': 'http://github.com/SiggiGue/pyfilterbank', 10 | 'author': u'Siegfried Gündert', 11 | 'author_email': 'siefried.guendert@gmail.com', 12 | 'license': 'MIT', 13 | 'packages': ['pyfilterbank'], 14 | 'zip_safe': False, 15 | 'package_data': { 16 | 'pyfilterbank': [ 17 | 'sosfilt.c', 18 | 'sosfilt64.dll', 19 | 'sosfilt32.dll', 20 | 'sosfilt.so' 21 | ] 22 | } 23 | } 24 | setup(**settings) 25 | -------------------------------------------------------------------------------- /pyfilterbank/tests/check_filterbank_filtering.py: -------------------------------------------------------------------------------- 1 | __test__ = False 2 | 3 | import imp 4 | import numpy as np 5 | import pyfilterbank 6 | from pysoundfile import SoundFile 7 | from pysoundcard import Stream 8 | 9 | 10 | filename = r'bonsai.wav' 11 | 12 | ofb = filterbank.OctaveFilterbank(nth_oct=3.0, start_band=-18, end_band=12, lphp_bounds=True, filterfun='cffi') 13 | 14 | st = Stream(input_device=False) 15 | sf = SoundFile(filename) 16 | 17 | st.start() 18 | 19 | def play_further(nblocks=120, blen=4096): 20 | states = np.zeros(2) 21 | for i in range(nblocks): 22 | x = sf.read(blen)[:,0] 23 | y, states = ofb.filter(x,states=states) 24 | out = y.sum(axis=1).values.flatten() 25 | st.write(out) 26 | 27 | play_further() 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyFilterbank 2 | ## Filter Banks and Acoustics Tools Package for Python 3 | This package provides filter banks, and other useful tools for the daily work of the acoustician. 4 | 5 | The main features are: 6 | 7 | * Fractional octave filter bank (applies to IEC-61260:1995) 8 | 9 | * Spectral weighting filters (IEC 61672:2003) 10 | + A-Weighting 11 | + B- and C-weighting 12 | 13 | * Mel-Frequency filterbank (Triangular Filterbank) 14 | + as Transformation matrix 15 | + planned: with STFT 16 | 17 | * Second order section / biquad filters and filter design 18 | + Butterworth SOS 19 | + RBJ audio EQ filter design module 20 | 21 | * Gammatone filter bank 22 | 23 | 24 | ## Documentation 25 | The [Documentation](http://siggigue.github.io/pyfilterbank) is placed on github pages and can be found [here](http://siggigue.github.io/pyfilterbank). 26 | 27 | 28 | ## Status 29 | This Project is in development status, be aware of that. If you find some bugs or if you want to help, just go for it and join! 30 | 31 | ## License 32 | The 4 clause BSD License applies. 33 | 34 | -------------------------------------------------------------------------------- /pyfilterbank/__init__.py: -------------------------------------------------------------------------------- 1 | """The package :mod:`pyfilterbank` provides tools for acousticians and audiologists/engineers working with python. 2 | 3 | A fractional octave filter bank is provided in the module :mod:`octbank`. You can use it to split your signals into many bands of constant relative fractional octave band width. The output signals stay in the same domain as the input signal but are band passed groups of it. The filtering routines are placed in :mod:`sosfiltering` and the filter design functionality is implemented in :mod:`butterworth`. 4 | 5 | Spectral weigthing for level measurements can be done with the tools in :mod:`splweighting`. 6 | For fft-based and more physiological motivated filtering there is the module :mod:`melbank` with some tools for transforming linear spectra to mel-spectra. 7 | A gammatone filter bank is planned but not implemented yet. 8 | 9 | """ 10 | 11 | from . import butterworth 12 | from . import melbank 13 | from . import octbank 14 | from . import rbj_audio_eq 15 | from . import sosfiltering 16 | from . import splweighting 17 | from . import stft 18 | from . import gammatone 19 | 20 | #from .sosfiltering import sosfilter 21 | from .octbank import FractionalOctaveFilterbank 22 | from .gammatone import GammatoneFilterbank 23 | 24 | __version__ = '0.0.0' 25 | __all__ = ['butterworth', 'melbank', 'octbank', 'rbj_audio_eq', 'sosfiltering', 'splweighting', 'stft'] 26 | -------------------------------------------------------------------------------- /pyfilterbank/tests/test_sosfiltering.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import sys 4 | sys.path.append("..") 5 | import pyfilterbank.sosfiltering as sf 6 | 7 | 8 | class SosFilterTestCase(unittest.TestCase): 9 | 10 | def setUp(self): 11 | self.signal = np.random.randn(1000) 12 | self.frames = len(self.signal) 13 | b = [0.5, 0.0, 0.5] 14 | a = [1.0, -0.8, 0.7] 15 | sos = [b + a] 16 | sos = np.array(sos + sos).astype(np.float32) 17 | self.sos = sos 18 | self.ksos = int(len(sos)/5) 19 | 20 | def test_implementation(self): 21 | """c-implementation and python implementation should be equal""" 22 | op, sp = sf.sosfilter_py(self.signal.copy(), self.sos) 23 | oc, sc = sf.sosfilter_double_c(self.signal.copy(), self.sos) 24 | opc, spc = sf.sosfilter_cprototype_py( 25 | self.signal.copy(), self.sos, None) 26 | sop = np.sum(np.abs(op)) 27 | soc = np.sum(np.abs(oc)) 28 | sopc = np.sum(np.abs(opc)) 29 | 30 | self.assertAlmostEqual(sop, soc, places=6) 31 | self.assertAlmostEqual(sop, sopc, places=6) 32 | self.assertAlmostEqual(soc, sopc, places=6) 33 | 34 | def test_zeros(self): 35 | oc, sc = sf.sosfilter_c(np.zeros(100), self.sos) 36 | self.assertTrue(np.all(oc == np.zeros(100))) 37 | 38 | def test_ones(self): 39 | oc, sc = sf.sosfilter_c(np.ones(100), self.sos) 40 | self.assertTrue(np.all(oc != np.ones(100))) 41 | 42 | 43 | if __name__ == '__main__': 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /pyfilterbank/tests/test_butterworth.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import sys 4 | sys.path.append("..") 5 | import pyfilterbank.sosfiltering as sf 6 | import pyfilterbank.butterworth as bw 7 | 8 | 9 | class ButterSosTestCase(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.types = 'lowpass', 'highpass', 'bandpass', 'bandstop' 13 | self.sample_rate = 44100 14 | self.order = 2 15 | self.v1 = 0.1272 16 | self.v2 = 0.3541 17 | 18 | def test_lowpass(self): 19 | sosmat = bw.butter_sos('lowpass', 2, self.v1) 20 | self.assertTrue(np.all(np.isfinite(sosmat))) 21 | x, y, F, Y = sf.freqz( 22 | sosmat, sample_rate=self.sample_rate, plot=False) 23 | mask = F >= self.v1*self.sample_rate 24 | self.assertTrue(np.all(np.abs(Y[mask]) <= 1.0/np.sqrt(2))) 25 | 26 | def test_highpass(self): 27 | sosmat = bw.butter_sos('highpass', 2, self.v1) 28 | self.assertTrue(np.all(np.isfinite(sosmat))) 29 | x, y, F, Y = sf.freqz( 30 | sosmat, sample_rate=self.sample_rate, plot=False) 31 | mask = np.logical_and(F <= self.v1*self.sample_rate, F >=0) 32 | self.assertTrue(np.all(np.abs(Y[mask]) <= 1.0/np.sqrt(2))) 33 | 34 | def test_bandpass(self): 35 | sosmat = bw.butter_sos('bandpass', 2, self.v1, self.v2) 36 | self.assertTrue(np.all(np.isfinite(sosmat))) 37 | x, y, F, Y = sf.freqz( 38 | sosmat, sample_rate=self.sample_rate, plot=False) 39 | mask = np.logical_and(F <= self.v1*self.sample_rate, 40 | F >= self.v2*self.sample_rate) 41 | self.assertTrue(np.all(np.abs(Y[mask]) <= 1.0/np.sqrt(2))) 42 | 43 | def test_bandstop(self): 44 | sosmat = bw.butter_sos('bandstop', 2, self.v1, self.v2) 45 | self.assertTrue(np.all(np.isfinite(sosmat))) 46 | x, y, F, Y = sf.freqz( 47 | sosmat, sample_rate=self.sample_rate, plot=False) 48 | mask = np.logical_and(F >= self.v1*self.sample_rate, 49 | F <= self.v2*self.sample_rate) 50 | self.assertTrue(np.all(np.abs(Y[mask]) <= 1.0/np.sqrt(2))) 51 | 52 | 53 | if __name__ == '__main__': 54 | unittest.main() 55 | -------------------------------------------------------------------------------- /pyfilterbank/tests/test_octbank.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | import pyfilterbank.octbank as fb 4 | 5 | 6 | def zeros(num_samples=1024, num_chan=1): 7 | return np.zeros((num_samples, num_chan)) 8 | 9 | def ones(num_samples=1024, num_chan=1): 10 | return np.ones((num_samples, num_chan)) 11 | 12 | 13 | class TestFractionalOctaveFilterBank(unittest.TestCase): 14 | def setUp(self): 15 | self.sample_rate = 44100 16 | self. order = 4 17 | self.filterfuns = ['cffi', 'py'] 18 | self.ofbs = [] 19 | for fun in self.filterfuns: 20 | self.ofbs += [fb.FractionalOctaveFilterbank( 21 | sample_rate=self.sample_rate, 22 | order=self.order) 23 | ] 24 | 25 | def test_zeros(self): 26 | for ofb in self.ofbs: 27 | signal, states = ofb.filter_mimo_c(zeros()) 28 | self.assertTrue(np.sum(np.sum(signal)) == 0) 29 | signal, states = ofb.filter(zeros()) 30 | self.assertTrue(np.sum(np.sum(signal)) == 0) 31 | 32 | def test_ones(self): 33 | for ofb in self.ofbs: 34 | signal, states = ofb.filter_mimo_c(ones()) 35 | self.assertTrue(np.all(signal != 1)) 36 | signal, states = ofb.filter(ones()) 37 | self.assertTrue(np.all(signal != 1)) 38 | 39 | 40 | class TestFilterbankModuleFuntions(unittest.TestCase): 41 | def setUp(self): 42 | self.start = -20 43 | self.stop = 20 44 | self.fnorm = 1000.0 45 | self.nth_oct = 3.0 46 | self.order = 2 47 | self.sample_rate = 44100 48 | 49 | def test_frequencies_fractional_octaves(self): 50 | centerfreqs, edges = fb.frequencies_fractional_octaves( 51 | self.start, self.stop, self.fnorm, self.nth_oct) 52 | num_bands = self.stop-self.start+1 53 | self.assertEqual(centerfreqs[+self.start-1], self.fnorm) 54 | self.assertEqual(len(centerfreqs), num_bands) 55 | self.assertEqual(len(edges), num_bands+1) 56 | self.assertTrue(np.all(np.isfinite(centerfreqs))) 57 | self.assertTrue(np.all(np.isfinite(edges))) 58 | 59 | def test_design_sosmat_band_passes(self): 60 | centerfreqs, edges = fb.frequencies_fractional_octaves( 61 | self.start, self.stop, self.fnorm, self.nth_oct) 62 | sosmat = fb.design_sosmat_band_passes( 63 | self.order, edges, self.sample_rate) 64 | self.assertTrue(np.all(np.isfinite(sosmat))) 65 | 66 | 67 | if __name__ == '__main__': 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /pyfilterbank/blockprocessing.py: -------------------------------------------------------------------------------- 1 | from numpy import arange, array, concatenate, floor, zeros 2 | 3 | def block_generator(data, sample_rate, block_len, overlap, 4 | num_blocks_zeropad=None): 5 | """Returns a generator that slices an input array into overlapping blocks. 6 | 7 | Parameters 8 | ---------- 9 | data : ndarray 10 | Input data is a scliceable object (e.g. ndarray) 11 | sample_rate : int 12 | The sampling rate of the signal 13 | block_len : int 14 | The length of one block in seconds. 15 | overlap : scalar 16 | The percentage of overlap between blocks. 17 | num_blocks_zeropad : int 18 | The number of blocks prepended and appended to the signal 19 | for block process. 20 | 21 | Returns 22 | ------- 23 | block : ndarray 24 | Data block with len of block_len. 25 | index : int 26 | Position of the current block in samples. 27 | 28 | Example 29 | ------- 30 | Example two with zeropadding 31 | ..code-block:: python 32 | 33 | data = ones(36) 34 | for block, index in block_generator(data, 6, 1, 0.5, 0.5): 35 | print(block, index) 36 | 37 | produces: 38 | | [ 0. 0. 0. 1. 1. 1.] 0 39 | | [ 1. 1. 1. 1. 1. 1.] 3 40 | | [ 1. 1. 1. 1. 1. 1.] 6 41 | | [ 1. 1. 1. 1. 1. 1.] 9 42 | | [ 1. 1. 1. 1. 1. 1.] 12 43 | | [ 1. 1. 1. 1. 1. 1.] 15 44 | | [ 1. 1. 1. 1. 1. 1.] 18 45 | | [ 1. 1. 1. 1. 1. 1.] 21 46 | | [ 1. 1. 1. 1. 1. 1.] 24 47 | | [ 1. 1. 1. 1. 1. 1.] 27 48 | | [ 1. 1. 1. 1. 1. 1.] 30 49 | | [ 1. 1. 1. 1. 1. 1.] 33 50 | | [ 1. 1. 1. 0. 0. 0.] 36 51 | 52 | """ 53 | 54 | block_samples = round(block_len * sample_rate) 55 | num_samples = len(data) 56 | 57 | if num_blocks_zeropad: 58 | num_samples_zeropad = num_blocks_zeropad * block_samples 59 | num_samples += num_samples_zeropad 60 | if data.ndim==1: 61 | zeropad = zeros(num_samples_zeropad) 62 | else: 63 | zeropad = zeros((num_samples_zeropad, data.shape[1:])) 64 | data = concatenate((zeropad, data, zeropad)) 65 | 66 | overlap_samples = round(block_samples * overlap) 67 | shift_samples = block_samples - overlap_samples 68 | num_blocks = floor((len(data)-overlap_samples) / shift_samples) 69 | 70 | for i in arange(0, num_blocks): 71 | samples = data[i*shift_samples:i*shift_samples + block_samples] 72 | yield array(samples, copy=True), int(i*shift_samples) 73 | -------------------------------------------------------------------------------- /pyfilterbank/sosfilt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | void sosfilter(float* signal, int nsamp, float* sos, int ksos, float* states){ 6 | 7 | for (int k=0; k. 62 | 4. Neither the name of the nor the 63 | names of its contributors may be used to endorse or promote products 64 | derived from this software without specific prior written permission. 65 | 66 | THIS SOFTWARE IS PROVIDED BY ''AS IS'' AND ANY 67 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 68 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 69 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 70 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 71 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 72 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 73 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 74 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 75 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 76 | -------------------------------------------------------------------------------- /pyfilterbank/butterworth.py: -------------------------------------------------------------------------------- 1 | """The :mod:`butterworth` module provides functions to design butterwort filters. 2 | 3 | 4 | """ 5 | from numpy import (mod, exp, zeros, ones, arange, kron, real, flipud, 6 | conj, pi, fliplr, sqrt, tan, tile, concatenate, append, double) 7 | from scipy.signal import butter, buttord, buttap 8 | 9 | from pyfilterbank.sosfiltering import bilinear_sos 10 | 11 | lowpass = 'lowpass' 12 | highpass = 'highpass' 13 | bandpass = 'bandpass' 14 | bandstop = 'bandstop' 15 | 16 | def butter_sos(band, L, v1, v2=0.0): 17 | """Compute weights of a digital Butterworth filter in cascade form. 18 | 19 | Parameters 20 | ---------- 21 | band : {'lowpass', 'highpass', 'bandpass', 'bandstop'} 22 | L : int 23 | Number of lowpass poles. L is doubled for 24 | 'bandpass' and 'bandstop'. L must be even. 25 | v1 : scalar 26 | First critical frequency (Hz-s); 0.0 =.5): 45 | raise Exception('Argument v1 must be >0.0 and <0.5') 46 | elif v2!=0 and (v2<=v1 or v2>=0.5): 47 | raise Exception('Argument v2 must be >v1 and <0.5') 48 | 49 | # Get the anlalog weights and convert to digital. 50 | if(v2 == 0): 51 | d, c = butter_analog_sos(band, L, tan(pi*v1)) 52 | else: 53 | d, c = butter_analog_sos(band, L, tan(pi*v1), tan(pi*v2)) 54 | b, a = bilinear_sos(d, c) 55 | # TODO: cut this step and give back b, a 56 | # but the other functions needing an sosmat should be adapted. 57 | sosmat = flipud(concatenate((b, a), axis=1)) 58 | return sosmat 59 | 60 | 61 | def butter_analog_sos(band, L, w1, w2=0): 62 | """Returns analog filter coeffitients for Butterworth filters. 63 | compute analog weights of a butterworth filter 64 | 65 | Parameters 66 | ---------- 67 | band : {'lowpass', 'highpass, 'bandpass', 'bandstop'} 68 | L : int 69 | Order of lowpass / highpass filter. 70 | w1 : scalar 71 | Critical frequency one. 72 | w2 : scalar 73 | Critical frequency two. (for 'bandpass' or 'bandstop'). 74 | 75 | Returns 76 | ------- 77 | d, c : Analog weights of the filter 78 | 79 | Notes 80 | ----- 81 | implements SOS H(s) butterwort 82 | if you need H(z) apply a bilinear transform 83 | 84 | """ 85 | band = band.lower() 86 | L2 = int(L / 2.0) 87 | 88 | if mod(L, 2): 89 | raise Exception('Number of poles L must be even') 90 | if w1 <= 0: 91 | raise Exception('Frequency w1 must be in rad/s and >0') 92 | 93 | # define center frequency wc: 94 | if band==lowpass or band==highpass: 95 | wc = w1 96 | else: 97 | wc = w2-w1 98 | 99 | p = wc * exp(-1j * (2*arange(1,L2+1,dtype=double) + L-1) * pi / (2*L)) 100 | 101 | # defining the lowpass filter: 102 | d = zeros((L2, 2), dtype=double).astype(complex) 103 | c = ones((L2, 2), dtype=double).astype(complex) 104 | d[:,1] = wc * ones(L2, dtype=double) 105 | c[:,1] = -p 106 | 107 | if band == highpass: 108 | d[:, 0] = d[:, 1] 109 | d[:, 1] = 0.0 110 | c = fliplr(c) 111 | c[:, 1] = c[:, 1] * wc**2 112 | 113 | elif band == bandpass: 114 | d[:, 0] = d[:, 1] 115 | d[:, 1] = zeros(L2) 116 | d = append(d, d, axis=0) 117 | d[L2:, 0] = zeros(L2) 118 | d[L2:, 1] = ones(L2) 119 | root = sqrt(c[:, 1]**2 - 4*c[:, 0]**2 * w1*w2) 120 | r1 = (-c[:, 1] + root) / (2 * c[:, 0]) 121 | r2 = (-c[:, 1] - root) / (2 * c[:, 0]) 122 | c[:, 0] = c[:, 0] 123 | c[:, 1] = -c[:, 0] * r1 124 | c = append(c, c, axis=0) 125 | c[L2:, 0] = 1.0 126 | c[L2:, 1] = -r2 127 | 128 | elif band == bandstop: 129 | root = sqrt(d[:, 0]**2 * wc**4 - 4*d[:, 1]**2 * w1*w2) 130 | r1 = (-d[:, 0] * wc**2 + root) / (2 * d[:, 1]) 131 | r2 = (-d[:, 0] * wc**2 - root) / (2 * d[:, 1]) 132 | d[:, 0] = d[:L2, 1] 133 | d[:, 1] = -d[:L2, 1] * r1 134 | d = append(d, d, axis=0) 135 | d[L2:2*L2, 0] = ones(L2) 136 | d[L2:2*L2, 1] = -r2 137 | root = sqrt(c[:, 0]**2 * wc**4 - 4*c[:, 1]**2 * w1*w2) 138 | r1 = (-c[:, 0] * wc**2 + root) / (2 * c[:, 1]) 139 | r2 = (-c[:, 0] * wc**2 - root) / (2 * c[:, 1]) 140 | c[:, 0] = c[:L2, 1] 141 | c[:, 1] = -c[:L2, 1] * r1 142 | c = append(c, c, axis=0) 143 | c[L2:2*L2, 0] = ones(L2) 144 | c[L2:2*L2, 1] = -r2 145 | 146 | return d, c 147 | -------------------------------------------------------------------------------- /pyfilterbank/melbank.py: -------------------------------------------------------------------------------- 1 | """This module implements a Mel Filter Bank. 2 | In other words it is a filter bank with triangular shaped bands 3 | arnged on the mel frequency scale. 4 | 5 | An example ist shown in the following figure: 6 | 7 | .. plot:: 8 | 9 | from pylab import plt 10 | import melbank 11 | 12 | f1, f2 = 1000, 8000 13 | melmat, (melfreq, fftfreq) = melbank.compute_melmat(6, f1, f2, num_fft_bands=4097) 14 | fig, ax = plt.subplots(figsize=(8, 3)) 15 | ax.plot(fftfreq, melmat.T) 16 | ax.grid(True) 17 | ax.set_ylabel('Weight') 18 | ax.set_xlabel('Frequency / Hz') 19 | ax.set_xlim((f1, f2)) 20 | ax2 = ax.twiny() 21 | ax2.xaxis.set_ticks_position('top') 22 | ax2.set_xlim((f1, f2)) 23 | ax2.xaxis.set_ticks(melbank.mel_to_hertz(melfreq)) 24 | ax2.xaxis.set_ticklabels(['{:.0f}'.format(mf) for mf in melfreq]) 25 | ax2.set_xlabel('Frequency / mel') 26 | plt.tight_layout() 27 | 28 | fig, ax = plt.subplots() 29 | ax.matshow(melmat) 30 | plt.axis('equal') 31 | plt.axis('tight') 32 | plt.title('Mel Matrix') 33 | plt.tight_layout() 34 | 35 | 36 | Functions 37 | --------- 38 | """ 39 | 40 | 41 | from numpy import abs, append, arange, insert, linspace, log10, round, zeros 42 | 43 | 44 | def hertz_to_mel(freq): 45 | """Returns mel-frequency from linear frequency input. 46 | 47 | Parameter 48 | --------- 49 | freq : scalar or ndarray 50 | Frequency value or array in Hz. 51 | 52 | Returns 53 | ------- 54 | mel : scalar or ndarray 55 | Mel-frequency value or ndarray in Mel 56 | 57 | """ 58 | return 2595.0 * log10(1 + (freq/700.0)) 59 | 60 | 61 | def mel_to_hertz(mel): 62 | """Returns frequency from mel-frequency input. 63 | 64 | Parameter 65 | --------- 66 | mel : scalar or ndarray 67 | Mel-frequency value or ndarray in Mel 68 | 69 | Returns 70 | ------- 71 | freq : scalar or ndarray 72 | Frequency value or array in Hz. 73 | 74 | """ 75 | return 700.0 * (10**(mel/2595.0)) - 700.0 76 | 77 | 78 | def melfrequencies_mel_filterbank(num_bands, freq_min, freq_max, num_fft_bands): 79 | """Returns centerfrequencies and band edges for a mel filter bank 80 | Parameters 81 | ---------- 82 | num_bands : int 83 | Number of mel bands. 84 | freq_min : scalar 85 | Minimum frequency for the first band. 86 | freq_max : scalar 87 | Maximum frequency for the last band. 88 | num_fft_bands : int 89 | Number of fft bands. 90 | 91 | Returns 92 | ------- 93 | center_frequencies_mel : ndarray 94 | lower_edges_mel : ndarray 95 | upper_edges_mel : ndarray 96 | 97 | """ 98 | 99 | mel_max = hertz_to_mel(freq_max) 100 | mel_min = hertz_to_mel(freq_min) 101 | delta_mel = abs(mel_max - mel_min) / (num_bands + 1.0) 102 | frequencies_mel = mel_min + delta_mel*arange(0, num_bands+2) 103 | lower_edges_mel = frequencies_mel[:-2] 104 | upper_edges_mel = frequencies_mel[2:] 105 | center_frequencies_mel = frequencies_mel[1:-1] 106 | 107 | return center_frequencies_mel, lower_edges_mel, upper_edges_mel 108 | 109 | 110 | def compute_melmat(num_mel_bands=12, freq_min=64, freq_max=8000, 111 | num_fft_bands=513, sample_rate=16000): 112 | """Returns tranformation matrix for mel spectrum. 113 | 114 | Parameters 115 | ---------- 116 | num_mel_bands : int 117 | Number of mel bands. Number of rows in melmat. 118 | Default: 24 119 | freq_min : scalar 120 | Minimum frequency for the first band. 121 | Default: 64 122 | freq_max : scalar 123 | Maximum frequency for the last band. 124 | Default: 8000 125 | num_fft_bands : int 126 | Number of fft-frequenc bands. This ist NFFT/2+1 ! 127 | number of columns in melmat. 128 | Default: 513 (this means NFFT=1024) 129 | sample_rate : scalar 130 | Sample rate for the signals that will be used. 131 | Default: 44100 132 | 133 | Returns 134 | ------- 135 | melmat : ndarray 136 | Transformation matrix for the mel spectrum. 137 | Use this with fft spectra of num_fft_bands_bands length 138 | and multiply the spectrum with the melmat 139 | this will tranform your fft-spectrum 140 | to a mel-spectrum. 141 | 142 | frequencies : tuple (ndarray , ndarray ) 143 | Center frequencies of the mel bands, center frequencies of fft spectrum. 144 | 145 | """ 146 | center_frequencies_mel, lower_edges_mel, upper_edges_mel = \ 147 | melfrequencies_mel_filterbank( 148 | num_mel_bands, 149 | freq_min, 150 | freq_max, 151 | num_fft_bands 152 | ) 153 | 154 | len_fft = float(num_fft_bands) / sample_rate 155 | center_frequencies_hz = mel_to_hertz(center_frequencies_mel) 156 | lower_edges_hz = mel_to_hertz(lower_edges_mel) 157 | upper_edges_hz = mel_to_hertz(upper_edges_mel) 158 | freqs = linspace(0.0, sample_rate/2.0, num_fft_bands) 159 | melmat = zeros((num_mel_bands, num_fft_bands)) 160 | 161 | for imelband, (center, lower, upper) in enumerate(zip( 162 | center_frequencies_hz, lower_edges_hz, upper_edges_hz)): 163 | 164 | left_slope = (freqs >= lower) == (freqs <= center) 165 | melmat[imelband, left_slope] = ( 166 | (freqs[left_slope] - lower) / (center - lower) 167 | ) 168 | 169 | right_slope = (freqs >= center) == (freqs <= upper) 170 | melmat[imelband, right_slope] = ( 171 | (upper - freqs[right_slope]) / (upper - center) 172 | ) 173 | 174 | return melmat, (center_frequencies_mel, freqs) 175 | -------------------------------------------------------------------------------- /pyfilterbank/rbj_audio_eq.py: -------------------------------------------------------------------------------- 1 | from numpy import sqrt, pi, cos, sin, sinh, log 2 | 3 | 4 | def rbj_sos(filtertype, sample_rate, f0, gain_db=None, 5 | q_factor=None, band_width=None, shelf_slope=None): 6 | 7 | if 'shelf' in filtertype and not shelf_slope: 8 | raise(ValueError('shelf_slope mus be specified.')) 9 | 10 | w0 = 2*pi * f0/sample_rate 11 | amplitude = None if not gain_db else sqrt(10**(gain_db/20.0)) 12 | alpha = _compute_alpha(amplitude, w0, q_factor, band_width, shelf_slope) 13 | params = {'amplitude': amplitude, 'w0': w0, 'alpha': alpha} 14 | 15 | filterfun = _filtertype_to_filterfun_dict[filtertype] 16 | sos = filterfun(**params) 17 | return sos 18 | 19 | 20 | class RbjEqCascade: 21 | def __init__(self, sample_rate): 22 | self._sample_rate = sample_rate 23 | self._sosmat = [] 24 | self._filterlist = [] 25 | 26 | def add(self, filtertype): 27 | self._filtertypelist += [filtertype] 28 | filtobj = RbjEq(filtertype, self._sample_rate) 29 | self._filterlist += [filtobj] 30 | self._sosmat += [filtobj.sos] 31 | 32 | 33 | 34 | class RbjEq: 35 | def __init__(self, filtertype, sample_rate, params=None): 36 | self._filtertype = filtertype 37 | self._sample_rate = sample_rate 38 | self._filterfun = _filtertype_to_filterfun_dict[filtertype] 39 | if not params: 40 | params, param_names = _get_params_filtertype(filtertype) 41 | self._params = params 42 | self._update(**params) 43 | 44 | def update(self, f0, 45 | gain_db=None, 46 | q_factor=None, 47 | band_width=None, 48 | shelf_slope=None): 49 | w0 = 2*pi * f0/self.sample_rate 50 | amplitude = None if not gain_db else sqrt(10**(gain_db/20.0)) 51 | alpha = _compute_alpha(amplitude, w0, q_factor, band_width, shelf_slope) 52 | params = {'amplitude': amplitude, 'w0': w0, 'alpha': alpha} 53 | self._sos = self._filterfun(**params) 54 | 55 | @property 56 | def sos(self): 57 | return self._sos 58 | @property 59 | def params(self): 60 | return self._params 61 | @params.setter 62 | def params(self, value): 63 | self._params = value 64 | self.update(**self.params) 65 | 66 | 67 | def _compute_alpha(amplitude=None, w0=None, q_factor=None, 68 | band_width=None, 69 | shelf_slope=None): 70 | if q_factor: 71 | return sin(w0) / (2*q_factor) 72 | elif band_width: 73 | return sin(w0) * sinh(0.5*log(2.0) * band_width * w0/sin(w0)) 74 | elif shelf_slope: 75 | return sin(w0) / 2 * sqrt((amplitude + 1/alpha) * (1/shelf_slope - 1) +2) 76 | else: 77 | raise(ValueError( 78 | '''You need to specify at least one of: 79 | q_factor, band_width or shelf_slope.''')) 80 | 81 | def _lowpass(w0, alpha): 82 | b0 = (1 - cos(w0)) / 2.0 83 | b1 = 1 - cos(w0) 84 | b2 = (1 - cos(w0)) / 2.0 85 | a0 = 1 + alpha 86 | a1 = -2 * cos(w0) 87 | a2 = 1 - alpha 88 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 89 | return sos 90 | 91 | def _highpass(w0, alpha): 92 | b0 = (1 + cos(w0)) / 2.0 93 | b1 = -(1 + cos(w0)) 94 | b2 = (1 + cos(w0)) / 2.0 95 | a0 = 1 + alpha 96 | a1 = -2 * cos(w0) 97 | a2 = 1 - alpha 98 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 99 | return sos 100 | 101 | def _bandpassQ(w0, alpha): 102 | b0 = sin(w0) / 2.0 # = Q*alpha 103 | b1 = 0.0 104 | b2 = -sin(w0) / 2.0 # = -Q*alpha 105 | a0 = 1 + alpha 106 | a1 = -2 * cos(w0) 107 | a2 = 1 - alpha 108 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 109 | return sos 110 | 111 | def _bandpass(w0, alpha): 112 | b0 = alpha 113 | b1 = 0.0 114 | b2 = -alpha 115 | a0 = 1 + alpha 116 | a1 = -2 * cos(w0) 117 | a2 = 1 - alpha 118 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 119 | return sos 120 | 121 | def _notch(w0, alpha): 122 | b0 = 1.0 123 | b1 = -2 * cos(w0) 124 | b2 = 1.0 125 | a0 = 1 + alpha 126 | a1 = -2 * cos(w0) 127 | a2 = 1 - alpha 128 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 129 | return sos 130 | 131 | def _apf(w0, alpha): 132 | b0 = 1 - alpha 133 | b1 = -2 * cos(w0) 134 | b2 = 1 + alpha 135 | a0 = 1 + alpha 136 | a1 = -2 *cos(w0) 137 | a2 = 1 - alpha 138 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 139 | return sos 140 | 141 | def _peq(amplitude, w0, alpha): 142 | b0 = 1 + alpha*amplitude 143 | b1 = -2 * cos(w0) 144 | b2 = 1 - alpha*amplitude 145 | a0 = 1 + alpha/amplitude 146 | a1 = -2 * cos(w0) 147 | a2 = 1 - alpha/amplitude 148 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 149 | return sos 150 | 151 | def _lowshelf(amplitude, w0, alpha): 152 | b0 = amplitude*((amplitude+1) - (amplitude-1)*cos(w0) + 2*sqrt(amplitude)*alpha) 153 | b1 = 2*amplitude*((amplitude-1) - (amplitude+1)*cos(w0)) 154 | b2 = amplitude*((amplitude+1) - (amplitude-1)*cos(w0) - 2*sqrt(amplitude)*alpha) 155 | a0 = (amplitude+1) + (amplitude-1)*cos(w0) + 2*sqrt(amplitude)*alpha 156 | a1 = -2*((amplitude-1) + (amplitude+1)*cos(w0)) 157 | a2 = (amplitude+1) + (amplitude-1)*cos(w0) - 2*sqrt(amplitude)*alpha 158 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 159 | return sos 160 | 161 | def _highshelf(amplitude, w0, alpha): 162 | b0 = amplitude*((amplitude+1) + (amplitude-1)*cos(w0) + 2*sqrt(amplitude)*alpha) 163 | b1 = -2*amplitude*((amplitude-1) + (amplitude+1)*cos(w0)) 164 | b2 = amplitude*((amplitude+1) + (amplitude-1)*cos(w0) - 2*sqrt(amplitude)*alpha) 165 | a0 = (amplitude+1) - (amplitude-1)*cos(w0) + 2*sqrt(amplitude)*alpha 166 | a1 = 2*((amplitude-1) - (amplitude+1)*cos(w0)) 167 | a2 = (amplitude+1) - (amplitude-1)*cos(w0) - 2*sqrt(amplitude)*alpha 168 | sos = array([b0, b1, b2, a0, a1, a2]) / a0 169 | return sos 170 | 171 | _filtertype_to_filterfun_dict = { 172 | 'lowpass': _lowpass, 173 | 'highpass': _highpass, 174 | 'bandpassQ': _bandpassQ, 175 | 'bandpass': _bandpass, 176 | 'notch': _notch, 177 | 'apf': _apf, 178 | 'peq': _peq, 179 | 'lowshelf': _lowshelf, 180 | 'highshelf': _highshelf, 181 | } 182 | available_filtertypes = list(_filtertype_to_filterfun_dict.keys()) 183 | -------------------------------------------------------------------------------- /pyfilterbank/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyFilterbank.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyFilterbank.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PyFilterbank" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyFilterbank" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /pyfilterbank/splweighting.py: -------------------------------------------------------------------------------- 1 | """This module implements spectral weighting filters for the sound pressure level (SPL) 2 | in air according to [IEC-61672]_. Spectral weighting is part of aucoustic measurements. 3 | It is used by sound level meters for example. The weighting functions are derived 4 | from different equal loudness contours of human hearing. The weighted levels aim to 5 | provide a better correlation to the perception of loudness. 6 | 7 | 8 | Implemented weighting functions 9 | ------------------------------- 10 | 11 | There are three weighting functions implemented: 12 | 13 | * A-Weighting: based on the 40-phon equal loudness contour 14 | * B- and C-weighting: for sounds above 70 phon, 15 | (B-Weighting is not used that often) 16 | 17 | The filter coefficient design is based on the implementation of A- and C-weighting in [2]_. 18 | 19 | The weighting functions are defined in [IEC-61672]_ can be described 20 | by the following equations: 21 | 22 | .. math:: 23 | 24 | R_A (f) = \\frac{12200^2 f^4} 25 | {(f^2+20.6^2)(f^2+12200^2)\\sqrt{(f^2+107.7.5)^2}\\sqrt{(f^2+737.9^2)}} 26 | 27 | R_B (f) = \\frac{12200^2 f^3} 28 | {(f^2+20.6^2)(f^2+12200^2)\\sqrt{(f^2+158.5^2)}} 29 | 30 | R_C (f) = \\frac{12200^2 f^2} 31 | {(f^2+20.6^2)(f^2+12200^2)} 32 | 33 | 34 | The frequency responses absolute values of all implemented weighting filters can be seen in the following figure: 35 | 36 | .. plot:: 37 | 38 | import splweighting 39 | fig, ax = splweighting.plot_weightings() 40 | fig.show() 41 | 42 | 43 | References 44 | ---------- 45 | 46 | .. [IEC-61672] Electroacoustics - Sound Level Meters (http://www.iec.ch) 47 | .. [2] *Christophe Couvreur*, MATLAB(R) implementation of weightings, 48 | http://www.mathworks.com/matlabcentral/fileexchange/69-octave, 49 | Faculte Polytechnique de Mons (Belgium) couvreur@thor.fpms.ac.be 50 | 51 | 52 | Functions 53 | --------- 54 | """ 55 | 56 | from numpy import pi, convolve 57 | from scipy.signal.filter_design import bilinear 58 | from scipy.signal import lfilter 59 | 60 | 61 | def weight_signal(data, sample_rate=44100, weighting='A'): 62 | """Returns filtered signal with a weighting filter. 63 | 64 | Parameters 65 | ---------- 66 | data : ndarray 67 | Input signal to be filtered. 68 | sample_rate : int 69 | Sample rate of the signal. 70 | weighting : {'A', 'B', 'C'} 71 | Specify the weighting function by a string. 72 | 73 | Returns 74 | ------- 75 | outdata : ndarray 76 | Filtered output signal. The output will be weighted by 77 | the specified filter function. 78 | """ 79 | b, a = _weighting_coeff_design_funsd[weighting](sample_rate) 80 | return lfilter(b, a, data) 81 | 82 | def a_weighting_coeffs_design(sample_rate): 83 | """Returns b and a coeff of a A-weighting filter. 84 | 85 | Parameters 86 | ---------- 87 | sample_rate : scalar 88 | Sample rate of the signals that well be filtered. 89 | 90 | Returns 91 | ------- 92 | b, a : ndarray 93 | Filter coefficients for a digital weighting filter. 94 | 95 | Examples 96 | -------- 97 | >>> b, a = a_weighting_coeff_design(sample_rate) 98 | 99 | To Filter a signal use scipy lfilter: 100 | 101 | >>> from scipy.signal import lfilter 102 | >>> y = lfilter(b, a, x) 103 | 104 | See Also 105 | -------- 106 | b_weighting_coeffs_design : B-Weighting coefficients. 107 | c_weighting_coeffs_design : C-Weighting coefficients. 108 | weight_signal : Apply a weighting filter to a signal. 109 | scipy.lfilter : Filtering signal with `b` and `a` coefficients. 110 | """ 111 | 112 | f1 = 20.598997 113 | f2 = 107.65265 114 | f3 = 737.86223 115 | f4 = 12194.217 116 | A1000 = 1.9997 117 | numerators = [(2*pi*f4)**2 * (10**(A1000 / 20.0)), 0., 0., 0., 0.]; 118 | denominators = convolve( 119 | [1., +4*pi * f4, (2*pi * f4)**2], 120 | [1., +4*pi * f1, (2*pi * f1)**2] 121 | ) 122 | denominators = convolve( 123 | convolve(denominators, [1., 2*pi * f3]), 124 | [1., 2*pi * f2] 125 | ) 126 | return bilinear(numerators, denominators, sample_rate) 127 | 128 | def b_weighting_coeffs_design(sample_rate): 129 | """Returns `b` and `a` coeff of a B-weighting filter. 130 | 131 | B-Weighting is no longer described in DIN61672. 132 | 133 | Parameters 134 | ---------- 135 | sample_rate : scalar 136 | Sample rate of the signals that well be filtered. 137 | 138 | Returns 139 | ------- 140 | b, a : ndarray 141 | Filter coefficients for a digital weighting filter. 142 | 143 | Examples 144 | -------- 145 | >>> b, a = b_weighting_coeff_design(sample_rate) 146 | 147 | To Filter a signal use :function: scipy.lfilter: 148 | 149 | >>> from scipy.signal import lfilter 150 | >>> y = lfilter(b, a, x) 151 | 152 | See Also 153 | -------- 154 | a_weighting_coeffs_design : A-Weighting coefficients. 155 | c_weighting_coeffs_design : C-Weighting coefficients. 156 | weight_signal : Apply a weighting filter to a signal. 157 | 158 | """ 159 | 160 | f1 = 20.598997 161 | f2 = 158.5 162 | f4 = 12194.217 163 | B1000 = 0.17 164 | numerators = [(2*pi*f4)**2 * (10**(B1000 / 20)), 0, 0, 0]; 165 | denominators = convolve( 166 | [1, +4*pi * f4, (2*pi * f4)**2], 167 | [1, +4*pi * f1, (2*pi * f1)**2] 168 | ) 169 | denominators = convolve(denominators, [1, 2*pi * f2]) 170 | return bilinear(numerators, denominators, sample_rate) 171 | 172 | 173 | def c_weighting_coeffs_design(sample_rate): 174 | """Returns b and a coeff of a C-weighting filter. 175 | 176 | Parameters 177 | ---------- 178 | sample_rate : scalar 179 | Sample rate of the signals that well be filtered. 180 | 181 | Returns 182 | ------- 183 | b, a : ndarray 184 | Filter coefficients for a digital weighting filter. 185 | 186 | Examples 187 | -------- 188 | b, a = c_weighting_coeffs_design(sample_rate) 189 | 190 | To Filter a signal use scipy lfilter: 191 | 192 | from scipy.signal import lfilter 193 | y = lfilter(b, a, x) 194 | 195 | See Also 196 | -------- 197 | a_weighting_coeffs_design : A-Weighting coefficients. 198 | b_weighting_coeffs_design : B-Weighting coefficients. 199 | weight_signal : Apply a weighting filter to a signal. 200 | 201 | """ 202 | 203 | f1 = 20.598997 204 | f4 = 12194.217 205 | C1000 = 0.0619 206 | numerators = [(2*pi * f4)**2 * (10**(C1000 / 20)), 0, 0] 207 | denominators = convolve( 208 | [1, +4*pi * f4, (2*pi * f4)**2], 209 | [1, +4*pi * f1, (2*pi * f1)**2] 210 | ) 211 | return bilinear(numerators, denominators, sample_rate) 212 | 213 | 214 | # This dictionary should contain all labels and functions 215 | # for weighting coeff design functions: 216 | _weighting_coeff_design_funsd = { 217 | 'A': a_weighting_coeffs_design, 218 | 'B': b_weighting_coeffs_design, 219 | 'C': c_weighting_coeffs_design 220 | } 221 | 222 | def plot_weightings(): 223 | """Plots all weighting functions defined in :module: splweighting.""" 224 | from scipy.signal import freqz 225 | from pylab import plt, np 226 | 227 | sample_rate = 48000 228 | num_samples = 2*4096 229 | 230 | fig, ax = plt.subplots() 231 | 232 | for name, weight_design in sorted( 233 | _weighting_coeff_design_funsd.items()): 234 | b, a = weight_design(sample_rate) 235 | w, H = freqz(b, a, worN=num_samples) 236 | 237 | freq = w*sample_rate / (2*np.pi) 238 | 239 | ax.semilogx(freq, 20*np.log10(np.abs(H)+1e-20), 240 | label='{}-Weighting'.format(name)) 241 | 242 | plt.legend(loc='lower right') 243 | plt.xlabel('Frequency / Hz') 244 | plt.ylabel('Damping / dB') 245 | plt.grid(True) 246 | plt.axis([10, 20000, -80, 5]) 247 | return fig, ax 248 | 249 | 250 | if __name__ == '__main__': 251 | fig, ax = plot_weightings() 252 | fig.show() 253 | -------------------------------------------------------------------------------- /pyfilterbank/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # PyFilterbank documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Jun 1 13:01:31 2014. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import numpy, numpydoc 19 | 20 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 21 | 22 | if not on_rtd: # only import and set the theme if we're building docs locally 23 | import sphinx_rtd_theme 24 | html_theme = 'sphinx_rtd_theme' 25 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 26 | else: 27 | html_theme = 'default' 28 | 29 | # If extensions (or modules to document with autodoc) are in another directory, 30 | # add these directories to sys.path here. If the directory is relative to the 31 | # documentation root, use os.path.abspath to make it absolute, like shown here. 32 | sys.path.insert(0, os.path.abspath('../..')) 33 | sys.path.insert(0, os.path.abspath('../../..')) 34 | 35 | #sys.path.insert(0, os.path.abspath('../images/')) 36 | 37 | # -- General configuration ------------------------------------------------ 38 | 39 | # If your documentation needs a minimal Sphinx version, state it here. 40 | #needs_sphinx = '1.0' 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be 43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 44 | # ones. 45 | extensions = [ 46 | 'matplotlib.sphinxext.only_directives', 47 | 'matplotlib.sphinxext.plot_directive', 48 | 'sphinx.ext.autodoc', 49 | 'sphinx.ext.doctest', 50 | 'sphinx.ext.mathjax', 51 | 'sphinx.ext.viewcode', 52 | 'numpydoc', 53 | ] 54 | numpydoc_use_plots = True 55 | # Add any paths that contain templates here, relative to this directory. 56 | templates_path = ['_templates'] 57 | 58 | # The suffix of source filenames. 59 | source_suffix = '.rst' 60 | 61 | # The encoding of source files. 62 | #source_encoding = 'utf-8-sig' 63 | 64 | # The master toctree document. 65 | master_doc = 'index' 66 | 67 | # General information about the project. 68 | project = 'PyFilterbank' 69 | copyright = '2014, Siegfried Gündert' 70 | 71 | # The version info for the project you're documenting, acts as replacement for 72 | # |version| and |release|, also used in various other places throughout the 73 | # built documents. 74 | # 75 | # The short X.Y version. 76 | version = '0.0.0' 77 | # The full version, including alpha/beta/rc tags. 78 | release = 'devN' 79 | 80 | # The language for content autogenerated by Sphinx. Refer to documentation 81 | # for a list of supported languages. 82 | #language = None 83 | 84 | # There are two options for replacing |today|: either, you set today to some 85 | # non-false value, then it is used: 86 | #today = '' 87 | # Else, today_fmt is used as the format for a strftime call. 88 | #today_fmt = '%B %d, %Y' 89 | 90 | # List of patterns, relative to source directory, that match files and 91 | # directories to ignore when looking for source files. 92 | exclude_patterns = [] 93 | 94 | # The reST default role (used for this markup: `text`) to use for all 95 | # documents. 96 | #default_role = None 97 | 98 | # If true, '()' will be appended to :func: etc. cross-reference text. 99 | #add_function_parentheses = True 100 | 101 | # If true, the current module name will be prepended to all description 102 | # unit titles (such as .. function::). 103 | #add_module_names = True 104 | 105 | # If true, sectionauthor and moduleauthor directives will be shown in the 106 | # output. They are ignored by default. 107 | #show_authors = False 108 | 109 | # The name of the Pygments (syntax highlighting) style to use. 110 | pygments_style = 'sphinx' 111 | 112 | # A list of ignored prefixes for module index sorting. 113 | #modindex_common_prefix = [] 114 | 115 | # If true, keep warnings as "system message" paragraphs in the built documents. 116 | #keep_warnings = False 117 | 118 | 119 | # -- Options for HTML output ---------------------------------------------- 120 | 121 | # The theme to use for HTML and HTML Help pages. See the documentation for 122 | # a list of builtin themes. 123 | #html_theme = 'default' 124 | 125 | # Theme options are theme-specific and customize the look and feel of a theme 126 | # further. For a list of options available for each theme, see the 127 | # documentation. 128 | #html_theme_options = {} 129 | 130 | # Add any paths that contain custom themes here, relative to this directory. 131 | #html_theme_path = [] 132 | 133 | # The name for this set of Sphinx documents. If None, it defaults to 134 | # " v documentation". 135 | #html_title = None 136 | 137 | # A shorter title for the navigation bar. Default is the same as html_title. 138 | #html_short_title = None 139 | 140 | # The name of an image file (relative to this directory) to place at the top 141 | # of the sidebar. 142 | #html_logo = None 143 | 144 | # The name of an image file (within the static path) to use as favicon of the 145 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 146 | # pixels large. 147 | #html_favicon = None 148 | 149 | # Add any paths that contain custom static files (such as style sheets) here, 150 | # relative to this directory. They are copied after the builtin static files, 151 | # so a file named "default.css" will overwrite the builtin "default.css". 152 | html_static_path = ['_static'] 153 | 154 | # Add any extra paths that contain custom files (such as robots.txt or 155 | # .htaccess) here, relative to this directory. These files are copied 156 | # directly to the root of the documentation. 157 | #html_extra_path = [] 158 | 159 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 160 | # using the given strftime format. 161 | #html_last_updated_fmt = '%b %d, %Y' 162 | 163 | # If true, SmartyPants will be used to convert quotes and dashes to 164 | # typographically correct entities. 165 | #html_use_smartypants = True 166 | 167 | # Custom sidebar templates, maps document names to template names. 168 | #html_sidebars = {} 169 | 170 | # Additional templates that should be rendered to pages, maps page names to 171 | # template names. 172 | #html_additional_pages = {} 173 | 174 | # If false, no module index is generated. 175 | #html_domain_indices = True 176 | 177 | # If false, no index is generated. 178 | #html_use_index = True 179 | 180 | # If true, the index is split into individual pages for each letter. 181 | #html_split_index = False 182 | 183 | # If true, links to the reST sources are added to the pages. 184 | #html_show_sourcelink = True 185 | 186 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 187 | #html_show_sphinx = True 188 | 189 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 190 | #html_show_copyright = True 191 | 192 | # If true, an OpenSearch description file will be output, and all pages will 193 | # contain a tag referring to it. The value of this option must be the 194 | # base URL from which the finished HTML is served. 195 | #html_use_opensearch = '' 196 | 197 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 198 | #html_file_suffix = None 199 | 200 | # Output file base name for HTML help builder. 201 | htmlhelp_basename = 'PyFilterbankdoc' 202 | 203 | 204 | # -- Options for LaTeX output --------------------------------------------- 205 | 206 | latex_elements = { 207 | # The paper size ('letterpaper' or 'a4paper'). 208 | #'papersize': 'letterpaper', 209 | 210 | # The font size ('10pt', '11pt' or '12pt'). 211 | 'pointsize': '10pt', 212 | 213 | # Additional stuff for the LaTeX preamble. 214 | 'preamble': '\\usepackage{libertine}', 215 | } 216 | 217 | # Grouping the document tree into LaTeX files. List of tuples 218 | # (source start file, target name, title, 219 | # author, documentclass [howto, manual, or own class]). 220 | latex_documents = [ 221 | ('index', 'PyFilterbank.tex', 'PyFilterbank Documentation', 222 | 'Siegfried Gündert', 'manual'), 223 | ] 224 | 225 | # The name of an image file (relative to this directory) to place at the top of 226 | # the title page. 227 | #latex_logo = None 228 | 229 | # For "manual" documents, if this is true, then toplevel headings are parts, 230 | # not chapters. 231 | #latex_use_parts = False 232 | 233 | # If true, show page references after internal links. 234 | #latex_show_pagerefs = False 235 | 236 | # If true, show URL addresses after external links. 237 | #latex_show_urls = False 238 | 239 | # Documents to append as an appendix to all manuals. 240 | #latex_appendices = [] 241 | 242 | # If false, no module index is generated. 243 | #latex_domain_indices = True 244 | 245 | 246 | # -- Options for manual page output --------------------------------------- 247 | 248 | # One entry per manual page. List of tuples 249 | # (source start file, name, description, authors, manual section). 250 | man_pages = [ 251 | ('index', 'pyfilterbank', 'PyFilterbank Documentation', 252 | ['Siegfried Gündert'], 1) 253 | ] 254 | 255 | # If true, show URL addresses after external links. 256 | #man_show_urls = False 257 | 258 | 259 | # -- Options for Texinfo output ------------------------------------------- 260 | 261 | # Grouping the document tree into Texinfo files. List of tuples 262 | # (source start file, target name, title, author, 263 | # dir menu entry, description, category) 264 | texinfo_documents = [ 265 | ('index', 'PyFilterbank', 'PyFilterbank Documentation', 266 | 'Siegfried Gündert', 'PyFilterbank', 'One line description of project.', 267 | 'Miscellaneous'), 268 | ] 269 | 270 | # Documents to append as an appendix to all manuals. 271 | #texinfo_appendices = [] 272 | 273 | # If false, no module index is generated. 274 | #texinfo_domain_indices = True 275 | 276 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 277 | #texinfo_show_urls = 'footnote' 278 | 279 | # If true, do not generate a @detailmenu in the "Top" node's menu. 280 | #texinfo_no_detailmenu = False 281 | -------------------------------------------------------------------------------- /pyfilterbank/gammatone.py: -------------------------------------------------------------------------------- 1 | """This module implements gammatone filters and a filtering routine. 2 | 3 | A filterbank is coming soon [Hohmann2002]_. 4 | 5 | .. plot:: 6 | 7 | import gammatone 8 | gammatone.example() 9 | 10 | 11 | TODO: 12 | - Tests, 13 | - nice introduction with example, 14 | - implementing the filterbank class 15 | 16 | References 17 | ---------- 18 | 19 | .. [Hohmann2002] 20 | Hohmann, V., Frequency analysis and synthesis using a Gammatone filterbank, 21 | Acta Acustica, Vol 88 (2002), 433--442 22 | 23 | 24 | Functions 25 | --------- 26 | """ 27 | 28 | import numpy as np 29 | from numpy.fft import rfft, rfftfreq 30 | from numpy import (arange, array, pi, cos, exp, log10, ones_like, sqrt, zeros) 31 | from scipy.misc import factorial 32 | from scipy.signal import lfilter 33 | 34 | 35 | # ERB means "Equivalent retangular band(-width)" 36 | # Constants: 37 | _ERB_L = 24.7 38 | _ERB_Q = 9.265 39 | 40 | 41 | def erb_count(centerfrequency): 42 | """Returns the equivalent rectangular band count up to centerfrequency. 43 | 44 | Parameters 45 | ---------- 46 | centerfrequency : scalar /Hz 47 | The center frequency in Hertz of the 48 | desired auditory filter. 49 | 50 | Returns 51 | ------- 52 | count : scalar 53 | Number of equivalent bandwidths below `centerfrequency`. 54 | 55 | """ 56 | return 21.4 * log10(4.37 * 0.001 * centerfrequency + 1) 57 | 58 | 59 | def erb_aud(centerfrequency): 60 | """Retrurns equivalent rectangular band width of an auditory filter. 61 | Implements Equation 13 in [Hohmann2002]_. 62 | 63 | Parameters 64 | ---------- 65 | centerfrequency : scalar /Hz 66 | The center frequency in Hertz of the 67 | desired auditory filter. 68 | 69 | Returns 70 | ------- 71 | erb : scalar 72 | Equivalent rectangular bandwidth of 73 | an auditory filter at `centerfrequency`. 74 | 75 | """ 76 | return _ERB_L + centerfrequency / _ERB_Q 77 | 78 | 79 | def hertz_to_erbscale(frequency): 80 | """Returns ERB-frequency from frequency in Hz. 81 | Implements Equation 16 in [Hohmann2002]_. 82 | 83 | Parameters 84 | ---------- 85 | frequency : scalar 86 | The Frequency in Hertz. 87 | 88 | Returns 89 | ------- 90 | erb : scalar 91 | The corresponding value on the ERB-Scale. 92 | 93 | """ 94 | return _ERB_Q * np.log(1 + frequency / (_ERB_L * _ERB_Q)) 95 | 96 | 97 | def erbscale_to_hertz(erb): 98 | """Returns frequency in Hertz from ERB value. 99 | Implements Equation 17 in [Hohmann2002]_. 100 | 101 | Parameters 102 | ---------- 103 | erb : scalar 104 | The corresponding value on the ERB-Scale. 105 | 106 | Returns 107 | ------- 108 | frequency : scalar 109 | The Frequency in Hertz. 110 | 111 | """ 112 | return (exp(erb/_ERB_Q) - 1) * _ERB_L * _ERB_Q 113 | 114 | 115 | def frequencies_gammatone_bank(start_band, end_band, norm_freq, density): 116 | """Returns centerfrequencies and auditory Bandwidths 117 | for a range of gamatone filters. 118 | 119 | Parameters 120 | ---------- 121 | start_band : int 122 | Erb counts below norm_freq. 123 | end_band : int 124 | Erb counts over norm_freq. 125 | norm_freq : scalar 126 | The reference frequency where all filters are around 127 | density : scalar 128 | ERB density 1would be `erb_aud`. 129 | 130 | Returns 131 | ------- 132 | centerfrequency_array : ndarray 133 | 134 | """ 135 | norm_erb = hertz_to_erbscale(norm_freq) 136 | centerfrequencies = erbscale_to_hertz( 137 | arange(start_band, end_band, density) + norm_erb) 138 | return centerfrequencies 139 | 140 | 141 | def design_filter( 142 | sample_rate=44100, 143 | order=4, 144 | centerfrequency=1000.0, 145 | band_width=None, 146 | band_width_factor=1.0, 147 | attenuation_half_bandwidth_db=-3): 148 | """Returns filter coefficient of a gammatone filter 149 | [Hohmann2002]_. 150 | 151 | Parameters 152 | ---------- 153 | sample_rate : int/scalar 154 | order : int 155 | centerfrequency : scalar 156 | band_width : scalar 157 | band_width_factor : scalar 158 | attenuation_half_bandwidth_db : scalar 159 | 160 | Returns 161 | ------- 162 | b, a : ndarray, ndarray 163 | 164 | """ 165 | if band_width: 166 | phi = pi * band_width / sample_rate 167 | # alpha = 10**(0.1 * attenuation_half_bandwidth_db / order) 168 | # p = (-2 + 2 * alpha * cos(phi)) / (1 - alpha) 169 | # lambda_ = -p/2 - sqrt(p*p/4 - 1) 170 | 171 | elif band_width_factor: 172 | erb_audiological = band_width_factor * erb_aud(centerfrequency) 173 | phi = pi * erb_audiological / sample_rate 174 | # a_gamma = ((factorial(pi * (2*order - 2)) * 175 | # 2**(-(2*order - 2))) / (factorial(order - 1)**2)) 176 | # b = erb_audiological / a_gamma 177 | # lambda_ = exp(-2 * pi * b / sample_rate) 178 | 179 | else: 180 | raise ValueError( 181 | 'You need to specify either `band_width` or `band_width_factor!`') 182 | 183 | alpha = 10**(0.1 * attenuation_half_bandwidth_db / order) 184 | p = (-2 + 2 * alpha * cos(phi)) / (1 - alpha) 185 | lambda_ = -p/2 - sqrt(p*p/4 - 1) 186 | beta = 2*pi * centerfrequency / sample_rate 187 | coef = lambda_ * exp(1j*beta) 188 | factor = 2 * (1 - abs(coef))**order 189 | b, a = array([factor]), array([1., -coef]) 190 | return b, a 191 | 192 | 193 | def fosfilter(b, a, order, signal, states=None): 194 | """Return signal filtered with `b` and `a` (first order section) 195 | by filtering the signal `order` times. 196 | 197 | This Function was created for filtering signals by first order section 198 | cascaded complex gammatone filters. 199 | 200 | Parameters 201 | ---------- 202 | b, a : ndarray, ndarray 203 | Filter coefficients of a first order section filter. 204 | Can be complex valued. 205 | order : int 206 | Order of the filter to be applied. This will 207 | be the count of refiltering the signal order times 208 | with the given coefficients. 209 | signal : ndarray 210 | Input signal to be filtered. 211 | states : ndarray, default None 212 | Array with the filter states of length `order`. 213 | Initial you can set it to None. 214 | 215 | Returns 216 | ------- 217 | signal : ndarray 218 | Output signal, that is filtered and complex valued 219 | (analytical signal). 220 | states : ndarray 221 | Array with the filter states of length `order`. 222 | You need to loop it back into this function when block 223 | processing. 224 | 225 | """ 226 | if not states: 227 | states = zeros(order, dtype=np.complex128) 228 | 229 | for i in range(order): 230 | state = [states[i]] 231 | signal, state = lfilter(b, a, signal, zi=state) 232 | states[i] = state[0] 233 | b = ones_like(b) 234 | return signal, states 235 | 236 | 237 | def freqz_fos(b, a, order, nfft, plotfun=None): 238 | impulse = _create_impulse(nfft) 239 | response, states = fosfilter(b, a, order, impulse) 240 | freqresponse = rfft(np.real(response)) 241 | frequencies = rfftfreq(nfft) 242 | if plotfun: 243 | plotfun(frequencies, freqresponse) 244 | return freqresponse, frequencies, response 245 | 246 | 247 | def design_filtbank_coeffs( 248 | samplerate, 249 | order, 250 | centerfrequencies, 251 | bandwidths=None, 252 | bandwidth_factor=None, 253 | attenuation_half_bandwidth_db=-3): 254 | 255 | for i, cf in enumerate(centerfrequencies): 256 | if bandwidths: 257 | bw = bandwidths[i] 258 | bwf = None 259 | else: 260 | bw = None 261 | bwf = bandwidth_factor 262 | 263 | yield design_filter( 264 | samplerate, order, cf, band_width=bw, 265 | band_width_factor=bwf, 266 | attenuation_half_bandwidth_db=attenuation_half_bandwidth_db) 267 | 268 | 269 | class GammatoneFilterbank: 270 | 271 | def __init__( 272 | self, 273 | samplerate=44100, 274 | order=4, 275 | startband=-12, 276 | endband=12, 277 | normfreq=1000.0, 278 | density=1.0, 279 | bandwidth_factor=1.0, 280 | desired_delay_sec=0.02): 281 | 282 | self.samplerate = samplerate 283 | self.order = order 284 | self.centerfrequencies = frequencies_gammatone_bank( 285 | startband, endband, normfreq, density) 286 | self._coeffs = tuple(design_filtbank_coeffs( 287 | samplerate, 288 | order, 289 | self.centerfrequencies, 290 | bandwidth_factor=bandwidth_factor)) 291 | self.init_delay(desired_delay_sec) 292 | self.init_gains() 293 | 294 | def init_delay(self, desired_delay_sec): 295 | self.desired_delay_sec = desired_delay_sec 296 | self.desired_delay_samples = int(self.samplerate*desired_delay_sec) 297 | self.max_indices, self.slopes = self.estimate_max_indices_and_slopes( 298 | delay_samples=self.desired_delay_samples) 299 | self.delay_samples = self.desired_delay_samples - self.max_indices 300 | self.delay_memory = np.zeros((len(self.centerfrequencies), 301 | np.max(self.delay_samples))) 302 | 303 | def init_gains(self): 304 | self.gains = np.ones(len(self.centerfrequencies)) 305 | # not correct until now: 306 | # x, s = list(zip(*self.analyze(_create_impulse(self.samplerate/10)))) 307 | # rss = [np.sqrt(np.sum(np.real(b)**2)) for b in x] 308 | # self.gains = 1/np.array(rss) 309 | 310 | def analyze(self, signal, states=None): 311 | for i, (b, a) in enumerate(self._coeffs): 312 | st = None if not states else states[i] 313 | yield fosfilter(b, a, self.order, signal, states=st) 314 | 315 | def reanalyze(self, bands, states=None): 316 | for i, ((b, a), band) in enumerate(zip(self._coeffs, bands)): 317 | st = None if not states else states[i] 318 | yield fosfilter(b, a, self.order, band, states=st) 319 | 320 | def synthesize(self, bands): 321 | return np.array(list(self.delay( 322 | [b*g for b, g in zip(bands, self.gains)]))).sum(axis=0) 323 | 324 | def delay(self, bands): 325 | self.phase_factors = np.abs(self.slopes)*1j / self.slopes 326 | for i, band in enumerate(bands): 327 | phase_factor = self.phase_factors[i] 328 | delay_samples = self.delay_samples[i] 329 | if delay_samples == 0: 330 | yield np.real(band) * phase_factor 331 | else: 332 | yield np.concatenate( 333 | (self.delay_memory[i, :delay_samples], 334 | np.real(band[:-delay_samples])), 335 | axis=0) 336 | self.delay_memory[i, :delay_samples] = np.real( 337 | band[-delay_samples:]) 338 | 339 | def estimate_max_indices_and_slopes(self, delay_samples=None): 340 | if not delay_samples: 341 | delay_samples = self.samplerate/10 342 | sig = _create_impulse(delay_samples) 343 | bands = list(zip(*self.analyze(sig)))[0] 344 | ibandmax = [np.argmax(np.abs(b[:delay_samples])) for b in bands] 345 | slopes = [b[i+1]-b[i-1] for (b, i) in zip(bands, ibandmax)] 346 | return np.array(ibandmax), np.array(slopes) 347 | 348 | def freqz(self, nfft=4096, plotfun=None): 349 | def gen_freqz(): 350 | for b, a in self._coeffs: 351 | yield freqz_fos(b, a, self.order, nfft, plotfun) 352 | return list(gen_freqz()) 353 | 354 | 355 | def _create_impulse(num_samples): 356 | sig = zeros(num_samples) + 0j 357 | sig[0] = 1.0 358 | return sig 359 | 360 | 361 | def example_filterbank(): 362 | from pylab import plt 363 | import numpy as np 364 | 365 | x = _create_impulse(2000) 366 | gfb = GammatoneFilterbank(density=1) 367 | 368 | analyse = gfb.analyze(x) 369 | imax, slopes = gfb.estimate_max_indices_and_slopes() 370 | fig, axs = plt.subplots(len(gfb.centerfrequencies), 1) 371 | for (band, state), imx, ax in zip(analyse, imax, axs): 372 | ax.plot(np.real(band)) 373 | ax.plot(np.imag(band)) 374 | ax.plot(np.abs(band)) 375 | ax.plot(imx, 0, 'o') 376 | ax.set_yticklabels([]) 377 | [ax.set_xticklabels([]) for ax in axs[:-1]] 378 | 379 | axs[0].set_title('Impulse responses of gammatone bands') 380 | 381 | fig, ax = plt.subplots() 382 | 383 | def plotfun(x, y): 384 | ax.semilogx(x, 20*np.log10(np.abs(y)**2)) 385 | plt.hold(True) 386 | 387 | gfb.freqz(nfft=2*4096, plotfun=plotfun) 388 | plt.grid(True) 389 | plt.title('Absolute spectra of gammatone bands.') 390 | plt.xlabel('Normalized Frequency (log)') 391 | plt.ylabel('Attenuation /dB(FS)') 392 | plt.axis('Tight') 393 | plt.ylim([-90, 1]) 394 | plt.show() 395 | 396 | return gfb 397 | 398 | 399 | def example_gammatone_filter(): 400 | from pylab import plt, np 401 | sample_rate = 44100 402 | order = 4 403 | b, a = design_filter( 404 | sample_rate=sample_rate, 405 | order=order, 406 | centerfrequency=1000.0, 407 | attenuation_half_bandwidth_db=-3, 408 | band_width_factor=1.0) 409 | 410 | x = _create_impulse(1000) 411 | y, states = fosfilter(b, a, order, x) 412 | y = y[:800] 413 | plt.plot(np.real(y), label='Re(z)') 414 | plt.plot(np.imag(y), label='Im(z)') 415 | plt.plot(np.abs(y), label='|z|') 416 | plt.legend() 417 | plt.show() 418 | return y, b, a 419 | 420 | 421 | if __name__ == '__main__': 422 | 423 | gfb = example_filterbank() 424 | y = example_gammatone_filter() 425 | -------------------------------------------------------------------------------- /pyfilterbank/sosfiltering.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """This module contains second order section filtering routines 3 | implemented in c, cffi and numpy. 4 | 5 | A bilinear transform converting sos analog weights to 6 | sos digital weights is provided by :func:`bilinear_sos`. 7 | 8 | There are different implementations of sos filtering routines: 9 | - A scipy.lfilter()-based :func:`sosfilter_py` 10 | - cffi implementations with float- and double-precision 11 | and a mimo-implementation: 12 | * :func:`sosfilter_c` (float) 13 | * :func:`sosfilter_double_c` (double) 14 | * :func:`sosfilter_double_mimo_c` 15 | (multi channel input and 3-dim output). 16 | 17 | - prototypes for the c-implementations 18 | (slowest, only for debugging) 19 | 20 | The c-implementations are for real valued signals only. 21 | 22 | With the function :func:`freqz` you can check the 23 | frequency response of your second order section filters. 24 | 25 | For the :mod:`cffi` you need :mod:`pycparser` being installed. 26 | 27 | Compiling the c source 28 | ---------------------- 29 | Firstly i implemented a prototype-function in python 30 | for easy debugging "sosfilter_cprototype_py()". 31 | After that i translated this prototype into a c-function. By 32 | compiling a shared library from it with the listed 33 | steps below, one can use the python cffi to access this 34 | shared library in python. :: 35 | $ gcc -c -std=c99 -O3 sosfilter.c 36 | $ gcc -shared -o sosfilter.so sosfilter.o 37 | $ or the last line for windows users: 38 | $ gcc -shared -o sosfilter.dll sosfilter.o 39 | 40 | Functions 41 | --------- 42 | """ 43 | import os 44 | from sys import platform 45 | from platform import architecture 46 | 47 | import numpy as np 48 | from cffi import FFI 49 | from scipy.signal import lfilter 50 | 51 | 52 | ffi = FFI() 53 | ffi.cdef(""" 54 | void sosfilter(float*, int, float*, int, float*); 55 | void sosfilter_double(double*, int, double*, int, double*); 56 | void sosfilter_double_mimo(double*, int, int, double*, int, int, double*); 57 | """) 58 | 59 | if platform == 'win32' and architecture()[0] == '64bit': 60 | _dl = 'sosfilt64.dll' 61 | elif platform == 'win32' and architecture()[0] == '32bit': 62 | _dl = 'sosfilt32.dll' 63 | else: 64 | _dl = 'sosfilt.so' 65 | 66 | 67 | if __name__ != '__main__': 68 | _mylibpath = os.path.join('.', os.path.dirname(__file__)) 69 | else: 70 | _mylibpath = os.curdir 71 | 72 | _mylibpath = os.path.join(_mylibpath, _dl) 73 | 74 | _c = ffi.dlopen(_mylibpath) 75 | 76 | 77 | def sosfilter_c(signal, sos, states=None): 78 | """Second order section filter function using cffi 79 | 80 | signal_out, states = sosfilter_c(signal_in, sos, states=None) 81 | 82 | Parameters 83 | ---------- 84 | signal : ndarray 85 | Input array of shape (N x 0). 86 | sos : ndarray 87 | Second order section coefficients array of shape (K*6 x 0). 88 | One biquad -> 6 coefficients: 89 | :code:`[b00, b01, b02, a00, a01, a02, ..., bK1, ..., aK2]` 90 | states : ndarray 91 | Array with filter states. Initial value can be None. 92 | 93 | Returns 94 | ------- 95 | signal : ndarray 96 | Filtered signal of shape (N x 0). 97 | states : ndarray 98 | Array with filter states. 99 | 100 | """ 101 | 102 | signal_c = ffi.new( 103 | 'char[]', np.array(signal, dtype=np.float32).flatten().tostring()) 104 | sos_c = ffi.new( 105 | 'char[]', np.array(sos, dtype=np.float32).flatten().tostring()) 106 | nsamp = int(len(signal)) 107 | ksos = int(sos.size/6) 108 | 109 | if isinstance(states, type(None)): 110 | states = np.zeros(ksos*2).astype(np.double) 111 | 112 | states_c = ffi.new( 113 | 'char[]', np.array(states, dtype=np.float32).flatten().tostring()) 114 | 115 | _c.sosfilter(ffi.cast("float*", signal_c), 116 | nsamp, 117 | ffi.cast("float*", sos_c), 118 | ksos, 119 | ffi.cast("float*", states_c)) 120 | 121 | out = np.fromstring( 122 | ffi.buffer(signal_c), 123 | dtype=np.float32, 124 | count=nsamp 125 | ) 126 | states = np.fromstring( 127 | ffi.buffer(states_c), 128 | dtype=np.float32, 129 | count=len(states) 130 | ) 131 | return out, states 132 | 133 | 134 | def sosfilter_double_c(signal, sos, states=None): 135 | """Second order section filter function using cffi, double precision. 136 | 137 | signal_out, states = sosfilter_c(signal_in, sos, states=None) 138 | 139 | Parameters 140 | ---------- 141 | signal : ndarray 142 | Signal array of shape (N x 0). 143 | sos : ndarray 144 | Second order section coefficients array of shape (K*6 x 0). 145 | One biquad -> 6 coefficients: 146 | ``[b00, b01, b02, a00, a01, a02, ..., b10, bK1 ... , aK2]`` 147 | states : ndarray 148 | Filter states, initial value can be None. 149 | 150 | Returns 151 | ------- 152 | signal : 153 | Filtered signal array of shape (N x 0). 154 | states : ndarray 155 | Filter states, initial value can be None. 156 | 157 | """ 158 | 159 | signal_c = ffi.new( 160 | 'char[]', np.array(signal, dtype=np.double).flatten().tostring()) 161 | sos_c = ffi.new( 162 | 'char[]', np.array(sos, dtype=np.double).flatten().tostring()) 163 | nsamp = int(len(signal)) 164 | ksos = int(sos.size/6) 165 | 166 | if isinstance(states, type(None)): 167 | states = np.zeros(ksos*2).astype(np.double) 168 | 169 | states_c = ffi.new( 170 | 'char[]', np.array(states, dtype=np.double).flatten().tostring()) 171 | 172 | _c.sosfilter_double(ffi.cast("double*", signal_c), 173 | nsamp, 174 | ffi.cast("double*", sos_c), 175 | ksos, 176 | ffi.cast("double*", states_c)) 177 | 178 | out = np.fromstring( 179 | ffi.buffer(signal_c), 180 | dtype=np.double, 181 | count=nsamp) 182 | states = np.fromstring( 183 | ffi.buffer(states_c), 184 | dtype=np.double, 185 | count=len(states)) 186 | return out, states 187 | 188 | 189 | def sosfilter_double_mimo_c(signal, sos, states=None): 190 | """Second order section filter function for multi channel input 191 | using cffi, double precision 192 | 193 | signal_out, states = sosfilter_c(signal_in, sos, states=None) 194 | 195 | Parameters 196 | ---------- 197 | signal : ndarray 198 | Signal array of shape (N x C). 199 | sos : ndarray 200 | Second order section filter coefficients (K*6 x B x C) np-array. 201 | 202 | states : ndarray 203 | Filter states, initial can be None. 204 | Otherwise shape is (K*2 x B x C) 205 | 206 | Returns 207 | ------- 208 | signal : ndarray 209 | Filtered signal of shape (N x B x C). 210 | Where N is the number of samples, B is th number of 211 | filter bands and C is the number of signal channels. 212 | states : ndarray 213 | Filter states of shape (K*2 x B x C). 214 | 215 | """ 216 | 217 | shape_signal = signal.shape 218 | nframes = int(shape_signal[0]) 219 | if len(shape_signal) > 1: 220 | nchan = int(shape_signal[1]) 221 | else: 222 | nchan = int(1) 223 | 224 | shape_sos = sos.shape 225 | ksos = int(shape_sos[0]/6) 226 | if len(shape_sos) > 1: 227 | kbands = int(shape_sos[1]) 228 | if len(shape_sos) == 2 and nchan > 1: 229 | sos = np.tile(sos.flatten('F'), (nchan)) 230 | else: 231 | kbands = int(1) 232 | 233 | if isinstance(states, type(None)): 234 | states = np.zeros(nchan*kbands*ksos*2).astype(np.double) 235 | 236 | states_c = ffi.new( 237 | 'char[]', np.array(states, dtype=np.double).flatten('F').tostring()) 238 | 239 | sos_c = ffi.new( 240 | 'char[]', np.array(sos, dtype=np.double).flatten('F').tostring()) 241 | 242 | if nchan > 1: 243 | signal = np.tile(signal, (kbands, 1)) 244 | else: 245 | signal = np.tile(signal, (kbands)) 246 | 247 | shape_signal = signal.shape 248 | signal_c = ffi.new( 249 | 'char[]', np.array(signal, dtype=np.double).T.flatten().tostring()) 250 | 251 | _c.sosfilter_double_mimo( 252 | ffi.cast("double*", signal_c), 253 | nframes, 254 | nchan, 255 | ffi.cast("double*", sos_c), 256 | ksos, 257 | kbands, 258 | ffi.cast("double*", states_c) 259 | ) 260 | 261 | out = np.fromstring( 262 | ffi.buffer(signal_c), 263 | dtype=np.double, 264 | count=signal.size 265 | ) 266 | states = np.fromstring( 267 | ffi.buffer(states_c), 268 | dtype=np.double, 269 | count=len(states) 270 | ) 271 | return out.reshape((nframes, kbands, nchan), order='F'), states 272 | 273 | 274 | def sosfilter_mimo_cprototype_py(signal_in, sos_in, states_in=None): 275 | """Prototype for the mimo c-filter function. 276 | Implements a IIR DF-II biquad filter strucure. But with multiple 277 | input und multiple bands.""" 278 | signal = signal_in.copy().flatten('F') 279 | shape_signal = signal_in.shape 280 | print(len(signal)) 281 | nframes = int(signal_in.shape[0]) 282 | print(nframes) 283 | nchan = int(signal_in.shape[2]) 284 | print(nchan) 285 | sos = np.tile(sos_in.copy().flatten('F'), (nchan)) 286 | ksos = int(sos_in.shape[0]/6) 287 | print(ksos) 288 | kbands = int(sos_in.shape[1]) 289 | print(kbands) 290 | if not states_in: 291 | states = np.zeros(nchan*ksos*kbands*2) 292 | else: 293 | states = states_in 294 | 295 | ii = 0 296 | for c in range(nchan): 297 | for b in range(kbands): 298 | for k in range(ksos): 299 | w1 = states[c*ksos*kbands*2 + b*ksos*2 + k*2] 300 | w2 = states[c*ksos*kbands*2 + b*ksos*2 + k*2 + 1] 301 | b0 = sos[ii] 302 | ii += 1 303 | b1 = sos[ii] 304 | ii += 1 305 | b2 = sos[ii] 306 | ii += 1 307 | a0 = sos[ii] 308 | ii += 1 309 | a1 = sos[ii] 310 | ii += 1 311 | a2 = sos[ii] 312 | ii += 1 313 | 314 | for n in range(nframes): 315 | w0 = signal[c*nframes*kbands + b*nframes + n] 316 | w0 = w0 - a1*w1 - a2*w2 317 | yn = b0*w0 + b1*w1 + b2*w2 318 | w2 = w1 319 | w1 = w0 320 | signal[c*nframes*kbands + b*nframes + n] = yn 321 | 322 | states[c*ksos*kbands*2 + b*ksos*2 + k*2] = w1 323 | states[c*ksos*kbands*2 + b*ksos*2 + k*2 + 1] = w2 324 | return signal.reshape(shape_signal), states 325 | 326 | 327 | def sosfilter_cprototype_py(signal, sos, states): 328 | """Prototype for second order section filtering c function. 329 | Implements a IIR DF-II biquad filter strucure. 330 | """ 331 | N = int(len(signal)) 332 | K = int(sos.size/6) 333 | if isinstance(states, type(None)): 334 | states = np.zeros(K*2).astype(np.double) 335 | signal = signal.copy() # only python specific 336 | sos = sos.copy().flatten() 337 | yn = 0.0 # buffer for output 338 | w0 = 0.0 # signal states 339 | 340 | for k in range(K): 341 | # get coefficients of current biquad 342 | w1 = states[k*2] 343 | w2 = states[k*2+1] 344 | b0 = sos[k*6] 345 | b1 = sos[k*6+1] 346 | b2 = sos[k*6+2] 347 | a0 = sos[k*6+3] 348 | a1 = sos[k*6+4] 349 | a2 = sos[k*6+5] 350 | 351 | for n in range(N): 352 | # get a sample 353 | w0 = signal[n].copy() 354 | # recursive path 355 | w0 = w0 - a1*w1 - a2*w2 356 | # transversal path 357 | yn = b0*w0 + b1*w1 + b2*w2 358 | # delays 359 | w2 = w1 360 | w1 = w0 361 | # write output signal 362 | signal[n] = yn 363 | 364 | states[k*2] = w1 365 | states[k*2+1] = w2 366 | 367 | return signal, states 368 | 369 | 370 | def sosfilter_py(x, sos, states=None): 371 | """Second order section filter routing with scipy lfilter. 372 | 373 | Parameters 374 | ---------- 375 | x : ndarray 376 | Input signal array. 377 | sos : ndarray 378 | Second order section coefficients array. 379 | states : ndarray or None 380 | Filter states, initial value can be None. 381 | 382 | 383 | Returns 384 | ------- 385 | signal : ndarray 386 | Filtered signal. 387 | states : ndarray 388 | Array with filter states. 389 | 390 | """ 391 | n = sos.shape[0] 392 | if isinstance(states, type(None)): 393 | states = dict() 394 | for i in np.arange(n): 395 | states[i] = np.zeros(2) 396 | for ii in np.arange(n): 397 | zi = states[ii] 398 | b = sos[ii, :3] 399 | a = sos[ii, 3:] 400 | x, zi = lfilter(b, a, x, 0, zi=zi) 401 | states[ii] = zi 402 | return x, states 403 | 404 | 405 | def bilinear_sos(d, c): 406 | """Bilinear transformation of analog weights to digital weights. 407 | >>>>>>> 8d01abb1e1f252834c0666d50c645dd3d35a1f52 408 | 409 | Bilinear transformation of analog weights to digital weights. 410 | Weights of IIR digital filter in cascade form with 411 | 2-pole sections; H(z)=H(z,1)H(z,2)...H(z,L/2) where 412 | L is the number of poles and each section is a ratio of quadratics. 413 | 414 | Parameters 415 | ---------- 416 | d : ndarray 417 | Numerator weights of analog filter in 1-pole 418 | sections. d is dimensioned (L/2 x 2). 419 | c : ndarray 420 | Denominator weights, dimensioned same as d. 421 | 422 | Returns 423 | ------- 424 | b : ndarray 425 | Digital numerator weights, dimensioned (L/2 x 3). 426 | a : ndarray 427 | Digital denominator weights, dimensioned the same. 428 | 429 | """ 430 | L2, ncd = d.shape 431 | nr, ncc = c.shape 432 | 433 | # Check for errors. 434 | if(nr != L2 or ncd != 2 or ncc != 2): 435 | raise Exception('Inputs d and c must both be L/2 x 2 arrays.') 436 | 437 | # Bilinear transformation of H(s) to H(z) using z and p vectors. 438 | a = np.zeros((L2, 3), dtype=np.double) 439 | a[:, 0] = np.abs(c[:, 0] + c[:, 1])**2 440 | 441 | if np.min(a[:, 0]) == 0: 442 | raise Exception('"c" should not have a row of zeros.') 443 | a[:, 1] = 2*np.real((c[:, 0] + c[:, 1]) * np.conj(c[:, 1] - c[:, 0])) 444 | a[:, 2] = np.abs(c[:, 1] - c[:, 0])**2 445 | 446 | b = np.zeros((L2, 3), dtype=np.double) 447 | b[:, 0] = np.abs(d[:, 0] + d[:, 1])**2 448 | b[:, 1] = 2*np.real((d[:, 0] + d[:, 1]) * np.conj(d[:, 1] - d[:, 0])) 449 | b[:, 2] = np.abs(d[:, 1] - d[:, 0])**2 450 | 451 | # Scale H(z) so a(:,1)=1: 452 | sa = np.kron(np.ones((3, 1)), a[:, 0]).T 453 | a = a / sa 454 | b = b / sa 455 | return b, a 456 | 457 | 458 | def freqz(sosmat, nsamples=44100, sample_rate=44100, plot=True): 459 | """Plots Frequency response of sosmat.""" 460 | from pylab import np, plt, fft, fftfreq 461 | x = np.zeros(nsamples) 462 | x[int(nsamples/2)] = 0.999 463 | y, states = sosfilter_double_c(x, sosmat) 464 | Y = fft(y) 465 | f = fftfreq(len(x), 1.0/sample_rate) 466 | if plot: 467 | plt.grid(True) 468 | plt.axis([0, sample_rate / 2, -100, 5]) 469 | L = 20*np.log10(np.abs(Y[:int(len(x)/2)]) + 1e-17) 470 | plt.semilogx(f[:int(len(x)/2)], L, lw=0.5) 471 | plt.hold(True) 472 | plt.title(u'freqz sos filter') 473 | plt.xlabel('Frequency / Hz') 474 | plt.ylabel(u'Damping /dB(FS)') 475 | plt.xlim((10, sample_rate/2)) 476 | plt.hold(False) 477 | return x, y, f, Y 478 | -------------------------------------------------------------------------------- /pyfilterbank/octbank.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """This module implements a fractional octave filter bank. 3 | The band passes are realized with butterworth second order sections 4 | described by [Stearns2002]_. 5 | For the second order section filter routines the 6 | module :mod:`sosfiltering` is used. 7 | With the class :class:`FractionalOctaveFilterbank` you can create 8 | filtering objects that apply to the [IEC-61260]_. 9 | 10 | An example filter bank is shown by the figures below. 11 | 12 | .. plot:: 13 | 14 | from pylab import plt 15 | import octbank 16 | octbank.example_plot() 17 | plt.show() 18 | 19 | 20 | References 21 | ---------- 22 | 23 | .. [Stearns2002] Stearns, Samuel D., Digital Signal Processing with examples in MATLAB 24 | .. [IEC-61260] Electroacoustics - Octave-band and fractional-octave-band filters 25 | 26 | 27 | Functions 28 | --------- 29 | """ 30 | import numpy as np # TODO: resolve imports for terz fft class... 31 | from numpy import (abs, arange, argmin, array, copy, diff, ones, 32 | pi, real, reshape, sqrt, tan, tile, zeros) 33 | from scipy.fftpack import rfft 34 | from pyfilterbank.sosfiltering import (sosfilter_py, sosfilter_double_c, 35 | sosfilter_cprototype_py, sosfilter_double_mimo_c) 36 | from pyfilterbank.butterworth import butter_sos 37 | 38 | standardized_nominal_frequencies = array([ 39 | 0.1, 0.125, 0.16, 0.2, 0.25, 0.315, 0.4, 0.5, 0.6, 3, 0.8, 40 | 1, 1.25, 1.6, 2, 2.5, 3.15, 4, 5, 6.3, 8, 10, 41 | 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 42 | 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 43 | 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000 44 | ]) 45 | 46 | 47 | def centerfreq_to_bandnum(center_freq, norm_freq, nth_oct): 48 | """Returns band number from given center frequency.""" 49 | return nth_oct * np.log2(center_freq / norm_freq) 50 | 51 | 52 | def find_nominal_freq(center_frequencies, nominal_frequencies): 53 | """Find the nearest nominal frequencies to a given array. 54 | 55 | Parameters 56 | ---------- 57 | center_frequencies : ndarray 58 | Some frequencies for those the neares neighbours shall be found. 59 | nominal_frequencies : ndarray 60 | The nominal frequencies we want to get the best fitting values to 61 | `center_frequencies` from. 62 | 63 | Returns 64 | ------- 65 | nominal_frequencies : generator object 66 | The neares neighbors nomina freqs to the given frequencies. 67 | 68 | """ 69 | for f in center_frequencies: 70 | dist = sqrt((standardized_nominal_frequencies - f)**2) 71 | yield nominal_frequencies[argmin(dist)] 72 | 73 | 74 | def frequencies_fractional_octaves(start_band, end_band, norm_freq, nth_oct): 75 | """Return center and band edge frequencies of fractional octaves. 76 | 77 | Parameters 78 | ---------- 79 | start_band : int 80 | The starting center frequency at `norm_freq`*2^(`start_band`/`nth_oct`). 81 | end_band : int 82 | The last center frequency at `norm_freq`*2^(`end_band`/`nth_oct`). 83 | norm_freq : scalar 84 | The center frequency of the band number 0. 85 | nth_oct : scalar 86 | The distance between the center frequencies. 87 | For third octaves `nth_oct=3.0`. 88 | 89 | Returns 90 | ------- 91 | center_frequencies : ndarray 92 | Frequencies spaced in `nth_oct` from `start_band` to `end_band` 93 | with the `norm_freq` at band number 0. 94 | band_edges : ndarray 95 | Edge frequencies (-3 dB points) of the fractional octave bands. 96 | With constant relative Bandwidth. 97 | 98 | """ 99 | k = arange(start_band-1, end_band+2) 100 | frequencies = norm_freq * 2.0**(k/float(nth_oct)) 101 | band_edges = sqrt(frequencies[:-1] * frequencies[1:]) 102 | center_frequencies = frequencies[1:-1] 103 | return center_frequencies, band_edges 104 | 105 | 106 | def to_normalized_frequencies(frequencies, sample_rate, clip=True): 107 | """Returns normalized frequency array. 108 | 109 | Parameters 110 | ---------- 111 | frequencies : ndarray 112 | Vector with given frequencies. 113 | sample_rate : scalar 114 | The sample rate. Frequencies beyond Nyquist criterion 115 | will be truncated. 116 | 117 | Returns 118 | ------- 119 | normalized_frequencies : ndarray 120 | Normalized, truncated frequency array. 121 | """ 122 | index_nyquis = frequencies >= 0.5*sample_rate 123 | freqs = copy(frequencies) 124 | if clip and any(index_nyquis): 125 | freqs[index_nyquis] = 0.499*sample_rate 126 | return freqs[:list(index_nyquis).index(True)+1] / sample_rate 127 | else: 128 | return freqs[~index_nyquis] / sample_rate 129 | 130 | 131 | def design_sosmat_band_passes(order, band_edges, sample_rate, 132 | edge_correction_percent=0.0): 133 | """Return matrix containig sos coeffs of bandpasses. 134 | 135 | Parameters 136 | ---------- 137 | order : int 138 | Order of the band pass filters. 139 | band_edges : ndarray 140 | Band edge frequencies for the bandpasses. 141 | sample_rate : scalar 142 | Sample frequency. 143 | edge_correction_percent : scalar 144 | Percentage for the correction of the bandedges. 145 | Float between -100 % and 100 %. 146 | It can be helpfull dependent on the used filter order. 147 | p > 0 widens the band passes. 148 | 149 | Returns 150 | ------- 151 | sosmat : ndarray 152 | Second order section coefficients. 153 | Each column is one band pass cascade of coefficients. 154 | """ 155 | num_coeffs_biquad_bandpass = 6 156 | num_coeffs_cascade = order * num_coeffs_biquad_bandpass 157 | num_bands = len(band_edges) - 1 158 | sosmat = zeros((num_coeffs_cascade, num_bands)) 159 | 160 | band_edges_normalized = to_normalized_frequencies(band_edges, sample_rate) 161 | p_lower = (1 - edge_correction_percent*1e-2) 162 | p_upper = (1 + edge_correction_percent*1e-2) 163 | 164 | for i, (lower_freq, upper_freq) in enumerate(zip( 165 | band_edges_normalized[:-1], 166 | band_edges_normalized[1:])): 167 | sos = butter_sos('bandpass', 168 | order, 169 | p_lower*lower_freq, 170 | p_upper*upper_freq) 171 | sosmat[:, i] = sos.flatten() 172 | return sosmat 173 | 174 | 175 | def design_sosmat_low_pass_high_pass_bounds(order, band_edges, sample_rate): 176 | """Returns matrix containing sos coeffs of low and highpass. 177 | The cutoff frequencies are placed at the first and last band edge. 178 | 179 | .. note:: This funtion is not used anymore. 180 | 181 | Parameters 182 | ---------- 183 | order : int 184 | Order of the band pass filters. 185 | band_edges : ndarray 186 | Band edge frequencies for the low an highpass. 187 | sample_rate : scalar 188 | Sample rate. 189 | 190 | Returns 191 | ------- 192 | sosdict : ndarray 193 | Second order section coefficients, 194 | the first column contains the low pass coefs 195 | and the second column contains the highpass coeffs. 196 | 197 | """ 198 | sosmat = zeros((0.5*order*6, 2)) 199 | band_edges_normalized = to_normalized_frequencies(band_edges, sample_rate) 200 | 201 | sosmat[:, 0] = butter_sos('lowpass', order, 202 | band_edges_normalized[0]).flatten() 203 | 204 | sosmat[:, 1] = butter_sos('highpass', order, 205 | band_edges_normalized[-1]).flatten() 206 | return sosmat 207 | 208 | 209 | class FractionalOctaveFilterbank: 210 | """Fractional octave filter bank 211 | with second order section butterworth band passes. 212 | 213 | Parameters 214 | ---------- 215 | sample_rate : int 216 | Sampling rate of the signals to be filtered. 217 | order : int 218 | Filter order of the bands. As this are second order sections, it 219 | has to be even. Otherweise you'll get an error. 220 | nth_oct : scalar 221 | Number of bands per octave. 222 | norm_freq : scalar 223 | This is the reference frequency for all fractional octaves 224 | placed around this band. 225 | start_band : int 226 | First Band number of fractional octaves below `norm_freq`. 227 | end_band : int 228 | Last band number of fractional octaves above `norm_freq`. 229 | edge_correction_percent : scalar 230 | Percentage of widening or narrowing the bands. 231 | filterfun : {'cffi', 'py', 'cprototype'} 232 | Function used by the method :func:`filter`. 233 | 234 | Attributes 235 | ---------- 236 | center_frequencies : ndarray 237 | band_edges : ndarray 238 | Frequencies at -3 dB point for all band passes. 239 | This are the cross sections of the bands if no edge correction 240 | applied. 241 | sosmat : ndarray 242 | Filter coefficient matrix with second order section band passes. 243 | num_bands : int 244 | Number of frequency bands in the filter bank. 245 | band_widths : ndarray 246 | The -3 dB band width of each band pass in the filter bank. 247 | effective_filter_lengths : ndarray 248 | The effective length of the filters in seconds. 249 | A filtered block should at least have same length 250 | if you want to avoid energy leakage. 251 | 252 | Examples 253 | -------- 254 | >>> from pyfilterbank import FractionalOctaveFilterbank 255 | >>> from pylab import plt, np 256 | >>> 257 | >>> sample_rate = 44100 258 | >>> ofb = FractionalOctaveFilterbank(sample_rate, order=4) 259 | >>> 260 | >>> x = np.random.randn(4*sample_rate) 261 | >>> y, states = ofb.filter(x) 262 | >>> L = 10 * np.log10(np.sum(y*y,axis=0)) 263 | >>> plt.plot(L) 264 | 265 | """ 266 | def __init__(self, 267 | sample_rate=44100, 268 | order=4, 269 | nth_oct=3.0, 270 | norm_freq=1000.0, 271 | start_band=-19, 272 | end_band=13, 273 | edge_correction_percent=0.01, 274 | filterfun='cffi'): 275 | self._sample_rate = sample_rate 276 | self._order = order 277 | self._nth_oct = nth_oct 278 | self._norm_freq = norm_freq 279 | self._start_band = start_band 280 | self._end_band = end_band 281 | self._edge_correction_percent = edge_correction_percent 282 | self._initialize_filter_bank() 283 | self.set_filterfun(filterfun) 284 | 285 | @property 286 | def sample_rate(self): 287 | return self._sample_rate 288 | 289 | @sample_rate.setter 290 | def sample_rate(self, value): 291 | self._sample_rate = value 292 | self._initialize_filter_bank() 293 | 294 | @property 295 | def order(self): 296 | return self._order 297 | 298 | @order.setter 299 | def order(self, value): 300 | self._order = value 301 | self._initialize_filter_bank() 302 | 303 | @property 304 | def nth_oct(self): 305 | return self._nth_oct 306 | 307 | @nth_oct.setter 308 | def nth_oct(self, value): 309 | self._nth_oct = value 310 | self._initialize_filter_bank() 311 | 312 | @property 313 | def norm_freq(self): 314 | return self._norm_freq 315 | 316 | @norm_freq.setter 317 | def norm_freq(self, value): 318 | self._norm_freq = value 319 | self._initialize_filter_bank() 320 | 321 | @property 322 | def start_band(self): 323 | return self._start_band 324 | 325 | @start_band.setter 326 | def start_band(self, value): 327 | self._start_band = value 328 | self._initialize_filter_bank() 329 | 330 | @property 331 | def end_band(self): 332 | return self._end_band 333 | 334 | @end_band.setter 335 | def end_band(self, value): 336 | self._end_band = value 337 | self._initialize_filter_bank() 338 | 339 | @property 340 | def edge_correction_percent(self): 341 | return self._edge_correction_percent 342 | 343 | @edge_correction_percent.setter 344 | def edge_correction_percent(self, value): 345 | self._edge_correction_percent = value 346 | self._initialize_filter_bank() 347 | 348 | @property 349 | def center_frequencies(self): 350 | return self._center_frequencies 351 | 352 | @property 353 | def band_edges(self): 354 | return self._band_edges 355 | 356 | @property 357 | def sosmat(self): 358 | return self._sosmat 359 | 360 | @property 361 | def num_bands(self): 362 | return len(self.center_frequencies) 363 | 364 | @property 365 | def band_widths(self): 366 | return diff(self.band_edges) 367 | 368 | @property 369 | def effective_filter_lengths(self): 370 | """Returns an estimate of the effective filter length""" 371 | return [int(l) for l in self.sample_rate*3//self.band_widths] 372 | 373 | def _initialize_filter_bank(self): 374 | center_frequencies, band_edges = frequencies_fractional_octaves( 375 | self.start_band, self.end_band, 376 | self.norm_freq, self.nth_oct 377 | ) 378 | self._center_frequencies = center_frequencies 379 | self._band_edges = band_edges 380 | 381 | sosmat_band_passes = design_sosmat_band_passes( 382 | self.order, self.band_edges, 383 | self.sample_rate, self.edge_correction_percent 384 | ) 385 | self._sosmat = sosmat_band_passes 386 | 387 | def set_filterfun(self, filterfun_name): 388 | """Set the function that is used for filtering 389 | with the method `self.filter`. 390 | 391 | Parameters 392 | ---------- 393 | filterfun_name : {'cffi', 'py', 'cprototype'} 394 | Three different filter functions, 395 | 'cffi' is the fastest, 'py' is implemented with `lfilter`. 396 | 397 | """ 398 | filterfun_name = filterfun_name.lower() 399 | if filterfun_name == 'cffi': 400 | self.sosfilterfun = sosfilter_double_c 401 | self.filterfun_name = filterfun_name 402 | elif filterfun_name == 'py': 403 | self.sosfilterfun = sosfilter_py 404 | self.filterfun_name = filterfun_name 405 | elif filterfun_name == 'cprototype': 406 | self.sosfilterfun = sosfilter_cprototype_py 407 | self.filterfun_name = filterfun_name 408 | else: 409 | print('Could not change filter function.') 410 | 411 | def filter_mimo_c(self, x, states=None): 412 | """Filters the input by the settings of the filterbank object. 413 | 414 | It supports multi channel audio and returns a 3-dim ndarray. 415 | Only for real valued signals. 416 | No ffilt (backward forward filtering) implemented in this method. 417 | 418 | Parameters 419 | ---------- 420 | x : ndarray 421 | Signal to be filtered. 422 | states : ndarray or None 423 | States of the filter sections (for block processing). 424 | 425 | Returns 426 | -------- 427 | signal : ndarray 428 | Signal array (NxBxC), with N samples, B frequency bands 429 | and C-signal channels. 430 | states : ndarray 431 | Filter states of all filter sections. 432 | """ 433 | return sosfilter_double_mimo_c(x, self.sosmat, states) 434 | 435 | def filter(self, x, ffilt=False, states=None): 436 | """Filters the input by the settings of the filterbank object. 437 | 438 | Parameters 439 | ---------- 440 | x : ndarray 441 | Input signal (Nx0) 442 | ffilt : bool 443 | Forward and backward filtering, if Ture. 444 | states : dict 445 | States of all filter sections in the filterbank. 446 | Initial you can states=None before block process. 447 | 448 | Returns 449 | ------- 450 | y : ndarray 451 | Fractional octave signals of the filtered input x 452 | states : dict 453 | Dictionary containing all filter section states. 454 | """ 455 | # in the next version this will be turne to a multi dimensional np array 456 | y_data = zeros((len(x), len(self.center_frequencies))) 457 | 458 | if not isinstance(states, dict): 459 | states_allbands = dict() 460 | for f in self.center_frequencies: states_allbands[f] = None 461 | else : 462 | states_allbands = states 463 | 464 | for i, f in enumerate(self.center_frequencies): 465 | states = states_allbands[f] 466 | sos = reshape(self.sosmat[:, i], (self.order, 6)) 467 | if not ffilt: 468 | y, states = self.sosfilterfun(x.copy(), sos, states) 469 | elif ffilt: 470 | y, states = self.sosfilterfun(x.copy()[::-1], sos, states) 471 | y, states = self.sosfilterfun(y[::-1], sos, states) 472 | 473 | y_data[:, i] = y 474 | states_allbands[f] = states 475 | return y_data, states_allbands 476 | 477 | 478 | def freqz(ofb, length_sec=6, ffilt=False, plot=True): 479 | """Computes the IR and FRF of a digital filter. 480 | 481 | Parameters 482 | ---------- 483 | ofb : FractionalOctaveFilterbank object 484 | length_sec : scalar 485 | Length of the impulse response test signal. 486 | ffilt : bool 487 | Backard forward filtering. Effectiv order is doubled then. 488 | plot : bool 489 | Create Plots or not. 490 | 491 | Returns 492 | ------- 493 | x : ndarray 494 | Impulse test signal. 495 | y : ndarray 496 | Impules responses signal of the filters. 497 | f : ndarray 498 | Frequency vector for the FRF. 499 | Y : Frequency response (FRF) of the summed filters. 500 | 501 | """ 502 | from pylab import np, plt, fft, fftfreq 503 | x = np.zeros(length_sec*ofb.sample_rate) 504 | x[int(length_sec*ofb.sample_rate/2)] = 0.9999 505 | 506 | if not ffilt: 507 | y, states = ofb.filter_mimo_c(x) 508 | y = y[:, :, 0] 509 | else: 510 | y, states = ofb.filter(x, ffilt=ffilt) 511 | s = np.zeros(len(x)) 512 | len_x_2 = int(len(x)/2) 513 | for i in range(y.shape[1]): 514 | s += y[:, i] 515 | X = fft(y[:, i]) # sampled frequency response 516 | f = fftfreq(len(x), 1.0/ofb.sample_rate) 517 | if plot: 518 | fig = plt.figure('freqz filter bank') 519 | plt.grid(True) 520 | plt.axis([0, ofb.sample_rate / 2, -100, 5]) 521 | 522 | L = 20*np.log10(np.abs(X[:len_x_2]) + 1e-17) 523 | plt.semilogx(f[:len_x_2], L, lw=0.5) 524 | 525 | Y = fft(s) 526 | if plot: 527 | plt.title(u'freqz() Filter Bank') 528 | plt.xlabel('Frequency / Hz') 529 | plt.ylabel(u'Damping /dB(FS)') 530 | plt.xlim((10, ofb.sample_rate/2)) 531 | plt.figure('sum') 532 | L = 20*np.log10(np.abs(Y[:len_x_2]) + 1e-17) 533 | plt.semilogx(f[:len_x_2], L, lw=0.5) 534 | 535 | level_input = 10*np.log10(np.sum(x**2)) 536 | level_output = 10*np.log10(np.sum(s**2)) 537 | plt.axis([5, ofb.sample_rate/1.8, -50, 5]) 538 | plt.grid(True) 539 | plt.title('Sum of filter bands') 540 | plt.xlabel('Frequency / Hz') 541 | plt.ylabel(u'Damping /dB(FS)') 542 | 543 | print('sum level', level_output, level_input) 544 | 545 | return x, y, f, Y 546 | 547 | 548 | class ThirdOctFFTLevel: 549 | 550 | """Third octave levels by fft. 551 | TODO: rename variables 552 | TODO: Write Documentation 553 | """ 554 | 555 | def __init__(self, 556 | fmin=30, 557 | fmax=17000, 558 | nfft=16384, 559 | fs=44100, 560 | flag_mean=False): 561 | self.nfft = nfft 562 | self.fs = fs 563 | 564 | # following should go into some functions: 565 | kmin = 11 + int(10*np.log10(fmin)) 566 | kmax = 11 + int(10*np.log10(fmax)) 567 | f_terz = standardized_nominal_frequencies[kmin:kmax] 568 | n = int(1 + kmax - kmin) 569 | halfbw = 2**(1.0/6) 570 | df = fs/nfft 571 | idx_lower = np.zeros(n) 572 | idx_lower[0] = 10 + np.round(( 573 | standardized_nominal_frequencies[kmin]/halfbw)/df) 574 | 575 | idx_upper = 10 + np.round( 576 | halfbw*standardized_nominal_frequencies[kmin:kmax]/df) 577 | idx_lower[1:n] = idx_upper[0:n-1] + 1 578 | 579 | upperedge = halfbw * standardized_nominal_frequencies[kmax] 580 | print(idx_upper[0]-idx_lower[0]) 581 | #if idx_upper(1) - idx_lower(1) < 4: 582 | # raise ValueError('Too few FFT lines per frequency band') 583 | 584 | M = np.zeros((n, int(nfft/2)+1)) 585 | 586 | for cc in range(n-1): 587 | kk = range(int(idx_lower[cc]), int(idx_upper[cc])) 588 | M[cc, kk] = 2.0/(self.nfft/2+1) 589 | if kk[0] == 0: 590 | M[cc, kk[0]] = 1.0/(self.nfft/2+1) 591 | 592 | self.M = M 593 | self.f_terz = f_terz 594 | 595 | def filter(self, x): 596 | Xsq = np.abs(rfft(x, self.nfft/2 + 1))**2 597 | return 10*np.log10(np.dot(self.M, Xsq)) 598 | 599 | 600 | def print_parseval(x, X): 601 | print(np.sum(x*x)) 602 | print(np.sum(X*X)) 603 | 604 | 605 | def example_plot(): 606 | """Creates a plot with :func:`freqz` of the default 607 | :class:`FractionalOctaveFilterbank`. 608 | """ 609 | ofb = FractionalOctaveFilterbank() 610 | x, y, f, Y = freqz(ofb) 611 | -------------------------------------------------------------------------------- /pyfilterbank/docs/filtbank_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xmlPyFilterbank 738 | 739 | 740 | --------------------------------------------------------------------------------