Skip to content

Commit 7e0b03f

Browse files
committed
Initial commit
0 parents  commit 7e0b03f

13 files changed

+1315
-0
lines changed

.github/workflows/python-package.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
4+
name: Python package
5+
6+
on:
7+
push:
8+
branches: [ "main" ]
9+
pull_request:
10+
branches: [ "main" ]
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
- name: Set up Python ${{ matrix.python-version }}
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: ${{ matrix.python-version }}
27+
- name: Install dependencies
28+
run: |
29+
python -m pip install --upgrade pip
30+
python -m pip install --upgrade setuptools setuptools_scm
31+
pip install build
32+
- name: Build Package
33+
run: |
34+
python -m build

.github/workflows/python-publish.yml

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This workflow will upload a Python Package using Twine when a release is created
2+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3+
4+
# This workflow uses actions that are not certified by GitHub.
5+
# They are provided by a third-party and are governed by
6+
# separate terms of service, privacy policy, and support
7+
# documentation.
8+
9+
name: Upload Python Package
10+
11+
on:
12+
release:
13+
types: [published]
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
deploy:
20+
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
- name: Set up Python
26+
uses: actions/setup-python@v3
27+
with:
28+
python-version: '3.x'
29+
- name: Install dependencies
30+
run: |
31+
python -m pip install --upgrade pip
32+
python -m pip install --upgrade setuptools setuptools_scm
33+
pip install build
34+
- name: Build package
35+
run: python -m build
36+
- name: Publish package
37+
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
38+
with:
39+
user: __token__
40+
password: ${{ secrets.PYPI_API_TOKEN }}

.gitignore

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#####=== Python ===#####
2+
3+
# Byte-compiled / optimized / DLL files
4+
__pycache__/
5+
*.py[cod]
6+
*$py.class
7+
8+
# C extensions
9+
*.c
10+
*.o
11+
*.so
12+
*.h.gch
13+
14+
# Distribution / packaging
15+
.Python
16+
env/
17+
build/
18+
develop-eggs/
19+
dist/
20+
downloads/
21+
eggs/
22+
.eggs/
23+
lib/
24+
lib64/
25+
parts/
26+
sdist/
27+
var/
28+
*.egg-info/
29+
.installed.cfg
30+
*.egg
31+
32+
# PyInstaller
33+
# Usually these files are written by a python script from a template
34+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
35+
*.manifest
36+
*.spec
37+
38+
# Installer logs
39+
pip-log.txt
40+
pip-delete-this-directory.txt
41+
42+
# Unit test / coverage reports
43+
htmlcov/
44+
.tox/
45+
.coverage
46+
.coverage.*
47+
.cache
48+
nosetests.xml
49+
coverage.xml
50+
*,cover
51+
52+
# Translations
53+
*.mo
54+
*.pot
55+
56+
# Django stuff:
57+
*.log
58+
59+
# Sphinx documentation
60+
docs/_build/
61+
62+
# PyBuilder
63+
target/
64+
rirgenerator.c

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020-2023 Friedrich-Alexander-Universität Erlangen-Nürnberg, Germany
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.

README.md

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# **Generating coherence-constrained multisensor signals using balanced mixing and spectrally smooth filters**
2+
3+
Project Overview
4+
====================
5+
This project provides a Python function for generating multi-channel audio signals with predefined spatial coherence.
6+
7+
Abstract
8+
====================
9+
10+
The spatial properties of a noise field can be described by a spatial coherence function. Synthetic multichannel noise signals exhibiting a specific spatial coherence can be generated by properly mixing a set of uncorrelated, possibly nonstationary, signals. The mixing matrix can be obtained by decomposing the spatial coherence matrix. As proposed in a widely used method, the factorization can be performed using a Cholesky or an eigenvalue decomposition. The limitations of these two methods are discussed and addressed in [[1]](#1). In particular, specific properties of the mixing matrix are analyzed, namely the spectral smoothness and the mix balance. The first quantifies the mixing matrix-filters variation across frequency, and the second quantifies how much each input contributes to each output. Three methods based on the unitary Procrustes solution are proposed to enhance the spectral smoothness, the mix balance, and both properties jointly. A performance evaluation confirms the improvements of the mixing matrix in terms of objective measures. Further, the evaluation results show that the error between the target and the generated coherence is lowered by increasing the spectral smoothness of the mixing matrix.
11+
12+
Method
13+
====================
14+
15+
This method is able to generate multi-channel audio signals that exhibit a predefined spatial coherence. It also enhances specific properties of the mixing matrix obtained with the baseline approach (Cholesky or eigenvalue decompositions). These properties are:<br/>
16+
17+
- **Spectral Variation**: variation of the mixing matrix's filter response in the frequency domain. The mixing matrix is considered smooth if it slowly varies over frequency. For this reason, we denote a low Spectral Variation with 'Spectral Smoothness'. This variation can be quantified by the squared Frobenius norm of the difference between two frequency-bands adjacent mixing matrices.
18+
- **Coherence Error**: accuracy of the generated spatial coherence at frequency bands that are not resolved by the chosen DFT length. A mixing matrix yields a low coherence error when the squared Frobenius norm of the difference between the target coherence matrix and the generated coherence matrix is low.
19+
- **Mix Balance**: balance of the mixing, i.e., the number of input signals that contribute to each output signal. A balanced mixing matrix contains similar contributions from each input signal to each output signal. The balance can be quantified by the l1-norm of the mixing matrix.
20+
21+
The coherence error is inherently decreased by increasing the spectral smoothness. In addition, a smooth mixing matrix yields shorter impulse responses compared to non-smooth counterparts. The benefits of improving the smoothness and the balance are also perceptually evident. A smooth mix leads to less distortions in the output signals (especially during transients). A balanced mix is such that the input signals are filtered and summed similarly among all the channels, leading to an increased perceptual plausibility.
22+
23+
Settings
24+
====================
25+
Different parameters can be chosen for the signal generation (e.g., sampling frequency, DFT length, spatial coherence model). The microphone positions are arranged in a matrix (`M` x 3), i.e., *number of channels* x coordinates *xyz*. The generator works for any arbitrary 3-D microphone constellation.
26+
27+
Supported spatial coherence models:<br/>
28+
1. 3-D spherically isotropic diffuse sound field: `spherical`
29+
2. 2-D cylindrically isotropic diffuse sound field: `cylindrical`
30+
3. Corcos model (turbulent aerodynamic noise, e.g., atmospheric-wind noise): `corcos`
31+
32+
The first and second model depends solely on the microphone positions. The third model has two additional parameters: `speed` and `direction` of the turbulent airflow. When selecting this model, it is suggested to use an inter-microphone distance of less than 2 cm to appreciate differences with respect to a noise field with uncorrelated signals.
33+
34+
Supported factorization methods to decompose the target coherence matrix and obtain the mixing matrix:
35+
1. Cholesky decomposition: `chd`
36+
2. Eigenvalue decomposition: `evd`
37+
38+
The `chd` yields a smooth but unbalanced mix. The `evd` yields a more balanced but non-smooth mix.
39+
40+
Three methods are available to enhance the properties of the mixing matrix:
41+
1. Enhance smoothness: `smooth`
42+
2. Enhance balance: `balanced`
43+
3. Enhance balance and smoothness: `balanced+smooth`
44+
4. No post-processing: `standard`
45+
46+
The `smooth` method enhances the smoothness (i.e., decreases the spectral variation) and lowers the coherence error while leaving almost unaltered the mix balance. The `balanced` method maximizes the balance but significantly increases the spectral variation and the coherence error. The `balanced+smooth` method enhances both properties with a reasonable trade-off. The `standard` method leaves the Cholesky or the eigenvalue decomposition unaltered.
47+
48+
Installation
49+
====================
50+
```ruby
51+
pip install anf-generator
52+
```
53+
54+
Example Code
55+
====================
56+
```ruby
57+
import numpy as np
58+
import anf_generator as anf
59+
60+
# Define signals
61+
duration = 10 # Duration in seconds
62+
num_channels = 4 # Number of microphones
63+
64+
# Define target spatial coherence
65+
params = anf.CoherenceMatrix.Parameters(
66+
mic_positions=np.array([[0.04 * i, 0, 0] for i in range(num_channels)]),
67+
sc_type="spherical",
68+
sample_frequency=16000,
69+
nfft=1024,
70+
)
71+
72+
# Generate "num_channels" mutually independent input signals of length "duration"
73+
input_signals = np.random.randn(num_channels, duration * params.sample_frequency)
74+
75+
# Generate output signals with the desired spatial coherence
76+
output_signals, coherence_target, mixing_matrix = anf.generate_signals(
77+
input_signals, params, decomposition='evd', processing='balance+smooth')
78+
```
79+
80+
Two more test scripts are provided: `tests/example.py` and `tests/example_babble.py`.
81+
82+
Audio Examples
83+
====================
84+
Click [here](https://www.audiolabs-erlangen.de/resources/2020-JASA-CCR) to listen to examples generated using this method.
85+
86+
References
87+
====================
88+
<a id="1">[1]</a> D. Mirabilii, S. J. Schlecht, E.A.P. Habets, *'Generating coherence-constrained multisensor signals using balanced mixing and spectrally smooth filters'*, The Journal of the Acoustical Society of America, Vol. 149, 1425, 2021.
89+
90+
<a id="2">[2]</a> E.A.P. Habets, I. Cohen and S. Gannot, *'Generating nonstationary multisensor signals under a spatial coherence constraint,'* Journal of the Acoustical Society of America, Vol. 124, Issue 5, pp. 2911-2917, Nov. 2008. [Code](https://github.com/ehabets/ANF-Generator)

anf_generator/CoherenceMatrix.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import numpy as np
2+
from scipy.special import jv
3+
4+
5+
class Parameters:
6+
def __init__(self, mic_positions, sc_type, sample_frequency, nfft=1024, c=340, speed=20, direction=60):
7+
self.mic_positions = mic_positions # Microphone positions [M x 3]
8+
# Spatial coherence model: 'corcos', 'spherical', or 'cylindrical'
9+
self.sc_type = sc_type.lower()
10+
self.sample_frequency = sample_frequency # Sample frequency (Hz)
11+
self.nfft = nfft # FFT length
12+
self.c = c # Sound velocity (m/s)
13+
self.speed = speed # Wind speed (m/s)
14+
self.direction = direction # Wind direction (degrees)
15+
16+
def copy(self):
17+
return Parameters(
18+
mic_positions=self.mic_positions.copy(),
19+
sc_type=self.sc_type,
20+
sample_frequency=self.sample_frequency,
21+
nfft=self.nfft,
22+
c=self.c,
23+
speed=self.speed,
24+
direction=self.direction
25+
)
26+
27+
28+
class CoherenceMatrix:
29+
def __init__(self, params: Parameters):
30+
self.params = params
31+
self.matrix = self.generate_target_coherence()
32+
33+
def generate_target_coherence(self):
34+
"""
35+
Generate the analytical target coherence based on the model
36+
(e.g., diffuse spherical/cylindrical, Corcos), the sensor positions,
37+
and the FFT length. Valid for an arbitrary 3D-array geometry.
38+
39+
Returns:
40+
DC (np.array): Target coherence matrix [Channels x Channels x nfft/2+1]
41+
"""
42+
43+
# Angular frequency vector
44+
ww = 2 * np.pi * self.params.sample_frequency * \
45+
np.arange(self.params.nfft // 2 + 1) / self.params.nfft
46+
47+
# Matrix of position vectors
48+
rr = self.params.mic_positions[:, None, :] - \
49+
self.params.mic_positions[None, :, :]
50+
51+
# Matrix of inter-sensor distances
52+
d = np.linalg.norm(rr, axis=2)
53+
54+
if self.params.sc_type == 'corcos':
55+
# Desired wind speed and direction
56+
Ud = self.params.speed / 3.6 # Wind speed [m/s]
57+
theta = np.deg2rad(self.params.direction) # Angle in radians
58+
59+
# Rotation matrix for wind direction
60+
R = np.array([[np.cos(theta), -np.sin(theta), 0],
61+
[np.sin(theta), np.cos(theta), 0],
62+
[0, 0, 1]])
63+
yy = np.array([0, 1, 0]) # y axis = true North
64+
u = np.dot(yy, R) # Wind direction unit vector
65+
# Wind direction unit perpendicular vector
66+
u_p = np.array([u[1], -u[0], 0])
67+
68+
# Coherence parameters
69+
alpha1, alpha2 = -0.125, -0.7
70+
alpha__ = alpha1 * np.abs(np.sum(u[None, None, :] * rr, axis=2)) \
71+
+ alpha2 * np.abs(np.sum(u_p[None, None, :] * rr, axis=2))
72+
im__ = np.sum(u[None, None, :] * rr, axis=2)
73+
U = 0.8 * Ud # Convective turbulence speed
74+
AA = np.einsum('ij,k->ijk', alpha__ - 1j * im__, ww)
75+
DC = np.exp(AA / U)
76+
77+
elif self.params.sc_type == 'spherical':
78+
DC = np.sinc(d[:, :, None] * ww[None, None, :] /
79+
(self.params.c * np.pi))
80+
81+
elif self.params.sc_type == 'cylindrical':
82+
DC = jv(0,d[:, :, None] * ww[None, None, :] /
83+
(self.params.c))
84+
85+
else:
86+
raise ValueError(
87+
'Unknown coherence model. Please select "corcos", "spherical", or "cylindrical".')
88+
89+
return DC

0 commit comments

Comments
 (0)