New library for PWM playback from SD cards: SimpleSDAudio

Hi,
it is not foreseen yet to change the pitch yet, at least not in fine amounts. You can select between half or full-rate, but that's all. If you want to change the playback speed in finer amounts, it is not possible with that library yet and you wont get usable result just by changing the timer rate because the timer already runs at relatively high speed allowing only massive pitch changes.

The easiest way to do such things might be to prepare multiple files on the card each with a different pitch and play those. You can do this using an audio editor like Audacity to prepare such files.

If you really want to go the hard way it should be possible to modify the library to support different playback rates - at least two options came in mind for me, changing the playback-interrupt function or the worker-function. The worker-function is written in plain C and should be easier to modify but with rate-changing it would not be longer operating at sector boundaries. In the playback function rate changing only have influence over buffer empty speed but it is harder to understand as it is written in Asm. The algorithm I would prefer is a phase-accumulator that decides if and how many samples are skipped or repeated for every target sample...

Tuttut

Thank you tuttut for the reply.
OK, I take the easy way you have mentioned.
One more question. I need to play a 5sec audio as a seamless loop:indefinitely .Though I am able to accomplish that with the "Bare minimum" you have provided,there occurs an audio glitch at the end-to-beginning of the file played. The file has perfect zero-crossing at the beginning and in the end.Could you please suggest anything to avoid this problem.
(My gadget: Ardiuno Atmega 328+LC studio SDcard breakout board).
Thank you again.

Hi,
there is no standard way, but you can try this patch (untested yet!)

void SdPlayClass::worker(void) {
  if(!_WorkerRunning && _pBuf && _fileinfo.Size) {
    uint16_t buflencpy;
    _WorkerRunning = 1;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
      buflencpy = _Buflen; 
    }      
    if(_fileinfo.ActBytePos < _fileinfo.Size) {
        // At least space for 1 sector?
        if(buflencpy < _BufsizeMinus512) {
            int16_t ret;
            ret = SD_L1_ReadBlock(_fileinfo.ActSector++, _pBuf + _Bufin);
            if(!ret) {
               uint32_t BytesLeft = _fileinfo.Size - _fileinfo.ActBytePos;
               _Bufin += 512;
               _fileinfo.ActBytePos += 512;
               if(_Bufin >= _Bufsize) _Bufin -= _Bufsize; 
               if(BytesLeft > 512UL) {
                   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
                   {
                     _Buflen += 512; 
                   }
                } else {
                   ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
                   {
                     _Buflen += BytesLeft; 
                   }
                   while(1) {    
                     ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
                     {
                        buflencpy = _Buflen; 
                     }
                     if(buflencpy == 0) break;
                   }
                   play();                   
                }
            } else {
              stop();
              _lastError = ret;
            }
        }
    } else {
      // Playback done
      if(buflencpy <= 1) {
        stop();
      }
    }
    _WorkerRunning = 0;
  }
}

Replace the worker-function inside the lib (file SimpleSDAudio.cpp) with the one above. To stop playback you need to use stop() then.

Please try it and tell if it works...

Tuttut

Hi,
Sorry, that patch did not work.
Result : Audio file not played.Instead, there were lot of glitches-only glitches.I have noticed that the glitches came out as a stream (loudest at the start and diminished along the time line and died in few seconds).But that was from "Bare minimum" sketch.
When I tried the " Bare minimum with Debug", it gave a continuous tone of 125 hz approx.(No audio file content played).
But, sketch examples with worker (bare minimum with worker,bare minimum with worker with debug),worked normally,of course with the glitch.
Could you please explain what exactly the patch modifies.
Anyway, thank you for trying to help me.

manmachine.

Hi,
sorry, I did the patch using a too old local copy of the library on my smartphone on-the-go and got an too old lib file. Now I edited my former post, please try to apply the patch again on SimpleSDAudio-Lib V1.03 (the current one). This time I tested it a little. Unfortunately actually there is no flag that tells you if the file played at least once...
Please try again and tell me if it is working for you.

BTW: The patch catches the moment when the worker function puts the last sector (or part of it) of the audio file in the playback buffer. In this case, it waits endless (and therefore precise) until the buffer is completely empty (_Buflen == 0). In this moment it triggers the play() function, which starts the playback again. This is how the play() function works - if playback is still playing it restarts playing from start again (done by calling internally the stop() function). The stop() function resets all the pointer and the buffer for the playback.

Tuttut

Hi,

I'm still getting audio glitch.May be,something wrong (sd card format etc)with my setup.Could you please confirm that the test file I am using (please see attachment)works Ok in your set up?

: Thank you for the explanation about the patch.Now I understand little more about the 'worker' function and I try to understand your libs more deep.

manmachine

440_22kh.wav (21.6 KB)

Hi,

I got it finally working.

But not with that FAKEduino.
I made my own board.

Sadly, outputting an audio is just a part of the project. The whole project isn't working yet.

Question: Are there pins affected when using this library? For example, some digital pins won't work...something like that?

Hi
@manmachine: Hm, I thought about my quick solution and it is wrong - the issue is that the buffer is completely empty when issuing play and that play first reads a whole sector of 512 bytes from card before it starts playback again. This should lead to a fast but not gapless start. I think it will need a bigger overwork to get around this. I could think about modifying the lib to read the sectors byte-wise which will make the lib slower but more flexible. Audio processing will then become possible like gapless playback, fine-tuned playback tempo and mixing. But the lib might be then not as simple to use as now.

@meow...: Please provide more info what is working and what not. Error code of lib or just no sound? Or just no sound in bigger software project?

Hi,
@Tuttut:Thank you for your reply.So, its going to be a different approach!Well,Could you post a FLOW CHART of the proposed lib implementation,what might be a possible solution.

manmachine

hey Tuttut-..

I sent you a PM a couple of days ago.. :slight_smile:

just curious if you got it?

thanks

Tuttut, thanks for the great library. I have an alpha release of my ChipKit port, BasicSDAudio, available on GitHub. I am also working on an amplifier board that would work for any PWM audio output.

Check out the release at:

another demo at:

and info on the amplifier/filter at:

I have yet to incorporate the 1.03 improvements.

I'd like to see/learn more about making this Awesome library better sounding with the use of filter/amp?

Small? (all SMD?)

Is there a schematic posted for it?

I am VERY happy Tuttut released this lib.. it was a HUGE missing part of the Arduino community (easy audio)..
but as 'stock' the volume is just 'meh'... so it needs some filters and an amp for sure for more sound hungry projects..

still learning/finding my way through filters and amps..

thanks!

What timers are used in this library for 16MHz, Full rate mono?

I'm hoping there is a timer or two open that I can use to output PWM so I can brighten and fade an LED while the audio file is playing.

In the case that there is a timer I can use, would using the timer affect the audio too much?

Thanks.

The lib use only the timer that is bound to the output port. It might be possible to even use analogWrite on neighbor ports that use thevsame timer but I never tested it, so please do so. But in this case the PWM is fixed to 8-Bit and the sampling rate of audio-playback, which should be no issue for LED dimming.

I have done/tried a set-up using the SimpleSDAudio lib as the audio output.

the most simple design, straight outout put on pin9 via a 100uF polarized cap.

the pwm 'works'... but you get noise in the audio.

Specs:

ATmga328P-AU
+3.3v @ 8MHz internal clock (ie; no crystal, cap or resonator)
straight connect microSD (ie: no level shifter or voltage divider)
3 x leds on PWM fade
5 x leds (digital on/off)
no filter
no amp

(I need to learn more about low-pass filter set-up and using an amp better) :frowning:

@cobra18t-

is there any schematics or anything for your BabyBOOMER?

Does it need the 'dual' output? I only have set-up where Im outputting on pin9 only (most basic)..

looking for the smallest way to get better sound.. :slight_smile:

thanks

Hi,
the thing is that people often don't understand where the noise in such systems come from and try to build filters to try to filter out 8-bit noise. This is just stupid, as the noise of having too less bits is usually even distributed over the audible spectrum and not only at the high frequencies. With very good algorithms that do dithering you can try to buy better noise performance at the cost of spectrum - in such a case the noise is not longer evenly distributed over the spectrum (this is called noise-shaping, SOX can do this under special conditions, see SoX - Sound eXchange | NoiseShaping. But this only makes sense if you have a high sampling rate AND you are using an external filter. You can do a simple experiment to understand 8-bit noise: Convert your audio file at your PC to 8-bit and listen to it and you will hear the static noise. I tried this using Audacity and the result is pretty much what I get with the Arduino 8-Bit output.

But: If you want an easy way to get rid of the noise go for double-PWM-Ouput aka 16-bit mode. Often you only need one additional output port and one resistor to get a much better sound with so less noise that you don't even think about filtering (which is often useless anyway!). If you want it cheap try my circuit from this entry (2nd post circuit for 16-bit): http://arduino.cc/forum/index.php/topic,158024.0.html. Just build one half of it if you stick with Mono-Output. The nice thing is: It is much louder on a speaker than a port-pin, so no need for additional amplifier and much less noise and still cheap. I tested it several times, I'll try if I can do it a little smaller using transistors.

thanks for the reply..

for myself..

1.) can 'dual' output be done on an UNO/Duemilanove? (I though I had read only on Mega's? could be wrong of course.. I thought i had 'tried' it originally and dont recall seeing (hearing) a difference.. however that could also have been user/set-up error.. LOL

also.. as to the noise I guess I was referring to.. I mean more specifically wen you run/do PWM actions 'while' playing audio.. you can hear the hum/buzz..etc..etc playing over the audio or interfering...etc..

unfortunately.. I dont have any 74AC14 chip.. (never even heard of them before now) :)..

but I do have a LM386 amp laying around..?

form the schematic though.. looks like only a:

coupling cap on the power rails
2 x resistors..
and the DC blocking cap on the PIN9 output?

actually looking again.. is that two caps for the decoupling? hard to see in the fritzing image) :slight_smile:

Are you saying that just using the DUAL PWM output will (not only help eliminate the noise) but increase the volume.. to where you say/think/believe no amp is even needed to boost volume?

I currently only have things set-up for single (pin9) output.. and actually used pin10 as the CS pin for my SD card..

but next revision could have that changed easy enough......

I'll have to go back and try the dual output again?

thanks.

Hi,
with standard Arduino (with ATmega328) two PWM outputs for SimpleSDAudio are availible, either for stereo 8-bit or mono 16-bit. For mono 16-bit you have to set it up like it would be stereo mode and you have to use the 16-bit SOX batch-files for processing. You end up with 2 PWM outputs, one playing the 8-bit sound and one playing loud "noise". But when you add this noise-channel to the other using those pots, the result will be much less noise.

For start you can also use a 74HC14 chip. Often in catalogs these chips have numbers starting with SN, so SN74AC14 is also the right one.

If you want to use your LM386 amp do the 16-bit combining before routing it to the amp. Use this circuit: Datei:SSDA Simple16bit.png – Hackerspace Ffm. But without the 74HC14 or 74AC14 it is hard to get rid of the noise coming from PWM-LED-Dimming.

Good power deblocking is essential for audio quality. Try to add some caps, 100nF but also one much bigger electrolytic cap in the 10uF to 100uF region. This is also done in my Fritzing schematic, so you got it right.

Dual-PWM output does not increase the volume itself (only if you use bridge-mode without any additional amplifiers). I use two PWM outputs with stereo-setting (SSDA_MODE_STEREO) and combine those to 16-bit mono for better audio quality. The 74AC14 will then increase volume and decrease noise if decoupled correctly by filtering the uC-noise out on the PWM outputs.

Hi,

I am trying to work on a Tardis project from Dr. Who. Basically I want to play the Tardis theme and have an LED brighten and dim while the Tardis Theme is playing. I have gotten the file and music to play correctly. In a separate sketch I have used timer 0 to brighten and dim the LED, which worked perfectly. When I put the codes together, however, only the light will work, and not the sound. The debug sketch in the library says it cannot initialize the card once I have my LED code in there.

Hopefully I am making sense. I will post my current code and explain what I am trying to do.

#include <SimpleSDAudio.h>

//Here I have set up an array of integers that start from zero and go to 255, and then decrease to 0. This is so I can brighten and dim an LED while the file is playing. 
int array[]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,255,254,253,252,251,250,249,248,247,246,245,244,243,242,241,240,239,238,237,236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221,220,219,218,217,216,215,214,213,212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197,196,195,194,193,192,191,190,189,188,187,186,185,184,183,182,181,180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,151,150,149,148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,133,132,131,130,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0};
int y=0;

void setup()
{ 
  pinMode (6, OUTPUT);   //This is the OC0A pin that I want to use for the LED

  
  cli();
  
  
  TCCR0A = 0x83;         //In the control registers, I have set fast PWM, non inverting mode. I used a prescaler of 256, so that every 4ms the OCR0B would match and set an interrupt. 
                         //During the interrupt, I want to increment the element in the array. So every 4ms, the light brigthens slightly.
  TCCR0B = 0x04;
  
  OCR0A = 255;
  
  OCR0B = 249;
  
  TCNT0 = 0;
  
  TIMSK0 = 0x04;
  
  sei();

  SdPlay.setSDCSPin(10); // Enable if your SD card CS-Pin is not at Pin 4... 
  SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO | SSDA_MODE_AUTOWORKER);
  SdPlay.setFile("EXAMPLE.AFM");
  SdPlay.play();
}

void loop(void) {
}

ISR(TIMER0_COMPB_vect)
{
  y++;
  OCR0A = (array [y]);
  if(y==512)
  {
    y=0;
  }
 
}

This section of code:

#include <SimpleSDAudio.h>
void setup()
{ 
  // SdPlay.setSDCSPin(10); // Enable if your SD card CS-Pin is not at Pin 4... 
  SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO | SSDA_MODE_AUTOWORKER);
  SdPlay.setFile("EXAMPLE.AFM"); 
  SdPlay.play();
}

void loop(void) {
}

comes directly from the AbsoluteMinimum example in the simple sd audio library. The only things I added is the controls for timer 0 to run the light, and an interrupt service routine.

So to set up timer0, I set Bits 7:6 – COM0A1:0 in the timer 0 control register A (TCCR0A) to 1, 0, which is non inverting PWM for the OC0A pin. I set Bits 1:0 – WGM01:0: in the TCCR0A to 1, 1, which selects the fast PWM mode. All I did in the TCCR0B register was set the clock to a 256 prescaler.

The way I set it up, the OCR0A register holds the PWM value for the OC0A pin (pin 6 on the arduino). The OCR0B holds a value that will set an interrupt every 4ms. In this interrupt, I set the next PWM value that needs to be in the OCR0A register. So in summary, every 4 ms, the OCR0A register is set to the next integer in the array, and the array makes the PWM values increase to 255 and then decrease to 0. This way I get my brightening and dimming LED on pin 6.

So, the SD audio works fine on its own, and the LED works fine on its own. But when I try to combine the two, only the light works; I do not get any audio. So is my ISR taking too much time and ruining the audio? Or does the audio library use all the timers? Note: if I use the BareMinimumWithDebug example in the library, and insert my timer 0 code, it says the SD card will not initialize. I checked the CS pin too, so it has something to do with my timer setup.

I hope my code makes sense and that I have explained my problem well. If anybody could let me know what is wrong, and if there is a possible solution or better way to do this, I would greatly appreciate it.

Thanks,
Thomas Hooper

Hi,
the reason is that SimpleSDAudio uses millis() in sd_l0.h for timeout and timing purposes. Unfortunately, timer0 is heavily used by the Arduino library for all kinds of Timings, like millis, delay etc., so it is a good idea not to figure with it. But: two interesting OCR-Outputs are only availible at this timer, that might be used even for 4-channel output with my lib even on ATmega328. On the other hand, timer2 is not as interesting, so I build a patch that moves all internal Arduino timing functions from timer0 to timer2. This patch can help you too. You find it already in the deepths of SimpleSDAudio library. Try it, if you got stuck ask again.