e8555c89 Andy McCall 1 month ago
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import math , os , struct , sys , wave
23
24
CAS_HEADER_MARKER = b " \x1f \xa6 \xde \xba \xcc \x13 \x7d \x74 "
25
SAMPLE_RATE = 22050
26
PEAK_AMPLITUDE = 0. 7
27
FIRST_LEADER_DURATION = 4. 0
28
NORMAL_LEADER_DURATION = 2. 0
29
FINAL_SILENCE_DURATION = 2. 0
30
BAUD_RATE = 1200
31
32
33
def generate_cycle ( freq ) :
34
samples_in_cycle = int ( SAMPLE_RATE / freq )
35
cycle = [ ]
36
for i in range ( samples_in_cycle ) :
37
value = PEAK_AMPLITUDE * math . sin ( 2 * math . pi * ( i / samples_in_cycle ) )
38
cycle . append ( int ( value * 32767. 0 ) )
39
return cycle
40
41
42
def generate_silence ( duration ) :
43
samples_in_silence = int ( SAMPLE_RATE * duration )
44
return [ 0 ] * samples_in_silence
45
46
47
def encode_bit ( bit_value ) :
48
samples = [ ]
49
if bit_value == 0 :
50
samples . extend ( generate_cycle ( BAUD_RATE ) )
51
else :
52
samples . extend ( generate_cycle ( 2 * BAUD_RATE ) )
53
samples . extend ( generate_cycle ( 2 * BAUD_RATE ) )
54
return samples
55
56
57
def encode_byte ( byte_val ) :
58
samples = [ ]
59
samples . extend ( encode_bit ( 0 ) )
60
for i in range ( 8 ) :
61
bit = ( byte_val > > i ) & 1
62
samples . extend ( encode_bit ( bit ) )
63
samples . extend ( encode_bit ( 1 ) )
64
samples . extend ( encode_bit ( 1 ) )
65
return samples
66
67
68
def generate_pilot_leader ( duration_seconds ) :
69
samples = [ ]
70
cycles_needed = int ( 2 * BAUD_RATE * duration_seconds )
71
for _ in range ( cycles_needed ) :
72
samples . extend ( generate_cycle ( 2 * BAUD_RATE ) )
73
return samples
74
75
76
def cas_to_wav ( cas_filename , wav_filename ) :
77
78
79
80
81
assert os . path . splitext ( cas_filename ) [ - 1 ] . lower ( ) == " .cas"
82
assert os . path . splitext ( wav_filename ) [ - 1 ] . lower ( ) == " .wav"
83
with open ( cas_filename , " rb" ) as f :
84
cas_data = f . read ( )
85
pcm_frames = [ ]
86
idx = 0
87
total_bytes = len ( cas_data )
88
is_first_header = True
89
while idx < total_bytes :
90
if cas_data [ idx : idx + 8 ] == CAS_HEADER_MARKER :
91
leader_duration = (
92
FIRST_LEADER_DURATION if is_first_header else NORMAL_LEADER_DURATION
93
)
94
pcm_frames . extend ( generate_pilot_leader ( leader_duration ) )
95
is_first_header = False
96
idx += 8
97
else :
98
pcm_frames . extend ( encode_byte ( cas_data [ idx ] ) )
99
idx += 1
100
pcm_frames . extend ( generate_silence ( FINAL_SILENCE_DURATION ) )
101
with wave . open ( wav_filename , " wb" ) as wav_file :
102
wav_file . setnchannels ( 1 )
103
wav_file . setsampwidth ( 2 )
104
wav_file . setframerate ( SAMPLE_RATE )
105
packed_data = struct . pack ( f " { len ( pcm_frames ) } h" , * pcm_frames )
106
wav_file . writeframes ( packed_data )
107
108
109
if __name__ == " __main__" :
110
if (
111
len ( sys . argv ) != 3
112
or os . path . splitext ( sys . argv [ 1 ] ) [ - 1 ] . lower ( ) != " .cas"
113
or os . path . splitext ( sys . argv [ 2 ] ) [ - 1 ] . lower ( ) != " .wav"
114
) :
115
print ( " Usage: python cas2wav.py <input.cas> <output.wav>" )
116
else :
117
cas_to_wav ( sys . argv [ 1 ] , sys . argv [ 2 ] )