├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── examples ├── spectrogram.py ├── transform_audio.py └── transform_stream.py ├── nsgt ├── __init__.py ├── audio.py ├── cq.py ├── fft.py ├── fscale.py ├── nsdual.py ├── nsgfwin.py ├── nsgfwin_sl.py ├── nsgtf.py ├── nsgtf_loop.py ├── nsgtf_loop.pyx ├── nsigtf.py ├── nsigtf_loop.py ├── nsigtf_loop.pyx ├── reblock.py ├── slicing.py ├── slicq.py ├── unslicing.py └── util.py ├── readme.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── cq_test.py ├── fft_test.py ├── nsgt_test.py ├── reblock_test.py └── slicq_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | nsgt.egg-info/ 4 | nsgt/*.c 5 | *.pyc 6 | *.pyo 7 | *.so 8 | ._* 9 | .DS_Store 10 | .settings/ -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | make_nsgt: 2 | stage: build 3 | script: 4 | - make 5 | artifacts: 6 | paths: 7 | - $CI_PROJECT_DIR/dist/* 8 | expire_in: 1h 9 | 10 | test_nsgt: 11 | stage: test 12 | script: 13 | - make test 14 | 15 | upload_pypi: 16 | stage: deploy 17 | script: 18 | - make upload_pypi 19 | 20 | push_github: 21 | stage: deploy 22 | script: 23 | - make push_github 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2000-2006, The Perl Foundation. 4 | 5 | Everyone is permitted to copy and distribute verbatim copies of this license 6 | document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software Package 11 | may be copied, modified, distributed, and/or redistributed. The intent is that 12 | the Copyright Holder maintains some artistic control over the development of 13 | that Package while still keeping the Package available as open source and free 14 | software. 15 | 16 | You are always permitted to make arrangements wholly outside of this license 17 | directly with the Copyright Holder of a given Package. If the terms of this 18 | license do not permit the full use that you propose to make of the Package, 19 | you should contact the Copyright Holder and seek a different licensing 20 | arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) named in the 25 | copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other material to 28 | the Package, in accordance with the Copyright Holder's procedures. 29 | 30 | "You" and "your" means any person who would like to copy, distribute, or 31 | modify the Package. 32 | 33 | "Package" means the collection of files distributed by the Copyright Holder, 34 | and derivatives of that collection and/or of those files. A given Package may 35 | consist of either the Standard Version, or a Modified Version. 36 | 37 | "Distribute" means providing a copy of the Package or making it accessible to 38 | anyone else, or in the case of a company or organization, to others outside of 39 | your company or organization. 40 | 41 | "Distributor Fee" means any fee that you charge for Distributing this Package 42 | or providing support for this Package to another party. It does not mean 43 | licensing fees. 44 | 45 | "Standard Version" refers to the Package if it has not been modified, or has 46 | been modified only in ways explicitly requested by the Copyright Holder. 47 | 48 | "Modified Version" means the Package, if it has been changed, and such changes 49 | were not explicitly requested by the Copyright Holder. 50 | 51 | "Original License" means this Artistic License as Distributed with the 52 | Standard Version of the Package, in its current version or as it may be 53 | modified by The Perl Foundation in the future. 54 | 55 | "Source" form means the source code, documentation source, and configuration 56 | files for the Package. 57 | 58 | "Compiled" form means the compiled bytecode, object code, binary, or any other 59 | form resulting from mechanical transformation or translation of the Source 60 | form. 61 | 62 | Permission for Use and Modification Without Distribution 63 | 64 | (1) You are permitted to use the Standard Version and create and use Modified 65 | Versions for any purpose without restriction, provided that you do not 66 | Distribute the Modified Version. 67 | 68 | Permissions for Redistribution of the Standard Version 69 | 70 | (2) You may Distribute verbatim copies of the Source form of the Standard 71 | Version of this Package in any medium without restriction, either gratis or 72 | for a Distributor Fee, provided that you duplicate all of the original 73 | copyright notices and associated disclaimers. At your discretion, such 74 | verbatim copies may or may not include a Compiled form of the Package. 75 | 76 | (3) You may apply any bug fixes, portability changes, and other modifications 77 | made available from the Copyright Holder. The resulting Package will still be 78 | considered the Standard Version, and as such will be subject to the Original 79 | License. 80 | 81 | Distribution of Modified Versions of the Package as Source 82 | 83 | (4) You may Distribute your Modified Version as Source (either gratis or for a 84 | Distributor Fee, and with or without a Compiled form of the Modified Version) 85 | provided that you clearly document how it differs from the Standard Version, 86 | including, but not limited to, documenting any non-standard features, 87 | executables, or modules, and provided that you do at least ONE of the 88 | following: 89 | 90 | (a) make the Modified Version available to the Copyright Holder of the 91 | Standard Version, under the Original License, so that the Copyright Holder may 92 | include your modifications in the Standard Version. 93 | 94 | (b) ensure that installation of your Modified Version does not prevent the 95 | user installing or running the Standard Version. In addition, the Modified 96 | Version must bear a name that is different from the name of the Standard 97 | Version. 98 | 99 | (c) allow anyone who receives a copy of the Modified Version to make the 100 | Source form of the Modified Version available to others under 101 | 102 | (i) the Original License or 103 | 104 | (ii) a license that permits the licensee to freely copy, modify and 105 | redistribute the Modified Version using the same licensing terms that apply to 106 | the copy that the licensee received, and requires that the Source form of the 107 | Modified Version, and of any works derived from it, be made freely available 108 | in that license fees are prohibited but Distributor Fees are allowed. 109 | 110 | Distribution of Compiled Forms of the Standard Version or Modified Versions 111 | without the Source 112 | 113 | (5) You may Distribute Compiled forms of the Standard Version without the 114 | Source, provided that you include complete instructions on how to get the 115 | Source of the Standard Version. Such instructions must be valid at the time of 116 | your distribution. If these instructions, at any time while you are carrying 117 | out such distribution, become invalid, you must provide new instructions on 118 | demand or cease further distribution. If you provide valid instructions or 119 | cease distribution within thirty days after you become aware that the 120 | instructions are invalid, then you do not forfeit any of your rights under 121 | this license. 122 | 123 | (6) You may Distribute a Modified Version in Compiled form without the Source, 124 | provided that you comply with Section 4 with respect to the Source of the 125 | Modified Version. 126 | 127 | Aggregating or Linking the Package 128 | 129 | (7) You may aggregate the Package (either the Standard Version or Modified 130 | Version) with other packages and Distribute the resulting aggregation provided 131 | that you do not charge a licensing fee for the Package. Distributor Fees are 132 | permitted, and licensing fees for other components in the aggregation are 133 | permitted. The terms of this license apply to the use and Distribution of the 134 | Standard or Modified Versions as included in the aggregation. 135 | 136 | (8) You are permitted to link Modified and Standard Versions with other works, 137 | to embed the Package in a larger work of your own, or to build stand-alone 138 | binary or bytecode versions of applications that include the Package, and 139 | Distribute the result without restriction, provided the result does not expose 140 | a direct interface to the Package. 141 | 142 | Items That are Not Considered Part of a Modified Version 143 | 144 | (9) Works (including, but not limited to, modules and scripts) that merely 145 | extend or make use of the Package, do not, by themselves, cause the Package to 146 | be a Modified Version. In addition, such works are not considered parts of the 147 | Package itself, and are not subject to the terms of this license. 148 | 149 | General Provisions 150 | 151 | (10) Any use, modification, and distribution of the Standard or Modified 152 | Versions is governed by this Artistic License. By using, modifying or 153 | distributing the Package, you accept this license. Do not use, modify, or 154 | distribute the Package, if you do not accept this license. 155 | 156 | (11) If your Modified Version has been derived from a Modified Version made by 157 | someone other than you, you are nevertheless required to ensure that your 158 | Modified Version complies with the requirements of this license. 159 | 160 | (12) This license does not grant you the right to use any trademark, service 161 | mark, tradename, or logo of the Copyright Holder. 162 | 163 | (13) This license includes the non-exclusive, worldwide, free-of-charge patent 164 | license to make, have made, use, offer to sell, sell, import and otherwise 165 | transfer the Package with respect to any patent claims licensable by the 166 | Copyright Holder that are necessarily infringed by the Package. If you 167 | institute patent litigation (including a cross-claim or counterclaim) against 168 | any party alleging that the Package constitutes direct or contributory patent 169 | infringement, then this Artistic License to you shall terminate on the date 170 | that such litigation is filed. 171 | 172 | (14) Disclaimer of Warranty: 173 | 174 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' 175 | AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF 176 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE 177 | DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, 178 | NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, 179 | INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE 180 | PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 181 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft examples 2 | graft nsgt 3 | graft tests 4 | include *.txt 5 | prune paper 6 | prune nsgt.egg-info 7 | exclude *.lprof 8 | prune .settings 9 | prune nsgt.egg-info 10 | exclude .project .pydevproject 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test build_bdist build_sdist test_nsgt test_dist upload_pypi push_github 2 | 3 | # all builds 4 | all: build_bdist build_sdist 5 | 6 | # all tests 7 | test: test_nsgt test_dist 8 | 9 | # build binary dist 10 | # resultant *.whl file will be in subfolder dist 11 | build_bdist: 12 | python3 setup.py bdist_wheel 13 | # for linux use auditwheel to convert to manylinux format 14 | if auditwheel repair dist/nsgt*.whl; then \ 15 | rm dist/nsgt*.whl; \ 16 | mv wheelhouse/nsgt*.whl dist/; \ 17 | fi 18 | 19 | # build source dist 20 | # resultant file will be in subfolder dist 21 | build_sdist: 22 | python3 setup.py sdist 23 | 24 | # test python module 25 | test_nsgt: 26 | python3 setup.py test 27 | 28 | # test packages 29 | test_dist: 30 | twine check dist/* 31 | 32 | # upload to pypi 33 | upload_pypi: 34 | twine upload --skip-existing dist/* 35 | 36 | # push to github 37 | push_github: 38 | -git remote add github https://$(GITHUB_ACCESS_TOKEN)@github.com/$(GITHUB_USERNAME)/nsgt.git 39 | # we need some extra treatment because the gitlab-runner doesn't check out the full history 40 | git push github HEAD:master --tags 41 | -------------------------------------------------------------------------------- /examples/spectrogram.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 3 | 4 | """ 5 | Python implementation of Non-Stationary Gabor Transform (NSGT) 6 | derived from MATLAB code by NUHAG, University of Vienna, Austria 7 | 8 | Thomas Grill, 2011-2016 9 | http://grrrr.org/nsgt 10 | """ 11 | 12 | import numpy as np 13 | import os 14 | from warnings import warn 15 | 16 | from nsgt import NSGT_sliced, LogScale, LinScale, MelScale, OctScale, SndReader 17 | 18 | 19 | def assemble_coeffs(cqt, ncoefs): 20 | """ 21 | Build a sequence of blocks out of incoming overlapping CQT slices 22 | """ 23 | cqt = iter(cqt) 24 | cqt0 = next(cqt) 25 | cq0 = np.asarray(cqt0).T 26 | shh = cq0.shape[0]//2 27 | out = np.empty((ncoefs, cq0.shape[1], cq0.shape[2]), dtype=cq0.dtype) 28 | 29 | fr = 0 30 | sh = max(0, min(shh, ncoefs-fr)) 31 | out[fr:fr+sh] = cq0[sh:] # store second half 32 | # add up slices 33 | for cqi in cqt: 34 | cqi = np.asarray(cqi).T 35 | out[fr:fr+sh] += cqi[:sh] 36 | cqi = cqi[sh:] 37 | fr += sh 38 | sh = max(0, min(shh, ncoefs-fr)) 39 | out[fr:fr+sh] = cqi[:sh] 40 | 41 | return out[:fr] 42 | 43 | 44 | from argparse import ArgumentParser 45 | parser = ArgumentParser() 46 | 47 | parser.add_argument("input", type=str, help="Input file") 48 | parser.add_argument("--output", type=str, help="Output data file (.npz, .hd5, .pkl)") 49 | parser.add_argument("--sr", type=int, default=44100, help="Sample rate used for the NSGT (default=%(default)s)") 50 | parser.add_argument("--data-times", type=str, default='times', help="Data name for times (default='%(default)s')") 51 | parser.add_argument("--data-frqs", type=str, default='f', help="Data name for frequencies (default='%(default)s')") 52 | parser.add_argument("--data-qs", type=str, default='q', help="Data name for q factors (default='%(default)s')") 53 | parser.add_argument("--data-coefs", type=str, default='coefs', help="Data name for coefficients (default='%(default)s')") 54 | parser.add_argument("--fps", type=float, default=0, help="Approx. time resolution for features in fps (default=%(default)s)") 55 | parser.add_argument("--fps-pooling", choices=('max','mean','median'), default='max', help="Temporal pooling function for features (default='%(default)s')") 56 | parser.add_argument("--fmin", type=float, default=50, help="Minimum frequency in Hz (default=%(default)s)") 57 | parser.add_argument("--fmax", type=float, default=22050, help="Maximum frequency in Hz (default=%(default)s)") 58 | parser.add_argument("--scale", choices=('oct','log','mel'), default='log', help="Frequency scale oct, log, lin, or mel (default='%(default)s')") 59 | parser.add_argument("--bins", type=int, default=50, help="Number of frequency bins (total or per octave, default=%(default)s)") 60 | parser.add_argument("--mag-scale", choices=('dB','log'), default='dB', help="Magnitude scale dB or log (default='%(default)s')") 61 | parser.add_argument("--sllen", type=int, default=2**20, help="Slice length in samples (default=%(default)s)") 62 | parser.add_argument("--trlen", type=int, default=2**18, help="Transition area in samples (default=%(default)s)") 63 | parser.add_argument("--real", action='store_true', help="Assume real signal") 64 | parser.add_argument("--matrixform", action='store_true', help="Use regular time division over frequency bins (matrix form)") 65 | parser.add_argument("--reducedform", type=int, default=0, help="If real, omit bins for f=0 and f=fs/2 (--reducedform=1), or also the transition bands (--reducedform=2) (default=%(default)s)") 66 | parser.add_argument("--recwnd", action='store_true', help="Use reconstruction window") 67 | parser.add_argument("--multithreading", action='store_true', help="Use multithreading") 68 | parser.add_argument("--downmix-after", action='store_true', help="Downmix signal after spectrogram generation") 69 | parser.add_argument("--plot", action='store_true', help="Plot transform (needs installed matplotlib package)") 70 | 71 | args = parser.parse_args() 72 | if not os.path.exists(args.input): 73 | parser.error("Input file '%s' not found"%args.input) 74 | 75 | fs = args.sr 76 | 77 | # build transform 78 | scales = {'log':LogScale, 'lin':LinScale, 'mel':MelScale, 'oct':OctScale} 79 | try: 80 | scale = scales[args.scale] 81 | except KeyError: 82 | parser.error('Scale unknown (--scale option)') 83 | 84 | scl = scale(args.fmin, args.fmax, args.bins, beyond=int(args.reducedform == 2)) 85 | 86 | slicq = NSGT_sliced(scl, args.sllen, args.trlen, fs, 87 | real=args.real, recwnd=args.recwnd, 88 | matrixform=args.matrixform, reducedform=args.reducedform, 89 | multithreading=args.multithreading, 90 | multichannel=True 91 | ) 92 | 93 | # Read audio data 94 | sf = SndReader(args.input, sr=fs, chns=2) 95 | signal = sf() 96 | 97 | # duration of signal in s 98 | dur = sf.frames/float(fs) 99 | 100 | # total number of coefficients to represent input signal 101 | ncoefs = int(sf.frames*slicq.coef_factor) 102 | 103 | # read slices from audio file and mix down signal, if necessary at all 104 | if not args.downmix_after: 105 | signal = ((np.mean(s, axis=0),) for s in signal) 106 | 107 | # generator for forward transformation 108 | c = slicq.forward(signal) 109 | 110 | # add up overlapping coefficient slices 111 | coefs = assemble_coeffs(c, ncoefs) 112 | 113 | del sf # not needed any more 114 | 115 | # compute magnitude spectrum 116 | mindb = -100. 117 | mls = np.abs(coefs) 118 | # mix down multichannel 119 | mls = np.mean(mls, axis=-1) 120 | np.maximum(mls, 10**(mindb/20.), out=mls) 121 | 122 | if args.mag_scale == 'dB': 123 | np.log10(mls, out=mls) 124 | mls *= 20. 125 | elif args.mag_scale == 'log': 126 | np.log(mls, out=mls) 127 | else: 128 | raise NotImplementedError("Magnitude scale '%s' not implemented."%args.magscale) 129 | 130 | fs_coef = fs*slicq.coef_factor # frame rate of coefficients 131 | mls_dur = len(mls)/fs_coef # final duration of MLS 132 | 133 | if args.fps: 134 | # pool features in time 135 | coefs_per_sec = fs*slicq.coef_factor 136 | poolf = int(coefs_per_sec/args.fps+0.5) # pooling factor 137 | pooled_len = mls.shape[0]//poolf 138 | mls_dur *= (pooled_len*poolf)/float(len(mls)) 139 | mls = mls[:pooled_len*poolf] 140 | poolfun = np.__dict__[args.fps_pooling] 141 | mls = poolfun(mls.reshape((pooled_len,poolf,)+mls.shape[1:]), axis=1) 142 | 143 | times = np.linspace(0, mls_dur, endpoint=True, num=len(mls)+1) 144 | 145 | # save file 146 | if args.output: 147 | if args.reducedform == 2: 148 | frqs = slicq.frqs[1:-1] 149 | qs = slicq.q[1:-1] 150 | else: 151 | frqs = slicq.frqs 152 | qs = slicq.q 153 | 154 | data = {args.data_coefs: mls, args.data_times: times, args.data_frqs: frqs, args.data_qs: qs} 155 | if args.output.endswith('.pkl') or args.output.endswith('.pck'): 156 | import pickle 157 | with file(args.output, 'wb') as f: 158 | pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL) 159 | elif args.output.endswith('.npz'): 160 | np.savez(args.output, **data) 161 | elif args.output.endswith('.hdf5') or args.output.endswith('.h5'): 162 | import h5py 163 | with h5py.File(args.output, 'w') as f: 164 | for k,v in data.items(): 165 | f[k] = v 166 | else: 167 | warn("Output file format not supported, skipping output.") 168 | 169 | if args.plot: 170 | print("Plotting t*f space") 171 | import matplotlib.pyplot as pl 172 | mls_max = np.percentile(mls, 99.9) 173 | pl.imshow(mls.T, aspect=mls_dur/mls.shape[1]*0.2, interpolation='nearest', origin='bottom', vmin=mls_max-60., vmax=mls_max, extent=(0,mls_dur,0,mls.shape[1])) 174 | pl.show() 175 | -------------------------------------------------------------------------------- /examples/transform_audio.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 3 | 4 | """ 5 | Python implementation of Non-Stationary Gabor Transform (NSGT) 6 | derived from MATLAB code by NUHAG, University of Vienna, Austria 7 | 8 | Thomas Grill, 2011-2020 9 | http://grrrr.org/nsgt 10 | """ 11 | 12 | import numpy as np 13 | from nsgt import NSGT, LogScale, LinScale, MelScale, OctScale 14 | 15 | try: 16 | from scikits.audiolab import Sndfile, Format 17 | except: 18 | Sndfile = None 19 | 20 | if Sndfile is None: 21 | from pysndfile import PySndfile, construct_format 22 | 23 | import os 24 | 25 | 26 | def cputime(): 27 | utime, stime, cutime, cstime, elapsed_time = os.times() 28 | return utime 29 | 30 | class interpolate: 31 | def __init__(self,cqt,Ls): 32 | from scipy.interpolate import interp1d 33 | self.intp = [interp1d(np.linspace(0, Ls, len(r)), r) for r in cqt] 34 | def __call__(self,x): 35 | try: 36 | len(x) 37 | except: 38 | return np.array([i(x) for i in self.intp]) 39 | else: 40 | return np.array([[i(xi) for i in self.intp] for xi in x]) 41 | 42 | 43 | from argparse import ArgumentParser 44 | parser = ArgumentParser() 45 | 46 | parser.add_argument("input", type=str, help="input audio file") 47 | parser.add_argument('-o', "--output", type=str, help="output audio file") 48 | parser.add_argument('-f', "--fmin", type=float, default=80, help="minimum frequency (default=%(default)s)") 49 | parser.add_argument('-F', "--fmax", type=float, default=22050, help="maximum frequency (default=%(default)s)") 50 | parser.add_argument('-s', "--scale", choices=("oct","log","mel"), default='oct', help="frequency scale (oct,log,mel)") 51 | parser.add_argument('-b', "--bins", type=int, default=24, help="frequency bins (total or per octave, default=%(default)s)") 52 | parser.add_argument('-r', "--real", action='store_true', help="assume real signal") 53 | parser.add_argument('-m', "--matrixform", action='store_true', help="use regular time division (matrix form)") 54 | parser.add_argument('-l', "--reducedform", action='count', default=0, help="if real==1: omit bins for f=0 and f=fs/2 (lossy=1), or also the transition bands (lossy=2)") 55 | parser.add_argument('-t', "--time", type=int, default=1, help="timing calculation n-fold (default=%(default)s)") 56 | parser.add_argument('-p', "--plot", action='store_true', help="plot results (needs installed matplotlib and scipy packages)") 57 | 58 | args = parser.parse_args() 59 | if not os.path.exists(args.input): 60 | parser.error("Input file '%s' not found"%args.input) 61 | 62 | # Read audio data 63 | if Sndfile is not None: 64 | sf = Sndfile(args.input) 65 | fs = sf.samplerate 66 | samples = sf.nframes 67 | else: 68 | sf = PySndfile(args.input) 69 | fs = sf.samplerate() 70 | samples = sf.frames() 71 | s = sf.read_frames(samples) 72 | 73 | if s.ndim > 1: 74 | s = np.mean(s, axis=1) 75 | 76 | scales = {'log':LogScale, 'lin':LinScale, 'mel':MelScale, 'oct':OctScale} 77 | try: 78 | scale = scales[args.scale] 79 | except KeyError: 80 | parser.error('scale unknown') 81 | 82 | scl = scale(args.fmin, args.fmax, args.bins) 83 | 84 | times = [] 85 | 86 | for _ in range(args.time or 1): 87 | t1 = cputime() 88 | 89 | # calculate transform parameters 90 | Ls = len(s) 91 | 92 | nsgt = NSGT(scl, fs, Ls, real=args.real, matrixform=args.matrixform, reducedform=args.reducedform) 93 | 94 | # forward transform 95 | c = nsgt.forward(s) 96 | 97 | c = np.asarray(c) 98 | print("Coefficients:", c.shape) 99 | # print "c",len(c),N.array(map(len,c)) 100 | 101 | # inverse transform 102 | s_r = nsgt.backward(c) 103 | 104 | t2 = cputime() 105 | times.append(t2-t1) 106 | 107 | norm = lambda x: np.sqrt(np.sum(np.abs(np.square(x)))) 108 | rec_err = norm(s-s_r)/norm(s) 109 | print("Reconstruction error: %.3e"%rec_err) 110 | print("Calculation time: %.3f±%.3fs (min=%.3f s)"%(np.mean(times), np.std(times)/2, np.min(times))) 111 | 112 | if args.output: 113 | print("Writing audio file '%s'"%args.output) 114 | if Sndfile is not None: 115 | sf = Sndfile(args.output, mode='w', format=Format('wav','pcm24'), channels=1, samplerate=fs) 116 | else: 117 | sf = PySndfile(args.output, mode='w', format=construct_format('wav','pcm24'), channels=1, samplerate=fs) 118 | sf.write_frames(s_r) 119 | sf.close() 120 | print("Done") 121 | 122 | if args.plot: 123 | print("Preparing plot") 124 | import matplotlib.pyplot as pl 125 | # interpolate CQT to get a grid 126 | hf = -1 if args.real else len(c)//2 127 | if not args.matrixform: 128 | x = np.linspace(0, Ls, 2000) 129 | grid = interpolate(map(np.abs, c[2:hf]), Ls)(x).T 130 | else: 131 | grid = np.abs(c[2:hf]) 132 | np.log10(grid, out=grid) 133 | grid *= 20 134 | pmax = np.percentile(grid, 99.99) 135 | pl.imshow(grid, aspect='auto', origin='lower', vmin=pmax-80, vmax=pmax) 136 | pl.colorbar() 137 | pl.show() 138 | -------------------------------------------------------------------------------- /examples/transform_stream.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 3 | 4 | """ 5 | Python implementation of Non-Stationary Gabor Transform (NSGT) 6 | derived from MATLAB code by NUHAG, University of Vienna, Austria 7 | 8 | Thomas Grill, 2011-2020 9 | http://grrrr.org/nsgt 10 | """ 11 | 12 | import numpy as np 13 | from scikits.audiolab import Sndfile, Format 14 | import os 15 | 16 | from nsgt import NSGT_sliced, LogScale, LinScale, MelScale, OctScale 17 | from nsgt.reblock import reblock 18 | 19 | def cputime(): 20 | utime, stime, cutime, cstime, elapsed_time = os.times() 21 | return utime 22 | 23 | 24 | from argparse import ArgumentParser 25 | parser = ArgumentParser() 26 | 27 | parser.add_argument("input", type=str, help="input file") 28 | parser.add_argument('-o', "--output", type=str, help="output audio file") 29 | parser.add_argument('-L', "--length", type=int, default=0, help="maximum length of signal (default=%(default)s)") 30 | parser.add_argument('-f', "--fmin", type=float, default=50, help="minimum frequency (default=%(default)s)") 31 | parser.add_argument('-F', "--fmax", type=float, default=22050, help="maximum frequency (default=%(default)s)") 32 | parser.add_argument('-s', "--scale", choices=('oct','log','mel'), default='log', help="frequency scale (oct,log,lin,mel)") 33 | parser.add_argument('-b', "--bins", type=int, default=50, help="frequency bins (total or per octave, default=%(default)s)") 34 | parser.add_argument("--sllen", type=int, default=2**16, help="slice length (default=%(default)s)") 35 | parser.add_argument("--trlen", type=int, default=4096, help="transition area (default=%(default)s)") 36 | parser.add_argument('-r', "--real", action='store_true', help="assume real signal") 37 | parser.add_argument('-m', "--matrixform", action='store_true', help="use regular time division (matrix form)") 38 | parser.add_argument('-l', "--reducedform", action='count', default=0, help="if real==1: omit bins for f=0 and f=fs/2 (lossy=1), or also the transition bands (lossy=2)") 39 | parser.add_argument('-w', "--recwnd", action='store_true', help="use reconstruction window") 40 | parser.add_argument('-t', "--multithreading", action='store_true', help="use multithreading") 41 | parser.add_argument('-p', "--plot", action='store_true', help="plot transform (needs installed matplotlib and scipy packages)") 42 | 43 | args = parser.parse_args() 44 | if not os.path.exists(args.input): 45 | parser.error("Input file '%s' not found"%args.input) 46 | 47 | # Read audio data 48 | sf = Sndfile(args.input) 49 | fs = sf.samplerate 50 | s = sf.read_frames(sf.nframes) 51 | if sf.channels > 1: 52 | s = np.mean(s, axis=1) 53 | 54 | if args.length: 55 | s = s[:args.length] 56 | 57 | scales = {'log':LogScale,'lin':LinScale,'mel':MelScale,'oct':OctScale} 58 | try: 59 | scale = scales[args.scale] 60 | except KeyError: 61 | parser.error('scale unknown') 62 | 63 | scl = scale(args.fmin, args.fmax, args.bins) 64 | slicq = NSGT_sliced(scl, args.sllen, args.trlen, fs, 65 | real=args.real, recwnd=args.recwnd, 66 | matrixform=args.matrixform, reducedform=args.reducedform, 67 | multithreading=args.multithreading 68 | ) 69 | 70 | t1 = cputime() 71 | 72 | signal = (s,) 73 | 74 | # generator for forward transformation 75 | c = slicq.forward(signal) 76 | 77 | # realize transform from generator 78 | c = list(c) 79 | 80 | # generator for backward transformation 81 | outseq = slicq.backward(c) 82 | 83 | # make single output array from iterator 84 | s_r = next(reblock(outseq, len(s), fulllast=False)) 85 | s_r = s_r.real 86 | 87 | t2 = cputime() 88 | 89 | norm = lambda x: np.sqrt(np.sum(np.abs(np.square(np.abs(x))))) 90 | rec_err = norm(s-s_r)/norm(s) 91 | print("Reconstruction error: %.3e"%rec_err) 92 | print("Calculation time: %.3fs"%(t2-t1)) 93 | 94 | # Compare the sliced coefficients with non-sliced ones 95 | if False: 96 | # not implemented yet! 97 | test_coeff_quality(c, s, g, shift, M, options.sl_len, len(s)) 98 | 99 | if args.output: 100 | print("Writing audio file '%s'"%args.output) 101 | sf = Sndfile(args.output, mode='w', format=Format('wav','pcm24'), channels=1, samplerate=fs) 102 | sf.write_frames(s_r) 103 | sf.close() 104 | print("Done") 105 | 106 | if args.plot: 107 | print("Plotting t*f space") 108 | import matplotlib.pyplot as pl 109 | tr = np.array([[np.mean(np.abs(cj)) for cj in ci] for ci in c]) 110 | pl.imshow(np.log(np.flipud(tr.T)+1.e-10), aspect=float(tr.shape[0])/tr.shape[1]*0.5, interpolation='nearest') 111 | pl.show() 112 | -------------------------------------------------------------------------------- /nsgt/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | 13 | 14 | covered by Creative Commons Attribution-NonCommercial-ShareAlike license (CC BY-NC-SA) 15 | http://creativecommons.org/licenses/by-nc-sa/3.0/at/deed.en 16 | 17 | 18 | -- 19 | Original matlab code copyright follows: 20 | 21 | AUTHOR(s) : Monika Dörfler, Gino Angelo Velasco, Nicki Holighaus, 2010-2011 22 | 23 | COPYRIGHT : (c) NUHAG, Dept.Math., University of Vienna, AUSTRIA 24 | http://nuhag.eu/ 25 | Permission is granted to modify and re-distribute this 26 | code in any manner as long as this notice is preserved. 27 | All standard disclaimers apply. 28 | 29 | """ 30 | 31 | __version__ = '0.18' 32 | 33 | from .cq import NSGT, CQ_NSGT 34 | from .slicq import NSGT_sliced, CQ_NSGT_sliced 35 | from .fscale import Scale, OctScale, LogScale, LinScale, MelScale 36 | from warnings import warn 37 | 38 | try: 39 | from .audio import SndReader, SndWriter 40 | except ImportError: 41 | warn("Audio IO routines (scikits.audio module) could not be imported") 42 | -------------------------------------------------------------------------------- /nsgt/audio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2021 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | import subprocess as sp 16 | import os.path 17 | import re 18 | import sys 19 | from functools import reduce 20 | 21 | try: 22 | from pysndfile import PySndfile, construct_format 23 | except: 24 | PySndfile = None 25 | 26 | def sndreader(sf, blksz=2**16, dtype=np.float32): 27 | frames = sf.frames() 28 | if dtype is float: 29 | dtype = np.float64 # scikits.audiolab needs numpy types 30 | if blksz < 0: 31 | blksz = frames 32 | if sf.channels() > 1: 33 | channels = lambda s: s.T 34 | else: 35 | channels = lambda s: s.reshape((1,-1)) 36 | for offs in range(0, frames, blksz): 37 | data = sf.read_frames(min(frames-offs, blksz), dtype=dtype) 38 | yield channels(data) 39 | 40 | def sndwriter(sf, blkseq, maxframes=None): 41 | written = 0 42 | for b in blkseq: 43 | b = b.T 44 | if maxframes is not None: 45 | b = b[:maxframes-written] 46 | sf.write_frames(b) 47 | written += len(b) 48 | 49 | def findfile(fn, path=os.environ['PATH'].split(os.pathsep), matchFunc=os.path.isfile): 50 | for dirname in path: 51 | candidate = os.path.join(dirname, fn) 52 | if matchFunc(candidate): 53 | return candidate 54 | return None 55 | 56 | 57 | class SndReader: 58 | def __init__(self, fn, sr=None, chns=None, blksz=2**16, dtype=np.float32): 59 | fnd = False 60 | 61 | if not fnd and (PySndfile is not None): 62 | try: 63 | sf = PySndfile(fn, mode='r') 64 | except IOError: 65 | pass 66 | else: 67 | if (sr is None or sr == sf.samplerate()) and (chns is None or chns == sf.channels()): 68 | # no resampling required 69 | self.channels = sf.channels() 70 | self.samplerate = sf.samplerate() 71 | self.frames = sf.frames() 72 | 73 | self.rdr = sndreader(sf, blksz, dtype=dtype) 74 | fnd = True 75 | 76 | if not fnd: 77 | ffmpeg = findfile('ffmpeg') or findfile('avconv') 78 | if ffmpeg is not None: 79 | pipe = sp.Popen([ffmpeg,'-i', fn,'-'],stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE) 80 | fmtout = pipe.stderr.read() 81 | if (sys.version_info > (3, 0)): 82 | fmtout = fmtout.decode() 83 | m = re.match(r"^(ffmpeg|avconv) version.*Duration: (\d\d:\d\d:\d\d.\d\d),.*Audio: (.+), (\d+) Hz, (.+), (.+), (\d+) kb/s", " ".join(fmtout.split('\n'))) 84 | if m is not None: 85 | self.samplerate = int(m.group(4)) if not sr else int(sr) 86 | chdef = m.group(5) 87 | if chdef.endswith(" channels") and len(chdef.split()) == 2: 88 | self.channels = int(chdef.split()[0]) 89 | else: 90 | try: 91 | self.channels = {'mono':1, '1 channels (FL+FR)':1, 'stereo':2, 'hexadecagonal':16}[chdef] if not chns else chns 92 | except: 93 | print(f"Channel definition '{chdef}' unknown") 94 | raise 95 | dur = reduce(lambda x,y: x*60+y, list(map(float, m.group(2).split(':')))) 96 | self.frames = int(dur*self.samplerate) # that's actually an estimation, because of potential resampling with round-off errors 97 | pipe = sp.Popen([ffmpeg, 98 | '-i', fn, 99 | '-f', 'f32le', 100 | '-acodec', 'pcm_f32le', 101 | '-ar', str(self.samplerate), 102 | '-ac', str(self.channels), 103 | '-'], 104 | # bufsize=self.samplerate*self.channels*4*50, 105 | stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE) 106 | def rdr(): 107 | bufsz = (blksz//self.channels)*self.channels*4 108 | while True: 109 | data = pipe.stdout.read(bufsz) 110 | if len(data) == 0: 111 | break 112 | data = np.fromstring(data, dtype=dtype) 113 | yield data.reshape((-1, self.channels)).T 114 | self.rdr = rdr() 115 | fnd = True 116 | 117 | if not fnd: 118 | raise IOError("Format not usable") 119 | 120 | def __call__(self): 121 | return self.rdr 122 | 123 | 124 | class SndWriter: 125 | def __init__(self, fn, samplerate, filefmt='wav', datafmt='pcm16', channels=1): 126 | fmt = construct_format(filefmt, datafmt) 127 | self.sf = PySndfile(fn, mode='w', format=fmt, channels=channels, samplerate=samplerate) 128 | 129 | def __call__(self, sigblks, maxframes=None): 130 | sndwriter(self.sf, sigblks, maxframes=None) 131 | 132 | -------------------------------------------------------------------------------- /nsgt/cq.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | 13 | -- 14 | Original matlab code copyright follows: 15 | 16 | AUTHOR(s) : Monika Dörfler, Gino Angelo Velasco, Nicki Holighaus, 2010-2011 17 | 18 | COPYRIGHT : (c) NUHAG, Dept.Math., University of Vienna, AUSTRIA 19 | http://nuhag.eu/ 20 | Permission is granted to modify and re-distribute this 21 | code in any manner as long as this notice is preserved. 22 | All standard disclaimers apply. 23 | 24 | """ 25 | 26 | from .nsgfwin_sl import nsgfwin 27 | from .nsdual import nsdual 28 | from .nsgtf import nsgtf 29 | from .nsigtf import nsigtf 30 | from .util import calcwinrange 31 | from .fscale import OctScale 32 | from math import ceil 33 | 34 | class NSGT: 35 | def __init__(self, scale, fs, Ls, real=True, matrixform=False, reducedform=0, multichannel=False, measurefft=False, multithreading=False, dtype=float): 36 | assert fs > 0 37 | assert Ls > 0 38 | assert 0 <= reducedform <= 2 39 | 40 | self.scale = scale 41 | self.fs = fs 42 | self.Ls = Ls 43 | self.real = real 44 | self.measurefft = measurefft 45 | self.multithreading = multithreading 46 | self.reducedform = reducedform 47 | 48 | self.frqs,self.q = scale() 49 | 50 | # calculate transform parameters 51 | self.g,rfbas,self.M = nsgfwin(self.frqs, self.q, self.fs, self.Ls, sliced=False, dtype=dtype) 52 | 53 | if real: 54 | assert 0 <= reducedform <= 2 55 | sl = slice(reducedform,len(self.g)//2+1-reducedform) 56 | else: 57 | sl = slice(0,None) 58 | 59 | # coefficients per slice 60 | self.ncoefs = max(int(ceil(float(len(gii))/mii))*mii for mii,gii in zip(self.M[sl],self.g[sl])) 61 | 62 | if matrixform: 63 | if self.reducedform: 64 | rm = self.M[self.reducedform:len(self.M)//2+1-self.reducedform] 65 | self.M[:] = rm.max() 66 | else: 67 | self.M[:] = self.M.max() 68 | 69 | if multichannel: 70 | self.channelize = lambda s: s 71 | self.unchannelize = lambda s: s 72 | else: 73 | self.channelize = lambda s: (s,) 74 | self.unchannelize = lambda s: s[0] 75 | 76 | # calculate shifts 77 | self.wins,self.nn = calcwinrange(self.g, rfbas, self.Ls) 78 | # calculate dual windows 79 | self.gd = nsdual(self.g, self.wins, self.nn, self.M) 80 | 81 | self.fwd = lambda s: nsgtf(s, self.g, self.wins, self.nn, self.M, real=self.real, reducedform=self.reducedform, measurefft=self.measurefft, multithreading=self.multithreading) 82 | self.bwd = lambda c: nsigtf(c, self.gd, self.wins, self.nn, self.Ls, real=self.real, reducedform=self.reducedform, measurefft=self.measurefft, multithreading=self.multithreading) 83 | 84 | @property 85 | def coef_factor(self): 86 | return float(self.ncoefs)/self.sl_len 87 | 88 | @property 89 | def slice_coefs(self): 90 | return self.ncoefs 91 | 92 | 93 | def forward(self, s): 94 | 'transform' 95 | s = self.channelize(s) 96 | c = list(map(self.fwd, s)) 97 | return self.unchannelize(c) 98 | 99 | def backward(self, c): 100 | 'inverse transform' 101 | c = self.channelize(c) 102 | s = list(map(self.bwd,c)) 103 | return self.unchannelize(s) 104 | 105 | class CQ_NSGT(NSGT): 106 | def __init__(self, fmin, fmax, bins, fs, Ls, real=True, matrixform=False, reducedform=0, multichannel=False, measurefft=False, multithreading=False): 107 | assert fmin > 0 108 | assert fmax > fmin 109 | assert bins > 0 110 | 111 | self.fmin = fmin 112 | self.fmax = fmax 113 | self.bins = bins 114 | 115 | scale = OctScale(fmin, fmax, bins) 116 | NSGT.__init__(self, scale, fs, Ls, real, matrixform=matrixform, reducedform=reducedform, multichannel=multichannel, measurefft=measurefft, multithreading=multithreading) 117 | -------------------------------------------------------------------------------- /nsgt/fft.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | from warnings import warn 16 | 17 | # Try engines in order of: 18 | # PyFFTW3 19 | # pyFFTW 20 | # numpy.fftpack 21 | try: 22 | import fftw3, fftw3f 23 | except ImportError: 24 | fftw3 = None 25 | fftw3f = None 26 | try: 27 | import pyfftw 28 | except ImportError: 29 | pyfftw = None 30 | 31 | 32 | if fftw3 is not None and fftw3f is not None: 33 | ENGINE = "PYFFTW3" 34 | # Use fftw3 methods 35 | class fftpool: 36 | def __init__(self, measure, dtype=float): 37 | self.measure = measure 38 | self.dtype = np.dtype(dtype) 39 | dtsz = self.dtype.itemsize 40 | if dtsz == 4: 41 | self.tpfloat = np.float32 42 | self.tpcplx = np.complex64 43 | self.fftw = fftw3f 44 | elif dtsz == 8: 45 | self.tpfloat = np.float64 46 | self.tpcplx = np.complex128 47 | self.fftw = fftw3 48 | else: 49 | raise TypeError("nsgt.fftpool: dtype '%s' not supported"%repr(self.dtype)) 50 | self.pool = {} 51 | 52 | def __call__(self, x, outn=None, ref=False): 53 | lx = len(x) 54 | try: 55 | transform = self.pool[lx] 56 | except KeyError: 57 | transform = self.init(lx, measure=self.measure, outn=outn) 58 | self.pool[lx] = transform 59 | plan,pre,post = transform 60 | if pre is not None: 61 | x = pre(x) 62 | plan.inarray[:] = x 63 | plan() 64 | if not ref: 65 | tx = plan.outarray.copy() 66 | else: 67 | tx = plan.outarray 68 | if post is not None: 69 | tx = post(tx) 70 | return tx 71 | 72 | class fftp(fftpool): 73 | def __init__(self, measure=False, dtype=float): 74 | fftpool.__init__(self, measure, dtype=dtype) 75 | def init(self, n, measure, outn): 76 | inp = self.fftw.create_aligned_array(n, dtype=self.tpcplx) 77 | outp = self.fftw.create_aligned_array(n, dtype=self.tpcplx) 78 | plan = self.fftw.Plan(inp, outp, direction='forward', flags=('measure' if measure else 'estimate',)) 79 | return (plan,None,None) 80 | 81 | class rfftp(fftpool): 82 | def __init__(self, measure=False, dtype=float): 83 | fftpool.__init__(self, measure, dtype=dtype) 84 | def init(self, n, measure, outn): 85 | inp = self.fftw.create_aligned_array(n, dtype=self.tpfloat) 86 | outp = self.fftw.create_aligned_array(n//2+1, dtype=self.tpcplx) 87 | plan = self.fftw.Plan(inp, outp, direction='forward', realtypes='halfcomplex r2c',flags=('measure' if measure else 'estimate',)) 88 | return (plan,None,None) 89 | 90 | class ifftp(fftpool): 91 | def __init__(self, measure=False, dtype=float): 92 | fftpool.__init__(self, measure, dtype=dtype) 93 | def init(self, n, measure, outn): 94 | inp = self.fftw.create_aligned_array(n, dtype=self.tpcplx) 95 | outp = self.fftw.create_aligned_array(n, dtype=self.tpcplx) 96 | plan = self.fftw.Plan(inp, outp, direction='backward', flags=('measure' if measure else 'estimate',)) 97 | return (plan,None,lambda x: x/len(x)) 98 | 99 | class irfftp(fftpool): 100 | def __init__(self, measure=False, dtype=float): 101 | fftpool.__init__(self, measure, dtype=dtype) 102 | def init(self, n, measure, outn): 103 | inp = self.fftw.create_aligned_array(n, dtype=self.tpcplx) 104 | outp = self.fftw.create_aligned_array(outn if outn is not None else (n-1)*2, dtype=self.tpfloat) 105 | plan = self.fftw.Plan(inp, outp, direction='backward', realtypes='halfcomplex c2r', flags=('measure' if measure else 'estimate',)) 106 | return (plan,lambda x: x[:n],lambda x: x/len(x)) 107 | elif pyfftw is not None: 108 | ENGINE = "PYFFTW" 109 | # Monkey patch in pyFFTW Numpy interface 110 | np.fft = pyfftw.interfaces.numpy_fft 111 | original_fft = np.fft 112 | # Enable cache to keep wisdom, etc. 113 | pyfftw.interfaces.cache.enable() 114 | else: 115 | # fall back to numpy methods 116 | warn("nsgt.fft falling back to numpy.fft") 117 | ENGINE = "NUMPY" 118 | 119 | if ENGINE in ["PYFFTW", "NUMPY"]: 120 | class fftp: 121 | def __init__(self, measure=False, dtype=float): 122 | pass 123 | def __call__(self,x, outn=None, ref=False): 124 | return np.fft.fft(x) 125 | class ifftp: 126 | def __init__(self, measure=False, dtype=float): 127 | pass 128 | def __call__(self,x, outn=None, n=None, ref=False): 129 | return np.fft.ifft(x,n=n) 130 | class rfftp: 131 | def __init__(self, measure=False, dtype=float): 132 | pass 133 | def __call__(self,x, outn=None, ref=False): 134 | return np.fft.rfft(x) 135 | class irfftp: 136 | def __init__(self, measure=False, dtype=float): 137 | pass 138 | def __call__(self,x,outn=None,ref=False): 139 | return np.fft.irfft(x,n=outn) 140 | -------------------------------------------------------------------------------- /nsgt/fscale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | 16 | class Scale: 17 | dbnd = 1.e-8 18 | 19 | def __init__(self, bnds): 20 | self.bnds = bnds 21 | 22 | def __len__(self): 23 | return self.bnds 24 | 25 | def Q(self, bnd=None): 26 | # numerical differentiation (if self.Q not defined by sub-class) 27 | if bnd is None: 28 | bnd = np.arange(self.bnds) 29 | return self.F(bnd)*self.dbnd/(self.F(bnd+self.dbnd)-self.F(bnd-self.dbnd)) 30 | 31 | def __call__(self): 32 | f = np.array([self.F(b) for b in range(self.bnds)],dtype=float) 33 | q = np.array([self.Q(b) for b in range(self.bnds)],dtype=float) 34 | return f,q 35 | 36 | 37 | class OctScale(Scale): 38 | def __init__(self, fmin, fmax, bpo, beyond=0): 39 | """ 40 | @param fmin: minimum frequency (Hz) 41 | @param fmax: maximum frequency (Hz) 42 | @param bpo: bands per octave (int) 43 | @param beyond: number of frequency bands below fmin and above fmax (int) 44 | """ 45 | lfmin = np.log2(fmin) 46 | lfmax = np.log2(fmax) 47 | bnds = int(np.ceil((lfmax-lfmin)*bpo))+1 48 | Scale.__init__(self, bnds+beyond*2) 49 | odiv = (lfmax-lfmin)/(bnds-1) 50 | lfmin_ = lfmin-odiv*beyond 51 | lfmax_ = lfmax+odiv*beyond 52 | self.fmin = 2**lfmin_ 53 | self.fmax = 2**lfmax_ 54 | self.pow2n = 2**odiv 55 | self.q = np.sqrt(self.pow2n)/(self.pow2n-1.)/2. 56 | 57 | def F(self, bnd=None): 58 | return self.fmin*self.pow2n**(bnd if bnd is not None else np.arange(self.bnds)) 59 | 60 | def Q(self, bnd=None): 61 | return self.q 62 | 63 | 64 | class LogScale(Scale): 65 | def __init__(self, fmin, fmax, bnds, beyond=0): 66 | """ 67 | @param fmin: minimum frequency (Hz) 68 | @param fmax: maximum frequency (Hz) 69 | @param bnds: number of frequency bands (int) 70 | @param beyond: number of frequency bands below fmin and above fmax (int) 71 | """ 72 | Scale.__init__(self, bnds+beyond*2) 73 | lfmin = np.log2(fmin) 74 | lfmax = np.log2(fmax) 75 | odiv = (lfmax-lfmin)/(bnds-1) 76 | lfmin_ = lfmin-odiv*beyond 77 | lfmax_ = lfmax+odiv*beyond 78 | self.fmin = 2**lfmin_ 79 | self.fmax = 2**lfmax_ 80 | self.pow2n = 2**odiv 81 | self.q = np.sqrt(self.pow2n)/(self.pow2n-1.)/2. 82 | 83 | def F(self, bnd=None): 84 | return self.fmin*self.pow2n**(bnd if bnd is not None else np.arange(self.bnds)) 85 | 86 | def Q(self, bnd=None): 87 | return self.q 88 | 89 | 90 | class LinScale(Scale): 91 | def __init__(self, fmin, fmax, bnds, beyond=0): 92 | """ 93 | @param fmin: minimum frequency (Hz) 94 | @param fmax: maximum frequency (Hz) 95 | @param bnds: number of frequency bands (int) 96 | @param beyond: number of frequency bands below fmin and above fmax (int) 97 | """ 98 | self.df = float(fmax-fmin)/(bnds-1) 99 | Scale.__init__(self, bnds+beyond*2) 100 | self.fmin = float(fmin)-self.df*beyond 101 | if self.fmin <= 0: 102 | raise ValueError("Frequencies must be > 0.") 103 | self.fmax = float(fmax)+self.df*beyond 104 | 105 | def F(self, bnd=None): 106 | return (bnd if bnd is not None else np.arange(self.bnds))*self.df+self.fmin 107 | 108 | def Q(self, bnd=None): 109 | return self.F(bnd)/(self.df*2) 110 | 111 | 112 | def hz2mel(f): 113 | "\cite{shannon:2003}" 114 | return np.log10(f/700.+1.)*2595. 115 | 116 | 117 | def mel2hz(m): 118 | "\cite{shannon:2003}" 119 | return (np.power(10.,m/2595.)-1.)*700. 120 | 121 | 122 | class MelScale(Scale): 123 | def __init__(self, fmin, fmax, bnds, beyond=0): 124 | """ 125 | @param fmin: minimum frequency (Hz) 126 | @param fmax: maximum frequency (Hz) 127 | @param bnds: number of frequency bands (int) 128 | @param beyond: number of frequency bands below fmin and above fmax (int) 129 | """ 130 | mmin = hz2mel(fmin) 131 | mmax = hz2mel(fmax) 132 | Scale.__init__(self, bnds+beyond*2) 133 | self.fmin = float(fmin) 134 | self.fmax = float(fmax) 135 | self.mbnd = (mmax-mmin)/(bnds-1) # mels per band 136 | self.mmin = mmin-self.mbnd*beyond 137 | self.mmax = mmax+self.mbnd*beyond 138 | 139 | def F(self, bnd=None): 140 | if bnd is None: 141 | bnd = np.arange(self.bnds) 142 | return mel2hz(bnd*self.mbnd+self.mmin) 143 | 144 | def Q1(self, bnd=None): # obviously not exact 145 | if bnd is None: 146 | bnd = np.arange(self.bnds) 147 | mel = bnd*self.mbnd+self.mmin 148 | odivs = (np.exp(mel/-1127.)-1.)*(-781.177/self.mbnd) 149 | pow2n = np.power(2, 1./odivs) 150 | return np.sqrt(pow2n)/(pow2n-1.)/2. 151 | -------------------------------------------------------------------------------- /nsgt/nsdual.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Thomas Grill, 2011-2015 5 | http://grrrr.org/nsgt 6 | 7 | Austrian Research Institute for Artificial Intelligence (OFAI) 8 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 9 | 10 | -- 11 | Original matlab code comments follow: 12 | 13 | NSDUAL.M - Nicki Holighaus 02.02.11 14 | 15 | Computes (for the painless case) the dual frame corresponding to a given 16 | non-stationary Gabor frame specified by the windows 'g' and time shifts 17 | 'shift'. 18 | 19 | Note, the time shifts corresponding to the dual window sequence is the 20 | same as the original shift sequence and as such already given. 21 | 22 | This routine's output can be used to achieve reconstruction of a signal 23 | from its non-stationary Gabor coefficients using the inverse 24 | non-stationary Gabor transform 'nsigt'. 25 | 26 | More information on Non-stationary Gabor transforms 27 | can be found at: 28 | 29 | http://www.univie.ac.at/nonstatgab/ 30 | 31 | minor edit by Gino Velasco 23.02.11 32 | """ 33 | 34 | import numpy as np 35 | 36 | from .util import chkM 37 | 38 | def nsdual(g, wins, nn, M=None): 39 | 40 | M = chkM(M,g) 41 | 42 | # Construct the diagonal of the frame operator matrix explicitly 43 | x = np.zeros((nn,), dtype=float) 44 | for gi,mii,sl in zip(g, M, wins): 45 | xa = np.square(np.fft.fftshift(gi)) 46 | xa *= mii 47 | x[sl] += xa 48 | 49 | # could be more elegant... 50 | # (w1a,w1b),(w2a,w2b) = sl 51 | # x[w1a] += xa[:w1a.stop-w1a.start] 52 | # xa = xa[w1a.stop-w1a.start:] 53 | # x[w1b] += xa[:w1b.stop-w1b.start] 54 | # xa = xa[w1b.stop-w1b.start:] 55 | # x[w2a] += xa[:w2a.stop-w2a.start] 56 | # xa = xa[w2a.stop-w2a.start:] 57 | # x[w2b] += xa[:w2b.stop-w2b.start] 58 | ## xa = xa[w1b.stop-w1b.start:] 59 | 60 | # Using the frame operator and the original window sequence, compute 61 | # the dual window sequence 62 | # gd = [gi/N.fft.ifftshift(N.hstack((x[wi[0][0]],x[wi[0][1]],x[wi[1][0]],x[wi[1][1]]))) for gi,wi in izip(g,wins)] 63 | gd = [gi/np.fft.ifftshift(x[wi]) for gi,wi in zip(g,wins)] 64 | return gd 65 | -------------------------------------------------------------------------------- /nsgt/nsgfwin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Thomas Grill, 2011-2015 5 | http://grrrr.org/nsgt 6 | 7 | -- 8 | Original matlab code comments follow: 9 | 10 | NSGFWIN.M 11 | --------------------------------------------------------------- 12 | [g,rfbas,M]=nsgfwin(fmin,bins,sr,Ls) creates a set of windows whose 13 | centers correspond to center frequencies to be 14 | used for the nonstationary Gabor transform with varying Q-factor. 15 | --------------------------------------------------------------- 16 | 17 | INPUT : fmin ...... Minimum frequency (in Hz) 18 | bins ...... Vector consisting of the number of bins per octave 19 | sr ........ Sampling rate (in Hz) 20 | Ls ........ Length of signal (in samples) 21 | 22 | OUTPUT : g ......... Cell array of window functions. 23 | rfbas ..... Vector of positions of the center frequencies. 24 | M ......... Vector of lengths of the window functions. 25 | 26 | AUTHOR(s) : Monika Dörfler, Gino Angelo Velasco, Nicki Holighaus, 2010 27 | 28 | COPYRIGHT : (c) NUHAG, Dept.Math., University of Vienna, AUSTRIA 29 | http://nuhag.eu/ 30 | Permission is granted to modify and re-distribute this 31 | code in any manner as long as this notice is preserved. 32 | All standard disclaimers apply. 33 | 34 | EXTERNALS : firwin 35 | """ 36 | 37 | import numpy as np 38 | from .util import hannwin,_isseq 39 | 40 | def nsgfwin(fmin, fmax ,bins, sr, Ls, min_win=4): 41 | 42 | nf = sr/2 43 | 44 | if fmax > nf: 45 | fmax = nf 46 | 47 | b = np.ceil(np.log2(fmax/fmin))+1 48 | 49 | if not _isseq(bins): 50 | bins = np.ones(b,dtype=int)*bins 51 | elif len(bins) < b: 52 | # TODO: test this branch! 53 | bins[bins <= 0] = 1 54 | bins = np.concatenate((bins, np.ones(b-len(bins), dtype=int)*np.min(bins))) 55 | 56 | fbas = [] 57 | for kk,bkk in enumerate(bins): 58 | r = np.arange(kk*bkk, (kk+1)*bkk, dtype=float) 59 | # TODO: use N.logspace instead 60 | fbas.append(2**(r/bkk)*fmin) 61 | fbas = np.concatenate(fbas) 62 | 63 | if fbas[np.min(np.where(fbas>=fmax))] >= nf: 64 | fbas = fbas[:np.max(np.where(fbas=fmax))+1] 68 | 69 | lbas = len(fbas) 70 | fbas = np.concatenate(((0.,), fbas, (nf,), sr-fbas[::-1])) 71 | fbas *= float(Ls)/sr 72 | 73 | # TODO: put together with array indexing 74 | M = np.empty(fbas.shape, dtype=int) 75 | M[0] = np.round(2.*fmin*Ls/sr) 76 | for k in range(1, 2*lbas+1): 77 | M[k] = np.round(fbas[k+1]-fbas[k-1]) 78 | M[-1] = np.round(Ls-fbas[-2]) 79 | 80 | M = np.clip(M, min_win, np.inf).astype(int) 81 | g = [hannwin(m) for m in M] 82 | 83 | fbas[lbas] = (fbas[lbas-1]+fbas[lbas+1])/2 84 | fbas[lbas+2] = Ls-fbas[lbas] 85 | rfbas = np.round(fbas).astype(int) 86 | 87 | return g,rfbas,M 88 | -------------------------------------------------------------------------------- /nsgt/nsgfwin_sl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Thomas Grill, 2011-2016 5 | http://grrrr.org/nsgt 6 | 7 | -- 8 | Original matlab code comments follow: 9 | 10 | NSGFWIN.M 11 | --------------------------------------------------------------- 12 | [g,rfbas,M]=nsgfwin(fmin,bins,sr,Ls) creates a set of windows whose 13 | centers correspond to center frequencies to be 14 | used for the nonstationary Gabor transform with varying Q-factor. 15 | --------------------------------------------------------------- 16 | 17 | INPUT : fmin ...... Minimum frequency (in Hz) 18 | bins ...... Vector consisting of the number of bins per octave 19 | sr ........ Sampling rate (in Hz) 20 | Ls ........ Length of signal (in samples) 21 | 22 | OUTPUT : g ......... Cell array of window functions. 23 | rfbas ..... Vector of positions of the center frequencies. 24 | M ......... Vector of lengths of the window functions. 25 | 26 | AUTHOR(s) : Monika Dörfler, Gino Angelo Velasco, Nicki Holighaus, 2010 27 | 28 | COPYRIGHT : (c) NUHAG, Dept.Math., University of Vienna, AUSTRIA 29 | http://nuhag.eu/ 30 | Permission is granted to modify and re-distribute this 31 | code in any manner as long as this notice is preserved. 32 | All standard disclaimers apply. 33 | 34 | EXTERNALS : firwin 35 | """ 36 | 37 | import numpy as np 38 | from .util import hannwin, blackharr, blackharrcw 39 | from math import ceil 40 | from warnings import warn 41 | from itertools import chain 42 | 43 | def nsgfwin(f, q, sr, Ls, sliced=True, min_win=4, Qvar=1, dowarn=True, dtype=np.float64): 44 | nf = sr/2. 45 | 46 | lim = np.argmax(f > 0) 47 | if lim != 0: 48 | # f partly <= 0 49 | f = f[lim:] 50 | q = q[lim:] 51 | 52 | lim = np.argmax(f >= nf) 53 | if lim != 0: 54 | # f partly >= nf 55 | f = f[:lim] 56 | q = q[:lim] 57 | 58 | assert len(f) == len(q) 59 | assert np.all((f[1:]-f[:-1]) > 0) # frequencies must be increasing 60 | assert np.all(q > 0) # all q must be > 0 61 | 62 | qneeded = f*(Ls/(8.*sr)) 63 | if np.any(q >= qneeded) and dowarn: 64 | warn("Q-factor too high for frequencies %s"%",".join("%.2f"%fi for fi in f[q >= qneeded])) 65 | 66 | fbas = f 67 | lbas = len(fbas) 68 | 69 | frqs = np.concatenate(((0.,),fbas,(nf,))) 70 | 71 | fbas = np.concatenate((frqs,sr-frqs[-2:0:-1])) 72 | 73 | # at this point: fbas.... frequencies in Hz 74 | 75 | fbas *= float(Ls)/sr 76 | 77 | # print "fbas",fbas 78 | 79 | # Omega[k] in the paper 80 | if sliced: 81 | M = np.zeros(fbas.shape, dtype=float) 82 | M[0] = 2*fbas[1] 83 | M[1] = fbas[1]/q[0] #(2**(1./bins[0])-2**(-1./bins[0])) 84 | for k in chain(range(2,lbas),(lbas+1,)): 85 | M[k] = fbas[k+1]-fbas[k-1] 86 | M[lbas] = fbas[lbas]/q[lbas-1] #(2**(1./bins[-1])-2**(-1./bins[-1])) 87 | # M[lbas+1] = fbas[lbas]/q[lbas-1] #(2**(1./bins[-1])-2**(-1./bins[-1])) 88 | M[lbas+2:2*(lbas+1)] = M[lbas:0:-1] 89 | # M[-1] = M[1] 90 | M *= Qvar/4. 91 | M = np.round(M).astype(int) 92 | M *= 4 93 | else: 94 | M = np.zeros(fbas.shape, dtype=int) 95 | M[0] = np.round(2*fbas[1]) 96 | for k in range(1,2*lbas+1): 97 | M[k] = np.round(fbas[k+1]-fbas[k-1]) 98 | M[-1] = np.round(Ls-fbas[-2]) 99 | 100 | np.clip(M, min_win, np.inf, out=M) 101 | 102 | # print "M",list(M) 103 | 104 | if sliced: 105 | g = [blackharr(m).astype(dtype) for m in M] 106 | else: 107 | g = [hannwin(m).astype(dtype) for m in M] 108 | 109 | if sliced: 110 | for kk in (1,lbas+2): 111 | if M[kk-1] > M[kk]: 112 | g[kk-1] = np.ones(M[kk-1], dtype=g[kk-1].dtype) 113 | g[kk-1][M[kk-1]//2-M[kk]//2:M[kk-1]//2+int(ceil(M[kk]/2.))] = hannwin(M[kk]) 114 | 115 | rfbas = np.round(fbas/2.).astype(int)*2 116 | else: 117 | fbas[lbas] = (fbas[lbas-1]+fbas[lbas+1])/2 118 | fbas[lbas+2] = Ls-fbas[lbas] 119 | rfbas = np.round(fbas).astype(int) 120 | 121 | # print "rfbas",rfbas 122 | # print "g",g 123 | 124 | return g,rfbas,M 125 | 126 | 127 | def nsgfwin_new(f, q, sr, Ls, sliced=True, min_win=4, Qvar=1, dowarn=True): 128 | nf = sr/2. 129 | 130 | lim = np.argmax(f > 0) 131 | if lim != 0: 132 | # f partly <= 0 133 | f = f[lim:] 134 | q = q[lim:] 135 | 136 | lim = np.argmax(f >= nf) 137 | if lim != 0: 138 | # f partly >= nf 139 | f = f[:lim] 140 | q = q[:lim] 141 | 142 | assert len(f) == len(q) 143 | assert np.all((f[1:]-f[:-1]) > 0) # frequencies must be increasing 144 | assert np.all(q > 0) # all q must be > 0 145 | 146 | qneeded = f*(Ls/(8.*sr)) 147 | if np.any(q >= qneeded) and dowarn: 148 | warn("Q-factor too high for frequencies %s"%",".join("%.2f"%fi for fi in f[q >= qneeded])) 149 | 150 | fbas = f 151 | lbas = len(fbas) 152 | 153 | frqs = np.concatenate(((0.,),fbas,(nf,))) 154 | 155 | fbas = np.concatenate((frqs,sr-frqs[-2:0:-1])) 156 | 157 | # at this point: fbas.... frequencies in Hz 158 | 159 | fbas *= float(Ls)/sr 160 | 161 | # print "fbas",fbas 162 | 163 | # Omega[k] in the paper 164 | if sliced: 165 | M = np.zeros(fbas.shape, dtype=float) 166 | M[0] = 2*fbas[1] 167 | M[1] = fbas[1]/q[0] #(2**(1./bins[0])-2**(-1./bins[0])) 168 | for k in chain(range(2,lbas),(lbas+1,)): 169 | M[k] = fbas[k+1]-fbas[k-1] 170 | M[lbas] = fbas[lbas]/q[lbas-1] #(2**(1./bins[-1])-2**(-1./bins[-1])) 171 | # M[lbas+1] = fbas[lbas]/q[lbas-1] #(2**(1./bins[-1])-2**(-1./bins[-1])) 172 | M[lbas+2:2*(lbas+1)] = M[lbas:0:-1] 173 | # M[-1] = M[1] 174 | 175 | ### M *= Qvar/4. 176 | ### M = N.round(M).astype(int) 177 | ### M *= 4 178 | else: 179 | M = np.zeros(fbas.shape, dtype=int) 180 | M[0] = np.round(2*fbas[1]) 181 | for k in range(1, 2*lbas+1): 182 | M[k] = np.round(fbas[k+1]-fbas[k-1]) 183 | M[-1] = np.round(Ls-fbas[-2]) 184 | 185 | np.clip(M, min_win, np.inf, out=M) 186 | 187 | if sliced: 188 | ### g = [blackharr(m) for m in M] 189 | 190 | rfbas = np.concatenate((np.floor(fbas[:lbas+2]), np.ceil(fbas[lbas+2:]))) 191 | corr_shift = fbas-rfbas 192 | 193 | # shift = N.concatenate(([(-rfbas[-1])%Ls],N.diff(rfbas))) 194 | 195 | g,M = np.array([blackharrcw(m*Qvar, csh) for m,csh in zip(M, corr_shift)]).T #### 196 | # M = N.ceil(M/4).astype(int)*4 # bailing out for some strange reason.... 197 | M = np.array([ceil(m/4)*4 for m in M], dtype=int) 198 | else: 199 | g = [hannwin(m) for m in M] 200 | 201 | 202 | 203 | if sliced: 204 | for kk in (1,lbas+2): 205 | if M[kk-1] > M[kk]: 206 | g[kk-1] = np.ones(M[kk-1],dtype=g[kk-1].dtype) 207 | g[kk-1][M[kk-1]//2-M[kk]//2:M[kk-1]//2+ceil(M[kk]/2.)] = hannwin(M[kk]) 208 | 209 | rfbas = np.round(fbas/2.).astype(int)*2 210 | else: 211 | fbas[lbas] = (fbas[lbas-1]+fbas[lbas+1])/2 212 | fbas[lbas+2] = Ls-fbas[lbas] 213 | rfbas = np.round(fbas).astype(int) 214 | 215 | # print "rfbas",rfbas 216 | # print "g",g 217 | 218 | return g,rfbas,M 219 | -------------------------------------------------------------------------------- /nsgt/nsgtf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | from math import ceil 16 | 17 | from .util import chkM 18 | from .fft import fftp, ifftp 19 | 20 | try: 21 | # try to import cython version 22 | from _nsgtf_loop import nsgtf_loop 23 | except ImportError: 24 | nsgtf_loop = None 25 | 26 | if nsgtf_loop is None: 27 | from .nsgtf_loop import nsgtf_loop 28 | 29 | if False: 30 | # what about theano? 31 | try: 32 | import theano as T 33 | except ImportError: 34 | T = None 35 | 36 | try: 37 | import multiprocessing as MP 38 | except ImportError: 39 | MP = None 40 | 41 | 42 | #@profile 43 | def nsgtf_sl(f_slices, g, wins, nn, M=None, real=False, reducedform=0, measurefft=False, multithreading=False): 44 | M = chkM(M,g) 45 | dtype = g[0].dtype 46 | 47 | fft = fftp(measure=measurefft, dtype=dtype) 48 | ifft = ifftp(measure=measurefft, dtype=dtype) 49 | 50 | if real: 51 | assert 0 <= reducedform <= 2 52 | sl = slice(reducedform,len(g)//2+1-reducedform) 53 | else: 54 | sl = slice(0,None) 55 | 56 | maxLg = max(int(ceil(float(len(gii))/mii))*mii for mii,gii in zip(M[sl],g[sl])) 57 | temp0 = None 58 | 59 | if multithreading and MP is not None: 60 | mmap = MP.Pool().map 61 | else: 62 | mmap = map 63 | 64 | loopparams = [] 65 | for mii,gii,win_range in zip(M[sl],g[sl],wins[sl]): 66 | Lg = len(gii) 67 | col = int(ceil(float(Lg)/mii)) 68 | assert col*mii >= Lg 69 | gi1 = gii[:(Lg+1)//2] 70 | gi2 = gii[-(Lg//2):] 71 | p = (mii,gii,gi1,gi2,win_range,Lg,col) 72 | loopparams.append(p) 73 | 74 | # main loop over slices 75 | for f in f_slices: 76 | Ls = len(f) 77 | 78 | # some preparation 79 | ft = fft(f) 80 | 81 | if temp0 is None: 82 | # pre-allocate buffer (delayed because of dtype) 83 | temp0 = np.empty(maxLg, dtype=ft.dtype) 84 | 85 | # A small amount of zero-padding might be needed (e.g. for scale frames) 86 | if nn > Ls: 87 | ft = np.concatenate((ft, np.zeros(nn-Ls, dtype=ft.dtype))) 88 | 89 | # The actual transform 90 | c = nsgtf_loop(loopparams, ft, temp0) 91 | 92 | # TODO: if matrixform, perform "2D" FFT along one axis 93 | # this could also be nicely parallelized 94 | y = list(mmap(ifft,c)) 95 | 96 | yield y 97 | 98 | 99 | # non-sliced version 100 | def nsgtf(f, g, wins, nn, M=None, real=False, reducedform=0, measurefft=False, multithreading=False): 101 | return next(nsgtf_sl((f,), g, wins, nn, M=M, real=real, reducedform=reducedform, measurefft=measurefft, multithreading=multithreading)) 102 | -------------------------------------------------------------------------------- /nsgt/nsgtf_loop.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | 16 | def nsgtf_loop(loopparams, ft, temp0): 17 | c = [] # Initialization of the result 18 | 19 | # The actual transform 20 | # TODO: stuff loop into theano 21 | for mii,_,gi1,gi2,win_range,Lg,col in loopparams: 22 | # Lg = len(gii) 23 | # if the number of time channels is too small (mii < Lg), aliasing is introduced 24 | # wrap around and sum up in the end (below) 25 | # col = int(ceil(float(Lg)/mii)) # normally col == 1 26 | # assert col*mii >= Lg 27 | 28 | temp = temp0[:col*mii] 29 | 30 | # original version 31 | # t = ft[win_range]*N.fft.fftshift(N.conj(gii)) 32 | # temp[:(Lg+1)//2] = t[Lg//2:] # if mii is odd, this is of length mii-mii//2 33 | # temp[-(Lg//2):] = t[:Lg//2] # if mii is odd, this is of length mii//2 34 | 35 | # modified version to avoid superfluous memory allocation 36 | t1 = temp[:(Lg+1)//2] 37 | t1[:] = gi1 # if mii is odd, this is of length mii-mii//2 38 | t2 = temp[-(Lg//2):] 39 | t2[:] = gi2 # if mii is odd, this is of length mii//2 40 | 41 | ftw = ft[win_range] 42 | t2 *= ftw[:Lg//2] 43 | t1 *= ftw[Lg//2:] 44 | 45 | # (wh1a,wh1b),(wh2a,wh2b) = win_range 46 | # t2[:wh1a.stop-wh1a.start] *= ft[wh1a] 47 | # t2[wh1a.stop-wh1a.start:] *= ft[wh1b] 48 | # t1[:wh2a.stop-wh2a.start] *= ft[wh2a] 49 | # t1[wh2a.stop-wh2a.start:] *= ft[wh2b] 50 | 51 | temp[(Lg+1)//2:-(Lg//2)] = 0 # clear gap (if any) 52 | 53 | if col > 1: 54 | temp = np.sum(temp.reshape((mii,-1)), axis=1) 55 | else: 56 | temp = temp.copy() 57 | 58 | c.append(temp) 59 | return c 60 | -------------------------------------------------------------------------------- /nsgt/nsgtf_loop.pyx: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | cimport numpy as np 16 | 17 | def nsgtf_loop(loopparams, np.ndarray ft not None, np.ndarray temp0 not None): 18 | cdef list c = [] # Initialization of the result 19 | 20 | # The actual transform 21 | # TODO: stuff loop into theano 22 | cdef int mii,Lg,col 23 | cdef np.ndarray gi1,gi2,t1,t2,ftw,temp,win_range 24 | for mii,_,gi1,gi2,win_range,Lg,col in loopparams: 25 | # Lg = len(gii) 26 | # if the number of time channels is too small (mii < Lg), aliasing is introduced 27 | # wrap around and sum up in the end (below) 28 | # col = int(ceil(float(Lg)/mii)) # normally col == 1 29 | # assert col*mii >= Lg 30 | 31 | temp = temp0[:col*mii] 32 | 33 | # original version 34 | # t = ft[win_range]*N.fft.fftshift(N.conj(gii)) 35 | # temp[:(Lg+1)//2] = t[Lg//2:] # if mii is odd, this is of length mii-mii//2 36 | # temp[-(Lg//2):] = t[:Lg//2] # if mii is odd, this is of length mii//2 37 | 38 | # modified version to avoid superfluous memory allocation 39 | t1 = temp[:(Lg+1)//2] 40 | t1[:] = gi1 # if mii is odd, this is of length mii-mii//2 41 | t2 = temp[-(Lg//2):] 42 | t2[:] = gi2 # if mii is odd, this is of length mii//2 43 | 44 | ftw = ft[win_range] 45 | t2 *= ftw[:Lg//2] 46 | t1 *= ftw[Lg//2:] 47 | 48 | # (wh1a,wh1b),(wh2a,wh2b) = win_range 49 | # t2[:wh1a.stop-wh1a.start] *= ft[wh1a] 50 | # t2[wh1a.stop-wh1a.start:] *= ft[wh1b] 51 | # t1[:wh2a.stop-wh2a.start] *= ft[wh2a] 52 | # t1[wh2a.stop-wh2a.start:] *= ft[wh2b] 53 | 54 | temp[(Lg+1)//2:-(Lg//2)] = 0 # clear gap (if any) 55 | 56 | if col > 1: 57 | temp = np.sum(temp.reshape((mii, -1)), axis=1) 58 | else: 59 | temp = temp.copy() 60 | 61 | c.append(temp) 62 | return c 63 | -------------------------------------------------------------------------------- /nsgt/nsigtf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Thomas Grill, 2011-2015 5 | http://grrrr.org/nsgt 6 | 7 | -- 8 | Original matlab code comments follow: 9 | 10 | NSIGTF.N - Gino Velasco 24.02.11 11 | 12 | fr = nsigtf(c,gd,shift,Ls) 13 | 14 | This is a modified version of nsigt.m for the case where the resolution 15 | evolves over frequency. 16 | 17 | Given the cell array 'c' of non-stationary Gabor coefficients, and a set 18 | of windows and frequency shifts, this function computes the corresponding 19 | inverse non-stationary Gabor transform. 20 | 21 | Input: 22 | c : Cell array of non-stationary Gabor coefficients 23 | gd : Cell array of Fourier transforms of the synthesis 24 | windows 25 | shift : Vector of frequency shifts 26 | Ls : Length of the analyzed signal 27 | 28 | Output: 29 | fr : Synthesized signal 30 | 31 | If a non-stationary Gabor frame was used to produce the coefficients 32 | and 'gd' is a corresponding dual frame, this function should give perfect 33 | reconstruction of the analyzed signal (up to numerical errors). 34 | 35 | The inverse transform is computed by simple 36 | overlap-add. For each entry of the cell array c, 37 | the coefficients of frequencies around a certain 38 | position in time, the Fourier transform 39 | is taken, giving 'frequency slices' of a signal. 40 | These slices are added onto each other with an overlap 41 | depending on the window lengths and positions, thus 42 | (re-)constructing the frequency side signal. In the 43 | last step, an inverse Fourier transform brings the signal 44 | back to the time side. 45 | 46 | More information can be found at: 47 | http://www.univie.ac.at/nonstatgab/ 48 | 49 | Edited by Nicki Holighaus 01.03.11 50 | """ 51 | 52 | import numpy as np 53 | from itertools import chain 54 | from .fft import fftp, ifftp, irfftp 55 | 56 | try: 57 | # try to import cython version 58 | from _nsigtf_loop import nsigtf_loop 59 | except ImportError: 60 | nsigtf_loop = None 61 | 62 | if nsigtf_loop is None: 63 | from .nsigtf_loop import nsigtf_loop 64 | 65 | if False: 66 | # what about theano? 67 | try: 68 | import theano as T 69 | except ImportError: 70 | T = None 71 | 72 | try: 73 | import multiprocessing as MP 74 | except ImportError: 75 | MP = None 76 | 77 | 78 | #@profile 79 | def nsigtf_sl(cseq, gd, wins, nn, Ls=None, real=False, reducedform=0, measurefft=False, multithreading=False): 80 | cseq = iter(cseq) 81 | dtype = gd[0].dtype 82 | 83 | fft = fftp(measure=measurefft, dtype=dtype) 84 | ifft = irfftp(measure=measurefft, dtype=dtype) if real else ifftp(measure=measurefft, dtype=dtype) 85 | 86 | if real: 87 | ln = len(gd)//2+1-reducedform*2 88 | fftsymm = lambda c: np.hstack((c[0],c[-1:0:-1])).conj() 89 | if reducedform: 90 | # no coefficients for f=0 and f=fs/2 91 | def symm(_fc): 92 | fc = list(_fc) 93 | return chain(fc, map(fftsymm, fc[::-1])) 94 | sl = lambda x: chain(x[reducedform:len(gd)//2+1-reducedform],x[len(gd)//2+reducedform:len(gd)+1-reducedform]) 95 | else: 96 | def symm(_fc): 97 | fc = list(_fc) 98 | return chain(fc, map(fftsymm, fc[-2:0:-1])) 99 | sl = lambda x: x 100 | else: 101 | ln = len(gd) 102 | symm = lambda fc: fc 103 | sl = lambda x: x 104 | 105 | maxLg = max(len(gdii) for gdii in sl(gd)) 106 | 107 | # get first slice 108 | c0 = next(cseq) 109 | 110 | fr = np.empty(nn, dtype=c0[0].dtype) # Allocate output 111 | temp0 = np.empty(maxLg, dtype=fr.dtype) # pre-allocation 112 | 113 | if multithreading and MP is not None: 114 | mmap = MP.Pool().map 115 | else: 116 | mmap = map 117 | 118 | loopparams = [] 119 | for gdii,win_range in zip(sl(gd), sl(wins)): 120 | Lg = len(gdii) 121 | temp = temp0[:Lg] 122 | wr1 = win_range[:(Lg)//2] 123 | wr2 = win_range[-((Lg+1)//2):] 124 | # wr1,wr2 = win_range 125 | sl1 = slice(None, (Lg+1)//2) 126 | sl2 = slice(-(Lg//2), None) 127 | p = (gdii,wr1,wr2,sl1,sl2,temp) 128 | loopparams.append(p) 129 | 130 | # main loop over slices 131 | for c in chain((c0,),cseq): 132 | assert len(c) == ln 133 | 134 | # do transforms on coefficients 135 | # TODO: for matrixform we could do a FFT on the whole matrix along one axis 136 | # this could also be nicely parallalized 137 | fc = mmap(fft, c) 138 | fc = symm(fc) 139 | 140 | # The overlap-add procedure including multiplication with the synthesis windows 141 | fr = nsigtf_loop(loopparams, fr, fc) 142 | 143 | ftr = fr[:nn//2+1] if real else fr 144 | 145 | sig = ifft(ftr, outn=nn) 146 | 147 | sig = sig[:Ls] # Truncate the signal to original length (if given) 148 | 149 | yield sig 150 | 151 | # non-sliced version 152 | def nsigtf(c, gd, wins, nn, Ls=None, real=False, reducedform=0, measurefft=False, multithreading=False): 153 | return next(nsigtf_sl((c,), gd, wins, nn, Ls=Ls, real=real, reducedform=reducedform, measurefft=measurefft, multithreading=multithreading)) 154 | -------------------------------------------------------------------------------- /nsgt/nsigtf_loop.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | 15 | 16 | def nsigtf_loop(loopparams, fr, fc): 17 | fr[:] = 0. 18 | # The overlap-add procedure including multiplication with the synthesis windows 19 | # TODO: stuff loop into theano 20 | for t,(gdii,wr1,wr2,sl1,sl2,temp) in zip(fc, loopparams): 21 | t1 = temp[sl1] 22 | t2 = temp[sl2] 23 | t1[:] = t[sl1] 24 | t2[:] = t[sl2] 25 | temp *= gdii 26 | temp *= len(t) 27 | 28 | fr[wr1] += t2 29 | fr[wr2] += t1 30 | 31 | return fr 32 | -------------------------------------------------------------------------------- /nsgt/nsigtf_loop.pyx: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | cimport numpy as np 16 | from itertools import izip 17 | 18 | def nsigtf_loop(loopparams, np.ndarray fr not None, fc): 19 | fr[:] = 0. 20 | # The overlap-add procedure including multiplication with the synthesis windows 21 | # TODO: stuff loop into theano 22 | cdef np.ndarray gdii,t,temp,t1,t2,wr1,wr2 23 | cdef slice sl1,sl2 24 | for t,(gdii,wr1,wr2,sl1,sl2,temp) in izip(fc, loopparams): 25 | t1 = temp[sl1] 26 | t2 = temp[sl2] 27 | t1[:] = t[sl1] 28 | t2[:] = t[sl2] 29 | temp *= gdii 30 | temp *= len(t) 31 | 32 | fr[wr1] += t2 33 | fr[wr2] += t1 34 | 35 | return fr 36 | -------------------------------------------------------------------------------- /nsgt/reblock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | 16 | 17 | def reblock(sseq, blocksize, dtype=None, fulllast=True, padding=0, multichannel=False): 18 | block = None 19 | dt = None 20 | chns = None 21 | 22 | if multichannel: 23 | channelize = lambda s: s 24 | unchannelize = lambda s: s 25 | else: 26 | channelize = lambda s: (s,) 27 | unchannelize = lambda s: s[0] 28 | 29 | for si in sseq: 30 | # iterate through sequence of sequences 31 | 32 | si = channelize(si) 33 | 34 | while True: 35 | if block is None: 36 | if dt is None: 37 | # output dtype still undefined 38 | if dtype is None: 39 | dt = type(si[0][0]) # take type is first input element 40 | else: 41 | dt = dtype 42 | chns = len(si) 43 | 44 | block = np.empty((chns,blocksize), dtype=dt) 45 | blockrem = block 46 | 47 | sout = [sj[:blockrem.shape[1]] for sj in si] 48 | avail = len(sout[0]) 49 | for blr,souti in zip(blockrem, sout): 50 | blr[:avail] = souti # copy data per channel 51 | si = [sj[avail:] for sj in si] # move ahead in input block 52 | blockrem = blockrem[:,avail:] # move ahead in output block 53 | 54 | if blockrem.shape[1] == 0: 55 | # output block is full 56 | yield unchannelize(block) 57 | block = None 58 | if len(si[0]) == 0: 59 | # input block is exhausted 60 | break 61 | 62 | if block is not None: 63 | if fulllast: 64 | blockrem[:] = padding # zero padding 65 | ret = block 66 | else: 67 | # output only filled part 68 | ret = block[:,:-len(blockrem[0])] 69 | yield unchannelize(ret) 70 | -------------------------------------------------------------------------------- /nsgt/slicing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | from .util import hannwin 16 | from .reblock import reblock 17 | from itertools import chain, cycle 18 | 19 | def makewnd(sl_len, tr_area): 20 | hhop = sl_len//4 21 | htr = tr_area//2 22 | # build window function within one slice (centered with transition areas around sl_len/4 and 3*sl_len/4 23 | w = hannwin(2*tr_area) # window is shifted 24 | tw = np.empty(sl_len, dtype=float) 25 | tw[:hhop-htr] = 0 26 | tw[hhop-htr:hhop+htr] = w[tr_area:] 27 | tw[hhop+htr:3*hhop-htr] = 1 28 | tw[3*hhop-htr:3*hhop+htr] = w[:tr_area] 29 | tw[3*hhop+htr:] = 0 30 | return tw 31 | 32 | def slicing(f, sl_len, tr_area): 33 | if tr_area%2 != 0: 34 | raise ValueError("Transition area 'tr_area' must be modulo 2") 35 | if sl_len%4 != 0: 36 | raise ValueError("Slice length 'sl_len' must be modulo 4") 37 | 38 | hhop = sl_len//4 # half hopsize 39 | 40 | tw = makewnd(sl_len, tr_area) 41 | # four parts of slice with centered window function 42 | tw = [tw[o:o+hhop] for o in range(0, sl_len, hhop)] 43 | 44 | # stream of hopsize/2 blocks with leading and trailing zero blocks 45 | fseq = reblock(f, hhop, dtype=float, fulllast=True, padding=0., multichannel=True) 46 | 47 | # get first block to deduce number of channels 48 | fseq0 = next(fseq) 49 | chns = len(fseq0) 50 | pad = np.zeros((chns,hhop), dtype=fseq0.dtype) 51 | # assemble a stream of front padding, already retrieved first block, the block stream and some tail padding 52 | fseq = chain((pad,pad,fseq0), fseq, (pad,pad,pad)) 53 | 54 | slices = [[slice(hhop*((i+3-k*2)%4), hhop*((i+3-k*2)%4+1)) for i in range(4)] for k in range(2)] 55 | slices = cycle(slices) 56 | 57 | past = [] 58 | for fi in fseq: 59 | past.append(fi) 60 | if len(past) == 4: 61 | f_slice = np.empty((chns,sl_len), dtype=fi.dtype) 62 | sl = next(slices) 63 | for sli,pi,twi in zip(sl, past, tw): 64 | f_slice[:,sli] = pi # signal 65 | f_slice[:,sli] *= twi # multiply with part of window function 66 | yield f_slice 67 | past = past[2:] # pop the two oldest slices 68 | -------------------------------------------------------------------------------- /nsgt/slicq.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | 13 | -- 14 | 15 | % Perfect reconstruction sliCQ 16 | 17 | % right now, even slice length (sl_len) is required. Parameters are the 18 | % same as NSGTF plus slice length, minimal required window length, 19 | % Q-factor variation, and test run parameters. 20 | """ 21 | 22 | import numpy as np 23 | from itertools import cycle, chain, tee 24 | from math import ceil 25 | 26 | from .slicing import slicing 27 | from .unslicing import unslicing 28 | from .nsdual import nsdual 29 | from .nsgfwin_sl import nsgfwin 30 | from .nsgtf import nsgtf_sl 31 | from .nsigtf import nsigtf_sl 32 | from .util import calcwinrange 33 | from .fscale import OctScale 34 | 35 | # one of the more expensive functions (32/400) 36 | def arrange(cseq, M, fwd): 37 | cseq = iter(cseq) 38 | try: 39 | c0 = next(cseq) # grab first stream element 40 | except StopIteration: 41 | return iter(()) 42 | cseq = chain((c0,), cseq) # push it back in 43 | M = list(map(len, c0[0])) # read off M from the coefficients 44 | ixs = ( 45 | [(slice(3*mkk//4, mkk), slice(0, 3*mkk//4)) for mkk in M], # odd 46 | [(slice(mkk//4, mkk), slice(0, mkk//4)) for mkk in M] # even 47 | ) 48 | if fwd: 49 | ixs = cycle(ixs) 50 | else: 51 | ixs = cycle(ixs[::-1]) 52 | 53 | return ([ 54 | [np.concatenate((ckk[ix0],ckk[ix1])) 55 | for ckk,(ix0,ix1) in zip(ci, ixi) 56 | ] 57 | for ci in cci 58 | ] 59 | for cci,ixi in zip(cseq, ixs) 60 | ) 61 | 62 | 63 | def starzip(iterables): 64 | def inner(itr, i): 65 | for t in itr: 66 | yield t[i] 67 | iterables = iter(iterables) 68 | it = next(iterables) # we need that to determine the length of one element 69 | iterables = chain((it,), iterables) 70 | return [inner(itr, i) for i,itr in enumerate(tee(iterables, len(it)))] 71 | 72 | 73 | def chnmap(gen, seq): 74 | chns = starzip(seq) # returns a list of generators (one for each channel) 75 | gens = list(map(gen, chns)) # generators including transformation 76 | return zip(*gens) # packing channels to one generator yielding channel tuples 77 | 78 | 79 | class NSGT_sliced: 80 | def __init__(self, scale, sl_len, tr_area, fs, 81 | min_win=16, Qvar=1, 82 | real=False, recwnd=False, matrixform=False, reducedform=0, 83 | multichannel=False, 84 | measurefft=False, 85 | multithreading=False, 86 | dtype=float): 87 | assert fs > 0 88 | assert sl_len > 0 89 | assert tr_area >= 0 90 | assert sl_len > tr_area*2 91 | assert min_win > 0 92 | assert 0 <= reducedform <= 2 93 | 94 | assert sl_len%4 == 0 95 | assert tr_area%2 == 0 96 | 97 | self.sl_len = sl_len 98 | self.tr_area = tr_area 99 | self.fs = fs 100 | self.real = real 101 | self.measurefft = measurefft 102 | self.multithreading = multithreading 103 | self.userecwnd = recwnd 104 | self.reducedform = reducedform 105 | 106 | self.scale = scale 107 | self.frqs,self.q = self.scale() 108 | 109 | self.g,self.rfbas,self.M = nsgfwin(self.frqs, self.q, self.fs, self.sl_len, sliced=True, min_win=min_win, Qvar=Qvar, dtype=dtype) 110 | 111 | # print "rfbas",self.rfbas/float(self.sl_len)*self.fs 112 | if real: 113 | assert 0 <= reducedform <= 2 114 | sl = slice(reducedform,len(self.g)//2+1-reducedform) 115 | else: 116 | sl = slice(0,None) 117 | 118 | # coefficients per slice 119 | self.ncoefs = max(int(ceil(float(len(gii))/mii))*mii for mii,gii in zip(self.M[sl],self.g[sl])) 120 | 121 | if matrixform: 122 | if self.reducedform: 123 | rm = self.M[self.reducedform:len(self.M)//2+1-self.reducedform] 124 | self.M[:] = rm.max() 125 | else: 126 | self.M[:] = self.M.max() 127 | 128 | if multichannel: 129 | self.channelize = lambda seq: seq 130 | self.unchannelize = lambda seq: seq 131 | else: 132 | self.channelize = lambda seq: ((it,) for it in seq) 133 | self.unchannelize = lambda seq: (it[0] for it in seq) 134 | 135 | self.wins,self.nn = calcwinrange(self.g, self.rfbas, self.sl_len) 136 | 137 | self.gd = nsdual(self.g, self.wins, self.nn, self.M) 138 | 139 | self.fwd = lambda fc: nsgtf_sl(fc, self.g, self.wins, self.nn, self.M, real=self.real, reducedform=self.reducedform, measurefft=self.measurefft, multithreading=self.multithreading) 140 | self.bwd = lambda cc: nsigtf_sl(cc, self.gd, self.wins, self.nn, self.sl_len ,real=self.real, reducedform=self.reducedform, measurefft=self.measurefft, multithreading=self.multithreading) 141 | 142 | @property 143 | def coef_factor(self): 144 | return float(self.ncoefs)/self.sl_len 145 | 146 | @property 147 | def slice_coefs(self): 148 | return self.ncoefs 149 | 150 | def forward(self, sig): 151 | 'transform - s: iterable sequence of sequences' 152 | 153 | sig = self.channelize(sig) 154 | 155 | # Compute the slices (zero-padded Tukey window version) 156 | f_sliced = slicing(sig, self.sl_len, self.tr_area) 157 | 158 | cseq = chnmap(self.fwd, f_sliced) 159 | 160 | cseq = arrange(cseq, self.M, True) 161 | 162 | cseq = self.unchannelize(cseq) 163 | 164 | return cseq 165 | 166 | 167 | def backward(self, cseq): 168 | 'inverse transform - c: iterable sequence of coefficients' 169 | 170 | cseq = self.channelize(cseq) 171 | 172 | cseq = arrange(cseq, self.M, False) 173 | 174 | frec_sliced = chnmap(self.bwd, cseq) 175 | 176 | # Glue the parts back together 177 | ftype = float if self.real else complex 178 | sig = unslicing(frec_sliced, self.sl_len, self.tr_area, dtype=ftype, usewindow=self.userecwnd) 179 | 180 | sig = self.unchannelize(sig) 181 | 182 | # discard first two blocks (padding) 183 | next(sig) 184 | next(sig) 185 | return sig 186 | 187 | 188 | class CQ_NSGT_sliced(NSGT_sliced): 189 | def __init__(self, fmin, fmax, bins, sl_len, tr_area, fs, min_win=16, Qvar=1, real=False, recwnd=False, matrixform=False, reducedform=0, multichannel=False, measurefft=False, multithreading=False): 190 | assert fmin > 0 191 | assert fmax > fmin 192 | assert bins > 0 193 | 194 | self.fmin = fmin 195 | self.fmax = fmax 196 | self.bins = bins # bins per octave 197 | 198 | scale = OctScale(fmin, fmax, bins) 199 | NSGT_sliced.__init__(self, scale, sl_len, tr_area, fs, min_win, Qvar, real, recwnd, matrixform=matrixform, reducedform=reducedform, multichannel=multichannel, measurefft=measurefft, multithreading=multithreading) 200 | -------------------------------------------------------------------------------- /nsgt/unslicing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | from itertools import cycle, chain 16 | from .util import hannwin 17 | 18 | def slicequads(frec_sliced, hhop): 19 | slices = [[slice(hhop*((i+3-k*2)%4),hhop*((i+3-k*2)%4+1)) for i in range(4)] for k in range(2)] 20 | slices = cycle(slices) 21 | 22 | for fsl,sl in zip(frec_sliced, slices): 23 | yield [[fslc[sli] for fslc in fsl] for sli in sl] 24 | 25 | 26 | def unslicing(frec_sliced, sl_len, tr_area, dtype=float, usewindow=True): 27 | hhop = sl_len//4 28 | islices = slicequads(frec_sliced, hhop) 29 | 30 | if usewindow: 31 | tr_area2 = min(2*hhop-tr_area, 2*tr_area) 32 | htr = tr_area//2 33 | htr2 = tr_area2//2 34 | hw = hannwin(tr_area2) 35 | tw = np.zeros(sl_len, dtype=dtype) 36 | tw[max(hhop-htr-htr2, 0):hhop-htr] = hw[htr2:] 37 | tw[hhop-htr:3*hhop+htr] = 1 38 | tw[3*hhop+htr:min(3*hhop+htr+htr2, sl_len)] = hw[:htr2] 39 | tw = [tw[o:o+hhop] for o in range(0, sl_len, hhop)] 40 | else: 41 | tw = cycle((1,)) 42 | 43 | # get first slice to deduce channels 44 | firstquad = next(islices) 45 | 46 | chns = len(firstquad[0]) # number of channels in first quad 47 | 48 | islices = chain((firstquad,), islices) 49 | 50 | output = [np.zeros((chns,hhop), dtype=dtype) for _ in range(4)] 51 | 52 | for quad in islices: 53 | for osl,isl,w in zip(output, quad, tw): 54 | # in a piecewise manner add slices to output stream 55 | osl[:] += isl*w 56 | for _ in range(2): 57 | # absolutely first two should be padding (and discarded by the receiver) 58 | yield output.pop(0) 59 | output.append(np.zeros((chns,hhop), dtype=dtype)) 60 | 61 | for _ in range(2): 62 | # absolutely last two should be padding (and discarded by the receiver) 63 | yield output.pop(0) 64 | 65 | # two more buffers remaining (and zero) 66 | -------------------------------------------------------------------------------- /nsgt/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | """ 13 | 14 | import numpy as np 15 | from math import exp, floor, ceil, pi 16 | 17 | 18 | def hannwin(l): 19 | r = np.arange(l,dtype=float) 20 | r *= np.pi*2./l 21 | r = np.cos(r) 22 | r += 1. 23 | r *= 0.5 24 | return r 25 | 26 | def blackharr(n,l=None,mod=True): 27 | if l is None: 28 | l = n 29 | nn = (n//2)*2 30 | k = np.arange(n) 31 | if not mod: 32 | bh = 0.35875 - 0.48829*np.cos(k*(2*pi/nn)) + 0.14128*np.cos(k*(4*pi/nn)) -0.01168*np.cos(k*(6*pi/nn)) 33 | else: 34 | bh = 0.35872 - 0.48832*np.cos(k*(2*pi/nn)) + 0.14128*np.cos(k*(4*pi/nn)) -0.01168*np.cos(k*(6*pi/nn)) 35 | bh = np.hstack((bh,np.zeros(l-n,dtype=bh.dtype))) 36 | bh = np.hstack((bh[-n//2:],bh[:-n//2])) 37 | return bh 38 | 39 | def blackharrcw(bandwidth,corr_shift): 40 | flip = -1 if corr_shift < 0 else 1 41 | corr_shift *= flip 42 | 43 | M = np.ceil(bandwidth/2+corr_shift-1)*2 44 | win = np.concatenate((np.arange(M//2,M), np.arange(0,M//2)))-corr_shift 45 | win = (0.35872 - 0.48832*np.cos(win*(2*np.pi/bandwidth))+ 0.14128*np.cos(win*(4*np.pi/bandwidth)) -0.01168*np.cos(win*(6*np.pi/bandwidth)))*(win <= bandwidth)*(win >= 0) 46 | 47 | return win[::flip],M 48 | 49 | 50 | def cont_tukey_win(n, sl_len, tr_area): 51 | g = np.arange(n)*(sl_len/float(n)) 52 | g[np.logical_or(g < sl_len/4.-tr_area/2., g > 3*sl_len/4.+tr_area/2.)] = 0. 53 | g[np.logical_and(g > sl_len/4.+tr_area/2., g < 3*sl_len/4.-tr_area/2.)] = 1. 54 | # 55 | idxs = np.logical_and(g >= sl_len/4.-tr_area/2., g <= sl_len/4.+tr_area/2.) 56 | temp = g[idxs] 57 | temp -= sl_len/4.+tr_area/2. 58 | temp *= pi/tr_area 59 | g[idxs] = np.cos(temp)*0.5+0.5 60 | # 61 | idxs = np.logical_and(g >= 3*sl_len/4.-tr_area/2., g <= 3*sl_len/4.+tr_area/2.) 62 | temp = g[idxs] 63 | temp += -3*sl_len/4.+tr_area/2. 64 | temp *= pi/tr_area 65 | g[idxs] = np.cos(temp)*0.5+0.5 66 | # 67 | return g 68 | 69 | def tgauss(ess_ln, ln=0): 70 | if ln < ess_ln: 71 | ln = ess_ln 72 | # 73 | g = np.zeros(ln, dtype=float) 74 | sl1 = int(floor(ess_ln/2)) 75 | sl2 = int(ceil(ess_ln/2))+1 76 | r = np.arange(-sl1, sl2) # (-floor(ess_len/2):ceil(ess_len/2)-1) 77 | r = np.exp((r*(3.8/ess_ln))**2*-pi) 78 | r -= exp(-pi*1.9**2) 79 | # 80 | g[-sl1:] = r[:sl1] 81 | g[:sl2] = r[-sl2:] 82 | return g 83 | 84 | def _isseq(x): 85 | try: 86 | len(x) 87 | except TypeError: 88 | return False 89 | return True 90 | 91 | def chkM(M, g): 92 | if M is None: 93 | M = np.array(list(map(len, g))) 94 | elif not _isseq(M): 95 | M = np.ones(len(g), dtype=int)*M 96 | return M 97 | 98 | def calcwinrange(g, rfbas, Ls): 99 | shift = np.concatenate(((np.mod(-rfbas[-1],Ls),), rfbas[1:]-rfbas[:-1])) 100 | 101 | timepos = np.cumsum(shift) 102 | nn = timepos[-1] 103 | timepos -= shift[0] # Calculate positions from shift vector 104 | 105 | wins = [] 106 | for gii,tpii in zip(g, timepos): 107 | Lg = len(gii) 108 | win_range = np.arange(-(Lg//2)+tpii, Lg-(Lg//2)+tpii, dtype=int) 109 | win_range %= nn 110 | 111 | # Lg2 = Lg//2 112 | # oh = tpii 113 | # o = oh-Lg2 114 | # oe = oh+Lg2 115 | # 116 | # if o < 0: 117 | # # wraparound is in first half 118 | # win_range = ((slice(o+nn,nn),slice(0,oh)),(slice(oh,oe),slice(0,0))) 119 | # elif oe > nn: 120 | # # wraparound is in second half 121 | # win_range = ((slice(o,oh),slice(0,0)),(slice(oh,nn),slice(0,oe-nn))) 122 | # else: 123 | # # no wraparound 124 | # win_range = ((slice(o,oh),slice(0,0)),(slice(oh,oe),slice(0,0))) 125 | 126 | wins.append(win_range) 127 | 128 | return wins,nn 129 | 130 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Python implementation of Non-Stationary Gabor Transform (NSGT) 2 | derived from MATLAB code by NUHAG, University of Vienna, Austria 3 | 4 | Thomas Grill, 2011-2022 5 | http://grrrr.org/nsgt 6 | 7 | Austrian Research Institute for Artificial Intelligence (OFAI) 8 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 9 | 10 | covered by the Artistic License 2.0 11 | http://www.perlfoundation.org/artistic_license_2_0 12 | 13 | 14 | Mandatory dependencies: 15 | ----------------------- 16 | Numerical Python (http://numpy.scipy.org) 17 | 18 | Optional dependencies: 19 | ----------------------- 20 | PyFFTW3 (https://launchpad.net/pyfftw) 21 | will greatly speed up the NSGT transformation if fftw3 is installed on your system 22 | 23 | pysndfile (https://pypi.org/project/pysndfile) 24 | is recommended for using the built-in audio import/streaming functionality (otherwise ffmpeg would be tried) 25 | 26 | 27 | Installation: 28 | ------------- 29 | 30 | In the console (terminal application) change to the folder containing this readme.txt file. 31 | 32 | To build the package run the following command: 33 | python setup.py build 34 | 35 | To install the package (with administrator rights): 36 | sudo python setup.py install 37 | 38 | 39 | Todo: 40 | ----- 41 | 42 | - Quality measurement for coefficients of sliced transform 43 | - Unify nsgfwin sliced/non-sliced 44 | 45 | 46 | Source: 47 | ------- 48 | 49 | Original matlab code copyright follows: 50 | 51 | AUTHOR(s) : Monika Dörfler, Gino Angelo Velasco, Nicki Holighaus, 2010-2011 52 | 53 | COPYRIGHT : (c) NUHAG, Dept.Math., University of Vienna, AUSTRIA 54 | http://nuhag.eu/ 55 | Permission is granted to modify and re-distribute this 56 | code in any manner as long as this notice is preserved. 57 | All standard disclaimers apply. 58 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = readme.txt 3 | 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 3 | 4 | """ 5 | Python implementation of Non-Stationary Gabor Transform (NSGT) 6 | derived from MATLAB code by NUHAG, University of Vienna, Austria 7 | 8 | Thomas Grill, 2011-2022 9 | http://grrrr.org/nsgt 10 | 11 | Austrian Research Institute for Artificial Intelligence (OFAI) 12 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 13 | 14 | covered by the Artistic License 2.0 15 | http://www.perlfoundation.org/artistic_license_2_0 16 | 17 | -- 18 | 19 | Installation: 20 | 21 | In the console (terminal application) change to the folder containing this readme.txt file. 22 | 23 | To build the package run the following command: 24 | python setup.py build 25 | 26 | To install the package (with administrator rights): 27 | sudo python setup.py install 28 | 29 | -- 30 | 31 | Attention: some Cython versions also need the Pyrex module installed! 32 | 33 | """ 34 | 35 | import warnings 36 | import os 37 | 38 | try: 39 | import setuptools 40 | except ImportError: 41 | warnings.warn("setuptools not found, resorting to distutils: unit test suite can not be run from setup.py") 42 | setuptools = None 43 | 44 | setup_options = {} 45 | 46 | if setuptools is None: 47 | from distutils.core import setup 48 | from distutils.extension import Extension 49 | else: 50 | from setuptools import setup 51 | from setuptools.extension import Extension 52 | setup_options['test_suite'] = 'tests' 53 | 54 | try: 55 | from Cython.Distutils import build_ext 56 | except ImportError: 57 | build_ext = None 58 | 59 | 60 | if build_ext is None: 61 | cmdclass = {} 62 | ext_modules = [] 63 | else: 64 | cmdclass = {'build_ext': build_ext} 65 | ext_modules = [ 66 | Extension("nsgt._nsgtf_loop", ["nsgt/nsgtf_loop.pyx"]), 67 | Extension("nsgt._nsigtf_loop", ["nsgt/nsigtf_loop.pyx"]) 68 | ] 69 | 70 | 71 | try: 72 | import numpy 73 | INCLUDE_DIRS = [numpy.get_include()] 74 | except ImportError: 75 | INCLUDE_DIRS = [] 76 | 77 | setup( 78 | name="nsgt", 79 | version="0.19", 80 | author="Thomas Grill", 81 | author_email="gr@grrrr.org", 82 | maintainer="Thomas Grill", 83 | maintainer_email="gr@grrrr.org", 84 | description="Python implementation of Non-Stationary Gabor Transform (NSGT)", 85 | license="Artistic License", 86 | keywords="fourier gabor", 87 | url="http://grrrr.org/nsgt", 88 | setup_requires=["numpy"], 89 | install_requires=["numpy"], 90 | include_dirs=INCLUDE_DIRS, 91 | packages=['nsgt'], 92 | cmdclass=cmdclass, 93 | ext_modules=ext_modules, 94 | classifiers=[ 95 | "Development Status :: 4 - Beta", 96 | "Topic :: Scientific/Engineering :: Mathematics", 97 | "License :: OSI Approved :: Artistic License", 98 | "Programming Language :: Python" 99 | ], 100 | **setup_options 101 | ) 102 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 2 | 3 | """ 4 | Python implementation of Non-Stationary Gabor Transform (NSGT) 5 | derived from MATLAB code by NUHAG, University of Vienna, Austria 6 | 7 | Thomas Grill, 2011-2015 8 | http://grrrr.org/nsgt 9 | 10 | Austrian Research Institute for Artificial Intelligence (OFAI) 11 | AudioMiner project, supported by Vienna Science and Technology Fund (WWTF) 12 | 13 | --- 14 | 15 | Unit test module 16 | """ 17 | -------------------------------------------------------------------------------- /tests/cq_test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from nsgt import NSGT, OctScale 3 | import unittest 4 | 5 | class TestNSGT(unittest.TestCase): 6 | 7 | def test_oct(self): 8 | siglen = int(10**np.random.uniform(4,6)) 9 | sig = np.random.random(siglen) 10 | fmin = np.random.random()*200+20 11 | fmax = np.random.random()*(22048-fmin)+fmin 12 | obins = np.random.randint(24)+1 13 | scale = OctScale(fmin,fmax,obins) 14 | nsgt = NSGT(scale, fs=44100, Ls=len(sig)) 15 | c = nsgt.forward(sig) 16 | s_r = nsgt.backward(c) 17 | self.assertTrue(np.allclose(sig, s_r, atol=1e-07)) 18 | 19 | def load_tests(*_): 20 | # seed random generators for unit testing 21 | np.random.seed(666) 22 | 23 | test_cases = unittest.TestSuite() 24 | for _ in range(100): 25 | test_cases.addTest(TestNSGT('test_oct')) 26 | return test_cases 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tests/fft_test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from nsgt.fft import rfftp, irfftp, fftp, ifftp 3 | import unittest 4 | 5 | class TestFFT(unittest.TestCase): 6 | def __init__(self, methodName, n=10000): 7 | super(TestFFT, self).__init__(methodName) 8 | self.n = n 9 | 10 | def test_rfft(self): 11 | seq = np.random.random(self.n) 12 | ft = rfftp() 13 | a = ft(seq) 14 | b = np.fft.rfft(seq) 15 | self.assertTrue(np.allclose(a, b)) 16 | 17 | def test_irfft(self): 18 | seq = np.random.random(self.n)+np.random.random(self.n)*1.j 19 | outn = (self.n-1)*2 + np.random.randint(0,2) # even or odd output size 20 | ft = irfftp() 21 | a = ft(seq, outn=outn) 22 | b = np.fft.irfft(seq, n=outn) 23 | self.assertTrue(np.allclose(a, b)) 24 | 25 | def test_fft(self): 26 | seq = np.random.random(self.n) 27 | ft = fftp() 28 | a = ft(seq) 29 | b = np.fft.fft(seq) 30 | self.assertTrue(np.allclose(a, b)) 31 | 32 | def test_ifft(self): 33 | seq = np.random.random(self.n)+np.random.random(self.n)*1.j 34 | ft = ifftp() 35 | a = ft(seq) 36 | b = np.fft.ifft(seq) 37 | self.assertTrue(np.allclose(a, b)) 38 | 39 | def load_tests(*_): 40 | # seed random generators for unit testing 41 | np.random.seed(666) 42 | 43 | test_cases = unittest.TestSuite() 44 | for _ in range(100): 45 | l = int(10*np.random.uniform(2, 5)) 46 | test_cases.addTest(TestFFT('test_rfft', l)) 47 | test_cases.addTest(TestFFT('test_irfft', l)) 48 | test_cases.addTest(TestFFT('test_fft', l)) 49 | test_cases.addTest(TestFFT('test_ifft', l)) 50 | return test_cases 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /tests/nsgt_test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from nsgt import CQ_NSGT 3 | import unittest 4 | 5 | class Test_CQ_NSGT(unittest.TestCase): 6 | 7 | def test_transform(self, length=100000, fmin=50, fmax=22050, bins=12, fs=44100): 8 | s = np.random.random(length) 9 | nsgt = CQ_NSGT(fmin, fmax, bins, fs, length) 10 | 11 | # forward transform 12 | c = nsgt.forward(s) 13 | # inverse transform 14 | s_r = nsgt.backward(c) 15 | 16 | self.assertTrue(np.allclose(s, s_r, atol=1e-07)) 17 | 18 | def load_tests(*_): 19 | # seed random generators for unit testing 20 | np.random.seed(666) 21 | 22 | test_cases = unittest.TestSuite() 23 | for _ in range(100): 24 | test_cases.addTest(Test_CQ_NSGT('test_transform')) 25 | return test_cases 26 | 27 | if __name__ == "__main__": 28 | unittest.main() 29 | -------------------------------------------------------------------------------- /tests/reblock_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nsgt.reblock import reblock 3 | import unittest 4 | 5 | class Test_reblock(unittest.TestCase): 6 | 7 | def test1(self): 8 | inblk = 17 9 | outblk = 13 10 | inp = (list(range(i*inblk,(i+1)*inblk)) for i in range(10)) 11 | resit = reblock(inp, outblk, dtype=None, fulllast=True, padding=-1) 12 | res = list(map(list, resit)) 13 | expres = ["0 1 2 3 4 5 6 7 8 9 10 11 12", 14 | "13 14 15 16 17 18 19 20 21 22 23 24 25", 15 | "26 27 28 29 30 31 32 33 34 35 36 37 38", 16 | "39 40 41 42 43 44 45 46 47 48 49 50 51", 17 | "52 53 54 55 56 57 58 59 60 61 62 63 64", 18 | "65 66 67 68 69 70 71 72 73 74 75 76 77", 19 | "78 79 80 81 82 83 84 85 86 87 88 89 90", 20 | " 91 92 93 94 95 96 97 98 99 100 101 102 103", 21 | "104 105 106 107 108 109 110 111 112 113 114 115 116", 22 | "117 118 119 120 121 122 123 124 125 126 127 128 129", 23 | "130 131 132 133 134 135 136 137 138 139 140 141 142", 24 | "143 144 145 146 147 148 149 150 151 152 153 154 155", 25 | "156 157 158 159 160 161 162 163 164 165 166 167 168", 26 | "169 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1"] 27 | expres = [list(map(int,s.split())) for s in expres] 28 | self.assertEqual(res, expres) 29 | 30 | if __name__ == '__main__': 31 | unittest.main() 32 | -------------------------------------------------------------------------------- /tests/slicq_test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from nsgt import NSGT_sliced, OctScale 3 | import unittest 4 | 5 | # reproducible noise signal 6 | maxlen = 50000 7 | np.random.seed(666) 8 | rndsig = np.random.random((maxlen,)) 9 | #assert np.allclose(rndsig[:3],[ 0.70043712, 0.84418664, 0.67651434]) 10 | 11 | class TestNSGT_slices(unittest.TestCase): 12 | 13 | def runit(self, siglen, fmin, fmax, obins, sllen, trlen, real): 14 | sig = rndsig[:siglen] 15 | 16 | scale = OctScale(fmin, fmax, obins) 17 | nsgt = NSGT_sliced(scale, fs=44100, sl_len=sllen, tr_area=trlen, real=real) 18 | 19 | c = nsgt.forward((sig,)) 20 | 21 | rc = nsgt.backward(c) 22 | 23 | s_r = np.concatenate(list(map(list,rc)))[:len(sig)] 24 | 25 | close = np.allclose(sig, s_r, atol=1.e-3) 26 | if not close: 27 | print("Failing params:", siglen, fmin, fmax, obins, sllen, trlen, real) 28 | dev = np.abs(s_r-sig) 29 | print("Error", np.where(dev>1.e-3), np.max(dev)) 30 | self.assertTrue(close) 31 | 32 | 33 | def test_1d1(self): 34 | self.runit(*list(map(int,"100000 100 18200 2 20000 5000 1".split()))) 35 | 36 | def test_1d11(self): 37 | self.runit(*list(map(int,"100000 80 18200 6 20000 5000 1".split()))) 38 | 39 | def test_1(self): 40 | self.runit(*list(map(int,"100000 99 19895 6 84348 5928 1".split()))) 41 | 42 | def test_1a(self): 43 | self.runit(*list(map(int,"100000 99 19895 6 84348 5928 0".split()))) 44 | 45 | def test_1b(self): 46 | self.runit(*list(map(int,"100000 100 20000 6 80000 5000 1".split()))) 47 | 48 | def test_1c(self): 49 | self.runit(*list(map(int,"100000 100 19000 6 80000 5000 1".split()))) 50 | 51 | def test_1d2(self): 52 | self.runit(*list(map(int,"100000 100 18100 6 20000 5000 1".split()))) 53 | 54 | def test_1e(self): 55 | self.runit(*list(map(int,"100000 100 18000 6 20000 5000 1".split()))) 56 | 57 | def test_err1(self): 58 | self.runit(*list(map(int,"30549 104 11970 25 7286 2030 1".split()))) 59 | 60 | def test_err2(self): 61 | self.runit(*list(map(int,"19746 88 19991 21 12674 4030 0".split()))) 62 | 63 | def test_err3(self): 64 | self.runit(*list(map(int,"92507 114 18387 20 29306 11848 1".split()))) 65 | 66 | def test_err4(self): 67 | self.runit(*list(map(int,"20724 191 2843 16 22354 6590 1".split()))) 68 | 69 | def test_err5(self): 70 | self.runit(*list(map(int,"10712 97 19363 3 10238 1876 1".split()))) 71 | 72 | def test_err6(self): 73 | self.runit(*list(map(int,"262597 100 15786 16 2858 556 1".split()))) 74 | 75 | def gtest_oct(self): 76 | siglen = int(10**np.random.uniform(4,6)) 77 | fmin = np.random.randint(200)+80 78 | fmax = max(np.random.randint(22048-fmin)+fmin,10000) 79 | obins = np.random.randint(24)+1 80 | sllen = max(1000,np.random.randint(maxlen))//4*4 81 | trlen = max(2,np.random.randint(sllen//2-2))//2*2 82 | real = np.random.randint(2) 83 | self.runit(siglen, fmin, fmax, obins, sllen, trlen, real) 84 | 85 | def load_tests(*_): 86 | test_cases = unittest.TestSuite() 87 | # for t in 'test_1d1 test_1d11 test_1 test_1a test_1b test_1c test_1d2 test_1e test_err1 test_err2 test_err3 test_err4 test_err5 test_err6'.split(): 88 | # test_cases.addTest(TestNSGT_slices(t)) 89 | 90 | for _ in range(50): 91 | test_cases.addTest(TestNSGT_slices('gtest_oct')) 92 | return test_cases 93 | 94 | if __name__ == '__main__': 95 | unittest.main() 96 | --------------------------------------------------------------------------------