├── LICENSE ├── README.md └── sf_func.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Shoichi Koyama 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KernelInterpSpatialANC 2 | 3 | ## Description 4 | An active noise control (ANC) method to reduce noise over a region in space based on kernel interpolation of sound field. This repository provides codes for reproducing results in the frequency domain described in the following article. 5 | 6 | - S. Koyama, J. Brunnström, H. Ito, N. Ueno, and H. Saruwatari, "Spatial Active Noise Control Based on Kernel Interpolation of Sound Field," *IEEE/ACM Transactions on Audio, Speech, and Language Processing*, DOI: 10.1109/TASLP.2021.3107983, 2021. 7 | 8 | The article is open access on [IEEE Xplore](https://doi.org/10.1109/TASLP.2021.3107983). 9 | 10 | ### Abstract 11 | An active noise control (ANC) method to reduce noise over a region in space based on kernel interpolation of sound field is proposed. Current methods of spatial ANC are largely based on spherical or circular harmonic expansion of the sound field, where the geometry of the error microphone array is restricted to a simple one such as a sphere or circle. We instead apply the kernel interpolation method, which allows for the estimation of a sound field in a continuous region with flexible array configurations. The interpolation scheme is used to derive adaptive filtering algorithms for minimizing the acoustic potential energy inside a target region. A practical time-domain algorithm is also developed together with its computationally efficient block-based equivalent. We conduct experiments to investigate the achievable level of noise reduction in a two-dimensional free space, as well as adaptive broadband noise control in a three-dimensional reverberant space. The experimental results indicated that the proposed method outperforms the multipoint-pressure-control-based method in terms of regional noise reduction. 12 | 13 | ## License 14 | [MIT](https://github.com/sh01k/KernelInterpSpatialANC/blob/main/LICENSE) 15 | 16 | ## Author 17 | - [Shoichi Koyama](https://www.sh01.org) 18 | - Jesper Brunnström 19 | - Hayato Ito 20 | - [Natsuki Ueno](https://natsuenono.github.io/) 21 | - [Hiroshi Saruwatari](https://researchmap.jp/read0102891/) -------------------------------------------------------------------------------- /sf_func.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.spatial.distance as distfuncs 3 | import scipy.special as special 4 | 5 | def uniformFilledRectangle(numPoints, lim): 6 | if len(lim) == 2: 7 | x = np.linspace(lim[0], lim[1], numPoints[0]) 8 | y = np.linspace(lim[0], lim[1], numPoints[1]) 9 | elif len(lim) == 4: 10 | x = np.linspace(lim[0], lim[2], numPoints[0]) 11 | y = np.linspace(lim[1], lim[3], numPoints[1]) 12 | 13 | [xGrid, yGrid] = np.meshgrid(x, y) 14 | points = np.vstack((xGrid.flatten(), yGrid.flatten())).T 15 | 16 | return points 17 | 18 | def equidistantRectangle(numPoints, dims, offset=0.5): 19 | if numPoints == 0: 20 | return np.zeros((0, 2)) 21 | totalLength = 2 * (dims[0] + dims[1]) 22 | pointDist = totalLength / numPoints 23 | 24 | points = np.zeros((numPoints, 2)) 25 | if numPoints < 4: 26 | points = equidistantRectangle(4, dims) 27 | pointChoices = np.random.choice(4, numPoints, replace=False) 28 | points = points[pointChoices, :] 29 | else: 30 | lengths = [dims[0], dims[1], dims[0], dims[1]] 31 | xVal = [-dims[0] / 2, dims[0] / 2, dims[0] / 2, -dims[0] / 2] 32 | yVal = [-dims[1] / 2, -dims[1] / 2, dims[1] / 2, dims[1] / 2] 33 | 34 | startPos = pointDist * offset 35 | xFac = [1, 0, -1, 0] 36 | yFac = [0, 1, 0, -1] 37 | numCounter = 0 38 | 39 | for i in range(4): 40 | numAxisPoints = 1 + int((lengths[i] - startPos) / pointDist) 41 | axisPoints = startPos + np.arange(numAxisPoints) * pointDist 42 | distLeft = lengths[i] - axisPoints[-1] 43 | points[numCounter:numCounter + numAxisPoints, 0] = xVal[i] + xFac[i] * axisPoints 44 | points[numCounter:numCounter + numAxisPoints, 1] = yVal[i] + yFac[i] * axisPoints 45 | numCounter += numAxisPoints 46 | startPos = pointDist - distLeft 47 | 48 | return points 49 | 50 | def equiangularCircle(numPoints, rad, offset=0): 51 | ang = np.linspace(0, 2*np.pi, numPoints) + offset*(2*np.pi/numPoints) 52 | points = np.vstack([rad*np.cos(ang), rad*np.sin(ang)]).T 53 | return points 54 | 55 | def PointSource(k, posSrc, posMic): 56 | r = distfuncs.cdist(posMic, posSrc)[None,:,:] 57 | p = -(1j/4) * special.hankel2(0, k[:,None,None] * r) 58 | return p 59 | 60 | ###### Kernel interpolation functions ###### 61 | def block_rect(rectDims, rng=None): 62 | if rng is None: 63 | rng = np.random.RandomState() 64 | totVol = rectDims[0] * rectDims[1] 65 | 66 | def pointGenerator(numSamples): 67 | x = rng.uniform(-rectDims[0] / 2, rectDims[0] / 2, numSamples) 68 | y = rng.uniform(-rectDims[1] / 2, rectDims[1] / 2, numSamples) 69 | points = np.stack((x, y)) 70 | return points.T 71 | 72 | return pointGenerator, totVol 73 | 74 | def block_circ(rad, rng=None): 75 | if rng is None: 76 | rng = np.random.RandomState() 77 | totVol = np.pi * rad**2 78 | 79 | def pointGenerator(numSamples): 80 | r = np.sqrt(2 * rng.uniform(0, 0.5*rad**2, numSamples)) 81 | ang = rng.uniform(0, 2*np.pi, numSamples) 82 | points = np.stack((r*np.cos(ang), r*np.sin(ang))) 83 | return points.T 84 | 85 | return pointGenerator, totVol 86 | 87 | def integrableAFunc(k, posErr): 88 | def intFunc(r): 89 | distance = np.transpose(distfuncs.cdist(r, posErr), (1, 0))[None,:,:] 90 | kappa = special.j0(k[:,None,None] * distance) 91 | funcVal = kappa[:, :, None, :].conj() * kappa[:, None, :, :] 92 | return funcVal 93 | 94 | return intFunc 95 | 96 | def integrableAwFunc(k, posErr, beta=0, ang=0): 97 | def intFunc(r): 98 | r_diff = (np.tile(r[None,:,:], (posErr.shape[0],1,1)) - np.tile(posErr[:,None,:], (1,r.shape[0],1)))[None,:,:,:] 99 | distance = 1j*np.sqrt((beta*np.cos(ang) + 1j*k[:,None,None]*r_diff[:,:,:,0])**2 + (beta*np.sin(ang) + 1j*k[:,None,None]*r_diff[:,:,:,1])**2) 100 | #distance = 1j*np.sqrt((beta*np.cos(ang) - 1j*k[:,None,None]*r_diff[:,:,:,0])**2 + (beta*np.sin(ang) - 1j*k[:,None,None]*r_diff[:,:,:,1])**2) 101 | kappa = special.jn(0, distance) 102 | funcVal = kappa[:, :, None, :].conj() * kappa[:, None, :, :] 103 | return funcVal 104 | 105 | return intFunc 106 | 107 | def integrate(func, pointGenerator, totNumSamples, totalVolume, numPerIter=50): 108 | """pointGenerator should return np array, [numSpatialDimensions, numPoints] 109 | func should return np array [funcDims, numPoints], 110 | where funcDims can be any number of dimensions (in a multidimensional array sense)""" 111 | samplesPerIter = numPerIter 112 | numBlocks = int(np.ceil(totNumSamples / samplesPerIter)) 113 | outDims = np.squeeze(func(pointGenerator(1)), axis=-1).shape 114 | integralVal = np.zeros(outDims) 115 | print( 116 | "Starting monte carlo integration \n", 117 | "Samples per block: ", 118 | numPerIter, 119 | "\nTotal samples: ", 120 | numBlocks * numPerIter, 121 | ) 122 | 123 | for i in range(numBlocks): 124 | points = pointGenerator(samplesPerIter) 125 | fVals = func(points) 126 | 127 | newIntVal = (integralVal * i + np.mean(fVals, axis=-1)) / (i + 1) 128 | print("Block ", i) 129 | 130 | integralVal = newIntVal 131 | integralVal *= totalVolume 132 | print("Finished!!") 133 | return integralVal 134 | 135 | 136 | --------------------------------------------------------------------------------