Writing to and playing wav files from an SD card (Advanced project Help!)

Ello! Names Victor and I've been playing with Arduino pretty much since summer. I got the wave shield and had no problem soldering it together and playing some lovely tunes. I don't have an issue in that realm. Seeing as the SD connections on the wave shield are the same on the datalogger shield I also had no issue programming the waveshield to log sensor data to my SD card. Now, the issue is combining the two.

My first go at it, my problem was RAM. I was running out..actually going to the negative zone (like -450 haha). With some memory optimization I was able to bring it down to -100 (Not really much better). So because I was not having much success, I was told of a nice library that was programmed to do something similar; the WaveRP library. I downloaded it and fell in love. It essentially did everything I wanted..but in blocked off parts (It needs user serial input) .

It plays wave files and also records them to the SD card. I used the base code in the example program and had no problem programming it to play wav files. When it came to logging the data to the SD card, I was actually able to get it to do it once and log it to an txt file. It logged weird unreadable values though.
Please help me if you can. I went to the adafruit forum and got little help. I'll post the code I used to finally log values (although useless and unreadable) to my SD card and play a wav file. Please help me get to my goal!

My goal is to essentially log the data streaming from the sensor, and if that sensor input passes a certain threshold, pause the data collecting, play a wave file, and once completed, return back to data collecting.
P.S. The code below is a slimed down version. I took out the functions I did not use. If you want the full thing, let me know. Sorry for any typos, it's pretty late.

Edit: Opps, looks like my code goes over the character limit. Trying to reduce without complicating program..Will post as soon as possible. In the mean time, conceptual advice is more than welcomed!

Thank you!!!

/**
 * Example Arduino record/play sketch for the Adafruit Wave Shield.
 * For best results use Wave Shield version 1.1 or later.
 *
 * The SD/SDHC card should be formatted with 32KB allocation units/clusters.
 * Cards with 2GB or less should be formatted FAT16 to minimize file
 * system overhead. If possible use SDFormatter from
 * www.sdcard.org/consumers/formatter/
 *
 * The user must supply a microphone preamp that delivers 0 - vref volts
 * of audio signal to the analog pin.  The preamp should deliver vref/2
 * volts for silence.
 *
 */
#include <SdFat.h>
#include <WaveRP.h>
#include <SdFatUtil.h>
#include <ctype.h>
// record rate - must be in the range 4000 to 44100 samples per second
// best to use standard values like 8000, 11025, 16000, 22050, 44100
#define RECORD_RATE 22050
//#define RECORD_RATE 44100
//
// max recorded file size.  Size should be a multiple of cluster size.
// the recorder creates and erases a contiguous file of this size.
// 100*1024*1024 bytes - about 100 MB or 150 minutes at 11025 samples/second
#define MAX_FILE_SIZE 104857600UL  // 100 MB
//#define MAX_FILE_SIZE 1048576000UL // 1 GB
//
// Analog pin connected to mic preamp
#define MIC_ANALOG_PIN 2
int sensorInput  = 0; //piezo sensor attached to arduino

//
// Voltage Reference Selections for ADC
//#define ADC_REFERENCE ADC_REF_AREF  // use voltage on AREF pin
 #define ADC_REFERENCE ADC_REF_AVCC  // use 5V VCC
//
// print the ADC range while recording if > 0
// print adcMax,adcMin if > 1
#define DISPLAY_RECORD_LEVEL 1
//
// print file info - useful for debug
#define PRINT_FILE_INFO 0
//
// print bad wave file size and SD busy errors for debug
#define PRINT_DEBUG_INFO 1
//------------------------------------------------------------------------------
// global variables
Sd2Card card;           // SD/SDHC card with support for version 2.00 features
SdVolume vol;           // FAT16 or FAT32 volume
SdFile root;            // volume's root directory
SdFile file;            // current file
WaveRP wave;            // wave file recorder/player
int16_t lastTrack = -1; // Highest track number
uint8_t trackList[32]; // bit list of used tracks
int sensorReading = analogRead(sensorInput);
//------------------------------------------------------------------------------
// print error message and halt
void error(char* str) {
  PgmPrint("error: ");
  Serial.println(str);
  if (card.errorCode()) {
    PgmPrint("sdError: ");
    Serial.println(card.errorCode(), HEX);
    PgmPrint("sdData: ");
    Serial.println(card.errorData(), HEX);
  }
  while(1);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// clear all bits in track list
void listClear(void)  {
  memset(trackList, 0, sizeof(trackList));
}
//------------------------------------------------------------------------------
// return bit for track n
uint8_t listGet(uint8_t n) {
  return (trackList[n >> 3] >> (n & 7)) & 1;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// set bit for track n
void listSet(uint8_t n) {
  trackList[n >> 3] |= 1 << (n & 7);
}

//------------------------------------------------------------------------------
// check for pause resume
void pauseResume(void) {
  if (!Serial.available()) return;
  uint8_t c = Serial.read();
  while (Serial.read() >= 0) {}
  if (c == 's') {
    wave.stop();
  } else if (c == 'p') {
    wave.pause();
    while (wave.isPaused()) {
      PgmPrintln("\nPaused - type 's' to stop 'r' to resume");
      while (!Serial.available()) {}
      c = Serial.read();
      if (c == 's') wave.stop();
      if (c == 'r') wave.resume();
    }
  }
}
//------------------------------------------------------------------------------
// play all files in the root dir
void playAll(void) {
  dir_t dir;
  char name[13];
  uint8_t np = 0;
  root.rewind();
  while (root.readDir(&dir) == sizeof(dir)) {
    //only play wave files
    if (strncmp_P((char *)&dir.name[8], PSTR("WAV"), 3)) continue;
    // remember current dir position
    uint32_t pos = root.curPosition();
    // format file name
    SdFile::dirName(dir, name);
    if (!playBegin(name)) continue;
    PgmPrintln(", type 's' to skip file 'a' to abort");
    while (wave.isPlaying()) {
      if (Serial.available()) {
        uint8_t c = Serial.read();
        while (Serial.read() >= 0) {}
        if (c == 's' || c == 'a') {
          wave.stop();
          file.close();
          if (c == 'a') return;
        }
      }
    }
    file.close();
    // restore dir position
    root.seekSet(pos);
  }
}
//------------------------------------------------------------------------------
// start file playing
uint8_t playBegin(char* name) {
  if (!file.open(&root, name, O_READ)) {
    PgmPrint("Can't open: ");
    Serial.println(name);
    return false;
  }
  if (!wave.play(&file)) {
    PgmPrint("Can't play: ");
    Serial.println(name);
    file.close();
    return false;
  }
#if PRINT_FILE_INFO
  Serial.print(wave.bitsPerSample, DEC);
  PgmPrint("-bit, ");
  Serial.print(wave.sampleRate/1000);
  PgmPrintln(" kps");
#endif // PRINT_FILE_INFO
#if PRINT_DEBUG_INFO
  if (wave.sdEndPosition > file.fileSize()) {
    PgmPrint("play Size mismatch,");
    Serial.print(file.fileSize());
    Serial.print(',');
    Serial.println(wave.sdEndPosition);
  }
#endif // PRINT_DEBUG_INFO
  PgmPrint("Playing: ");
  Serial.print(name);
  return true;
}
//------------------------------------------------------------------------------
// play a file
void playFile(char* name) {

  if (!playBegin(name)) return;
  PgmPrintln(", type 's' to stop 'p' to pause");
  while (wave.isPlaying()) {
    pauseResume();
  }
  file.close();
#if PRINT_DEBUG_INFO
  if (wave.errors()) {
    PgmPrint("busyErrors: ");
    Serial.println(wave.errors(), DEC);
  }
#endif // PRINT_DEBUG_INFO
}
//-----------------------------------------------------------------------------
void recordManualControl(void) {
  PgmPrintln("Recording - type 's' to stop 'p' to pause");
  uint8_t nl = 0;
  while (wave.isRecording()) {
#if DISPLAY_RECORD_LEVEL > 0
    wave.adcClearRange();
    delay(500);
#if DISPLAY_RECORD_LEVEL > 1
    Serial.print(wave.adcGetMax(), DEC);
    Serial.print(',');
    Serial.println(wave.adcGetMin(), DEC);
#else // #if DISPLAY_RECORD_LEVEL > 1
    Serial.print(wave.adcGetRange(), DEC);
    if (++nl % 8) {
      Serial.print(' ');
    } else {
      Serial.println();
    }
#endif // DISPLAY_RECORD_LEVEL > 1
#endif // DISPLAY_RECORD_LEVEL > 0
    // check for pause/stop
    pauseResume();
  }
}
//-----------------------------------------------------------------------------

Part 2

#define SAR_TIMEOUT 4
#define SAR_THRESHOLD 40
void recordSoundActivated(void) {
  uint32_t t;
  wave.pause();
  uint8_t n = 0;
  wave.adcClearRange();
  PgmPrintln("Recording - type 's' to stop");
  while (1) {
    if (wave.adcGetRange() >= SAR_THRESHOLD) {
      if (wave.isPaused()) {
        wave.resume();
        Serial.print('r');
        if (++n % 40 == 0) Serial.println();
      }
      t = millis();
      wave.adcClearRange();
    } else if (!wave.isPaused()) {
      if ((millis() - t) > 1000*SAR_TIMEOUT) {
        wave.pause();
        Serial.print('p');
        if (++n % 40 == 0) Serial.println();
      }
    }
    if (Serial.read() == 's') {
      wave.stop();
      return;
    }
  }
}
//------------------------------------------------------------------------------
// scan root directory for track list and recover partial tracks
void scanRoot(void) {
  dir_t dir;
  char name[13];
  listClear();
  root.rewind();
  lastTrack = -1;
  while (root.readDir(&dir) == sizeof(dir)) {
    // only accept TRACKnnn.WAV with nnn < 256
    if (strncmp_P((char *)dir.name, PSTR("TRACK"), 5)) continue;
    if (strncmp_P((char *)&dir.name[8], PSTR("WAV"), 3)) continue;
    int16_t n = 0;
    uint8_t i;
    for (i = 5; i < 8 ; i++) {
      char c = (char)dir.name[i];
      if (!isdigit(c)) break;
      n *= 10;
      n += c - '0';
    }
    // nnn must be three digits and less than 256
    if (i != 8 || n > 255) continue;
    if (n > lastTrack) lastTrack = n;
    // mark track found
    listSet(n);
    if (dir.fileSize != MAX_FILE_SIZE) continue;
    // try to recover untrimmed file
    uint32_t pos = root.curPosition();
    if (!trackName(n, name)
      || !file.open(&root, name, O_READ |O_WRITE)
      || !wave.trim(&file)) {
      if (!file.truncate(0)) {
        PgmPrint("Can't trim: ");
        Serial.println(name);
      }
    }
    file.close();
    root.seekSet(pos);
  }
}

//------------------------------------------------------------------------------
// format a track name in 8.3 format
uint8_t trackName(int16_t number, char* name) {
  if (0 <= number && number <= 255) {
    strcpy_P(name, PSTR("TRACK000.txt"));
    name[5] = '0' + number/100;
    name[6] = '0' + (number/10)%10;
    name[7] = '0' + number%10;
    return true;
  }
  PgmPrint("Invalid track number: ");
  Serial.println(number);
  return false;
}
//------------------------------------------------------------------------------
// play a track
void trackPlay(int16_t track) {
  char name[13];
  if (!trackName(track, name)) return;
  playFile(name);
}
//------------------------------------------------------------------------------
// record a track
void trackRecord(int16_t track, uint8_t mode) {
  char name[13];
  if (track < 0) track = lastTrack + 1;
  if (!trackName(track , name)) return;
  if (file.open(&root, name, O_READ)) {
    PgmPrint("Track already exists. Use '");
    Serial.print(track);
    Serial.print("d' to delete it.");
    file.close();
    return;
  }
  PgmPrint("Creating: ");
  Serial.println(name);
  if (!file.createContiguous(&root, name, MAX_FILE_SIZE)) {
    PgmPrintln("Create failed");
    return;
  }
  if(!wave.record(&file, RECORD_RATE, MIC_ANALOG_PIN, ADC_REFERENCE)) {
    PgmPrintln("Record failed");
    file.remove();
    return;
  }
  if (mode == 'v') {
    recordSoundActivated();
  } else {
    recordManualControl();
  }
  // trim unused space from file
  wave.trim(&file);
  file.close();
#if PRINT_DEBUG_INFO
  if (wave.errors() ){
    PgmPrint("busyErrors: ");
    Serial.println(wave.errors(), DEC);
  }
#endif // PRINT_DEBUG_INFO
}
//==============================================================================
// Standard Arduino setup() and loop() functions
//------------------------------------------------------------------------------
// setup Serial port and SD card
void setup(void) {
  Serial.begin(9600);
  delay(10);
  PgmPrint("\nFreeRam: ");
  Serial.println(FreeRam());
  if (!card.init()) error("card.init");
  if (!vol.init(&card)) error("vol.init");
  if (!root.openRoot(&vol)) error("openRoot");
  //nag();  // nag user about power and SD card
}
//------------------------------------------------------------------------------
// loop to play and record files.
void loop() {
  Serial.println(sensorReading);
  delay(00);// insure file is closed
  if (file.isOpen()) file.close();
  playFile("case1.wav");
  delay(100);  // scan root dir to build track list and set lastTrack
  scanRoot();
  //while (Serial.read() >= 0) {}
//PgmPrintln("\ntype a command or h for help");
  int16_t track = -1;
  uint8_t c;
  c = sensorReading;
  /*while(track < 256){                   // Victor here! This is pretty much all serial inputs.
    while (!Serial.available()) {}       // not needed for my needs.
    c = Serial.read();
    if (!isdigit(c)) break;
    track = (track < 0 ? 0 : 10 * track) + c - '0';
  }*/
  if (track < 0 && (c == 'd' || c == 'p')) {
    if (lastTrack < 0) {
      PgmPrintln("No tracks exist");
      return;
    }
    track = lastTrack;
  }
  //Serial.println(); 
  if (c == 'x') playAll();           // C is an int so the char based functions won't activate
  //else if (c == 'c') trackClear();
  //else if (c == 'd') trackDelete(track);
  //else if (c == 'h') help();
  //else if (c == 'l') listPrint();
  else if (c == 'p') trackPlay(track);
  else if (c == '100' || c == '200') playFile("case1.wav");
  else trackRecord(track, c);
}

thats quite a bit to wade through

pull all functions and text not relevant to the problem

Where are the logging to file calls? i cant see em.. and your only reading the sensor input the once when declaring the variable sensorreading, adding a sensor reading call at the end of the main loop will load it for the next time round.

your main loop should look more like

loop{

read sensor
log sensor
if sensor > threshold
do over threshold sound stuff

capture user input

switch case user input

case 1

}

thats quite a bit to wade through

pull all functions and text not relevant to the problem

Where are the logging to file calls? i cant see em.. and your only reading the sensor input the once when declaring the variable sensorreading, adding a sensor reading call at the end of the main loop will load it for the next time round.

your main loop should look more like

loop{

read sensor
log sensor
if sensor > threshold
do over threshold sound stuff

capture user input

switch case user input
.. call functions to perform User directed action

}

Thank you for the reply!
Yea, I understand. I am actually re-writing the code from scratch since there was so much extra baggage in the example.

I like your method but the problem is I won't have a user input (serial wise). The system would need to simply do it by itself. My main problem is finding a way to stop the log functions to play the wav files since the arduino can't do both.

It would look like this

loop(x){
read sensor
log sensorInput
if (sensorinput > threshold)
pause log sensorInput
play wav file
continue log sensorInput
}

I just don't know how to pause and continue the log functions. There are functions like that for wav like wav.pause and wave.play. I also thought about a delay, but that would delay the entire system, not just the data logging.

the clue is in the playFile function from your example..

// play a file
void playFile(char* name) {

  if (!playBegin(name)) return;
  PgmPrintln(", type 's' to stop 'p' to pause");
  while (wave.isPlaying()) {                            //HINT HINT!!
    pauseResume();
  }
  file.close();

#if PRINT_DEBUG_INFO
  if (wave.errors()) {
    PgmPrint("busyErrors: ");
    Serial.println(wave.errors(), DEC);
  }
#endif // PRINT_DEBUG_INFO
}

Woah. Never thought about it. So it should look something like

loop{
sensorInput
while (!wav.play){
log sensorInput
}
if (sensorInput> threshold)
wav.play
}
logsensorInput

Not sure if I used the (!wav.play) correctly

Coldsoldier:
Woah. Never thought about it. So it should look something like

loop{
sensorInput
while (!wav.play){
log sensorInput
}
if (sensorInput> threshold)
wav.play
}
logsensorInput

Not sure if I used the (!wav.play) correctly

in the example the playfile function plays the file and then while its playing loops continually calling the
pasueresume() function which i guess (cos im too lazy to check) is listening on the pins assigned to pause and resume buttons for input,

to play a file without a break from user input from buttons a new PlayUninteruptedFile() fuction (im sure you can name it better) could be made by copying the original and deleting the call to pauseResume() so your new version just loops while the file is playing, and when complete drops out of the funtion back to your main loop.

so you have a function to play a file with pause resume ability when you want that and a playfile till finished funtion to call when you want the file to play and do nothing till finished

A lot of arduino libraries come with a manual which lists the functions thier paramaters and return values, if not present or available easily online, the .h files or header files contain the information too These are generally very well annotated to make things clearer too.

I see. I'm not sure I'm understanding. The problem is that I can't add the .pause to the Logfile/logData functions so I can't stop them and play my wav file. The arduino does not have the capability to play wave files and log data to the SD card at the same time. The pauseResume takes a user inputed via serial and runs it to a database where it processes a command based on the input. I could change that and make it based on what my sensor is reading like

c = analogRead(sensorInput);
while(wav.isPlaying){
if (c = 100;)
wave.stop
}

I want to do this with my logData functions. But I don't think it allows you to, atleast the way I've tried (As in you logData.pause is not recognized)

Something like this is what would be ideal in a perfect world

c = sensorReading
while (logData){
if c = 100;
logData.pause
playFile("case1.wav);
delay(1000)
logData;

It might be better for me to make a log function with a break thrown in

void awesomeLog(charname){
log data(char
name);
if (wave.IsPlaying()){
break;
}
else (){
logData;
}
}

Would this be a better solution?

ok i will have a look at pauseResume() - ok takes serial input but acts as i suggested

I think you may be "too deep in the forest to see any trees"

look at playfile again.

and then tell me what you think this will do

 while (wave.isPlaying()) {}

It looks like it calls the function and does nothing if a wav file is being played from the SD card.

not quite, check the references, while refference

what would

while(42){}
do if it was the first line of your main loop?

the condition for a while statement must equate to true or false where false is 0 and true is any other value

and the statements executed within a while LOOP repeat until the condition tested is false

so while(wave.isPlaying()){} will sit and do nothing because it has nothing to do within its { and } and will continue doing nothing until wave.isPalying()=False, when it has finished playing.

Oh I see. I assumed that if a function was called before the while loop, it would continue to run even when the while loop returns true and does nothing. It was my thought the while loop would do nothing as in (do nothing new) and any other functions called before it would continue to run.

loop{
logData
while(wave.isPlaying){};
}

Would this still run the logData function even when it's called outside of the while loop? or would the while loop stop running the log function?

I see your point now though. I was over thinking it a bit.

the best way to investigate whats happening in a small sketch is to load it with serial write and print statements to tell you whats happening where you are and what value your control variables are.

for example in a loop using int x as a counter if it should stop before x reaches 10
and you have a print statement or 2 printing out x within that loop and you see 11 in the output you know there is a problem there :wink:

Thank you for all of your help! I'll give it a try and see if I can't get this thing to do what I need. I'll post back my results/error.

Ah, I reprogrammed it and got REALLY close to getting everything to work the way I want.
I ran into a small problem though. I get ("error file is open")

Heres my setup and Loop

// setup Serial port and SD card
void setup(void) {
  Serial.begin(9600);
  delay(10);
  PgmPrint("\nFreeRam: ");
  Serial.println(FreeRam());
  if (!card.init()) error("card.init");
  if (!vol.init(&card)) error("vol.init");
  if (!root.openRoot(&vol)) error("openRoot");
  //nag();  // nag user about power and SD card
 
}
//------------------------------------------------------------------------------
// loop to play and record files.
void loop() {
  Serial.println(sensorReading);
 delay(100);
// insure file is closed
  //if (file.isOpen()) file.close();
  if (sensorReading > 100){
  playFile("case1.wav");
  delay(100); 
 }
 while (wave.isPlaying()){};
 delay(1000);
 firstSetup();
 logTime += 1000UL*SAMPLE_INTERVAL_MS;
  
  // Wait for log time.
  int32_t diff;
  do {
    diff = micros() - logTime;
  } while (diff < 0);
  
  // Check for data rate too high. 
  if (diff > 10) error("Missed data record");
  
  logData();
}

The error comes from this function. I'll highlight/bold the exact line of code.

void firstSetup() {
  const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
  char fileName[13] = FILE_BASE_NAME "00.CSV";
  
  Serial.begin(9600);
  
  // Initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
  // breadboards.  use SPI_FULL_SPEED for better performance.
  if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt();
  
  // Find an unused file name.
  if (BASE_NAME_SIZE > 6) {
    error("FILE_BASE_NAME too long");
  }
  while (sd.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      error("Can't create file name");
    }
  }
/-----------------------------------------------------------------------------------------------------
  if (!file.open(fileName, O_CREAT | O_WRITE | O_EXCL)) error("file.open");    //HERE!!!
  do {
    delay(10);
  } 
/--------------------------------------------------------------------------------------------------------
while (Serial.read() >= 0);
  Serial.print(F("Logging to: "));
  Serial.println(fileName);
  Serial.println(F("Type any character to stop"));
  
  // Write data header.
  writeHeader();
  
  // Start on a multiple of the sample interval.
  logTime = micros()/(1000UL*SAMPLE_INTERVAL_MS) + 1;
  logTime *= 1000UL*SAMPLE_INTERVAL_MS;
}

I don't understand why the error is showing up. I have plenty of ram so I'm not really sure how to approach it.

if the error is file open its probably a file that didnt get closed when you exited with a crash with the file open?

whats the file system and how does the arduino access it and flag a file as open? its going to be a byte somewhere in the file header but the file library your using may well have tools for this situation i dont know?
to avoid unexpected exits with a file open make a record of data in advance and verify it before opening the file, writing the record, and then closing the file asap.

might be as simple as If (file open){ close file}

,

Yea I used the if (file.open) file.close and solved the error but then I run into a different problem. Once I close the file, it completely stops logging data.

I solved it. Turned out I need to also close the file after running the wave file and then restart the setup. Thank you for your diligence and expertise! I really appreciate it :smiley:

no probs next thing on my learn how to do list is very similar..