Coverage for src / audioio / playaudio.py: 40%

828 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-12 09:39 +0000

1"""Play numpy arrays as audio. 

2 

3Accepted data for playback are 1-D or 2-D (frames, channels) numpy 

4arrays with values ranging from -1 to 1. 

5If necessary data are downsampled automatically to match supported 

6sampling rates. 

7 

8## Class 

9 

10Use the `PlayAudio` class for audio output to a speaker: 

11 

12``` 

13with PlayAudio() as audio: 

14 audio.beep() 

15``` 

16 

17or without context management: 

18 

19``` 

20audio = PlayAudio() 

21audio.beep(1.0, 'a4') 

22audio.close() 

23``` 

24 

25## Functions 

26 

27Alternatively, the globally defined functions `play()` and `beep()` 

28use the global instance `handle` of the `PlayAudio` class to play a 

29sound on the default audio output device. 

30 

31- `play()`: playback audio data. 

32- `beep()`: playback a tone. 

33- `close()`: close the global PlayAudio instance. 

34 

35 

36## Helper functions 

37 

38- `speaker_devices()`: query available output devices. 

39- `print_speaker_devices()`: print available output devices. 

40- `fade_in()`: fade in a signal in place. 

41- `fade_out()`: fade out a signal in place. 

42- `fade()`: fade in and out a signal in place. 

43- `note2freq()`: convert textual note to corresponding frequency. 

44 

45 

46## Installation 

47 

48You might need to install additional packages for better audio output. 

49See [installation](https://bendalab.github.io/audioio/installation) 

50for further instructions. 

51 

52 

53## Demo 

54 

55For a demo, run the script as: 

56``` 

57python -m src.audioio.playaudio 

58``` 

59 

60""" 

61 

62import os 

63import warnings 

64import numpy as np 

65 

66from sys import platform 

67from scipy.signal import decimate 

68from time import sleep 

69from io import BytesIO 

70from multiprocessing import Process 

71 

72from .audiomodules import * 

73 

74 

75handle = None 

76"""Default audio device handler. 

77 

78Defaults to `None`. Will get a PlayAudio instance assigned via 

79`play()` or `beep()`. 

80""" 

81 

82 

83def note2freq(note, a4freq=440.0): 

84 """Convert textual note to corresponding frequency. 

85 

86 Parameters 

87 ---------- 

88 note: string 

89 A musical note like 'a4', 'f#3', 'eb5'. 

90 The first character is the note, it can be 

91 'a', 'b', 'c', 'd', 'e', 'f', or 'g'. 

92 The optional second character is either a 'b' 

93 or a '#' to decrease or increase by half a note. 

94 The last character specifies the octave. 

95 'a4' is defined by `a4freq`. 

96 a4freq: float 

97 The frequency of a4 in Hertz. 

98 

99 Returns 

100 ------- 

101 freq: float 

102 The frequency of the note in Hertz. 

103 

104 Raises 

105 ------ 

106 ValueError 

107 No or an invalid note was specified. 

108 """ 

109 freq = a4freq 

110 tone = 0 

111 octave = 4 

112 if not isinstance(note, str) or len(note) == 0: 

113 raise ValueError('no note specified') 

114 # note: 

115 if note[0] < 'a' or note[0] > 'g': 

116 raise ValueError('invalid note', note[0]) 

117 index = 0 

118 tonemap = [0, 2, 3, 5, 7, 8, 10] 

119 tone = tonemap[ord(note[index]) - ord('a')] 

120 index += 1 

121 # flat or sharp: 

122 flat = False 

123 sharp = False 

124 if index < len(note): 

125 if note[index] == 'b': 

126 flat = True 

127 tone -= 1 

128 index += 1 

129 elif note[index] == '#': 

130 sharp = True 

131 tone += 1 

132 index += 1 

133 # octave: 

134 if index < len(note) and note[index] >= '0' and note[index] <= '9': 

135 octave = 0 

136 while index < len(note) and note[index] >= '0' and note[index] <= '9': 

137 octave *= 10 

138 octave += ord(note[index]) - ord('0') 

139 index += 1 

140 # remaining characters: 

141 if index < len(note): 

142 raise ValueError('invalid characters in note', note) 

143 # compute frequency: 

144 if (tone >= 3 and not sharp) or (tone == 2 and flat): 

145 octave -= 1 

146 tone += 12*(octave-4) 

147 # frequency: 

148 freq = a4freq * 2.0**(tone/12.0) 

149 return freq 

150 

151 

152def fade_in(data, rate, fadetime): 

153 """Fade in a signal in place. 

154 

155 The first `fadetime` seconds of the data are multiplied with a 

156 squared sine in place. If `fadetime` is larger than half the 

157 duration of the data, then `fadetime` is reduced to half of the 

158 duration. 

159  

160 Parameters 

161 ---------- 

162 data: array 

163 The data to be faded in, either 1-D array for single channel output, 

164 or 2-D array with first axis time and second axis channel. 

165 rate: float 

166 The sampling rate in Hertz. 

167 fadetime: float 

168 Time for fading in in seconds. 

169 """ 

170 if len(data) < 4: 

171 return 

172 nr = min(int(np.round(fadetime*rate)), len(data)//2) 

173 x = np.arange(float(nr))/float(nr) # 0 to pi/2 

174 y = np.sin(0.5*np.pi*x)**2.0 

175 if data.ndim > 1: 

176 data[:nr, :] *= y[:, None] 

177 else: 

178 data[:nr] *= y 

179 

180 

181def fade_out(data, rate, fadetime): 

182 """Fade out a signal in place. 

183 

184 The last `fadetime` seconds of the data are multiplied with a 

185 squared sine in place. If `fadetime` is larger than half the 

186 duration of the data, then `fadetime` is reduced to half of the 

187 duration. 

188  

189 Parameters 

190 ---------- 

191 data: array 

192 The data to be faded out, either 1-D array for single channel output, 

193 or 2-D array with first axis time and second axis channel. 

194 rate: float 

195 The sampling rate in Hertz. 

196 fadetime: float 

197 Time for fading out in seconds. 

198 """ 

199 if len(data) < 4: 

200 return 

201 nr = min(int(np.round(fadetime*rate)), len(data)//2) 

202 x = np.arange(float(nr))/float(nr) + 1.0 # pi/2 to pi 

203 y = np.sin(0.5*np.pi*x)**2.0 

204 if data.ndim > 1: 

205 data[-nr:, :] *= y[:, None] 

206 else: 

207 data[-nr:] *= y 

208 

209 

210def fade(data, rate, fadetime): 

211 """Fade in and out a signal in place. 

212 

213 The first and last `fadetime` seconds of the data are multiplied 

214 with a squared sine in place. If `fadetime` is larger than half the 

215 duration of the data, then `fadetime` is reduced to half of the 

216 duration. 

217  

218 Parameters 

219 ---------- 

220 data: array 

221 The data to be faded, either 1-D array for single channel output, 

222 or 2-D array with first axis time and second axis channel. 

223 rate: float 

224 The sampling rate in Hertz. 

225 fadetime: float 

226 Time for fading in and out in seconds. 

227 """ 

228 fade_in(data, rate, fadetime) 

229 fade_out(data, rate, fadetime) 

230 

231 

232class PlayAudio(object): 

233 """ Audio playback. 

234 

235 Parameters 

236 ---------- 

237 device_index: int or None 

238 Index of the playback device to be used. 

239 If None take the default device. 

240 Use the speaker_devices() function to query available devices. 

241 verbose: int 

242 Verbosity level. 

243 library: str or None 

244 If specified, open a specific sound library. 

245 

246 

247 Attributes 

248 ---------- 

249 lib: string 

250 The library used for playback. 

251 verbose: int 

252 Verbosity level. 

253 

254 Methods 

255 ------- 

256 - `play(data, rate, scale=None, blocking=True)`: Playback audio data. 

257 - `beep(duration=0.5, frequency=880.0, amplitude=0.5, rate=44100.0, fadetime=0.05, blocking=True)`: Playback a pure tone. 

258 - `open()`: Initialize the PlayAudio class with the best module available. 

259 - `close()`: Terminate module for playing audio. 

260 - `stop()`: Stop any playback in progress. 

261 - `active()`: Report whether playback is in progress. 

262 

263 Examples 

264 -------- 

265 ``` 

266 from audioio import PlayAudio 

267  

268 with PlayAudio() as audio: 

269 audio.beep() 

270 ``` 

271 or without context management: 

272 ``` 

273 audio = PlayAudio() 

274 audio.beep(1.0, 'a4') 

275 audio.close() 

276 ``` 

277 """ 

278 

279 def __init__(self, device_index=None, verbose=0, library=None): 

280 self.verbose = verbose 

281 self.handle = None 

282 self._do_play = self._play 

283 self.close = self._close 

284 self.stop = self._stop 

285 self.active = self._active 

286 self.lib = None 

287 self.open(device_index, library) 

288 

289 def _close(self): 

290 """Terminate PlayAudio class for playing audio.""" 

291 self.handle = None 

292 self._do_play = self._play 

293 self.close = self._close 

294 self.stop = self._stop 

295 self.lib = None 

296 

297 def _stop(self): 

298 """Stop any playback in progress.""" 

299 pass 

300 

301 def _active(self): 

302 """Report whether playback is in progress. 

303 

304 Returns 

305 ------- 

306 active: bool 

307 True if audio output is still active. 

308 Libraries that do not support this return False independent 

309 of whether output is in progress or not. 

310 """ 

311 return False 

312 

313 def _play(self, blocking=True): 

314 """Default implementation of playing a sound: does nothing.""" 

315 pass 

316 

317 def play(self, data, rate, scale=None, blocking=True, device_index=None): 

318 """Playback audio data. 

319 

320 Parameters 

321 ---------- 

322 data: array 

323 The data to be played, either 1-D array for single channel output, 

324 or 2-D array with first axis time and second axis channel. 

325 Data values range between -1 and 1. 

326 rate: float 

327 The sampling rate in Hertz. 

328 scale: float 

329 Multiply data with scale before playing. 

330 If `None` scale it to the maximum value, if 1.0 do not scale. 

331 blocking: boolean 

332 If False do not block.  

333 device_index: int or None 

334 Index of the playback device to be used, 

335 if not already openend via the constructor. 

336 If None take the default device. 

337 

338 Raises 

339 ------ 

340 ValueError 

341 Invalid sampling rate (after some attemps of resampling). 

342 FileNotFoundError 

343 No audio device for playback. 

344 """ 

345 if self.handle is None: 

346 self.open(device_index) 

347 else: 

348 self.stop() 

349 self.rate = rate 

350 self.channels = 1 

351 if data.ndim > 1: 

352 self.channels = data.shape[1] 

353 # convert data: 

354 rawdata = data - np.mean(data, axis=0) 

355 if scale is None: 

356 scale = 1.0/np.max(np.abs(rawdata)) 

357 rawdata *= scale 

358 self.data = np.floor(rawdata*(2**15-1)).astype(np.int16, order='C') 

359 self.index = 0 

360 self._do_play(blocking) 

361 

362 def beep(self, duration=0.5, frequency=880.0, amplitude=0.5, rate=44100.0, 

363 fadetime=0.05, blocking=True, device_index=None): 

364 """Playback a pure tone. 

365 

366 Parameters 

367 ---------- 

368 duration: float 

369 The duration of the tone in seconds. 

370 frequency: float or string 

371 If float, the frequency of the tone in Hertz. 

372 If string, a musical note like 'f#5'. 

373 See `note2freq()` for details. 

374 amplitude: float 

375 The ampliude (volume) of the tone in the range from 0.0 to 1.0. 

376 rate: float 

377 The sampling rate in Hertz. 

378 fadetime: float 

379 Time for fading in and out in seconds. 

380 blocking: boolean 

381 If False do not block. 

382 device_index: int or None 

383 Index of the playback device to be used, 

384 if not already openend via the constructor. 

385 If None take the default device. 

386 

387 Raises 

388 ------ 

389 ValueError 

390 Invalid sampling rate (after some attemps of resampling). 

391 FileNotFoundError 

392 No audio device for playback. 

393  

394 See also 

395 -------- 

396 https://mail.python.org/pipermail/tutor/2012-September/091529.html 

397 for fourier series based construction of waveforms.  

398 """ 

399 # frequency 

400 if isinstance(frequency, str): 

401 frequency = note2freq(frequency) 

402 # sine wave: 

403 time = np.arange(0.0, duration, 1.0/rate) 

404 data = amplitude*np.sin(2.0*np.pi*frequency*time) 

405 # fade in and out: 

406 fade(data, rate, fadetime) 

407 # # final click for testing (mono only): 

408 # data = np.hstack((data, np.sin(2.0*np.pi*1000.0*time[0:int(np.ceil(4.0*rate/1000.0))]))) 

409 # play: 

410 self.play(data, rate, scale=1.0, blocking=blocking, 

411 device_index=device_index) 

412 

413 def _down_sample(self, channels, scale=1): 

414 """Sample the data down and adapt maximum channel number.""" 

415 iscale = 1 

416 rscale = scale 

417 if isinstance(scale, int): 

418 iscale = scale 

419 rscale = 1.0 

420 elif scale > 2: 

421 iscale = int(np.floor(scale)) 

422 rscale = scale/iscale 

423 

424 if iscale > 1: 

425 data = decimate(self.data, iscale, axis=0) 

426 if self.data.ndim > 1: 

427 self.data = np.asarray(data[:,:channels], 

428 dtype=np.int16, order='C') 

429 else: 

430 self.data = np.asarray(data, dtype=np.int16, order='C') 

431 if self.verbose > 0: 

432 print(f'decimated sampling rate from {self.rate:.1f}Hz down to {self.rate/iscale:.1f}Hz') 

433 self.rate /= iscale 

434 

435 if rscale != 1.0: 

436 dt0 = 1.0/self.rate 

437 dt1 = rscale/self.rate 

438 old_time = np.arange(len(self.data))*dt0 

439 new_time = np.arange(0.0, old_time[-1]+0.5*dt0, dt1) 

440 if self.data.ndim > 1: 

441 data = np.zeros((len(new_time), channels), order='C') 

442 for c in range(channels): 

443 data[:,c] = np.interp(new_time, old_time, self.data[:,c]) 

444 else: 

445 data = np.interp(new_time, old_time, self.data) 

446 self.data = np.asarray(data, dtype=self.data.dtype, order='C') 

447 if self.verbose > 0: 

448 print(f'adapted sampling rate from {self.rate:.1f}Hz to {self.rate/rscale:.1f}Hz') 

449 self.rate /= rscale 

450 self.channels = channels 

451 

452 def __del__(self): 

453 """Terminate the audio module.""" 

454 self.close() 

455 

456 def __enter__(self): 

457 return self 

458 

459 def __exit__(self, type, value, tb): 

460 self.__del__() 

461 return value 

462 

463 

464 def open_pyaudio(self, device_index=None): 

465 """Initialize audio output via PyAudio module. 

466 

467 Parameters 

468 ---------- 

469 device_index: int or None 

470 Index of the playback device to be used. 

471 If None take the default device. 

472 

473 Raises 

474 ------ 

475 ImportError 

476 PyAudio module is not available. 

477 FileNotFoundError 

478 Failed to open audio device. 

479 

480 Documentation 

481 ------------- 

482 https://people.csail.mit.edu/hubert/pyaudio/ 

483 http://www.portaudio.com/ 

484 

485 Installation 

486 ------------ 

487 ``` 

488 sudo apt install -y libportaudio2 portaudio19-dev python-pyaudio python3-pyaudio 

489 ``` 

490  

491 On Windows, download an appropriate (latest version, 32 or 64 bit) wheel from 

492 <https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio>. Install this file with pip, 

493 that is go to the folder where the wheel file is downloaded and run 

494 ``` 

495 pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl 

496 ``` 

497 replace the wheel file name by the one you downloaded. 

498 """ 

499 if not audio_modules['pyaudio']: 

500 raise ImportError 

501 oldstderr = os.dup(2) 

502 os.close(2) 

503 tmpfile = 'tmpfile.tmp' 

504 os.open(tmpfile, os.O_WRONLY | os.O_CREAT) 

505 self.handle = pyaudio.PyAudio() 

506 self.stream = None 

507 os.close(2) 

508 os.dup(oldstderr) 

509 os.close(oldstderr) 

510 os.remove(tmpfile) 

511 try: 

512 if device_index is None: 

513 info = self.handle.get_default_output_device_info() 

514 else: 

515 info = self.handle.get_device_info_by_index(device_index) 

516 self.max_channels = info['maxOutputChannels'] 

517 self.default_rate = info['defaultSampleRate'] 

518 self.device_index = info['index'] 

519 self.handle.is_format_supported(self.default_rate, 

520 output_device=self.device_index, 

521 output_channels=1, 

522 output_format=pyaudio.paInt16) 

523 except Exception as e: 

524 if self.verbose > 0: 

525 print(str(e)) 

526 self.handle.terminate() 

527 self._close() 

528 raise FileNotFoundError('failed to initialize audio device') 

529 self.index = 0 

530 self.data = None 

531 self.close = self._close_pyaudio 

532 self.stop = self._stop_pyaudio 

533 self.active = self._active_pyaudio 

534 self._do_play = self._play_pyaudio 

535 self.lib = 'pyaudio' 

536 return self 

537 

538 def _callback_pyaudio(self, in_data, frames, time_info, status): 

539 """Callback for pyaudio for supplying output with data.""" 

540 flag = pyaudio.paContinue 

541 if not self.run: 

542 flag = pyaudio.paComplete 

543 if self.index < len(self.data): 

544 out_data = self.data[self.index:self.index+frames] 

545 self.index += len(out_data) 

546 # zero padding: 

547 if len(out_data) < frames: 

548 if self.data.ndim > 1: 

549 out_data = np.vstack((out_data, 

550 np.zeros((frames-len(out_data), self.channels), dtype=np.int16))) 

551 else: 

552 out_data = np.hstack((out_data, np.zeros(frames-len(out_data), dtype=np.int16))) 

553 return (out_data, flag) 

554 else: 

555 # we need to play more to make sure everything is played! 

556 # This is because of an ALSA bug and might be fixed in newer versions, 

557 # see http://music.columbia.edu/pipermail/portaudio/2012-May/013959.html 

558 out_data = np.zeros(frames*self.channels, dtype=np.int16) 

559 self.index += frames 

560 if self.index >= len(self.data) + 2*self.latency: 

561 flag = pyaudio.paComplete 

562 return (out_data, flag) 

563 

564 def _stop_pyaudio(self): 

565 """Stop any ongoing activity of the pyaudio module.""" 

566 if self.stream is not None: 

567 if self.stream.is_active(): 

568 # fade out: 

569 fadetime = 0.1 

570 nr = int(np.round(fadetime*self.rate)) 

571 index = self.index+nr 

572 if nr > len(self.data) - index: 

573 nr = len(self.data) - index 

574 else: 

575 self.data[index+nr:] = 0 

576 if nr > 0: 

577 for k in range(nr) : 

578 self.data[index+(nr-k-1)] = np.array(self.data[index+(nr-k-1)] * 

579 np.sin(0.5*np.pi*float(k)/float(nr))**2.0, np.int16, order='C') 

580 try: 

581 sleep(2*fadetime) 

582 except SystemError: 

583 # pyaudio interferes with sleep in python 3.10 

584 pass 

585 if self.stream.is_active(): 

586 self.run = False 

587 while self.stream.is_active(): 

588 try: 

589 sleep(0.01) 

590 except SystemError: 

591 # pyaudio interferes with sleep in python 3.10 

592 pass 

593 self.stream.stop_stream() 

594 self.stream.close() 

595 self.stream = None 

596 

597 def _active_pyaudio(self): 

598 """Report whether playback is in progress. 

599 

600 Returns 

601 ------- 

602 active: bool 

603 True if audio output is still active. 

604 """ 

605 return self.stream is not None and self.stream.is_active() 

606 

607 def _play_pyaudio(self, blocking=True): 

608 """Play audio data using the pyaudio module. 

609 

610 Parameters 

611 ---------- 

612 blocking: boolean 

613 If False do not block. 

614 

615 Raises 

616 ------ 

617 ValueError 

618 Invalid sampling rate (after some attemps of resampling). 

619 """ 

620 # check channel count: 

621 channels = self.channels 

622 if self.channels > self.max_channels: 

623 channels = self.max_channels 

624 # check sampling rate: 

625 scale_fac = 1 

626 scaled_rate = self.rate 

627 max_rate = 48000.0 

628 if self.rate > max_rate: 

629 scale_fac = int(np.ceil(self.rate/max_rate)) 

630 scaled_rate = int(self.rate//scale_fac) 

631 rates = [self.rate, scaled_rate, 44100, 48000, 22050, self.default_rate] 

632 scales = [1, scale_fac, None, None, None, None] 

633 success = False 

634 for rate, scale in zip(rates, scales): 

635 try: 

636 if self.handle.is_format_supported(int(rate), 

637 output_device=self.device_index, 

638 output_channels=channels, 

639 output_format=pyaudio.paInt16): 

640 if scale is None: 

641 scale = self.rate/float(rate) 

642 success = True 

643 break 

644 except Exception as e: 

645 if self.verbose > 0: 

646 print(f'invalid sampling rate of {rate}Hz') 

647 if e.args[1] != pyaudio.paInvalidSampleRate: 

648 raise 

649 if not success: 

650 raise ValueError('No valid sampling rate found') 

651 if channels != self.channels or scale != 1: 

652 self._down_sample(channels, scale) 

653 

654 # play: 

655 self.run = True 

656 self.stream = self.handle.open(format=pyaudio.paInt16, channels=self.channels, 

657 rate=int(self.rate), output=True, 

658 stream_callback=self._callback_pyaudio) 

659 self.latency = int(self.stream.get_output_latency()*self.rate) 

660 self.stream.start_stream() 

661 if blocking: 

662 while self.stream.is_active(): 

663 try: 

664 sleep(0.01) 

665 except (ValueError, SystemError): 

666 # pyaudio interferes with sleep in python 3.10 

667 pass 

668 self.run = False 

669 self.stream.stop_stream() 

670 self.stream.close() 

671 self.stream = None 

672 

673 def _close_pyaudio(self): 

674 """Terminate pyaudio module.""" 

675 self._stop_pyaudio() 

676 if self.handle is not None: 

677 self.handle.terminate() 

678 self._close() 

679 

680 

681 def open_sounddevice(self, device_index=None): 

682 """Initialize audio output via sounddevice module. 

683 

684 Parameters 

685 ---------- 

686 device_index: int or None 

687 Index of the playback device to be used. 

688 If None take the default device. 

689 

690 Raises 

691 ------ 

692 ImportError 

693 sounddevice module is not available.  

694 FileNotFoundError 

695 Failed to open audio device. 

696 

697 Documentation 

698 ------------- 

699 https://python-sounddevice.readthedocs.io 

700 

701 Installation 

702 ------------ 

703 ``` 

704 sudo apt install -y libportaudio2 portaudio19-dev 

705 sudo pip install sounddevice 

706 ``` 

707 """ 

708 if not audio_modules['sounddevice']: 

709 raise ImportError 

710 self.handle = True 

711 self.index = 0 

712 self.data = None 

713 self.stream = None 

714 try: 

715 if device_index is None: 

716 info_in = sounddevice.query_devices(kind='input') 

717 info_out = sounddevice.query_devices(kind='output') 

718 if info_in['index'] == info_out['index']: 

719 info = info_out 

720 else: 

721 info = info_out 

722 if info_in['max_output_channels'] > info_out['max_output_channels']: 

723 info = info_in 

724 else: 

725 info = sounddevice.query_devices(device_index) 

726 self.device_index = info['index'] 

727 self.max_channels = info['max_output_channels'] 

728 self.default_rate = info['default_samplerate'] 

729 sounddevice.check_output_settings(device=self.device_index, 

730 channels=1, dtype=np.int16, 

731 samplerate=48000) 

732 except Exception as e: 

733 if self.verbose > 0: 

734 print(str(e)) 

735 self._close() 

736 raise FileNotFoundError('failed to initialize audio device') 

737 self.close = self._close_sounddevice 

738 self.stop = self._stop_sounddevice 

739 self.active = self._active_sounddevice 

740 self._do_play = self._play_sounddevice 

741 self.lib = 'sounddevice' 

742 return self 

743 

744 def _callback_sounddevice(self, out_data, frames, time_info, status): 

745 """Callback for sounddevice for supplying output with data.""" 

746 if status: 

747 print(status) 

748 if self.index < len(self.data): 

749 ndata = len(self.data) - self.index 

750 if ndata >= frames : 

751 if self.data.ndim <= 1: 

752 out_data[:,0] = self.data[self.index:self.index+frames] 

753 else: 

754 out_data[:, :] = self.data[self.index:self.index+frames, :] 

755 self.index += frames 

756 else: 

757 if self.data.ndim <= 1: 

758 out_data[:ndata, 0] = self.data[self.index:] 

759 out_data[ndata:, 0] = np.zeros(frames-ndata, dtype=np.int16) 

760 else: 

761 out_data[:ndata, :] = self.data[self.index:, :] 

762 out_data[ndata:, :] = np.zeros((frames-ndata, self.channels), 

763 dtype=np.int16) 

764 self.index += frames 

765 else: 

766 # we need to play more to make sure everything is played! 

767 # This is because of an ALSA bug and might be fixed in newer versions, 

768 # see http://music.columbia.edu/pipermail/portaudio/2012-May/013959.html 

769 if self.data.ndim <= 1: 

770 out_data[:, 0] = np.zeros(frames, dtype=np.int16) 

771 else: 

772 out_data[:, :] = np.zeros((frames, self.channels), dtype=np.int16) 

773 self.index += frames 

774 if self.index >= len(self.data) + 2*self.latency: 

775 raise sounddevice.CallbackStop 

776 if not self.run: 

777 raise sounddevice.CallbackStop 

778 

779 def _stop_sounddevice(self): 

780 """Stop any ongoing activity of the sounddevice module.""" 

781 if self.stream is not None: 

782 if self.stream.active: 

783 # fade out: 

784 fadetime = 0.1 

785 nr = int(np.round(fadetime*self.rate)) 

786 index = self.index+nr 

787 if nr > len(self.data) - index: 

788 nr = len(self.data) - index 

789 else: 

790 self.data[index+nr:] = 0 

791 if nr > 0: 

792 for k in range(nr) : 

793 self.data[index+(nr-k-1)] = np.array(self.data[index+(nr-k-1)] * 

794 np.sin(0.5*np.pi*float(k)/float(nr))**2.0, np.int16, order='C') 

795 sounddevice.sleep(int(2000*fadetime)) 

796 if self.stream.active: 

797 self.run = False 

798 while self.stream.active: 

799 sounddevice.sleep(10) 

800 self.stream.stop() 

801 self.stream.close() 

802 self.stream = None 

803 

804 def _active_sounddevice(self): 

805 """Report whether playback is in progress. 

806 

807 Returns 

808 ------- 

809 active: bool 

810 True if audio output is still active. 

811 """ 

812 return self.stream is not None and self.stream.active 

813 

814 def _play_sounddevice(self, blocking=True): 

815 """Play audio data using the sounddevice module. 

816 

817 Parameters 

818 ---------- 

819 blocking: boolean 

820 If False do not block. 

821 

822 Raises 

823 ------ 

824 ValueError 

825 Invalid sampling rate (after some attemps of resampling). 

826 """ 

827 # check channel count: 

828 channels = self.channels 

829 if self.channels > self.max_channels: 

830 channels = self.max_channels 

831 # check sampling rate: 

832 scale_fac = 1 

833 scaled_rate = self.rate 

834 max_rate = 48000.0 

835 if self.rate > max_rate: 

836 scale_fac = int(np.ceil(self.rate/max_rate)) 

837 scaled_rate = int(self.rate//scale_fac) 

838 rates = [self.rate, scaled_rate, 44100, 48000, 22050, self.default_rate] 

839 scales = [1, scale_fac, None, None, None, None] 

840 success = False 

841 for rate, scale in zip(rates, scales): 

842 try: 

843 sounddevice.check_output_settings(device=self.device_index, 

844 channels=channels, 

845 dtype=np.int16, 

846 samplerate=rate) 

847 if scale is None: 

848 scale = self.rate/float(rate) 

849 success = True 

850 break 

851 except sounddevice.PortAudioError as pae: 

852 if pae.args[1] != -9997: 

853 raise 

854 elif self.verbose > 0: 

855 print(f'invalid sampling rate of {rate}Hz') 

856 if not success: 

857 raise ValueError('No valid sampling rate found') 

858 if channels != self.channels or scale != 1: 

859 self._down_sample(channels, scale) 

860 

861 # play: 

862 self.stream = sounddevice.OutputStream(samplerate=self.rate, 

863 device=self.device_index, 

864 channels=self.channels, 

865 dtype=np.int16, 

866 callback=self._callback_sounddevice) 

867 self.latency = self.stream.latency*self.rate 

868 self.run = True 

869 self.stream.start() 

870 if blocking: 

871 while self.stream.active: 

872 sounddevice.sleep(10) 

873 self.run = False 

874 self.stream.stop() 

875 self.stream.close() 

876 self.stream = None 

877 

878 def _close_sounddevice(self): 

879 """Terminate sounddevice module.""" 

880 self._stop_sounddevice() 

881 self._close() 

882 

883 

884 def open_simpleaudio(self, device_index=None): 

885 """Initialize audio output via simpleaudio package. 

886 

887 Parameters 

888 ---------- 

889 device_index: int or None 

890 Index of the playback device to be used. 

891 If None take the default device. 

892 Not supported by simpleaudio. 

893 

894 Raises 

895 ------ 

896 ImportError 

897 simpleaudio module is not available. 

898 

899 Documentation 

900 ------------- 

901 https://simpleaudio.readthedocs.io 

902 """ 

903 if not audio_modules['simpleaudio']: 

904 raise ImportError 

905 self.handle = True 

906 self._do_play = self._play_simpleaudio 

907 self.close = self._close_simpleaudio 

908 self.stop = self._stop_simpleaudio 

909 self.active = self._active_simpleaudio 

910 self.lib = 'simpleaudio' 

911 return self 

912 

913 def _stop_simpleaudio(self): 

914 """Stop any ongoing activity of the simpleaudio package.""" 

915 if self.handle is not None and self.handle is not True: 

916 self.handle.stop() 

917 

918 def _active_simpleaudio(self): 

919 """Report whether playback is in progress. 

920 

921 Returns 

922 ------- 

923 active: bool 

924 True if audio output is still active. 

925 """ 

926 return self.handle is not None and \ 

927 self.handle is not True and self.handle.is_playing() 

928 

929 def _play_simpleaudio(self, blocking=True): 

930 """Play audio data using the simpleaudio package. 

931 

932 Parameters 

933 ---------- 

934 blocking: boolean 

935 If False do not block.  

936 

937 Raises 

938 ------ 

939 ValueError 

940 Invalid sampling rate (after some attemps of resampling). 

941 FileNotFoundError 

942 No audio device for playback. 

943 """ 

944 rates = [self.rate, 44100, 48000, 22050] 

945 scales = [1, None, None, None] 

946 success = False 

947 for rate, scale in zip(rates, scales): 

948 if scale is None: 

949 scale = self.rate/float(rate) 

950 if scale != 1: 

951 self._down_sample(self.channels, scale) 

952 try: 

953 self.handle = simpleaudio.play_buffer(self.data, self.channels, 

954 2, int(self.rate)) 

955 success = True 

956 break 

957 except ValueError as e: 

958 if self.verbose > 0: 

959 print(f'invalid sampling rate of {rate}Hz') 

960 except simpleaudio._simpleaudio.SimpleaudioError as e: 

961 if self.verbose > 0: 

962 print('simpleaudio SimpleaudioError:', str(e)) 

963 if 'Error opening' in str(e): 

964 raise FileNotFoundError('No audio device found') 

965 except Exception as e: 

966 if self.verbose > 0: 

967 print('simpleaudio Exception:', str(e)) 

968 if not success: 

969 raise ValueError('No valid sampling rate found') 

970 elif blocking: 

971 self.handle.wait_done() 

972 

973 def _close_simpleaudio(self): 

974 """Close audio output using simpleaudio package.""" 

975 self._stop_simpleaudio() 

976 simpleaudio.stop_all() 

977 self._close() 

978 

979 

980 def open_soundcard(self, device_index=None): 

981 """Initialize audio output via soundcard package. 

982 

983 Parameters 

984 ---------- 

985 device_index: int or None 

986 Index of the playback device to be used. 

987 If None take the default device. 

988 

989 Raises 

990 ------ 

991 ImportError 

992 soundcard module is not available. 

993 FileNotFoundError 

994 Failed to open audio device. 

995 

996 Documentation 

997 ------------- 

998 https://github.com/bastibe/SoundCard 

999 """ 

1000 if not audio_modules['soundcard']: 

1001 raise ImportError 

1002 try: 

1003 if device_index is None: 

1004 self.handle = soundcard.default_speaker() 

1005 else: 

1006 self.handle = soundcard.all_speakers()[device_index] 

1007 except IndexError: 

1008 raise FileNotFoundError('No audio device found') 

1009 except Exception as e: 

1010 print('soundcard Exception:', type(e).__name__, str(e)) 

1011 if self.handle is None: 

1012 raise FileNotFoundError('No audio device found') 

1013 self._do_play = self._play_soundcard 

1014 self.close = self._close_soundcard 

1015 self.stop = self._stop_soundcard 

1016 self.active = self._active # not supported ? 

1017 self.lib = 'soundcard' 

1018 return self 

1019 

1020 def _stop_soundcard(self): 

1021 """Stop any ongoing activity of the soundcard package.""" 

1022 pass 

1023 

1024 def _play_soundcard(self, blocking=True): 

1025 """Play audio data using the soundcard package. 

1026 

1027 Parameters 

1028 ---------- 

1029 blocking: boolean 

1030 If False do not block. 

1031 Non-blocking playback not supported by soundcard. 

1032 Return immediately without playing sound. 

1033 

1034 Raises 

1035 ------ 

1036 ValueError 

1037 Invalid sampling rate (after some attemps of resampling). 

1038 """ 

1039 if not blocking: 

1040 warnings.warn('soundcard module does not support non-blocking playback') 

1041 return 

1042 rates = [self.rate, 44100, 48000, 22050] 

1043 scales = [1, None, None, None] 

1044 success = False 

1045 for rate, scale in zip(rates, scales): 

1046 if scale is None: 

1047 scale = self.rate/float(rate) 

1048 if scale != 1: 

1049 self._down_sample(self.channels, scale) 

1050 try: 

1051 self.handle.play(self.data, samplerate=int(self.rate)) 

1052 success = True 

1053 break 

1054 except RuntimeError as e: 

1055 if 'invalid sample spec' in str(e): 

1056 if self.verbose > 0: 

1057 print(f'invalid sampling rate of {rate}Hz') 

1058 else: 

1059 if self.verbose > 0: 

1060 print('soundcard error:', type(e).__name__, str(e)) 

1061 except Exception as e: 

1062 if self.verbose > 0: 

1063 print('soundcard error:', type(e).__name__, str(e)) 

1064 if not success: 

1065 raise ValueError('No valid sampling rate found') 

1066 

1067 def _close_soundcard(self): 

1068 """Close audio output using soundcard package.""" 

1069 self._stop_soundcard() 

1070 self._close() 

1071 

1072 

1073 def open_ossaudiodev(self, device_index=None): 

1074 """Initialize audio output via ossaudiodev module. 

1075 

1076 The OSS audio module is part of the python standard library. 

1077 

1078 Parameters 

1079 ---------- 

1080 device_index: int or None 

1081 Index of the playback device to be used. 

1082 If None take the default device. 

1083 There is only a single OSS audio device. 

1084 

1085 Raises 

1086 ------ 

1087 ImportError 

1088 ossaudiodev module is not available. 

1089 FileNotFoundError 

1090 Failed to open audio device. 

1091 

1092 Documentation 

1093 ------------- 

1094 https://docs.python.org/2/library/ossaudiodev.html 

1095 

1096 Installation 

1097 ------------ 

1098 The ossaudiodev module needs an oss `/dev/dsp` device file. 

1099 Enable an oss emulation via alsa by installing 

1100 ``` 

1101 sudo apt install -y osspd 

1102 ``` 

1103 """ 

1104 if not audio_modules['ossaudiodev']: 

1105 raise ImportError 

1106 self.handle = True 

1107 self.osshandle = None 

1108 self.run = False 

1109 self.play_thread = None 

1110 try: 

1111 handle = ossaudiodev.open('w') 

1112 handle.close() 

1113 except Exception as e: 

1114 if self.verbose > 0: 

1115 print(str(e)) 

1116 self._close() 

1117 raise FileNotFoundError('failed to initialize audio device') 

1118 self.close = self._close_ossaudiodev 

1119 self.stop = self._stop_ossaudiodev 

1120 self.active = self._active_ossaudiodev 

1121 self._do_play = self._play_ossaudiodev 

1122 self.lib = 'ossaudiodev' 

1123 return self 

1124 

1125 def _stop_ossaudiodev(self): 

1126 """Stop any ongoing activity of the ossaudiodev module.""" 

1127 if self.osshandle is not None: 

1128 self.run = False 

1129 self.osshandle.reset() 

1130 if self.play_thread is not None: 

1131 if self.play_thread.is_alive(): 

1132 self.play_thread.join() 

1133 self.play_thread = None 

1134 self.osshandle.close() 

1135 self.osshandle = None 

1136 

1137 def _active_ossaudiodev(self): 

1138 """Report whether playback is in progress. 

1139 

1140 Returns 

1141 ------- 

1142 active: bool 

1143 True if audio output is still active. 

1144 """ 

1145 return self.run 

1146 

1147 def _run_play_ossaudiodev(self): 

1148 """Play the data using the ossaudiodev module.""" 

1149 self.osshandle.writeall(self.data) 

1150 if self.run: 

1151 sleep(0.5) 

1152 self.osshandle.close() 

1153 self.osshandle = None 

1154 self.run = False 

1155 

1156 def _play_ossaudiodev(self, blocking=True): 

1157 """Play audio data using the ossaudiodev module. 

1158 

1159 Raises 

1160 ------ 

1161 ValueError 

1162 Invalid sampling rate (after some attemps of resampling). 

1163 

1164 Parameters 

1165 ---------- 

1166 blocking: boolean 

1167 If False do not block.  

1168 """ 

1169 self.osshandle = ossaudiodev.open('w') 

1170 self.osshandle.setfmt(ossaudiodev.AFMT_S16_LE) 

1171 # set and check channel count: 

1172 channels = self.osshandle.channels(self.channels) 

1173 # check sampling rate: 

1174 scale_fac = 1 

1175 scaled_rate = self.rate 

1176 max_rate = 48000.0 

1177 if self.rate > max_rate: 

1178 scale_fac = int(np.ceil(self.rate/max_rate)) 

1179 scaled_rate = int(self.rate//scale_fac) 

1180 rates = [self.rate, scaled_rate, 44100, 48000, 22050, 8000] 

1181 scales = [1, scale_fac, None, None, None, None] 

1182 success = False 

1183 for rate, scale in zip(rates, scales): 

1184 set_rate = self.osshandle.speed(int(rate)) 

1185 if abs(set_rate - rate) < 2: 

1186 if scale is None: 

1187 scale = self.rate/float(set_rate) 

1188 success = True 

1189 break 

1190 else: 

1191 if self.verbose > 0: 

1192 print(f'invalid sampling rate of {rate}Hz') 

1193 if not success: 

1194 raise ValueError('No valid sampling rate found') 

1195 if channels != self.channels or scale != 1: 

1196 self._down_sample(channels, scale) 

1197 if blocking: 

1198 self.run = True 

1199 self.osshandle.writeall(self.data) 

1200 sleep(0.5) 

1201 self.osshandle.close() 

1202 self.run = False 

1203 self.osshandle = None 

1204 else: 

1205 self.play_thread = Process(target=self._run_play_ossaudiodev) 

1206 self.run = True 

1207 self.play_thread.start() 

1208 

1209 def _close_ossaudiodev(self): 

1210 """Close audio output using ossaudiodev module.""" 

1211 self._stop_ossaudiodev() 

1212 self._close() 

1213 

1214 

1215 def open_winsound(self, device_index=None): 

1216 """Initialize audio output via winsound module. 

1217 

1218 The winsound module is part of the python standard library. 

1219 

1220 Parameters 

1221 ---------- 

1222 device_index: int or None 

1223 Index of the playback device to be used. 

1224 If None take the default device. 

1225 Device selection is not supported by the winsound module. 

1226 

1227 Raises 

1228 ------ 

1229 ImportError 

1230 winsound module is not available. 

1231 

1232 Documentation 

1233 ------------- 

1234 https://docs.python.org/3.6/library/winsound.html 

1235 https://mail.python.org/pipermail/tutor/2012-September/091529.html 

1236 """ 

1237 if not audio_modules['winsound'] or not audio_modules['wave']: 

1238 raise ImportError 

1239 self.handle = True 

1240 self._do_play = self._play_winsound 

1241 self.close = self._close_winsound 

1242 self.stop = self._stop_winsound 

1243 self.active = self._active # not supported 

1244 self.audio_file = '' 

1245 self.lib = 'winsound' 

1246 return self 

1247 

1248 def _stop_winsound(self): 

1249 """Stop any ongoing activity of the winsound module.""" 

1250 try: 

1251 winsound.PlaySound(None, winsound.SND_MEMORY) 

1252 except Exception as e: 

1253 pass 

1254 

1255 def _play_winsound(self, blocking=True): 

1256 """Play audio data using the winsound module. 

1257 

1258 Parameters 

1259 ---------- 

1260 blocking: boolean 

1261 If False do not block.  

1262 """ 

1263 # play file: 

1264 if blocking: 

1265 # write data as wav file to memory: 

1266 self.data_buffer = BytesIO() 

1267 w = wave.open(self.data_buffer, 'w') 

1268 w.setnchannels(self.channels) 

1269 w.setsampwidth(2) 

1270 w.setframerate(int(self.rate)) 

1271 w.setnframes(len(self.data)) 

1272 try: 

1273 w.writeframes(self.data.tobytes()) 

1274 except AttributeError: 

1275 w.writeframes(self.data.tostring()) 

1276 w.close() 

1277 try: 

1278 winsound.PlaySound(self.data_buffer.getvalue(), winsound.SND_MEMORY) 

1279 except Exception as e: 

1280 if self.verbose > 0: 

1281 print(str(e)) 

1282 return 

1283 else: 

1284 if self.verbose > 0: 

1285 print('Warning: asynchronous playback is limited to playing wav files by the winsound module. Install an alternative package as recommended by the audiomodules script. ') 

1286 # write data as wav file to file: 

1287 self.audio_file = 'audioio-async_playback.wav' 

1288 w = wave.open(self.audio_file, 'w') 

1289 w.setnchannels(self.channels) 

1290 w.setsampwidth(2) 

1291 w.setframerate(int(self.rate)) 

1292 w.setnframes(len(self.data)) 

1293 try: 

1294 w.writeframes(self.data.tobytes()) 

1295 except AttributeError: 

1296 w.writeframes(self.data.tostring()) 

1297 w.close() 

1298 try: 

1299 winsound.PlaySound(self.audio_file, winsound.SND_ASYNC) 

1300 except Exception as e: 

1301 if self.verbose > 0: 

1302 print(str(e)) 

1303 return 

1304 

1305 def _close_winsound(self): 

1306 """Close audio output using winsound module.""" 

1307 self._stop_winsound() 

1308 self.handle = None 

1309 if len(self.audio_file) > 0 and os.path.isfile(self.audio_file): 

1310 os.remove(self.audio_file) 

1311 self._close() 

1312 

1313 

1314 def open(self, device_index=None, library=None): 

1315 """Initialize the PlayAudio class with the best module available. 

1316 

1317 Parameters 

1318 ---------- 

1319 device_index: int or None 

1320 Index of the playback device to be used. 

1321 If None take the default device. 

1322 library: str or None 

1323 If specified, open a specific sound library. 

1324 """ 

1325 # list of implemented play functions: 

1326 audio_open = [ 

1327 ['sounddevice', self.open_sounddevice], 

1328 ['pyaudio', self.open_pyaudio], 

1329 ['simpleaudio', self.open_simpleaudio], 

1330 ['soundcard', self.open_soundcard], 

1331 ['ossaudiodev', self.open_ossaudiodev], 

1332 ['winsound', self.open_winsound] 

1333 ] 

1334 if platform[0:3] == "win": 

1335 sa = audio_open.pop(2) 

1336 audio_open.insert(0, sa) 

1337 # open audio device by trying various modules: 

1338 success = False 

1339 for lib, open_device in audio_open: 

1340 if library and library != lib: 

1341 continue 

1342 if not audio_modules[lib]: 

1343 if self.verbose > 0: 

1344 print(f'module {lib} not available') 

1345 continue 

1346 try: 

1347 open_device(device_index) 

1348 success = True 

1349 if self.verbose > 0: 

1350 print(f'successfully opened {lib} module for playing') 

1351 break 

1352 except Exception as e: 

1353 if self.verbose > 0: 

1354 print(f'failed to open {lib} module for playing:', 

1355 type(e).__name__, str(e)) 

1356 if not success: 

1357 warnings.warn('cannot open any device for audio output') 

1358 return self 

1359 

1360 

1361def play(data, rate, scale=None, blocking=True, device_index=None, verbose=0): 

1362 """Playback audio data. 

1363 

1364 Create a `PlayAudio` instance on the global variable `handle`. 

1365 

1366 Parameters 

1367 ---------- 

1368 data: array 

1369 The data to be played, either 1-D array for single channel output, 

1370 or 2-D array with first axis time and second axis channel. 

1371 Data values range between -1 and 1. 

1372 rate: float 

1373 The sampling rate in Hertz. 

1374 scale: float 

1375 Multiply data with scale before playing. 

1376 If `None` scale it to the maximum value, if 1.0 do not scale. 

1377 blocking: boolean 

1378 If False do not block.  

1379 device_index: int or None 

1380 Index of the playback device to be used, 

1381 if not already openend. 

1382 If None take the default device. 

1383 verbose: int 

1384 Verbosity level.  

1385 """ 

1386 global handle 

1387 if handle is None: 

1388 handle = PlayAudio(device_index, verbose) 

1389 handle.verbose = verbose 

1390 handle.play(data, rate, scale, blocking, device_index) 

1391 

1392 

1393def beep(duration=0.5, frequency=880.0, amplitude=0.5, rate=44100.0, 

1394 fadetime=0.05, blocking=True, device_index=None, verbose=0): 

1395 """Playback a tone. 

1396 

1397 Create a `PlayAudio` instance on the global variable `handle`. 

1398 

1399 Parameters 

1400 ---------- 

1401 duration: float 

1402 The duration of the tone in seconds. 

1403 frequency: float or string 

1404 If float the frequency of the tone in Hertz. 

1405 If string, a musical note like 'f#5'. 

1406 See `note2freq()` for details 

1407 amplitude: float 

1408 The ampliude (volume) of the tone from 0.0 to 1.0. 

1409 rate: float 

1410 The sampling rate in Hertz. 

1411 fadetime: float 

1412 Time for fading in and out in seconds. 

1413 blocking: boolean 

1414 If False do not block. 

1415 device_index: int or None 

1416 Index of the playback device to be used, 

1417 if not already openend. 

1418 If None take the default device. 

1419 verbose: int 

1420 Verbosity level.  

1421 """ 

1422 global handle 

1423 if handle is None: 

1424 handle = PlayAudio(device_index, verbose) 

1425 handle.verbose = verbose 

1426 handle.beep(duration, frequency, amplitude, rate, fadetime, blocking, 

1427 device_index) 

1428 

1429 

1430def close(): 

1431 """Close the global PlayAudio instance. 

1432 """ 

1433 global handle 

1434 if handle is not None: 

1435 handle.close() 

1436 handle = None 

1437 

1438 

1439def speaker_devices_pyaudio(): 

1440 """Query available output devices of the pyaudio module. 

1441 

1442 Returns 

1443 ------- 

1444 indices: list of int 

1445 Device indices. 

1446 devices: list of str 

1447 Devices corresponding to `indices`. 

1448 default_device: int 

1449 Index of default device. 

1450 -1 if no default output device is available. 

1451 """ 

1452 if not audio_modules['pyaudio']: 

1453 raise ImportError 

1454 oldstderr = os.dup(2) 

1455 os.close(2) 

1456 tmpfile = 'tmpfile.tmp' 

1457 os.open(tmpfile, os.O_WRONLY | os.O_CREAT) 

1458 pa = pyaudio.PyAudio() 

1459 os.close(2) 

1460 os.dup(oldstderr) 

1461 os.close(oldstderr) 

1462 os.remove(tmpfile) 

1463 indices = [] 

1464 devices = [] 

1465 for i in range(pa.get_device_count()): 

1466 info = pa.get_device_info_by_index(i) 

1467 if info['maxOutputChannels'] > 0: 

1468 host = sounddevice.query_hostapis(info['hostApi'])['name'] 

1469 device = f'{info["name"]}, {host} ({info["maxInputChannels"]} in, {info["maxOutputChannels"]} out)' 

1470 indices.append(info['index']) 

1471 devices.append(device) 

1472 try: 

1473 default_device = pa.get_default_output_device_info()['index'] 

1474 except OSError: 

1475 default_device = -1 

1476 return indices, devices, default_device 

1477 

1478def speaker_devices_sounddevice(): 

1479 """Query available output devices of the sounddevice module. 

1480 

1481 Returns 

1482 ------- 

1483 indices: list of int 

1484 Device indices. 

1485 devices: list of str 

1486 Devices corresponding to `indices`. 

1487 default_device: int 

1488 Index of default device. 

1489 """ 

1490 if not audio_modules['sounddevice']: 

1491 raise ImportError 

1492 indices = [] 

1493 devices = [] 

1494 infos = sounddevice.query_devices() 

1495 for info in infos: 

1496 if info['max_output_channels'] > 0: 

1497 host = sounddevice.query_hostapis(info['hostapi'])['name'] 

1498 device = f'{info["name"]}, {host} ({info["max_input_channels"]} in, {info["max_output_channels"]} out)' 

1499 indices.append(info['index']) 

1500 devices.append(device) 

1501 try: 

1502 info_out = sounddevice.query_devices(kind='output') 

1503 except sounddevice.PortAudioError: 

1504 return indices, devices, -1 

1505 try: 

1506 info_in = sounddevice.query_devices(kind='input') 

1507 if info_in['index'] != info_out['index'] and \ 

1508 info_in['max_output_channels'] > info_out['max_output_channels']: 

1509 info_out = info_in 

1510 except sounddevice.PortAudioError: 

1511 pass 

1512 return indices, devices, info_out['index'] 

1513 

1514def speaker_devices_soundcard(): 

1515 """Query available output devices of the soundcard module. 

1516 

1517 Returns 

1518 ------- 

1519 indices: list of int 

1520 Device indices. 

1521 devices: list of str 

1522 Devices corresponding to `indices`. 

1523 default_device: int 

1524 Index of default device. 

1525 """ 

1526 if not audio_modules['soundcard']: 

1527 raise ImportError 

1528 indices = [] 

1529 devices = [] 

1530 infos = soundcard.all_speakers() 

1531 def_speaker = str(soundcard.default_speaker()) 

1532 default_device = -1 

1533 for i, info in enumerate(infos): 

1534 if str(info) == def_speaker: 

1535 default_device = i 

1536 indices.append(i) 

1537 devices.append(str(info).lstrip('<').rstrip('>')) 

1538 return indices, devices, default_device 

1539 

1540def speaker_devices(library=None, verbose=0): 

1541 """Query available output devices. 

1542 

1543 Parameters 

1544 ---------- 

1545 library: str or None 

1546 If specified, use specific sound library. 

1547 verbose: int 

1548 Verbosity level. 

1549 

1550 Returns 

1551 ------- 

1552 indices: list of int 

1553 Device indices. 

1554 devices: list of str 

1555 Devices corresponding to `indices`. 

1556 default_device: int 

1557 Index of default device. 

1558 """ 

1559 # list of implemented list functions: 

1560 audio_devices = [ 

1561 ['sounddevice', speaker_devices_sounddevice], 

1562 ['pyaudio', speaker_devices_pyaudio], 

1563 ['simpleaudio', None], 

1564 ['soundcard', speaker_devices_soundcard], 

1565 ['ossaudiodev', None], 

1566 ['winsound', None] 

1567 ] 

1568 if platform[0:3] == "win": 

1569 sa = audio_open.pop(2) 

1570 audio_open.insert(0, sa) 

1571 # query audio devices by trying various modules: 

1572 success = False 

1573 for lib, devices in audio_devices: 

1574 if library and library != lib: 

1575 continue 

1576 if not audio_modules[lib]: 

1577 if verbose > 0: 

1578 print(f'module {lib} not available') 

1579 continue 

1580 if devices is None: 

1581 return [0], ['default output device'], 0 

1582 else: 

1583 return devices() 

1584 warnings.warn('no library for audio output available for devices') 

1585 return [], [], -1 

1586 

1587 

1588def print_speaker_devices(library=None): 

1589 """Print available output devices. 

1590 

1591 Parameters 

1592 ---------- 

1593 library: str or None 

1594 If specified, use specific sound library. 

1595 """ 

1596 indices, devices, default_device = speaker_devices() 

1597 for i, d in zip(indices, devices): 

1598 if i == default_device: 

1599 print(f'* {i:2d}: {d}') 

1600 else: 

1601 print(f' {i:2d}: {d}') 

1602 

1603 

1604def demo(device_index=None): 

1605 """ Demonstrate the playaudio module.""" 

1606 print('play mono beep 1') 

1607 audio = PlayAudio(device_index, verbose=2) 

1608 audio.beep(1.0, 440.0) 

1609 audio.close() 

1610 

1611 print('play mono beep 2') 

1612 with PlayAudio(device_index) as audio: 

1613 audio.beep(1.0, 'b4', 0.75, blocking=False) 

1614 print(' done') 

1615 sleep(0.3) 

1616 sleep(0.5) 

1617 

1618 print('play mono beep 3') 

1619 beep(1.0, 'c5', 0.25, blocking=False, device_index=device_index) 

1620 print(' done') 

1621 sleep(0.5) 

1622 

1623 print('play stereo beep') 

1624 duration = 1.0 

1625 rate = 44100.0 

1626 t = np.arange(0.0, duration, 1.0/rate) 

1627 data = np.zeros((len(t),2)) 

1628 data[:,0] = np.sin(2.0*np.pi*note2freq('a4')*t) 

1629 data[:,1] = 0.25*np.sin(2.0*np.pi*note2freq('e5')*t) 

1630 fade(data, rate, 0.1) 

1631 play(data, rate, verbose=2, device_index=device_index) 

1632 

1633 

1634def main(*args): 

1635 """Call demo with command line arguments. 

1636 

1637 Parameters 

1638 ---------- 

1639 args: list of strings 

1640 Command line arguments as provided by sys.argv[1:] 

1641 """ 

1642 help = False 

1643 mod = False 

1644 dev = False 

1645 ldev = False 

1646 device_index = None 

1647 for arg in args: 

1648 if mod: 

1649 if not select_module(arg): 

1650 print(f'module {arg} not installed. Exit!') 

1651 return 

1652 mod = False 

1653 elif dev: 

1654 device_index = int(arg) 

1655 dev = False 

1656 elif arg == '-h': 

1657 help = True 

1658 break 

1659 elif arg == '-l': 

1660 ldev = True 

1661 break 

1662 elif arg == '-m': 

1663 mod = True 

1664 elif arg == '-d': 

1665 dev = True 

1666 else: 

1667 break 

1668 

1669 if help: 

1670 print() 

1671 print('Usage:') 

1672 print(' python -m src.audioio.playaudio [-m <module>] [-l] [-d <device index>]') 

1673 print(' -m: audio module to be used') 

1674 print(' -l: list available audio output devices') 

1675 print(' -d: set audio output device to be used') 

1676 return 

1677 

1678 if ldev: 

1679 print_speaker_devices() 

1680 return 

1681 

1682 demo(device_index) 

1683 

1684 

1685if __name__ == "__main__": 

1686 import sys 

1687 main(*sys.argv[1:])