Building an audio device #3: prototypes, interactions and code
The box at its most simplest should be able to start, stop and pause audio tracks in a playlist. The code below does this based on a few simple interactions.
In this prototype, there are two switches, wired into the underside of the box. Put the box down on a surface and both buttons are toggled on. Lift it up and they are toggled off. These interactions fire the goToSleep() and waken() functions, which in this case play respective salutary and valedictory audio clips.
While the box is being carried, both buttons on the underside are in the off position. Pressing one of the buttons pauses or unpauses any audio clip that is currently playing (togglePauseButton(). Pressing the other button loads up and plays the next audio clip in the list (skipToNextFile()).
You can see the prototype in use during this little vignette of our trial run:
/* adapted from http://www.ladyada.net/make/waveshield/libraryhc.html */
#include "WaveUtil.h"
#include "WaveHC.h"
SdReader card; // This object holds the information for the card
FatVolume vol; // This holds the information for the partition on the card
FatReader root; // This holds the information for the filesystem on the card
FatReader f; // This holds the information for the file we're play
WaveHC wave; // This is the only wave (audio) object,
// since we will only play one at a time
void setup() {
Serial.begin(9600); // set up serial port
pinMode(2, OUTPUT); // Set the output pins for the DAC control.
pinMode(3, OUTPUT); // These pins are defined in the library
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(13, OUTPUT); // pin13 LED
if (!card.init()) {
sdErrorCheck();
while(1); // then 'halt' - do nothing!
}
card.partialBlockRead(true); // enable optimize read - some cards may timeout.
// Disable if you're having problems
uint8_t part; // Now we will look for a FAT partition!
for (part = 0; part < 5; part++) { // we have up to 5 slots to look in
if (vol.init(card, part))
break; // we found one, lets bail
}
if (part == 5) { // if we ended up not finding one :(
sdErrorCheck(); // Something went wrong, lets print out why
while(1); // then 'halt' - do nothing!
}
if (!root.openRoot(vol)) { // Try to open the root directory
// Something went wrong,
while(1); // so 'hang' - do nothing!
}
// Whew! We got past the tough parts.
}
/* device state flags */
int paused = 0; // flag for whether we've activated a pause button
int awake = 0; // flag for whether the device is awake
/* button state flags */
int buttonValue_1 = 0; // value of button one's analogue input
int buttonState_1 = 0; // 'on' or 'off' value of button one's state
int buttonValue_2 = 0; // value of button two's analogue input
int buttonState_2 = 0; // 'on' or 'off' value of button two's state
int bothButtonsOn = 0; // default start value is all buttons off
int buttonOneOn = 0; // these values help to track
int buttonTwoOn = 0; // the state of buttons over time
int bothButtonsOff = 0; // over time
/* normal play mode for the device is to iterate over a predefined playlist */
const int NumberOfFiles = 9; // number of files in the playlist
char* fileList[NumberOfFiles] = { // ordered list of filenames in the playlist
"F005.WAV", // not only there does he see this sight, tim
"F007.WAV", // it's so easy to miss someone, tim
"F009.WAV", // his visitor, becka
"F006.WAV", // you always want to be somewhere else, tim
"F012.WAV", // lament, tim
"F020.WAV", // the haunter, becka
"F024.WAV", // we used to stand on the bridge, tim
"F021.WAV", // the phantom horsewoman, becka
"F001.WAV", // a curl of hair, tim
};
int filePointer = -1; // reference to the file which should
// be played at any time
char* fileToPlay; // reference to the name of file to play
/* device control functions */
void togglePauseButton() { // handles the pause button toggle
if (paused < 1){ // state is 'unpaused'
if (wave.isplaying){ // check if a wave is playing
wave.pause(); // if it is, pause it
paused = 1; // store 'paused' state
}
} else if (paused > 0){ // state is 'paused'
wave.resume(); // resume the paused wave
paused = 0; // store 'unpaused' state
}
}
void skipToNextFile() { // skips to next file in playlist
filePointer++; // increment the file reference
if (filePointer == NumberOfFiles){ // reset if we exceed
filePointer = 0; // the length of the playlist
}
if (wave.isplaying) { // already playing something, so stop it!
wave.stop();
}
fileToPlay = fileList[filePointer]; // pick the file to play
playfile(fileToPlay); // play it
}
void goToSleep() {
if (awake) { // if we're only now being sent to sleep,
fileToPlay = "F027.WAV"; // pick a specific 'goodbye' sound
playcomplete(fileToPlay); // and play it
}
awake = 0;
}
void waken() {
if (!awake) { // if we're only just waking up,
fileToPlay = "F026.WAV"; // pick a specific 'greeting' sound
playcomplete(fileToPlay); // and play it
}
awake = 1;
}
void loop() {
/* capture input */
buttonValue_1 = analogRead(0) / 4; // read analogue pin 0
buttonValue_2 = analogRead(1) / 4; // read analogue pin 1
if (buttonValue_1 > 128) { buttonState_1 = 1; } else { buttonState_1 = 0; }
if (buttonValue_2 > 128) { buttonState_2 = 1; } else { buttonState_2 = 0; }
// convert to boolean
if (buttonState_1 > 0 && buttonState_2 > 0) {
// both buttons pressed
if (bothButtonsOn) { // both buttons have been held in
// so device has been put down
goToSleep(); // send to sleep
} else { // first detection
/* - do nothing for now */
}
bothButtonsOn = 1; // remember button state for the next pass
buttonOneOn = 0; // all other buttons parameters are false
buttonTwoOn = 0;
bothButtonsOff = 0;
delay(1000); // wait a second
} else if (buttonState_1 > 0) { // only button 1 pressed
if (buttonOneOn) { // button has been held in
/* - we could add 'hold button one down' functionality here */
} else {
togglePauseButton(); // on first press, button 1 pauses / resumes
}
bothButtonsOn = 0;
buttonOneOn = 1; // remember button state for the next pass
buttonTwoOn = 0; // all other buttons parameters are false
bothButtonsOff = 0;
delay(1000);
} else if (buttonState_2 > 0){ // only button 2 pressed
if (buttonTwoOn) { // button has been held in
/* - we could add 'hold button two down' functionality here */
} else {
skipToNextFile(); // button two skips to next track
}
bothButtonsOn = 0;
buttonOneOn = 0;
buttonTwoOn = 1; // remember button state for the next pass
bothButtonsOff = 0; // all other buttons parameters are false
delay(1000);
} else { // neither button pressed
if (bothButtonsOff > 0){ // both buttons off for a second pass...
// we have been lifted up!
if (!awake){ // if we haven't already been woken up
waken(); // wake up
}
} else { // we must be inside the first pass
// of both buttons being switched 'off'
delay(1000); // await the second pass
}
bothButtonsOn = 0;
buttonOneOn = 0;
buttonTwoOn = 0;
bothButtonsOff = 1; // remember button state for the next pass
// all other buttons parameters are false
}
}
void playcomplete(char *name) { // Plays a full file from beginning to end
// pause is disabled
playfile(name); // call our helper to find and play this name
while (wave.isplaying) {
// do nothing while its playing
}
// now its done playing
}
void playfile(char *name) {
if (wave.isplaying) { // see if the wave object is currently doing something
// already playing something, so stop it!
wave.stop(); // stop it
}
if (!f.open(root, name)) { // look in the root directory and open the file
// do nothing, put an error message here
}
if (!wave.create(f)) { // OK read the file and turn it into a wave object
return;
}
wave.play(); // ok time to play! start playback
}
int freeRam(void) // this handy function will return the number of bytes
// currently free in RAM, great for debugging!
{
extern int __bss_end;
extern int *__brkval;
int free_memory;
if((int)__brkval == 0) {
free_memory = ((int)&free_memory) - ((int)&__bss_end);
}
else {
free_memory = ((int)&free_memory) - ((int)__brkval);
}
return free_memory;
}
void sdErrorCheck(void)
{
if (!card.errorCode()) return; // no error; get on with it!
while(1); // there's an error with the card
// so 'hang'
}