1 INTRODUCTION
Welcome to our final project. This music maker device was designed for our client, Henry, a 17-year-old who enjoys music, cars, and bowling, social with people and uses a dedicated communication device. We developed this project as part of CMU's Advanced Physical Computing course. The class's primary goal is to improve our client's quality of life through collaborative technological solutions. By focusing on Henry's specific interactions and needs, this device was made to help Henry play music both by himself and with others. It allows Henry to play and create music.
This Music Creator is a simple device that lets Henry make musical sounds by pressing large buttons. We designed a two-piece system: a button controller and a main unit. The controller features four flat, easy-to-reach buttons mounted at an angle to make pressing them comfortable. This controller connects to the main unit, which holds the speaker and all the necessary parts. When Henry presses a button, the device plays different notes or chords from instruments like the piano, guitar, or flute. The controller is designed to stick securely to a table, allowing Henry to easily and independently explore and enjoy different musical options.
Henry enjoys playing with his music maker.
The overall look of the music maker.
Circuit inside the design.
Early prototype design featuring a microphone hole (discontinued design)
Back of the device
The First Prototype: In the first prototype, we used 4 buttons and laptop to play the music.
Henry is playing with the first prototype.
After the first visit, we decided to create three basic prototypes: a music maker, a bowling game, and an audio recorder. We then presented these initial prototypes. We observed that it was difficult for Henry to aim at the target in the bowling game, and the audio recorder seemed too simple for him. Henry especially enjoyed making sounds using the music maker. In the end, we decided to continue developing the music maker idea.
The Second Prototype: In the second prototype, we used four buttons in the music maker and laser-cut four boxes. We also used an actual speaker to play the music instead of a laptop. We transitioned from Arduino to Raspberry Pi Pico for more power.
Four buttons, each corresponding to a different piano note.
After the second prototype, we learned several important things from the visit:
The colors of the buttons should avoid green and blue, since these colors are used in Henry’s communication device and have different meanings for him.
The buttons need to be flat so that Henry cannot easily grab them.
The shape of the device should be curved (arc-shaped) to make it easier for Henry to reach.
Four is the maximum number of buttons that Henry can comfortably handle.
The buttons need to be placed close to each other.
We should add suction cups or a mount in case Henry tries to grab the device from the table.
The Final Prototype: There are 4 buttons. Six modes including soung mode. Use Teensy4.1.
Change from Raspberry Pi Pico to Teensy4.1
Our first 3-d printed module.
Solder all the wires from breadboard to a protoboard.
Take a button apart to insert transparent film to create custom colors
Several additional changes were made in our final prototype:
We used a Teensy 4.1 instead of a Raspberry Pi Pico to achieve better sound quality.
We added more instruments, such as guitar and organ.
We added a song mode that divides a single song into different parts. Each LED lights up, and Henry needs to press the corresponding button to play each part. Eventually, this forms a complete song.
Several breakthrough moments and discoveries:
It took me hours to finally get the Teensy 4.1 working! Their examples were not very clear. Thanks to Zach for his help.
Our first modeling is finally done.
3D card reading can be tricky. It turns out that the file needs a special name format for the Teensy 4.1 to read: all capital letters and fewer than 7 characters before the decimal. This was really surprising.
Our original plan is to combine the audio recording idea with the music maker. But it turns out the audio recording using microcontroller is much harder than we expected. Also, there is no example code for teensy4.1 to do the recording. Because we want to finish the main functioanlity, we gave up on that idea.
Pic7: Our Gantt Chart
Because we thought that no one would want to work during Thanksgiving (which turned out to be true, lol), we made this ambitious Gantt chart. We tried to finish everything before Thanksgiving, and then did the testing right after Thanksgiving. In reality, we completed most of it, but we did not finish the first integration before Thanksgiving. However, most of the tasks stayed on track. This was because we had a clear division of work, and everyone knew what they needed to do. I guess that’s the reason.
The final critique gave us several useful suggestions that can help us improve our design in both functionality and usability. One piece of feedback noted that we should add more safety protections for Henry, which led us to add protective wrappings around the screws in the final send-off to ensure they would not hurt Henry’s hands during use. Another piece of written feedback encouraged us to consider the non-standard uses, such as how it is carried, stored, cleaned, or what happens if it is dropped. This suggestion made us realize that we should pay more attention to real-world usage scenarios. However, due to limited time, we focused more on overall functionality. If we were to iterate on this project, we would explore making the enclosure more durable and impact-resistant, especially in case it is dropped. We would also think more intentionally about storage and cleaning, which are important aspects of design for children. If we had more time, we would definitely consider these situations more thoroughly.
We also received feedback suggesting a volume knob and possibly adding Bluetooth functionality. We agree that a physical volume knob would greatly improve usability and is especially important for a sound-based project. Next time, we would definitely add this feature. Bluetooth support is another exciting idea that could expand the device’s flexibility, but it would require additional time and technical complexity. These are features we would strongly consider if given more development time. Similarly, feedback about making the background darker is useful. Next time, we would consider purchasing 3D printing filament online instead of using the default gray.
Additional feedback pointed out that putting something to make the project stick to the table and that making the SD card more accessible would improve usability. We had considered adding suction cups or a mount to secure the device to the table, but ultimately forgot as we prioritized overall functionality. This is important for our device, since during the final send-off we observed that Henry would grab the buttons and pull the device away from the table. This is something we should address in a final prototype. Making the SD card more accessible is technically challenging due to the Teensy 4.1 audio shield layout, but with more time, we could redesign the enclosure to better accommodate access.
We learned a lot during this process of creating a device for a single, specific client. We needed to pay attention to every small detail, such as the colors of the buttons and the choices between different button types. These details seem to be small but they were important because this device was crafted for Henry himself. We created multiple design iterations to ensure the device was easier for him to use. Also, we gained more experiences on how to work with different microcontrollers, such as the Raspberry Pi Pico and the Teensy 4.1. We learned that creating a Gantt chart is easy, but it is difficult to stick to it. Sometimes we can be too ambitious when planning the schedule. Next time, we need to start earlier. Furthermore, working with audio can be very tricky—for example, coding for the Teensy 4.1, reading from the SD card, and managing sound files.
For next time, we will definitely begin the final assembly earlier to save time for additional iterations. If time permits, we would like to implement the sound recording feature we considered; we initially thought about it, but it turned out to be more challenging than expected, so we eventually pivoted to other critical features. Also, if we have time, we could attempt a custom PCB design.
/*
Music Maker Teensy4.1 Code
Music Maker is a self-assistance device for Henry. There are four buttons on the device.
Henry can press them to make music. Each button represents one note or one chord.
There is a button to switch between note and chord.
Also there is a knob that can change the instruments.
Pin mapping:
Teensy4.1 pin | role | details
10 input SDcard CS Pin
7 input SDcard MOSI Pin
4 input SDcard SCK Pin
26 input Knob Position 1
27 input Knob Position 2
28 input Mode Button
29 input Button 1 Pin
30 input Button 2 Pin
31 input Button 3 Pin
32 input Button 4 Pin
33 output LED 1 Pin
34 output LED 2 Pin
35 output LED 3 Pin
36 output LED 4 Pin
37 input Knob Position 3
38 input Knob Position 4
39 input Knob Position 5
40 input Knob Position 6
Reference: I used Teensy4.1 library example code online: https://www.pjrc.com/teensy/td_libs_Audio.html
*/
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
// Audio objects
AudioPlaySdWav playWav;
AudioOutputI2S i2s1;
AudioConnection patchCord1(playWav, 0, i2s1, 0);
AudioConnection patchCord2(playWav, 1, i2s1, 1);
AudioControlSGTL5000 sgtl5000_1;
#define SDCARD_CS_PIN 10
#define SDCARD_MOSI_PIN 7 // Teensy 4 ignores this, uses pin 11
#define SDCARD_SCK_PIN 14 // Teensy 4 ignores this, uses pin 13
// #define SDCARD_CS_PIN BUILTIN_SDCARD
// #define SDCARD_MOSI_PIN 11 // not actually used
// #define SDCARD_SCK_PIN 13 // not actually used
// Button and LED pins
const int buttonPins[4] = { 29, 30, 31, 32 };
const int ledPins[4] = { 33, 34, 35, 36 };
// File paths on SD
const char* popFiles[4] = {
"POP1.WAV",
"POP2.WAV",
"POP3.WAV",
"POP4.WAV"
};
const char* popAppFiles[4] = {
"POPC1.WAV",
"POPC2.WAV",
"POPC3.WAV",
"POPC4.WAV"
};
const char* rowFiles[4] = {
"ROWBT1.WAV",
"ROWBT2.WAV",
"ROWBT3.WAV",
"ROWBT4.WAV"
};
const char* song2Files[8] = {
"SONG1.WAV",
"SONG2.WAV",
"SONG3.WAV",
"SONG4.WAV",
"SONG5.WAV",
"SONG6.WAV",
"SONG7.WAV",
"SONG8.WAV"
};
const char* guitarFiles[4] = {
"GUI1.WAV",
"GUI2.WAV",
"GUI3.WAV",
"GUI4.WAV"
};
const char* guitarAppFiles[4] = {
"GAPP1.WAV",
"GAPP2.WAV",
"GAPP3.WAV",
"GAPP4.WAV"
};
const char* harmonicFiles[4] = {
"HAR1.WAV",
"HAR2.WAV",
"HAR3.WAV",
"HAR4.WAV"
};
const char* harmonicAppFiles[4] = {
"HAPP1.WAV",
"HAPP2.WAV",
"HAPP3.WAV",
"HAPP4.WAV"
};
const char* organFiles[4] = {
"ORG1.WAV",
"ORG2.WAV",
"ORG3.WAV",
"ORG4.WAV"
};
const char* organAppFiles[4] = {
"OAPP1.WAV",
"OAPP2.WAV",
"OAPP3.WAV",
"OAPP4.WAV"
};
// Mode toggle
const int modePin = 28;
bool apprMode = false;
bool prevModeState = false;
int currentState = -1;
int prevState = -1;
int currentStep = 0;
#define POS_1_PIN 40
#define POS_2_PIN 27
#define POS_3_PIN 26
#define POS_4_PIN 37
#define POS_5_PIN 38
#define POS_6_PIN 39
int readSwitchPosition() {
// Read all switch pins (LOW = connected, HIGH = not connected due to INPUT_PULLUP)
bool pin1 = !digitalRead(POS_1_PIN);
bool pin2 = !digitalRead(POS_2_PIN);
bool pin3 = !digitalRead(POS_3_PIN);
bool pin4 = !digitalRead(POS_4_PIN);
bool pin5 = !digitalRead(POS_5_PIN);
bool pin6 = !digitalRead(POS_6_PIN);
// Determine position based on which pin is active
// This assumes a binary-coded or single-pin-per-position switch
if (pin1) return 1;
if (pin2) return 2;
if (pin3) return 3;
if (pin4) return 4;
if (pin5) return 5;
if (pin6) return 6;
return 0; // No position detected
}
void setup() {
Serial.begin(9600);
AudioMemory(20);
// Enable audio shield
sgtl5000_1.enable();
sgtl5000_1.volume(1.0);
sgtl5000_1.unmuteHeadphone();
sgtl5000_1.unmuteLineout();
// Initialize buttons and LEDs
for (int i = 0; i < 4; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
pinMode(ledPins[i], OUTPUT);
}
pinMode(modePin, INPUT_PULLUP);
// Initialize SD
if (!SD.begin(SDCARD_CS_PIN)) {
Serial.println("SD init failed!");
while (1)
;
}
//rotate
pinMode(POS_1_PIN, INPUT_PULLUP);
pinMode(POS_2_PIN, INPUT_PULLUP);
pinMode(POS_3_PIN, INPUT_PULLUP);
pinMode(POS_4_PIN, INPUT_PULLUP);
pinMode(POS_5_PIN, INPUT_PULLUP);
pinMode(POS_6_PIN, INPUT_PULLUP);
prevState = readSwitchPosition();
Serial.print("start state ");
Serial.println(prevState);
}
bool buttonLatched = false; // remembers that the button was pressed
void songMode() {
// Light only the current LED
for (int i = 0; i < 4; i++) {
digitalWrite(ledPins[i], i == currentStep ? HIGH : LOW);
}
int btn = buttonPins[currentStep];
// --- 1. Detect moment button is pressed ---
if (!digitalRead(btn) && !buttonLatched) {
buttonLatched = true; // latch it so we don't trigger again
if (playWav.isPlaying()) playWav.stop();
const char* fileToPlay = rowFiles[currentStep];
Serial.print("Playing: ");
Serial.println(fileToPlay);
playWav.play(fileToPlay);
}
// --- 2. If latched, wait for playback to finish ---
if (buttonLatched) {
if (!playWav.isPlaying()) {
// playback done → move to next LED
buttonLatched = false;
currentStep++;
if (currentStep >= 4) currentStep = 0;
delay(2000); // debounce
}
}
}
void song2Mode() {
int ledIndex = currentStep % 4; // LEDs repeat every 4
int btnIndex = currentStep % 4; // Buttons repeat every 4
int fileIndex = currentStep % 8; // Files repeat every 8
// ---- 1. Light correct LED ----
for (int i = 0; i < 4; i++) {
digitalWrite(ledPins[i], i == ledIndex ? HIGH : LOW);
}
int btn = buttonPins[btnIndex];
// ---- 2. Detect *moment* button is pressed ----
if (!digitalRead(btn) && !buttonLatched) {
buttonLatched = true;
if (playWav.isPlaying()) playWav.stop();
const char* fileToPlay = song2Files[fileIndex];
Serial.print("Playing: ");
Serial.println(fileToPlay);
playWav.play(fileToPlay);
}
if (buttonLatched) {
if (!playWav.isPlaying()) {
buttonLatched = false;
currentStep++; // move to next file
if (currentStep >= 8) // loop all 8 files
currentStep = 0;
delay(2000); // simple debounce
}
}
}
void pianoMode() {
// Check mode toggle
bool modeState = digitalRead(modePin);
if (modeState != prevModeState) {
if (!modeState) apprMode = !apprMode;
prevModeState = modeState;
}
// Check buttons
for (int i = 0; i < 4; i++) {
if (!digitalRead(buttonPins[i])) {
Serial.println(i);
// Stop current audio
if (playWav.isPlaying()) playWav.stop();
const char* fileToPlay = apprMode ? popAppFiles[i] : popFiles[i];
Serial.print("Playing: ");
Serial.println(fileToPlay);
if (!playWav.play(fileToPlay)) {
Serial.println("Failed to play file");
}
digitalWrite(ledPins[i], HIGH);
delay(50);
while (playWav.isPlaying())
; // wait until finished
digitalWrite(ledPins[i], LOW);
}
}
}
void guitarMode() {
// Check mode toggle
bool modeState = digitalRead(modePin);
if (modeState != prevModeState) {
if (!modeState) apprMode = !apprMode;
prevModeState = modeState;
}
// Check buttons
for (int i = 0; i < 4; i++) {
if (!digitalRead(buttonPins[i])) {
// Stop current audio
if (playWav.isPlaying()) playWav.stop();
const char* fileToPlay = apprMode ? guitarAppFiles[i] : guitarFiles[i];
Serial.print("Playing: ");
Serial.println(fileToPlay);
if (!playWav.play(fileToPlay)) {
Serial.println("Failed to play file");
}
digitalWrite(ledPins[i], HIGH);
delay(50);
while (playWav.isPlaying())
; // wait until finished
digitalWrite(ledPins[i], LOW);
}
}
}
void organMode() {
// Check mode toggle
bool modeState = digitalRead(modePin);
if (modeState != prevModeState) {
if (!modeState) apprMode = !apprMode;
prevModeState = modeState;
}
// Check buttons
for (int i = 0; i < 4; i++) {
if (!digitalRead(buttonPins[i])) {
// Stop current audio
if (playWav.isPlaying()) playWav.stop();
const char* fileToPlay = apprMode ? organAppFiles[i] : organFiles[i];
Serial.print("Playing: ");
Serial.println(fileToPlay);
if (!playWav.play(fileToPlay)) {
Serial.println("Failed to play file");
}
digitalWrite(ledPins[i], HIGH);
delay(50);
while (playWav.isPlaying())
; // wait until finished
digitalWrite(ledPins[i], LOW);
}
}
}
void harmonicMode() {
// Check mode toggle
bool modeState = digitalRead(modePin);
if (modeState != prevModeState) {
if (!modeState) apprMode = !apprMode;
prevModeState = modeState;
}
// Check buttons
for (int i = 0; i < 4; i++) {
if (!digitalRead(buttonPins[i])) {
// Stop current audio
if (playWav.isPlaying()) playWav.stop();
const char* fileToPlay = apprMode ? harmonicAppFiles[i] : harmonicFiles[i];
Serial.print("Playing: ");
Serial.println(fileToPlay);
if (!playWav.play(fileToPlay)) {
Serial.println("Failed to play file");
}
digitalWrite(ledPins[i], HIGH);
delay(50);
while (playWav.isPlaying())
; // wait until finished
digitalWrite(ledPins[i], LOW);
}
}
}
void resetModes() {
// Stop any audio
if (playWav.isPlaying()) playWav.stop();
// Turn OFF all LEDs
for (int i = 0; i < 4; i++) {
digitalWrite(ledPins[i], LOW);
}
// Reset state machines
currentStep = 0;
}
void loop() {
currentState = readSwitchPosition();
if (currentState != prevState){
Serial.print("switched to pos ");
Serial.println(currentState);
resetModes();
prevState = currentState;
}
if (currentState == 1) {
songMode();
}else if (currentState == 2) {
song2Mode();
}else if (currentState == 3) {
pianoMode();
}else if (currentState == 4) {
harmonicMode();
}else if (currentState == 5) {
organMode();
}else if (currentState == 6) {
guitarMode();
}
delay(100);
}
We provide Fusion 360 model files with accompanying links for your 3D printing and laser cutting needs.
THE MODEL LINK!!!
1. FUSION 360 LINK
https://a360.co/4qgdIyh
2. GOOGLE DRIVE LINK
https://drive.google.com/file/d/1fOt2zGtMLxBSTH5sjALOXRMOCfGURzMN/view?usp=drive_link