From b614114286602b66b020b6300e47aae537a91c46 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Wed, 24 Jun 2026 22:17:47 +0200 Subject: [PATCH] =?UTF-8?q?swap=20pygame=20for=20miniaudio=20=E2=80=94=20z?= =?UTF-8?q?ero=20system=20deps,=20pip=20install=20miniaudio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- session/ambience_options.md | 4 +- tools/run.py | 112 ++++++++++++++++-------------------- 2 files changed, 53 insertions(+), 63 deletions(-) diff --git a/session/ambience_options.md b/session/ambience_options.md index f50e104..38a5105 100644 --- a/session/ambience_options.md +++ b/session/ambience_options.md @@ -9,7 +9,7 @@ When multiple files are listed, one is chosen at random each time the ambience a ## Requirements ```bash -pip install pygame yt-dlp +pip install miniaudio yt-dlp ``` `ffmpeg` must also be installed on your system. @@ -79,4 +79,4 @@ The TUI status bar shows the current ambience: Dillion ❤ 10 │ 42 entries │ 3 todo │ 2026-06-24 │ ♫ tavern ``` -If pygame is not installed, the music icon is absent and no audio plays. +If miniaudio is not installed, the status bar shows a hint and no audio plays. diff --git a/tools/run.py b/tools/run.py index 5655b6f..7fd8e3b 100755 --- a/tools/run.py +++ b/tools/run.py @@ -3,7 +3,7 @@ run.py — The Chaos TTRPG Session Client Layout: banner top | log (main) + character (right) | input bottom. -Music: polls session/ambience.md, crossfades via pygame.mixer. +Music: polls session/ambience.md, plays via miniaudio. """ import os @@ -18,12 +18,13 @@ from textual.containers import Horizontal, Vertical from textual.reactive import reactive from textual.widgets import Input, Static -# ── Optional pygame ─────────────────────────────────────── +# ── Optional miniaudio ──────────────────────────────────── try: - import pygame - HAS_PYGAME = True + import miniaudio + HAS_AUDIO = True except ImportError: - HAS_PYGAME = False + HAS_AUDIO = False + print("Note: miniaudio not installed — no ambience music. Install with: pip install miniaudio", file=sys.stderr) # ── Paths ──────────────────────────────────────────────── @@ -144,29 +145,19 @@ def parse_ambience_options(): class AmbiencePlayer: - """Monitors ambience.md and crossfades background music.""" + """Monitors ambience.md and plays background music via miniaudio.""" def __init__(self): self.current_ambience = 'silence' - self._enabled = False self._last_mtime = 0 self._options = {} - self._init_pygame() + self._device = None + self._stream = None self.load_options() - def _init_pygame(self): - if not HAS_PYGAME: - return - try: - os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' - pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512) - self._enabled = True - except Exception: - self._enabled = False - @property def available(self): - return self._enabled + return HAS_AUDIO @property def ambience_name(self): @@ -175,8 +166,17 @@ class AmbiencePlayer: def load_options(self): self._options = parse_ambience_options() + def _stop(self): + if self._device: + try: + self._device.close() + except Exception: + pass + self._device = None + self._stream = None + def poll(self): - if not self._enabled: + if not HAS_AUDIO: return try: mtime = os.path.getmtime(AMBIENCE_PATH) @@ -195,23 +195,19 @@ class AmbiencePlayer: if name == self.current_ambience: return self.current_ambience = name + self._stop() if name == 'silence' or name not in self._options: - if pygame.mixer.music.get_busy(): - pygame.mixer.music.fadeout(2000) return tracks = self._options.get(name, []) - # filter to existing files valid = [t for t in tracks if t.exists()] if not valid: return track = random.choice(valid) - if pygame.mixer.music.get_busy(): - pygame.mixer.music.fadeout(2000) try: - pygame.mixer.music.load(str(track)) - pygame.mixer.music.set_volume(0.4) - pygame.mixer.music.play(loops=-1, fade_ms=2000) - except pygame.error: + self._stream = miniaudio.stream_file(str(track)) + self._device = miniaudio.PlaybackDevice() + self._device.start(self._stream) + except Exception: self.current_ambience = None @@ -250,7 +246,9 @@ class StatusBar(AutoStatic): count = log_count() todo = len(read_todo()) music = "" - if app_ambience_player and app_ambience_player.available: + if not HAS_AUDIO: + music = " │ ♫ (install miniaudio)" + elif app_ambience_player: name = app_ambience_player.ambience_name music = f" │ ♫ {name}" self.update(f"{char} │ {count} entries │ {todo} todo │ {TODAY}{music}") @@ -295,10 +293,6 @@ class ChaosTUI(App): #main { height: 100%; - } - - #log-col { - border-right: solid #3a3a3a; background: #111111; } #todo-header { @@ -315,6 +309,20 @@ class ChaosTUI(App): height: 5; max-height: 5; } + #char-header { + background: #2d2d3a; + color: #b0a0e0; + text-style: bold; + padding: 0 1; + height: 1; + } + #char-content { + background: #1e1e2a; + padding: 0 1; + color: #c0c0c0; + height: 14; + max-height: 14; + } #log-header { background: #1d2d1d; color: #7dcd7d; @@ -325,23 +333,7 @@ class ChaosTUI(App): #transcript { padding: 0 1; color: #c8c8c8; - } - - #sidebar { - width: 36; - min-width: 28; - background: #181818; - } - #side-header { - background: #2d2d3a; - color: #b0a0e0; - text-style: bold; - padding: 0 1; - height: 1; - } - #char-content { - padding: 0 1; - color: #c0c0c0; + height: 1fr; } #status-bar { background: #222222; @@ -367,16 +359,14 @@ class ChaosTUI(App): def compose(self): yield Static(f"⚔ The Chaos ╎ {TODAY}", id="banner") - with Horizontal(id="main"): - with Vertical(id="log-col"): - yield Static("TODO", id="todo-header") - yield TodoPane(id="todo-content") - yield Static("LOG", id="log-header") - yield TranscriptPane(id="transcript") - with Vertical(id="sidebar"): - yield Static("CHARACTER", id="side-header") - yield CharPane(id="char-content") - yield StatusBar(id="status-bar") + with Vertical(id="main"): + yield Static("TODO", id="todo-header") + yield TodoPane(id="todo-content") + yield Static("CHARACTER", id="char-header") + yield CharPane(id="char-content") + yield Static("LOG", id="log-header") + yield TranscriptPane(id="transcript") + yield StatusBar(id="status-bar") with Horizontal(id="input-row"): self.input = Input(placeholder=" What do you do? (just type)", id="input") yield self.input