Alarm Clock Sounds

by Audrey M. Roy Greenfeld | Fri, Jan 17, 2025

FastHTML MonsterUI example app that uses Tone.js to make different alarm clock sounds.


The backstory: I've been adjusting my sleep schedule to match up better with my coworkers. This afternoon I found myself quite sleepy at the wrong time. I made this as a little utility for myself.

Setup

from fasthtml.common import *
from fasthtml.jupyter import *
from monsterui.all import *
app,rt = fast_app(hdrs=(Theme.blue.headers(),Script(src="https://unpkg.com/tone")))
server = JupyUvi(app)

Sounds JS

Note: I may break this up, putting the code for each function into its corresponding FT. I feel like that would be a little cleaner.

sounds_js = """const synth = new Tone.PolySynth().toDestination();
const pingPong = new Tone.PingPongDelay("4n", 0.2).toDestination();
synth.connect(pingPong);

let currentInterval;

// Classic alarm sound (repeating high-pitched beeps)
function playClassicAlarm() {
    stopCurrentSound();
    currentInterval = setInterval(() => {
        synth.triggerAttackRelease("C5", "16n");
        setTimeout(() => {
            synth.triggerAttackRelease("G4", "16n");
        }, 200);
    }, 400);
}

// Digital alarm sound (ascending beeps)
function playDigitalAlarm() {
    stopCurrentSound();
    const notes = ["C4", "E4", "G4", "C5"];
    let index = 0;
    currentInterval = setInterval(() => {
        synth.triggerAttackRelease(notes[index % notes.length], "8n");
        index++;
    }, 200);
}

// Gentle wake sound (soft pulsing chord)
function playGentleAlarm() {
    stopCurrentSound();
    const chord = ["C4", "E4", "G4"];
    currentInterval = setInterval(() => {
        synth.volume.value = -10;
        synth.triggerAttackRelease(chord, "2n");
    }, 2000);
}

// Stop all sounds
function stopCurrentSound() {
    if (currentInterval) {
        clearInterval(currentInterval);
        currentInterval = null;
    }
    synth.volume.value = 0;
}"""
alarms = (
    ('Classic', 'Repeating high-pitched beeps', 'playClassicAlarm'),
    ('Digital Beep', 'Ascending beeps', 'playDigitalAlarm'),
    ('Gentle Wake', 'Soft pulsing chord', 'playGentleAlarm')
)

SoundBar Page

def SoundBar():
    mbrs1 = [Li('Classic', onmousedown="playClassicAlarm()"), Li('Digital Beep', onmousedown="playDigitalAlarm()"), Li('Gentle Wake', onmousedown="playGentleAlarm()"), Li('Stop', onmousedown="stopCurrentSound()")]
    return NavContainer(*mbrs1)
SoundBar()
@rt
def index():
    return Titled("Alarm Clock Sounds", SoundBar(), Script(sounds_js))

Click on the first 3 to play their sounds, and "Stop" to stop:

# HTMX(index)

At this point I have clickable Li elements with onmousedown handlers. I could call this done if my goal is just to play different alarm clock sounds.

Better Alarm Display

Let's take it up a level with UI improvements with MonsterUI.

alarms[0]
Card?
c = Card((Img(alarms[0][1])), header=H2(alarms[0][0]), onmousedown=f"{alarms[0][2]}()")
c
show(c)

Note: It would be nice if the card looked like a MonsterUI card here, so I could build them up cell by cell. Instead let's look at it by redefining the index handler.

@rt
def index():
    return Titled("Alarm Clock Sounds", c, Script(sounds_js))

It works when I click on it on the index page. In the notebook it doesn't. Maybe I will break up the JS to fix that. Maybe.

But first let's show all the cards.

def AlarmCard(alarm): return Card((Img(alarm[1])), header=H2(alarm[0]), onmousedown=f"{alarm[2]}()")
show(AlarmCard(alarms[2]))
@rt
def index():
    return Titled("Alarm Clock Sounds", 
        *[AlarmCard(a) for a in alarms], Script(sounds_js))
# HTMX(index)
@rt
def index():
    return Titled("Alarm Clock Sounds", 
        *[AlarmCard(a) for a in alarms], 
        Card("Stop the current alarm", header=H2("STOP"), onmousedown="stopCurrentSound()"),
        Script(sounds_js))
# HTMX(index)
@rt
def index():
    return Titled("Alarm Clock Sounds", 
        DivFullySpaced(
            *[AlarmCard(a) for a in alarms], 
            Card("Stop the current alarm", header=H2("STOP"), onmousedown="stopCurrentSound()"),
        ),
        Script(sounds_js))
# HTMX(index)
@rt
def index():
    return Titled("Alarm Clock Sounds", 
        DivFullySpaced(
            Div(
                *[AlarmCard(a) for a in alarms], 
            ),
            Card("Stop the current alarm", header=H2("STOP"), onmousedown="stopCurrentSound()"),
        ),
        Script(sounds_js))
# HTMX(index)

Stop the Server

server.stop()