diff --git a/turntable/application.py b/turntable/application.py index f4b49f8..43a9bdb 100644 --- a/turntable/application.py +++ b/turntable/application.py @@ -9,7 +9,7 @@ from typing import Any, Dict, Iterator from dejavu import Dejavu # type: ignore -from turntable.audio import Listener +from turntable.audio import Listener, Player from turntable.icecast import Icecast from turntable.models import PCM from turntable.turntable import ( @@ -38,11 +38,12 @@ def run() -> "Iterator[Queue[Event]]": pcm_in: "Queue[PCM]" = Queue() + pcm_out: "Queue[PCM]" = Queue() events: "Queue[Event]" = Queue() audio_config = config.get("audio", dict()) listener = Listener( - pcm_in, + [pcm_in, pcm_out], events, audio_config.get("device", "default"), framerate=audio_config.get("framerate", 44100), @@ -50,9 +51,17 @@ def run() -> "Iterator[Queue[Event]]": period_size=audio_config.get("period_size", 4096), ) + player = Player( + pcm_out, + audio_config.get("output_device", "null"), + framerate=audio_config.get("framerate", 44100), + channels=audio_config.get("channels", 2), + period_size=audio_config.get("period_size", 4096), + ) + dejavu = Dejavu(config.get("dejavu", dict())) - player = Turntable(listener.framerate, listener.channels, dejavu, pcm_in, events) + turntable = Turntable(listener.framerate, listener.channels, dejavu, pcm_in, events) icecast_config = config.get("icecast", dict()) icecast = Icecast( @@ -63,7 +72,7 @@ def run() -> "Iterator[Queue[Event]]": password=icecast_config.get("admin_password", "hackme"), ) - processes = [listener, player] + processes = [listener, player, turntable] for process in processes: process.daemon = True process.start() diff --git a/turntable/audio.py b/turntable/audio.py index 2250432..317c1c8 100644 --- a/turntable/audio.py +++ b/turntable/audio.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) class Listener(Process): def __init__( self, - pcm_in: "Queue[PCM]", + pcm_in: "List[Queue[PCM]]", events: Queue, device: str, sample_length: int = 30, @@ -25,7 +25,7 @@ class Listener(Process): period_size: int = 1024, ) -> None: super().__init__() - logger.info("Initializing Listener") + logger.info(f"Initializing Listener using '{device}'") self.pcm_in = pcm_in self.events = events self.framerate = framerate @@ -55,13 +55,54 @@ class Listener(Process): ) def run(self) -> None: + framecount = 0 + event_limit = self.framerate while True: length, data = self.capture.read() if length > 0: pcm = PCM(self.framerate, self.channels, data) - self.pcm_in.put(pcm) - self.events.put(Audio(pcm)) + for queue in self.pcm_in: + queue.put(pcm) + framecount += length + if framecount >= event_limit: + framecount = 0 + self.events.put(Audio(pcm)) else: logger.warning( "Sampler error (length={}, bytes={})".format(length, len(data)) ) + +class Player(Process): + def __init__( + self, + pcm_in: "Queue[PCM]", + device: str, + sample_length: int = 30, + framerate: int = 44100, + channels: int = 2, + period_size: int = 1024, + ) -> None: + super().__init__() + logger.info(f"Initializing Player using '{device}'") + self.pcm_in = pcm_in + self.framerate = framerate + self.channels = channels + self.playback = alsaaudio.PCM( + device=device, + type=alsaaudio.PCM_PLAYBACK, + format=alsaaudio.PCM_FORMAT_S16_LE, + periodsize=period_size, + rate=framerate, + channels=channels, + ) + logger.info( + "Player started on '%s' [rate=%d, channels=%d, periodsize=%d]", + device, + framerate, + channels, + period_size, + ) + + def run(self) -> None: + while pcm := self.pcm_in.get(): + self.playback.write(pcm.raw)