We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] Recorder Memory Usage
### Library Version

5.11.0

### Describe the Bug

Leaving and rejoining a voice channel with a bot recording causes enormous RAM usage and the bot recorder to stop functioning properly or the bot to crash completely (from out-of-memory). This is because the calculation for how many silence frames to insert is wrong (multiplies by sample rate twice).

### Steps to Reproduce

Use whatever you prefer and start monitoring your system memory usage. Join a voice channel and have a bot join it to record. Talk and then disconnect from the voice channel. Wait 3-5 seconds and rejoin the voice channel. Memory usage will spike into the 10-20 GB range or the bot will crash.

The exact outcome and how long to wait between rejoining the voice channel  really depends on the amount of memory available on your system. If you wait too long and don't have enough memory, you likely will see "Error while recording: " in the logs because Python was smart enough to not let you try to allocate that much memory. I have 64 GB of RAM, of which 32 GB is available to the bot, and it will spike up to 25 GB consistently or just crash. When it crashes, you will see "Killed" in the terminal and the exit code will be 137, which is an out-of-memory exit code.

### Expected Results

The memory will not have a substantial spike and the bot will not crash.

### Minimal Reproducible Code

_No response_

### Traceback

_No response_

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.
- [X] I have attempted to debug this myself, and I believe this issue is with the library

### Additional Information

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `interactions/api/voice/recorder.py`
Content:
```
1 import asyncio
2 import io
3 import logging
4 import os
5 import shutil
6 import struct
7 import threading
8 import time
9 from asyncio import AbstractEventLoop
10 from collections import defaultdict
11 from typing import TYPE_CHECKING
12 
13 import select
14 
15 from interactions.api.voice.audio import RawInputAudio
16 from interactions.api.voice.audio_writer import AudioWriter
17 from interactions.api.voice.encryption import Decryption
18 from interactions.api.voice.opus import Decoder
19 from interactions.client.const import logger_name, Missing
20 from interactions.client.utils.input_utils import unpack_helper
21 from interactions.models.discord.snowflake import Snowflake_Type, to_snowflake_list
22 
23 if TYPE_CHECKING:
24     from interactions.models.internal.active_voice_state import ActiveVoiceState
25 
26 __all__ = ("Recorder",)
27 
28 log = logging.getLogger(logger_name)
29 
30 
31 class Recorder(threading.Thread):
32     def __init__(self, v_state, loop, *, output_dir: str | None = None) -> None:
33         super().__init__()
34         self.daemon = True
35 
36         self.state: "ActiveVoiceState" = v_state
37         self.loop: AbstractEventLoop = loop
38         self.decrypter: Decryption = Decryption(self.state.ws.secret)
39         self._decoders: dict[str, Decoder] = defaultdict(Decoder)
40 
41         # check if output_dir is a folder not a file
42         if output_dir and not os.path.isdir(output_dir):
43             raise ValueError("output_dir must be a directory")
44 
45         self.output_dir = output_dir
46         self.audio: AudioWriter | None = None
47         self.encoding = "mp3"
48         self.recording = False
49         self.used = False
50 
51         self.start_time = 0
52         self.user_timestamps = {}
53         self.recording_whitelist: list[Snowflake_Type] = []
54 
55         if not shutil.which("ffmpeg"):
56             raise RuntimeError(
57                 "Unable to start recorder. FFmpeg was not found. Please add it to your project directory or PATH. (https://ffmpeg.org/)"
58             )
59 
60     async def __aenter__(self) -> "Recorder":
61         return self
62 
63     async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
64         await self.stop_recording()
65 
66     async def start_recording(self, *user_id: Snowflake_Type, output_dir: str | Missing = Missing) -> None:
67         """
68         Start recording audio from the current channel.
69 
70         Args:
71             *user_id: The user_id(s) to record, if not specified everyone will be recorded.
72             output_dir: The directory to save the audio to (overrides the constructor output_dir if specified)
73 
74         """
75         if self.used:
76             raise RuntimeError("Cannot reuse a recorder.")
77         self.used = True
78 
79         if user_id:
80             self.recording_whitelist = to_snowflake_list(unpack_helper(user_id))
81 
82         if output_dir is not Missing:
83             self.output_dir = output_dir
84 
85         self.recording = True
86         self.audio = AudioWriter(self, self.state.channel.id)
87         self.start()
88         self.start_time = time.monotonic()
89 
90     async def stop_recording(self) -> None:
91         """Stop recording audio from the current channel."""
92         self.recording = False
93 
94         def wait() -> None:
95             self.audio.cleanup()
96             self.audio.encode_audio(self.encoding)
97 
98         await asyncio.to_thread(wait)
99 
100     def decrypt(self, header: bytes, data: bytes) -> bytes:
101         """
102         An alias to call the decryption methods.
103 
104         Args:
105             header: The payload header
106             data: The payload data
107         Returns:
108               The decrypted payload
109 
110         """
111         # a shorter alias to call
112         return self.decrypter.decrypt(self.state.ws.selected_mode, header, data)
113 
114     def get_decoder(self, ssrc) -> Decoder:
115         return self._decoders[ssrc]
116 
117     def get_user(self, ssrc: str) -> Snowflake_Type:
118         """
119         Get the corresponding user from a ssrc.
120 
121         Args:
122             ssrc: The source to retrieve the user from
123         Returns:
124             A snowflake representing the user
125 
126         """
127         return self.state.ws.user_ssrc_map.get(ssrc)["user_id"]
128 
129     def get_ssrc(self, user_id: Snowflake_Type) -> str:
130         """
131         Get the corresponding ssrc from a user.
132 
133         Args:
134             user_id: The user to retrieve the ssrc from
135         Returns:
136             A string representing the ssrc
137 
138         """
139         return next((ssrc for ssrc, user in self.state.ws.user_ssrc_map.items() if user["user_id"] == user_id), None)
140 
141     def __enter__(self) -> "Recorder":
142         return self
143 
144     @property
145     def output(self) -> dict[int, io.BytesIO | str]:
146         """
147         The output of the recorder.
148 
149         Returns:
150             A dictionary of the user_id and the output file.
151             Output file can be a BytesIO or a string (if output_dir is specified)
152 
153         """
154         return self.audio.files if self.audio.finished.is_set() else {}
155 
156     @property
157     def elapsed_time(self) -> float:
158         return time.monotonic() - self.start_time
159 
160     def filter(self, *user_id: Snowflake_Type) -> None:
161         """
162         Filter the users that are being recorded.
163 
164         Args:
165             *user_id: The user_id(s) to record
166 
167         """
168         if not user_id:
169             self.recording_whitelist = []
170         self.recording_whitelist = to_snowflake_list(unpack_helper(user_id))
171 
172     def run(self) -> None:
173         """The recording loop itself."""
174         sock = self.state.ws.socket
175 
176         # purge any data that is already in the socket
177         readable, _, _ = select.select([sock], [], [], 0)
178         log.debug("Purging socket buffer")
179         while readable and sock.recv(4096):
180             readable, _, _ = select.select([sock], [], [], 0)
181         log.debug("Socket buffer purged, starting recording")
182 
183         with self.audio:
184             while self.recording:
185                 ready, _, err = select.select([sock], [], [sock], 0.01)
186                 if not ready:
187                     if err:
188                         log.error("Error while recording: %s", err)
189                     continue
190 
191                 data = sock.recv(4096)
192 
193                 if 200 <= data[1] <= 204:
194                     continue
195 
196                 try:
197                     raw_audio = RawInputAudio(self, data)
198                     self.process_data(raw_audio)
199                 except Exception as ex:
200                     log.error("Error while recording: %s", ex)
201 
202     def process_data(self, raw_audio: RawInputAudio) -> None:
203         """
204         Processes incoming audio data and writes it to the corresponding buffer.
205 
206         Args:
207             raw_audio: The raw audio that has been received
208 
209         """
210         if raw_audio.user_id is None:
211             return  # usually the first frame when a user rejoins
212 
213         if self.recording_whitelist and raw_audio.user_id not in self.recording_whitelist:
214             return
215 
216         decoder = self.get_decoder(raw_audio.ssrc)
217 
218         if raw_audio.ssrc not in self.user_timestamps:
219             if last_timestamp := self.audio.last_timestamps.get(raw_audio.user_id, None):
220                 diff = raw_audio.timestamp - last_timestamp
221                 silence = int(diff * decoder.sample_rate)
222                 log.debug(
223                     f"{self.state.channel.id}::{raw_audio.user_id} - User rejoined, adding {silence} silence frames ({diff} seconds)"
224                 )
225             else:
226                 silence = 0
227 
228             self.user_timestamps.update({raw_audio.ssrc: raw_audio.timestamp})
229         else:
230             silence = raw_audio.timestamp - self.user_timestamps[raw_audio.ssrc]
231             if silence < 0.1:
232                 silence = 0
233             self.user_timestamps[raw_audio.ssrc] = raw_audio.timestamp
234 
235         raw_audio.pcm = struct.pack("<h", 0) * int(silence * decoder.sample_rate) * 2 + raw_audio.decoded
236 
237         self.audio.write(raw_audio, raw_audio.user_id)
238
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/interactions/api/voice/recorder.py b/interactions/api/voice/recorder.py
--- a/interactions/api/voice/recorder.py
+++ b/interactions/api/voice/recorder.py
@@ -217,10 +217,10 @@
 
         if raw_audio.ssrc not in self.user_timestamps:
             if last_timestamp := self.audio.last_timestamps.get(raw_audio.user_id, None):
-                diff = raw_audio.timestamp - last_timestamp
-                silence = int(diff * decoder.sample_rate)
+                silence = raw_audio.timestamp - last_timestamp
+                frames = int(silence * decoder.sample_rate)
                 log.debug(
-                    f"{self.state.channel.id}::{raw_audio.user_id} - User rejoined, adding {silence} silence frames ({diff} seconds)"
+                    f"{self.state.channel.id}::{raw_audio.user_id} - User rejoined, adding {frames} silence frames ({silence} seconds)"
                 )
             else:
                 silence = 0

{"golden_diff": "diff --git a/interactions/api/voice/recorder.py b/interactions/api/voice/recorder.py\n--- a/interactions/api/voice/recorder.py\n+++ b/interactions/api/voice/recorder.py\n@@ -217,10 +217,10 @@\n \n         if raw_audio.ssrc not in self.user_timestamps:\n             if last_timestamp := self.audio.last_timestamps.get(raw_audio.user_id, None):\n-                diff = raw_audio.timestamp - last_timestamp\n-                silence = int(diff * decoder.sample_rate)\n+                silence = raw_audio.timestamp - last_timestamp\n+                frames = int(silence * decoder.sample_rate)\n                 log.debug(\n-                    f\"{self.state.channel.id}::{raw_audio.user_id} - User rejoined, adding {silence} silence frames ({diff} seconds)\"\n+                    f\"{self.state.channel.id}::{raw_audio.user_id} - User rejoined, adding {frames} silence frames ({silence} seconds)\"\n                 )\n             else:\n                 silence = 0\n", "issue": "[BUG] Recorder Memory Usage\n### Library Version\n\n5.11.0\n\n### Describe the Bug\n\nLeaving and rejoining a voice channel with a bot recording causes enormous RAM usage and the bot recorder to stop functioning properly or the bot to crash completely (from out-of-memory). This is because the calculation for how many silence frames to insert is wrong (multiplies by sample rate twice).\n\n### Steps to Reproduce\n\nUse whatever you prefer and start monitoring your system memory usage. Join a voice channel and have a bot join it to record. Talk and then disconnect from the voice channel. Wait 3-5 seconds and rejoin the voice channel. Memory usage will spike into the 10-20 GB range or the bot will crash.\r\n\r\nThe exact outcome and how long to wait between rejoining the voice channel  really depends on the amount of memory available on your system. If you wait too long and don't have enough memory, you likely will see \"Error while recording: \" in the logs because Python was smart enough to not let you try to allocate that much memory. I have 64 GB of RAM, of which 32 GB is available to the bot, and it will spike up to 25 GB consistently or just crash. When it crashes, you will see \"Killed\" in the terminal and the exit code will be 137, which is an out-of-memory exit code.\n\n### Expected Results\n\nThe memory will not have a substantial spike and the bot will not crash.\n\n### Minimal Reproducible Code\n\n_No response_\n\n### Traceback\n\n_No response_\n\n### Checklist\n\n- [X] I have searched the open issues for duplicates.\n- [X] I have shown the entire traceback, if possible.\n- [X] I have removed my token from display, if visible.\n- [X] I have attempted to debug this myself, and I believe this issue is with the library\n\n### Additional Information\n\n_No response_\n", "before_files": [{"content": "import asyncio\nimport io\nimport logging\nimport os\nimport shutil\nimport struct\nimport threading\nimport time\nfrom asyncio import AbstractEventLoop\nfrom collections import defaultdict\nfrom typing import TYPE_CHECKING\n\nimport select\n\nfrom interactions.api.voice.audio import RawInputAudio\nfrom interactions.api.voice.audio_writer import AudioWriter\nfrom interactions.api.voice.encryption import Decryption\nfrom interactions.api.voice.opus import Decoder\nfrom interactions.client.const import logger_name, Missing\nfrom interactions.client.utils.input_utils import unpack_helper\nfrom interactions.models.discord.snowflake import Snowflake_Type, to_snowflake_list\n\nif TYPE_CHECKING:\n    from interactions.models.internal.active_voice_state import ActiveVoiceState\n\n__all__ = (\"Recorder\",)\n\nlog = logging.getLogger(logger_name)\n\n\nclass Recorder(threading.Thread):\n    def __init__(self, v_state, loop, *, output_dir: str | None = None) -> None:\n        super().__init__()\n        self.daemon = True\n\n        self.state: \"ActiveVoiceState\" = v_state\n        self.loop: AbstractEventLoop = loop\n        self.decrypter: Decryption = Decryption(self.state.ws.secret)\n        self._decoders: dict[str, Decoder] = defaultdict(Decoder)\n\n        # check if output_dir is a folder not a file\n        if output_dir and not os.path.isdir(output_dir):\n            raise ValueError(\"output_dir must be a directory\")\n\n        self.output_dir = output_dir\n        self.audio: AudioWriter | None = None\n        self.encoding = \"mp3\"\n        self.recording = False\n        self.used = False\n\n        self.start_time = 0\n        self.user_timestamps = {}\n        self.recording_whitelist: list[Snowflake_Type] = []\n\n        if not shutil.which(\"ffmpeg\"):\n            raise RuntimeError(\n                \"Unable to start recorder. FFmpeg was not found. Please add it to your project directory or PATH. (https://ffmpeg.org/)\"\n            )\n\n    async def __aenter__(self) -> \"Recorder\":\n        return self\n\n    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:\n        await self.stop_recording()\n\n    async def start_recording(self, *user_id: Snowflake_Type, output_dir: str | Missing = Missing) -> None:\n        \"\"\"\n        Start recording audio from the current channel.\n\n        Args:\n            *user_id: The user_id(s) to record, if not specified everyone will be recorded.\n            output_dir: The directory to save the audio to (overrides the constructor output_dir if specified)\n\n        \"\"\"\n        if self.used:\n            raise RuntimeError(\"Cannot reuse a recorder.\")\n        self.used = True\n\n        if user_id:\n            self.recording_whitelist = to_snowflake_list(unpack_helper(user_id))\n\n        if output_dir is not Missing:\n            self.output_dir = output_dir\n\n        self.recording = True\n        self.audio = AudioWriter(self, self.state.channel.id)\n        self.start()\n        self.start_time = time.monotonic()\n\n    async def stop_recording(self) -> None:\n        \"\"\"Stop recording audio from the current channel.\"\"\"\n        self.recording = False\n\n        def wait() -> None:\n            self.audio.cleanup()\n            self.audio.encode_audio(self.encoding)\n\n        await asyncio.to_thread(wait)\n\n    def decrypt(self, header: bytes, data: bytes) -> bytes:\n        \"\"\"\n        An alias to call the decryption methods.\n\n        Args:\n            header: The payload header\n            data: The payload data\n        Returns:\n              The decrypted payload\n\n        \"\"\"\n        # a shorter alias to call\n        return self.decrypter.decrypt(self.state.ws.selected_mode, header, data)\n\n    def get_decoder(self, ssrc) -> Decoder:\n        return self._decoders[ssrc]\n\n    def get_user(self, ssrc: str) -> Snowflake_Type:\n        \"\"\"\n        Get the corresponding user from a ssrc.\n\n        Args:\n            ssrc: The source to retrieve the user from\n        Returns:\n            A snowflake representing the user\n\n        \"\"\"\n        return self.state.ws.user_ssrc_map.get(ssrc)[\"user_id\"]\n\n    def get_ssrc(self, user_id: Snowflake_Type) -> str:\n        \"\"\"\n        Get the corresponding ssrc from a user.\n\n        Args:\n            user_id: The user to retrieve the ssrc from\n        Returns:\n            A string representing the ssrc\n\n        \"\"\"\n        return next((ssrc for ssrc, user in self.state.ws.user_ssrc_map.items() if user[\"user_id\"] == user_id), None)\n\n    def __enter__(self) -> \"Recorder\":\n        return self\n\n    @property\n    def output(self) -> dict[int, io.BytesIO | str]:\n        \"\"\"\n        The output of the recorder.\n\n        Returns:\n            A dictionary of the user_id and the output file.\n            Output file can be a BytesIO or a string (if output_dir is specified)\n\n        \"\"\"\n        return self.audio.files if self.audio.finished.is_set() else {}\n\n    @property\n    def elapsed_time(self) -> float:\n        return time.monotonic() - self.start_time\n\n    def filter(self, *user_id: Snowflake_Type) -> None:\n        \"\"\"\n        Filter the users that are being recorded.\n\n        Args:\n            *user_id: The user_id(s) to record\n\n        \"\"\"\n        if not user_id:\n            self.recording_whitelist = []\n        self.recording_whitelist = to_snowflake_list(unpack_helper(user_id))\n\n    def run(self) -> None:\n        \"\"\"The recording loop itself.\"\"\"\n        sock = self.state.ws.socket\n\n        # purge any data that is already in the socket\n        readable, _, _ = select.select([sock], [], [], 0)\n        log.debug(\"Purging socket buffer\")\n        while readable and sock.recv(4096):\n            readable, _, _ = select.select([sock], [], [], 0)\n        log.debug(\"Socket buffer purged, starting recording\")\n\n        with self.audio:\n            while self.recording:\n                ready, _, err = select.select([sock], [], [sock], 0.01)\n                if not ready:\n                    if err:\n                        log.error(\"Error while recording: %s\", err)\n                    continue\n\n                data = sock.recv(4096)\n\n                if 200 <= data[1] <= 204:\n                    continue\n\n                try:\n                    raw_audio = RawInputAudio(self, data)\n                    self.process_data(raw_audio)\n                except Exception as ex:\n                    log.error(\"Error while recording: %s\", ex)\n\n    def process_data(self, raw_audio: RawInputAudio) -> None:\n        \"\"\"\n        Processes incoming audio data and writes it to the corresponding buffer.\n\n        Args:\n            raw_audio: The raw audio that has been received\n\n        \"\"\"\n        if raw_audio.user_id is None:\n            return  # usually the first frame when a user rejoins\n\n        if self.recording_whitelist and raw_audio.user_id not in self.recording_whitelist:\n            return\n\n        decoder = self.get_decoder(raw_audio.ssrc)\n\n        if raw_audio.ssrc not in self.user_timestamps:\n            if last_timestamp := self.audio.last_timestamps.get(raw_audio.user_id, None):\n                diff = raw_audio.timestamp - last_timestamp\n                silence = int(diff * decoder.sample_rate)\n                log.debug(\n                    f\"{self.state.channel.id}::{raw_audio.user_id} - User rejoined, adding {silence} silence frames ({diff} seconds)\"\n                )\n            else:\n                silence = 0\n\n            self.user_timestamps.update({raw_audio.ssrc: raw_audio.timestamp})\n        else:\n            silence = raw_audio.timestamp - self.user_timestamps[raw_audio.ssrc]\n            if silence < 0.1:\n                silence = 0\n            self.user_timestamps[raw_audio.ssrc] = raw_audio.timestamp\n\n        raw_audio.pcm = struct.pack(\"<h\", 0) * int(silence * decoder.sample_rate) * 2 + raw_audio.decoded\n\n        self.audio.write(raw_audio, raw_audio.user_id)\n", "path": "interactions/api/voice/recorder.py"}], "after_files": [{"content": "import asyncio\nimport io\nimport logging\nimport os\nimport shutil\nimport struct\nimport threading\nimport time\nfrom asyncio import AbstractEventLoop\nfrom collections import defaultdict\nfrom typing import TYPE_CHECKING\n\nimport select\n\nfrom interactions.api.voice.audio import RawInputAudio\nfrom interactions.api.voice.audio_writer import AudioWriter\nfrom interactions.api.voice.encryption import Decryption\nfrom interactions.api.voice.opus import Decoder\nfrom interactions.client.const import logger_name, Missing\nfrom interactions.client.utils.input_utils import unpack_helper\nfrom interactions.models.discord.snowflake import Snowflake_Type, to_snowflake_list\n\nif TYPE_CHECKING:\n    from interactions.models.internal.active_voice_state import ActiveVoiceState\n\n__all__ = (\"Recorder\",)\n\nlog = logging.getLogger(logger_name)\n\n\nclass Recorder(threading.Thread):\n    def __init__(self, v_state, loop, *, output_dir: str | None = None) -> None:\n        super().__init__()\n        self.daemon = True\n\n        self.state: \"ActiveVoiceState\" = v_state\n        self.loop: AbstractEventLoop = loop\n        self.decrypter: Decryption = Decryption(self.state.ws.secret)\n        self._decoders: dict[str, Decoder] = defaultdict(Decoder)\n\n        # check if output_dir is a folder not a file\n        if output_dir and not os.path.isdir(output_dir):\n            raise ValueError(\"output_dir must be a directory\")\n\n        self.output_dir = output_dir\n        self.audio: AudioWriter | None = None\n        self.encoding = \"mp3\"\n        self.recording = False\n        self.used = False\n\n        self.start_time = 0\n        self.user_timestamps = {}\n        self.recording_whitelist: list[Snowflake_Type] = []\n\n        if not shutil.which(\"ffmpeg\"):\n            raise RuntimeError(\n                \"Unable to start recorder. FFmpeg was not found. Please add it to your project directory or PATH. (https://ffmpeg.org/)\"\n            )\n\n    async def __aenter__(self) -> \"Recorder\":\n        return self\n\n    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:\n        await self.stop_recording()\n\n    async def start_recording(self, *user_id: Snowflake_Type, output_dir: str | Missing = Missing) -> None:\n        \"\"\"\n        Start recording audio from the current channel.\n\n        Args:\n            *user_id: The user_id(s) to record, if not specified everyone will be recorded.\n            output_dir: The directory to save the audio to (overrides the constructor output_dir if specified)\n\n        \"\"\"\n        if self.used:\n            raise RuntimeError(\"Cannot reuse a recorder.\")\n        self.used = True\n\n        if user_id:\n            self.recording_whitelist = to_snowflake_list(unpack_helper(user_id))\n\n        if output_dir is not Missing:\n            self.output_dir = output_dir\n\n        self.recording = True\n        self.audio = AudioWriter(self, self.state.channel.id)\n        self.start()\n        self.start_time = time.monotonic()\n\n    async def stop_recording(self) -> None:\n        \"\"\"Stop recording audio from the current channel.\"\"\"\n        self.recording = False\n\n        def wait() -> None:\n            self.audio.cleanup()\n            self.audio.encode_audio(self.encoding)\n\n        await asyncio.to_thread(wait)\n\n    def decrypt(self, header: bytes, data: bytes) -> bytes:\n        \"\"\"\n        An alias to call the decryption methods.\n\n        Args:\n            header: The payload header\n            data: The payload data\n        Returns:\n              The decrypted payload\n\n        \"\"\"\n        # a shorter alias to call\n        return self.decrypter.decrypt(self.state.ws.selected_mode, header, data)\n\n    def get_decoder(self, ssrc) -> Decoder:\n        return self._decoders[ssrc]\n\n    def get_user(self, ssrc: str) -> Snowflake_Type:\n        \"\"\"\n        Get the corresponding user from a ssrc.\n\n        Args:\n            ssrc: The source to retrieve the user from\n        Returns:\n            A snowflake representing the user\n\n        \"\"\"\n        return self.state.ws.user_ssrc_map.get(ssrc)[\"user_id\"]\n\n    def get_ssrc(self, user_id: Snowflake_Type) -> str:\n        \"\"\"\n        Get the corresponding ssrc from a user.\n\n        Args:\n            user_id: The user to retrieve the ssrc from\n        Returns:\n            A string representing the ssrc\n\n        \"\"\"\n        return next((ssrc for ssrc, user in self.state.ws.user_ssrc_map.items() if user[\"user_id\"] == user_id), None)\n\n    def __enter__(self) -> \"Recorder\":\n        return self\n\n    @property\n    def output(self) -> dict[int, io.BytesIO | str]:\n        \"\"\"\n        The output of the recorder.\n\n        Returns:\n            A dictionary of the user_id and the output file.\n            Output file can be a BytesIO or a string (if output_dir is specified)\n\n        \"\"\"\n        return self.audio.files if self.audio.finished.is_set() else {}\n\n    @property\n    def elapsed_time(self) -> float:\n        return time.monotonic() - self.start_time\n\n    def filter(self, *user_id: Snowflake_Type) -> None:\n        \"\"\"\n        Filter the users that are being recorded.\n\n        Args:\n            *user_id: The user_id(s) to record\n\n        \"\"\"\n        if not user_id:\n            self.recording_whitelist = []\n        self.recording_whitelist = to_snowflake_list(unpack_helper(user_id))\n\n    def run(self) -> None:\n        \"\"\"The recording loop itself.\"\"\"\n        sock = self.state.ws.socket\n\n        # purge any data that is already in the socket\n        readable, _, _ = select.select([sock], [], [], 0)\n        log.debug(\"Purging socket buffer\")\n        while readable and sock.recv(4096):\n            readable, _, _ = select.select([sock], [], [], 0)\n        log.debug(\"Socket buffer purged, starting recording\")\n\n        with self.audio:\n            while self.recording:\n                ready, _, err = select.select([sock], [], [sock], 0.01)\n                if not ready:\n                    if err:\n                        log.error(\"Error while recording: %s\", err)\n                    continue\n\n                data = sock.recv(4096)\n\n                if 200 <= data[1] <= 204:\n                    continue\n\n                try:\n                    raw_audio = RawInputAudio(self, data)\n                    self.process_data(raw_audio)\n                except Exception as ex:\n                    log.error(\"Error while recording: %s\", ex)\n\n    def process_data(self, raw_audio: RawInputAudio) -> None:\n        \"\"\"\n        Processes incoming audio data and writes it to the corresponding buffer.\n\n        Args:\n            raw_audio: The raw audio that has been received\n\n        \"\"\"\n        if raw_audio.user_id is None:\n            return  # usually the first frame when a user rejoins\n\n        if self.recording_whitelist and raw_audio.user_id not in self.recording_whitelist:\n            return\n\n        decoder = self.get_decoder(raw_audio.ssrc)\n\n        if raw_audio.ssrc not in self.user_timestamps:\n            if last_timestamp := self.audio.last_timestamps.get(raw_audio.user_id, None):\n                silence = raw_audio.timestamp - last_timestamp\n                frames = int(silence * decoder.sample_rate)\n                log.debug(\n                    f\"{self.state.channel.id}::{raw_audio.user_id} - User rejoined, adding {frames} silence frames ({silence} seconds)\"\n                )\n            else:\n                silence = 0\n\n            self.user_timestamps.update({raw_audio.ssrc: raw_audio.timestamp})\n        else:\n            silence = raw_audio.timestamp - self.user_timestamps[raw_audio.ssrc]\n            if silence < 0.1:\n                silence = 0\n            self.user_timestamps[raw_audio.ssrc] = raw_audio.timestamp\n\n        raw_audio.pcm = struct.pack(\"<h\", 0) * int(silence * decoder.sample_rate) * 2 + raw_audio.decoded\n\n        self.audio.write(raw_audio, raw_audio.user_id)\n", "path": "interactions/api/voice/recorder.py"}]}