feat: styles own their ringing tone (simple beeps, blink plays music)

Move audio ownership out of the shared runner and into each style via an
injected Sound capability (play_music(path) / play_beep()). The runner now
only owns button sampling and cleanup; the style kicks off its own tone in
start() and drives the LED.

- simple: a repeating square-wave beep (ordinary alarm-clock tone), no music
  file required. The beep is synthesised in memory as signed-16-bit stereo
  PCM (stdlib array+math) and played via pygame.mixer.Sound — no shipped
  audio asset, no new dependency.
- blink: unchanged behaviour — music on an endless loop from --music-file /
  MUSIC_FILE / the default track, via pygame.mixer.music.
- AlarmStyle contract gains sound + music_file in __init__ and a start()
  lifecycle hook; AlarmSound Protocol documents the audio seam for future
  styles that handle their own tone.
- setup() no longer loads music (that is the style's job now).

README updated: per-style ringing tone, the --music-file note (blink only),
and the extended plugin contract.
This commit is contained in:
2026-08-02 16:29:40 +02:00
parent 88a57dab6e
commit 92eba762ce
8 changed files with 291 additions and 87 deletions
+9 -7
View File
@@ -33,13 +33,13 @@ Please refer to the [arcade-button-wiring.md](arcade-button-wiring.md) file for
# Use the default music file or the MUSIC_FILE environment variable
python3 wecker.py
# Or pass a custom music file directly
# Or pass a custom music file (used by the blink style; ignored by simple)
python3 wecker.py --music-file /path/to/your/alarm.mp3
# Choose an alarm style (default: blink; see Alarm Styles below)
python3 wecker.py --style simple
```
The music file is resolved in this order: `--music-file` argument, `MUSIC_FILE` environment variable, default `Laid Back - Sunshine Reggae.mp3`. The `--style` argument selects the alarm behaviour and defaults to `blink` (so existing cron entries keep working unchanged).
`--style` selects the alarm behaviour and defaults to `blink` (so existing cron entries keep working unchanged). Each style owns its own ringing tone: `blink` plays a music file on loop (resolved from `--music-file`, then the `MUSIC_FILE` env var, then the default `Laid Back - Sunshine Reggae.mp3`); `simple` ignores the music file and beeps.
## Automating and Managing Alarms (GraphQL API)
@@ -145,12 +145,14 @@ Returns `true` if the alarm was stopped, `false` if it wasn't ringing.
Each alarm has a **style** that decides how you turn it off. Set it per alarm via the `style` field of `setAlarm` (or the `style` argument of `startRinging`). Available styles:
| Style | Behaviour |
|---|---|
| `simple` | Press the button once to stop. The LED stays solid on so the button is findable in the dark. **Default for new alarms.** |
| `blink` | The memory-and-attention puzzle described in [How the Puzzle Works](#how-the-puzzle-works). |
| Style | Ringing tone | How to stop |
|---|---|---|
| `simple` | A repeating, ordinary-alarm-clock **beep** (no music file needed). | Press the button once. The LED stays solid on so the button is findable in the dark. **Default for new alarms.** |
| `blink` | A music file on an endless loop (from `--music-file` / `MUSIC_FILE` / the default track). | The memory-and-attention puzzle described in [How the Puzzle Works](#how-the-puzzle-works). |
Adding a style is a plugin-style drop-in: add a class under `styles/` implementing the `AlarmStyle` contract (`__init__(set_led)` + `update(now, is_pressed) -> bool`) and register one line in `styles/__init__.py`.
**The style owns its ringing tone.** The runner hands each style an audio capability (`play_music(path)` and `play_beep()`); the style decides which to use and when. This is the seam for future styles that handle their own tone.
Adding a style is a plugin-style drop-in: add a class under `styles/` implementing the `AlarmStyle` contract (`__init__(set_led, sound, music_file)` + `start()` + `update(now, is_pressed) -> bool`) and register one line in `styles/__init__.py`.
**Backward compatibility:** existing cron entries created before this feature have no `--style` flag and keep running the `blink` puzzle, so an upgrade never silently changes an alarm. To switch an existing alarm to `simple`, re-save it with `setAlarm(id: ..., cronExpression: ..., style: "simple")`.