The Nano Piano:
I first got the idea by watching my dorm mates playing the large piano in the dorm. I live on the first floor os I always hear the piano whenever it is being played, even when I'm trying to sleep. Now I would love to learn to play the piano, but I would feel bad practicing on the large piano where everyone could hear my awful playing. So it inspired me to build a small scale piano to help me practice.
Design:
Prototype:
Schematics of the different circuits:
Mp3->4050->Arduino
LCD -> Arduino
Switches-> shift registers
Final circuit:
Mechanics:
Code:
Resources used:
http://www.gammon.com.au/forum/?id=11979 for the shift registers
https://www.sparkfun.com/Code/MIDI_Example.pde for Midi player
#include <avr/pgmspace.h>
#include <SPI.h>
#include <VS1053.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(5, 6); //Soft TX on 3, we don't use RX in this code
VS1053 player(A0, A1, A2, -1);
VS1053::RtMidi midi(player);
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
#include <bitBangedSPI.h>
bitBangedSPI bbSPI (bitBangedSPI::NO_PIN, 9, 10); // MOSI, MISO, SCK
const double sensorMin = 0;
const double sensorMax = 1024;
int volPin = A3;
int octPin = A4;
int instrPin = A5;
const int numReadings = 20;
int readingVol[numReadings]; // the readings from the analog input
int index = 0; // the index of the current reading
int totalVol = 0; // the running total
int averageVol = 0;
int readingInstr[numReadings];
int totalInstr = 0;
int averageInstr = 0;
const byte LATCH = 8;
byte optionSwitch1;
byte oldOptionSwitch1; // previous state
byte optionSwitch2;
byte oldOptionSwitch2;
byte optionSwitch3;
byte oldOptionSwitch3;
byte vol = 0x07;
int oct = 0;
int onvel = 60;
int offvel = 60;
#define encoder0PinA 0
#define encoder0PinB 1
byte instr = 0;
uint8_t notes[6][17] =
{
{24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40},
{36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52},
{48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64},
{60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76},
{72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88},
{84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100}
};
byte resetMIDI = -1; //Tied to VS1053 Reset line
byte ledPin = 13; //MIDI traffic inidicator
void setup(){
bbSPI.begin ();
Serial.begin (115200);
mySerial.begin(31250);
delay(2000);
pinMode(resetMIDI, OUTPUT);
digitalWrite(resetMIDI, LOW);
delay(100);
digitalWrite(resetMIDI, HIGH);
delay(100);
lcd.begin(16, 2);
pinMode (LATCH, OUTPUT);
digitalWrite (LATCH, HIGH);
SPI.begin();
player.begin();
midi.begin();
player.setVolume(0x0);
midi.selectDrums(0);
updateScreen();
}
void loop(){
talkMIDI(0xB0, 0x07, vol);
talkMIDI(0xB0, 0, 0x00); //Default bank GM1
talkMIDI(0xC0, instr, 0); //Set instrument number. 0xC0 is a 1 data byte command
digitalWrite (LATCH, LOW); // pulse the parallel load latch
digitalWrite (LATCH, HIGH);
optionSwitch1 = bbSPI.transfer (0);
optionSwitch2 = bbSPI.transfer (0);
optionSwitch3 = bbSPI.transfer (0);
byte mask = 1;
for (int i = 0; i <= 7; i++)
{
if ((optionSwitch3 & mask) != (oldOptionSwitch3 & mask))
{
Serial.print ("Switch 1:");
Serial.print (i);
Serial.print (" now ");
Serial.println ((optionSwitch3 & mask) ? "closed" : "open");
(optionSwitch3 & mask) ? noteOn(0,notes[oct][i],onvel) :noteOff(0,notes[oct][i],offvel);
} // end of bit has changed
mask <<= 1;
} // end of for each bit
oldOptionSwitch3 = optionSwitch3;
delay(20);
mask = 1;
for (int i = 6; i <= 13; i++)
{
if ((optionSwitch2 & mask) != (oldOptionSwitch2 & mask))
{
Serial.print ("Switch 2:");
Serial.print (i);
Serial.print (" now ");
Serial.println ((optionSwitch2 & mask) ? "closed" : "open");
(optionSwitch2 & mask) ? noteOn(0,notes[oct][i],onvel) :noteOff(0,notes[oct][i],offvel);
} // end of bit has changed
mask <<= 1;
} // end of for each bit
oldOptionSwitch2 = optionSwitch2;
delay (20); // debounce
mask = 1;
for (int i = 12; i <= 19; i++)
{
if ((optionSwitch1 & mask) != (oldOptionSwitch1 & mask))
{
Serial.print ("Switch 3:");
Serial.print (i);
Serial.print (" now ");
Serial.println ((optionSwitch1 & mask) ? "closed" : "open");
(optionSwitch1 & mask) ? noteOn(0,notes[oct][i],onvel) :noteOff(0,notes[oct][i],offvel);
} // end of bit has changed
mask <<= 1;
} // end of for each bit
oldOptionSwitch1 = optionSwitch1;
delay (20);
totalVol= totalVol - readingVol[index];
// read from the sensor:
readingVol[index] = analogRead(volPin);
// add the reading to the total:
totalVol= totalVol + readingVol[index];
// calculate the average:
averageVol = totalVol / numReadings;
totalInstr = totalInstr - readingInstr[index];
// read from the sensor:
readingInstr[index] = analogRead(instrPin);
// add the reading to the total:
totalInstr= totalInstr + readingInstr[index];
// advance to the next position in the array:
index = index + 1;
// if we're at the end of the array...
if (index >= numReadings)
// ...wrap around to the beginning:
index = 0;
// calculate the average:
averageInstr = totalInstr / numReadings;
byte nvol = map(averageVol, sensorMin,sensorMax, 127 , 0);
int noct = map(analogRead(octPin), sensorMin, sensorMax, 5 , 0);
byte ninstr = map(averageInstr,sensorMin,sensorMax, 127, -1);
if(nvol != vol || noct != oct||ninstr != instr){
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
// If interrupts come faster than 200ms, assume it's a bounce and ignore
if (interrupt_time - last_interrupt_time > 200)
{
vol = nvol;
oct = noct;
instr = ninstr;
updateScreen();
}
last_interrupt_time = interrupt_time;
}
}
void updateScreen(){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Instr");
lcd.setCursor(7,0);
lcd.print("Oct");
lcd.setCursor(12,0);
lcd.print("Vol");
lcd.setCursor(0,1);
lcd.print(instr);
lcd.setCursor(7,1);
lcd.print(oct);
lcd.setCursor(12,1);
lcd.print(vol);
delay(250);
}
//Send a MIDI note-on message. Like pressing a piano key
//channel ranges from 0-15
void noteOn(byte channel, byte note, byte attack_velocity) {
talkMIDI( (0x90 | channel), note, attack_velocity);
}
//Send a MIDI note-off message. Like releasing a piano key
void noteOff(byte channel, byte note, byte release_velocity) {
talkMIDI( (0x80 | channel), note, release_velocity);
}
//Plays a MIDI note. Doesn't check to see that cmd is greater than 127, or that data values are less than 127
void talkMIDI(byte cmd, byte data1, byte data2) {
digitalWrite(ledPin, HIGH);
mySerial.write(cmd);
mySerial.write(data1);
//Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes
//(sort of: http://253.ccarh.org/handout/midiprotocol/)
if( (cmd & 0xF0) <= 0xB0)
mySerial.write(data2);
digitalWrite(ledPin, LOW);
}
Final Product!
( The speakers in the second part were very soft for some reason, but you can still hear it.)