An Arduino-powered smart piano that allows users to play notes with buttons, change octaves with a potentiometer, and control sustain using light.
Devices and components
10 connecting wires 150 mm male
Arduino Uno Rev3
Breadboard - 400 contacts
Breadboard - 830 contacts
16x2 LCD screen with I²C interface
10K resistance
piezo speaker
100 ohm resistance
Push button
Photoresistor
Potentiometer
160 ohm resistance
1K resistance
Software and tools
Tinkercad
Project description
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 10, 11, 12, 13);
lcd.begin(16, 2);
const int buttonPins[8] = {
A1, A2, 2, 3, 4, 5, 6, 7
};
const String noteNames[8] = {
"C", "D", "E", "F", "G", "A", "B", "C5"
};
const int baseFrequencies[8] = {
262, 294, 330, 349, 392, 440, 494, 523
};
const int octavePotPin = A0;
int octaveValue = analogRead(octavePotPin);
Low octave
Middle octave
High octave
const int buzzerPin = A3;
tone(buzzerPin, currentFrequency);
noTone(buzzerPin);
const int lightSensorPin = A5;
int lightValue = analogRead(lightSensorPin);
bool sustainMode = lightValue > brightThreshold;
Arduino Piano
1#include <LiquidCrystal.h>
2
3/*
4 Arduino Smart Piano with Light-Controlled Sustain
5
6 Project purpose:
7 This project works like a small electronic piano.
8 The user presses push buttons to play different notes.
9 The LCD shows the note, octave, light mode, and sensor information.
10 The potentiometer changes the octave.
11 The photoresistor controls sustain mode.
12
13 LCD pins:
14 RS -> 8
15 E -> 9
16 D4 -> 10
17 D5 -> 11
18 D6 -> 12
19 D7 -> 13
20
21 Button note mapping:
22 C -> A1
23 D -> A2
24 E -> 2
25 F -> 3
26 G -> 4
27 A -> 5
28 B -> 6
29 High C -> 7
30
31 Buzzer:
32 Pin A3
33
34 Potentiometer:
35 Pin A0
36
37 Photoresistor:
38 Pin A5
39
40 Button behavior with pull-down resistors:
41 Not pressed = LOW
42 Pressed = HIGH
43
44 Photoresistor feature:
45 Bright light = sustain mode
46 Dark / covered photoresistor = normal mode
47*/
48
49// LCD object setup
50LiquidCrystal lcd(8, 9, 10, 11, 12, 13);
51
52// Pin constants
53const int buzzerPin = A3;
54const int octavePotPin = A0;
55const int lightSensorPin = A5;
56
57// Button pins
58const int buttonPins[8] = {
59 A1, A2, 2, 3, 4, 5, 6, 7
60};
61
62// Note names
63const String noteNames[8] = {
64 "C", "D", "E", "F", "G", "A", "B", "C5"
65};
66
67// Base note frequencies in Hertz
68const int baseFrequencies[8] = {
69 262, 294, 330, 349, 392, 440, 494, 523
70};
71
72// Button debounce variables
73const unsigned long debounceDelay = 25;
74int lastButtonReading[8];
75int stableButtonState[8];
76unsigned long lastDebounceTime[8];
77
78// Photoresistor / sustain variables
79const int brightThreshold = 600;
80const unsigned long sustainTime = 800;
81
82int lastFrequency = 0;
83String lastNoteName = "";
84bool sustaining = false;
85unsigned long sustainStartTime = 0;
86
87// State tracking variables
88int currentButton = -1;
89int previousButton = -99;
90int currentFrequency = 0;
91int previousFrequency = -1;
92
93// LCD update variables
94unsigned long lastIdleUpdate = 0;
95const unsigned long idleUpdateInterval = 700;
96
97String lastLine1 = "";
98String lastLine2 = "";
99
100void setup() {
101 // Start the LCD with 16 columns and 2 rows.
102 lcd.begin(16, 2);
103
104 // Set up the buzzer.
105 pinMode(buzzerPin, OUTPUT);
106 noTone(buzzerPin);
107
108 // Set up all piano buttons as inputs.
109 for (int i = 0; i < 8; i++) {
110 pinMode(buttonPins[i], INPUT);
111
112 lastButtonReading[i] = digitalRead(buttonPins[i]);
113 stableButtonState[i] = lastButtonReading[i];
114 lastDebounceTime[i] = 0;
115 }
116
117 // Startup message
118 showLCD("Arduino Piano", "Smooth Mode");
119 delay(1200);
120
121 // Short startup sound to confirm the buzzer works.
122 tone(buzzerPin, 440);
123 delay(120);
124 noTone(buzzerPin);
125 delay(80);
126 tone(buzzerPin, 523);
127 delay(120);
128 noTone(buzzerPin);
129
130 showLCD("Ready to play", "Press a key");
131}
132
133void loop() {
134 // Read analog sensors.
135 int octaveValue = analogRead(octavePotPin);
136 int lightValue = analogRead(lightSensorPin);
137
138 // Convert potentiometer value into octave setting.
139 int octaveShift = getOctaveShift(octaveValue);
140
141 // Bright light activates sustain mode.
142 bool sustainMode = lightValue > brightThreshold;
143
144 // Check whether a piano key is pressed.
145 currentButton = readPressedButton();
146
147 if (currentButton != -1) {
148 handleButtonPressed(currentButton, octaveShift, lightValue, sustainMode);
149 } else {
150 handleNoButton(octaveValue, lightValue, octaveShift, sustainMode);
151 }
152}
153
154void handleButtonPressed(int buttonIndex, int octaveShift, int lightValue, bool sustainMode) {
155 // Find the frequency for the selected note and octave.
156 currentFrequency = calculateFrequency(baseFrequencies[buttonIndex], octaveShift);
157
158 // Only restart the tone if the frequency changed.
159 if (currentFrequency != previousFrequency) {
160 tone(buzzerPin, currentFrequency);
161 previousFrequency = currentFrequency;
162 }
163
164 // Save this note in case sustain mode is used after release.
165 lastFrequency = currentFrequency;
166 lastNoteName = noteNames[buttonIndex];
167 sustaining = false;
168
169 // Update the LCD only when the selected button changes.
170 if (buttonIndex != previousButton) {
171 String line1 = "Note:" + noteNames[buttonIndex] + " " + String(currentFrequency) + "Hz";
172
173 String line2;
174
175 if (sustainMode) {
176 line2 = "Sustain ";
177 } else {
178 line2 = "Normal ";
179 }
180
181 line2 += octaveName(octaveShift);
182 line2 += " L:";
183 line2 += String(lightValue / 10);
184
185 showLCD(line1, line2);
186
187 previousButton = buttonIndex;
188 }
189}
190
191void handleNoButton(int octaveValue, int lightValue, int octaveShift, bool sustainMode) {
192 previousButton = -1;
193 previousFrequency = -1;
194
195 // If sustain mode is active, continue the last note briefly after release.
196 if (sustainMode && lastFrequency > 0) {
197 if (!sustaining) {
198 sustaining = true;
199 sustainStartTime = millis();
200 tone(buzzerPin, lastFrequency);
201 }
202
203 if (millis() - sustainStartTime < sustainTime) {
204 String line1 = "Sustain:" + lastNoteName;
205 String line2 = "Light:" + String(lightValue);
206 showLCD(line1, line2);
207 return;
208 }
209 }
210
211 // Stop sound when no key is pressed and sustain is not active.
212 noTone(buzzerPin);
213
214 lastFrequency = 0;
215 sustaining = false;
216
217 // Update idle screen slowly to reduce flickering.
218 if (millis() - lastIdleUpdate >= idleUpdateInterval) {
219 lastIdleUpdate = millis();
220
221 String line1;
222
223 if (sustainMode) {
224 line1 = "Mode:Sustain";
225 } else {
226 line1 = "Mode:Normal";
227 }
228
229 String line2 = "L:" + String(lightValue) + " Oct:" + octaveName(octaveShift);
230
231 showLCD(line1, line2);
232 }
233}
234
235int readPressedButton() {
236 /*
237 Returns:
238 0 to 7 = button index
239 -1 = no button pressed
240 */
241
242 for (int i = 0; i < 8; i++) {
243 int reading = digitalRead(buttonPins[i]);
244
245 // Reset debounce timer when a button reading changes.
246 if (reading != lastButtonReading[i]) {
247 lastDebounceTime[i] = millis();
248 lastButtonReading[i] = reading;
249 }
250
251 // Accept the reading after it has been stable.
252 if ((millis() - lastDebounceTime[i]) > debounceDelay) {
253 stableButtonState[i] = reading;
254 }
255
256 // With pull-down resistors, HIGH means pressed.
257 if (stableButtonState[i] == HIGH) {
258 return i;
259 }
260 }
261
262 return -1;
263}
264
265int getOctaveShift(int potValue) {
266 /*
267 Potentiometer range:
268 0-340 = low octave
269 341-681 = middle octave
270 682-1023 = high octave
271 */
272
273 if (potValue < 341) {
274 return -1;
275 } else if (potValue < 682) {
276 return 0;
277 } else {
278 return 1;
279 }
280}
281
282int calculateFrequency(int baseFrequency, int octaveShift) {
283 // Octaves are created by halving or doubling the base frequency.
284
285 if (octaveShift == -1) {
286 return baseFrequency / 2;
287 } else if (octaveShift == 1) {
288 return baseFrequency * 2;
289 } else {
290 return baseFrequency;
291 }
292}
293
294String octaveName(int octaveShift) {
295 if (octaveShift == -1) {
296 return "Low";
297 } else if (octaveShift == 1) {
298 return "High";
299 } else {
300 return "Mid";
301 }
302}
303
304void showLCD(String line1, String line2) {
305 /*
306 Updates the LCD smoothly by only changing text
307 when the message is different.
308 */
309
310 line1 = padTo16(line1);
311 line2 = padTo16(line2);
312
313 if (line1 != lastLine1) {
314 lcd.setCursor(0, 0);
315 lcd.print(line1);
316 lastLine1 = line1;
317 }
318
319 if (line2 != lastLine2) {
320 lcd.setCursor(0, 1);
321 lcd.print(line2);
322 lastLine2 = line2;
323 }
324}
325
326String padTo16(String text) {
327 /*
328 Pads text with spaces so old characters do not remain
329 on the LCD when a shorter message is printed.
330 */
331
332 if (text.length() > 16) {
333 text = text.substring(0, 16);
334 }
335
336 while (text.length() < 16) {
337 text += " ";
338 }
339
340 return text;
341}
Note: Content and images are from: https://projecthub.arduino.cc/, with some modifications.
If you want it removed due to copyright reasons, please leave a comment. Thank you.
I want to share this article more widely so that everyone knows about Arduino and your project.