forked from gstrike/py-kcs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kcs_encode.py
executable file
·89 lines (75 loc) · 2.88 KB
/
kcs_encode.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
#!/usr/bin/env python3
# kcs_encode.py
#
# Author : David Beazley (http://www.dabeaz.com)
# Copyright (C) 2010
#
# Requires Python 3.1.2 or newer
#
# Updated 2022: Greg Strike (https://www.gregorystrike.com)
"""
Takes the contents of any file and encodes it into a Kansas
City Standard WAV file, that when played will upload data via the
cassette tape input on various vintage home computers. See
http://en.wikipedia.org/wiki/Kansas_City_standard
"""
import wave
# A few global parameters related to the encoding
FRAMERATE = 9600 # Hz
ONES_FREQ = 2400 # Hz (per KCS)
ZERO_FREQ = 1200 # Hz (per KCS)
AMPLITUDE = 128 # Amplitude of generated square waves
CENTER = 128 # Center point of generated waves
# Create a single square wave cycle of a given frequency
def make_square_wave(freq,framerate):
n = int(framerate/freq/2)
return bytearray([CENTER-AMPLITUDE//2])*n + \
bytearray([CENTER+AMPLITUDE//2])*n
# Create the wave patterns that encode 1s and 0s
one_pulse = make_square_wave(ONES_FREQ,FRAMERATE)*8
zero_pulse = make_square_wave(ZERO_FREQ,FRAMERATE)*4
# Pause to insert after carriage returns (10 NULL bytes)
null_pulse = ((zero_pulse * 9) + (one_pulse * 2))*10
# Take a single byte value and turn it into a bytearray representing
# the associated waveform along with the required start and stop bits.
def kcs_encode_byte(byteval):
bitmasks = [0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80]
# The start bit (0)
encoded = bytearray(zero_pulse)
# 8 data bits
for mask in bitmasks:
encoded.extend(one_pulse if (byteval & mask) else zero_pulse)
# Two stop bits (1)
encoded.extend(one_pulse)
encoded.extend(one_pulse)
return encoded
# Write a WAV file with encoded data. leader and trailer specify the
# number of seconds of carrier signal to encode before and after the data
def kcs_write_wav(filename,data,leader,trailer):
w = wave.open(filename,"wb")
w.setnchannels(1)
w.setsampwidth(1)
w.setframerate(FRAMERATE)
# Write the leader
w.writeframes(one_pulse*(int(FRAMERATE/len(one_pulse))*leader))
# Encode the actual data
for byteval in data:
w.writeframes(kcs_encode_byte(byteval))
# The following 3 lines are not appropriate for encoding of binary data
#if byteval == 0x0d:
# If CR, emit a short pause (10 NULL bytes)
# w.writeframes(null_pulse)
# Write the trailer
w.writeframes(one_pulse*(int(FRAMERATE/len(one_pulse))*trailer))
w.close()
if __name__ == '__main__':
import sys
if len(sys.argv) != 3:
print("Usage : %s infile outfile" % sys.argv[0],file=sys.stderr)
raise SystemExit(1)
in_filename = sys.argv[1]
out_filename = sys.argv[2]
data = open(in_filename,"rb").read()
#data = data.replace('\n','\r\n') # Fix line endings
rawdata = bytearray(data)
kcs_write_wav(out_filename,rawdata,5,5)