NanoPiano


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:

Piano.ai

 

Display.ai

 

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.)