Modulino Buzzer + UNO Q - Virtual piano
Create a virtual piano using the Arduino UNO Q and play tones through the Arduino Modulino Buzzer. Click the keys (or use your computer keyboard), and hear each note played by the piezo buzzer on your board.
Devices & Components
1
Modulino™ Buzzer
1
Arduino® UNO™ Q 2GB
Software & Tools
Arduino App Lab
Project description
Code
app
js
Javascript file
1var socket = io(); 2 3var statusEl = document.getElementById('status'); 4 5socket.on('connect', function () { statusEl.className = 'status connected'; statusEl.textContent = '● Connected'; }); 6socket.on('disconnect', function () { statusEl.className = 'status disconnected'; statusEl.textContent = '● Disconnected'; }); 7socket.on('connect_error', function () { statusEl.className = 'status connecting'; statusEl.textContent = 'Connecting…'; }); 8 9// ── Note definitions ────────────────────────────────────── 10// key: keyboard shortcut (lowercase), null = mouse only 11var NOTES = [ 12 // Octave 4 13 { name: 'C4', freq: 262, key: 'a', type: 'white' }, 14 { name: 'C#4', freq: 277, key: 'w', type: 'black' }, 15 { name: 'D4', freq: 294, key: 's', type: 'white' }, 16 { name: 'D#4', freq: 311, key: 'e', type: 'black' }, 17 { name: 'E4', freq: 330, key: 'd', type: 'white' }, 18 { name: 'F4', freq: 349, key: 'f', type: 'white' }, 19 { name: 'F#4', freq: 370, key: 't', type: 'black' }, 20 { name: 'G4', freq: 392, key: 'g', type: 'white' }, 21 { name: 'G#4', freq: 415, key: 'y', type: 'black' }, 22 { name: 'A4', freq: 440, key: 'h', type: 'white' }, 23 { name: 'A#4', freq: 466, key: 'u', type: 'black' }, 24 { name: 'B4', freq: 494, key: 'j', type: 'white' }, 25 // Octave 5 26 { name: 'C5', freq: 523, key: 'k', type: 'white' }, 27 { name: 'C#5', freq: 554, key: 'o', type: 'black' }, 28 { name: 'D5', freq: 587, key: 'l', type: 'white' }, 29 { name: 'D#5', freq: 622, key: 'p', type: 'black' }, 30 { name: 'E5', freq: 659, key: ';', type: 'white' }, 31 { name: 'F5', freq: 698, key: null, type: 'white' }, 32 { name: 'F#5', freq: 740, key: null, type: 'black' }, 33 { name: 'G5', freq: 784, key: null, type: 'white' }, 34 { name: 'G#5', freq: 831, key: null, type: 'black' }, 35 { name: 'A5', freq: 880, key: null, type: 'white' }, 36 { name: 'A#5', freq: 932, key: null, type: 'black' }, 37 { name: 'B5', freq: 988, key: null, type: 'white' }, 38]; 39 40// Build a shortcut → note lookup 41var keyMap = {}; 42NOTES.forEach(function (note) { 43 if (note.key) keyMap[note.key] = note; 44}); 45 46// ── Build piano DOM ──────────────────────────────────────── 47var WHITE_KEY_W = 48; // must match --white-key-w in CSS 48var BLACK_KEY_W = 30; // must match --black-key-w in CSS 49 50var pianoEl = document.getElementById('piano'); 51var keyElements = {}; // name → DOM element 52 53var whiteCount = 0; 54 55NOTES.forEach(function (note) { 56 var el = document.createElement('div'); 57 el.dataset.freq = note.freq; 58 el.dataset.name = note.name; 59 60 var labelHtml = '<span class="note-label">' + note.name + '</span>'; 61 var shortcutHtml = note.key 62 ? '<span class="key-shortcut">' + note.key.toUpperCase() + '</span>' 63 : ''; 64 65 if (note.type === 'white') { 66 el.className = 'white-key'; 67 el.innerHTML = labelHtml + shortcutHtml; 68 pianoEl.appendChild(el); 69 whiteCount++; 70 } else { 71 el.className = 'black-key'; 72 el.innerHTML = labelHtml + shortcutHtml; 73 // Position: centered on the boundary between the two surrounding white keys 74 var leftPx = whiteCount * WHITE_KEY_W - BLACK_KEY_W / 2; 75 el.style.left = leftPx + 'px'; 76 pianoEl.appendChild(el); 77 } 78 79 keyElements[note.name] = el; 80}); 81 82// ── Note playback ────────────────────────────────────────── 83var HIGHLIGHT_MS = 300; // matches buzzer duration in sketch 84var highlightTimers = {}; 85 86function playNote(note) { 87 socket.emit('play_note', { frequency: note.freq }); 88 89 // Visual highlight 90 var el = keyElements[note.name]; 91 if (!el) return; 92 93 el.classList.add('active'); 94 95 clearTimeout(highlightTimers[note.name]); 96 highlightTimers[note.name] = setTimeout(function () { 97 el.classList.remove('active'); 98 }, HIGHLIGHT_MS); 99} 100 101// ── Mouse / touch events ─────────────────────────────────── 102pianoEl.addEventListener('mousedown', function (e) { 103 var el = e.target.closest('[data-freq]'); 104 if (!el) return; 105 var note = NOTES.find(function (n) { return n.name === el.dataset.name; }); 106 if (note) playNote(note); 107}); 108 109// ── Keyboard events ──────────────────────────────────────── 110var pressedKeys = {}; 111 112document.addEventListener('keydown', function (e) { 113 if (e.repeat) return; // prevent key-repeat spam 114 var key = e.key === ';' ? ';' : e.key.toLowerCase(); 115 if (pressedKeys[key]) return; 116 var note = keyMap[key]; 117 if (!note) return; 118 pressedKeys[key] = true; 119 playNote(note); 120}); 121 122document.addEventListener('keyup', function (e) { 123 var key = e.key === ';' ? ';' : e.key.toLowerCase(); 124 delete pressedKeys[key]; 125});
main
python
python file
1import json 2import os 3 4from arduino.app_utils import App, Bridge 5from arduino.app_bricks.web_ui import WebUI 6 7_ui_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "ui") 8ui = WebUI(assets_dir_path=_ui_dir) 9 10 11def _call_mcu(method, *args): 12 try: 13 return json.loads(Bridge.call(method, *args)) 14 except Exception as exc: 15 print(f"[bridge] {method} error: {exc}") 16 return None 17 18 19def on_play_note(sid, data): 20 frequency = int(data.get("frequency", 440)) 21 _call_mcu("play_note", frequency) 22 23 24ui.on_message("play_note", on_play_note) 25 26App.run()
sketch
cpp
Arduino sketch
1#include <Arduino_RouterBridge.h> 2#include <Arduino_Modulino.h> 3 4ModulinoBuzzer buzzer; 5 6String rpc_play_note(int frequency) { 7 buzzer.tone(frequency, 300); 8 return "{\"status\":\"ok\"}"; 9} 10 11void setup() { 12 Bridge.begin(); 13 Modulino.begin(); 14 buzzer.begin(); 15 Bridge.provide("play_note", rpc_play_note); 16} 17 18void loop() { 19 // Bridge handles RPC on a background thread; nothing needed here. 20}
styles
css
Style file
1:root { 2 --bg: #0f1117; 3 --surface: #1c1f2a; 4 --border: #2e3244; 5 --accent: #4fa3e0; 6 --text: #e2e8f0; 7 --muted: #8892a4; 8 --green: #22c55e; 9 --red: #ef4444; 10 --radius: 10px; 11 12 --white-key-w: 48px; 13 --white-key-h: 160px; 14 --black-key-w: 30px; 15 --black-key-h: 100px; 16} 17 18* { box-sizing: border-box; margin: 0; padding: 0; } 19 20body { 21 font-family: 'Segoe UI', system-ui, sans-serif; 22 background: var(--bg); 23 color: var(--text); 24 min-height: 100vh; 25 padding: 1rem; 26} 27 28/* ── Header ─────────────────────────────────────────────── */ 29header { 30 display: flex; 31 flex-direction: column; 32 align-items: center; 33 gap: 0.5rem; 34 margin-bottom: 1.5rem; 35 text-align: center; 36} 37 38header h1 { font-size: 1.35rem; letter-spacing: -0.02em; } 39 40.status { 41 font-size: 0.8rem; 42 font-weight: 600; 43 padding: 0.25rem 0.75rem; 44 border-radius: 999px; 45 letter-spacing: 0.04em; 46 text-transform: uppercase; 47} 48 49.status.connected { background: var(--green); color: #fff; } 50.status.disconnected { background: var(--red); color: #fff; } 51.status.connecting { background: var(--muted); color: #fff; } 52 53/* ── Layout ─────────────────────────────────────────────── */ 54main { display: flex; flex-direction: column; gap: 1.25rem; } 55 56.card { 57 background: var(--surface); 58 border: 1px solid var(--border); 59 border-radius: var(--radius); 60 padding: 1.25rem 1.5rem; 61} 62 63.card h2 { 64 font-size: 0.95rem; 65 font-weight: 600; 66 color: var(--muted); 67 text-transform: uppercase; 68 letter-spacing: 0.06em; 69 margin-bottom: 1rem; 70} 71 72/* ── Shortcuts ───────────────────────────────────────────── */ 73.shortcut-grid { display: flex; flex-direction: column; gap: 0.4rem; } 74 75.shortcut-row { 76 display: flex; 77 align-items: center; 78 gap: 1rem; 79 font-size: 0.85rem; 80} 81 82.shortcut-label { color: var(--muted); min-width: 210px; } 83 84.shortcut-keys { 85 font-family: 'Courier New', monospace; 86 color: var(--accent); 87 letter-spacing: 0.05em; 88} 89 90/* ── Piano scroll wrapper ────────────────────────────────── */ 91.piano-scroll { 92 overflow-x: auto; 93 padding-bottom: 0.5rem; 94} 95 96/* ── Piano container ─────────────────────────────────────── */ 97#piano { 98 position: relative; 99 display: flex; 100 height: var(--white-key-h); 101 user-select: none; 102} 103 104/* ── White keys ──────────────────────────────────────────── */ 105.white-key { 106 position: relative; 107 width: var(--white-key-w); 108 height: var(--white-key-h); 109 background: #f0f0f0; 110 border: 1px solid #999; 111 border-radius: 0 0 6px 6px; 112 cursor: pointer; 113 display: flex; 114 flex-direction: column; 115 justify-content: flex-end; 116 align-items: center; 117 padding-bottom: 8px; 118 gap: 2px; 119 transition: background 0.05s; 120 flex-shrink: 0; 121 z-index: 1; 122} 123 124.white-key:hover { background: #e0eeff; } 125.white-key.active { background: var(--accent); } 126 127/* ── Black keys ──────────────────────────────────────────── */ 128.black-key { 129 position: absolute; 130 width: var(--black-key-w); 131 height: var(--black-key-h); 132 background: #222; 133 border: 1px solid #111; 134 border-radius: 0 0 4px 4px; 135 cursor: pointer; 136 z-index: 2; 137 display: flex; 138 flex-direction: column; 139 justify-content: flex-end; 140 align-items: center; 141 padding-bottom: 6px; 142 gap: 2px; 143 transition: background 0.05s; 144 top: 0; 145} 146 147.black-key:hover { background: #3a3a3a; } 148.black-key.active { background: #1a5fa0; } 149 150/* ── Key labels ──────────────────────────────────────────── */ 151.note-label { 152 font-size: 0.55rem; 153 font-weight: 600; 154 color: #666; 155 text-align: center; 156 line-height: 1; 157} 158 159.white-key.active .note-label { color: #fff; } 160 161.black-key .note-label { 162 color: #aaa; 163 font-size: 0.5rem; 164} 165 166.black-key.active .note-label { color: #fff; } 167 168.key-shortcut { 169 font-size: 0.6rem; 170 font-weight: 700; 171 color: var(--accent); 172 text-transform: uppercase; 173 line-height: 1; 174} 175 176.black-key .key-shortcut { 177 font-size: 0.5rem; 178 color: #7ab8e0; 179} 180 181.white-key.active .key-shortcut, 182.black-key.active .key-shortcut { color: #fff; } 183 184/* ── Responsive ──────────────────────────────────────────── */ 185@media (min-width: 600px) { 186 main { max-width: 900px; margin: 0 auto; } 187} 188 189/* ── Centering ───────────────────────────────────────────── */ 190body { display: flex; flex-direction: column; align-items: center; } 191 192header { width: 100%; max-width: 900px; } 193 194main { width: 100%; align-items: center; } 195 196.piano-scroll { display: flex; justify-content: center; } 197 198.shortcut-grid { align-items: center; }
index
markup
HTML file
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>🎹 Virtual Piano</title> 7 <link rel="stylesheet" href="styles.css" /> 8</head> 9<body> 10 <header> 11 <h1>🎹 Modulino Buzzer - Virtual Piano</h1> 12 <span id="status" class="status connecting">Connecting…</span> 13 </header> 14 15 <main> 16 <section class="card"> 17 <h2>Keyboard Shortcuts</h2> 18 <div class="shortcut-grid"> 19 <div class="shortcut-row"> 20 <span class="shortcut-label">White keys (Octave 4):</span> 21 <span class="shortcut-keys">A S D F G H J</span> 22 </div> 23 <div class="shortcut-row"> 24 <span class="shortcut-label">Black keys (Octave 4):</span> 25 <span class="shortcut-keys">W E T Y U</span> 26 </div> 27 <div class="shortcut-row"> 28 <span class="shortcut-label">White keys (Octave 5, partial):</span> 29 <span class="shortcut-keys">K L ;</span> 30 </div> 31 <div class="shortcut-row"> 32 <span class="shortcut-label">Black keys (Octave 5, partial):</span> 33 <span class="shortcut-keys">O P</span> 34 </div> 35 </div> 36 </section> 37 38 <section class="card piano-card"> 39 <h2>Piano</h2> 40 <div class="piano-scroll"> 41 <div id="piano"></div> 42 </div> 43 </section> 44 </main> 45 46 <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script> 47 <script src="app.js"></script> 48</body> 49</html>
Arduino App Lab
Modulino Buzzer - Virtual Piano
A 2-octave virtual piano that plays notes on the Modulino Buzzer
🎹
Modulino Buzzer - Virtual Piano
Comments
Only logged in users can leave comments