File: noice.py 1 #!/usr/bin/python3 2 3 # The MIT License (MIT) 4 # 5 # Copyright © 2024 pacman64 6 # 7 # Permission is hereby granted, free of charge, to any person obtaining a copy 8 # of this software and associated documentation files (the “Software”), to deal 9 # in the Software without restriction, including without limitation the rights 10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 # copies of the Software, and to permit persons to whom the Software is 12 # furnished to do so, subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be included in 15 # all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 # SOFTWARE. 24 25 26 # noice [options...] [duration...] 27 # 28 # Make white-noise as 48khz stereo wave-format sound data. 29 # 30 # The duration is an optional floating-point number of seconds: if not given, 31 # the default duration is 1 second. 32 33 34 from math import isinf, isnan 35 from random import normalvariate 36 from struct import pack 37 from sys import argv, exit, stderr, stdout 38 from wave import open as open_wav, Wave_write 39 40 41 # info is the help message shown when asked to 42 info = ''' 43 noice [options...] [duration...] 44 45 Make white-noise as 48khz stereo wave-format sound data. 46 47 The duration is an optional floating-point number of seconds: if not given, 48 the default duration is 1 second. 49 '''.strip() 50 51 # handle standard help cmd-line options, quitting right away in that case 52 if len(argv) == 2 and argv[1] in ('-h', '--h', '-help', '--help'): 53 print(info, file=stderr) 54 exit(0) 55 56 57 def run(w: Wave_write, channels, samples: int) -> None: 58 for i in range(samples): 59 for j in range(channels): 60 f = min(max(normalvariate(0.0, 0.5), -1.0), +1.0) 61 d = pack('<h', int(32_767 * f)) 62 w.writeframesraw(d) 63 64 65 duration = 1.0 66 # handle leading options 67 if len(argv) == 2: 68 try: 69 duration = float(argv[1]) 70 if duration < 0 or isnan(duration) or isinf(duration): 71 duration = 0.0 72 except Exception as e: 73 print(f'\x1b[31m{e}\x1b[0m', file=stderr) 74 exit(1) 75 76 try: 77 rate = 48_000 78 channels = 2 79 samples = int(duration * rate) 80 params = (channels, 2, rate, samples, 'NONE', 'none') 81 with open_wav(stdout.buffer, 'wb') as w: 82 w.setparams(params) 83 run(w, channels, samples) 84 stdout.buffer.flush() 85 except (BrokenPipeError, KeyboardInterrupt): 86 # quit quietly, instead of showing a confusing error message 87 stderr.flush() 88 stderr.close() 89 except Exception as e: 90 print(f'\x1b[31m{e}\x1b[0m', file=stderr) 91 exit(1)