-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathsstv.py
136 lines (116 loc) · 4.25 KB
/
sstv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/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 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)
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
yield from self.gen_image_tuples()
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