From 80f83f2026aa915d1e827abaabc1706c270d88a3 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 9 Apr 2022 23:54:28 -0400 Subject: [PATCH] //WIP Sounddevice migration Initial --- BotResources.py | 4 +- NoiseGatev2.py | 127 ++++++++++++++++++++---------------------------- 2 files changed, 54 insertions(+), 77 deletions(-) diff --git a/BotResources.py b/BotResources.py index 4e551d0..d7f8efd 100644 --- a/BotResources.py +++ b/BotResources.py @@ -3,7 +3,7 @@ import logging import os from datetime import date from os.path import exists -from NoiseGatev2 import AudioStream +from NoiseGatev2 import AudioStream, query_devices PDB_ACCEPTABLE_HANDLERS = {'gqrx': { 'Modes': ['wfm', 'fm'] @@ -102,7 +102,7 @@ def write_config_file(**kwargs): def get_device_list(): - list_of_devices = AudioStream().list_devices() + list_of_devices = query_devices().items() LOGGER.debug(list_of_devices) return list_of_devices diff --git a/NoiseGatev2.py b/NoiseGatev2.py index 96a8db0..9602b35 100644 --- a/NoiseGatev2.py +++ b/NoiseGatev2.py @@ -2,109 +2,58 @@ import audioop import logging import math import time - +import sounddevice import pyaudio import discord import numpy +from pprint import pformat voice_connection = None LOGGER = logging.getLogger("Discord_Radio_Bot.NoiseGateV2") +DEFAULT = 48000 # noinspection PyUnresolvedReferences class AudioStream: - def __init__(self, _channels: int = 2, _sample_rate: int = 48000, _frames_per_buffer: int = 1024, - _input_device_index: int = None, _output_device_index: int = None, _input: bool = True, - _output: bool = True, _init_on_startup: bool = True): - self.paInstance_kwargs = { - 'format': pyaudio.paInt16, + def __init__(self, _channels: int = 2, _sample_rate: int = DEFAULT, _frames_per_buffer: int = 1024, + _device_index: int = None, _init_on_startup: bool = True): + self.sd_kwargs = { + 'dtype': 'int16', 'channels': _channels, - 'rate': _sample_rate, - 'input': _input, - 'output': _output, - 'frames_per_buffer': _frames_per_buffer + 'samplerate': _sample_rate, + 'blocksize': _frames_per_buffer } - if _input_device_index: - if _input: - self.paInstance_kwargs['input_device_index'] = _input_device_index - else: - LOGGER.warning(f"[AudioStream.__init__]:\tInput was not enabled." - f" Reinitialize with '_input=True'") + if _device_index: + self.sd_kwargs['device'] = _device_index - if _output_device_index: - if _output: - self.paInstance_kwargs['output_device_index'] = _output_device_index - else: - LOGGER.warning(f"[AudioStream.__init__]:\tOutput was not enabled." - f" Reinitialize with '_output=True'") + # Define and initialize stream object if we have been passed a device ID (pyaudio.open) + self.stream = None - if _init_on_startup: - # Init PyAudio instance - LOGGER.info("Creating PyAudio instance") - self.paInstance = pyaudio.PyAudio() - - # Define and initialize stream object if we have been passed a device ID (pyaudio.open) - self.stream = None - - if _output_device_index or _input_device_index: + if _device_index: if _init_on_startup: LOGGER.info("Init stream") self.init_stream() - def init_stream(self, _new_output_device_index: int = None, _new_input_device_index: int = None): + def init_stream(self, _new_device_index: int = None): # Check what device was asked to be changed (or set) - if _new_input_device_index: - if self.paInstance_kwargs['input']: - self.paInstance_kwargs['input_device_index'] = _new_input_device_index - else: - LOGGER.warning(f"[AudioStream.init_stream]:\tInput was not enabled when initialized." - f" Reinitialize with '_input=True'") - - if _new_output_device_index: - if self.paInstance_kwargs['output']: - self.paInstance_kwargs['output_device_index'] = _new_output_device_index - else: - LOGGER.warning(f"[AudioStream.init_stream]:\tOutput was not enabled when initialized." - f" Reinitialize with '_output=True'") + if _new_device_index: + self.sd_kwargs['device'] = _new_input_device_index self.close_if_open() # Open the stream - self.stream = self.paInstance.open(**self.paInstance_kwargs) + self.stream = sounddevice.RawStream(**self.paInstance_kwargs) def close_if_open(self): # Stop the stream if it is started if self.stream: - if self.stream.is_active(): - self.stream.stop_stream() + if self.stream.active: + self.stream.stop() self.stream.close() LOGGER.debug(f"[ReopenStream.close_if_open]:\t Stream was open; It was closed.") - def list_devices(self, _display_input_devices: bool = True, _display_output_devices: bool = True): - info = self.paInstance.get_host_api_info_by_index(0) - numdevices = info.get('deviceCount') - - devices = { - 'Input': {}, - 'Output': {} - } - for i in range(0, numdevices): - if (self.paInstance.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0: - input_device = self.paInstance.get_device_info_by_host_api_device_index(0, i).get('name') - devices['Input'][i] = input_device - if _display_input_devices: - LOGGER.debug("Input Device id ", i, " - ", input_device) - - if (self.paInstance.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')) > 0: - output_device = self.paInstance.get_device_info_by_host_api_device_index(0, i).get('name') - devices['Output'][i] = output_device - if _display_output_devices: - LOGGER.debug("Output Device id ", i, " - ", output_device) - - return devices - async def stop(self): await voice_connection.disconnect() self.close_if_open() @@ -126,7 +75,7 @@ class NoiseGate(AudioStream): global voice_connection # Start the audio stream LOGGER.debug(f"Starting stream") - self.stream.start_stream() + self.stream.start() # Start the stream to discord self.core() @@ -144,8 +93,8 @@ class NoiseGate(AudioStream): async def close(self): LOGGER.debug(f"Closing") await voice_connection.disconnect() - if self.stream.is_active: - self.stream.stop_stream() + if self.stream.active: + self.stream.stop() LOGGER.debug(f"Stopping stream") @@ -167,7 +116,10 @@ class NoiseGateStream(discord.AudioSource): buffer_decibel = 20 * math.log10(buffer_rms) if self.process_set_count % 10 == 0: - LOGGER.debug(f"{buffer_decibel} db") + if buffer_decibel >= self.stream.THRESHOLD: + LOGGER.debug(f"[Noisegate Open] {buffer_decibel} db") + else: + LOGGER.debug(f"[Noisegate Closed] {buffer_decibel} db") if buffer_decibel >= self.stream.THRESHOLD: self.NG_fadeout_count = self.NG_fadeout @@ -199,6 +151,31 @@ class NoiseGateStream(discord.AudioSource): datalist[i] = chunk.astype(numpy.int16) +class DeviceNotFoundError(Exception): + def __init__(self): + self.devices = sounddevice.query_devices() + self.host_apis = sounddevice.query_hostapis() + super().__init__("No Devices Found") + def __str__(self): + return ( + f"Devices \n" + f"{self.devices} \n " + f"Host APIs \n" + f"{pformat(self.host_apis)}" + ) + + +def query_devices(): + options = { + device.get("name"): index + for index, device in enumerate(sounddevice.query_devices()) + if (device.get("max_input_channels") > 0 and device.get("hostapi") == DEFAULT) + } + if not options: + raise DeviceNotFoundError() + return options + + if __name__ == '__main__': input_index = int(input("Input:\t"))