Compare commits

...

16 Commits

Author SHA1 Message Date
Roman Zeyde
b944ae2989 Bump version: 1.15.7 → 1.16.0 2024-12-20 21:13:09 +02:00
Roman Zeyde
4322c39e46 Fix .bumpversion.cfg to use pyproject.toml 2024-12-20 21:04:56 +02:00
Roman Zeyde
73fb5bff62 Switch to pyproject.toml 2024-12-14 20:24:28 +02:00
Roman Zeyde
1f3ca560e8 Allow setting calibration tx gain 2024-12-12 20:27:42 +02:00
Roman Zeyde
297c66c284 Use contextlib.nullcontext (Python 3.7+) 2024-12-12 18:52:10 +02:00
Roman Zeyde
451551d37e Bump version: 1.15.5 → 1.15.6 2024-10-25 14:06:58 +03:00
Roman Zeyde
1d5fb11481 Add amodem.tests to source distribution
Also, fix a few pylint issues.
2024-10-24 21:48:10 +03:00
Roman Zeyde
a8e8bbbf02 Bump version: 1.15.4 → 1.15.5 2024-10-21 21:59:40 +03:00
Roman Zeyde
d015118d76 Update supported Python versions 2024-10-21 21:59:26 +03:00
Roman Zeyde
9410a988b1 Test on Python 3.13 2024-10-21 21:53:39 +03:00
Roman Zeyde
2a03d9f72c Fix a small pylint issue 2024-10-21 21:53:39 +03:00
Roman Zeyde
2925becfac Test on Python 3.12 (#67) 2023-11-11 21:26:03 +02:00
Roman Zeyde
30d6af680a Bump CI actions (#68) 2023-11-11 21:09:28 +02:00
Roman Zeyde
3284447d1c Fix division by 0 during calibration 2023-08-19 14:58:01 +03:00
Roman Zeyde
8557faba6e Fix lint nits 2023-07-15 21:48:13 +03:00
Roman Zeyde
d8059b5ff0 Require the user to specify send/recv subcommand 2023-07-15 21:40:01 +03:00
25 changed files with 121 additions and 134 deletions

View File

@@ -1,6 +1,6 @@
[bumpversion]
commit = True
tag = True
current_version = 1.15.4
current_version = 1.16.0
[bumpversion:file:setup.py]
[bumpversion:file:pyproject.toml]

View File

@@ -12,17 +12,17 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade pip setuptools
pip install pytest pytest-cov mock pycodestyle coverage pylint
pip install -e .

View File

@@ -1,2 +1,2 @@
[MESSAGES CONTROL]
disable=invalid-name, missing-docstring, too-many-instance-attributes, too-few-public-methods, logging-format-interpolation, consider-using-with
disable=invalid-name, missing-docstring, too-many-instance-attributes, too-few-public-methods, logging-format-interpolation, consider-using-with, redefined-outer-name

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
import argparse
import contextlib
import logging
import os
import sys
@@ -88,11 +89,11 @@ def FileType(mode, interface_factory=None):
def get_volume_cmd(args):
volume_controllers = [
dict(test='pactl --version',
send='pactl set-sink-volume @DEFAULT_SINK@',
recv='pactl set-source-volume @DEFAULT_SOURCE@')
]
volume_controllers = [{
'test': 'pactl --version',
'send': 'pactl set-sink-volume @DEFAULT_SINK@',
'recv': 'pactl set-source-volume @DEFAULT_SOURCE@'
}]
if args.calibrate == 'auto':
for c in volume_controllers:
if os.system(c['test']) == 0:
@@ -106,7 +107,7 @@ def wrap(cls, stream, enable):
def create_parser(description, interface_factory):
p = argparse.ArgumentParser(description=description)
subparsers = p.add_subparsers()
subparsers = p.add_subparsers(required=True)
# Modulator
sender = subparsers.add_parser(
@@ -129,7 +130,8 @@ def create_parser(description, interface_factory):
),
calib=lambda config, args: calib.send(
config=config, dst=args.dst,
volume_cmd=get_volume_cmd(args)
volume_cmd=get_volume_cmd(args),
gain=args.gain,
),
input_type=FileType('rb'),
output_type=FileType('wb', interface_factory),
@@ -183,19 +185,13 @@ def create_parser(description, interface_factory):
return p
class _Dummy:
def __enter__(self):
return self
def __exit__(self, *args):
pass
def _version():
return pkg_resources.require('amodem')[0].version
def _config_log(args):
level = fmt = None
if args.verbose == 0:
level, fmt = 'INFO', '%(message)s'
elif args.verbose == 1:
@@ -238,11 +234,11 @@ def _main():
from . import alsa # pylint: disable=import-outside-toplevel
interface = alsa.Interface(config)
elif args.audio_library == '-':
interface = _Dummy() # manually disable PortAudio
interface = contextlib.nullcontext() # manually disable PortAudio
elif args.command == 'send' and args.output is not None:
interface = _Dummy() # redirected output
interface = contextlib.nullcontext() # redirected output
elif args.command == 'recv' and args.input is not None:
interface = _Dummy() # redirected input
interface = contextlib.nullcontext() # redirected input
else:
interface = audio.Interface(config)
interface.load(args.audio_library)

View File

@@ -7,6 +7,10 @@ import time
log = logging.getLogger(__name__)
class AudioError(Exception):
pass
class Interface:
def __init__(self, config, debug=False):
self.debug = bool(debug)
@@ -35,7 +39,7 @@ class Interface:
def _error_check(self, res):
if res != 0:
raise Exception(res, self._error_string(res))
raise AudioError(res, self._error_string(res))
def __enter__(self):
self.call('Initialize')

View File

@@ -66,7 +66,7 @@ def detector(config, src, frame_length=200):
max_index = np.argmax(coeffs)
freq = config.frequencies[max_index]
rms = abs(coeffs[max_index])
coherency = rms / total
coherency = rms / total if total > 0 else 0.0
flags = [total > 0.1, peak < 1.0, coherency > 0.99]
success = all(flags)
@@ -75,10 +75,10 @@ def detector(config, src, frame_length=200):
else:
msg = f'too {errors[flags.index(False)]} signal'
yield dict(
freq=freq, rms=rms, peak=peak, coherency=coherency,
total=total, success=success, msg=msg
)
yield {
'freq': freq, 'rms': rms, 'peak': peak, 'coherency': coherency,
'total': total, 'success': success, 'msg': msg
}
def volume_calibration(result_iterator, volume_ctl):

View File

@@ -79,7 +79,7 @@ class MODEM:
symbols = np.array(list(symbols))
bits_per_symbol = np.log2(len(symbols))
bits_per_symbol = np.round(bits_per_symbol)
N = (2 ** bits_per_symbol)
N = 2 ** bits_per_symbol
assert N == len(symbols)
bits_per_symbol = int(bits_per_symbol)

View File

@@ -43,7 +43,7 @@ class Receiver:
self.plt.subplot(1, 2, 2)
self.plt.plot(np.abs(S))
self.plt.plot(equalizer.prefix)
errors = (bits != equalizer.prefix)
errors = bits != equalizer.prefix
if any(errors):
msg = f'Incorrect prefix: {sum(errors)} errors'
raise ValueError(msg)

View File

@@ -5,7 +5,7 @@ class Reader:
wait = 0.2
timeout = 2.0
bufsize = (8 << 10)
bufsize = 8 << 10
def __init__(self, fd, data_type=None, eof=False):
self.fd = fd

0
amodem/tests/__init__.py Normal file
View File

View File

@@ -1,7 +1,7 @@
from amodem import alsa, config
import mock
from .. import alsa, config
def test_alsa():
interface = alsa.Interface(config=config.fastest())

View File

@@ -1,8 +1,11 @@
import mock
import time
import pytest
from amodem import async_reader
import logging
import time
import mock
import pytest
from .. import async_reader
logging.basicConfig(format='%(message)s')

View File

@@ -1,8 +1,8 @@
from amodem import audio, config
import mock
import pytest
from .. import audio, config
def test():
length = 1024
@@ -31,4 +31,4 @@ def test():
s.close()
with pytest.raises(Exception):
interface._error_check(1)
interface._error_check(1) # pylint: disable=protected-access

View File

@@ -1,13 +1,12 @@
from amodem import calib
from amodem import common
from amodem import config
from io import BytesIO
import numpy as np
import random
import pytest
import mock
import numpy as np
import pytest
from .. import calib, common, config
config = config.fastest()

View File

@@ -1,7 +1,7 @@
from amodem import common
from amodem import config
import numpy as np
from .. import common, config
def iterlist(x, *args, **kwargs):
x = np.array(x)

View File

@@ -1,4 +1,4 @@
from amodem import config
from .. import config
def test_bitrates():

View File

@@ -1,13 +1,8 @@
import numpy as np
import pytest
from amodem import dsp
from amodem import recv
from amodem import detect
from amodem import equalizer
from amodem import sampling
from amodem import config
from amodem import common
from .. import common, config, detect, dsp, equalizer, recv, sampling
config = config.fastest()
@@ -17,7 +12,7 @@ def test_detect():
x = np.cos(2 * np.pi * config.Fc * t)
detector = detect.Detector(config, pylab=common.Dummy())
samples, amp, freq_err = detector.run(x)
_samples, amp, freq_err = detector.run(x)
assert abs(1 - amp) < 1e-12
assert abs(freq_err) < 1e-12
@@ -39,11 +34,11 @@ def test_prefix():
sampler = sampling.Sampler(signal)
return dsp.Demux(sampler=sampler, omegas=[omega], Nsym=config.Nsym)
r = recv.Receiver(config, pylab=common.Dummy())
r._prefix(symbols_stream(signal))
r._prefix(symbols_stream(signal)) # pylint: disable=protected-access
with pytest.raises(ValueError):
silence = 0 * signal
r._prefix(symbols_stream(silence))
r._prefix(symbols_stream(silence)) # pylint: disable=protected-access
def test_find_start():

View File

@@ -1,12 +1,11 @@
from amodem import dsp
from amodem import sampling
from amodem import config
import utils
import numpy as np
import random
import itertools
import numpy as np
from .. import dsp, sampling, config
from . import utils
config = config.fastest()
@@ -69,7 +68,7 @@ def quantize(q, s):
def test_overflow():
q = dsp.MODEM(config.symbols)
r = np.random.RandomState(seed=0)
for i in range(10000):
for _ in range(10000):
s = 10*(r.normal() + 1j * r.normal())
quantize(q, s)

View File

@@ -1,10 +1,9 @@
from numpy.random import RandomState
import numpy as np
import utils
from amodem import equalizer
from amodem import dsp
from amodem import config
from . import utils
from .. import config, dsp, equalizer
config = config.fastest()

View File

@@ -1,9 +1,10 @@
from amodem import framing
import random
import itertools
import random
import pytest
from .. import framing
def concat(iterable):
return bytearray(itertools.chain.from_iterable(iterable))

View File

@@ -1,8 +1,8 @@
from amodem import sampling
from amodem import common
from io import BytesIO
import numpy as np
from io import BytesIO
from .. import common, sampling
def test_resample():

View File

@@ -1,7 +1,8 @@
from amodem import stream
import subprocess as sp
import sys
from .. import stream
script = br"""
import sys
import time

View File

@@ -1,15 +1,13 @@
from amodem import main
from amodem import common
from amodem import sampling
from amodem import config
import utils
from io import BytesIO
import logging
import os
import numpy as np
import os
from io import BytesIO
import pytest
import logging
from .. import common, config, main, sampling
from . import utils
logging.basicConfig(level=logging.DEBUG, # useful for debugging
format='%(asctime)s %(levelname)-12s %(message)s')

37
pyproject.toml Normal file
View File

@@ -0,0 +1,37 @@
[project]
name = "amodem"
version = "1.16.0"
authors = [
{ name="Roman Zeyde", email="dev@romanzey.de" },
]
description = "Audio Modem Communication Library"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.7"
dependencies = [
"numpy",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Networking",
"Topic :: Communications",
]
[project.scripts]
amodem = "amodem.__main__:_main"
[project.urls]
Homepage = "https://github.com/romanz/amodem"
Issues = "https://github.com/romanz/amodem/issues"

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env python
from setuptools import setup
from setuptools.command.test import test as TestCommand
class PyTest(TestCommand):
def finalize_options(self):
self.test_args = []
self.test_suite = True
def run_tests(self):
import sys
import pytest
sys.exit(pytest.main(['.']))
setup(
name='amodem',
version='1.15.4',
description='Audio Modem Communication Library',
author='Roman Zeyde',
author_email='dev@romanzey.de',
license='MIT',
url='http://github.com/romanz/amodem',
packages=['amodem'],
tests_require=['pytest'],
cmdclass={'test': PyTest},
install_requires=['numpy'],
platforms=['POSIX'],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: MIT License',
'Operating System :: POSIX',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Networking',
'Topic :: Communications',
],
entry_points={'console_scripts': ['amodem = amodem.__main__:_main']},
)