Skip to content

Commit

Permalink
Clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
synesthesiam committed Aug 25, 2024
1 parent 95ab22b commit b14435a
Show file tree
Hide file tree
Showing 17 changed files with 191 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ tmp/
*.egg
build
htmlcov
*.egg-info/

/.venv/
/dist/
/build/
/*.so
6 changes: 6 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[settings]
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=88
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# pyspeex-noise

Noise suppression using speex.
Noise suppression and automatic gain control using speex.

``` python
from pyspeex_noise import AudioProcessor

auto_gain = 4000
noise_suppression = -30
audio_processor = AudioProcessor(auto_gain, noise_suppression)

# Process 10ms chunks of 16-bit mono PCM @16Khz
while audio := get_10ms_of_audio():
assert len(audio) == 160 * 2 # 160 samples
clean_audio = audio_processor.Process10ms(audio).audio
```
5 changes: 5 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[mypy]
ignore_missing_imports = true

[mypy-setuptools.*]
ignore_missing_imports = True
37 changes: 37 additions & 0 deletions pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[MESSAGES CONTROL]
disable=
format,
abstract-method,
cyclic-import,
duplicate-code,
global-statement,
import-outside-toplevel,
inconsistent-return-statements,
locally-disabled,
not-context-manager,
too-few-public-methods,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-lines,
too-many-locals,
too-many-public-methods,
too-many-return-statements,
too-many-statements,
too-many-boolean-expressions,
unnecessary-pass,
unused-argument,
broad-except,
too-many-nested-blocks,
invalid-name,
unused-import,
fixme,
useless-super-delegation,
missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
import-error,
consider-using-with

[FORMAT]
expected-line-ending-format=LF
5 changes: 4 additions & 1 deletion pyspeex_noise/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
from speex_noise_cpp import AudioProcessor
"""Noise suppression and auto gain with speex."""
from speex_noise_cpp import AudioProcessor # pylint: disable=E0611

__all__ = ["AudioProcessor"]
8 changes: 7 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
pytest
black==22.12.0
flake8==6.0.0
isort==5.11.3
mypy==0.991
pylint==2.15.9
pytest==8.2.2
pybind11
build
Binary file added samples/noise.wav
Binary file not shown.
Binary file added samples/noise_clean.wav
Binary file not shown.
Binary file added samples/speech.wav
Binary file not shown.
Binary file added samples/speech_clean.wav
Binary file not shown.
16 changes: 16 additions & 0 deletions script/format
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
import subprocess
import venv
from pathlib import Path

_DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"
_MODULE_DIR = _PROGRAM_DIR / "pyspeex_noise"
_TESTS_DIR = _PROGRAM_DIR / "tests"

_FORMAT_DIRS = [_MODULE_DIR, _TESTS_DIR]

context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
subprocess.check_call([context.env_exe, "-m", "black"] + _FORMAT_DIRS)
subprocess.check_call([context.env_exe, "-m", "isort"] + _FORMAT_DIRS)
19 changes: 19 additions & 0 deletions script/lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
import subprocess
import venv
from pathlib import Path

_DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"
_MODULE_DIR = _PROGRAM_DIR / "pyspeex_noise"
_TESTS_DIR = _PROGRAM_DIR / "tests"

_LINT_DIRS = [_MODULE_DIR, _TESTS_DIR]

context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
subprocess.check_call([context.env_exe, "-m", "black"] + _LINT_DIRS + ["--check"])
subprocess.check_call([context.env_exe, "-m", "isort"] + _LINT_DIRS + ["--check"])
subprocess.check_call([context.env_exe, "-m", "flake8"] + _LINT_DIRS)
subprocess.check_call([context.env_exe, "-m", "pylint"] + _LINT_DIRS)
subprocess.check_call([context.env_exe, "-m", "mypy"] + _LINT_DIRS)
11 changes: 11 additions & 0 deletions script/package
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python3
import subprocess
import venv
from pathlib import Path

_DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"

context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
subprocess.check_call([context.env_exe, "-m", "build", "--sdist", "--wheel"])
22 changes: 22 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[flake8]
# To work with Black
max-line-length = 88
# E501: line too long
# W503: Line break occurred before a binary operator
# E203: Whitespace before ':'
# D202 No blank lines allowed after function docstring
# W504 line break after binary operator
ignore =
E501,
W503,
E203,
D202,
W504

[isort]
multi_line_output = 3
include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=88
indent = " "
Binary file added tests/noise.wav
Binary file not shown.
47 changes: 46 additions & 1 deletion tests/test_audio_processor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,53 @@
import array
import math
import statistics
import wave
from pathlib import Path

from pyspeex_noise import AudioProcessor

_DIR = Path(__file__).parent

SAMPLES_10MS = 160
BYTES_10MS = SAMPLES_10MS * 2


def _get_energy(chunk: bytes):
"""RMS"""
chunk_array = array.array("h", chunk)
energy = -math.sqrt(sum(x**2 for x in chunk_array) / len(chunk_array))
debiased_energy = math.sqrt(
sum((x + energy) ** 2 for x in chunk_array) / len(chunk_array)
)

return debiased_energy


def test_audio_processor():
def test_no_processing():
"""Test that audio is not changed if no auto gain or noise suppression is applied."""
audio_processor = AudioProcessor(0, 0)
data_in = bytes(320)
result = audio_processor.Process10ms(data_in)
assert result.audio == data_in


def test_noise_suppression():
"""Test default settings on a noisy file."""
audio_processor = AudioProcessor(4000, -30)
noisy_energy = []
clean_energy = []

with wave.open(str(_DIR / "noise.wav"), "rb") as wav_file:
assert wav_file.getframerate() == 16000
assert wav_file.getsampwidth() == 2
assert wav_file.getnchannels() == 1

chunk = wav_file.readframes(SAMPLES_10MS)
while len(chunk) == BYTES_10MS:
clean_chunk = audio_processor.Process10ms(chunk).audio
noisy_energy.append(_get_energy(chunk))
clean_energy.append(_get_energy(clean_chunk))
chunk = wav_file.readframes(SAMPLES_10MS)

# A lot less energy
assert (statistics.mean(noisy_energy) / statistics.mean(clean_energy)) > 30

0 comments on commit b14435a

Please sign in to comment.