kopia lustrzana https://github.com/dnet/pySSTV
138 wiersze
4.3 KiB
Python
138 wiersze
4.3 KiB
Python
#!/usr/bin/env python
|
|
|
|
from __future__ import division, with_statement
|
|
from math import sin, pi
|
|
from random import random
|
|
from contextlib import closing
|
|
from itertools import imap, izip, cycle, chain
|
|
from array import array
|
|
import 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
|
|
FREQ_FSKID_BIT1 = 1900
|
|
FREQ_FSKID_BIT0 = 2100
|
|
|
|
MSEC_VIS_START = 300
|
|
MSEC_VIS_SYNC = 10
|
|
MSEC_VIS_BIT = 30
|
|
MSEC_FSKID_BIT = 22
|
|
|
|
|
|
class SSTV(object):
|
|
|
|
def __init__(self, image, samples_per_sec, bits):
|
|
self.image = image
|
|
self.samples_per_sec = samples_per_sec
|
|
self.bits = bits
|
|
self.vox_enabled = False
|
|
self.fskid_payload = ''
|
|
self.nchannels = 1
|
|
self.on_init()
|
|
|
|
def on_init(self):
|
|
pass
|
|
|
|
BITS_TO_STRUCT = {8: 'b', 16: 'h'}
|
|
|
|
def write_wav(self, filename):
|
|
"""writes the whole image to a Microsoft WAV file"""
|
|
fmt = self.BITS_TO_STRUCT[self.bits]
|
|
data = array(fmt, self.gen_samples())
|
|
if self.nchannels != 1:
|
|
data = array(fmt, chain.from_iterable(
|
|
izip(*([data] * self.nchannels))))
|
|
with closing(wave.open(filename, 'wb')) as wav:
|
|
wav.setnchannels(self.nchannels)
|
|
wav.setsampwidth(self.bits // 8)
|
|
wav.setframerate(self.samples_per_sec)
|
|
wav.writeframes(data.tostring())
|
|
|
|
def gen_samples(self):
|
|
"""generates discrete samples from gen_values()
|
|
|
|
performs quantization according to
|
|
the bits per sample value given during construction
|
|
"""
|
|
max_value = 2 ** self.bits
|
|
alias = 1 / max_value
|
|
amp = max_value // 2
|
|
lowest = -amp
|
|
highest = amp - 1
|
|
alias_cycle = cycle((alias * (random() - 0.5) for _ in xrange(1024)))
|
|
for value, alias_item in izip(self.gen_values(), alias_cycle):
|
|
sample = int(value * amp + alias_item)
|
|
yield (lowest if sample <= lowest else
|
|
sample if sample <= highest else highest)
|
|
|
|
def gen_values(self):
|
|
"""generates samples between -1 and +1 from gen_freq_bits()
|
|
|
|
performs sampling according to
|
|
the samples per second value given during construction
|
|
"""
|
|
spms = self.samples_per_sec / 1000
|
|
offset = 0
|
|
samples = 0
|
|
factor = 2 * pi / self.samples_per_sec
|
|
sample = 0
|
|
for freq, msec in self.gen_freq_bits():
|
|
samples += spms * msec
|
|
tx = int(samples)
|
|
freq_factor = freq * factor
|
|
for sample in xrange(tx):
|
|
yield sin(sample * freq_factor + offset)
|
|
offset += (sample + 1) * freq_factor
|
|
samples -= tx
|
|
|
|
def gen_freq_bits(self):
|
|
"""generates tuples (freq, msec) that describe a sine wave segment
|
|
|
|
frequency "freq" in Hz and duration "msec" in ms
|
|
"""
|
|
if self.vox_enabled:
|
|
for freq in (1900, 1500, 1900, 1500, 2300, 1500, 2300, 1500):
|
|
yield freq, 100
|
|
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
|
|
for freq_tuple in self.gen_image_tuples():
|
|
yield freq_tuple
|
|
for fskid_byte in imap(ord, self.fskid_payload):
|
|
for _ in xrange(6):
|
|
bit = fskid_byte & 1
|
|
fskid_byte >>= 1
|
|
bit_freq = FREQ_FSKID_BIT1 if bit == 1 else FREQ_FSKID_BIT0
|
|
yield bit_freq, MSEC_FSKID_BIT
|
|
|
|
def gen_image_tuples(self):
|
|
return []
|
|
|
|
def add_fskid_text(self, text):
|
|
self.fskid_payload += '\x20\x2a{0}\x01'.format(
|
|
''.join(chr(ord(c) - 0x20) for c in text))
|
|
|
|
def horizontal_sync(self):
|
|
yield FREQ_SYNC, self.SYNC
|
|
|
|
|
|
def byte_to_freq(value):
|
|
return FREQ_BLACK + FREQ_RANGE * value / 255
|