MUSIC CONTROLLING GLOVE FOR DANCERS

by kkachikat in Circuits > Arduino

32 Views, 0 Favorites, 0 Comments

MUSIC CONTROLLING GLOVE FOR DANCERS

MUS CONT.jpg

When you are dancing in a big practice room, isn't it so annoying to go and fetch your phone/laptop to control music?


I made a glove that you can wear while dancing and control the music playing in the place without breaking your focus mode.


https://www.youtube.com/shorts/NZ7UXoIQ6ZE?si=MVC4MjujsmFEzejL



Overall feature

One click: play/pause (works)

Two clicks: 5 Seconds Backward (future feature)

Three clicks: 5 Seconds Forward (future feature)

Supplies

ESP32-C3 Wi-Fi or Bluetooth already soldered (1)

Li-Po Battery (3.7V 200mAh) (1)

TP4056 Charging Module C TYPE (1)

Wrist Brace / Guard (1)

Breadboard (1)

Jumper Wires (4)

P5 JS

Arduino IDE


To build this project, you will need the following hardware components. The total estimated cost is around 11,820 KRW.


Link to BOM


Assuming you already have this:


  1. Soldering Iron & Solder
  2. Micro USB-C Data Cable
  3. Multimeter (for voltage checking)




Soldering

Things to solder:

The end of the Lithium battery










TP4056 Charging Module

ESP32-C3



⚠️ Soldering Note: Special thanks to my dad for helping solder the sensitive lithium battery leads securely! Always double-check with a multimeter to ensure the voltage output sits steadily near 3.7V–4.2V before connecting it to the MCU to prevent short circuits.

Software & Interaction Logic

3.jpg

The architecture is divided into two parts: an Arduino firmware that broadcasts raw button states via Bluetooth Low Energy (BLE), and a p5.js web interface that decodes these wireless packets to update the audio timeline.


Put components on the breadboard









Arduino

Library --> Manage

Search and download

ESP32-C3 Dev Module & NimBLE-Arduino by h2zero


Copy the Arduino code and upload



#include <NimBLEDevice.h>

#define BUTTON_PIN 2 // change to your wiring

NimBLECharacteristic *pCharacteristic;
bool deviceConnected = false;

#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"

class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
deviceConnected = true;
}

void onDisconnect(NimBLEServer* pServer) {
deviceConnected = false;
}
};

void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);

NimBLEDevice::init("ESP32-C3-MUSIC");

NimBLEServer *pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());

NimBLEService *pService = pServer->createService(SERVICE_UUID);

pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
);

pService->start();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->start();
}

bool lastState = HIGH;

void loop() {
bool currentState = digitalRead(BUTTON_PIN);

if (deviceConnected && lastState == HIGH && currentState == LOW) {
// button pressed → PLAY
pCharacteristic->setValue("1");
pCharacteristic->notify();
delay(200);
}

if (deviceConnected && lastState == LOW && currentState == HIGH) {
// button released → PAUSE
pCharacteristic->setValue("0");
pCharacteristic->notify();
delay(200);
}

lastState = currentState;
}

P5.JS

Try the P5 JS code below. 1 button: play pause. You must upload a mp3 file on P5 Js.

It should look like this.



let mySound;

let bleDevice = null;

let bleCharacteristic = null;

let connectButton;



// BLE UUIDs

const SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";

const CHARACTERISTIC_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";



// Combo click variables for Bluetooth signals

let clickCount = 0;

let lastClickTime = 0;

const maxComboDelay = 400; // 0.4 seconds window for multi-clicks



function preload() {

soundFormats('mp3');

mySound = loadSound('practice_song.mp3');

}



function setup() {

// 시각적으로 시원하게 보이도록 500x500 크기로 설정!

createCanvas(500, 500);


connectButton = createButton('Connect Glove (BLE)');

connectButton.position(20, 20);

connectButton.style('padding', '8px 12px');

connectButton.mousePressed(connectBluetooth);

}



function draw() {

background(20); // Presentation 다크 테마

textAlign(CENTER, CENTER);


// 1. Music Status Display

if (mySound.isPlaying()) {

fill(57, 255, 20); // Neon Green when playing

textSize(32);

text("🎵 MUSIC PLAYING", width / 2, height / 2 - 40);


// Show current playback time live

let currentTime = mySound.currentTime().toFixed(1);

let duration = mySound.duration().toFixed(1);

fill(255);

textSize(24);

text(currentTime + "s / " + duration + "s", width / 2, height / 2 + 10);

} else {

fill(150); // Gray when paused

textSize(32);

text("⏸️ MUSIC PAUSED", width / 2, height / 2 - 20);

}


// 2. Beautiful Interface Guide Lines (All English)

stroke(40);

line(40, height - 120, width - 40, height - 120);


noStroke();

textSize(14);

fill(130);

textAlign(LEFT, CENTER);

let startX = width / 2 - 120;

text("• 1 Click : Play / Pause", startX, height - 90);

text("• 2 Clicks : Jump 5s Backward", startX, height - 65);

text("• 3 Clicks : Jump 5s Forward", startX, height - 40);

}



async function connectBluetooth() {

try {

console.log("Searching for ESP32-C3-MUSIC...");

bleDevice = await navigator.bluetooth.requestDevice({

filters: [{ services: [SERVICE_UUID] }]

});



console.log("Device selected. Connecting...");

const server = await bleDevice.gatt.connect();


const service = await server.getPrimaryService(SERVICE_UUID);

bleCharacteristic = await service.getCharacteristic(CHARACTERISTIC_UUID);



await bleCharacteristic.startNotifications();

bleCharacteristic.addEventListener('characteristicvaluechanged', handleBLEData);



console.log("Wireless Bluetooth Connected Successfully!");

connectButton.hide();


} catch (error) {

console.error("Bluetooth Connection Failed:", error);

}

}



// Handles raw wireless signals and triggers multi-click combos

function handleBLEData(event) {

let value = event.target.value;

let decoder = new TextDecoder('utf-8');

let rawString = decoder.decode(value);

let cleanData = rawString.replace(/[\r\n\t ]/g, "").trim();



// "1" means the physical button on the glove was just pressed down

if (cleanData === "1" || cleanData.includes("1")) {

clickCount++;

lastClickTime = millis();


// Set a slight timeout to evaluate the action after the click window closes

setTimeout(executePlaybackAction, maxComboDelay);

}

}



// Decodes the counted clicks into exact music control commands

function executePlaybackAction() {

// Only evaluate when the user has finished tapping inside the 400ms window

if (clickCount > 0 && (millis() - lastClickTime) > maxComboDelay) {


// --- 1 CLICK: PLAY / PAUSE ---

if (clickCount === 1) {

if (mySound.isPlaying()) {

mySound.pause();

console.log("Action -> Paused");

} else {

mySound.play();

console.log("Action -> Playing");

}

}


// --- 2 CLICKS: JUMP 5 SECONDS BACKWARD ---

else if (clickCount === 2) {

let targetTime = mySound.currentTime() - 5;

if (targetTime < 0) targetTime = 0; // Boundary check

mySound.jump(targetTime);

console.log("Action -> Jumped -5s");

}


// --- 3 CLICKS: JUMP 5 SECONDS FORWARD ---
else if (clickCount === 3) {

let targetTime = mySound.currentTime() + 5;

if (targetTime > mySound.duration()) targetTime = mySound.duration(); // Boundary check

mySound.jump(targetTime);

console.log("Action -> Jumped +5s");

}


// Reset click counter for the next interaction

clickCount = 0;

}

}



What Works and What Not Works

✅ Hardware Wiring: Correct breadboard layout for glove placement.

✅ WebSerial Connect: Successful port pairing between browser and chip.

✅ the lithium battery


❌ Double/Triple Click.

Double: 5 sec backward

Triple: 5 sec forward