import machine
import time
import _thread
# ── Hardware ───────────────────────────────────────
buzzer = machine.PWM(machine.Pin(14))
servo = machine.PWM(machine.Pin(15), freq=50)
# ── Servo helper ───────────────────────────────────
def angle_to_duty(angle):
return int(1638 + (angle / 180) * (8192 - 1638))
def move(angle, delay_ms=15):
servo.duty_u16(angle_to_duty(max(0, min(180, angle))))
time.sleep_ms(delay_ms)
# ── Notes ──────────────────────────────────────────
NOTES = {
'A3': 220, 'B3': 247, 'Bb3': 233,
'C4': 262, 'D4': 294, 'Eb4': 311, 'E4': 330, 'F4': 349,
'G4': 392, 'Ab4': 415, 'A4': 440, 'Bb4': 466, 'B4': 494,
'C5': 523, 'D5': 587, 'Eb5': 622, 'E5': 659, 'F5': 698,
'G5': 784, 'Ab5': 831, 'A5': 880,
'R' : 0
}
# ── Timing — 120 BPM (classical allegro feel) ───────
BPM = 120
Q = int(60000 / BPM) # quarter ≈ 500ms
E = Q // 2 # eighth ≈ 250ms
S = Q // 4 # sixteenth ≈ 125ms
H = Q * 2 # half ≈ 1000ms
DQ = Q + E # dotted quarter
DE = E + S # dotted eighth
W = Q * 4 # whole
GAP = 20
# ── Shared state ───────────────────────────────────
state = ['idle']
# ── Buzzer ─────────────────────────────────────────
def play(note, dur):
if NOTES[note] == 0:
buzzer.duty_u16(0)
time.sleep_ms(dur)
else:
buzzer.freq(NOTES[note])
buzzer.duty_u16(32768)
time.sleep_ms(max(1, dur - GAP))
buzzer.duty_u16(0)
time.sleep_ms(GAP)
def play_song(melody):
for note, dur in melody:
play(note, dur)
# ── MOZART — Eine kleine Nachtmusik (K. 525) ───────
# 1st Movement: Allegro - Key: G Major - Most iconic Mozart melody
# Graceful, elegant, classical - perfect for servo dance
# Exposition Theme 1 — bright, cheerful opening
exposition = [
('G4', Q), ('D5', Q), ('G5', Q), ('D5', Q),
('G5', E), ('F#5', E), ('E5', Q), ('D5', H),
('G4', Q), ('D5', Q), ('G5', Q), ('D5', Q),
('G5', E), ('F#5', E), ('E5', Q), ('D5', H),
('G4', Q), ('B4', Q), ('C5', Q), ('D5', Q),
('E5', E), ('D5', E), ('C5', Q), ('B4', H),
('A4', Q), ('B4', Q), ('C5', Q), ('D5', Q),
('C5', E), ('B4', E), ('A4', Q), ('G4', H),
]
# Development — playful variations, more movement
development = [
('D5', Q), ('B4', Q), ('A4', Q), ('G4', Q),
('F#4', E), ('G4', E), ('A4', Q), ('B4', H),
('C5', Q), ('A4', Q), ('B4', Q), ('C5', Q),
('D5', E), ('C5', E), ('B4', Q), ('A4', H),
('E5', Q), ('C5', Q), ('D5', Q), ('E5', Q),
('F#5', E), ('E5', E), ('D5', Q), ('C5', H),
('B4', Q), ('G4', Q), ('A4', Q), ('B4', Q),
('C5', E), ('B4', E), ('A4', Q), ('G4', H),
]
# Recapitulation — returns to main theme with variation
recap = [
('G4', Q), ('D5', Q), ('G5', Q), ('R', E), ('F#5', E),
('E5', Q), ('D5', H),
('G4', Q), ('D5', Q), ('G5', Q), ('D5', Q),
('G5', E), ('F#5', E), ('E5', Q), ('D5', H),
('G4', Q), ('B4', Q), ('C5', Q), ('D5', Q),
('E5', E), ('D5', E), ('C5', Q), ('B4', H),
]
# Bridge/Cadence — elegant transition
bridge = [
('A4', Q), ('F#5', Q), ('G5', Q), ('E5', Q),
('D5', E), ('E5', E), ('F#5', Q), ('G5', H),
('D5', Q), ('B4', Q), ('C5', Q), ('D5', Q),
('E5', E), ('D5', E), ('C5', Q), ('B4', H),
]
# Closing theme — graceful resolution
closing = [
('G4', Q), ('A4', Q), ('B4', Q), ('C5', Q),
('D5', E), ('C5', E), ('B4', Q), ('A4', H),
('G4', Q), ('D5', Q), ('G5', Q), ('D5', Q),
('G5', E), ('F#5', E), ('E5', Q), ('D5', W),
# Final cadence
('G4', H), ('D5', Q), ('G5', Q), ('R', W),
]
# ── Servo choreography — elegant & classical ────────
def servo_dancer():
move(90) # centre on start
while state[0] != 'done':
sec = state[0]
if sec == 'exposition':
# Graceful bowing motion — matches classical elegance
for pos in [70, 110, 70, 110, 50, 130, 90]:
if state[0] != 'exposition': break
move(pos, 25)
time.sleep_ms(350)
elif sec == 'development':
# Lively, skipping steps — playful development
for pos in [80, 100, 60, 120, 80, 100, 90]:
if state[0] != 'development': break
move(pos, 18)
time.sleep_ms(220)
elif sec == 'recap':
# Returns to main bowing pattern with slight variation
for pos in [65, 115, 65, 115, 55, 125, 90]:
if state[0] != 'recap': break
move(pos, 22)
time.sleep_ms(320)
elif sec == 'bridge':
# Smooth, flowing arcs — transitional elegance
for a in range(90, 40, -2):
if state[0] != 'bridge': break
move(a, 30)
for a in range(40, 141, 2):
if state[0] != 'bridge': break
move(a, 30)
elif sec == 'closing':
# Slows down gracefully to resolution
for pos in [90, 75, 105, 75, 105, 90]:
if state[0] != 'closing': break
move(pos, 35)
time.sleep_ms(400)
move(90, 50)
else:
move(90, 80) # idle — hold centre
# ── Launch ─────────────────────────────────────────
_thread.start_new_thread(servo_dancer, ())
print("EINE KLEINE NACHTMUSIK — Mozart (K. 525)")
print("1st Movement: Allegro")
state[0] = 'exposition'; play_song(exposition)
state[0] = 'development';play_song(development)
state[0] = 'recap'; play_song(recap)
state[0] = 'bridge'; play_song(bridge)
state[0] = 'exposition'; play_song(exposition)
state[0] = 'development'; play_song(development)
state[0] = 'closing'; play_song(closing)
# ── Cleanup ────────────────────────────────────────
state[0] = 'done'
time.sleep_ms(500)
move(90)
buzzer.duty_u16(0)
buzzer.deinit()
servo.deinit()
print("Done.")