Aller au contenu

Steel Drum Player

Introduction

The goal of our project is to automate the playing of a tongue drum (a melodic percussion instrument known for its soothing, meditative sound) using solenoids controlled by an ESP32. Each solenoid is assigned to a specific note, allowing precise control of the instrument through code. This system enables the instrument to be played autonomously, offering a relaxing musical background while the listener engages in other activities, such as meditating, studying, or simply unwinding.

A mobile application can be used to trigger the notes manually via Bluetooth, giving users interactive control over the instrument in real-time.

Demonstration of the final project

Material

NameQuantity
Sonic Energy Small Steel Tongue Drum (8 notes)1
12V DC Solenoids (~1 to 2A)8
ESP32 module1
12V DC Power supply (min. 3A)1
N-Channel MOSFET8
10kΩ resistor8
100Ω resistor8
100µF capacitor2
0.33µF capacitor1
0.1µF capacitor1
L7805 voltage regulator IC1
Diode8
3D-Printing Filament (PLA)
Rubber rings8
5mm CTP
Wires & prototyping boards

Model and structure

Each note of the tongue drum is played using a dedicated mechanical setup composed of a solenoid mounted on a custom 3D-printed support. The solenoid strikes a 3D-printed stick, which in turn hits the instrument to produce sound. To preserve the integrity of the tongue drum and reproduce the soft, percussive effect of a human hit, the tip of each stick is capped with a 3D-printed half-sphere, featuring a slot to insert a rubber ring. This rubber mimics the original mallet material, ensuring both sound quality and impact absorption.

Multiple revisions of the support were made, mostly to adapt its size and the mounting holes positions to the different solenoids we tested, but also to help better stabilize the mallet when the solenoid is actuated. To achieve this, we had to enlarge the to of the support where the mallet is mounted and we added an other support on the other side, to further reduce lateral movements.

The 4 revisions of the support (from right to left)

All solenoid systems and the electronic system are installed on a wooden board placed below the instrument. This placement protects the components from mechanical stress and ensures neat wiring. Special attention was given to vibration management and cable organization, to keep the setup clean, stable, and functional during extended use.

The final assembly with a solenoid

See the source project on OnShape

The Solenoids 

We chose to use solenoids rather than servo motors primarily to reduce mechanical noise, which is especially important when working with a relaxing instrument like the tongue drum. Solenoids offer a fast and direct linear motion that fits well with the percussive nature of the instrument, and they are quieter in operation when properly adjusted.

Each solenoid is mounted on a custom 3D-printed support that holds it in place and transfers the motion to a 3D-printed mallet. The support can be screwed into the wooden structure to prevent movement caused by actuation of the solenoids.

To achieve the desired sound quality without damaging the drum, we conducted numerous tests, varying the voltage, max current, distance from the drum, and angle of impact. These adjustments allowed us to find the optimal configuration for each note, balancing sufficient striking force with controlled motion.

To further reduce unwanted noise and mechanical wear, several modifications were made. The solenoids were lubricated to eliminate metal friction sounds, and the original metal plunger tip was replaced with a plastic one. We also added a felt layer to absorb shock and removed the return spring, as the solenoid naturally returns to its resting position due to its own weight and the vertical mounting.

On the support side, we improved damping by inserting plastic disks and reinforcing the mallet attachment to prevent rattling sounds caused by loose fittings. For the mallet tip, we opted to add a rubber ring around the 3D-printed half-sphere, as it produced a more pleasant sound compared to our initial idea of coating the tip with silicone.

Eletronics

To control the solenoids, we designed a simple driver circuit around N-Channel MOSFETs, allowing the ESP32 to switch each solenoid on and off independently using its GPIO pins.

Each solenoid is connected as follows:

  • One side of the solenoid is tied directly to +12V
  • The other side connects to the drain of an N-Channel MOSFET
  • The source of each MOSFET is connected to ground

To ensure sage and reliable operation:

  • A flyback diode is connected between the drain and +12V, to safely dissipate the inductive kickback produced when the solenoids is turned off
  • A 10kΩ resistor is placed between the gate and GND, to prevent floating input when the GPIO in unconfigured
  • A 100Ω resistor is added between each ESP32 GPIO pin and the gate of the corresponding MOSFET to limit inrush current

We used 8 indentical copies of this circuit, one for each solenoid.

The ESP32 is powered from the same 12V supply used for the solenoids, but stepped down to 5V using an L7805 linear voltage regulator. To stabilize the voltage and suppress noise:

  • A 0.33µF capacitor is connected between the input of the regulator and ground
  • A 0.1µF capacitor is connected between the output and ground
  • Additionnaly, two 100µF bulk capacitors are placed across the +12V and GND rails near the MOSFETs to absorb current spikes when the solenoids are activated

This setups ensures that both the ESP32 and the solenoids receive clean, stable power during operation.

For practical reasons, we separated our circuit onto two boards. The first one hosts the ESP32, L7805 voltage regulator and two solenoid control circuits and served as a proof of concept, while the second board is just an extension of the first one and hosts 6 more control circuits. Here’s the final board design we went with:

Code

One of the goals of this project was to be able to control our instrument through a mobile app. To make this, we chose to implement a simple Bluetooth client/server serial communication.

Controller (ESP32)

The ESP32 module serves as an interface between our electronic circuit and the mobile app we made to control the solenoids.

It runs a BLE (Bluetooth® Low Energy) server, based on the BLE SPP Server example code provided by Espressif, the manufacturer of the ESP32 chip. This example code handles the establishment of a connection with a client device and advertises itself as a device receiving serial data.

To control our electronics, we had to adapt this code. The modified source code for the controller is available on GitHub. All the new functionnalities we implemented have been put in a file named steel_drum_ctrl.c.

First, we created a setup function (drum_setup) that initializes all GPIO pins that we’re using as outputs.

void drum_setup() {
	gpio_config_t io_conf = {};
	io_conf.intr_type = GPIO_INTR_DISABLE;
	io_conf.mode = GPIO_MODE_OUTPUT;
	io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
	io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
	io_conf.pin_bit_mask = 0;

	for (int i = 0; i < NOTE_COUNT; i++) {
    		io_conf.pin_bit_mask |= (1ULL << gpios[i]);
	}

	gpio_config(&io_conf);

	for (int i = 0; i < NOTE_COUNT; i++) {
    		gpio_set_level(i, 0);
	}
}

Adapt the code to your setup

We’ve created a NOTE_COUNT macro and gpios array that contains the identifier of the pin assigned to each note at the top of this file, so that you can easily reconfigure it if you want to make this project for yourself.

This setup function is called in the app_main function of the BLE server, right after the Bluetooth has been initialized.

We then need to parse the messages received by the server (in the GATTS event handler function) to determine which note to play. This is implemented in the following function:

void drum_parse_command(const char* command, uint32_t len) {
    char* cmd = malloc(sizeof(char) * len);
    strcpy(cmd, command);

    char* cmdName = strtok(cmd, " ");
    if (cmdName == NULL) {
        free(cmd);
        return;
    }

    if (strcmp(cmdName, "play") == 0) {
        char* noteStr = strtok(NULL, " ");
        if (noteStr == NULL) {
            free(cmd);
            return;
        }
        
        int noteNumber = atoi(noteStr);
        drum_play_note(noteNumber);

        free(cmd);
        return;
    }
    free(cmd);
}

This function starts by copying the received string, as we’re using the strtok function to split it on whitespaces and this function overwrites the string given as an input. We then check that the string starts with play and contains a number, before calling the drum_play_note function.

void drum_play_note(int note) {
    if (note < 0 || note >= NOTE_COUNT) {
        ESP_LOGE("STEEL DRUM CTRL", "Unable to play note %d (out of acceptable range)\n", note);
        return;
    }
    gpio_set_level(gpios[note], 1);
    vTaskDelay(pdMS_TO_TICKS(NOTE_DURATION_MS));
    gpio_set_level(gpios[note], 0);
}

Adapt the code to your setup

If your reproduce this project, you may want to tweak the NOTE_DURATION_MS macro so your solenoids get enough power to get fully actuated.

This function checks that the received note is in the valid range [0, NOTE_COUNT - 1], then sets the corresponding GPIO output to HIGH for NOTE_DURATION_MS milliseconds.

Mobile app

To send messages to our controller, we made an Android app using the Android SDK and the Kotlin programming language. The source code for this app is available on GitHub. Alternatively, you can download a pre-built Application Package (APK) on the release page of the GitHub repository if you’re planning to reproduce the project as is.

The app is composed of 4 activities:

  • MainActivity serves as a landing screen when starting the app
  • DeviceSelectActivity scans for Bluetooth devices and lists them to the user
  • RemoteActivity displays buttons, each sending the corresponding play command to the selected Bluetooth device when the user taps them; from this activity, the user can also play a pre-defined melody
  • SettingsActivity allows the user to configure the behavior of certain parts of the app

These activities are standard Android acitivites, so their code won’t be covered in detail here.

To manage Bluetooth communication between the Android app and the ESP32 controller, the app uses two key components: BluetoothDiscoveryService and BluetoothService. These kotlin classes are helpers that encapsulate all the Bluetooth logic.

BluetoothDiscoveryService

This object handles device discovery using regular Bluetooth (not BLE). It performs the following tasks:

  • Initializes the device’s Bluetooth adapter
  • Starts discovery using BluetoothAdapter.startDiscovery() and listens for BluetoothDevice.ACTION_FOUND broadcasts
  • Triggers a user-defined callback (onDeviceFound) each time a device is found
BluetoothService

Once a device is selected, BluetoothService handles the connection and communication with the ESP32 using Bluetooth Low Energy (BLE). It performs the following steps:

  1. Connects to the selected BLE device using connectGatt()
  2. Discovers services and characteristics on the ESP32
  3. Locates the write characteristic (UUID: 0000abf1-...) used to send commands
  4. Offers a sendCommand(command: String) method that writes data to the controller over BLE
  5. Handles automatic connection, reconnection and error callbacks

It uses Android’s BluetoothGattCallback to listen for connection state changes and service discovery results.

Here’s an overview of the architecture:

Difficulties

During this project, we faced several challenges, both mechanical and electronic:

  1. Design and modeling of the solenoid support system
    One of the first major difficulties was designing the support system for the solenoids. We had to determine the correct dimensions (height, length) to ensure the mallets were properly positioned, while also keeping them balanced. The goal was for the mallet to rest horizontally when inactive, naturally leaning on the solenoid without causing imbalance or unwanted friction. Several iterations were needed to achieve a stable and functional setup.
  2. Reducing solenoid noise
    The main challenge of the project was minimizing the mechanical noise produced by the solenoids when activated. This unwanted noise affected the sound quality of the instrument, so we tested various solutions to dampen it as much as possible (adding insulating materials, mechanical adjustments, power tuning, etc.).
  3. Design and assembly of the electronic circuit
    Setting up the electronic circuit also required a lot of care. We had to make sure everything was thoroughly planned before soldering, as mistakes would be difficult to fix afterwards. The modeling phase was therefore essential, and precision was key during the actual assembly process to ensure all components were correctly connected.
  4. Adjusting the strike position for each note
    Lastly, we had to find the right angle and distance between each mallet and its corresponding tongue on the instrument. The strike needed to be both accurate and effective. This step involved many tests ,not particularly complex, but time-consuming, to achieve a consistent and harmonious sound.

Final shape / layout

The solenoids and mallets were arranged in a circular layout around the drum to ensure clean and even strikes. Several adjustments were made to the angle and position of each mallet to optimize the precision and consistency of the playing. A visual of the layout is shown in the picture below.

Conclusion

We successfully built a functional system capable of playing an instrument using solenoids controlled via a mobile application. The project achieved its main goal: allowing each note to be played individually and in real time.

There are still some aspects to improve, especially reducing the noise from the solenoids, which is still quite noticeable. Another improvement would be to add velocity control, allowing the strength of the hit to be adjusted for more expressive playing.

For future versions, we plan to expand the app’s functionality to make the system adaptable to different instrument sizes and layouts.

This project taught us a lot: 3D modeling, soldering, electronic circuit design, and how to use the Fablab machines. It also had a creative side: we could test everything by ear, and depending on our design choices, we could make the melody sound better or worse. It was really satisfying to experiment and improve the musical result as we went along.

Resources & links