This week, I wanted to continue my piano project, officially started last week with servo and solenoid test boards. The eventual plan is to have 88 actuators over the keys of a piano (likely the grand piano in the MIT SAE library). Since this week’s assignment was to make something big with the CNC, I decided it was fitting to build a housing for these actuators to sit over the piano keys.
I began by measuring out the piano at the house, capturing all the details of its structure.
Next, I designed two 5-foot-long beams, each 3 inches tall, to maximize the "h" term in the \( \frac{bh^3}{12} \) formula. One beam is designated for white key actuators and the other for black key actuators. Here’s the design:
I cut the design on OSB wood using the CNC machine. The entire job took less than 10 minutes. Afterward, I used a bandsaw and file for some final touches.
After assembling the pieces, I brought it to New Vassar's music room to test it on a piano. Here’s an image of the cover placed over the keys:
I glued the solenoid driver PCB and two solenoids (aligned with the F4 and Bb4 keys) onto the wooden cover. I programmed it to play "One-Note Samba." Spotify link to One-Note Samba.
Here’s the code I used:
#define NUM_SOLENOIDS 5 #define BPM 120 int solenoidPins[NUM_SOLENOIDS] = {D0, D1, D2, D3, D4}; // solenoid GPIO pins enum noteTypes { EIGHTH, // Eighth note QUARTER, // Quarter note HALF, DOT_QUARTER, EIGHTH_HALF, // 8th + 1/2 REST // Rest note }; void setup() { for (int i = 0; i < NUM_SOLENOIDS; i++) { pinMode(solenoidPins[i], OUTPUT); // Set each solenoid pin as output } } // Function to calculate the duration of a note based on BPM int calculateNoteDuration(enum noteTypes note) { int beatDuration = 60000 / BPM; // Calculate duration of a quarter note in milliseconds switch (note) { case EIGHTH: return beatDuration / 2; // Eighth note is half of a quarter note case QUARTER: return beatDuration; // Quarter note is the full beat duration case HALF: return beatDuration * 2; case DOT_QUARTER: return beatDuration * 3 / 2; case EIGHTH_HALF: return beatDuration * 5 / 2; case REST: return beatDuration / 2; // eighth note rest default: return 0; // Default to no duration if undefined } } // Helper function to play a note on a specified solenoid pin void playNote(int solenoidPin, enum noteTypes note) { int duration = calculateNoteDuration(note); if (note != REST) { // Activate only if it's not a rest digitalWrite(solenoidPin, HIGH); // Activate the solenoid delay(duration/2); // Wait for the note duration digitalWrite(solenoidPin, LOW); // Deactivate the solenoid delay(duration/2); } else { delay(duration); // Wait for the rest duration without activating the solenoid } } void samba(int solenoidPin) { playNote(solenoidPin, QUARTER); playNote(solenoidPin, EIGHTH); playNote(solenoidPin, QUARTER); playNote(solenoidPin, EIGHTH); playNote(solenoidPin, QUARTER); playNote(solenoidPin, QUARTER); playNote(solenoidPin, QUARTER); playNote(solenoidPin, DOT_QUARTER); playNote(solenoidPin, QUARTER); playNote(solenoidPin, EIGHTH); playNote(solenoidPin, QUARTER); playNote(solenoidPin, EIGHTH); playNote(solenoidPin, QUARTER); playNote(solenoidPin, QUARTER); playNote(solenoidPin, EIGHTH_HALF); // half + eighth } void loop() { // One-note samba for (int i = 0; i < 2; i++) { samba(solenoidPins[0]); } samba(solenoidPins[1]); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], EIGHTH); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], EIGHTH); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], DOT_QUARTER); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], EIGHTH); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], EIGHTH); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[0], QUARTER); playNote(solenoidPins[1], EIGHTH_HALF); // half + eighth delay(2000); }
I used the 3D-printed piano finger beneath the solenoid rod to prevent damage to the keys. Here’s what the finger looked like after repetitive use: