Are there any inexpensive Arduino variants w/ micro SD and DACs built in?

I'm not sure I have any answers to this, but I do have technical explanations as to what's going on -- which may help you decide what course of action to take...

Gapless playback of MP3 is basically impossible. (Wait -- there's a caveat coming!) Now, I don't mean "it's impossible on a WaveShield", I mean, the format isn't designed to care about gaps on the order of ms to either side of the audio. MP3 is broken into frames that last a certain number of ms in time. But the sound doesn't start at the beginning of the frame. Like many other perceptual audio coding formats, audio is converted from time domain to frequency domain, so it's described in terms of FFT, not samples. There's some feeding required before sound can start, and there's some cleanup required to transition from sound to silence. Additionally, frames are dependent on each other, which complicates things further.

You can get around this by using a mechanism like the LAME tag, which knows and records the number of audio samples in the source material. Then, the encoder can describe how many samples to throw away at the start of playback (to account for the delay incurred by the encoding), and how many to throw away at the end (to accurately match the length of the source material). To benefit from this, the decoder must have enough intelligence to analyze the metadata frames, then treat the MP3 stream as a whole (by acknowledging the first and last frames and discarding useless decoded samples) rather than just an arbitrary sequence of frames (which is how most decoder ICs will handle it.) Even if you were to use a dedicated decoder IC that gives you back PCM samples to feed to a separate DAC, doing the stream processing on a microcontroller would be difficult since you have so little RAM to split between parsing the frames and tags, and buffering frames to be sent to the decoder, and samples that arrive back from it ... not to mention whatever else you intended to do.

Since most decoder ICs just want single frames or a certain number of bytes at a time, you could just avoid the cookie-cutter file-based playback libraries and handle file reading and buffering on your own. If you carefully craft your MP3s, you can, in theory, send an endless stream of frames to the decoder. The loop gap will be limited to just what is inherent in the encoded frames, and "seamless" transitions from file to file would have a similar constraint. Gaps would be fairly short and you might be able to tolerate them depending on the nature of the sound. Certainly less intrusive than the MP3 delay plus the time to empty the playback buffers, read a new file, fill the buffers, and begin playback.

If sample-based formats (wav, etc.) are an option, you could do the same trick and get completely gapless playback. Simple PCM wave files have very basic headers that are pretty easy to parse, but if your files are all the same format (e.g., 8-bit, 22kHz, mono), you could skip the wav format complete, dump raw samples to your SD card and hard-code the sample properties during decoder initialization.

to clarify on my post.. I was talking about using .wav files.. (the only format I have tried/used with it)

I dont know enough to edit fat16lib's WaveHC library to make it perform seamless/gapless playback.. (nor able to write my own from scratch) :slight_smile:

so as a default/out of the box solution..I havent seen the WaveShield, use .wav files in a seamless/gapless playback before/yet.

:slight_smile: (I wish ti did)

dont know enough to edit fat16lib's WaveHC library

Typically, the "open" of a file is an expensive procedure compared to reading from it. You have to parse through directories and allocation tables and such. To minimize the results on audio, you would want to either speed up that access (usually by throwing memory at it, which is hard to do on avr-class microcontrollers), and/or open the next file before the current file is done playing.

westfw:

dont know enough to edit fat16lib's WaveHC library

Typically, the "open" of a file is an expensive procedure compared to reading from it. You have to parse through directories and allocation tables and such. To minimize the results on audio, you would want to either speed up that access (usually by throwing memory at it, which is hard to do on avr-class microcontrollers), and/or open the next file before the current file is done playing.

I was just playing/using the same file in a 'loop' to get seamless/gapless playback (attempt)..

I haven't looked into the existing libraries, but I have read the datasheet for the VLSI codecs that Sparkfun sells breakout boards for. There's a process of priming the buffer, then continued polling or interrupts to keep it fed, then a process of telling the chip you intend to stop sending data, and waiting for it to tell you it's done playing. File-based playback routines are going to go through that cycle every time you "loop" a file by repeatedly calling whatever whatever equivalent to: SoundLibrary->PlayFile("somefile.wav").

You would have to decouple the file contents from the stream sent to the codec by not flushing the playback buffer at EOF, but just going back to the beginning and proceeding like it was one continuous stream. (The VLSI codec requires you to send it a modified wave header with data chunk size 0xFFFFFFF in this case.) If you wanted to, you could take the "software mixer" route and open a PCM stream on bootup that never gets closed. When there's no sound file playing, you just keep passing digital silence to the codec anyway. Exactly what "digital silence" is depends on the sample format -- signed or unsigned, particularly -- but it's a constant 8- or 16-bit number anyway...

scswift:
The ideal solution... would cover virtually everything

Well....yeah.

Unfortunately, this world is less than ideal. Hardware design and engineering in particular is about trade-offs. Usually trading between cheap, small, and/or fully capable. Of course, "ideal" would have them all....but we already covered that.

So....what is most important to you?
What price is too high?
What size is acceptable?
What minimum functionality do you require?

Where in these are you flexible?

If you haven't figured it out already, the answer to your original question is basically "not that we know of". But people are trying to help you find alternatives which, by nature, will be less than ideal.

And, out of curiosity, are YOU adding sounds to "props"? Or, are you attempting to replace your custom board as part of a kit you sell to others so THEY can add sound to their props?

xl97:
I have a couple of Adafruit WaveShields..
using the default/stock WaveHC library.. you will -NOT- get seamless/gapless playback..
unless you can rip into the lib and get under the hood to edit what fat16lib author did...
(I have posted here, and on Adafruit about getting the same results.. I have done it/seen it yet) :slight_smile:

Well I can't guarantee it's seamless without checking with a scope, but I modified the WaveHC lib myself to allow for both looping and queuing the next sound effect, and it sounds seamless to me. And in theory it should be seamless. The WaveHC lib uses double buffering, with an interrupt to fill one buffer while playing the other. As I modified the interrupt that fills the buffer to restart from the beginning of the file when it reaches the end, or swap files and then fill the buffer, the playback should be uninterrupted, except perhaps in rare cases where there's only a few samples left to load into one buffer and the other doesn't have sufficient time to fill. But as I said, I haven't heard any popping as I switch between effects, even while playing the audio through a 20W amplifier on decent car speakers, so if it's not seamless, it's close enough. And no changes needed to be made to the Fat lib.

SirNickity:

I understand that MP3 can't be gapless. But an MP3 module doesn't have to be limited to playing MP3s. Many play other formats as well. I was hoping there might be one such module out there that could at least play WAVs seamlessly.

Also, for my end users, MP3 playback would be a convenient way to add theme music to their props. Music doesn't need to play seamlessly.

1ChicagoDave:
Unfortunately, this world is less than ideal. Hardware design and engineering in particular is about trade-offs. Usually trading between cheap, small, and/or fully capable. Of course, "ideal" would have them all....but we already covered that.

Yes, I'm aware that I may not find the ideal board for my needs. But that's why I first requested a board with just a MicroSD and DAC on it. That would be good enough. My minimum requirement is that I be able to loop WAVs and transition seamlessly between them. With that, I can play back sound effects that have a head, body, and tail, where the body loops while a trigger is held.

So....what is most important to you?
What price is too high?
What size is acceptable?
What minimum functionality do you require?
Where in these are you flexible?

Aside from the requirements stated above... It needs to be as small as possible. If it's an Arduino with a DAC on it, it can be a bit larger since I don't need to stick an Arduino Micro in there with it. But if it's a MicroSD card holder with a DAC on it, then ideally it would be the size of a postage stamp. But if it were three postage stamps it would still be useful. It would just mean that I might not be able to fit it in something really small, like Han Solo's blaster, or a tricorder.

As for price, it again depends on the features and if it was some form of Arduino, or just a shield. So anywhere from $8 to $35. But I could go as high as $50 if it was some form of Arduino that did most of what I needed.

And, out of curiosity, are YOU adding sounds to "props"? Or, are you attempting to replace your custom board as part of a kit you sell to others so THEY can add sound to their props?

I was attempting to replace my custom board that I sell as part of a kit that others install in their props. However I recently worked out a deal with a manufacturer who is currently making more of them for me.

This is my board by the way:

And this is the first kit I made with it:

Despite having more of these made, I am still looking for other options. My boards are 2"x2" and and as such won't fit comfortably in certain props, like this Predator computer gauntlet which is the kit I am presently working on:

Anyway... I have been considering designing an audio module of my own, like the wave shield, but much smaller, using surface mount components and fully assembled, and with a decent speaker amp. I may even include an ATTiny on there to handle the audio playback, which would take the majority of the workload off the Arduino and free up 1K of ram a timer. This would be something I'd offer for sale to the community. The hard part is getting the price low enough to make it a viable alternative to those mp3 modules for people. I'd need to get several hundred made. And I may need to forgo the 12bit DAC for 10bit PWM. Of course those modules don't include an amp, so I can charge a bit more for that.

I'd like to play with your modified WaveHC library and check out the seamless/gapless loop stuff (if your sharing/posting that is) :slight_smile:

and you can definitely design your own board, that is an Arduino/Waveshield all-in-one board.. with on-board DAC/miroSD/AMP..etc.....etc.. more a generic 'platform' for me to use, than a purpose designed board though. :slight_smile:

this is the one I use

used in a nerf/blaster project (for size comparison)

it uses a modified WavePinDefs.h (WaveHC lib) file since the DAC is mapped to different pins..etc.. but essentially is the same.
I use this in my props/projects as well..

It seems I was partly mistaken about the changes I made to the waveHC lib... In my defense, I did make them almost a year ago.

I did modify the library to seamlessly loop audio files. But I did not modify it to seamlessly queue them. Mainly because as long as the pause between files is short enough, there isn't a real need to seamlessly transition from one to the next. At least, I have not noticed pops when doing said transitions. I suppose if you really wanted to though you could ramp the sample up/down to the zero crossing before playing the next track.

Anyway, here's my code to queue sounds up and play the next one when ready:

   boolean paused;   
   byte songSelected;
   byte songPlaying;

   char* queuedFile = NULL;
   byte queuedLoop;

/*
This function plays a sound file.  
Unless otherwise specified, playing a sound file with play() will clear the queue of pending sounds.
Normally, only updateSound() needs to call play without it clearing the queue.
*/
void play(char* filename, boolean loopsound = false, boolean emptyQueue = true, boolean logerror = false) {
  
  wave.stop(); // Halt playback of sound currently playing.

  if (emptyQueue) { clearQueue(); } 
 
  if (!file.open(root, filename)) { // "root" and "file" are global variables.
    if (logerror) { Serial1.println(F("Failed to open WAV file!")); halt(4); }
    return;
  }
    
  if (!wave.create(file)) { 
    if (logerror) { Serial1.println(F("Failed to create WAV object!")); halt(5); } 
    return;
  }  
    
  setVolume(1.0); // Must be set after call to wave.create()
  wave.play(loopsound);
  
}


/*
This function queues up a file to be played after the current one ends.  It's used when you want to play a sound effect which has a head and a loop.
*/
void queue(char* filename, boolean loopsound = false, boolean logerror = false) {
  
  queuedFile = filename;
  queuedLoop = loopsound;
  
}  


/*
This function begins playing the queued file when the current one ends.
*/
void updateSound() {
  if (!wave.isplaying) {
    if (queuedFile != NULL) { play(queuedFile, queuedLoop); } 
  }
}


/*
This function stops any sounds in the queue from being played back.
*/
void clearQueue() {
   queuedFile = NULL;
}

And here is the WaveHC lib:

Note, this has been heavily modified. First, the SD card on my boards is on USART0. (That was a terrible mistake which added a month to my development time trying to debug that.) I may also have changed the timer it's using. The play buffer interrupt has the code that checks to see if a loop variable I added to the wave class is true when it reaches the end of the file. I also did not need 44khz stereo playback, but I did desire fine volume control, so there's a bunch of optimized assembly in there to achieve that at 22khz.