#!/usr/bin/env python3
import sys
import wave
# Open the file and read the samples
# The format expected is that this is a 1-channel file, with samples which are signed 16-bits integers
file_path = sys.argv[1]
file = wave.open(file_path, "rb")
print("Open wav file")
nb_samples = file.getnframes()
print("Sample width: {}".format(file.getsampwidth()))
print("Number of samples: {}".format(nb_samples))
binary_data = file.readframes(file.getnframes())
print("Length of the bytes obj: {}".format(len(binary_data)))
# Samples are 16-bits signed integers. We translate them from [-32768,32767] to [0,65535]
samples = []
for i in range(nb_samples):
samples.append(int.from_bytes(binary_data[i * 2:i * 2 + 2], 'little', signed=True) + 32768)
# Transform the analog signal into a digital one
# Digitalized contains something like 000000001111100000011...
min_sample = min(samples)
max_sample = max(samples)
median = int((max_sample - min_sample) / 2)
digitalized = []
# Dumb way to transform an analog signal into a digital one, this could be improved for more robust decoding.
for sample in samples:
# Response of the photodiode is inverse to the intesity: low intensity is 1 and high intensity is 0
if sample > median:
digitalized.append(0)
else:
digitalized.append(1)
# Compute the length of each sequence of bits into a list: transform smth like 000001110000 into [5,3,4]
digit_len = []
previous = digitalized[0]
cur_idx = 0
count = 0
while cur_idx < len(digitalized):
if previous != digitalized[cur_idx]:
digit_len.append(count)
count = 1
else:
count += 1
previous = digitalized[cur_idx]
cur_idx += 1
# Remove long blanks and make bit bursts into lists
bitbursts = []
first_idx = 0
cur_idx = 0
while cur_idx < len(digit_len):
# Rule of thumb for recognizing a long blank (e.g. LED off) :
# The signal is sampled at 192 kHz, hence each period takes about 7 samples. With the current encoding, while
# While transmitting data there can be no more than two half-periods of 1 or 0 at a time. So if there is a blank of
# more than two periods, it means we are in the preamble or that the LED is off.
if digit_len[cur_idx] > 14:
if cur_idx - first_idx > 1:
bitbursts.append(digit_len[first_idx:cur_idx])
first_idx = cur_idx
first_idx += 1
cur_idx += 1
if cur_idx - first_idx > 1:
bitbursts.append(digit_len[first_idx:cur_idx])
# Decode each payload
payloads = []
cur_idx = 0
payload = ""
for bitburst in bitbursts:
str = ""
# All bitbursts start with a 1, this allows to synchronize the first bit expected
cur_bit = 1
for bit in bitburst:
if bit > 5:
str += "{}{}".format(cur_bit, cur_bit)
else:
str += "{}".format(cur_bit)
cur_bit = cur_bit ^ 1
str = str[2:] # Remove the first sync bits 10 from the bit burst
if len(str) % 2 == 1:
# If last bit was a 0, it was eaten by the next slug of zeros, we need to add it manually
str += "0"
payload += str
cur_idx += 1
if cur_idx == 29:
cur_idx = 0
payloads.append(payload)
payload = ""
# Print each payload
errors = 0
bits = 0
for payload in payloads:
decoded = b""
cur_byte = 0
cur_idx = 0
for i in range(int(len(payload) / 2)):
bits += 1
cur_byte = cur_byte << 1
if payload[i * 2:i * 2 + 2] == "10":
cur_byte += 1
elif payload[i * 2:i * 2 + 2] == "01":
cur_byte += 0
else:
errors += 1
print("Decoding error of bit {} in final string".format(len(decoded) * 8 + cur_idx))
cur_idx += 1
if cur_idx == 8:
decoded += bytes([cur_byte])
cur_byte = 0
cur_idx = 0
print("\n\nData transmitted: \n{}".format(decoded))
print("Percentage of decoding errors: {:5.2f}%".format(errors / bits * 100))