QC

Arduino piano with light-controlled sustain

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.

Easy remote control

Remotely control an LED with ease. Devices and components Arduino Nano 33 BLE with headers Box 525 Resistors precision 1% - 17 values Breadb...