from logging import getLogger from queue import Full, Queue from threading import Thread from time import time from typing import Any, Callable, Optional from ._mix import Mix class AudioGenerator(Thread): """ The ``AudioGenerator`` class is a thread that generates synthetic raw audio waves and dispatches them to a queue that can be consumed by other players, streamers and converters. """ def __init__( self, *args, audio_queue: Queue, # Queue[NDArray[np.number]], mix: Mix, blocksize: int, sample_rate: int, queue_timeout: Optional[float] = None, should_stop: Callable[[], bool] = lambda: False, wait_running: Callable[[], Any] = lambda: None, on_stop: Callable[[], Any] = lambda: None, **kwargs, ): super().__init__(*args, **kwargs) self._audio_queue = audio_queue self._t_start: float = 0 self._blocksize: int = blocksize self._sample_rate: int = sample_rate self._blocktime = self._blocksize / self._sample_rate self._should_stop = should_stop self._queue_timeout = queue_timeout self._wait_running = wait_running self._on_stop = on_stop self.mix = mix self.logger = getLogger(__name__) def _next_t(self, t: float) -> float: """ Calculates the next starting time for the wave function. """ return ( min(t + self._blocktime, self._duration) if self._duration is not None else t + self._blocktime ) def should_stop(self) -> bool: """ Stops if the upstream dependencies have signalled to stop or if the duration is set and we have reached it. """ return self._should_stop() or ( self._duration is not None and time() - self._t_start >= self._duration ) @property def _duration(self) -> Optional[float]: """ Proxy to the mix object's duration. """ return self.mix.duration() def run(self): super().run() self._t_start = time() t = 0 while not self.should_stop(): self._wait_running() if self.should_stop(): break next_t = self._next_t(t) try: data = self.mix.get_wave( t_start=t, t_end=next_t, sample_rate=self._sample_rate ) except Exception as e: self.logger.warning('Could not generate the audio wave: %s', e) break try: self._audio_queue.put(data, timeout=self._queue_timeout) t = next_t except Full: self.logger.warning( 'The processing queue is full: either the audio consumer is stuck, ' 'or you may want to increase queue_size' ) self._on_stop()