diff --git a/amodem/detect.py b/amodem/detect.py new file mode 100644 index 0000000..9992f60 --- /dev/null +++ b/amodem/detect.py @@ -0,0 +1,83 @@ +import numpy as np +import logging +import itertools +import collections + +log = logging.getLogger(__name__) + +from . import dsp +from . import train +from . import common + + +class Detector(object): + + COHERENCE_THRESHOLD = 0.9 + + CARRIER_DURATION = sum(train.prefix) + CARRIER_THRESHOLD = int(0.9 * CARRIER_DURATION) + SEARCH_WINDOW = int(0.1 * CARRIER_DURATION) + + TIMEOUT = 10.0 # [seconds] + + def __init__(self, config): + self.freq = config.Fc + self.omega = 2 * np.pi * self.freq / config.Fs + self.Nsym = config.Nsym + self.Tsym = config.Tsym + self.maxlen = config.baud # 1 second of symbols + self.max_offset = self.TIMEOUT * config.Fs + + def run(self, samples): + counter = 0 + bufs = collections.deque([], maxlen=self.maxlen) + for offset, buf in common.iterate(samples, self.Nsym, enumerate=True): + if offset > self.max_offset: + raise ValueError('Timeout waiting for carrier') + bufs.append(buf) + + coeff = dsp.coherence(buf, self.omega) + if abs(coeff) > self.COHERENCE_THRESHOLD: + counter += 1 + else: + counter = 0 + + if counter == self.CARRIER_THRESHOLD: + break + else: + raise ValueError('No carrier detected') + + length = (self.CARRIER_THRESHOLD - 1) * self.Nsym + begin = offset - length + + x = np.concatenate(tuple(bufs)[-self.CARRIER_THRESHOLD:-1]) + Hc = dsp.exp_iwt(-self.omega, len(x)) + Zc = np.dot(Hc, x) / (0.5*len(x)) + amplitude = abs(Zc) + start_time = begin * self.Tsym / self.Nsym + log.info('Carrier detected at ~%.1f ms @ %.1f kHz:' + ' coherence=%.3f%%, amplitude=%.3f', + start_time * 1e3, self.freq / 1e3, + np.abs(dsp.coherence(x, self.omega)) * 100, amplitude) + + log.debug('Buffered %d ms of audio', len(bufs)) + + bufs = list(bufs)[-self.CARRIER_THRESHOLD-self.SEARCH_WINDOW:] + n = self.SEARCH_WINDOW + self.CARRIER_DURATION - self.CARRIER_THRESHOLD + trailing = list(itertools.islice(samples, n * self.Nsym)) + bufs.append(np.array(trailing)) + + buf = np.concatenate(bufs) + offset = self.find_start(buf, self.CARRIER_DURATION*self.Nsym) + start_time += (offset / self.Nsym - self.SEARCH_WINDOW) * self.Tsym + log.debug('Carrier starts at %.3f ms', start_time * 1e3) + + return itertools.chain(buf[offset:], samples), amplitude + + def find_start(self, buf, length): + N = len(buf) + carrier = dsp.exp_iwt(self.omega, N) + z = np.cumsum(buf * carrier) + z = np.concatenate([[0], z]) + correlations = np.abs(z[length:] - z[:-length]) + return np.argmax(correlations) diff --git a/amodem/recv.py b/amodem/recv.py index ec5a531..8d5ee6c 100644 --- a/amodem/recv.py +++ b/amodem/recv.py @@ -2,7 +2,6 @@ import numpy as np import logging import itertools import functools -import collections import time log = logging.getLogger(__name__) @@ -14,79 +13,7 @@ from . import train from . import common from . import framing from . import equalizer - - -class Detector(object): - - COHERENCE_THRESHOLD = 0.9 - - CARRIER_DURATION = sum(train.prefix) - CARRIER_THRESHOLD = int(0.9 * CARRIER_DURATION) - SEARCH_WINDOW = int(0.1 * CARRIER_DURATION) - - TIMEOUT = 10.0 # [seconds] - - def __init__(self, config): - self.freq = config.Fc - self.omega = 2 * np.pi * self.freq / config.Fs - self.Nsym = config.Nsym - self.Tsym = config.Tsym - self.maxlen = config.baud # 1 second of symbols - self.max_offset = self.TIMEOUT * config.Fs - - def run(self, samples): - counter = 0 - bufs = collections.deque([], maxlen=self.maxlen) - for offset, buf in common.iterate(samples, self.Nsym, enumerate=True): - if offset > self.max_offset: - raise ValueError('Timeout waiting for carrier') - bufs.append(buf) - - coeff = dsp.coherence(buf, self.omega) - if abs(coeff) > self.COHERENCE_THRESHOLD: - counter += 1 - else: - counter = 0 - - if counter == self.CARRIER_THRESHOLD: - break - else: - raise ValueError('No carrier detected') - - length = (self.CARRIER_THRESHOLD - 1) * self.Nsym - begin = offset - length - - x = np.concatenate(tuple(bufs)[-self.CARRIER_THRESHOLD:-1]) - Hc = dsp.exp_iwt(-self.omega, len(x)) - Zc = np.dot(Hc, x) / (0.5*len(x)) - amplitude = abs(Zc) - start_time = begin * self.Tsym / self.Nsym - log.info('Carrier detected at ~%.1f ms @ %.1f kHz:' - ' coherence=%.3f%%, amplitude=%.3f', - start_time * 1e3, self.freq / 1e3, - np.abs(dsp.coherence(x, self.omega)) * 100, amplitude) - - log.debug('Buffered %d ms of audio', len(bufs)) - - bufs = list(bufs)[-self.CARRIER_THRESHOLD-self.SEARCH_WINDOW:] - n = self.SEARCH_WINDOW + self.CARRIER_DURATION - self.CARRIER_THRESHOLD - trailing = list(itertools.islice(samples, n * self.Nsym)) - bufs.append(np.array(trailing)) - - buf = np.concatenate(bufs) - offset = self.find_start(buf, self.CARRIER_DURATION*self.Nsym) - start_time += (offset / self.Nsym - self.SEARCH_WINDOW) * self.Tsym - log.debug('Carrier starts at %.3f ms', start_time * 1e3) - - return itertools.chain(buf[offset:], samples), amplitude - - def find_start(self, buf, length): - N = len(buf) - carrier = dsp.exp_iwt(self.omega, N) - z = np.cumsum(buf * carrier) - z = np.concatenate([[0], z]) - correlations = np.abs(z[length:] - z[:-length]) - return np.argmax(correlations) +from . import detect class Receiver(object): @@ -292,7 +219,7 @@ def main(args): reader.check = common.check_saturation - detector = Detector(config=config) + detector = detect.Detector(config=config) receiver = Receiver(config=config, plt=args.plot) success = False try: diff --git a/tests/test_recv.py b/tests/test_detect.py similarity index 93% rename from tests/test_recv.py rename to tests/test_detect.py index a42ca0a..9d03a7c 100644 --- a/tests/test_recv.py +++ b/tests/test_detect.py @@ -3,6 +3,7 @@ import pytest from amodem import dsp from amodem import recv +from amodem import detect from amodem import train from amodem import sampling from amodem import config @@ -14,7 +15,7 @@ def test_detect(): t = np.arange(P * config.Nsym) * config.Ts x = np.cos(2 * np.pi * config.Fc * t) - detector = recv.Detector(config) + detector = detect.Detector(config) samples, amp = detector.run(x) assert abs(1 - amp) < 1e-12 @@ -46,7 +47,7 @@ def test_prefix(): def test_find_start(): sym = np.cos(2 * np.pi * config.Fc * np.arange(config.Nsym) * config.Ts) - detector = recv.Detector(config) + detector = detect.Detector(config) length = 200 prefix = postfix = np.tile(0 * sym, 50) diff --git a/tests/test_whole.py b/tests/test_transfer.py similarity index 100% rename from tests/test_whole.py rename to tests/test_transfer.py