mirror of
https://github.com/romanz/amodem.git
synced 2026-05-03 08:27:26 +08:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dbb3826c7 | ||
|
|
c991b2264e | ||
|
|
f30d28e39a | ||
|
|
fff10853a9 | ||
|
|
147404645c | ||
|
|
0e29f9a606 | ||
|
|
93a174142b | ||
|
|
3210638003 | ||
|
|
ceaf893675 | ||
|
|
6629cb6762 | ||
|
|
555186c2d8 | ||
|
|
66acac3e35 | ||
|
|
e1bdae2069 | ||
|
|
1ff777d226 | ||
|
|
40460e0291 | ||
|
|
43b68779a4 | ||
|
|
90aae24600 | ||
|
|
4c6315daf2 | ||
|
|
f7a151534f | ||
|
|
e637e701df | ||
|
|
5be6684fa6 | ||
|
|
0876be18e4 | ||
|
|
3b8f913fcb | ||
|
|
45d4ccae76 | ||
|
|
7cb05aaaf7 | ||
|
|
3911f16bd7 | ||
|
|
b5f8e07ae2 | ||
|
|
835841bf2e | ||
|
|
c70b3c9dc7 | ||
|
|
544dd28ddd | ||
|
|
c887dbf4e6 | ||
|
|
bf6282127c | ||
|
|
65f2559a19 | ||
|
|
1c6f8894a5 | ||
|
|
c19d11744f |
7
.bumpversion.cfg
Normal file
7
.bumpversion.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 1.14.0
|
||||
|
||||
[bumpversion:file:setup.py]
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
|
||||
install:
|
||||
- pip install .
|
||||
- pip install coveralls pep8 mock
|
||||
- pip install pytest>=2.7.3 --upgrade
|
||||
- pip install coveralls pycodestyle mock
|
||||
|
||||
script:
|
||||
- pep8 amodem/ scripts/
|
||||
- pycodestyle amodem/ scripts/
|
||||
- echo "Hello World!" | amodem send -vv -l- -o- | amodem recv -vv -l- -i-
|
||||
- coverage run --source=amodem --omit="*/__main__.py" -m py.test -vvs
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
amodem -- Audio Modem Communication Library
|
||||
|
||||
Copyright (C) 2014, Roman Zeyde.
|
||||
Copyright (C) 2014, Roman Zeyde, Google Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
13
README.rst
13
README.rst
@@ -8,8 +8,8 @@ Audio Modem Communication Library
|
||||
:target: https://coveralls.io/r/romanz/amodem?branch=master
|
||||
:alt: Code Coverage
|
||||
.. image:: https://landscape.io/github/romanz/amodem/master/landscape.svg?style=flat
|
||||
:target: https://landscape.io/github/romanz/amodem/master
|
||||
:alt: Code Health
|
||||
:target: https://landscape.io/github/romanz/amodem/master
|
||||
:alt: Code Health
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/amodem.svg
|
||||
:target: https://pypi.python.org/pypi/amodem/
|
||||
@@ -23,9 +23,6 @@ Audio Modem Communication Library
|
||||
.. image:: https://img.shields.io/pypi/status/amodem.svg
|
||||
:target: https://pypi.python.org/pypi/amodem/
|
||||
:alt: Development Status
|
||||
.. image:: https://img.shields.io/pypi/dm/amodem.svg
|
||||
:target: https://pypi.python.org/pypi/amodem/
|
||||
:alt: Downloads
|
||||
|
||||
.. image:: https://badge.waffle.io/romanz/amodem.svg?label=ready&title=ready
|
||||
:target: https://waffle.io/romanz/amodem
|
||||
@@ -55,7 +52,7 @@ The modem is using OFDM over an audio cable with the following parameters:
|
||||
|
||||
- Sampling rate: 8/16/32 kHz
|
||||
- Baud rate: 1 kHz
|
||||
- Symbol modulation: BPSK, 4-PSK, 16-QAM ,64-QAM, 256-QAM
|
||||
- Symbol modulation: BPSK, 4-PSK, 16-QAM, 64-QAM, 256-QAM
|
||||
- Carriers: 2-11 kHz (up to ten carriers)
|
||||
|
||||
This way, modem may achieve 80kbps bitrate = 10 kB/s (for best SNR).
|
||||
@@ -142,7 +139,7 @@ and send me the resulting ``audio.raw`` file for debugging::
|
||||
|
||||
~/receiver $ arecord --format=S16_LE --channels=1 --rate=32000 audio.raw
|
||||
|
||||
You can see a video of the `calibration process <http://www.youtube.com/watch?v=jRUj2Ifk-Po>`_.
|
||||
You can see a screencast of the `calibration process <https://asciinema.org/a/25065?autoplay=1>`_.
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -226,7 +223,7 @@ After the receiver has finished, verify the received file's hash::
|
||||
~/receiver $ sha256sum data.rx
|
||||
008df57d4f3ed6e7a25d25afd57d04fc73140e8df604685bd34fcab58f5ddc01 data.rx
|
||||
|
||||
You can see a video of the `data transfer process <http://www.youtube.com/watch?v=GZQUtHB8so4>`_.
|
||||
You can see a screencast of the `data transfer process <https://asciinema.org/a/25066?autoplay=1>`_.
|
||||
|
||||
Visualization
|
||||
-------------
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
from . import main, calib, audio, async
|
||||
from .config import bitrates
|
||||
from . import version
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from . import async
|
||||
from . import audio
|
||||
from . import calib
|
||||
from . import main
|
||||
from .config import bitrates
|
||||
|
||||
|
||||
# Python 3 has `buffer` attribute for byte-based I/O
|
||||
_stdin = getattr(sys.stdin, 'buffer', sys.stdin)
|
||||
@@ -97,6 +102,7 @@ def get_volume_cmd(args):
|
||||
for c in volume_controllers:
|
||||
if os.system(c['test']) == 0:
|
||||
return c[args.command]
|
||||
return None
|
||||
|
||||
|
||||
def wrap(cls, stream, enable):
|
||||
@@ -188,11 +194,15 @@ class _Dummy(object):
|
||||
pass
|
||||
|
||||
|
||||
def _version():
|
||||
return pkg_resources.require('amodem')[0].version
|
||||
|
||||
|
||||
def _main():
|
||||
fmt = ('Audio OFDM MODEM v{0:s}: '
|
||||
'{1:.1f} kb/s ({2:d}-QAM x {3:d} carriers) '
|
||||
'Fs={4:.1f} kHz')
|
||||
description = fmt.format(version.__doc__,
|
||||
description = fmt.format(_version(),
|
||||
config.modem_bps / 1e3, len(config.symbols),
|
||||
config.Nfreq, config.Fs / 1e3)
|
||||
interface = None
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
"""Code which adds Linux ALSA support for interfaces,
|
||||
recording and playing.
|
||||
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import threading
|
||||
import six # since `Queue` module was renamed to `queue` (in Python 3)
|
||||
"""Asynchronous Reading capabilities for amodem."""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
|
||||
import six # since `Queue` module was renamed to `queue` (in Python 3)
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Audio capabilities for amodem."""
|
||||
|
||||
import ctypes
|
||||
import logging
|
||||
import time
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
"""Calibration capabilities for amodem."""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import common
|
||||
from . import dsp
|
||||
from . import sampling
|
||||
from . import stream
|
||||
|
||||
import numpy as np
|
||||
import itertools
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -72,10 +75,10 @@ def detector(config, src, frame_length=200):
|
||||
else:
|
||||
msg = 'too {0} signal'.format(errors[flags.index(False)])
|
||||
|
||||
yield common.AttributeHolder(dict(
|
||||
yield dict(
|
||||
freq=freq, rms=rms, peak=peak, coherency=coherency,
|
||||
total=total, success=success, msg=msg
|
||||
))
|
||||
)
|
||||
|
||||
|
||||
def volume_calibration(result_iterator, volume_ctl):
|
||||
@@ -90,7 +93,7 @@ def volume_calibration(result_iterator, volume_ctl):
|
||||
for index, result in enumerate(itertools.chain([None], result_iterator)):
|
||||
if index % iters_per_update == 0:
|
||||
if index > 0: # skip dummy (first result)
|
||||
sign = 1 if (result.total < target_level) else -1
|
||||
sign = 1 if (result['total'] < target_level) else -1
|
||||
level = level + step * sign
|
||||
level = min(max(level, min_level), max_level)
|
||||
step = step * 0.5
|
||||
@@ -102,6 +105,7 @@ def volume_calibration(result_iterator, volume_ctl):
|
||||
|
||||
|
||||
def iter_window(iterable, size):
|
||||
# pylint: disable=stop-iteration-return
|
||||
block = []
|
||||
while True:
|
||||
item = next(iterable)
|
||||
@@ -111,12 +115,7 @@ def iter_window(iterable, size):
|
||||
yield block
|
||||
|
||||
|
||||
def recv(config, src, verbose=False, volume_cmd=None, dump_audio=None):
|
||||
fmt = '{0.freq:6.0f} Hz: {0.msg:20s}'
|
||||
if verbose:
|
||||
fields = ['total', 'rms', 'coherency', 'peak']
|
||||
fmt += ', '.join('{0}={{0.{0}:.4f}}'.format(f) for f in fields)
|
||||
|
||||
def recv_iter(config, src, volume_cmd=None, dump_audio=None):
|
||||
volume_ctl = volume_controller(volume_cmd)
|
||||
|
||||
if dump_audio:
|
||||
@@ -125,6 +124,19 @@ def recv(config, src, verbose=False, volume_cmd=None, dump_audio=None):
|
||||
result_iterator = volume_calibration(result_iterator, volume_ctl)
|
||||
for _prev, curr, _next in iter_window(result_iterator, size=3):
|
||||
# don't log errors during frequency changes
|
||||
if _prev.success and _next.success and _prev.freq != _next.freq:
|
||||
curr.msg = curr.msg if curr.success else 'frequency change'
|
||||
log.info(fmt.format(curr))
|
||||
if _prev['success'] and _next['success']:
|
||||
if _prev['freq'] != _next['freq']:
|
||||
if not curr['success']:
|
||||
curr['msg'] = 'frequency change'
|
||||
yield curr
|
||||
|
||||
|
||||
def recv(config, src, verbose=False, volume_cmd=None, dump_audio=None):
|
||||
fmt = '{freq:6.0f} Hz: {msg:20s}'
|
||||
log.info('verbose: %s', verbose)
|
||||
if verbose:
|
||||
fields = ['total', 'rms', 'coherency', 'peak']
|
||||
fmt += ', '.join('{0}={{{0}:.4f}}'.format(f) for f in fields)
|
||||
|
||||
for state in recv_iter(config, src, volume_cmd, dump_audio):
|
||||
log.info(fmt.format(**state))
|
||||
|
||||
@@ -1,28 +1,38 @@
|
||||
""" Common package functionality.
|
||||
Commom utilities and procedures for amodem.
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
scaling = 32000.0 # out of 2**15
|
||||
|
||||
|
||||
def load(fileobj):
|
||||
""" Load signal from file object. """
|
||||
return loads(fileobj.read())
|
||||
|
||||
|
||||
def loads(data):
|
||||
""" Load signal from memory buffer. """
|
||||
x = np.frombuffer(data, dtype='int16')
|
||||
x = x / scaling
|
||||
return x
|
||||
|
||||
|
||||
def dumps(sym):
|
||||
""" Dump signal to memory buffer. """
|
||||
sym = sym.real * scaling
|
||||
return sym.astype('int16').tostring()
|
||||
|
||||
|
||||
def iterate(data, size, func=None, truncate=True, index=False):
|
||||
""" Iterate over a signal, taking each time *size* elements. """
|
||||
offset = 0
|
||||
data = iter(data)
|
||||
|
||||
@@ -40,6 +50,9 @@ def iterate(data, size, func=None, truncate=True, index=False):
|
||||
|
||||
|
||||
def split(iterable, n):
|
||||
""" Split an iterable of n-tuples into n iterables of scalars.
|
||||
The k-th iterable will be equivalent to (i[k] for i in iter).
|
||||
"""
|
||||
def _gen(it, index):
|
||||
for item in it:
|
||||
yield item[index]
|
||||
@@ -49,37 +62,30 @@ def split(iterable, n):
|
||||
|
||||
|
||||
def icapture(iterable, result):
|
||||
""" Appends each yielded item to result. """
|
||||
for i in iter(iterable):
|
||||
result.append(i)
|
||||
yield i
|
||||
|
||||
|
||||
def take(iterable, n):
|
||||
""" Take n elements from iterable, and return them as a numpy array. """
|
||||
return np.array(list(itertools.islice(iterable, n)))
|
||||
|
||||
|
||||
# "Python 3" zip re-implementation for Python 2
|
||||
def izip(iterables):
|
||||
""" "Python 3" zip re-implementation for Python 2. """
|
||||
# pylint: disable=stop-iteration-return
|
||||
iterables = [iter(iterable) for iterable in iterables]
|
||||
while True:
|
||||
yield tuple([next(iterable) for iterable in iterables])
|
||||
|
||||
|
||||
class Dummy(object):
|
||||
""" Dummy placeholder object for testing and mocking. """
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
|
||||
class AttributeHolder(object):
|
||||
|
||||
def __init__(self, d):
|
||||
self.__dict__.update(d)
|
||||
|
||||
def __repr__(self):
|
||||
items = sorted(self.__dict__.items())
|
||||
args = ', '.join('{0}={1}'.format(k, v) for k, v in items)
|
||||
return '{0}({1})'.format(self.__class__.__name__, args)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Configuration class."""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
@@ -49,7 +51,7 @@ class Configuration(object):
|
||||
])
|
||||
|
||||
# QAM constellation
|
||||
Nx = 2 ** int(np.ceil(bits_per_symbol / 2))
|
||||
Nx = 2 ** int(np.ceil(bits_per_symbol // 2))
|
||||
Ny = self.Npoints // Nx
|
||||
symbols = [complex(x, y) for x in range(Nx) for y in range(Ny)]
|
||||
symbols = np.array(symbols)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
"""Signal detection capabilities for amodem."""
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import dsp
|
||||
from . import equalizer
|
||||
from . import common
|
||||
|
||||
import numpy as np
|
||||
import logging
|
||||
import itertools
|
||||
import collections
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"""Digital Signal Processing capabilities for amodem."""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import common
|
||||
@@ -53,14 +55,13 @@ def coherence(x, omega):
|
||||
n = len(x)
|
||||
Hc = exp_iwt(-omega, n) / np.sqrt(0.5*n)
|
||||
norm_x = norm(x)
|
||||
if norm_x:
|
||||
return np.dot(Hc, x) / norm_x
|
||||
else:
|
||||
if not norm_x:
|
||||
return 0.0
|
||||
return np.dot(Hc, x) / norm_x
|
||||
|
||||
|
||||
def linear_regression(x, y):
|
||||
''' Find (a,b) such that y = a*x + b. '''
|
||||
""" Find (a,b) such that y = a*x + b. """
|
||||
x = np.array(x)
|
||||
y = np.array(y)
|
||||
mean_x = np.mean(x)
|
||||
@@ -98,7 +99,7 @@ class MODEM(object):
|
||||
yield self.encode_map[bits_tuple]
|
||||
|
||||
def decode(self, symbols, error_handler=None):
|
||||
''' Maximum-likelihood decoding, using naive nearest-neighbour. '''
|
||||
""" Maximum-likelihood decoding, using naive nearest-neighbour. """
|
||||
symbols_vec = self.symbols
|
||||
_dec = self.decode_list
|
||||
for received in symbols:
|
||||
@@ -111,7 +112,7 @@ class MODEM(object):
|
||||
|
||||
|
||||
def prbs(reg, poly, bits):
|
||||
''' Simple pseudo-random number generator. '''
|
||||
""" Simple pseudo-random number generator. """
|
||||
mask = (1 << bits) - 1
|
||||
|
||||
size = 0 # effective register size (in bits)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""Audio equalizing capabilities for amodem."""
|
||||
|
||||
import itertools
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import dsp
|
||||
from . import sampling
|
||||
from . import levinson
|
||||
|
||||
import numpy as np
|
||||
import itertools
|
||||
|
||||
|
||||
class Equalizer(object):
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from . import common
|
||||
|
||||
import binascii
|
||||
import functools
|
||||
import itertools
|
||||
import binascii
|
||||
import struct
|
||||
import logging
|
||||
import struct
|
||||
|
||||
from . import common
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -26,8 +27,10 @@ class Checksum(object):
|
||||
payload = data[self.size:]
|
||||
expected = _checksum_func(payload)
|
||||
if received != expected:
|
||||
log.warning('Invalid checksum: %04x != %04x', received, expected)
|
||||
log.warning('Invalid checksum: %08x != %08x', received, expected)
|
||||
raise ValueError('Invalid checksum')
|
||||
else:
|
||||
log.debug('Good checksum: %08x', received)
|
||||
return payload
|
||||
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import numpy as np
|
||||
|
||||
|
||||
def solver(t, y):
|
||||
''' Solve Mx = y for x, where M[i,j] = t[|i-j|], in O(N^2) steps.
|
||||
""" Solve Mx = y for x, where M[i,j] = t[|i-j|], in O(N^2) steps.
|
||||
See http://en.wikipedia.org/wiki/Levinson_recursion for details.
|
||||
'''
|
||||
"""
|
||||
N = len(t)
|
||||
assert len(y) == N
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import numpy as np
|
||||
import logging
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import send as _send
|
||||
from . import recv as _recv
|
||||
from . import framing, common, stream, detect, sampling
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import dsp
|
||||
from . import common
|
||||
from . import framing
|
||||
from . import equalizer
|
||||
|
||||
import numpy as np
|
||||
import logging
|
||||
import itertools
|
||||
import functools
|
||||
import time
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -28,6 +29,7 @@ class Receiver(object):
|
||||
self.equalizer = equalizer.Equalizer(config)
|
||||
self.carrier_index = config.carrier_index
|
||||
self.output_size = 0 # number of bytes written to output stream
|
||||
self.freq_err_gain = 0.01 * self.Tsym # integration feedback gain
|
||||
|
||||
def _prefix(self, symbols, gain=1.0):
|
||||
S = common.take(symbols, len(equalizer.prefix))
|
||||
@@ -48,10 +50,10 @@ class Receiver(object):
|
||||
log.debug('Prefix OK')
|
||||
|
||||
def _train(self, sampler, order, lookahead):
|
||||
Nfreq = len(self.frequencies)
|
||||
equalizer_length = equalizer.equalizer_length
|
||||
train_symbols = self.equalizer.train_symbols(equalizer_length)
|
||||
train_signal = self.equalizer.modulator(train_symbols) * Nfreq
|
||||
train_signal = (self.equalizer.modulator(train_symbols) *
|
||||
len(self.frequencies))
|
||||
|
||||
prefix = postfix = equalizer.silence_length * self.Nsym
|
||||
signal_length = equalizer_length * self.Nsym + prefix + postfix
|
||||
@@ -137,10 +139,10 @@ class Receiver(object):
|
||||
|
||||
def _update_sampler(self, errors, sampler):
|
||||
err = np.array([e for v in errors.values() for e in v])
|
||||
err = np.mean(np.angle(err))/(2*np.pi) if len(err) else 0
|
||||
err = np.mean(np.angle(err))/(2*np.pi) if err.size else 0
|
||||
errors.clear()
|
||||
|
||||
sampler.freq -= 0.01 * err * self.Tsym
|
||||
sampler.freq -= self.freq_err_gain * err
|
||||
sampler.offset -= err
|
||||
|
||||
def _report_progress(self, noise, sampler):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
import numpy as np
|
||||
import itertools
|
||||
|
||||
from amodem import common
|
||||
import numpy as np
|
||||
|
||||
from . import common
|
||||
|
||||
|
||||
class Interpolator(object):
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import common
|
||||
from . import equalizer
|
||||
from . import dsp
|
||||
|
||||
import numpy as np
|
||||
import logging
|
||||
import itertools
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ def test_too_strong():
|
||||
calib.send(config, p, gain=1.001, limit=32)
|
||||
p.buf.seek(0)
|
||||
for r in calib.detector(config, src=p):
|
||||
assert not r.success
|
||||
assert r.msg == 'too strong signal'
|
||||
assert not r['success']
|
||||
assert r['msg'] == 'too strong signal'
|
||||
|
||||
|
||||
def test_too_weak():
|
||||
@@ -48,8 +48,8 @@ def test_too_weak():
|
||||
calib.send(config, p, gain=0.01, limit=32)
|
||||
p.buf.seek(0)
|
||||
for r in calib.detector(config, src=p):
|
||||
assert not r.success
|
||||
assert r.msg == 'too weak signal'
|
||||
assert not r['success']
|
||||
assert r['msg'] == 'too weak signal'
|
||||
|
||||
|
||||
def test_too_noisy():
|
||||
@@ -57,8 +57,8 @@ def test_too_noisy():
|
||||
signal = np.array([r.choice([-1, 1]) for i in range(int(config.Fs))])
|
||||
src = BytesIO(common.dumps(signal * 0.5))
|
||||
for r in calib.detector(config, src=src):
|
||||
assert not r.success
|
||||
assert r.msg == 'too noisy signal'
|
||||
assert not r['success']
|
||||
assert r['msg'] == 'too noisy signal'
|
||||
|
||||
|
||||
def test_errors():
|
||||
@@ -94,9 +94,9 @@ def test_drift(freq_err):
|
||||
src = BytesIO(common.dumps(signal))
|
||||
iters = 0
|
||||
for r in calib.detector(config, src, frame_length=frame_length):
|
||||
assert r.success is True
|
||||
assert abs(r.rms - rms) < 1e-3
|
||||
assert abs(r.total - rms) < 1e-3
|
||||
assert r['success'] is True
|
||||
assert abs(r['rms'] - rms) < 1e-3
|
||||
assert abs(r['total'] - rms) < 1e-3
|
||||
iters += 1
|
||||
|
||||
assert iters > 0
|
||||
@@ -146,3 +146,15 @@ def test_recv_binary_search():
|
||||
fmt = 'ctl {0:.0f}%'
|
||||
expected = [mock.call(shell=True, args=fmt.format(100 * g)) for g in gains]
|
||||
assert check_call.mock_calls == expected
|
||||
|
||||
|
||||
def test_recv_freq_change():
|
||||
p = ProcessMock()
|
||||
calib.send(config, p, gain=0.5, limit=2)
|
||||
offset = p.buf.tell() // 16
|
||||
p.buf.seek(offset)
|
||||
messages = [state['msg'] for state in calib.recv_iter(config, p)]
|
||||
assert messages == [
|
||||
'good signal', 'good signal', 'good signal',
|
||||
'frequency change',
|
||||
'good signal', 'good signal', 'good signal']
|
||||
|
||||
@@ -54,14 +54,6 @@ def test_izip():
|
||||
assert list(common.izip([x, y])) == list(zip(x, y))
|
||||
|
||||
|
||||
def test_holder():
|
||||
d = {'x': 1, 'y': 2.3}
|
||||
a = common.AttributeHolder(d)
|
||||
assert a.x == d['x']
|
||||
assert a.y == d['y']
|
||||
assert repr(a) == 'AttributeHolder(x=1, y=2.3)'
|
||||
|
||||
|
||||
def test_configs():
|
||||
default = config.Configuration()
|
||||
fastest = config.fastest()
|
||||
|
||||
@@ -4,3 +4,9 @@ from amodem import config
|
||||
def test_bitrates():
|
||||
for rate, cfg in sorted(config.bitrates.items()):
|
||||
assert rate * 1000 == cfg.modem_bps
|
||||
|
||||
|
||||
def test_slowest():
|
||||
c = config.slowest()
|
||||
assert c.Npoints == 2
|
||||
assert list(c.symbols) == [-1j, 1j]
|
||||
|
||||
@@ -8,6 +8,7 @@ import pytest
|
||||
def concat(iterable):
|
||||
return bytearray(itertools.chain.from_iterable(iterable))
|
||||
|
||||
|
||||
r = random.Random(0)
|
||||
blob = bytearray(r.randrange(0, 256) for i in range(64 * 1024))
|
||||
|
||||
|
||||
@@ -20,6 +20,6 @@ def test_resample():
|
||||
|
||||
|
||||
def test_coeffs():
|
||||
I = sampling.Interpolator(width=4, resolution=16)
|
||||
err = I.filt[0] - [0, 0, 0, 1, 0, 0, 0, 0]
|
||||
interp = sampling.Interpolator(width=4, resolution=16)
|
||||
err = interp.filt[0] - [0, 0, 0, 1, 0, 0, 0, 0]
|
||||
assert np.max(np.abs(err)) < 1e-10
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
'1.12.0'
|
||||
@@ -1,3 +0,0 @@
|
||||
numpy
|
||||
six
|
||||
argcomplete
|
||||
@@ -1,4 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Script that exposes pylab's spectogram plotting
|
||||
capabilities to the command line. It implements this
|
||||
for amodem.config Configurations.
|
||||
|
||||
"""
|
||||
|
||||
import pylab
|
||||
import numpy as np
|
||||
from amodem import common
|
||||
@@ -27,5 +34,6 @@ def main():
|
||||
|
||||
pylab.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Script that records audio through an interface
|
||||
and stores it into an amodem.config Configuration.
|
||||
|
||||
"""
|
||||
import argparse
|
||||
from amodem import audio
|
||||
from amodem.config import Configuration
|
||||
@@ -7,7 +12,6 @@ from amodem.config import Configuration
|
||||
def run(args):
|
||||
config = Configuration()
|
||||
with open(args.filename, 'wb') as dst:
|
||||
print dst
|
||||
interface = audio.Interface(config=config)
|
||||
with interface.load(args.audio_library):
|
||||
src = interface.recorder()
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Script that exposes the amodem.resample() function
|
||||
to the command line, taking parameters via standard
|
||||
inputs and returning results via standard outputs.
|
||||
"""
|
||||
|
||||
from amodem.sampling import resample
|
||||
import argparse
|
||||
import sys
|
||||
@@ -11,5 +17,6 @@ def main():
|
||||
|
||||
resample(src=sys.stdin, dst=sys.stdout, df=args.df)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
22
setup.py
22
setup.py
@@ -2,17 +2,6 @@
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
import os
|
||||
import ast
|
||||
|
||||
def parse_vesrion():
|
||||
cwd = os.path.dirname(__name__)
|
||||
version_file = os.path.join(cwd, 'amodem', 'version.py')
|
||||
|
||||
tree = ast.parse(open(version_file).read())
|
||||
expr, = tree.body
|
||||
return expr.value.s
|
||||
|
||||
class PyTest(TestCommand):
|
||||
|
||||
def finalize_options(self):
|
||||
@@ -22,14 +11,14 @@ class PyTest(TestCommand):
|
||||
def run_tests(self):
|
||||
import sys
|
||||
import pytest
|
||||
sys.exit(pytest.main(['tests']))
|
||||
sys.exit(pytest.main(['.']))
|
||||
|
||||
setup(
|
||||
name='amodem',
|
||||
version=parse_vesrion(),
|
||||
version='1.14.0',
|
||||
description='Audio Modem Communication Library',
|
||||
author='Roman Zeyde',
|
||||
author_email='roman.zeyde@gmail.com',
|
||||
author_email='dev@romanzey.de',
|
||||
license='MIT',
|
||||
url='http://github.com/romanz/amodem',
|
||||
packages=['amodem'],
|
||||
@@ -38,16 +27,17 @@ setup(
|
||||
install_requires=['numpy', 'six'],
|
||||
platforms=['POSIX'],
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Information Technology',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: POSIX',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: System :: Networking',
|
||||
'Topic :: Communications',
|
||||
|
||||
10
tox.ini
10
tox.ini
@@ -1,15 +1,15 @@
|
||||
[tox]
|
||||
envlist = py27,py34
|
||||
envlist = py27,py3
|
||||
[testenv]
|
||||
deps=
|
||||
pytest
|
||||
mock
|
||||
pep8
|
||||
pycodestyle
|
||||
coverage
|
||||
pylint
|
||||
six
|
||||
six
|
||||
commands=
|
||||
pep8 amodem/ scripts/
|
||||
pylint --extension-pkg-whitelist=numpy --report=no amodem --rcfile .pylintrc
|
||||
pycodestyle amodem/ scripts/
|
||||
pylint --extension-pkg-whitelist=numpy --reports=no amodem --rcfile .pylintrc
|
||||
coverage run --source amodem/ --omit="*/__main__.py" -m py.test -v
|
||||
coverage report
|
||||
|
||||
Reference in New Issue
Block a user