Mapping Touch to Sound
Learn how to map electrodes to specific MP3 tracks using custom track tables, implement polyphonic playback, hold-to-loop audio, stop on release, and crossfade between tracks.
From Touch Event to Sound
The Touch Board combines the MPR121 sensor with a VS1053 audio codec and a microSD card slot. When you touch an electrode, the default firmware plays a file named TRACK000.mp3 for electrode 0, TRACK001.mp3 for electrode 1, and so on. That one-to-one mapping is a great starting point, but real projects almost always need more control. This tutorial shows you how to build custom track tables, play sounds polyphonically, loop audio on hold, and stop or crossfade on release.
The Default Playback Sketch
Before customising anything, it helps to understand the default behaviour. The Touch Board ships with a sketch that does roughly this:
#include <MPR121.h>
#include <SPI.h>
#include <SdFat.h>
#include <FreeStack.h>
#include <SFEMP3Shield.h>
SFEMP3Shield MP3player;
SdFat sd;
void setup() {
sd.begin(SD_SEL, SPI_HALF_SPEED);
MP3player.begin();
MPR121.begin(0x5C);
}
void loop() {
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) {
MP3player.stopTrack();
char filename[12];
sprintf(filename, "TRACK%03d.mp3", i);
MP3player.playMP3(filename);
}
}
}
}
This stops the current track and plays a new one every time any electrode is touched. That’s fine for a simple soundboard, but it prevents overlapping sounds and doesn’t respond to releases.
Custom Track Tables
If your project has more meaning to it — for instance, different areas of a map that play regional music, or body parts on a diagram that play different sounds — you want control over which file each electrode plays. A simple array lookup is all you need:
// Map each electrode (0–11) to a filename on the SD card
const char* trackTable[12] = {
"intro.mp3",
"drums.mp3",
"bass.mp3",
"melody.mp3",
"pad.mp3",
"fx01.mp3",
"fx02.mp3",
"voice.mp3",
"birds.mp3",
"rain.mp3",
"thunder.mp3",
"wind.mp3"
};
void loop() {
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) {
MP3player.stopTrack();
MP3player.playMP3(trackTable[i]);
}
}
}
}
The const char* array lives in SRAM. For 12 short filenames that’s fine. If you have longer filenames or need to save memory, store them in program memory using PROGMEM:
const char t0[] PROGMEM = "intro.mp3";
const char t1[] PROGMEM = "drums.mp3";
// ... etc
const char* const trackTable[12] PROGMEM = { t0, t1, /* ... */ };
Reading them back requires pgm_read_ptr() and strcpy_P(), which is more verbose but frees up valuable SRAM for other state.
Polyphonic Playback
The VS1053 codec supports a single active audio stream in MP3 mode. True hardware polyphony (multiple simultaneous MP3 streams) is not possible. However, you have two practical options:
Option 1 — Pre-mixed stems. If you know which combinations will be triggered together, mix them in Audacity before putting them on the SD card. This gives perfect audio quality with no runtime cost.
Option 2 — MIDI mode. Switch the VS1053 to MIDI mode instead of MP3 playback. MIDI is fully polyphonic (up to 8 simultaneous notes) and needs no audio files. See the MIDI Output tutorial for details.
For most beginner sound art projects, Option 1 is the right choice. Keep your track design in mind during the sound design phase — layer instruments into a single file rather than trying to mix in real-time.
Hold-to-Loop
A common interaction design pattern for installations and instruments is: tap to play once, hold to loop. The VS1053 supports looping through the setLoopMode() call:
#include <MPR121.h>
#include <SFEMP3Shield.h>
SFEMP3Shield MP3player;
unsigned long touchStart[12] = {0};
const unsigned long LOOP_THRESHOLD = 300; // ms — hold this long to loop
bool looping = false;
void setup() {
// ... init SD, MP3player, MPR121 as usual
}
void loop() {
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) {
touchStart[i] = millis();
looping = false;
MP3player.stopTrack();
// Start with loop mode off — play once first
MP3player.playMP3(trackTable[i]);
}
if (MPR121.isNewRelease(i)) {
if (looping) {
// Stop looping when finger lifts
MP3player.stopTrack();
looping = false;
}
}
}
}
// Check if any electrode is held past the loop threshold
for (int i = 0; i < 12; i++) {
if (MPR121.getTouchData(i) && !looping) {
if (millis() - touchStart[i] > LOOP_THRESHOLD) {
// Switch to loop mode
MP3player.stopTrack();
MP3player.playMP3(trackTable[i]);
// The SFEMP3Shield loop mode — keep calling playMP3
// in a non-blocking way, or use a loop flag and restart on track end
looping = true;
}
}
}
// Re-trigger looping track when it finishes
if (looping && !MP3player.isPlaying()) {
// Find the currently held electrode and restart its track
for (int i = 0; i < 12; i++) {
if (MPR121.getTouchData(i)) {
MP3player.playMP3(trackTable[i]);
break;
}
}
}
}
For cleaner loop behaviour, encode your audio files with no silence at the start or end, and use a sample rate that divides evenly into the loop length. A 2-bar loop at 120 BPM should be exactly 4.0 seconds long — no more, no less. See the MP3 Files tutorial for encoding advice.
Stopping on Release
For interactive installations you often want sound to stop the moment the hand leaves the electrode — like a theremin or a pressure-sensitive synth pad. Modify the release handler:
if (MPR121.isNewRelease(i)) {
// Only stop if this electrode was the one playing
if (currentTrack == i) {
MP3player.stopTrack();
currentTrack = -1;
}
}
Track the active electrode with a currentTrack variable (initialised to -1 to indicate “nothing playing”).
Volume Control
The VS1053 has a hardware volume register with a range of 0 (maximum) to 254 (silence). The SFEMP3Shield library exposes it:
// Set volume — lower number = louder
MP3player.setVolume(20, 20); // left channel, right channel
You can map touch duration or the number of simultaneous touches to volume for expressive control:
// Make sound louder the longer you hold
unsigned long held = millis() - touchStart[i];
uint8_t vol = map(constrain(held, 0, 2000), 0, 2000, 40, 5);
MP3player.setVolume(vol, vol);
Crossfading Between Tracks
Hardware crossfading requires two simultaneous audio streams, which the VS1053 doesn’t support in MP3 mode. You can fake a crossfade by rapidly alternating volume:
void crossfadeTo(const char* nextTrack) {
// Fade out current track over 500 ms
for (int vol = currentVol; vol <= 80; vol += 2) {
MP3player.setVolume(vol, vol);
delay(10);
}
MP3player.stopTrack();
// Start new track and fade in
MP3player.playMP3(nextTrack);
for (int vol = 80; vol >= currentVol; vol -= 2) {
MP3player.setVolume(vol, vol);
delay(10);
}
}
This is a blocking crossfade using delay() — it locks up touch detection for the duration. For non-blocking crossfade, replace delay() with a state machine driven by millis(). The crossfade effect is subtle with MP3 audio; for dramatic effect, design your tracks to musically transition at their boundaries instead.
Putting It Together — Full Example
#include <MPR121.h>
#include <SPI.h>
#include <SdFat.h>
#include <SFEMP3Shield.h>
SdFat sd;
SFEMP3Shield MP3player;
const char* trackTable[12] = {
"kick.mp3", "snare.mp3", "hihat.mp3", "bass.mp3",
"lead.mp3", "pad.mp3", "fx1.mp3", "fx2.mp3",
"atmos.mp3","bell.mp3", "vox.mp3", "end.mp3"
};
int8_t currentTrack = -1;
unsigned long touchStart[12] = {0};
void setup() {
Serial.begin(9600);
sd.begin(SD_SEL, SPI_HALF_SPEED);
MP3player.begin();
MP3player.setVolume(10, 10);
MPR121.begin(0x5C);
delay(500);
}
void loop() {
if (MPR121.touchStatusChanged()) {
MPR121.updateTouchData();
for (int i = 0; i < 12; i++) {
if (MPR121.isNewTouch(i)) {
touchStart[i] = millis();
currentTrack = i;
MP3player.stopTrack();
MP3player.playMP3(trackTable[i]);
}
if (MPR121.isNewRelease(i)) {
if (currentTrack == i) {
MP3player.stopTrack();
currentTrack = -1;
}
}
}
}
}
This gives you a foundation you can extend in any direction. The next tutorial covers everything you need to know about preparing your MP3 files for the best possible audio quality from the SD card.