pySSTV/pysstv/sstv.py

137 wiersze
4.3 KiB
Python

2013-05-23 11:53:47 +00:00
#!/usr/bin/env python
from __future__ import division, with_statement
2013-07-02 13:18:12 +00:00
from math import sin, pi
2013-05-23 11:53:47 +00:00
from random import random
from contextlib import closing
from itertools import cycle, chain
2013-06-26 09:22:30 +00:00
from array import array
2013-06-18 22:15:18 +00:00
import wave
2013-05-23 11:53:47 +00:00
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
2013-06-19 14:45:47 +00:00
FREQ_FSKID_BIT1 = 1900
FREQ_FSKID_BIT0 = 2100
2013-05-23 11:53:47 +00:00
MSEC_VIS_START = 300
MSEC_VIS_SYNC = 10
MSEC_VIS_BIT = 30
2013-06-19 14:45:47 +00:00
MSEC_FSKID_BIT = 22
2013-05-23 11:53:47 +00:00
2013-06-18 22:15:18 +00:00
2013-05-23 11:53:47 +00:00
class SSTV(object):
2013-06-18 22:15:18 +00:00
def __init__(self, image, samples_per_sec, bits):
self.image = image
self.samples_per_sec = samples_per_sec
self.bits = bits
2013-06-19 13:44:30 +00:00
self.vox_enabled = False
2013-06-19 14:45:47 +00:00
self.fskid_payload = ''
2013-06-24 15:05:21 +00:00
self.nchannels = 1
2013-11-22 17:19:00 +00:00
self.on_init()
def on_init(self):
pass
2013-06-18 22:15:18 +00:00
BITS_TO_STRUCT = {8: 'b', 16: 'h'}
def write_wav(self, filename):
"""writes the whole image to a Microsoft WAV file"""
2013-06-26 09:22:30 +00:00
fmt = self.BITS_TO_STRUCT[self.bits]
data = array(fmt, self.gen_samples())
2013-07-01 21:14:28 +00:00
if self.nchannels != 1:
data = array(fmt, chain.from_iterable(
zip(*([data] * self.nchannels))))
2013-06-18 22:15:18 +00:00
with closing(wave.open(filename, 'wb')) as wav:
2013-06-24 15:05:21 +00:00
wav.setnchannels(self.nchannels)
2013-06-18 22:15:18 +00:00
wav.setsampwidth(self.bits // 8)
wav.setframerate(self.samples_per_sec)
2021-04-19 14:23:40 +00:00
wav.writeframes(data)
2013-06-18 22:15:18 +00:00
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
2013-06-18 22:15:18 +00:00
lowest = -amp
highest = amp - 1
alias_cycle = cycle((alias * (random() - 0.5) for _ in range(1024)))
for value, alias_item in zip(self.gen_values(), alias_cycle):
sample = int(value * amp + alias_item)
2013-07-01 21:14:28 +00:00
yield (lowest if sample <= lowest else
sample if sample <= highest else highest)
2013-06-18 22:15:18 +00:00
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
2013-06-18 22:15:18 +00:00
samples = 0
factor = 2 * pi / self.samples_per_sec
sample = 0
2013-06-18 22:15:18 +00:00
for freq, msec in self.gen_freq_bits():
samples += spms * msec
2013-07-02 11:51:06 +00:00
tx = int(samples)
freq_factor = freq * factor
for sample in range(tx):
yield sin(sample * freq_factor + offset)
offset += (sample + 1) * freq_factor
2013-06-18 22:15:18 +00:00
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
"""
2013-06-19 13:44:30 +00:00
if self.vox_enabled:
for freq in (1900, 1500, 1900, 1500, 2300, 1500, 2300, 1500):
yield freq, 100
2013-06-18 22:15:18 +00:00
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 range(7):
2013-06-18 22:15:18 +00:00
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
yield from self.gen_image_tuples()
for fskid_byte in map(ord, self.fskid_payload):
for _ in range(6):
2013-06-19 14:45:47 +00:00
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 []
2013-06-18 22:15:18 +00:00
2013-06-19 14:45:47 +00:00
def add_fskid_text(self, text):
self.fskid_payload += '\x20\x2a{0}\x01'.format(
''.join(chr(ord(c) - 0x20) for c in text))
2013-06-18 22:15:18 +00:00
def horizontal_sync(self):
yield FREQ_SYNC, self.SYNC
def byte_to_freq(value):
2013-06-18 22:15:18 +00:00
return FREQ_BLACK + FREQ_RANGE * value / 255