Skip to content

make-doorbell.py

Files / atom-echo / make-doorbell.py

Files in atom-echo/

DownloadRawPython · 50 lines · 1.5 KB

From M5Stack Atom Echo on your own ESPHome build: push to talk, a doorbell, no internet. MIT licence: LICENSE.

atom-echo/make-doorbell.py
#!/usr/bin/env python3
"""Make doorbell.wav: a two-note "ding-dong" chime, synthesised (no licence issues).
Each note is a few decaying sine partials, like a struck metal bar.
Mono, 22.05 kHz, 16-bit, peak -3 dBFS (the Atom Echo's small speaker rattles near 0 dBFS).
Usage: python3 make-doorbell.py [out.wav]
"""
import math
import struct
import sys
import wave
RATE = 22050
LENGTH = 3.2 # seconds
NOTES = [(659.25, 0.0), (523.25, 0.75)] # E5 "ding", then C5 "dong" 0.75 s later
# (multiple of the note, loudness, decay per second)
PARTIALS = [(1.0, 1.0, 2.5), (2.0, 0.6, 4.0), (3.0, 0.3, 6.0), (4.07, 0.2, 9.0)]
ATTACK = 0.004 # seconds; avoids a click at the start of each note
FADE = 0.3 # seconds of fade-out at the end
PEAK = 10 ** (-3 / 20)
def note(freq, t):
if t < 0:
return 0.0
s = sum(a * math.sin(2 * math.pi * freq * m * t) * math.exp(-d * t) for m, a, d in PARTIALS)
return s * min(1.0, t / ATTACK)
def main():
out = sys.argv[1] if len(sys.argv) > 1 else "doorbell.wav"
n = int(RATE * LENGTH)
samples = []
for i in range(n):
t = i / RATE
v = sum(note(f, t - start) for f, start in NOTES)
v *= min(1.0, (LENGTH - t) / FADE)
samples.append(v)
scale = PEAK / max(abs(v) for v in samples)
with wave.open(out, "wb") as w:
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(RATE)
w.writeframes(b"".join(struct.pack("<h", round(v * scale * 32767)) for v in samples))
print(f"🟢 wrote {out} ({LENGTH} s, {RATE} Hz)")
if __name__ == "__main__":
main()

Text, photos and drawings: CC BY 4.0. Code: MIT. Share and adapt them, crediting HomeLab Guides (blog.jacq.cc). Licence and privacy