The Electric Pentachrome: a 5-tone Touchless Musical Interface

by learningninja496i in Circuits > Microcontrollers

70 Views, 0 Favorites, 0 Comments

The Electric Pentachrome: a 5-tone Touchless Musical Interface

IMG_5578.jpg

Since I was five, I have loved playing my favorite songs on the piano. However, one aspect of it I didn't like while playing was how widespread the instrument was, extending 5 feet wide. This made it difficult for me to play, as I could not stretch across it to play the different notes, and I was inspired to create an instrument that had a shorter range of notes, yet more notes to play.

The Electric Pentachrome, my custom-designed instrument, runs on a 5-volt power supply and utilizes 5 ultrasonic distance sensors, 5 buttons, 5 passive buzzers, and a Raspberry Pi Pico. The ultrasonic sensors determine the pitch of the note played, from 27 hertz (A0) to 7902 hertz (B8), encompassing 99 notes. The buttons determine whether the note is played or not. Each note on the distance sensors' "invisible fretboard" has approximately 1 centimeter of space, and the total height of it is 1 meter.

Supplies

Tools:

  1. Soldering Rod
  2. Device (needs to have Thonny IDE installed)
  3. Solder
  4. Multimeter
  5. 6 Small Breadboards (for prototyping)
  6. Electrical Tape
  7. Hot Glue Gun

Materials:

  1. 1x Raspberry Pi Pico - System Brain
  2. 1x Large Breadboard - Mounting for System Brain
  3. 5x 7cm x 3cm PCB - Component Mounting for Ultrasonic Sensors
  4. 2x 7cm x 9cm PCB - Component Mounting for Buttons and Buzzers
  5. 2x 8cm x 2cm PCB - Component Mounting for Buttons and Buzzers
  6. 5x Tactile Push Button - Trigger for Note Generation
  7. 5x Passive Buzzer - Variable Note Generation
  8. 5x S8050 Transistor - Current Regulation for 5V Rail
  9. 10x 1K Resistor - Current Limiter
  10. 5x 2K Resistor - Voltage Divider for Echo Pins
  11. 5x 10K Resistor - Button Voltage Regulation
  12. 5x HC-SR04 Distance Sensor - Distance Detection for Pitch Mapping
  13. 1x Power Bank - Power Supply
  14. Jumper Wires - Electrical Connection

Wiring

Screenshot 2026-07-05 165451.png

Wire the circuit shown above on breadboards. Note your GPIO pins can vary; these are just what I used in my circuit.

Coding

Write the following code in Thonny IDE. Note that you will have to adjust the pin numbers in the code if you change your GPIO pins.

#import necessary modules
from machine import time_pulse_us
import machine
import utime
import math


#define note frequencies
notes = [27, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 77, 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1864, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902]


#define buzzers, ultrasonic pins, and buttons
buz1 = machine.PWM(machine.Pin(15))
btn1 = machine.Pin(11, machine.Pin.IN)
trig1 = machine.Pin(10, machine.Pin.OUT)
echo1 = machine.Pin(9, machine.Pin.IN)
buz2 = machine.PWM(machine.Pin(13))
btn2 = machine.Pin(22, machine.Pin.IN)
trig2 = machine.Pin(8, machine.Pin.OUT)
echo2 = machine.Pin(7, machine.Pin.IN)
buz3 = machine.PWM(machine.Pin(12))
btn3 = machine.Pin(18, machine.Pin.IN)
trig3 = machine.Pin(6, machine.Pin.OUT)
echo3 = machine.Pin(5, machine.Pin.IN)
buz4 = machine.PWM(machine.Pin(14))
btn4 = machine.Pin(19, machine.Pin.IN)
trig4 = machine.Pin(4, machine.Pin.OUT)
echo4 = machine.Pin(3, machine.Pin.IN)
buz5 = machine.PWM(machine.Pin(0))
btn5 = machine.Pin(20, machine.Pin.IN)
trig5 = machine.Pin(2, machine.Pin.OUT)
echo5 = machine.Pin(1, machine.Pin.IN)


#define note-playing function
def tone(frequency, buz):
buz.freq(frequency)
buz.duty_u16(30000)
utime.sleep_ms(50)


#define buzzer-stopping function
def stop(buz):
buz.duty_u16(0)


#define distance function
def distance(trig, echo):
trig.value(0)
utime.sleep_us(5)
trig.value(1)
utime.sleep_us(10)
trig.value(0)


ultrasonic_duration = time_pulse_us(echo, 1, 30000)
distance_cm = 343* ultrasonic_duration / 20000
return distance_cm


#define note indexes as 'None'
index1 = None
index2 = None
index3 = None
index4 = None
index5 = None


#begin the cycle of gathering distance and playing notes accordingly
while True:
try:
dist = distance(trig1, echo1)
if dist >= 2 and dist < 102:
index1 = math.floor(dist)
index1 = index1 - 2
if btn1.value() == 1:
stop(buz1)
elif btn1.value() == 0 and index1 >=0 and index1 <= 100:
tone(notes[index1], buz1)
dist = distance(trig2, echo2)
if dist >= 2 and dist < 102:
index2 = math.floor(dist)
index2 = index2 - 2
if btn2.value() == 1:
stop(buz2)
elif btn2.value() == 0 and index2 >=0 and index2 <= 100:
tone(notes[index2], buz2)
dist = distance(trig3, echo3)
if dist >= 2 and dist < 102:
index3 = math.floor(dist)
index3 = index3 - 2
if btn3.value() == 1:
stop(buz3)
elif btn3.value() == 0 and index3 >=0 and index3 <= 100:
tone(notes[index3], buz3)
dist = distance(trig4, echo4)
if dist >= 2 and dist < 102:
index4 = math.floor(dist)
index4 = index4 - 2
if btn4.value() == 1:
stop(buz4)
elif btn4.value() == 0 and index4 >=0 and index4 <= 100:
tone(notes[index4], buz4)
dist = distance(trig5, echo5)
if dist >= 2 and dist < 102:
index5 = math.floor(dist)
index5 = index5 - 2
if btn5.value() == 1:
stop(buz5)
elif btn5.value() == 0 and index5 >=0 and index5 <= 100:
tone(notes[index5], buz5)
except TypeError:
continue
except IndexError:
continue

How the Code Works

First, the compiler imports the necessary modules and defines the note frequencies and the GPIO pins:

#import necessary modules
from machine import time_pulse_us
import machine
import utime
import math


#define note frequencies
notes = [27, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 77, 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1864, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902]


#define buzzers, ultrasonic pins, and buttons
buz1 = machine.PWM(machine.Pin(15))
btn1 = machine.Pin(11, machine.Pin.IN)
trig1 = machine.Pin(10, machine.Pin.OUT)
echo1 = machine.Pin(9, machine.Pin.IN)
buz2 = machine.PWM(machine.Pin(13))
btn2 = machine.Pin(22, machine.Pin.IN)
trig2 = machine.Pin(8, machine.Pin.OUT)
echo2 = machine.Pin(7, machine.Pin.IN)
buz3 = machine.PWM(machine.Pin(12))
btn3 = machine.Pin(18, machine.Pin.IN)
trig3 = machine.Pin(6, machine.Pin.OUT)
echo3 = machine.Pin(5, machine.Pin.IN)
buz4 = machine.PWM(machine.Pin(14))
btn4 = machine.Pin(19, machine.Pin.IN)
trig4 = machine.Pin(4, machine.Pin.OUT)
echo4 = machine.Pin(3, machine.Pin.IN)
buz5 = machine.PWM(machine.Pin(0))
btn5 = machine.Pin(20, machine.Pin.IN)
trig5 = machine.Pin(2, machine.Pin.OUT)
echo5 = machine.Pin(1, machine.Pin.IN)

Next, it defines the functions for playing notes, stopping notes, and gathering distance values:

#define note-playing function
def tone(frequency, buz):
buz.freq(frequency)
buz.duty_u16(30000)
utime.sleep_ms(50)


#define buzzer-stopping function
def stop(buz):
buz.duty_u16(0)


#define distance function
def distance(trig, echo):
trig.value(0)
utime.sleep_us(5)
trig.value(1)
utime.sleep_us(10)
trig.value(0)


ultrasonic_duration = time_pulse_us(echo, 1, 30000)
distance_cm = 343* ultrasonic_duration / 20000
return distance_cm

Then, it ends with the main loop of gathering distance, checking if the button is pressed or not, and playing (or not playing) the corresponding notes:

#define note indexes as 'None'
index1 = None
index2 = None
index3 = None
index4 = None
index5 = None


#begin the cycle of gathering distance and playing notes accordingly
while True:
try:
dist = distance(trig1, echo1)
if dist >= 2 and dist < 102:
index1 = math.floor(dist)
index1 = index1 - 2
if btn1.value() == 1:
stop(buz1)
elif btn1.value() == 0 and index1 >=0 and index1 <= 100:
tone(notes[index1], buz1)
dist = distance(trig2, echo2)
if dist >= 2 and dist < 102:
index2 = math.floor(dist)
index2 = index2 - 2
if btn2.value() == 1:
stop(buz2)
elif btn2.value() == 0 and index2 >=0 and index2 <= 100:
tone(notes[index2], buz2)
dist = distance(trig3, echo3)
if dist >= 2 and dist < 102:
index3 = math.floor(dist)
index3 = index3 - 2
if btn3.value() == 1:
stop(buz3)
elif btn3.value() == 0 and index3 >=0 and index3 <= 100:
tone(notes[index3], buz3)
dist = distance(trig4, echo4)
if dist >= 2 and dist < 102:
index4 = math.floor(dist)
index4 = index4 - 2
if btn4.value() == 1:
stop(buz4)
elif btn4.value() == 0 and index4 >=0 and index4 <= 100:
tone(notes[index4], buz4)
dist = distance(trig5, echo5)
if dist >= 2 and dist < 102:
index5 = math.floor(dist)
index5 = index5 - 2
if btn5.value() == 1:
stop(buz5)
elif btn5.value() == 0 and index5 >=0 and index5 <= 100:
tone(notes[index5], buz5)
except TypeError:
continue
except IndexError:
continue

CAD Design

From this instructable, download the files titled "Electric Pentachrome Lid" and "Electric Pentachrome Box" which are the lid and box for the instrument, respectively. Then 3D print them.

The Electric Pentachrome lid looks like this:


Soldering

IMG_0025.jpeg

Solder the components in the following format:

  1. 7cm x 9cm PCB: 4 buttons
  2. 7cm x 9cm PCB: 4 buzzers
  3. 8cm x 2cm PCB: 1 button
  4. 8cm x 2cm PCB: 1 buzzer
  5. 5 7cm x 3cm PCBs: Ultrasonic sensors

With the exception of the passive buzzers and the buttons (more information in the next step), solder the entire circuit for each component on the PCB itself. For the passive buzzers and the buttons, just solder them onto the PCB.

Breadboard Circuit

IMG_0024.jpeg

Next, mount the Pico on the breadboard, and connect the corresponding pins in the circuit to the Pico. The only other things on the breadboard are the transistor-resistor setup for the passive buzzers, and the resistors leading to GND for the buttons.

Box Assembly

IMG_0023.jpeg
IMG_0022.jpeg

In the 3D-printed design, there are 4 rectangular pieces. Glue them to the sides of the box, as shown. These make sure the box lid does not slide off of the box.

Next, use your soldering rod to burn a small hole in the box. 3D printing plastics like PLA melt around 60 degrees Celsius, and the soldering rod is at a temperature of 350-400 degrees Celsius. The hole is so the USB that runs from the Pico can connect to a power bank.

Final Circuit Integration

IMG_5580.jpg

Finally, use electrical tape to stick the PCBs to the lid of the holes in the lid and to stick the breadboard to the bottom of the box to ensure it does not move.

Slide the USB wire through the hole, and connect it to the Pico and the power bank.