Industrial manufacturing
Industrial Internet of Things | Industrial materials | Equipment Maintenance and Repair | Industrial programming |
home  MfgRobots >> Industrial manufacturing >  >> Manufacturing Technology >> Manufacturing process

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

Components and supplies

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance
Arduino 101
×1
Texas Instruments 74HC595
×11
IRFZ44N
N-Channel Mosfet
×88
12V Battery
×2
PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance
Capacitor 100 µF
×11
PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance
OpenBuilds Wire Cable - By the Foot
a lot of them.
×1
PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance
Breadboard (generic)
Or generic prototype board. Just about anything that will put things together.
×11
8 legs IC socket
Optional if you're confident that you won't burn ANY of these expensive 595
×11
PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance
Resistor 10k ohm
Not used for this project but better to have for each mosfet
×88
PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance
1N4007 – High Voltage, High Current Rated Diode
Not used for this project but better to have for each mosfet
×88

Necessary tools and machines

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance
Hot glue gun (generic)

Apps and online services

Processing
Python
Binary MIDI file to text

About this project

How it begin:

Many years ago, Yamaha introduced automated piano. Young and innocent me saw that piano playing music behind glass window of an instrument shop.

Enough of small talks, there really isn't big reason why I started this project besides I just wanted to.

Overview:

A single board of Arduino Mega costs about $40 and two will be required to control 88 solenoids. That's quite expensive. Instead, get a cheap Arduino Uno and 11 of shift-register. Shift Register is a method to control many outputs (usually LEDs) with small number of output pins. Essentially, it's one Arduino with 11 shift registers and control 88 Solenoids.

Since we're using Shift registers, a PC will send a set of bits to Arduino instead of MIDI com. MIDI file will be translated into set of bits before hand.

Hardware:

When I got the solenoids straight from China, I found out that these solenoids are not strong enough to push piano keys. Of course pushing piano keys from inner most spot takes more force but I thought it was the best method that doesn't wreck a piano. Eventually I pushed 24V through 12V solenoids to get enough power.

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

88 Solenoids consume a lot of power and because I can't go and buy an expensive PSU, I decided go with my dad's car battery. (Guess he won't be going anywhere now!)

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

With that out of the way, each one of shift registers and MOSFETs will go on a controller board.

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

595 on the right with a socket in case I burn it. (Which I did once.) Circuit Diagram is exactly same as example 2 from here. Replace LEDs with MOSFET gate. As you can see, there's no pull down resistor because extra resistors will bring the cost up and soldering them on that board will melt my fingers. On a bit more serious side, these MOSFETs will open at 5V and close under 4V or so. Confirmed it through countless hours of testing. (Not theoretically correct. Do not listen to me.)

Lastly, get a peice of plastic plate to glue solenoids on to. Using hot glue and plastic plate is a bad idea considering that it will get hot, but it's the best I can afford.

And then run one side of solenoid wires to the positive side of the battery.

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

Software:

The very first step is to get a midi file.

The second step is to get the midi into the text form. This can be done on this handy website: http://flashmusicgames.com/midi/mid2txt.php.

For the sake of simplicity, ignore time signature, tempo, and par. Tempo can be multiplied to the time later. Essentially you want a file like this:

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

Now, use this to create 11 sets of 8 bit data with time by running it through the Python code (attached).

PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

They are ready to be sent to Arduino through processing COM.

See the attachment to figure out how processing sends these data and how Arduino handles them.

*Note: My coding habits are bad and it might be difficult to read these. Processing sends data from the right to left because Arduino pushes data to right on the physical piano.

This thing working:

Troubleshooting:

If a professional engineer were to see this post, he will think that this entire system will have many problems. And there are many problems.

Since the solenoids were hot glued to the plate, solenoids overheating and melting the hot glue was a big problem. The solution was to simply remove them and replace it with a double sided tape that can withstand up to 150C.

Code

  • Python Code for translation
  • Processing to send data to the arduino
  • Arduino code
Python Code for translationPython
Takes textified mifi file and translates it to 11 sets of binary for arduino to take it.
output_file = open("translated.txt", "w")
input_file = open("megalocania.txt")
raw_input_data = input_file.read()
work_data = raw_input_data.splitlines()
result = []

#output_file = open("result.txt", "w")

def main():

    for a in work_data:
        temp_time = time_finder(a)
        
        
        if result == []:
            result.append(str(temp_time) + ",")
            if on_off_finder(a):
                result[-1] += set_bit(True, note_finder(a))
            elif not on_off_finder(a):
                result[-1] += set_bit(True, note_finder(a))
                
        elif time_finder_comm(result[-1]) == temp_time:
            result[-1] = str(temp_time) + "," + set_bit_prev(on_off_finder(a), note_finder(a), -1)

        elif time_finder_comm(result[-1]) != temp_time:
            result.append(str(temp_time) + "," + set_bit_prev(on_off_finder(a), note_finder(a), -1))

    for b in result:
        output_file.write(b)
        output_file.write("\n")

    output_file.close()
            
def set_bit(On, note):
    #Takes boolean for if it is on or not, and note number.
    #Generates bit
    if(note >= 21 and note <= 28 and On):
        return str(2**(note - 21)) + ",0,0,0,0,0,0,0,0,0,0"
    elif(note >= 29 and note <= 36 and On):
        return "0," + str(2**(note - 29)) + ",0,0,0,0,0,0,0,0,0"
    elif(note >= 37 and note <= 44 and On):
        return "0,0," + str(2**(note - 37)) + ",0,0,0,0,0,0,0,0"
    elif(note >= 45 and note <= 52 and On):
        return "0,0,0," + str(2**(note - 45)) + ",0,0,0,0,0,0,0"
    elif(note >= 53 and note <= 60 and On):
        return "0,0,0,0," + str(2**(note - 53)) + ",0,0,0,0,0,0"
    elif(note >= 61 and note <= 68 and On):
        return "0,0,0,0,0," + str(2**(note - 61)) + ",0,0,0,0,0"
    elif(note >= 69 and note <= 76 and On):
        return "0,0,0,0,0,0," + str(2**(note - 69)) + ",0,0,0,0"
    elif(note >= 77 and note <= 84 and On):
        return "0,0,0,0,0,0,0," + str(2**(note - 77)) + ",0,0,0"
    elif(note >= 85 and note <= 92 and On):
        return "0,0,0,0,0,0,0,0," + str(2**(note - 85)) + ",0,0"
    elif(note >= 93 and note <= 100 and On):
        return "0,0,0,0,0,0,0,0,0," + str(2**(note - 93)) + ",0"
    elif(note >= 101 and note <= 108 and On):
        return "0,0,0,0,0,0,0,0,0,0," + str(2**(note - 101))
    else:
        return "0,0,0,0,0,0,0,0,0,0,0"

def set_bit_prev(On, note, index):
    #Same as set_bit but previous aware
    temp = result[index]
    temp = temp[(temp.find(",") + 1):]
    
    if(note >= 21 and note <= 28):
        local_temp = temp[0:temp.find(",")]
        if(On):
            return str(int(local_temp) + (2**(note - 21))) + temp[temp.find(","):]
        if(not On):
            return str(int(local_temp) - (2**(note - 21))) + temp[temp.find(","):]
        
    elif(note >= 29 and note <= 36):
        local_temp = temp[(temp.find(",") + 1):indexTh(temp, ",", 2)]
        if(On):
            return temp[0:temp.find(",") + 1] + str(int(local_temp) + (2**(note - 29))) + temp[indexTh(temp, ",", 2):]
        if(not On):
            return temp[0:temp.find(",") + 1] + str(int(local_temp) - (2**(note - 29))) + temp[indexTh(temp, ",", 2):]
        
    elif(note >= 37 and note <= 44):
        local_temp = temp[(indexTh(temp, ",", 2) + 1):indexTh(temp, ",", 3)]
        if(On):
            return temp[0:indexTh(temp, ",", 2) + 1] + str(int(local_temp) + (2**(note - 37))) + temp[indexTh(temp, ",", 3):]
        if(not On):
            return temp[0:indexTh(temp, ",", 2) + 1] + str(int(local_temp) - (2**(note - 37))) + temp[indexTh(temp, ",", 3):]
        
    elif(note >= 45 and note <= 52):
        local_temp = temp[(indexTh(temp, ",", 3) + 1):indexTh(temp, ",", 4)]
        if(On):
            return temp[0:indexTh(temp, ",", 3) + 1] + str(int(local_temp) + (2**(note - 45))) + temp[indexTh(temp, ",", 4):]
        if(not On):
            return temp[0:indexTh(temp, ",", 3) + 1] + str(int(local_temp) - (2**(note - 45))) + temp[indexTh(temp, ",", 4):]
        
    elif(note >= 53 and note <= 60):
        local_temp = temp[(indexTh(temp, ",", 4) + 1):indexTh(temp, ",", 5)]
        if(On):
            return temp[0:indexTh(temp, ",", 4) + 1] + str(int(local_temp) + (2**(note - 53))) + temp[indexTh(temp, ",", 5):]
        if(not On):
            return temp[0:indexTh(temp, ",", 4) + 1] + str(int(local_temp) - (2**(note - 53))) + temp[indexTh(temp, ",", 5):]
        
    elif(note >= 61 and note <= 68):
        local_temp = temp[(indexTh(temp, ",", 5) + 1):indexTh(temp, ",", 6)]
        if(On):
            return temp[0:indexTh(temp, ",", 5) + 1] + str(int(local_temp) + (2**(note - 61))) + temp[indexTh(temp, ",", 6):]
        if(not On):
            return temp[0:indexTh(temp, ",", 5) + 1] + str(int(local_temp) - (2**(note - 61))) + temp[indexTh(temp, ",", 6):]
        
    elif(note >= 69 and note <= 76):
        local_temp = temp[(indexTh(temp, ",", 6) + 1):indexTh(temp, ",", 7)]
        if(On):
            return temp[0:indexTh(temp, ",", 6) + 1] + str(int(local_temp) + (2**(note - 69))) + temp[indexTh(temp, ",", 7):]
        if(not On):
            return temp[0:indexTh(temp, ",", 6) + 1] + str(int(local_temp) - (2**(note - 69))) + temp[indexTh(temp, ",", 7):]
        
    elif(note >= 77 and note <= 84):
        local_temp = temp[(indexTh(temp, ",", 7) + 1):indexTh(temp, ",", 8)]
        if(On):
            return temp[0:indexTh(temp, ",", 7) + 1] + str(int(local_temp) + (2**(note - 77))) + temp[indexTh(temp, ",", 8):]
        if(not On):
            return temp[0:indexTh(temp, ",", 7) + 1] + str(int(local_temp) - (2**(note - 77))) + temp[indexTh(temp, ",", 8):]
        
    elif(note >= 85 and note <= 92):#error here
        local_temp = temp[(indexTh(temp, ",", 8) + 1):indexTh(temp, ",", 9)]
        if(On):
            return temp[0:indexTh(temp, ",", 8) + 1] + str(int(local_temp) + (2**(note - 85))) + temp[indexTh(temp, ",", 9):]
        if(not On):
            return temp[0:indexTh(temp, ",", 8) + 1] + str(int(local_temp) - (2**(note - 85))) + temp[indexTh(temp, ",", 9):]
        
    elif(note >= 93 and note <= 100):
        local_temp = temp[(indexTh(temp, ",", 9) + 1):indexTh(temp, ",", 10)]
        if(On):
            return temp[0:indexTh(temp, ",", 9) + 1] + str(int(local_temp) + (2**(note - 93))) + temp[indexTh(temp, ",", 10):]
        if(not On):
            return temp[0:indexTh(temp, ",", 9) + 1] + str(int(local_temp) - (2**(note - 93))) + temp[indexTh(temp, ",", 10):]
        
    elif(note >= 101 and note <= 108):
        local_temp = temp[(indexTh(temp, ",", 10) + 1):]
        if(On):
            return temp[0:indexTh(temp, ",", 10) + 1] + str(int(local_temp) + (2**(note - 101)))
        if(not On):
            return temp[0:indexTh(temp, ",", 10) + 1] + str(int(local_temp) - (2**(note - 101)))
        

def indexTh(in_string, find_this, th):
    #Takes String, string to find, and order to find string to find at that order
    #returns index
    order = 1
    last_index = 0
    while(True):
        temp = in_string.find(find_this, last_index)
        if(temp == -1):
            return -1
        if(order == th):
            return temp
        order += 1
        last_index = temp + 1

def time_finder(in_string):
    #Takes a string and finds time, returns it as an int
    time_end = in_string.index(" ")
    return int(in_string[0:time_end])

def time_finder_comm(in_string):
    #Takes a string and finds time, returns it as an int comma
    time_end = in_string.index(",")
    return int(in_string[0:time_end])
    
def note_finder(in_string):
    #Takes a string, looks for n=, returns n value as an int
    num_start = in_string.index("n=") + 2
    num_end = in_string.index("v=") - 1
    return int(in_string[num_start:num_end])

def on_off_finder(in_string):
    #takes a string, looks for On or Off, return true if On
    start = in_string.index(" ") + 1
    end = in_string.index("ch=") - 1
    if in_string[start:end] == "On":
        return True
    elif in_string[start:end] == "Off":
        return False

main()
Processing to send data to the arduinoProcessing
Reads the translated text file and sends it to the arduino.
Must mod tempo multiplier if tempo is different than 50000.
Reverses bytes because it shifts from left to right. (Text file assumes right to left)
import processing.serial.*;

Serial myPort;
String[] inputLines;


void setup()
{
  myPort = new Serial(this, "COM3", 9600);
  inputLines = loadStrings("translated.txt");
  run();
}

void run()
{
  //reads time and sends data bt line using data method
  int lastTime = 0;
  
  for(int i = 0; i < inputLines.length; i++)
  {
    String temp = inputLines[i];
    
    //*5 is a tempo multiplier. increase the number if tempo is lower.
    delay((Integer.parseInt(temp.substring(0, temp.indexOf(","))) - lastTime) * 5);
    lastTime = Integer.parseInt(temp.substring(0, temp.indexOf(",")));
    send(temp.substring(temp.indexOf(",") + 1));
  }
}

void send(String data)
{
  //String first = data.substring(indexOforder(data, ",", 1), (indexOforder(data, ",", 2) + 1));
  //String second = data.substring(indexOforder(data, ",", 2), (indexOforder(data, ",", 3) + 1));
  //String third = data.substring(indexOforder(data, ",", 3), (indexOforder(data, ",", 4) + 1));
  //String forth = data.substring(indexOforder(data, ",", 4), (indexOforder(data, ",", 5) + 1));
  //String fifth = data.substring(indexOforder(data, ",", 5), (indexOforder(data, ",", 6) + 1));
  //String sixth = data.substring(indexOforder(data, ",", 6), (indexOforder(data, ",", 7) + 1));
  //String seventh = data.substring(indexOforder(data, ",", 7), (indexOforder(data, ",", 8) + 1));
  //String eighth = data.substring(indexOforder(data, ",", 8), (indexOforder(data, ",", 9) + 1));
  //String ninth = data.substring(indexOforder(data, ",", 9), (indexOforder(data, ",", 10) + 1));
  //String tenth = data.substring(indexOforder(data, ",", 10), (indexOforder(data, ",", 11) + 1));
  //String eleventh = data.substring(indexOforder(data, ",", 11), (indexOforder(data, ",", 12) + 1));
  
  //inverse declare
  String eleventh = data.substring( 0 , indexOforder(data, ",", 1));
  String tenth = data.substring((indexOforder(data, ",", 1) + 1), (indexOforder(data, ",", 2)));
  String ninth = data.substring((indexOforder(data, ",", 2) + 1), (indexOforder(data, ",", 3)));
  String eighth = data.substring((indexOforder(data, ",", 3) + 1), (indexOforder(data, ",", 4)));
  String seventh = data.substring((indexOforder(data, ",", 4) + 1), (indexOforder(data, ",", 5)));
  String sixth = data.substring((indexOforder(data, ",", 5) + 1), (indexOforder(data, ",", 6)));
  String fifth = data.substring((indexOforder(data, ",", 6) + 1), (indexOforder(data, ",", 7)));
  String forth = data.substring((indexOforder(data, ",", 7) + 1), (indexOforder(data, ",", 8)));
  String third = data.substring((indexOforder(data, ",", 8) + 1), (indexOforder(data, ",", 9)));
  String second = data.substring((indexOforder(data, ",", 9) + 1), (indexOforder(data, ",", 10)));
  String first = data.substring(indexOforder(data, ",", 10) + 1);
  
  myPort.write("888f");
  myPort.write(first + "f");
  myPort.write(second + "f");
  myPort.write(third + "f");
  myPort.write(forth + "f");
  myPort.write(fifth + "f");
  myPort.write(sixth + "f");
  myPort.write(seventh + "f");
  myPort.write(eighth + "f");
  myPort.write(ninth + "f");
  myPort.write(tenth + "f");
  myPort.write(eleventh + "f");
  myPort.write("999f");
}

int indexOforder(String data, String find, int order)
{
  int currentOrder = 0;
  int lastLocation = 0;
  
  while(currentOrder < order)
  {
    lastLocation = data.indexOf(find, (lastLocation + 1));
    currentOrder += 1;
  }
  
  return lastLocation;
}
Arduino codeArduino
Simple code for arduino. Takes inputs from Serial. 888 and 999 are reserved for shift register open and close command.
No preview (download only).

Schematics

I'm sorry for un-professional drawing. This is the whole concept. There's no difference between Arduino -ShiftOut document's diagram except for the mosfet. I recommend looking at that too.PiBot: The Arduino‑Powered Piano Robot for Precision Music Performance

Manufacturing process

  1. Build a Bluetooth‑Controlled Raspberry Pi Robot with Audio Feedback
  2. Control LEDs with Alexa via Raspberry Pi – Easy Step‑by‑Step Guide
  3. Find Me: Smart Item Locator with Arduino and Bluetooth
  4. Build a Voice‑Controlled Robot with Arduino Nano
  5. MobBob: Build Your Own Arduino Robot, Controlled Seamlessly via Android Smartphone
  6. Build a Custom Arduino Joystick Steering Wheel for Gaming
  7. Build a 4-Wheel Arduino Robot Controlled via Dabble App
  8. Build a Mini Piano with Arduino UNO – Step-by-Step Tutorial
  9. Build the Simplest Arduino Line‑Following Robot with SparkFun L298
  10. Build a Recordable Cardboard Robot Arm – Easy DIY with Arduino & Servos