diff --git a/.gitignore b/.gitignore index 8684325..a9c4648 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.idea/ /Releases/ +/Old/ /__pycache__/ */__pycache__/ /venv/ diff --git a/BotResources.py b/BotResources.py index 0d05b7e..b0d9ec6 100644 --- a/BotResources.py +++ b/BotResources.py @@ -2,6 +2,14 @@ import sound import configparser from os.path import exists +PDB_ACCEPTABLE_HANDLERS = {'gqrx': { + 'Modes': ['wfm', 'fm'] + }, + 'op25': { + 'Modes': ['d', 'p25'] + }} +PDB_KNOWN_BOT_IDS = {756327271597473863: "Greada", 915064996994633729: "Jorn", 943742040255115304: "Brent"} + def check_if_config_exists(): if exists('./config.ini'): @@ -167,9 +175,10 @@ def get_handler(): handler = None while not handler: handler = str(input(f"Please enter the name of the handler you would like to use:\t")) - if handler == "gqrx": + if handler in PDB_ACCEPTABLE_HANDLERS: return handler - elif handler == '': + elif handler == ['', "none"]: + handler = "None" return handler diff --git a/TODO.md b/TODO.md index 69bda45..679fd44 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ - [ ] Fix the bug where they *disconnect* after a period of time and must be manually moved out and back in to hear them - *May* have been fixed with the noise gate? - [ ] Fix bug that shows different index number for audio device selection on linux -- [ ] Add more proper help text with real examples for discord commands +- [x] Add more proper help text with real examples for discord commands - [ ] Add command line args with argparse for main bot - [ ] Add method for user to change audio device without redoing entire config file - [ ] Clean up code diff --git a/bot.py b/bot.py index bf1f148..24e9e25 100644 --- a/bot.py +++ b/bot.py @@ -1,10 +1,12 @@ +import asyncio import os import platform import discord + +import BotResources import sound import configparser from discord.ext import commands -from gqrxHandler import GQRXHandler # Init class for bot @@ -23,6 +25,7 @@ class Bot(commands.Bot): self.Default_Channel_ID = kwargs['Channel_ID'] self.Default_Mention_Group = kwargs['Mention_Group'] self.Handler = kwargs['Handler'] + self.Command_Prefix = kwargs['command_prefix'] # Init Variable for sound self.streamHandler = None @@ -43,9 +46,8 @@ class Bot(commands.Bot): self.system_os_type = None self.sdr_started = False - if self.Handler == "gqrx": - print("Starting gqrx handler") - self.GQRXHandler = GQRXHandler() + # Check the handler being used + self.check_handler() # Set linux or windows self.check_os_type() @@ -53,8 +55,8 @@ class Bot(commands.Bot): # Add discord commands to the bot self.add_commands() - # Check the ./modules folder for any modules (cog.py) - self.check_for_modules() + # Add discord events to the bot + self.add_events() # Start the bot def start_bot(self): @@ -65,7 +67,8 @@ class Bot(commands.Bot): # Test command to see if the bot is on (help command can also be used) @self.command(help="Use this to test if the bot is alive", brief="Sends a 'pong' in response") async def ping(ctx): - await ctx.send('pong') + if ctx.author.id != self.user.id: + await ctx.send('pong') # Command to join the bot the voice channel the user who called the command is in @self.command(help="Use this command to join the bot to your channel", @@ -88,9 +91,12 @@ class Bot(commands.Bot): voice_connection = await channel.connect() # Create an audio stream from selected device - self.streamHandler = sound.PCMStream(voice_connection) - # Ensure the selected device is available and start the audio stream - self.streamHandler.change_device(self.DEVICE_ID) + self.streamHandler = sound.PCMStream(self.DEVICE_ID) + # Start the audio stream + await self.streamHandler.play() + + # Play the stream + voice_connection.play(discord.PCMAudio(self.streamHandler)) # Start the SDR and begin playing to the audio stream self.start_sdr() @@ -110,38 +116,68 @@ class Bot(commands.Bot): @self.command(help="Use this command to have the bot leave your channel", brief="Leaves the current voice channel") - async def leave(ctx): + async def leave(ctx, member: discord.Member = None): + member = member or ctx.author.display_name if self.Bot_Connected: + print("Cleaning up") # Stop the sound handlers - self.streamHandler.clean_up() + await self.streamHandler.pause() + + print("Disconnecting") # Disconnect the client from the voice channel await ctx.voice_client.disconnect() + + print("Changing presence") # Change the presence to away and '@ me' await self.set_activity(False) + # Stop the SDR so it can cool off + print("Stopping SDR") self.stop_sdr() + + print("Unlocking the bot") # 'Unlock' the bot self.Bot_Connected = False - if self.Handler == 'gqrx': - @self.command(name='chfreq', help="Use this command to change the frequency the bot is listening to. " - "\nExample command: '@ chfreq wfm 104700000\n" - "Example command: '@ chfreq fm 154785000", + print("Sending Goodbye") + await ctx.send(f"Goodbye {str(member).capitalize()}.") + else: + await ctx.send(f"{str(member).capitalize()}, I'm not in a channel") + + # Add commands for GQRX and OP25 + if self.Handler in BotResources.PDB_ACCEPTABLE_HANDLERS.keys(): + # Command to display the current config + @self.command(name='displayprofile', + help="Use this command to display the current configuration of the bot.\n" + "Example command:\n" + "\t@ displayprofile", + breif="Display current bot config") + async def _displayprofile(ctx, member: discord.Member = None): + member = member or ctx.author.display_name + message = self.display_current_radio_config() + await ctx.send(f"Ok {str(member).capitalize()},\n{message}") + + # Command to change the current frequency and mode + @self.command(name='chfreq', help="Use this command to change the frequency the bot is listening to.\n" + "Example GQRX command:\n" + "\tTune to 104.7Mhz Wideband FM (Radio) - '@ chfreq wfm 104700000\n" + "\tTune to 155.505Mhz Narrowband FM (Radio) - '@ chfreq fm 155505000\n" + "Example OP25 command:\n" + "\tTune to 155.310Mhz, decode using P25 - '@ chfreq p25 155.310", brief="Changes radio frequency") async def chfreq(ctx, mode: str, freq: str, member: discord.Member = None): # Possible band-types that can be used - possible_modes = ['wfm', 'fm'] member = member or ctx.author.display_name # Check to make sure the frequency input matches the syntax needed if len(freq) >= 6: self.freq = freq # Check to make sure the selected mode is valid - if mode in possible_modes: + if mode in self.possible_modes: self.mode = mode - await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the mode to {str(self.mode).upper()} and frequency to" - f" {self.freq}") + await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the mode to " + f"{str(self.mode).upper()} and frequency to {self.freq}") # Reset the profile name since we have made a change to the freq self.profile_name = None @@ -151,22 +187,29 @@ class Bot(commands.Bot): self.start_sdr() await self.set_activity() else: - await ctx.send(f"{str(member).capitalize()}, {mode} is not valid. You may only enter 'fm' or 'wbfm'") + await ctx.send(f"{str(member).capitalize()}, {mode} is not valid." + f" You may only enter {self.possible_modes}") else: - await ctx.send(f"{str(member).capitalize()}, {freq} is not valid. please refer to the help page '@ help chfreq'") + await ctx.send(f"{str(member).capitalize()}, {freq} is not valid. " + f"Please refer to the help page '@ help chfreq'") - @self.command(name='chsquelch', help="Use this command to change the squelch for the frequency" - "the bot is listening to", - brief="Changes radio squelch") - async def chsquelch(ctx, squelch: float, member: discord.Member = None): - member = member or ctx.author.display_name + # GQRX Specific commands + if self.Handler == 'gqrx': + @self.command(name='chsquelch', help="Use this command to change the squelch for the frequency " + "the bot is listening to\n" + "Example Commands:\n" + "\tNo Squelch\t'@ chsquelch 150'\n" + "\tFully Squelched\t'@ chsquelch 0'", + brief="Changes radio squelch") + async def chsquelch(ctx, squelch: float, member: discord.Member = None): + member = member or ctx.author.display_name - self.squelch = squelch - await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the squelch to {self.squelch}") + self.squelch = squelch + await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the squelch to {self.squelch}") - # If the SDR is started, restart it with the updates - if self.sdr_started: - self.start_sdr() + # If the SDR is started, restart it with the updates + if self.sdr_started: + self.start_sdr() # Hidden admin commands @self.command(name='saveprofile', hidden=True) @@ -193,47 +236,117 @@ class Bot(commands.Bot): await ctx.send(f"Ok {str(member).capitalize()}, I reloaded {module}") else: await ctx.send(f"{str(member).capitalize()}, something went wrong. Please check the console") - + @self.command(name='startsdr', hidden=True) - async def _startsdr(ctx, member: discord.Member = None): + async def _startsdr(*args): self.start_sdr() @self.command(name='stopsdr', hidden=True) - async def _stopsdr(ctx, member: discord.Member = None): + async def _stopsdr(*args): self.stop_sdr() + # Add discord events to the bot + def add_events(self): + # Run any functions that need to have the bot running to complete + @self.event + async def on_ready(): + # Check the ./modules folder for any modules (cog.py) + await self.check_for_modules() + print("Bot started!") + + @self.event + async def on_message(message): + await self.check_and_reply_to_ping(message) + + # Check to see if other bots are online + async def check_other_bots_online(self): + print('Checking if other bots are online') + channel = self.get_channel(self.Default_Channel_ID) + print(f"Testing in: {channel}") + + bots_online = [] + + def verify_bot_msg(msg): + if msg.author.id in BotResources.PDB_KNOWN_BOT_IDS.keys(): + bots_online.append(BotResources.PDB_KNOWN_BOT_IDS[msg.author.id]) + + await self.wait_until_ready() + + # Send the ping command with the prefix the current bot is using + await channel.send(f"{self.Command_Prefix}ping") + + seconds_waited = 0 + while seconds_waited < 3: + try: + await self.wait_for("message", check=verify_bot_msg, timeout=1) + except asyncio.exceptions.TimeoutError: + seconds_waited += 1 + + print(f"Bots Online: {bots_online}") + + if len(bots_online) == 0: + return False + elif len(bots_online) > 0: + return True + + # Check the handler being used during init + def check_handler(self): + if self.Handler == "gqrx": + print("Starting GQRX handler") + from gqrxHandler import GQRXHandler + self.GQRXHandler = GQRXHandler() + self.possible_modes = BotResources.PDB_ACCEPTABLE_HANDLERS['gqrx']['Modes'] + + elif self.Handler == 'op25': + print("Starting OP25 handler") + from op25Handler import OP25Handler + self.OP25Handler = OP25Handler() + self.possible_modes = BotResources.PDB_ACCEPTABLE_HANDLERS['op25']['Modes'] + + # Load the proper OPUS library for the device being used def load_opus(self): # Check the system type and load the correct library - if self.system_os_type == 'Linux_32': - discord.opus.load_opus('./opus/libopus.so') - elif self.system_os_type == 'Linux_64': + + # Linux ARM AARCH64 running 32bit OS + if self.system_os_type == 'Linux_ARMv7l': + discord.opus.load_opus('./opus/libopus_armv7l.so') + # Linux ARM AARCH64 running 64bit OS + if self.system_os_type == 'Linux_AARCH64': discord.opus.load_opus('./opus/libopus_aarcch64.so') - elif self.system_os_type == 'Windows': - discord.opus.load_opus('./opus/libopus.dll') + # Windows 64bit + if self.system_os_type == 'Windows_x64': + discord.opus.load_opus('./opus/libopus_amd64.dll') - # Check to make sure the selected device is still available and has not changed it's index - def check_device(self): - for device, index in self.Devices_List: - if int(index) == self.DEVICE_ID and str(device) == self.DEVICE_NAME: - return True + # Check to make sure the selected device is still available and has not changed its index + def check_device(self, _override): + # Check to see if an override has been passed + if not _override: + for device, index in self.Devices_List: + if int(index) == self.DEVICE_ID and str(device) == self.DEVICE_NAME: + return True - for device, index in self.Devices_List: - if str(device) == self.DEVICE_NAME: - self.DEVICE_ID = int(index) - return True + for device, index in self.Devices_List: + if str(device) == self.DEVICE_NAME: + self.DEVICE_ID = int(index) + return True + else: + return False else: - return False + # If an override has been passed just reply true + return True # Search the ./modules folder for any modules to load - def check_for_modules(self): - # A valid module must be built as a 'cog', refer to the docs for more information - for folder_name in os.listdir("modules"): - if str(folder_name)[0] == '.': - continue - elif os.path.exists(os.path.join("modules", folder_name, "cog.py")): - print(f"Loaded extension: {folder_name}") - self.load_extension(f"modules.{folder_name}.cog") + async def check_for_modules(self): + # Check to see if other bots are online and don't load the modules if they are + if not await self.check_other_bots_online(): + # A valid module must be built as a 'cog', refer to the docs for more information + for folder_name in os.listdir("modules"): + if str(folder_name)[0] == '.': + continue + elif os.path.exists(os.path.join("modules", folder_name, "cog.py")): + print(f"Loaded extension: {folder_name}") + self.load_extension(f"modules.{folder_name}.cog") # Reload a selected module for changes def reload_modules(self, module): @@ -249,18 +362,19 @@ class Bot(commands.Bot): # Check and store the OS type of the system for later use def check_os_type(self): + processor = platform.machine() if os.name == 'nt': - self.system_os_type = 'Windows' + if processor == "AMD64": + self.system_os_type = 'Windows_x64' else: - processor = platform.architecture()[0] - if processor == "64bit": - self.system_os_type = 'Linux_64' - elif processor == "32bit": - self.system_os_type = 'Linux_32' + if processor == "aarch64": + self.system_os_type = 'Linux_AARCH64' + elif processor == "armv7l": + self.system_os_type = 'Linux_ARMv7l' # Check to see if there is only one frequency def start_sdr(self): - if self.Handler == 'gqrx': + if self.Handler in BotResources.PDB_ACCEPTABLE_HANDLERS.keys(): if type(self.freq) == str: # Single freq sent # Stop the SDR if it is running @@ -269,8 +383,13 @@ class Bot(commands.Bot): # Start the radio print(f"Starting freq: {self.freq}") - # Set the settings in GQRX - self.GQRXHandler.set_all_settings(self.mode, self.squelch, self.freq) + if self.Handler == 'gqrx': + # Set the settings in GQRX + self.GQRXHandler.set_all_settings(self.mode, self.squelch, self.freq) + + elif self.Handler == 'op25': + self.OP25Handler.set_op25_parameters(self.freq) + self.OP25Handler.start() # Set the started variable for later checks self.sdr_started = True @@ -279,13 +398,17 @@ class Bot(commands.Bot): def stop_sdr(self): if self.sdr_started: # Wait for the running processes to close + # Close the OP25 handler + if self.Handler == 'op25': + self.OP25Handler.close_op25() + # self.OP25Handler.join() # Need a way to 'close' GQRX self.sdr_started = False # Set the activity of the bot async def set_activity(self, connected=True): if connected: - if self.Handler == 'gqrx': + if self.Handler in BotResources.PDB_ACCEPTABLE_HANDLERS.keys(): if self.profile_name is None: await self.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"{self.freq[:-1]}" @@ -303,12 +426,14 @@ class Bot(commands.Bot): await self.change_presence(activity=discord.Game(name=f"@ me"), status=discord.Status.idle) # Save the current radio settings as a profile - async def save_radio_config(self, profile_name): + async def save_radio_config(self, profile_name: str): config = configparser.SafeConfigParser() if os.path.exists('./profiles.ini'): config.read('./profiles.ini') + profile_name = str(profile_name).upper() + if not config.has_section(str(profile_name)): config.add_section(str(profile_name)) @@ -330,11 +455,11 @@ class Bot(commands.Bot): config = configparser.ConfigParser() if os.path.exists('./profiles.ini'): config.read('./profiles.ini') - if config.has_section(str(profile_name)): - self.profile_name = profile_name - self.freq = config[str(profile_name)]['Frequency'] - self.mode = config[str(profile_name)]['Mode'] - self.squelch = float(config[str(profile_name)]['Squelch']) + if config.has_section(str(profile_name).upper()): + self.profile_name = str(profile_name).upper() + self.freq = config[self.profile_name]['Frequency'] + self.mode = config[self.profile_name]['Mode'] + self.squelch = float(config[self.profile_name]['Squelch']) if self.sdr_started: self.start_sdr() @@ -345,3 +470,24 @@ class Bot(commands.Bot): return False else: return False + + def display_current_radio_config(self): + message_body = "" + if self.profile_name: + message_body += f"Profile Name: {str(self.profile_name).upper()}\n" + message_body += f"Frequency: {self.freq}\n" \ + f"Mode: {str(self.mode).upper()}\n" + if self.squelch: + message_body += f"Squelch: {self.squelch}" + + return message_body + + # Check if message is a ping request and respond even if it is a bot + async def check_and_reply_to_ping(self, message): + if "ping" in message.content: + ctx = await self.get_context(message) + await self.invoke(ctx) + return True + else: + await self.process_commands(message) + return False diff --git a/main.py b/main.py index e1a67de..f401c0e 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,9 @@ from BotResources import check_if_config_exists, write_config_file, read_config_ # Greada #token = 'NzU2MzI3MjcxNTk3NDczODYz.X2QOqQ.LVLj2b-RXQzPmhNuBC1eGFMcYls' +# Brent +#token = OTQzNzQyMDQwMjU1MTE1MzA0.Yg3eRA.ZxEbRr55xahjfaUmPY8pmS-RHTY + #name = "VoiceMeeter Output" @@ -23,7 +26,7 @@ class BotDeviceNotFound(Exception): #os.execv(__file__, sys.argv) -def main(): +def main(**passed_config): print('Checking config file...') if not check_if_config_exists(): print("No config file exists, please enter this information now") @@ -31,27 +34,92 @@ def main(): config = read_config_file() + # Overwrite config options if they were passed + for sub in config: + # checking if key present in other dictionary + if sub in passed_config and passed_config[sub]: + config[sub] = passed_config[sub] + print('Starting Bot...') - discord_bot_client = bot.Bot(Token=config['Bot Token'], Device_ID=config['Device ID'], Device_Name=config['Device Name'], - Mention_Group=config['Mention Group'], Channel_ID=config['Channel ID'], Handler=config['Handler']) + discord_bot_client = bot.Bot(Token=config['Bot Token'], Device_ID=config['Device ID'], + Device_Name=config['Device Name'], Mention_Group=config['Mention Group'], + Channel_ID=config['Channel ID'], Handler=config['Handler']) print(f"Verifying audio device:\t{config['Device Name']}") - if not discord_bot_client.check_device(): + if not discord_bot_client.check_device(_override=bool("Device ID" in passed_config.keys())): raise BotDeviceNotFound(config['Device Name']) - print("Bot started!") - discord_bot_client.start_bot() +def cmd_arguments(): + parser = argparse.ArgumentParser(description='Discord Bot Gang - Premium Discord Bot\n' + 'The more you listen, the less you can see') + # Add the arguments + + # Arg to override bot token + parser.add_argument('-t', '--token', + metavar='Bot Token', + dest='Bot Token', + type=str, + help='Override saved bot token') + + # Arg to override the input device in the config + parser.add_argument('-i', '--input_device_id', + metavar='Device ID', + dest='Device ID', + type=int, + help='Override saved input device') + + # Arg to override mention group + parser.add_argument('-m', '--mention', + metavar='Mention Group', + dest='Mention Group', + type=str, + help='Override saved mention group') + + # Arg to override default channel to send messages + parser.add_argument('-c', '--channel', + metavar='Channel ID', + dest='Channel ID', + type=int, + help='Override saved sending channel') + + # Arg to override handler + parser.add_argument('-u', '--handler', + metavar='Handler', + dest='Handler', + type=str, + help='Override saved SDR handler') + + # Arg to save the overridden arguments + #parser.add_argument('-s', '--save', + # metavar='save_overrides', + # dest='save_overrides', + # type=str, + # help='Save the overridden arguments passed') + + # Retrieve the args + args = vars(parser.parse_args()) + + return args + if __name__ == '__main__': + args = cmd_arguments() + + if not all(args.values()): + print("Passed arguments:") + for arg in args: + if args[arg]: + print(f"\t{arg}:\t{args[arg]}") + try: print('Starting...') while True: try: - main() + main(**args) except BotDeviceNotFound: print("Restarting...") time.sleep(2) diff --git a/modules/LinkCop/cog.py b/modules/LinkCop/cog.py index b2ccdf9..7556d3c 100644 --- a/modules/LinkCop/cog.py +++ b/modules/LinkCop/cog.py @@ -2,6 +2,7 @@ import re import discord from discord.ext import commands import random +from BotResources import PDB_KNOWN_BOT_IDS regex_link = re.compile('(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)') @@ -40,11 +41,10 @@ class LinkCop(commands.Cog): 758792783020163084 # Bots ] - self.whitelisted_ID = [ - 756327271597473863, # Greada ID - 915064996994633729, # Jorn ID - 235148962103951360 # Carl Bot - ] + # Bring in the known bot IDs from PDB bots + self.whitelisted_ID = PDB_KNOWN_BOT_IDS + + self.whitelisted_ID['Carl Bot'] = 235148962103951360 self.add_events() @@ -71,7 +71,7 @@ class LinkCop(commands.Cog): print(f"Link Cop Error: '{err}'") print(f"Bot or other non-user that has not been whitelisted sent a message") - await self.bot.process_commands(ctx) + await self.bot.check_and_reply_to_ping(ctx) async def send_message(self, message): send_channel = self.bot.get_channel(id=self.reply_channel_id) diff --git a/op25Handler.py b/op25Handler.py new file mode 100644 index 0000000..b35f8dd --- /dev/null +++ b/op25Handler.py @@ -0,0 +1,56 @@ +import threading +import subprocess +import asyncio +import os + + +class OP25Handler: #(threading.Thread): + def __init__(self): + super().__init__() + self.OP25Dir: str = "/home/pi/op25/op25/gr-op25_repeater/apps" + self.OP25Proc = None + + self.Frequency = None + + def start(self) -> None: + self.open_op25() + + def set_op25_parameters(self, _frequency): + self.Frequency = _frequency + + def open_op25(self): + if self.OP25Proc is not None: + self.close_op25() + + print(f"Starting OP25") + + cwd = os.getcwd() + print(cwd) + + os.chdir(self.OP25Dir) + + print(os.getcwd()) + + self.OP25Proc = subprocess.Popen([f"./rx.py", "--args", "rtl", "-N", "LNA:49", "-s", + "200000", "-o", "25600", "-U", "-f", f"{self.Frequency}e6", "-X", "-2", + "-l" "http:0.0.0.0:8080"]) + + os.chdir(cwd) + + def close_op25(self): + print(f"Closing OP25") + try: + self.OP25Proc.kill() + + seconds_waited = 0 + while self.OP25Proc.poll() is None: + # Terminate the process every 5 seconds + if seconds_waited % 5 == 0: + print("Terminating OP25") + self.OP25Proc.terminate() + asyncio.sleep(1000) + print(f"Waited {seconds_waited} seconds") + seconds_waited += 1 + + except Exception as e: + print(e) diff --git a/opus/libopus.dll b/opus/libopus_amd64.dll similarity index 100% rename from opus/libopus.dll rename to opus/libopus_amd64.dll diff --git a/opus/libopus.so b/opus/libopus_armv7l.so similarity index 100% rename from opus/libopus.so rename to opus/libopus_armv7l.so diff --git a/sound.py b/sound.py index bf0297f..c33d97e 100644 --- a/sound.py +++ b/sound.py @@ -1,6 +1,8 @@ +import time + +import sounddevice import sounddevice as sd from pprint import pformat -from NoiseGate import NoiseGate DEFAULT = 0 sd.default.channels = 2 @@ -8,44 +10,42 @@ sd.default.dtype = "int16" sd.default.latency = "low" sd.default.samplerate = 48000 -noisegate_obj = NoiseGate() - class PCMStream: globals() - def __init__(self, voice_connection): - self.stream = None - self.voice_connection = voice_connection + def __init__(self, _device_id): + self.stream = sd.RawInputStream(device=_device_id) - def read(self, num_bytes): - # frame is 4 bytes - frames = int(num_bytes / 4) - data = self.stream.read(frames)[0] - - # convert to pcm format - return bytes(data) - - def change_device(self, device_ID): + def change_device(self, _device_id): self.clean_up() - self.stream = sd.RawInputStream(device=device_ID) - noisegate_obj.init_stream(device_ID, self.voice_connection, self) - - self.stream.start() - noisegate_obj.start() + self.stream = sd.RawInputStream(device=_device_id) + # Stops and destroys the current stream def clean_up(self): - global noisegate_obj - if self.stream is not None: - self.stream.stop() - self.stream.close() + if not self.stream.closed: + self.stream.stop(ignore_errors=True) + self.stream.close(ignore_errors=True) - if noisegate_obj.NG_Started.is_set(): - print("Closing the noisegate") - noisegate_obj.stop_NG.set() - noisegate_obj.join() - noisegate_obj = NoiseGate() - print("Started the noisegate") + # Stops the current running stream but does not destroy it + async def pause(self): + if self.stream.active: + self.stream.stop(ignore_errors=True) + + # Plays the stream + async def play(self): + if not self.stream.active: + self.stream.start() + + # call back read function for the stream + def read(self, num_bytes): + if self.stream.active: + # frame is 4 bytes + frames = int(num_bytes / 4) + data = self.stream.read(frames)[0] + + # convert to pcm format + return bytes(data) class DeviceNotFoundError(Exception):