Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 11 of 11

Thread: Renaming a recorded WAV file on SD card: Teensy 3.6 + Audio board

  1. #1
    Junior Member
    Join Date
    Jun 2018
    Posts
    7

    Renaming a recorded WAV file on SD card: Teensy 3.6 + Audio board

    Hi all, a project I've been working on which goes through a wake-sleep cycle and records audio is working great, no humming noises/clicks, I2C LCD is great, RTC works, etc., but I'm encountering difficulty when trying create WAV files with variable/unique filenames (rather than predefined string literals/constants), based on the RTC's timestamp.

    The SD.h and corresponding functions seem to require "const char*", which is interferring with my ability to create a unique filename each time.

    I experimented with going through and removing the "const"s in the SD.h and SD.cpp, but that just resulted in no file being created at all (when feeding in "char*"), with silent failures occurring (and a "0") object being returned by the SD.open() command. However, specifying the constant "BOB.WAV" in the file.open() still works, even with the const's having been removed (making think there's something else somewhere causing it to fail with a non-const data type).

    Anybody have an solutions to get around this? My main desire is that each WAV file be uniquely named using the timestamp. I'm open to even initially writing a fixed filename like "temp.WAV", then renaming it to something with a timestamp, but I've encountered headwinds doing that (with different SD libraries eating each other) .

    Thank you for any help .

    Code:
    #include <SPI.h>
    #include <SD.h>
    #include <SD_t3.h>
    #include <SerialFlash.h>
    
    // for buttons
    #include <Bounce.h>
    
    #include <Audio.h>
    #include <Wire.h>
    
    // for RTC
    #include <TimeLib.h>
    
    
    
    //write wav
    unsigned long ChunkSize = 0L;
    unsigned long Subchunk1Size = 16;
    unsigned int AudioFormat = 1;
    unsigned int numChannels = 1;
    unsigned long sampleRate = 44100;
    unsigned int bitsPerSample = 16;
    unsigned long byteRate = sampleRate * numChannels * (bitsPerSample / 8); // samplerate x channels x (bitspersample / 8)
    unsigned int blockAlign = numChannels * bitsPerSample / 8;
    unsigned long Subchunk2Size = 0L;
    unsigned long recByteSaved = 0L;
    unsigned long NumSamples = 0L;
    byte byte1, byte2, byte3, byte4;
    
    //const int myInput = AUDIO_INPUT_LINEIN;
    const int myInput = AUDIO_INPUT_MIC;
    
    AudioPlaySdWav           audioSD;
    AudioInputI2S            audioInput;
    AudioOutputI2S           audioOutput;
    AudioRecordQueue         queue1;
    
    //recod from mic
    AudioConnection          patchCord1(audioInput, 0, queue1, 0);
    AudioConnection          patchCord2(audioSD, 0, audioOutput, 0);
    AudioConnection          patchCord3(audioSD, 0, audioOutput, 1);
    
    AudioControlSGTL5000     audioShield;
    
    
    // Bounce objects to easily and reliably read the buttons
    // example: Bounce <alias> = Bounce(<pin number>, delay-in-ms)
    Bounce buttonRecord = Bounce(32, 100);
    Bounce buttonStop =   Bounce(31, 100);  // 100 = 100 ms debounce time
    Bounce buttonPlay =   Bounce(30, 100);
    
    int mode = 0;  // 0=stopped, 1=recording, 2=playing
    File frec;
    elapsedMillis  msecs;
    
    // Use these with the Teensy Audio Shield
    #define SDCARD_CS_PIN    10
    #define SDCARD_MOSI_PIN  7
    #define SDCARD_SCK_PIN   14
    
    // Define LED to indicate when recording
    #define LED 13
    
    // declare variables used to hold the sleep-time length, and recording length
    // TODO: use these addresses to store updated cycle parameters
    unsigned int sleepaddr = 1;
    unsigned int recordaddr = 0;
    
    
    //////////////////// For setting up display //////////////////////////////
    // Include NewLiquidCrystal Library for I2C
    #include <LiquidCrystal_I2C.h>
    
    // Define LCD pinout
    const int  en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3;
    
    // Define I2C Address - change if reqiuired
    const int i2c_addr = 0x3F;
    
    LiquidCrystal_I2C lcd(i2c_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);
    
    bool screen_is_here = false;
    
    /////////////////////////////////////////////////////////////////////////
    
    //character array to store changing filename
    char fname[16];
    
    
    void setup() {
    
    
      //check if screen is plugged in
      Wire.begin();
      {
        Wire.beginTransmission(i2c_addr);
        if (Wire.endTransmission() == 0) {
    
          // Set the screen_is_here flag to 'true'
          screen_is_here = true;
    
          // Set display type as 16 char, 2 rows
          lcd.begin(16, 2);
    
          // Print on first row
          lcd.setCursor(0, 0);
          lcd.print("Recording Test");
    
          // Wait 1 second
          delay(1000);
    
          // Print on second row
          lcd.setCursor(0, 1);
          lcd.print("Press any button.");
    
          // Wait 8 seconds
          delay(8000);
    
          // Clear the display
          lcd.clear();
        }else{
          // execute automated stuff
         }
      }
    
      // set the Time library to use Teensy 3.0's RTC to keep time
      setSyncProvider(getTeensy3Time);
    
      // Setup name for new recording
      {
    
        String filename = month();
        filename += "-";
        filename += day();
        filename += "-";
        filename += hour();
        filename += "-";
        filename += minute();
        filename += ".WAV ";
    
        unsigned int stringlength = filename.length();
    
        filename.toCharArray(fname, stringlength);
      }
    
    
      // Configure the pushbutton pins
      pinMode(32, INPUT_PULLUP);
      pinMode(31, INPUT_PULLUP);
      pinMode(30, INPUT_PULLUP);
      pinMode(13, OUTPUT);
    
      Serial.begin(9600);
      AudioMemory(60);
      audioShield.enable();
      audioShield.inputSelect(myInput);
      audioShield.micGain(40);  //0-63
      audioShield.volume(0.5);  //0-1
    
      SPI.setMOSI(SDCARD_MOSI_PIN);
      SPI.setSCK(SDCARD_SCK_PIN);
    }
    
    
    /*
      TODO: In the main loop, there will be main functions:
      1. sleep
      2. record
      3. save
    
      TODO: In addition, there will be the functions for the menu, which listen to button input for changing
      the recording and sleep length.
    
    */
    
    
    void loop() {
      asm volatile(" WFI"); //Tell cpu to wait for interrupt... saves battery.
    
      if (screen_is_here) {
    
        // First, read the buttons
        buttonRecord.update();
        buttonStop.update();
        buttonPlay.update();
    
        // Respond to button presses
        if (buttonRecord.fallingEdge()) {
          Serial.println("Record Button Press");
    
          digitalWrite(LED, HIGH);
          //LCD section
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Recording...");
          if (mode == 2) stopPlaying();
          if (mode == 0) {
            startRecording();
          }
        }
        if (  buttonStop.fallingEdge())  {//incomingByte == '2'  ||
          Serial.println("Stop Button Press");
          digitalWrite(LED, LOW);
    
          //LCD section
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Stopped...");
          if (mode == 1) stopRecording();
          if (mode == 2) stopPlaying();
        }
        if ( buttonPlay.fallingEdge())  {// incomingByte == '3'  ||
          Serial.println("Play Button Press");
    
          //LCD section
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Playing...");
    
          if (mode == 1) stopRecording();
          if (mode == 0) startPlaying();
        }
        if (mode == 1) {
          continueRecording();
        }
      }
      else
      { /* What to do when the screen isn't here */
        Serial.println("Screen detection failed");
      }
    }
    
    String startRecording() { // TODO: Modify to take a time parameter?
    
      //frec = SD.open("BOB.WAV", FILE_WRITE);
      frec = SD.open(fname, FILE_WRITE);
      Serial.print(frec);
      Serial.println();
      Serial.println(fname);
      // if the dated initial file on SD is successfully created, then start recording.
      if (frec) {
        queue1.begin();
        Serial.println("Supposedly started recording to SD..."); //Execution never enters this block :-(...
        mode = 1;
        recByteSaved = 0L;
      }
      return fname;
    }
    
    void continueRecording() {
    
      if (queue1.available() >= 2) {
        byte buffer[512];
        memcpy(buffer, queue1.readBuffer(), 256);
        queue1.freeBuffer();
        memcpy(buffer + 256, queue1.readBuffer(), 256);
        queue1.freeBuffer();
        // write all 512 bytes to the SD card
        frec.write(buffer, 512);
        recByteSaved += 512;
        elapsedMicros usec = 0;
        Serial.print("SD write, us=");
        Serial.println(usec);
      }
    }
    
    void stopRecording() {
      Serial.println("stopRecording");
      queue1.end();
      if (mode == 1) {
        while (queue1.available() > 0) {
          frec.write((byte*)queue1.readBuffer(), 256);
          queue1.freeBuffer();
          recByteSaved += 256;
        }
        writeOutHeader();
        frec.close();
      }
      Serial.println("Setting mode to 0");
      mode = 0;
      Serial.println(mode);
    }
    
    
    void startPlaying() { //Not certain that we need this later.
      Serial.println("startPlaying");
      Serial.println(fname);
      Serial.println();
      //audioSD.play("BOB.WAV");
      audioSD.play(fname);
      mode = 2;
    
    }
    
    
    void stopPlaying() { //Not certain that we need this later.
      Serial.println("stopPlaying");
      if (mode == 2) audioSD.stop();
      mode = 0;
    }
    
    
    
    // This function is what turns the RAW audio into a WAV, by adding a header to the file.
    void writeOutHeader() { // update WAV header with final filesize/datasize
    
      //  NumSamples = (recByteSaved*8)/bitsPerSample/numChannels;
      //  Subchunk2Size = NumSamples*numChannels*bitsPerSample/8; // number of samples x number of channels x number of bytes per sample
      Subchunk2Size = recByteSaved;
      ChunkSize = Subchunk2Size + 36;
      frec.seek(0);
      frec.write("RIFF");
      byte1 = ChunkSize & 0xff;
      byte2 = (ChunkSize >> 8) & 0xff;
      byte3 = (ChunkSize >> 16) & 0xff;
      byte4 = (ChunkSize >> 24) & 0xff;
      frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
      frec.write("WAVE");
      frec.write("fmt ");
      byte1 = Subchunk1Size & 0xff;
      byte2 = (Subchunk1Size >> 8) & 0xff;
      byte3 = (Subchunk1Size >> 16) & 0xff;
      byte4 = (Subchunk1Size >> 24) & 0xff;
      frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
      byte1 = AudioFormat & 0xff;
      byte2 = (AudioFormat >> 8) & 0xff;
      frec.write(byte1);  frec.write(byte2);
      byte1 = numChannels & 0xff;
      byte2 = (numChannels >> 8) & 0xff;
      frec.write(byte1);  frec.write(byte2);
      byte1 = sampleRate & 0xff;
      byte2 = (sampleRate >> 8) & 0xff;
      byte3 = (sampleRate >> 16) & 0xff;
      byte4 = (sampleRate >> 24) & 0xff;
      frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
      byte1 = byteRate & 0xff;
      byte2 = (byteRate >> 8) & 0xff;
      byte3 = (byteRate >> 16) & 0xff;
      byte4 = (byteRate >> 24) & 0xff;
      frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
      byte1 = blockAlign & 0xff;
      byte2 = (blockAlign >> 8) & 0xff;
      frec.write(byte1);  frec.write(byte2);
      byte1 = bitsPerSample & 0xff;
      byte2 = (bitsPerSample >> 8) & 0xff;
      frec.write(byte1);  frec.write(byte2);
      frec.write("data");
      byte1 = Subchunk2Size & 0xff;
      byte2 = (Subchunk2Size >> 8) & 0xff;
      byte3 = (Subchunk2Size >> 16) & 0xff;
      byte4 = (Subchunk2Size >> 24) & 0xff;
      frec.write(byte1);  frec.write(byte2);  frec.write(byte3);  frec.write(byte4);
      frec.close();
    
      Serial.println("header written");
      Serial.print("Subchunk2: ");
      Serial.println(Subchunk2Size);
    
    }
    
    
    
    
    
    
    //////////// Below code the controls the overall system sleep  ////////////////
    // https://forum.pjrc.com/threads/33243-RTC-alarm-to-wake-up-from-deep-sleep //
    ///////////////////////////////////////////////////////////////////////////////
    
    /******************* Seting Alarm **************************/
    #define RTC_IER_TAIE_MASK       0x4u
    #define RTC_SR_TAF_MASK         0x4u
    
    void rtcSetup(void)
    {
      SIM_SCGC6 |= SIM_SCGC6_RTC;// enable RTC clock
      RTC_CR |= RTC_CR_OSCE;// enable RTC
    }
    
    void rtcSetAlarm(uint32_t nsec)
    {
      RTC_TAR = RTC_TSR + nsec;
      RTC_IER |= RTC_IER_TAIE_MASK;
    }
    
    /********************LLWU**********************************/
    #define WMUF5_MASK      0x20u
    
    static void llwuISR(void)
    {
    #if defined(HAS_KINETIS_LLWU_32CH)
      LLWU_MF5 |= WMUF5_MASK;
    #elif defined(HAS_KINETIS_LLWU_16CH)
      LLWU_F3 |= WMUF5_MASK; // clear source in LLWU Flag register
    #endif
      RTC_IER = 0;// clear RTC interrupts
    }
    
    void llwuSetup(void)
    {
      attachInterruptVector( IRQ_LLWU, llwuISR );
      NVIC_SET_PRIORITY( IRQ_LLWU, 2 * 16 );
    
      NVIC_CLEAR_PENDING( IRQ_LLWU );
      NVIC_ENABLE_IRQ( IRQ_LLWU );
    
      LLWU_PE1 = 0;
      LLWU_PE2 = 0;
      LLWU_PE3 = 0;
      LLWU_PE4 = 0;
      LLWU_ME  = LLWU_ME_WUME5; //rtc alarm
    }
    
    /********************* go to deep sleep *********************/
    // These are defined in the core now
    #define SMC_PMPROT_AVLLS_MASK   0x2u
    #define SMC_PMCTRL_STOPM_MASK   0x7u
    #define SCB_SCR_SLEEPDEEP_MASK  0x4u
    
    void goSleep(void) {
      //volatile unsigned int dummyread;
      /* Make sure clock monitor is off so we don't get spurious reset */
      // currently not set by anything I know, so the clock monitor is not set from reset
      MCG_C6 &= ~MCG_C6_CME0;
      //
      /* Write to PMPROT to allow all possible power modes */
      SMC_PMPROT = SMC_PMPROT_AVLLS_MASK;// Not needed already taken care of in here.
      /* Set the STOPM field to 0b100 for VLLSx mode */
      SMC_PMCTRL &= ~SMC_PMCTRL_STOPM_MASK;
      SMC_PMCTRL |= SMC_PMCTRL_STOPM(0x4); // VLLSx
    
      SMC_VLLSCTRL =  SMC_VLLSCTRL_VLLSM(0x3); // VLLS3
      /*wait for write to complete to SMC before stopping core */
      (void) SMC_PMCTRL;
    
      SYST_CSR &= ~SYST_CSR_TICKINT;      // disable systick timer interrupt
      SCB_SCR |= SCB_SCR_SLEEPDEEP_MASK;  // Set SLEEPDEEP bit to enable deep sleep mode (STOP)
      asm volatile( "wfi" );  // WFI instruction will start entry into STOP mode
      // will never return, but generates system reset
    }
    
    /*  From previously used example for how to activate the sleep/alarm
      void setup() {
       //
       // put your setup code here, to run once:
       flashLed(100);
       //
       rtcSetup();
       llwuSetup();
    
       rtcSetAlarm(5); // This value is seconds
       goSleep();
      }
    */
    
    //This is used by setup loop to specify where to get time from, pointing to RTC
    time_t getTeensy3Time()
    {
      return Teensy3Clock.get();
    }
    SD card is in the audio board, rather than the Teensy 3.6's SD slot.
    Last edited by MaxLee; 06-19-2018 at 06:04 PM. Reason: Additional info

  2. #2
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,743
    Quote Originally Posted by MaxLee View Post
    Hi all, a project I've been working on which goes through a wake-sleep cycle and records audio is working great, no humming noises/clicks, I2C LCD is great, RTC works, etc., but I'm encountering difficulty when trying create WAV files with variable/unique filenames (rather than predefined string literals/constants), based on the RTC's timestamp.
    Did you try to print the filename to Serial before open/creating the file on disk?
    Did you also check that the filename length is <8+3 (<8 characters before and 3 characters after the dot?

    I would exclude that String class is an issue , by simply using sprint to generate the filename.

  3. #3
    Junior Member
    Join Date
    Jun 2018
    Posts
    7
    Quote Originally Posted by WMXZ View Post
    Did you try to print the filename to Serial before open/creating the file on disk?
    Did you also check that the filename length is <8+3 (<8 characters before and 3 characters after the dot?

    I would exclude that String class is an issue , by simply using sprint to generate the filename.
    Thank you for replying! I appreciate it.

    Generating the string then converting to a char array doesn't seem to be an issue, as I've gotten it to print fine to serial and to an LCD display.

    Hmm... I didn't consider that there might be a length limit. It must be <8 then?

    I've been aiming for the format:
    (m)m-dd-(h)h-(m)m.WAV <--- (Where a single-digit month/hour/minute would be a single digit)

  4. #4
    Junior Member
    Join Date
    Jun 2018
    Posts
    7
    I got rid of the "-" character, which brought the timestamp down to 7 characters in length. Pressing the record, stop, then play button results in the following serial output:

    Code:
    Record Button Press
    0
    6191220.WAV
    Stop Button Press
    Play Button Press
    startPlaying
    6191220.WAV
    The lone "0" output is what is being returned by the "file.open()" command, and the execution flow doesn't go into the queue creation block.

  5. #5
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,743
    I would use mmddHHMM.wav, if you can force String to do so fine. I would use
    Code:
    sprintf(filename,"%02d%02d%02d%02d.wav",month,day,hour,minute);
    Also you must start the queue with
    Code:
    queue1.begin();
    at the end of setup()

  6. #6
    Junior Member
    Join Date
    Jun 2018
    Posts
    7
    Quote Originally Posted by WMXZ View Post
    I would use mmddHHMM.wav, if you can force String to do so fine. I would use
    Code:
    sprintf(filename,"%02d%02d%02d%02d.wav",month,day,hour,minute);
    I'm taking your wise advice here.

    You might also be right about the placement of that queue1.begin() statement, though it doesn't seem to have made a difference prior, nor did it seem to negatively affect recording. What's the rationale for having it be in the setup() block instead of its current location?

    At the moment, I'm still focusing trying to get audio to record via variably changing names.

  7. #7
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,743
    Quote Originally Posted by MaxLee View Post
    You might also be right about the placement of that queue1.begin() statement, though it doesn't seem to have made a difference prior, nor did it seem to negatively affect recording. What's the rationale for having it be in the setup() block instead of its current location?
    I typically have it at the end of setup, but you are write it could be also after opening the file, it only set a variable (enabled) to 1.

    I cannot find that you initialized SD after you set mosi and clk. I expect SD.begin(CS); or similar (caveat I'm not using SD but other filesystems)
    Not clear to me, why it works with BOB.WAV

  8. #8
    Junior Member
    Join Date
    Jun 2018
    Posts
    7
    Bless you.

    Yup, during one of my iterations, I chopped that bit out of my code.

    Everything is now working perfectly, including timestamped WAV files.

    I appreciate your help. Thank you!

  9. #9
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,743
    Quote Originally Posted by MaxLee View Post

    I appreciate your help. Thank you!
    You are very welcome, but thank the forum.

  10. #10
    Junior Member
    Join Date
    Jun 2018
    Posts
    7
    I have a few more details to touch up on, but I'll happily share a generalized version of my code here when finished. Feel free to incorporate anything you see into your other audio recorder project that you have going on.

    And again, thank you forum!
    Last edited by MaxLee; 06-19-2018 at 07:37 PM. Reason: To forum.

  11. #11
    Junior Member
    Join Date
    Jun 2018
    Posts
    7
    And here's the final code, very much a product of the good work of many folks here on this forum. For simplistic setups where a low-power, scheduled audio recording is needed, you'd be hard-pressed to beat this.

    Only outside requirement (outside of Arduino and Teensyduino libs) is "Newliquidcrystal" (I used v1.3.5).

    Take it, use it, enjoy.
    https://github.com/gaolaowai/TeensyOutdoorRecorder

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •