platypush/platypush/plugins/sound/_streams/_player/_synth/_generator.py

99 lines
2.9 KiB
Python

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()