diff --git a/decode.py b/decode.py new file mode 100755 index 0000000..8c024c1 --- /dev/null +++ b/decode.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 + +""" @package docstring +Manchester encoder + +Encodes a file using the manchester encoding and outputs it as audio file +See https://www.youtube.com/watch?v=8BhjXqw9MqI&list=PLowKtXNTBypH19whXTVoG3oKSuOcw_XeW&index=3 +and following videos by awesome Ben Eater +Very slow and inefficient implementation, meant only to be clear and didactic + +@author Daniele Verducci + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +import os +import sys +import logging +import wave +import struct + +NAME = 'manchester-decoder' +VERSION = '0.1' +DESCRIPTION = 'Decodes a file using the manchester encoding' + +FRAME_DELIMITER = 129 # (1, 0, 0, 0, 0, 0, 0, 1) +PREAMBLE_DURATION = 128 +ZERO_POINT = 0 # The 0 value: values less than this are considered 0, more than this 1 + +class Main: + + def __init__(self): + self._log = logging.getLogger('main') + self.clockDuration = 0 + + def run(self, inputFile, outputFile): + # Open input audio file + self.audioSource = wave.open(inputFile,'r') + + try: + self.syncWithClock() + self._log.info("Sync: clock duration is {}".format(self.clockDuration)) + #self.waitForStart() + #self._log.info("Found start of data") + except ValueError as e: + self._log.error("Ran out of input data before completing initialization!") + + #self.decodeFile() + + self.audioSource.close() + + def syncWithClock(self): + # Uses the preamble to obtain the clock duration + i = 0 + while True: + (duration, value) = self.readRawBit() + + print(duration, value) + + if i < PREAMBLE_DURATION/4: + # First cycles are used to extimate clock duration + self.clockDuration = self.clockDuration + duration / 2 + i = i + 1 + # We are now synced with clock. Add a 25% "slack" to make following samples at + # 1/4 and 3/4 to clock cycle: remember that every cycle has 2 values: 01 if the + # encoded bit was 1 and 10 if it was 0 + for x in range(0, int(self.clockDuration / 4)): + self.readRawBit() + + def waitForStart(self): + # After the clock has been extimated, continue reading and wait for first delimiter + lastByte = 0 + while True: + value = decodeBit() + + # Shift the byte to left and add the read bit in the least significant position + lastByte = lastByte << 1 + if value: + lastByte = lastByte + 1 + + if lastByte == FRAME_DELIMITER: + # Found frame delimiter! We are in sync! YAY! + return + + #def decodeFile(): + # From the bit after the FRAME_DELIMITER on, there is the real data. Decode and write to file + + #try: + + #except ValueError as e: + # Completed reading file + + def readRawBit(self): + # Reads a raw bit. Returns the duration of that bit and the value. + duration = 0 + prev = None + while True: + frame = self.audioSource.readframes(1) + if not frame: + raise ValueError('No more data to read') + + v = int(struct.unpack(' ZERO_POINT + + if prev is None: + prev = v + else: + # Detect zero crossing + if prev < ZERO_POINT and v > ZERO_POINT: + break + if prev > ZERO_POINT and v < ZERO_POINT: + break + + prev = v + duration = duration + 1 + + return (duration, prev > ZERO_POINT) + + def decodeBit(self): + # Reads and decodes 2 raw bits into 1 decoded bit. 01 (raising) = 1, 10 (falling) = 0 + # Works only once clock is synced + # The bits are read at 1/4 and 3/4 of clock cycle + firstHalfFrame = self.audioSource.readframes((self.clockDuration/2) - 1) + if not firstHalfFrame: + raise ValueError('No more data to read') + + secondHalfFrame = self.audioSource.readframes(1) + if not secondHalfFrame: + raise ValueError('No more data to read') + + firstHalfRawBit = int(struct.unpack(' ZERO_POINT + secondHalfRawBit = int(struct.unpack(' ZERO_POINT + + if not firstHalfRawBit and secondHalfRawBit: + return True + elif firstHalfRawBit and not secondHalfRawBit: + return True + else: + # Lost sync!!! + raise Exception() + + + + + + + + + + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser( + prog = NAME + '.py', + description = NAME + ' ' + VERSION + '\n' + DESCRIPTION, + formatter_class = argparse.RawTextHelpFormatter + ) + parser.add_argument('inputFile', help="audio file to decode (in wav format)") + parser.add_argument('outputFile', help="decoded file to write") + parser.add_argument('-v', '--verbose', action='store_true', help="verbose output") + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.INFO) + else: + logging.basicConfig() + + main = Main() + main.run(args.inputFile, args.outputFile) + + sys.exit(0) diff --git a/encode.py b/encode.py index 42ee5d1..27bbfdb 100755 --- a/encode.py +++ b/encode.py @@ -33,6 +33,7 @@ VERSION = '0.1' DESCRIPTION = 'Encodes a file using the manchester encoding and outputs it as audio file' FRAME_DELIMITER = 129 # (1, 0, 0, 0, 0, 0, 0, 1) +PREAMBLE_DURATION = 128 AUDIO_VOLUME = 16384 # 0 to 32767 AUDIO_BITRATE = 44100 @@ -110,7 +111,7 @@ class Main: def outputPreamble(self): # Outputs the preable: a sequence of 64 "1" and "0" used to facilitate the receiver # syncronizing on our clock. The sequence starts with 1 and ends with 0 - for x in range(64): + for x in range(PREAMBLE_DURATION): self.encodeBit(x % 2 == 0) def encodeBit(self, bit):