diff --git a/packages/passive_sound_localization/passive_sound_localization/localization.py b/packages/passive_sound_localization/passive_sound_localization/localization.py index 35837bf..cd65a07 100644 --- a/packages/passive_sound_localization/passive_sound_localization/localization.py +++ b/packages/passive_sound_localization/passive_sound_localization/localization.py @@ -1,6 +1,6 @@ from typing import List, Iterator, Optional, Tuple -from passive_sound_localization.models.configs.localization import LocalizationConfig -# from models.configs.localization import LocalizationConfig # Only needed to run with `realtime_audio.py` +# from passive_sound_localization.models.configs.localization import LocalizationConfig +from models.configs.localization import LocalizationConfig # Only needed to run with `realtime_audio.py` from dataclasses import dataclass import numpy as np import logging diff --git a/packages/passive_sound_localization/passive_sound_localization/models/configs/__init__.py b/packages/passive_sound_localization/passive_sound_localization/models/configs/__init__.py index 25a94a3..e24fd91 100644 --- a/packages/passive_sound_localization/passive_sound_localization/models/configs/__init__.py +++ b/packages/passive_sound_localization/passive_sound_localization/models/configs/__init__.py @@ -1,30 +1,30 @@ -from passive_sound_localization.models.configs.localization import ( - LocalizationConfig, -) -from passive_sound_localization.models.configs.logging import ( - LoggingConfig, -) -from passive_sound_localization.models.configs.feature_flags import ( - FeatureFlagsConfig, -) - -from passive_sound_localization.models.configs.openai_websocket import ( - OpenAIWebsocketConfig, -) - -from passive_sound_localization.models.configs.realtime_streamer import RealtimeAudioStreamerConfig - -# from models.configs.localization import ( +# from passive_sound_localization.models.configs.localization import ( # LocalizationConfig, -# ) # Need import paths like this to test audio streaming with `realtime_audio.py` -# from models.configs.logging import ( +# ) +# from passive_sound_localization.models.configs.logging import ( # LoggingConfig, -# ) # Need import paths like this to test audio streaming with `realtime_audio.py` -# from models.configs.feature_flags import ( +# ) +# from passive_sound_localization.models.configs.feature_flags import ( # FeatureFlagsConfig, -# ) # Need import paths like this to test audio streaming with `realtime_audio.py` -# from models.configs.realtime_streamer import RealtimeAudioStreamerConfig # Need import paths like this to test audio streaming with `realtime_audio.py` -# from models.configs.openai_websocket import OpenAIWebsocketConfig # Need import paths like this to test audio streaming with `realtime_audio.py` +# ) + +# from passive_sound_localization.models.configs.openai_websocket import ( +# OpenAIWebsocketConfig, +# ) + +# from passive_sound_localization.models.configs.realtime_streamer import RealtimeAudioStreamerConfig + +from models.configs.localization import ( + LocalizationConfig, +) # Need import paths like this to test audio streaming with `realtime_audio.py` +from models.configs.logging import ( + LoggingConfig, +) # Need import paths like this to test audio streaming with `realtime_audio.py` +from models.configs.feature_flags import ( + FeatureFlagsConfig, +) # Need import paths like this to test audio streaming with `realtime_audio.py` +from models.configs.realtime_streamer import RealtimeAudioStreamerConfig # Need import paths like this to test audio streaming with `realtime_audio.py` +from models.configs.openai_websocket import OpenAIWebsocketConfig # Need import paths like this to test audio streaming with `realtime_audio.py` from dataclasses import dataclass, field diff --git a/packages/passive_sound_localization/passive_sound_localization/models/configs/localization.py b/packages/passive_sound_localization/passive_sound_localization/models/configs/localization.py index d7964d2..e1b9cfe 100644 --- a/packages/passive_sound_localization/passive_sound_localization/models/configs/localization.py +++ b/packages/passive_sound_localization/passive_sound_localization/models/configs/localization.py @@ -5,7 +5,7 @@ @dataclass(frozen=True) class LocalizationConfig: speed_of_sound: float = 343.0 # Speed of sound in m/s - sample_rate: int = 24000 # Sample rate of the audio in Hz + sample_rate: int = 44100 # Sample rate of the audio in Hz fft_size: int = 1024 # Size of FFT to use mic_positions: List[List[float]] = field( diff --git a/packages/passive_sound_localization/passive_sound_localization/models/configs/realtime_streamer.py b/packages/passive_sound_localization/passive_sound_localization/models/configs/realtime_streamer.py index 70cc7a4..e10a1d1 100644 --- a/packages/passive_sound_localization/passive_sound_localization/models/configs/realtime_streamer.py +++ b/packages/passive_sound_localization/passive_sound_localization/models/configs/realtime_streamer.py @@ -3,9 +3,10 @@ @dataclass(frozen=True) class RealtimeAudioStreamerConfig: - sample_rate: int = 24000 + sample_rate: int = 44100 channels: int = 1 chunk: int = 1024 - device_indices: List[int] = field(default_factory=lambda: [2, 3, 4, 5]) # Lab configuration + # device_indices: List[int] = field(default_factory=lambda: [2, 3, 4, 5]) # Lab configuration + device_indices: List[int] = field(default_factory=lambda: [1, 2, 3, 4]) # Nico's laptop (configuration with soundcard) # device_indices: List[int] = field(default_factory=lambda: [4, 6, 8, 10]) # Nico's laptop (configuration 1) # device_indices: List[int] = field(default_factory=lambda: [2, 4, 6, 8]) # Nico's laptop (configuration 2) \ No newline at end of file diff --git a/packages/passive_sound_localization/passive_sound_localization/realtime_audio.py b/packages/passive_sound_localization/passive_sound_localization/realtime_audio.py index 7f96b26..44cc162 100644 --- a/packages/passive_sound_localization/passive_sound_localization/realtime_audio.py +++ b/packages/passive_sound_localization/passive_sound_localization/realtime_audio.py @@ -25,11 +25,13 @@ def send_audio_continuously(client, single_channel_generator): print("Threading...") for single_channel_audio in single_channel_generator: + if single_channel_audio is None: + continue client.send_audio(single_channel_audio) def receive_text_messages(client, logger): - logger.info("OpanAI: Listening to audio stream") + logger.info("OpenAI: Listening to audio stream") while True: try: command = client.receive_text_response() @@ -44,13 +46,16 @@ def receive_text_messages(client, logger): # commands.append(command) except Exception as e: print(f"Error receiving response: {e}") - break # Exit loop if server disconnects def realtime_localization(multi_channel_stream, localizer, logger): logger.info("Localization: Listening to audio stream") try: did_get = True for audio_data in multi_channel_stream: + # Skip calculating if there's any issues with the audio data + if audio_data is None: + continue + # Stream audio data and pass it to the localizer localization_stream = localizer.localize_stream( [audio_data[k] for k in audio_data.keys()] @@ -92,11 +97,8 @@ def command_executor(publisher, logger): def publish_results(localization_results): print(localization_results) - - def main(): - print("Hello world") audio_streamer_config = RealtimeAudioStreamerConfig() localizer_config = LocalizationConfig() websocket_config = OpenAIWebsocketConfig() diff --git a/packages/passive_sound_localization/passive_sound_localization/realtime_audio_streamer.py b/packages/passive_sound_localization/passive_sound_localization/realtime_audio_streamer.py index a18b066..33d973e 100644 --- a/packages/passive_sound_localization/passive_sound_localization/realtime_audio_streamer.py +++ b/packages/passive_sound_localization/passive_sound_localization/realtime_audio_streamer.py @@ -3,6 +3,9 @@ import soundcard as sc import numpy as np import threading +from pydub import AudioSegment +from io import BytesIO + # from passive_sound_localization.models.configs.realtime_streamer import ( # RealtimeAudioStreamerConfig, @@ -27,7 +30,7 @@ def __init__(self, config: RealtimeAudioStreamerConfig): print(self.device_indices) def __enter__(self): - microphones: List[sc._Microphone] = sc.all_microphones() + microphones = sc.all_microphones() self.streams = { device_index: np.zeros((self.chunk, self.channels), dtype=np.float32) for device_index in self.device_indices @@ -42,7 +45,7 @@ def __enter__(self): return self - def record_audio(self, microphones: List[sc._Microphone]): + def record_audio(self, microphones): while self.streaming: for device_index in self.device_indices: self.streams[device_index] = microphones[device_index].record( @@ -58,18 +61,22 @@ def __exit__(self, *args): def get_stream(self, device_index: int) -> Optional[bytes]: """Retrieve the audio stream for a specific device index.""" if device_index in self.device_indices: - return self.streams[device_index].tobytes() + return np.nan_to_num(self.streams[device_index]).tobytes() else: print(f"Device index {device_index} not found.") return None - def multi_channel_gen(self) -> Generator[Dict[int, bytes], None, None]: + def multi_channel_gen(self) -> Generator[Optional[Dict[int, bytes]], None, None]: try: while self.streaming: audio_arrays = [] for device_index in self.device_indices: audio_arrays.append(self.get_stream(device_index)) + # Return none if any audio is None or empty bytes + if any(audio == b"" or audio is None for audio in audio_arrays): + yield None + yield { device_index: audio for device_index, audio in zip(self.device_indices, audio_arrays) @@ -80,10 +87,31 @@ def multi_channel_gen(self) -> Generator[Dict[int, bytes], None, None]: def merge_streams(self, streams: List[np.ndarray]) -> np.ndarray: return np.sum(streams, axis=0) / len(streams) + + def resample_stream(self, stream: bytes, target_sample_rate: int = 24000, sample_width: int=2) -> bytes: + try: + audio = AudioSegment.from_file(BytesIO(stream)) - def single_channel_gen(self) -> Generator[bytes, None, None]: + # Resample to 24kHz mono pcm16 + return audio.set_frame_rate(target_sample_rate).set_channels(self.channels).set_sample_width(sample_width).raw_data + + except Exception as e: + print(f"Error in resample_stream: {e}") + return b"" + + + def single_channel_gen(self) -> Generator[Optional[bytes], None, None]: try: while self.streaming: - yield self.merge_streams(list(self.streams.values())).tobytes() + stream = self.get_stream(self.device_indices[0]) + if stream == b"" or stream is None: + yield None + + resampled_stream = self.resample_stream(stream) + + if resampled_stream != b"": + yield resampled_stream + else: + yield None except Exception as e: - print(f"Error in single_channel_gen: {e}") + print(f"Error in single_channel_gen: {e}") \ No newline at end of file diff --git a/packages/passive_sound_localization/passive_sound_localization/realtime_openai_websocket.py b/packages/passive_sound_localization/passive_sound_localization/realtime_openai_websocket.py index 829a670..aba6d21 100644 --- a/packages/passive_sound_localization/passive_sound_localization/realtime_openai_websocket.py +++ b/packages/passive_sound_localization/passive_sound_localization/realtime_openai_websocket.py @@ -5,8 +5,8 @@ import logging from typing import Optional -from passive_sound_localization.models.configs.openai_websocket import OpenAIWebsocketConfig -# from models.configs.openai_websocket import OpenAIWebsocketConfig # Only needed to run with `realtime_audio.py` +# from passive_sound_localization.models.configs.openai_websocket import OpenAIWebsocketConfig +from models.configs.openai_websocket import OpenAIWebsocketConfig # Only needed to run with `realtime_audio.py` logger = logging.getLogger(__name__) @@ -111,24 +111,24 @@ def send_audio(self, audio_chunk: bytes) -> None: "audio": audio_b64 })) - def receive_text_response(self, timeout:float=0.3) -> str: + def receive_text_response(self, timeout:float=5.0) -> str: try: # Tries to receive the next message (in a blocking manner) from the OpenAI websocket # If the message doesn't arrive in 300ms, then it raises a TimeoutError message = json.loads(self.ws.recv(timeout=timeout)) except TimeoutError: - return OpenAITimeoutError(timeout=timeout) + raise OpenAITimeoutError(timeout=timeout) # Print message just to see what we're receiving - print(message) + # print(message) # Checks to see any general errors if message["type"] == "error": - return OpenAIWebsocketError(error_code=message["error"]["code"], error_message=["error"]["message"]) + raise OpenAIWebsocketError(error_code=message["error"]["code"], error_message=["error"]["message"]) # Checks to see whether OpenAI is specifically rate limiting our responses if message["type"] == "response.done" and message["response"]["status_details"]["error"]["code"] == "rate_limit_exceeded": - return OpenAIRateLimitError() + raise OpenAIRateLimitError() # Checks to see if an actual text response was sent, and returns the text if message["type"] == "response.text.done": diff --git a/poetry.lock b/poetry.lock index 4a32e62..ac493fa 100644 --- a/poetry.lock +++ b/poetry.lock @@ -54,6 +54,85 @@ files = [ {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "colorama" version = "0.4.6" @@ -873,6 +952,17 @@ files = [ [package.extras] test = ["numpy"] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydantic" version = "2.9.2" @@ -997,6 +1087,17 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydub" +version = "0.25.1" +description = "Manipulate audio with an simple and easy high level interface" +optional = false +python-versions = "*" +files = [ + {file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"}, + {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"}, +] + [[package]] name = "pyparsing" version = "3.1.4" @@ -1213,6 +1314,21 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "soundcard" +version = "0.4.3" +description = "Play and record audio without resorting to CPython extensions" +optional = false +python-versions = ">=3.5" +files = [ + {file = "SoundCard-0.4.3-py3-none-any.whl", hash = "sha256:2af6f6b49c24dc8a997d8189206206f1bf1d48d7e8f313777293996809cfdfe3"}, + {file = "SoundCard-0.4.3.tar.gz", hash = "sha256:410835514ba10809803cb9887d4270f392b59eaf365915bb94516af3f8b1d037"}, +] + +[package.dependencies] +cffi = "*" +numpy = "*" + [[package]] name = "tomli" version = "2.0.2" @@ -1353,4 +1469,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "6691924d922cc52511f5280d1299181c806ce9a8fd9c88ac714b838bf051ede8" +content-hash = "fcf6dbd8a942be50f776f84d2308e64f295c6348b83236098a9f6a9f977bab34" diff --git a/pyproject.toml b/pyproject.toml index 4c553aa..64b3684 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,8 @@ hydra-core = "^1.3.2" openai = "^1.51.0" python-dotenv = "^1.0.1" websockets = "^13.1" +soundcard = "^0.4.3" +pydub = "^0.25.1" [tool.poetry.group.test.dependencies] pytest = "^8.3.3"