From a7cf0d33a0e56d4f2346ff410f6a1fdfc00c5ec4 Mon Sep 17 00:00:00 2001 From: KoyoNoguchi Date: Mon, 5 Oct 2026 15:53:29 +0900 Subject: [PATCH] =?UTF-8?q?=E7=B2=BE=E5=BA=A6=E5=AE=9F=E9=A8=93=E7=94=A8?= =?UTF-8?q?=E3=82=B9=E3=82=AF=E3=83=AA=E3=83=97=E3=83=88=E3=81=AE=E8=A7=92?= =?UTF-8?q?=E5=BA=A6=E7=9F=A5=E3=82=89=E3=81=9B=E6=A9=9F=E8=83=BD=E3=81=AE?= =?UTF-8?q?=E9=9F=B3=E5=A3=B0=E3=82=92=E7=AC=A6=E5=8F=B7=E3=81=A8=E6=95=B0?= =?UTF-8?q?=E5=AD=97=E3=81=AB=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/accuracy_session.py | 68 +++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/src/accuracy_session.py b/src/accuracy_session.py index e13a2fa..17ac898 100644 --- a/src/accuracy_session.py +++ b/src/accuracy_session.py @@ -44,20 +44,19 @@ INTERVAL_AUDIO = { "block": Path("assets/audio/block.wav"), "auto": Path("assets/audio/auto_interval.wav"), } -# One prepared announcement file per target yaw angle. +# Angle announcement is composed from separate sign and number WAV files. # Prepare these files under assets/audio/ using exactly these names. -ANGLE_AUDIO = { - 0.0: Path("assets/audio/yaw_0.wav"), - 5.0: Path("assets/audio/yaw_plus_5.wav"), - -5.0: Path("assets/audio/yaw_minus_5.wav"), - 10.0: Path("assets/audio/yaw_plus_10.wav"), - -10.0: Path("assets/audio/yaw_minus_10.wav"), - 15.0: Path("assets/audio/yaw_plus_15.wav"), - -15.0: Path("assets/audio/yaw_minus_15.wav"), - 20.0: Path("assets/audio/yaw_plus_20.wav"), - -20.0: Path("assets/audio/yaw_minus_20.wav"), - 30.0: Path("assets/audio/yaw_plus_30.wav"), - -30.0: Path("assets/audio/yaw_minus_30.wav"), +ANGLE_SIGN_AUDIO = { + "plus": Path("assets/audio/plus.wav"), + "minus": Path("assets/audio/minus.wav"), +} +ANGLE_NUMBER_AUDIO = { + 0: Path("assets/audio/0.wav"), + 5: Path("assets/audio/5.wav"), + 10: Path("assets/audio/10.wav"), + 15: Path("assets/audio/15.wav"), + 20: Path("assets/audio/20.wav"), + 30: Path("assets/audio/30.wav"), } LOGGER = logging.getLogger(__name__) DEFAULT_ANGLES = (0.0, 5.0, -5.0, 10.0, -10.0, 15.0, -15.0, 20.0, -20.0, 30.0, -30.0) @@ -185,14 +184,22 @@ class IntervalAudioPlayer: self._sounds[kind] = mixer.Sound(str(path)) except Exception as exc: LOGGER.error("Failed to load interval audio %s: %s", path, exc) - for angle, path in ANGLE_AUDIO.items(): + for sign, path in ANGLE_SIGN_AUDIO.items(): if not path.is_file(): - LOGGER.warning("Angle announcement audio file does not exist (%+g deg): %s", angle, path) + LOGGER.warning("Angle sign audio file does not exist (%s): %s", sign, path) continue try: - self._sounds[f"angle:{angle:g}"] = mixer.Sound(str(path)) + self._sounds[f"angle_sign:{sign}"] = mixer.Sound(str(path)) except Exception as exc: - LOGGER.error("Failed to load angle announcement audio (%+g deg) %s: %s", angle, path, exc) + LOGGER.error("Failed to load angle sign audio (%s) %s: %s", sign, path, exc) + for number, path in ANGLE_NUMBER_AUDIO.items(): + if not path.is_file(): + LOGGER.warning("Angle number audio file does not exist (%d): %s", number, path) + continue + try: + self._sounds[f"angle_number:{number}"] = mixer.Sound(str(path)) + except Exception as exc: + LOGGER.error("Failed to load angle number audio (%d) %s: %s", number, path, exc) except Exception as exc: LOGGER.error("Failed to initialize pygame mixer for interval audio: %s", exc) self._initialized = False @@ -210,9 +217,30 @@ class IntervalAudioPlayer: return False def play_angle(self, angle: float) -> bool: - """Announce the target yaw angle using the prepared per-angle WAV file.""" - key = f"angle:{angle:g}" - return self.play(key) + """Announce the target yaw as separate sign + number WAV files.""" + value = float(angle) + if value == 0: + return self.play("angle_number:0") + + number = int(abs(value)) if abs(value).is_integer() else None + if number is None: + LOGGER.warning("No integer angle audio is defined for %+g deg", value) + return False + + sign = "plus" if value > 0 else "minus" + sign_played = self.play(f"angle_sign:{sign}") + # Wait until the sign audio has finished before starting the number. + # pygame Sound.play() is asynchronous, so use the sound length when available. + sign_sound = self._sounds.get(f"angle_sign:{sign}") + if sign_played and sign_sound is not None: + try: + duration_ms = max(0, int(float(sign_sound.get_length()) * 1000)) # type: ignore[attr-defined] + if duration_ms: + time.sleep(duration_ms / 1000.0) + except Exception: + pass + number_played = self.play(f"angle_number:{number}") + return sign_played and number_played def close(self) -> None: if self._closed: