1#!/usr/bin/env python3
2"""Play a sine signal."""
3import argparse
4import sys
5
6import numpy as np
7import sounddevice as sd
8
9
10def int_or_str(text):
11    """Helper function for argument parsing."""
12    try:
13        return int(text)
14    except ValueError:
15        return text
16
17
18parser = argparse.ArgumentParser(add_help=False)
19parser.add_argument(
20    '-l', '--list-devices', action='store_true',
21    help='show list of audio devices and exit')
22args, remaining = parser.parse_known_args()
23if args.list_devices:
24    print(sd.query_devices())
25    parser.exit(0)
26parser = argparse.ArgumentParser(
27    description=__doc__,
28    formatter_class=argparse.RawDescriptionHelpFormatter,
29    parents=[parser])
30parser.add_argument(
31    'frequency', nargs='?', metavar='FREQUENCY', type=float, default=500,
32    help='frequency in Hz (default: %(default)s)')
33parser.add_argument(
34    '-d', '--device', type=int_or_str,
35    help='output device (numeric ID or substring)')
36parser.add_argument(
37    '-a', '--amplitude', type=float, default=0.2,
38    help='amplitude (default: %(default)s)')
39args = parser.parse_args(remaining)
40
41start_idx = 0
42
43try:
44    samplerate = sd.query_devices(args.device, 'output')['default_samplerate']
45
46    def callback(outdata, frames, time, status):
47        if status:
48            print(status, file=sys.stderr)
49        global start_idx
50        t = (start_idx + np.arange(frames)) / samplerate
51        t = t.reshape(-1, 1)
52        outdata[:] = args.amplitude * np.sin(2 * np.pi * args.frequency * t)
53        start_idx += frames
54
55    with sd.OutputStream(device=args.device, channels=1, callback=callback,
56                         samplerate=samplerate):
57        print('#' * 80)
58        print('press Return to quit')
59        print('#' * 80)
60        input()
61except KeyboardInterrupt:
62    parser.exit('')
63except Exception as e:
64    parser.exit(type(e).__name__ + ': ' + str(e))
65