2021-04-14 12:43:58 +00:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple
|
2020-11-03 21:24:45 +00:00
|
|
|
import sounddevice as sd
|
2021-04-05 21:13:53 +00:00
|
|
|
from helpers.os_environment import isLinux, isMacOS, isWindows
|
2021-11-02 22:48:52 +00:00
|
|
|
import os
|
|
|
|
|
|
|
|
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide"
|
2021-11-02 23:26:26 +00:00
|
|
|
if isLinux():
|
|
|
|
os.putenv('SDL_AUDIODRIVER', 'pulseaudio')
|
2021-11-02 22:48:52 +00:00
|
|
|
import pygame._sdl2 as sdl2
|
|
|
|
from pygame import mixer
|
2021-04-05 21:13:53 +00:00
|
|
|
import glob
|
2021-09-11 15:49:08 +00:00
|
|
|
|
2021-04-17 11:27:18 +00:00
|
|
|
if isWindows():
|
|
|
|
from serial.tools.list_ports_windows import comports
|
2020-11-03 21:24:45 +00:00
|
|
|
|
2021-04-14 12:43:58 +00:00
|
|
|
# TODO: https://wiki.libsdl.org/FAQUsingSDL maybe try setting some of these env variables for choosing different host APIs?
|
|
|
|
WINDOWS_APIS = ["Windows DirectSound"]
|
|
|
|
|
2021-04-08 19:53:51 +00:00
|
|
|
|
|
|
|
class DeviceManager:
|
2020-11-03 21:28:05 +00:00
|
|
|
@classmethod
|
2021-04-08 19:53:51 +00:00
|
|
|
def _isOutput(cls, device: Dict[str, Any]) -> bool:
|
2020-11-03 21:28:05 +00:00
|
|
|
return device["max_output_channels"] > 0
|
2020-11-03 21:24:45 +00:00
|
|
|
|
2021-04-14 12:43:58 +00:00
|
|
|
@classmethod
|
|
|
|
def _isHostAPI(cls, host_api) -> bool:
|
|
|
|
return host_api
|
|
|
|
|
2020-11-03 21:28:05 +00:00
|
|
|
@classmethod
|
2021-10-12 19:45:29 +00:00
|
|
|
def _getSDAudioDevices(cls):
|
2020-11-03 21:28:05 +00:00
|
|
|
# To update the list of devices
|
2021-10-12 19:45:29 +00:00
|
|
|
# Sadly this only works on Windows. Linux hangs, MacOS crashes.
|
|
|
|
if isWindows():
|
2020-11-03 21:28:05 +00:00
|
|
|
sd._terminate()
|
|
|
|
sd._initialize()
|
2020-12-19 14:57:37 +00:00
|
|
|
devices: sd.DeviceList = sd.query_devices()
|
2020-11-03 21:28:05 +00:00
|
|
|
return devices
|
2020-11-03 21:24:45 +00:00
|
|
|
|
2020-11-03 21:28:05 +00:00
|
|
|
@classmethod
|
2021-04-14 12:43:58 +00:00
|
|
|
def getAudioOutputs(cls) -> Tuple[List[Dict]]:
|
2021-10-12 19:45:29 +00:00
|
|
|
|
|
|
|
host_apis = list(sd.query_hostapis())
|
|
|
|
devices: sd.DeviceList = cls._getSDAudioDevices()
|
2021-04-14 12:43:58 +00:00
|
|
|
|
|
|
|
for host_api_id in range(len(host_apis)):
|
2021-10-12 19:45:29 +00:00
|
|
|
# Linux SDL uses PortAudio, which SoundDevice doesn't find. So mark all as unsable.
|
|
|
|
if (isWindows() and host_apis[host_api_id]["name"] not in WINDOWS_APIS) or (isLinux()):
|
2021-04-14 13:39:53 +00:00
|
|
|
host_apis[host_api_id]["usable"] = False
|
|
|
|
else:
|
|
|
|
host_apis[host_api_id]["usable"] = True
|
2021-04-14 12:43:58 +00:00
|
|
|
|
2021-09-11 15:49:08 +00:00
|
|
|
host_api_devices = (
|
|
|
|
device for device in devices if device["hostapi"] == host_api_id
|
|
|
|
)
|
2021-04-14 12:43:58 +00:00
|
|
|
|
|
|
|
outputs: List[Dict] = list(filter(cls._isOutput, host_api_devices))
|
|
|
|
outputs = sorted(outputs, key=lambda k: k["name"])
|
|
|
|
|
2021-04-14 13:39:53 +00:00
|
|
|
host_apis[host_api_id]["output_devices"] = outputs
|
2021-04-14 12:43:58 +00:00
|
|
|
|
|
|
|
return host_apis
|
2021-04-05 21:13:53 +00:00
|
|
|
|
2021-11-02 22:48:52 +00:00
|
|
|
@classmethod
|
|
|
|
def getAudioDevices(cls) -> List[str]:
|
|
|
|
mixer.init(44100, -16, 2, 1024)
|
|
|
|
is_capture = 0 # zero to request playback devices, non-zero to request recording devices
|
2024-03-23 11:11:30 +00:00
|
|
|
names = sdl2.audio.get_audio_device_names(is_capture)
|
2021-11-02 22:48:52 +00:00
|
|
|
mixer.quit()
|
|
|
|
return names
|
|
|
|
|
2021-04-05 21:13:53 +00:00
|
|
|
@classmethod
|
|
|
|
def getSerialPorts(cls) -> List[Optional[str]]:
|
2021-04-08 19:53:51 +00:00
|
|
|
"""Lists serial port names
|
2021-04-05 21:13:53 +00:00
|
|
|
|
2021-04-08 19:53:51 +00:00
|
|
|
:raises EnvironmentError:
|
|
|
|
On unsupported or unknown platforms
|
|
|
|
:returns:
|
|
|
|
A list of the serial ports available on the system
|
2021-04-05 21:13:53 +00:00
|
|
|
"""
|
|
|
|
if isWindows():
|
2021-04-17 11:27:18 +00:00
|
|
|
ports = [port.device for port in comports()]
|
2021-04-05 21:13:53 +00:00
|
|
|
elif isLinux():
|
|
|
|
# this excludes your current terminal "/dev/tty"
|
2021-04-08 19:53:51 +00:00
|
|
|
ports = glob.glob("/dev/tty[A-Za-z]*")
|
2021-04-05 21:13:53 +00:00
|
|
|
elif isMacOS():
|
2021-04-08 19:53:51 +00:00
|
|
|
ports = glob.glob("/dev/tty.*")
|
2021-04-05 21:13:53 +00:00
|
|
|
else:
|
2021-04-08 19:53:51 +00:00
|
|
|
raise EnvironmentError("Unsupported platform")
|
2021-04-05 21:13:53 +00:00
|
|
|
|
|
|
|
valid: List[str] = ports
|
|
|
|
|
|
|
|
result: List[Optional[str]] = []
|
|
|
|
|
|
|
|
if len(valid) > 0:
|
|
|
|
valid.sort()
|
|
|
|
|
2021-04-08 19:53:51 +00:00
|
|
|
result.append(None) # Add the None option
|
2021-04-05 21:13:53 +00:00
|
|
|
result.extend(valid)
|
|
|
|
|
|
|
|
return result
|