The idea is that you either paint on the sensors or clip the wires to metallic objects so that when touched it triggers the playing an MP3 track.
It turned out to be a bit too complicated for a 9 year old, but my son likes to fall asleep and wake up to music and I had the idea that I would use the kit to make an interesting MP3 player on Alex's bedhead.
The first thing was to program the Arduino board, something I was now familiar with after the ticket gate project. The code examples provided were only for the triggering of single tracks, whereas I wanted proper player navigation. This included the ability to:
- Stop, play, pause and skip tracks
- Shuffle tracks
- Change the volume
- Play collections
By default the library used by the code only plays files named in the format TRACKxxx.mp3, where each x is a number 0 to 9. I did see some code for alternative names, but couldn't get it to work so I stuck with that format.
When the shuffle mode is selected random tracks are picked. Each played track is added to a list which is scanned to ensure that it is not repeated. Skipping a track forwards or backwards only moves to the next number and doesn't use random selection.
For playing collections I made a subdirectory, in my case called sleep and placed the relevant tracks in there. I also swapped the 128 MB microSD card for an 2 GB card I'd got with an old phone. This is the largest size supported by the FAT16 library.
The code, listed at the bottom of the post, is memory intensive and occasionally locks, but it mostly seems to work.
The need to manually copy and rename tracks made the player impractical for use by my son, who also likes current pop. Instead a I got him a portable bluetooth speaker and an old mobile phone with Google Play Music installed.
Still, after all that effort I decided to get my player working anyway and painted up a pre-primed MDF board with the stencil, affixing the Arduino board and speaker at the bottom. Each silhouette corresponds to a control. It's not intuitive, but it is artistic and I might add to the design later. I like the look of the bare white, gold and black Arduino as well, a nice reminder of what is usually hidden inside the designs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
Andrew's Touch MP3 player | |
------------------------------ | |
E0 - Stop | |
E1 - Play | |
E2 - Pause | |
E3 - | |
E4 - Skip backwards | |
E5 - Skip forwards | |
E6 - | |
E7 - Change Mode (Random/Continuous) | |
E8 - Sleep folder | |
E9 - Volume Down | |
E10 - Volume Up | |
E11 - Mute | |
Based on code by Bare Conductive code written by Stefan Dzisiewski-Smith and Peter Krige. | |
*******************************************************************************/ | |
// touch includes | |
#include <MPR121.h> | |
#include <Wire.h> | |
#define MPR121_ADDR 0x5C | |
#define MPR121_INT 4 | |
// mp3 includes | |
#include <SPI.h> | |
#include <SdFat.h> | |
#include <FreeStack.h> | |
#include <SFEMP3Shield.h> | |
// mp3 settings | |
#define TRACKCOUNT 130 | |
#define MINVOLUME 65278 | |
#define MAXVOLUME 15 | |
#define OFF 0 | |
#define ON 1 | |
#define CONTPLAY 1 | |
#define RANDPLAY 2 | |
// mp3 variables | |
SFEMP3Shield MP3player; | |
byte result; | |
int lastPlayed = 0; | |
int currentTrack = 0; | |
int playedTracks[TRACKCOUNT]; | |
long volume = 50; | |
int playMode = OFF; | |
int inct = 0; | |
int muted = OFF; | |
int sleep = OFF; | |
// mp3 behaviour defines | |
#define REPLAY_MODE TRUE | |
// touch behaviour definitions | |
#define firstPin 0 | |
#define lastPin 11 | |
// sd card instantiation | |
SdFat sd; | |
SdFile file; | |
void setup(){ | |
Serial.begin(57600); | |
pinMode(LED_BUILTIN, OUTPUT); | |
//while (!Serial) ; {} //uncomment when using the serial monitor | |
Serial.println("Bare Conductive Touch MP3 player"); | |
if(!sd.begin(SD_SEL, SPI_HALF_SPEED)) sd.initErrorHalt(); | |
if(!MPR121.begin(MPR121_ADDR)) Serial.println("error setting up MPR121"); | |
MPR121.setInterruptPin(MPR121_INT); | |
MPR121.setTouchThreshold(40); | |
MPR121.setReleaseThreshold(20); | |
result = MP3player.begin(); | |
MP3player.setVolume(volume,volume); | |
if(result != 0) { | |
Serial.print("Error code: "); | |
Serial.print(result); | |
Serial.println(" when trying to start MP3 player"); | |
} | |
} | |
void loop(){ | |
if (playMode == OFF) { | |
readTouchInputs(); | |
} else { | |
playMusic(); | |
} | |
} | |
void playMusic() { | |
int inct = 0; | |
if (playMode != OFF ) { | |
if (playMode == RANDPLAY) { | |
randomSeed(analogRead(0)); | |
if (currentTrack == 0) { | |
currentTrack = random(TRACKCOUNT); | |
} | |
MP3player.playTrack(currentTrack); | |
} | |
do { | |
switch(playMode) { | |
case RANDPLAY: | |
if (!MP3player.isPlaying()) { | |
MP3player.stopTrack(); | |
playedTracks[inct] = currentTrack; | |
do | |
currentTrack = random(TRACKCOUNT); | |
while (checkPlayed(inct, currentTrack)); | |
inct++; | |
MP3player.playTrack(currentTrack); | |
Serial.print("Playing track: "); | |
Serial.println(currentTrack); | |
} | |
break; | |
case CONTPLAY: | |
if (!MP3player.isPlaying()) { | |
inct++; | |
MP3player.stopTrack(); | |
currentTrack = inct; | |
MP3player.playTrack(inct); | |
Serial.print("Playing track: "); | |
Serial.println(currentTrack); | |
} | |
default: | |
break; | |
} | |
readTouchInputs(); | |
} while (inct < TRACKCOUNT); | |
playMode = OFF; | |
} | |
} | |
boolean checkPlayed(int i, int track) { | |
boolean played = false; | |
for (int j = 0; j < i + 1; j++) { | |
if (playedTracks[j] == track) { | |
played = true; | |
break; | |
} | |
} | |
return played; | |
} | |
void readTouchInputs(){ | |
if(MPR121.touchStatusChanged()){ | |
MPR121.updateTouchData(); | |
// only make an action if we have one or fewer pins touched | |
// ignore multiple touches | |
if(MPR121.getNumTouches()<=1){ | |
for (int i=0; i < 12; i++){ // Check which electrodes were pressed | |
if(MPR121.isNewTouch(i)){ | |
//pin i was just touched | |
Serial.print("pin "); | |
Serial.print(i); | |
Serial.println(" was just touched"); | |
digitalWrite(LED_BUILTIN, HIGH); | |
switch(i) { | |
case 0: | |
// Stop | |
playMode = OFF; | |
currentTrack = 0; | |
MP3player.stopTrack(); | |
Serial.println("Stopped playing"); | |
break; | |
case 1: | |
// Play | |
if (playMode == OFF) { | |
playMode = CONTPLAY; | |
MP3player.playTrack(currentTrack); | |
Serial.print("Playing track: "); | |
Serial.println(currentTrack); | |
} else { | |
MP3player.resumeMusic(); | |
Serial.print("Resume playing: "); | |
Serial.println(currentTrack); | |
} | |
break; | |
case 2: | |
// Pause | |
MP3player.pauseMusic(); | |
Serial.println("Paused music "); | |
break; | |
case 4: | |
// Skip backwards | |
MP3player.stopTrack(); | |
currentTrack = currentTrack - 1; | |
if (playMode == RANDPLAY && inct > 0) { | |
currentTrack = playedTracks[inct - 1]; | |
} | |
if (currentTrack < 0) { | |
currentTrack = 0; | |
} | |
MP3player.playTrack(currentTrack); | |
Serial.print("Skipped to track: "); | |
Serial.println(currentTrack); | |
break; | |
case 5: | |
// Skip forwards | |
MP3player.stopTrack(); | |
currentTrack = currentTrack + 1; | |
if (currentTrack == TRACKCOUNT) { | |
currentTrack = TRACKCOUNT - 1; | |
} | |
MP3player.playTrack(currentTrack); | |
Serial.print("Skipped to track: "); | |
Serial.println(currentTrack); | |
break; | |
case 7: | |
// Change play mode | |
MP3player.stopTrack(); | |
if (playMode == CONTPLAY) { | |
playMode = RANDPLAY; | |
randomSeed(analogRead(0)); | |
currentTrack = random(TRACKCOUNT); | |
} else { | |
playMode = CONTPLAY; | |
currentTrack = 0; | |
} | |
Serial.print("Changed mode to: "); | |
Serial.println(playMode); | |
break; | |
case 8: | |
// Sleep directory | |
if (sleep == OFF) { | |
if(!sd.chdir("sleep")){ // select our directory | |
Serial.println("error changing to sleep directory"); // error message if reqd. | |
} else { | |
MP3player.stopTrack(); | |
sleep = ON; | |
currentTrack = 0; | |
inct = 0; | |
MP3player.playTrack(currentTrack); | |
Serial.print("Current track: "); | |
Serial.println( currentTrack ); | |
Serial.println("Sleep directory selected"); | |
} | |
} else { | |
if(!sd.chdir()){ // select root | |
Serial.println("error changing to root directory"); // error message if reqd. | |
} else { | |
MP3player.stopTrack(); | |
sleep = OFF; | |
currentTrack = 0; | |
inct = 0; | |
MP3player.playTrack(currentTrack); | |
Serial.print("Current track: "); | |
Serial.println( currentTrack ); | |
Serial.println("Root directory selected"); | |
} | |
} | |
break; | |
case 9: | |
// Volume down | |
if ( volume < MINVOLUME - 1) { | |
volume = volume + 1; | |
} else { | |
volume = MINVOLUME; | |
} | |
Serial.print("Volume: "); | |
Serial.println(volume); | |
MP3player.setVolume(volume,volume); | |
break; | |
case 10: | |
// Volume up | |
if ( volume > MAXVOLUME + 1) { | |
volume = volume - 1; | |
} else { | |
volume = MAXVOLUME; | |
} | |
Serial.print("Volume: "); | |
Serial.println(volume); | |
MP3player.setVolume(volume,volume); | |
break; | |
case 11: | |
// Mute | |
if (muted == OFF) { | |
MP3player.setVolume(MINVOLUME,MINVOLUME); | |
Serial.println("Muted volume"); | |
muted = ON; | |
} else { | |
muted = OFF; | |
MP3player.setVolume(volume, volume); | |
Serial.println("Unmuted volume"); | |
} | |
break; | |
default: | |
break; | |
} | |
} else { | |
if(MPR121.isNewRelease(i)){ | |
Serial.print("pin "); | |
Serial.print(i); | |
Serial.println(" is no longer being touched"); | |
digitalWrite(LED_BUILTIN, LOW); | |
} | |
} | |
} | |
} | |
} | |
} |