Wren Linux

wren-linux / tools / make-alarm-sound.py

#!/usr/bin/python3
"""Writes data/alarm.wav, the sound the timer and alarm clock ring with: a calm
C-major bell phrase (C, E, G, C, G, E), three times, 8 seconds, mono 16-bit at
22.05 kHz (about 350 KB). Made only from sine waves with a soft attack and a
bell-like fade, so it can be remade and nothing in it comes from anywhere else.
The alarm clock repeats it for a few minutes (bin/timer).
    python3 tools/make-alarm-sound.py"""
import math
import os
import struct
import wave

SAMPLE_RATE = 22050
DURATION = 8.0
VOLUME = 0.32
NOTES = [523.25, 659.25, 783.99, 1046.50, 783.99, 659.25]
BEAT = 0.55
NOTE_LENGTH = 0.9


def main():
    events = []
    for repeat_start in (0.0, 3.3, 6.6):
        for i, frequency in enumerate(NOTES):
            start = repeat_start + i * BEAT
            if start < DURATION:
                events.append((start, frequency))

    frames = bytearray()
    for i in range(int(DURATION * SAMPLE_RATE)):
        t = i / SAMPLE_RATE
        sample = 0.0
        for start, frequency in events:
            elapsed = t - start
            if 0 <= elapsed < NOTE_LENGTH:
                attack = min(1.0, elapsed / 0.035)      # gentle attack
                decay = math.exp(-3.2 * elapsed)        # bell-like fade
                tone = (
                    math.sin(2 * math.pi * frequency * elapsed)
                    + 0.22 * math.sin(2 * math.pi * frequency * 2 * elapsed)
                    + 0.08 * math.sin(2 * math.pi * frequency * 3 * elapsed)
                )
                sample += tone * attack * decay
        sample = max(-1.0, min(1.0, sample * VOLUME))
        frames.extend(struct.pack("<h", int(sample * 32767)))

    path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "data", "alarm.wav")
    with wave.open(path, "wb") as wav:
        wav.setnchannels(1)
        wav.setsampwidth(2)
        wav.setframerate(SAMPLE_RATE)
        wav.writeframes(bytes(frames))
    print("wrote", os.path.normpath(path))


main()