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:
@@ -15,6 +15,7 @@ from musicmouse.effects import (
|
||||
EffectCircularConfig,
|
||||
EffectRandomTwoColorInterpolationConfig,
|
||||
EffectReverseSwipe,
|
||||
EffectSegmentSelect,
|
||||
EffectStaticConfig,
|
||||
EffectStaticDetailedConfig,
|
||||
EffectSwipeAndChange,
|
||||
@@ -135,7 +136,54 @@ def test_swipe_and_change_is_the_two_payloads_concatenated() -> None:
|
||||
(EffectRandomTwoColorInterpolationConfig(), 35),
|
||||
(EffectReverseSwipe(), 12),
|
||||
(EffectSwipeAndChange(), 64),
|
||||
(EffectSegmentSelect(colors=(ColorRGBW(0, 0, 0, 0),)), 53),
|
||||
],
|
||||
)
|
||||
def test_payload_sizes(effect: LedEffect, size: int) -> None:
|
||||
assert len(effect.as_bytes()) == size
|
||||
|
||||
|
||||
def test_segment_select() -> None:
|
||||
effect = EffectSegmentSelect(
|
||||
colors=(ColorRGBW(1, 0, 0, 0), ColorRGBW(0, 0, 1, 0)),
|
||||
selected=1,
|
||||
previous=0,
|
||||
gap_leds=2,
|
||||
offset_degrees=180.0,
|
||||
direction=-1,
|
||||
dim_factor=0.5,
|
||||
wobble_amplitude=1.5,
|
||||
wobble_period_ms=1000.0,
|
||||
slide_ms=200.0,
|
||||
edge_blend=0.25,
|
||||
)
|
||||
assert effect.as_bytes().hex() == (
|
||||
"02" "01" "00" "02" # count, selected, previous, gap
|
||||
"ff000000" "0000ff00" + "00000000" * 4 # always six colours on the wire
|
||||
+ "00003443" # offset 180.0
|
||||
"ff" # direction -1
|
||||
"0000003f" # dim 0.5
|
||||
"0000c03f" # wobble 1.5
|
||||
"00007a44" # period 1000.0
|
||||
"00004843" # slide 200.0
|
||||
"0000803e" # edge blend 0.25
|
||||
)
|
||||
|
||||
|
||||
def test_segment_select_without_previous_does_not_glide() -> None:
|
||||
effect = EffectSegmentSelect(colors=(ColorRGBW(1, 0, 0, 0),) * 3, selected=2)
|
||||
assert effect.as_bytes()[:3] == bytes([3, 2, 2])
|
||||
|
||||
|
||||
def test_segment_select_round_trips() -> None:
|
||||
# Every float exact in float32, as the firmware has them.
|
||||
effect = EffectSegmentSelect(
|
||||
colors=(ColorRGBW(1, 0, 0, 0), ColorRGBW(0, 1, 0, 0)), previous=1, dim_factor=0.25
|
||||
)
|
||||
assert EffectSegmentSelect.from_bytes(effect.as_bytes()) == effect
|
||||
|
||||
|
||||
@pytest.mark.parametrize("count", [0, 7])
|
||||
def test_segment_select_takes_one_to_six_sections(count: int) -> None:
|
||||
with pytest.raises(ValueError, match="between 1 and 6"):
|
||||
EffectSegmentSelect(colors=(ColorRGBW(1, 0, 0, 0),) * count)
|
||||
|
||||
Reference in New Issue
Block a user