Arduino Piano with Light-Controlled Sustain
An Arduino-powered smart piano that lets users play notes with buttons, change octaves with a potentiometer, and control sustain using light.
Devices & Components
20
10 jumper wires 150mm male
1
Arduino Uno Rev3
1
Breadboard - 400 contacts
1
Breadboard - 830 contacts
1
16x2 LCD display with I²C interface
1
10K Resistor
1
piezospeaker
1
100 Ohm Resistor
8
push Button
1
PhotoResistor
2
PotentioMeter
1
160 ohm Resistor
8
1K Resistor
Software & Tools
1
Tinkercad
Project description
Code
Arduino Piano
cpp
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}
Comments
Only logged in users can leave comments