mirror of
https://github.com/dnet/pySSTV.git
synced 2026-01-01 14:20:00 +01:00
Python 2 returns the string representation with just calling
writeframes(data) instead of writeframes(data.tostring())
/tmp/post.wav
0000 0000: 52 49 46 46 C4 AA 00 00 57 41 56 45 66 6D 74 20 RIFF.... WAVEfmt
0000 0010: 10 00 00 00 01 00 01 00 80 BB 00 00 00 77 01 00 ........ .....w..
0000 0020: 02 00 10 00 64 61 74 61 A0 AA 00 00 61 72 72 61 ....data ....arra
0000 0030: 79 28 27 68 27 2C 20 5B 30 2C 20 38 30 36 35 2C y('h', [ 0, 8065,
0000 0040: 20 31 35 36 33 35 2C 20 32 32 32 34 32 2C 20 32 15635, 22242, 2
This change adds an escape hatch that could work for both major Python
versions, except Python 3.0 and 3.1 but those should be rare and
unsupported anyway.
142 lines
4.4 KiB
Python
142 lines
4.4 KiB
Python
#!/usr/bin/env python
|
|
|
|
from __future__ import division, with_statement
|
|
from six.moves import range
|
|
from six.moves import map
|
|
from six.moves import zip
|
|
from six import PY3
|
|
from math import sin, pi
|
|
from random import random
|
|
from contextlib import closing
|
|
from itertools import 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(
|
|
zip(*([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 if PY3 else 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 range(1024)))
|
|
for value, alias_item in zip(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 range(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 range(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 map(ord, self.fskid_payload):
|
|
for _ in range(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
|