Figures: several albums per figure, picked on the LED ring
A figure folder that holds album folders instead of audio can play any of them, up to six, in folder order (depth-first, alphabetical). Each is its own album, titled "Fuchs - <folder>" and of the figure's kind. Putting the figure down plays the album it played last, or the first, straight away; another figure placed in between forgets the position in that album, not the album itself. Meanwhile the ring offers all of them: one section per album from a fixed palette, edges shifting towards the neighbours' hue, the selected one bright and wobbling. The knob moves the selection one section per detent and wraps; pressing plays the selected album, and left alone for 8 s the picker fades into the figure's colours and the music carries on. While open, the device hands the knob to the picker (`capture_knob`): turns do not change the volume and the press is not published to Home Assistant. A tick per step and a confirm "pling" go with it. The firmware only draws: one new ring effect, LED_WHEEL_EFFECT_SEGMENT_SELECT (23), re-sent on every detent with the section to glide from. The look is tunable through a temporary `general.figure_selection` config section until the right values become constants. Every UI sound - the screen's blubb, the parents' timer, the picker - now follows the music volume with a floor, times a new parent-mode "Effekt-Lautstärke" (`general.effects_volume`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,9 @@ async handlers, so running it there would hold every other event up behind ``xse
|
||||
Here each press gets its own task, and a hung or failing command is only logged.
|
||||
|
||||
Each switch is answered with a "blubb" (:mod:`musicmouse.blubb`) on the music's own
|
||||
ALSA device, at the player's volume. The command's last word of output says which way
|
||||
it went - ``on`` or ``off``, as ``kiosk-screen`` prints it; anything else gets no sound.
|
||||
ALSA device, as loud as UI sounds are (:func:`musicmouse.sound.ui_level`). The
|
||||
command's last word of output says which way it went - ``on`` or ``off``, as
|
||||
``kiosk-screen`` prints it; anything else gets no sound.
|
||||
|
||||
With an ``off_command`` it also switches the monitor off by itself once nothing has
|
||||
played and nobody has done anything for ``auto_off_minutes`` - silently, since whoever
|
||||
@@ -68,7 +69,8 @@ class ScreenService:
|
||||
self.config = config
|
||||
#: Where the blubb plays, or ``None`` for no sound (simulated audio).
|
||||
self.alsa_device = alsa_device
|
||||
#: The player's volume as a fraction, read at the moment the sound plays.
|
||||
#: How loud UI sounds are right now, 0..1 (see :func:`musicmouse.sound.ui_level`),
|
||||
#: read at the moment the sound plays.
|
||||
self.volume = volume
|
||||
self.is_playing = is_playing
|
||||
self._clock = clock or RealClock()
|
||||
|
||||
@@ -49,8 +49,6 @@ RING_SECONDS = 60.0
|
||||
|
||||
#: The chime's peak at full player volume, as a fraction of full scale - soft.
|
||||
_CHIME_GAIN = 0.3
|
||||
#: Never quieter than at this player volume: a muted player must not mute the timer.
|
||||
_CHIME_MIN_VOLUME = 0.3
|
||||
|
||||
#: After a failed holiday fetch, try again this much later rather than at midnight.
|
||||
_RETRY_SECONDS = 3600.0
|
||||
@@ -94,7 +92,8 @@ class TimerService:
|
||||
self.client = client
|
||||
#: Where the chime plays, or ``None`` for no sound (simulated audio).
|
||||
self.alsa_device = alsa_device
|
||||
#: The player's volume as a fraction, read when the chime plays.
|
||||
#: How loud UI sounds are right now, 0..1 (see :func:`musicmouse.sound.ui_level`),
|
||||
#: read when the chime plays.
|
||||
self.volume = volume
|
||||
self._clock = clock or RealClock()
|
||||
self._wall = wall
|
||||
@@ -211,7 +210,7 @@ class TimerService:
|
||||
async def _chime(self) -> None:
|
||||
if self.alsa_device is None:
|
||||
return
|
||||
gain = _CHIME_GAIN * max(_CHIME_MIN_VOLUME, min(1.0, self.volume()))
|
||||
gain = _CHIME_GAIN * max(0.0, min(1.0, self.volume()))
|
||||
wav = await asyncio.to_thread(chime_wav, gain=gain)
|
||||
await play_wav(self.alsa_device, wav)
|
||||
|
||||
|
||||
@@ -293,6 +293,7 @@ class SettingsOut(BaseModel):
|
||||
initial_volume: int
|
||||
volume_increment: int
|
||||
button_leds_brightness: float
|
||||
effects_volume: int
|
||||
#: ``general.screen.auto_off_minutes``, or ``None`` when there is no screen to
|
||||
#: switch off (no ``screen`` section, or one without an ``off_command``).
|
||||
screen_auto_off_minutes: int | None
|
||||
@@ -304,6 +305,8 @@ class SettingsIn(BaseModel):
|
||||
initial_volume: int = Field(ge=0, le=200)
|
||||
volume_increment: int = Field(ge=1, le=100)
|
||||
button_leds_brightness: float = Field(ge=0, le=1)
|
||||
#: UI sounds relative to the music, in percent.
|
||||
effects_volume: int = Field(ge=0, le=100)
|
||||
#: Ignored without a screen section. ``None`` leaves it as it is.
|
||||
screen_auto_off_minutes: int | None = Field(default=None, ge=0, le=720)
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ def read_settings(general: GeneralConfig) -> SettingsOut:
|
||||
initial_volume=general.initial_volume,
|
||||
volume_increment=general.volume_increment,
|
||||
button_leds_brightness=general.button_leds_brightness,
|
||||
effects_volume=general.effects_volume,
|
||||
screen_auto_off_minutes=(
|
||||
screen.auto_off_minutes if screen is not None and screen.off_command else None
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user