1import sys, math
2
3DOT = 30
4DAH = 80
5OCTAVE = 2                              # 1 == 441 Hz, 2 == 882 Hz, ...
6SAMPWIDTH = 2
7FRAMERATE = 44100
8BASEFREQ = 441
9QSIZE = 20000
10
11morsetab = {
12        'A': '.-',              'a': '.-',
13        'B': '-...',            'b': '-...',
14        'C': '-.-.',            'c': '-.-.',
15        'D': '-..',             'd': '-..',
16        'E': '.',               'e': '.',
17        'F': '..-.',            'f': '..-.',
18        'G': '--.',             'g': '--.',
19        'H': '....',            'h': '....',
20        'I': '..',              'i': '..',
21        'J': '.---',            'j': '.---',
22        'K': '-.-',             'k': '-.-',
23        'L': '.-..',            'l': '.-..',
24        'M': '--',              'm': '--',
25        'N': '-.',              'n': '-.',
26        'O': '---',             'o': '---',
27        'P': '.--.',            'p': '.--.',
28        'Q': '--.-',            'q': '--.-',
29        'R': '.-.',             'r': '.-.',
30        'S': '...',             's': '...',
31        'T': '-',               't': '-',
32        'U': '..-',             'u': '..-',
33        'V': '...-',            'v': '...-',
34        'W': '.--',             'w': '.--',
35        'X': '-..-',            'x': '-..-',
36        'Y': '-.--',            'y': '-.--',
37        'Z': '--..',            'z': '--..',
38        '0': '-----',
39        '1': '.----',
40        '2': '..---',
41        '3': '...--',
42        '4': '....-',
43        '5': '.....',
44        '6': '-....',
45        '7': '--...',
46        '8': '---..',
47        '9': '----.',
48        ',': '--..--',
49        '.': '.-.-.-',
50        '?': '..--..',
51        ';': '-.-.-.',
52        ':': '---...',
53        "'": '.----.',
54        '-': '-....-',
55        '/': '-..-.',
56        '(': '-.--.-',
57        ')': '-.--.-',
58        '_': '..--.-',
59        ' ': ' '
60}
61
62# If we play at 44.1 kHz (which we do), then if we produce one sine
63# wave in 100 samples, we get a tone of 441 Hz.  If we produce two
64# sine waves in these 100 samples, we get a tone of 882 Hz.  882 Hz
65# appears to be a nice one for playing morse code.
66def mkwave(octave):
67    global sinewave, nowave
68    sinewave = ''
69    n = int(FRAMERATE / BASEFREQ)
70    for i in range(n):
71        val = int(math.sin(2 * math.pi * i * octave / n) * 0x7fff)
72        sample = chr((val >> 8) & 255) + chr(val & 255)
73        sinewave = sinewave + sample[:SAMPWIDTH]
74    nowave = '\0' * (n*SAMPWIDTH)
75
76mkwave(OCTAVE)
77
78class BufferedAudioDev:
79    def __init__(self, *args):
80        import audiodev
81        self._base = apply(audiodev.AudioDev, args)
82        self._buffer = []
83        self._filled = 0
84        self._addmethods(self._base, self._base.__class__)
85    def _addmethods(self, inst, cls):
86        for name in cls.__dict__.keys():
87            if not hasattr(self, name):
88                try:
89                    setattr(self, name, getattr(inst, name))
90                except:
91                    pass
92        for basecls in cls.__bases__:
93            self._addmethods(self, inst, basecls)
94    def writeframesraw(self, frames):
95        self._buffer.append(frames)
96        self._filled = self._filled + len(frames)
97        if self._filled >= QSIZE:
98            self.flush()
99    def wait(self):
100        self.flush()
101        self._base.wait()
102    def flush(self):
103        print 'flush: %d blocks, %d bytes' % (len(self._buffer), self._filled)
104        if self._buffer:
105            import string
106            self._base.writeframes(string.joinfields(self._buffer, ''))
107            self._buffer = []
108            self._filled = 0
109
110def main(args = sys.argv[1:]):
111    import getopt, string
112    try:
113        opts, args = getopt.getopt(args, 'o:p:')
114    except getopt.error:
115        sys.stderr.write('Usage ' + sys.argv[0] +
116                         ' [ -o outfile ] [ args ] ...\n')
117        sys.exit(1)
118    dev = None
119    for o, a in opts:
120        if o == '-o':
121            import aifc
122            dev = aifc.open(a, 'w')
123            dev.setframerate(FRAMERATE)
124            dev.setsampwidth(SAMPWIDTH)
125            dev.setnchannels(1)
126        if o == '-p':
127            mkwave(string.atoi(a))
128    if not dev:
129        dev = BufferedAudioDev()
130        dev.setoutrate(FRAMERATE)
131        dev.setsampwidth(SAMPWIDTH)
132        dev.setnchannels(1)
133        dev.close = dev.stop
134    if args:
135        line = string.join(args)
136    else:
137        line = sys.stdin.readline()
138    while line:
139        print line
140        mline = morse(line)
141        print mline
142        play(mline, dev)
143        if hasattr(dev, 'wait'):
144            dev.wait()
145        if not args:
146            line = sys.stdin.readline()
147        else:
148            line = ''
149    dev.close()
150
151# Convert a string to morse code with \001 between the characters in
152# the string.
153def morse(line):
154    res = ''
155    for c in line:
156        try:
157            res = res + morsetab[c] + '\001'
158        except KeyError:
159            pass
160    return res
161
162# Play a line of morse code.
163def play(line, dev):
164    for c in line:
165        if c == '.':
166            sine(dev, DOT)
167        elif c == '-':
168            sine(dev, DAH)
169        else:
170            pause(dev, DAH)
171        pause(dev, DOT)
172
173def sine(dev, length):
174    dev.writeframesraw(sinewave*length)
175
176def pause(dev, length):
177    dev.writeframesraw(nowave*length)
178
179if __name__ == '__main__' or sys.argv[0] == __name__:
180    main()
181