feat: notification chimes for room join / peer join / peer leave (phase 1)

Adds audible notifications for the basic membership events: a bright ascending
triad when you join a room, a soft two-note rise when another participant
joins, and a two-note fall when one leaves. Hooked into the app's UiEvent
handling (RoomJoined / PeerJoined / PeerLeft) so each is played locally by
whoever observes the event.

The chimes are short mono 16-bit WAVs generated by a committed stdlib-only
Python script (assets/sounds/generate_chimes.py) and embedded in the binary
with include_bytes!, so a deployed single binary is self-contained. The new
notify module materializes each to a temp file once, then plays it
fire-and-forget via pw-play (PipeWire-native; falls back to paplay/aplay) on a
detached thread that waits on the child -- never blocks the UI, never leaves a
zombie, and silently no-ops if no player is available.

Phase 1 of a larger plan; always-on for now. Later phases: reconnect-attempt /
reconnected sounds, a settings enable/disable toggle, per-event custom sounds,
and extra events (self-leave, mute/unmute, connection-lost).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 00:47:38 -04:00
co-authored by Claude Opus 4.8
parent ffe5b43d8d
commit 673b72d9ba
7 changed files with 175 additions and 0 deletions
+4
View File
@@ -1,5 +1,6 @@
use crate::core::{CoreController, messages::{CoreCommand, UiEvent}};
use crate::network::PeerState;
use crate::notify::{self, Sound};
use crate::audio::pw_cli::{AudioDevice, enumerate_audio_devices};
use crate::config::{AppConfig, NetworkMode};
@@ -208,6 +209,7 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
state.self_id = self_id;
state.status_message = "Connected".to_string();
state.current_screen = Screen::Room;
notify::play(Sound::SelfJoin);
}
UiEvent::RoomLeft => {
state.ticket = "".to_string();
@@ -220,12 +222,14 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
}
UiEvent::PeerJoined { id, state: peer_state } => {
state.peers.insert(id, peer_state);
notify::play(Sound::PeerJoin);
}
UiEvent::PeerLeft { id } => {
state.peers.remove(&id);
state.audio_levels.remove(&id);
state.connecting.remove(&id);
state.ever_connected.remove(&id);
notify::play(Sound::PeerLeave);
}
UiEvent::PeerUpdated { id, state: peer_state } => {
state.peers.insert(id, peer_state);