Skip to content

Redesigned glucose alarm sounds and a per-alarm sound picker - #2597

Merged
ps2 merged 11 commits into
LoopKit:next-devfrom
dnzxy:feat/alarm-sound-themes
Oct 26, 2026
Merged

ps2 merged 11 commits into
LoopKit:next-devfrom
dnzxy:feat/alarm-sound-themes

Conversation

@dnzxy

@dnzxy dnzxy commented Oct 22, 2026 •

Copy link
Copy Markdown

Why

Trio runs the same glucose alert design as next-dev and shipped it to a wide user base. The alert logic held up; the tone set did not. The volume of complaints about the sounds was among the highest Trio has seen in four years — too harsh, too startling, and in several cases actively unpleasant to be woken by. Normalising the existing files did not move the needle, because loudness was never the real problem: the tones themselves were wrong.

So we worked with someone active in sound design to build a purpose-made set. These have been GA in Trio since 2026-09-15 and the feedback flipped completely. This PR adapts that work to Loop's next-dev alert system, offered upstream in case it's useful.

What changes

Themed sets, written per alarm. Each glucose alarm (Urgent Low, Low, Predicted Low, High) gets its own Acoustic, Electronic and Voice set. These are not one recording transposed — the Urgent Low set is a different piece of sound design from the High set, so the alarm is identifiable before you read the banner.

Urgency is a recording, not a gain stage. Acoustic and Electronic each come in Mild / Medium / Harsh. Each level is its own recording, with increasing dissonance and harsher high/low content to cut through background noise and sleep. Nothing is simply "the same file, louder".

Voice alerts. Spoken alerts in a male and a female voice (Bryce, Amy), generated with Piper, following the example of Loop Follow but with current models and explicit licensing.

Defaults move to Acoustic / Medium for all four alarms. That's the designer's recommendation after hallway testing with adult and child PWDs, and it is the single change that resolved most of the complaints we had.

The picker leads with what fits. The sound list now shows the sets written for the alarm you're editing first, with the urgency slider or voice toggle inline on the selected row. The existing generic tones are all still there, moved under a collapsible "Other Sounds" group that auto-expands if that's where your current pick lives. Each row has its own play/stop preview, and changing urgency auditions the new level immediately.

New generic tones are added alongside the existing ones: Glass Bell, Harp, Meow, Saw Wave, Hard Saw Wave, Sine Wave, Open Can, Open Wrapper. (The cat meow was the single most-requested sound from PWDs under 10.)

Sound design principles used

  1. The primary job is to get attention. Anything a tone can additionally convey about what triggered it is a bonus.
  2. Rising and falling pitch maps to rising and falling glucose, so users who perceive pitch get direction for free.
  3. Dissonance and harsh high frequencies cut through background noise and wake people more reliably.
  4. A shared voicing and key across the set makes the app identifiable by sound alone.

Upgrade behaviour

The stored value is still just a filename, so nothing needs migrating:

  • Users who picked a sound keep it. Every legacy tone stays in the catalog, under "Other Sounds".
  • Users who never picked one move from urgent_low.caf / simple.caf / chime.caf / mood_synth.caf to their alarm's Acoustic Medium set. That default change is the point of the PR, but it is a behaviour change on upgrade and worth calling out in release notes.

Size and format

40 new .caf files, +5.6 MB, IMA4-encoded per the existing bundled tones and the ≤30 s / bundled-.caf constraint that CriticalAlertAlarmScheduler already relies on for AlarmKit. Longest file is 10.8 s. Duration is itself part of the design — Carbs-Required-class tones are short, Urgent Low is long — so the files are shipped exactly as delivered apart from the IMA4 encode.

Licensing

ACKNOWLEDGEMENTS.md is added at the repo root:

  • Themed sets and one-off tones (acoustic_*, electronic_*, glass_bell, harp, meow, saw_wave, hard_saw_wave, sine_wave, open_can, open_wrapper) — CC0 1.0. The author has dedicated them to the public domain, and states that they are the author, that they hold the right to license them, and that the files contain no third-party material they cannot license. They ask to remain anonymous and to be credited as "an anonymous diabetes dad and Trio user" — CC0 does not require that, so it is a courtesy credit in ACKNOWLEDGEMENTS.md. That dedication is what lets Loop carry them, not a Trio-only grant.
  • Quoting from shared sound file package:

License
I am the author of the attached files in mild-medium-harsh sets and _One-offs-.
I have the right to license them, and they contain no third-party material I cannot license. I license them under the Creative Commons Zero license for inclusion in Trio. I also grant Dan / the project maintainers permission to distribute them under any later license adopted by the project. I wish to remain anonymous; please credit me as "An anonymous diabetes dad and Trio user" or not at all.

  • Bryce (voice_*_bryce.caf) — public domain; the speaker released the training data.
  • Amy (voice_*_amy.caf) — CC BY-SA 4.0 via MycroftAI/mimic3-voices, attributed in ACKNOWLEDGEMENTS.md. The audio files are under CC BY-SA 4.0; this does not affect Loop's own licence. This is the only ShareAlike asset in the PR — if that is unacceptable here, dropping the Amy files is a self-contained change: the Voice entry collapses to Bryce only.

dnzxy and others added 11 commits October 22, 2026 18:34
The in-app fallback stopped after a flat 6s, which was a few loops of the
old ~0.84s tone but just over half of the new 10.8s urgent-low alarms.
Run whole passes of whatever sound is configured instead, up to a floor
of 6s and a 30s ceiling.
The display names were plain Swift strings, so nothing in the catalog was
reachable by the string catalog: a translated build showed Mild / Medium /
Harsh, Acoustic, Voice and all 26 tone names in English. The set-plus-variant
label is a format string rather than interpolation, so the separator can move.
The Urgent Low screen had a debug-only arm that substituted a below-threshold
value for the next real CGM reading, so the reading it displaced was never
evaluated. Replace it with one control on all four alarms, available in every
build: the test alert goes out alongside the next reading (the next loop cycle
for Predicted Low) under its own identifier, carrying that alarm's configured
sound and interruption level.

Arming rather than firing immediately is what makes it worth having — it
leaves time to lock the phone, turn on a Focus or turn the ringer down first,
which is the setup users actually need to check. Issuing it in addition to
the normal evaluation, on its own identifier, keeps it clear of the real
alarm's episode state, retraction and AlarmKit alarm.
Five things that read wrong on screen:

- The picker was titled "Sound" on all four alarms, with nothing saying
  which one you were editing. Title it with the alarm.
- The footer talked about Urgency even when Voice was selected. Describe
  whichever control the selected set is showing.
- "Other Sounds" was a DisclosureGroup, whose chevron is the one a
  NavigationLink draws, so the row looked like a push. Draw a rotating
  chevron instead.
- Opening that group left the stored pick off screen if it was one of the
  later tones. Scroll to it.
- The Voice row said "Voice" twice, as the row title and again above the
  segmented control.

Also pass the finishing player to the preview delegate, so a preview that
ends during the hop to the main actor can't stop the one that replaced it.
LICENSE.md opens by saying exceptions are noted below and is where the
graphics exceptions live, so the new sound and voice licences belong on
that trail rather than only in a file of their own.
@ps2

ps2 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

Took this for a run in the simulator and measured the bundled files. The picker design holds up well — three rows instead of eighteen, with the control on the selected row, is a real improvement, and keeping the old tones selectable under "Other Sounds" is the right call for upgrades.

I've pushed five commits to this branch (details at the bottom). One thing I did not change, because it's a design decision rather than a bug, is the level of the new files. Recording the measurements here so it's a decision rather than an oversight.

Level of the new defaults

iOS doesn't normalise notification or alarm audio, so the file level is the alarm loudness. Loudest-1-second RMS, each new default against the tone it replaces:

alarm old default new default Δ
Urgent Low urgent_low.caf −13.7 dB acoustic_ulg_medium −22.5 dB −8.8
Low simple.caf −9.9 dB acoustic_lg_medium −23.7 dB −13.8
High chime.caf −11.1 dB acoustic_hg_medium −24.7 dB −13.6
Predicted Low mood_synth.caf −10.0 dB acoustic_lgs_medium −23.7 dB −13.7

Peaks say the same thing: the legacy tones sit at −0.3 to −1 dBFS, the themed sets at −8.4 to −14 dBFS. Roughly 13 dB is subjectively something like a quarter the loudness.

ACKNOWLEDGEMENTS.md says the themed sets were left as delivered "since their length and level carry the urgency along with the timbre", so this may well be deliberate. If we do want to close the gap without touching the sound design, a single uniform gain per theme family preserves every relationship inside it, including mild → medium → harsh: +7.4 dB across all acoustic, +7.8 dB across all electronic, +1.8 dB across voice, each landing at −1 dBFS peak.

Two related oddities in the same measurements:

  • On Urgent Low Acoustic, Harsh is quieter than Medium. mild −24.9 / medium −22.5 / harsh −24.3 dB loudest-1s RMS (peaks −8.7 / −9.6 / −11.0). Moving the slider to the most urgent position makes the most safety-critical alarm quieter. The other three alarms are monotonic.
  • For all four Electronic sets, Mild and Medium are within 0.3 dB of each other. They are genuinely different recordings (checksummed), but the first two slider positions won't read as a change in level.

Bundle and on-device size

Bundled sounds go 4.8 MB → 10.4 MB, and the catalog matches the directory exactly (58 names, 58 files, no orphans either way). Worth knowing that AlertManager.initializeSoundVendor then copies every vendored sound into Library/Sounds and never prunes: on a live install I measured 58 files / 11 MB there, written during launch before onboarding finishes, against 18 files / 4.9 MB on a pre-PR container. So on-device footprint goes from about 9.7 MB to about 21 MB to back four selected sounds. Not a blocker — just worth being a deliberate choice, and worth knowing that if a tone is ever retired its Library/Sounds copy will linger forever.

Pushed to this branch

  • Play the whole alarm in the critical audio fallback. CriticalAlertAudioPlayer stopped after a flat 6 s. That was a few loops of the old ~0.84 s tone, but the new acoustic/electronic urgent-low files are 10.8 s, so you heard 56% of one. It now runs whole passes of whatever sound is configured, with 6 s as a floor rather than a cap and a 30 s ceiling. (Only the non-AlarmKit path; iOS 26+ hands the file to AlarmKit.)
  • Localize the alarm sound names. displayName was a plain String throughout the catalog, so Mild / Medium / Harsh, Acoustic, Electronic, Voice and all 26 tone names were unreachable from the string catalog. The set-plus-variant label is now a format string rather than interpolation, so the separator can move.
  • Offer a test alert on every glucose alarm. The debug-only arm on Urgent Low substituted a below-threshold value for the next real CGM reading, which meant that reading was never evaluated. It's now one control on all four alarms in every build: the test goes out alongside the next reading (the next loop cycle for Predicted Low) on its own identifier, carrying that alarm's configured sound and interruption level, in addition to the normal evaluation. Arming rather than firing on the spot is the point — it leaves time to lock the phone, turn a Focus on, or turn the ringer down first, and then see whether the alert still reaches you, which is the setup people actually need to check. The arm is persisted, since iOS can terminate a backgrounded Loop before the next reading and a test that quietly evaporates reads exactly like a broken alarm.
  • Tidy the sound picker. Title it with the alarm rather than just "Sound"; describe whichever control the selected set shows (the footer talked about Urgency with Voice selected); replace the DisclosureGroup whose chevron is the one NavigationLink draws, so the row no longer looks like a push; scroll to the stored pick when it's one of the later generic tones; drop the second "Voice" label above the segmented control. Plus: pass the finishing player to the preview delegate, so a preview ending during the hop to the main actor can't stop the one that replaced it.
  • Point LICENSE at the sound acknowledgements. LICENSE.md opens by saying exceptions are noted below and is where the graphics exceptions live, so the new sound and voice licences belong on that trail too.

One note for whenever the level work above happens: the Amy files are CC BY-SA 4.0, so a gain pass over them produces an adaptation that stays CC BY-SA 4.0. ACKNOWLEDGEMENTS.md already documents the looping and re-encoding the same way, so it's just a line to keep current rather than anything that constrains the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants