#!/usr/bin/env python from __future__ import division, with_statement from math import sin, pi, floor from random import random from contextlib import closing import struct, wave FREQ_VIS_BIT1 = 1100 FREQ_SYNC = 1200 FREQ_VIS_BIT0 = 1300 FREQ_BLACK = 1500 FREQ_VIS_START = 1900 FREQ_WHITE = 2300 FREQ_RANGE = FREQ_WHITE - FREQ_BLACK MSEC_VIS_START = 300 MSEC_VIS_SYNC = 10 MSEC_VIS_BIT = 30 class SSTV(object): def __init__(self, image, samples_per_sec, bits): self.image = image self.samples_per_sec = samples_per_sec self.bits = bits BITS_TO_STRUCT = {8: 'b', 16: 'h'} def write_wav(self, filename): fmt = '<' + self.BITS_TO_STRUCT[self.bits] data = ''.join(struct.pack(fmt, b) for b in self.gen_samples()) with closing(wave.open(filename, 'wb')) as wav: wav.setnchannels(1) wav.setsampwidth(self.bits // 8) wav.setframerate(self.samples_per_sec) wav.writeframes(data) def gen_samples(self): """generates bits from gen_values""" max_value = 2 ** self.bits alias = 1 / max_value amp = max_value / 2 lowest = -amp highest = amp - 1 for value in self.gen_values(): sample = int(round(value * amp + alias * (random() - 0.5))) yield max(min(highest, sample), lowest) def gen_values(self): """generates -1 .. +1 values from freq_bits""" spms = self.samples_per_sec / 1000 param = 0 samples = 0 for freq, msec in self.gen_freq_bits(): offset = param samples += spms * msec tx = floor(samples) for sample in xrange(int(tx)): t = sample / self.samples_per_sec param = t * freq * 2 * pi + offset yield sin(param) samples -= tx def gen_freq_bits(self): """generates (freq, msec) tuples from image""" yield FREQ_VIS_START, MSEC_VIS_START yield FREQ_SYNC, MSEC_VIS_SYNC yield FREQ_VIS_START, MSEC_VIS_START yield FREQ_SYNC, MSEC_VIS_BIT # start bit vis = self.VIS_CODE num_ones = 0 for _ in xrange(7): bit = vis & 1 vis >>= 1 num_ones += bit bit_freq = FREQ_VIS_BIT1 if bit == 1 else FREQ_VIS_BIT0 yield bit_freq, MSEC_VIS_BIT parity_freq = FREQ_VIS_BIT1 if num_ones % 2 == 1 else FREQ_VIS_BIT0 yield parity_freq, MSEC_VIS_BIT yield FREQ_SYNC, MSEC_VIS_BIT # stop bit def horizontal_sync(self): yield FREQ_SYNC, self.SYNC def byte_to_freq(value): return FREQ_BLACK + FREQ_RANGE * value / 255