Playing two (or more) RAW files

Status
Not open for further replies.

grubba

Member
I'm having a bit of a hard time playing more than one (raw) file at a time. Even using the Part_2_02_Mixers.ino tutorial code shows the same problem. Most of the time SD.open() fails or when it works, the sound is garbled. Playing one file at a time (one from each instance of the SD raw file player) works fine. Only when playing with two simultaneously this happens.

My project is a simple Sound Machine (noise machine), which plays an audio loop indefinitely along with an alarm clock. It works in conjunction with an ESP8266, which handles the TCP/IP stack for getting the time, integration with the rest of my automation system, and also handling the (clock) LED array (more on that (OTA) later on a different thread -- in fact, the reason I'm doing the bulk on the work on the ESP8266 is the ease of uploading updates).

For that I created a new derivation of AudioStream, using AudioPlaySdRaw as a starting point.

The main question: Is it really possible to play more than one file at a time? The SD header file has a comment on the open method that says otherwise:

"Note that currently only one file can be open at a time."

For what's worth, here is my AudioPlaySdRawLoop class, which plays mono or stereo RAW files and optionally loops them.

Header:

Code:
#ifndef play_raw_h_
#define play_raw_h_

#include "AudioStream.h"
#include "SD.h"

class AudioPlaySdRawLoop : public AudioStream
{
public:

    AudioPlaySdRawLoop();

    void        begin           ();
    bool        play            (const char *filename, bool stereo = false, bool loop = true);
    void        stop            ();
    bool        isPlaying       () { return _playing; }
    uint32_t    positionMillis  ();
    uint32_t    lengthMillis    ();
    void        update          ();
    void        endLoop         () { _loop = false; }
    //-- For debugging
    uint32_t    rewindTime      () { return _rewindTime; }

private:
    uint32_t   _rewind         (uint32_t n);
    void       _transmit       (audio_block_t* blockL, audio_block_t* blockR = NULL);

private:
    bool                _stereo;
    bool                _loop;
    File                _rawfile;
    uint32_t            _read_size;
    uint32_t            _file_size;
    volatile uint32_t   _file_offset;
    volatile bool       _playing;
    uint8_t             _buffer[(AUDIO_BLOCK_SAMPLES * 2) * 2];
    volatile uint32_t   _rewindTime;
};

#endif

And the implementation:

Code:
#include "soundmachine.h"
#include "play_raw.h"
#include "spi_interrupt.h"

#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT)

AudioPlaySdRawLoop::AudioPlaySdRawLoop()
    : AudioStream(0, NULL)
{
    begin();
}

//-----------------------------------------------------------------------------
void
AudioPlaySdRawLoop::begin()
{
    _stereo = false;
    _playing = false;
    _file_offset = 0;
    _file_size = 0;
    _read_size = ((AUDIO_BLOCK_SAMPLES * 2));
    _rewindTime = 0;
}

//-----------------------------------------------------------------------------
bool
AudioPlaySdRawLoop::play(const char *filename, bool stereo, bool loop)
{
    stop();
    Serial.print("Playing ");
    Serial.print(filename);
    if(stereo)
        Serial.println(" Stereo");
    else
        Serial.println(" Mono");
    AudioStartUsingSPI();
    __disable_irq();
    _rawfile = SD.open(filename);
    __enable_irq();
    if (!_rawfile) {
        AudioStopUsingSPI();
        return false;
    }
    _stereo         = stereo;
    _loop           = loop;
    if(_stereo)
        _read_size  = ((AUDIO_BLOCK_SAMPLES * 2) * 2);
    else
        _read_size  = (AUDIO_BLOCK_SAMPLES * 2);
    _file_size      = _rawfile.size();
    _file_offset    = 0;
    _rewindTime     = 0;
    _playing        = true;
    return true;
}

//-----------------------------------------------------------------------------
void
AudioPlaySdRawLoop::stop()
{
    __disable_irq();
    if (_playing) {
        _playing = false;
        _rawfile.close();
        __enable_irq();
        AudioStopUsingSPI();
    } else {
        __enable_irq();
    }
}

//-----------------------------------------------------------------------------
void
AudioPlaySdRawLoop::update()
{
    if (!_playing)
        return;
    bool close = false;
    audio_block_t* blockL = NULL;
    audio_block_t* blockR = NULL;
    blockL = allocate();
    if (!blockL)
        return;
    if(_stereo) {
        blockR = allocate();
        if (!blockR) {
            release(blockL);
            return;
        }
    }
    if (_rawfile.available()) {
        uint32_t n = _rawfile.read(_buffer, _read_size);
        _file_offset += n;
        if (_file_offset >= _file_size || n < _read_size) {
            if(_loop)
                _rewind(n);
            else {
                //-- Zero out rest of buffer and be done with it.
                memset(&_buffer[n], 0, _read_size - n);
                close = true;
            }
        }
        _transmit(blockL, blockR);
    } else {
        if(_loop) {
            if(_rewind(0))
                _transmit(blockL, blockR);
        } else
            close = true;
    }
    release(blockL);
    if(blockR)
        release(blockR);
    if(close) {
        _rawfile.close();
        AudioStopUsingSPI();
        _playing = false;
    }
}

//-----------------------------------------------------------------------------
uint32_t
AudioPlaySdRawLoop::positionMillis()
{
    if(_stereo)
        return ((uint64_t)_file_offset * B2M) >> 32;
    else
        return ((uint64_t)_file_offset * (B2M >> 1)) >> 32;
}

//-----------------------------------------------------------------------------
uint32_t
AudioPlaySdRawLoop::lengthMillis()
{
    if(_stereo)
        return ((uint64_t)_file_size * B2M) >> 32;
    else
        return ((uint64_t)_file_size * (B2M >> 1)) >> 32;
}

//-----------------------------------------------------------------------------
uint32_t
AudioPlaySdRawLoop::_rewind(uint32_t n)
{
    uint32_t t = micros();
    _rawfile.seek(0);
    _file_offset = 0;
    n = _rawfile.read(&_buffer[n], _read_size - n);
    _file_offset += n;
    _rewindTime = micros() - t;
    return n;
}

//-----------------------------------------------------------------------------
void
AudioPlaySdRawLoop::_transmit(audio_block_t* blockL, audio_block_t* blockR)
{
    if(_stereo) {
        uint8_t  lsb, msb;
        uint8_t* ptr = _buffer;
        for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
            lsb = *ptr++;
            msb = *ptr++;
            blockL->data[i] = (msb << 8) | lsb;
            lsb = *ptr++;
            msb = *ptr++;
            blockR->data[i] = (msb << 8) | lsb;
        }
    } else {
        memcpy(blockL->data, _buffer, (AUDIO_BLOCK_SAMPLES * 2));
    }
    transmit(blockL, 0);
    if(_stereo)
        transmit(blockR, 1);
    else
        transmit(blockL, 1);
}
 
Try editing hardware/teensy/avr/libraries/SD/SD_t3.h. Find any uncomment this:

Code:
// This Teensy 3.x optimized version is a work-in-progress.
// Uncomment this line to use the Teensy version.  Otherwise,
// the normal SD library is used.
//#define USE_TEENSY3_OPTIMIZED_CODE

Please let me know if this solves the problem? As you can probably guess, this optimization work is still somewhat experimental. Its main limitation is you can't write at all to the SD card with it enabled.
 
Paul,

Thanks for the quick response. It turned out to be the MicroSD card. I had one exactly like the one you suggested (an 8G SanDisk Ultra) and given that the tested seek times were within reason (around 800us), it didn't occur to me to test a different one. I found another, newer one (16G), formatted it and made sure to copy the files one at a time from the shell this time, so they would be contiguous (copying from the Finder ends up with multiple simultaneous copies and the writes are all over the place). The seek/read block time now is down to around 480us.

The code is usually running one loop continuously (the file is about a minute long) and the second instance plays "alarms" and "announcements" (adjusting the mixer gains accordingly in the process). It now all works as expected.

Thanks again for such a cool dev board!
 
Try editing hardware/teensy/avr/libraries/SD/SD_t3.h. Find any uncomment this:

Code:
// This Teensy 3.x optimized version is a work-in-progress.
// Uncomment this line to use the Teensy version.  Otherwise,
// the normal SD library is used.
//#define USE_TEENSY3_OPTIMIZED_CODE

Please let me know if this solves the problem? As you can probably guess, this optimization work is still somewhat experimental. Its main limitation is you can't write at all to the SD card with it enabled.

Hey there everyone! Listen, hate to be a bother here but i've tried this several different ways and it won't let me save the file with the change. Is that what we are supposed to do? Save it once we uncomment it? Or do we just type in the USE_TEENSY3_OPTIMIZED_CODE in whichever sketches we want to run the optimized alternative with? Thank you and man i'm super psyched to try this out grubba i've been searching high and low for a way to do pretty much exactly what your code says it does (i'm sure it works as advertised, just saying i haven't gotten it to yet and am giddy with anticipation).

thanks!
 
Hi NewtonBIV,
Try running the editor as Administrator if you're using Windows - then you should be able to save the changes.
 
Hi NewtonBIV,
Try running the editor as Administrator if you're using Windows - then you should be able to save the changes.

Aha! Of course, i was getting mixed up since it was an h file I kept trying to open the file as an administrator. I just opened up an instance of notepad as admin and changed it there. For some reason now none of my sketches are working. You've already helped bigtime, and thanks for that if I don't hear back. If you were still curious about the saga of Newton and his ridiculously simple (should be at least) mission here is the error code i'm getting, which honestly seems completely unrelated to my actual code though i went ahead and put in the code that got this particular error code (same with any sketch I run pretty much so surely has to be the SD_t3.h adjustment and worst case i'll just change it back, was playing two files close enough to simultaneously i was able to do what I needed sort of anyway).

ERROR CODE:

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:31:29: warning: declaration of 'volatile unsigned int* SDClass::csreg' outside of class is not definition [-fpermissive]

volatile uint8_t * SDClass::csreg;

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:32:18: error: conflicting declaration 'uint8_t SDClass::csmask'

uint8_t SDClass::csmask;

^

In file included from C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:28:0:

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\SD_t3.h:86:21: note: previous declaration as 'unsigned int SDClass::csmask'

static IO_REG_TYPE csmask;

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:32:18: warning: declaration of 'unsigned int SDClass::csmask' outside of class is not definition [-fpermissive]

uint8_t SDClass::csmask;

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp: In static member function 'static bool SDClass::sd_read(uint32_t, void*)':

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:113:2: error: 'SPI0_PUSHR' was not declared in this scope

SPI0_PUSHR = 0xFFFF | SPI_PUSHR_CTAS(1);

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:113:40: error: 'SPI_PUSHR_CTAS' was not declared in this scope

SPI0_PUSHR = 0xFFFF | SPI_PUSHR_CTAS(1);

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:116:12: error: 'SPI0_SR' was not declared in this scope

while (!(SPI0_SR & 0xF0)) ;

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:118:17: error: 'SPI0_POPR' was not declared in this scope

uint32_t in = SPI0_POPR;

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:122:11: error: 'SPI0_SR' was not declared in this scope

while (!(SPI0_SR & 0xF0)) ;

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:123:16: error: 'SPI0_POPR' was not declared in this scope

uint32_t in = SPI0_POPR;

^

C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD\card_t3.cpp:126:11: error: 'SPI0_SR' was not declared in this scope

while (!(SPI0_SR & 0xF0)) ;

^

Multiple libraries were found for "SD.h"
Used: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD
Not used: C:\Program Files (x86)\Arduino\libraries\SD
Using library Audio at version 1.3 in folder: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\Audio
Using library SPI at version 1.0 in folder: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SPI
Using library SD at version 1.2.2 in folder: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SD
Using library SerialFlash at version 0.5 in folder: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\SerialFlash
Using library Wire at version 1.0 in folder: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\Wire
Using library Bounce in folder: C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\Bounce (legacy)
Error compiling for board Teensy 4.0.




AND HERE WAS THE CODE THAT GOT THIS ERROR:

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h>

AudioPlaySdWav           playSdWav2;     
AudioPlaySdWav           playSdWav1;    
AudioMixer4              mixer1;     
AudioMixer4              mixer2;       
AudioOutputI2S           i2s1;           
AudioConnection          patchCord1(playSdWav2, 0, mixer2, 0);
AudioConnection          patchCord2(playSdWav2, 1, mixer1, 1);
AudioConnection          patchCord3(playSdWav1, 0, mixer1, 0);
AudioConnection          patchCord4(playSdWav1, 1, mixer2, 1);
AudioConnection          patchCord5(mixer1, 0, i2s1, 0);
AudioConnection          patchCord6(mixer2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     

Bounce button0 = Bounce(0, 15); //this is the GLOBAL AUTO STOP & START
Bounce button1 = Bounce(1, 15); //DECK 1 SKIP TO NEXT TRACK
Bounce button2 = Bounce(2, 15); //DECK 1 GO BACK TO PREVIOUS TRACK
Bounce button3 = Bounce(3, 15); //DECK 2 SKIP TO NEXT TRACK
Bounce button4 = Bounce(4, 15); //DECK 2 GO BACK TO PREVIOUS TRACK

#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  11
#define SDCARD_SCK_PIN   13

void setup() {
  Serial.begin(9600);
  AudioMemory(8);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  SPI.setMOSI(SDCARD_MOSI_PIN);
  SPI.setSCK(SDCARD_SCK_PIN);
  if (!(SD.begin(SDCARD_CS_PIN))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
  pinMode(0, INPUT_PULLUP); //STOP ALL & START OVER AS IS
  pinMode(1, INPUT_PULLUP); //DECK 1 SKIP
  pinMode(2, INPUT_PULLUP); //DECK 1 REWIND
  pinMode(3, INPUT_PULLUP); //DECK 2 SKIP
  pinMode(4, INPUT_PULLUP); //DECK 2 REWIND
  delay(1000);
}
int filenumber = 0; //track playing on deck1
int filenumber1 = 0; //track playing on deck2
const char * filelist[4] = {
  "DRUM87B1.WAV", "DRUM87B3.WAV", "800001.WAV", "800003.WAV"
  };   
const char * filelist1[4] = {
  "BASS87A#.WAV", "BASS87E1.WAV", "80000006.WAV", "80000008.WAV"
  };    
void loop() {
  if (playSdWav1.isPlaying() == false) {
    const char *filename = filelist[filenumber];
    const char *filename1 = filelist1[filenumber1];
    playSdWav1.play(filename);
    playSdWav2.play(filename1);
    Serial.println("both played");
    delay(10); 
  }
  button0.update();
  button1.update();
  button2.update();
  button3.update();
  button4.update();
  if (button0.fallingEdge()) {
     playSdWav1.stop();
     playSdWav2.stop();
     Serial.println("Starting Over");
    delay(5);
  }
  if (button1.fallingEdge()) {
    filenumber = filenumber + 1;
    if (filenumber >= 3) filenumber = 0;
    Serial.println("Next Track Deck1");
  }
  if (button2.fallingEdge()) {
    filenumber = filenumber - 1;
    if (filenumber < 0) filenumber = filenumber + 4;
    Serial.println("Rewind Track Deck1");
  }
  if (button3.fallingEdge()) {
    filenumber1 = filenumber1 + 1;
    if (filenumber1 >= 3) filenumber1 = 0;
    Serial.println("Next Track Deck2");
  }
  if (button4.fallingEdge()) {
    filenumber1 = filenumber1 - 1;
    if (filenumber1 < 0) filenumber1 = filenumber1 + 4;
    Serial.println("Rewind Track Deck2");
  }
}
 
Status
Not open for further replies.
Back
Top