Piano Tombol Raspberry Pi Pico (5 Tombol + Buzzer + Perekam Melodi)
Piano mini lima tuts yang mengingat apa yang kamu mainkan! Raspberry Pi Pico memainkan DO, RE, MI, FA, dan SOL di buzzer pasif dari lima tombol, selama tutsnya ditahan. Tahan DO selama satu detik, mainkan lagumu, lalu tahan DO lagi: Pico memutarnya kembali, memperpendek jeda antarnada yang lebih dari 1,5 detik menjadi 1,5 detik. Indikator rekamnya LED bawaan board, jadi tanpa komponen tambahan. Nada PWM, debounce, dan waktu non-blocking dalam satu proyek.

Video
Skema rangkaian

Kode sumber
1# ─────────────────────────────────────────
2# BlueGrays · bluegrays.com
3# Piano Tombol Raspberry Pi Pico (5 Tombol + Buzzer + Perekam Melodi)
4# ─────────────────────────────────────────
5# Piano mini lima tombol: DO, RE, MI, FA, SOL. Nada berbunyi selama
6# tombolnya ditahan. Tahan DO selama 1 s: perekaman dimulai dan nada
7# disimpan beserta timing-nya. Tahan DO selama 1 s lagi: perekaman
8# berhenti dan melodi diputar ulang sesuai ritmenya.
9#
10# Pin:
11# GP7 -> Tombol DO (kaki lainnya ke GND)
12# GP8 -> Tombol RE (kaki lainnya ke GND)
13# GP9 -> Tombol MI (kaki lainnya ke GND)
14# GP10 -> Tombol FA (kaki lainnya ke GND)
15# GP11 -> Tombol SOL (kaki lainnya ke GND)
16# GP17 -> Buzzer (+), kaki lainnya ke GND
17# LED -> LED onboard: rekam / putar
18
19from machine import Pin, PWM
20import time
21
22# ============ PENGATURAN ============
23NOTE_PINS = (7, 8, 9, 10, 11)
24NOTE_NAMES = ("DO", "RE", "MI", "FA", "SOL")
25NOTE_HZ = (523, 587, 659, 698, 784) # C5 D5 E5 F5 G5 (Hz)
26VOLUME = 32768 # Duty PWM, 32768 = 50%
27
28REC_KEY = 0 # Tombol rekam: DO
29LONG_PRESS_MS = 1000 # Waktu tahan untuk merekam
30DEBOUNCE_MS = 20 # Debounce tombol
31MAX_NOTES = 64 # Maks. nada dalam satu rekaman
32MAX_GAP_MS = 1500 # Diam terlama dalam pemutaran
33PLAY_DELAY_MS = 500 # Jeda sebelum pemutaran
34REC_BLINK_MS = 500 # Kedip LED selama merekam
35LOOP_MS = 2 # Langkah loop utama
36
37FREE, REC, PLAY = 0, 1, 2 # Mode
38
39# ============ HARDWARE ============
40buzzer = PWM(Pin(17))
41buzzer.duty_u16(0)
42try:
43 led = Pin("LED", Pin.OUT) # LED onboard
44except (TypeError, ValueError):
45 led = Pin(25, Pin.OUT) # Firmware lama
46led.value(0)
47
48
49class Key:
50 # Tombol dengan debounce; update() mengembalikan +1 ketika ditekan, -1 ketika dilepas
51 def __init__(self, gpio):
52 self.pin = Pin(gpio, Pin.IN, Pin.PULL_UP)
53 self.raw = 1
54 self.stable = 1
55 self.changed = time.ticks_ms()
56 self.down_at = 0
57 self.used = False # Sudah dipakai oleh perintah
58
59 def update(self, now):
60 raw = self.pin.value()
61 if raw != self.raw:
62 self.raw = raw
63 self.changed = now
64 if raw != self.stable and time.ticks_diff(now, self.changed) >= DEBOUNCE_MS:
65 self.stable = raw
66 if raw == 0:
67 self.down_at = now
68 self.used = False
69 return 1
70 return -1
71 return 0
72
73 def held_ms(self, now):
74 return time.ticks_diff(now, self.down_at) if self.stable == 0 else 0
75
76
77keys = [Key(p) for p in NOTE_PINS]
78
79
80# ============ SUARA ============
81def tone(hz):
82 buzzer.freq(hz)
83 buzzer.duty_u16(VOLUME)
84
85
86def silence():
87 buzzer.duty_u16(0)
88
89
90def chirp(*steps):
91 # Bunyi sinyal pendek: pasangan (Hz, ms); 0 Hz = diam
92 for hz, ms in steps:
93 if hz:
94 tone(hz)
95 else:
96 silence()
97 time.sleep_ms(ms)
98 silence()
99
100
101# ============ STATUS ============
102mode = FREE
103held = [] # Nada yang ditahan, yang terbaru di akhir
104rec_notes = [] # Rekaman: [tombol, ms mulai, panjang ms]
105rec_t0 = 0
106play_list = [] # Pemutaran: (waktu ms, tombol, panjang ms)
107play_index = 0
108play_start = 0
109play_end = 0
110play_sounding = False
111
112
113def update_sound():
114 # Buzzer memainkan satu nada: tombol yang terakhir ditekan yang menang
115 if held:
116 tone(NOTE_HZ[held[-1]])
117 else:
118 silence()
119
120
121def start_recording(now):
122 global mode, rec_notes, rec_t0
123 mode = REC
124 rec_notes = []
125 rec_t0 = now
126 chirp((880, 70), (0, 40), (1319, 110)) # Sinyal naik
127 print("Kayit basladi / Recording started (en fazla / max %d nota / notes)" % MAX_NOTES)
128
129
130def build_play_list():
131 # Urutkan nada menurut waktu mulai, geser yang pertama ke 0, perpendek diam yang lama
132 notes = sorted(rec_notes, key=lambda n: n[1])
133 result = []
134 t = 0
135 for i, (k, start, length) in enumerate(notes):
136 if i > 0:
137 prev_k, prev_start, prev_len = notes[i - 1]
138 t += min(start - prev_start, prev_len + MAX_GAP_MS)
139 result.append((t, k, length))
140 return result
141
142
143def stop_recording(now):
144 global mode, play_list, play_index, play_start, play_end, play_sounding
145 led.value(0)
146 chirp((1319, 70), (0, 40), (880, 110)) # Sinyal turun
147 if not rec_notes:
148 mode = FREE
149 print("Kayit bos / Recording is empty")
150 chirp((220, 300))
151 return
152 play_list = build_play_list()
153 names = " ".join(NOTE_NAMES[n[1]] for n in play_list)
154 print("Kayit bitti / Recording stopped: %d nota / notes" % len(play_list))
155 print("Melodi / Melody:", names)
156 mode = PLAY
157 play_index = 0
158 play_end = 0
159 play_sounding = False
160 play_start = time.ticks_add(time.ticks_ms(), PLAY_DELAY_MS)
161 print("Geri calma / Playback...")
162
163
164def finish_playback(message):
165 global mode
166 mode = FREE
167 silence()
168 led.value(0)
169 print(message)
170
171
172def update_playback(now):
173 # Pemutaran non-blocking: mulai nada ketika waktunya tiba, diamkan ketika selesai
174 global play_index, play_end, play_sounding
175 elapsed = time.ticks_diff(now, play_start)
176 if elapsed < 0:
177 return
178 while play_index < len(play_list) and elapsed >= play_list[play_index][0]:
179 t, k, length = play_list[play_index]
180 tone(NOTE_HZ[k])
181 led.value(1) # LED menyala bersama nada
182 play_end = t + length
183 play_sounding = True
184 play_index += 1
185 if play_sounding and elapsed >= play_end:
186 silence()
187 led.value(0)
188 play_sounding = False
189 if not play_sounding and play_index >= len(play_list):
190 finish_playback("Bitti / Done. DO 1 sn = yeni kayit / DO 1 s = new recording")
191
192
193def on_press(k, now):
194 if mode == PLAY:
195 # Tombol apa pun menghentikan pemutaran
196 keys[k].used = True
197 finish_playback("Durduruldu / Stopped")
198 return
199 held.append(k)
200 update_sound()
201 tag = "[REC] " if mode == REC else ""
202 print("%sNota / Note: %s (%d Hz)" % (tag, NOTE_NAMES[k], NOTE_HZ[k]))
203
204
205def on_release(k, now):
206 if k in held:
207 held.remove(k)
208 update_sound()
209 if keys[k].used or mode != REC:
210 return
211 start = time.ticks_diff(keys[k].down_at, rec_t0)
212 length = time.ticks_diff(now, keys[k].down_at)
213 rec_notes.append([k, start, length])
214 if len(rec_notes) >= MAX_NOTES:
215 print("Kayit dolu / Recording full")
216 stop_recording(now)
217
218
219def on_long_press(k, now):
220 # Tekan lama DO: mulai atau hentikan perekaman. Nada DO yang ditahan tidak direkam.
221 keys[k].used = True
222 if k in held:
223 held.remove(k)
224 update_sound()
225 if mode == FREE:
226 start_recording(now)
227 elif mode == REC:
228 stop_recording(now)
229
230
231# ============ LOOP UTAMA ============
232print("== BlueGrays · bluegrays.com ==")
233print("Pico Buton Piyano / Button Piano")
234print("Tuslar / Keys: DO RE MI FA SOL")
235print("DO 1 sn basili = kayit baslat / bitir + geri cal")
236print("Hold DO 1 s = start / stop recording + play back")
237chirp((NOTE_HZ[0], 90), (NOTE_HZ[2], 90), (NOTE_HZ[4], 90), (1047, 180)) # Awal
238
239while True:
240 now = time.ticks_ms()
241 for i, key in enumerate(keys):
242 event = key.update(now)
243 if event == 1:
244 on_press(i, now)
245 elif event == -1:
246 on_release(i, now)
247
248 rec_key = keys[REC_KEY]
249 if (mode != PLAY and not rec_key.used
250 and rec_key.held_ms(now) >= LONG_PRESS_MS):
251 on_long_press(REC_KEY, now)
252
253 if mode == PLAY:
254 update_playback(now)
255 elif mode == REC:
256 # LED berkedip selama merekam
257 led.value(1 if (time.ticks_diff(now, rec_t0) // REC_BLINK_MS) % 2 == 0 else 0)
258
259 time.sleep_ms(LOOP_MS)
260FAQ
Bagaimana cara merekam melodi dan memutarnya kembali?
Tahan DO selama satu detik: dua bunyi bip naik berarti perekaman dimulai. Mainkan lagumu, lalu tahan DO satu detik lagi. Setelah dua bunyi bip turun, melodi diputar kembali; jeda di antara nada yang lebih dari 1,5 detik diperpendek menjadi 1,5 detik. Menekan tuts apa pun selama pemutaran akan menghentikannya.
Apakah aku perlu menambahkan LED terpisah untuk indikator rekam?
Tidak. Kode menggerakkan LED di board lewat `Pin("LED")`, yaitu GP25 di Pico, dan juga bekerja di Pico W. LED berkedip setiap setengah detik selama merekam dan menyala bersama setiap nada selama pemutaran.
Bagaimana cara mengubah nada atau oktafnya?
Frekuensinya ada di `NOTE_HZ`: 523, 587, 659, 698, dan 784 Hz. Bagi semuanya dua untuk satu oktaf lebih rendah, atau kalikan dua untuk satu oktaf lebih tinggi. Untuk menambahkan tuts keenam, tambahkan pin, nama, dan frekuensi baru (880 untuk LA) ke `NOTE_PINS`, `NOTE_NAMES`, dan `NOTE_HZ`.
Bagaimana cara memainkan piano di Wokwi?
Mulai simulasi dan klik rangkaian sekali. Tombol 1 sampai 5 di keyboard memainkan DO sampai SOL, dan menahan tombol membuat nadanya lebih panjang. Untuk merekam, tahan 1 sekitar satu detik; nada yang kamu mainkan dan melodi yang terekam muncul di serial monitor.
