Here's my PCM audio player with SD card (fat16lib)

Edited from Arduino Playground - PCMAudio

First, you should convert sound files to PCM 8 bit 8Khz Wav files, I use Switch Sound File Converter for this. Then fill playlist string variable with file names separated with space(so files must not include space).

Also the code uses Timer0 so you cannot use delay/milis while playing sounds, after playing is finished you can use delay/milis as usual.

I hope someone could give an idea whether it can be accomplished with only 1 timer.

#include <Fat16.h>

SdCard card;
Fat16 file;

int speakerPin = 6;

volatile boolean playing = false;
String playlist = "";//non volatile since it is not used by loop while interrupts are on

boolean firstRampUp;//non volatile since it is not used by loop while interrupts are on

uint32_t sample;//non volatile since it is not used by loop while interrupts are on
uint32_t sounddata_length;//non volatile since it is not used by loop while interrupts are on

// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect)
{
    if (playing) {
        if (firstRampUp) {
            if (OCR0A >= 128) {
                firstRampUp = false;
            				
                OCR0A = file.read();
                sample++;
            } else {
                OCR0A++;
            }
        } else {
            if (sample >= sounddata_length) {
                if (OCR0A == 0) {
                    stopPlayback();
                } else {
                    OCR0A--;
                }
            } else {
                OCR0A = file.read();
            }

            sample++;
        }
    }
}

void startPlayback()
{
    firstRampUp = true;

    sample = 0;
    
    file.open(nextsound(), O_READ);
    file.seekSet(46);//skip header bytes

    sounddata_length = file.fileSize() - 4000;//skip garbage bytes at the end, is it a converter issue ?

    bitSet(TCCR0A, COM0A1);
    bitClear(TCCR0B, CS01);

    cli();
    
    bitSet(TCCR1B, WGM12);
    bitClear(TCCR1A, WGM10);
    bitClear(TCCR1B, CS11);
    
    OCR1A = F_CPU / 8000;

    bitSet(TIMSK1, OCIE1A);

    sei();
	
    playing = true;
}

void fixInternals()//set back to arduino default settings of interrupts, timers etc...
{
    bitClear(TCCR0A, COM0A1);
    bitSet(TCCR0B, CS01);

    bitClear(TCCR1B, WGM12);
    bitSet(TCCR1A, WGM10);
    bitSet(TCCR1B, CS11);

    OCR1A = 0;

    bitClear(TIMSK1, OCIE1A);
}

void stopPlayback()
{
    fixInternals();

    file.close();

    playing = false;
}

const char* nextsound()
{
    String next = "";

    int space = playlist.indexOf(" ");
    if (space != -1) {
        next = playlist.substring(0, space);

        playlist = playlist.substring(space + 1);
    } else {
        next = playlist;

        playlist = "";
    }

    next = next + ".WAV";

    int l = next.length() + 1;
    char nextcstr[l];
    next.toCharArray(nextcstr, l);

    return nextcstr;
}

void setup()
{
    pinMode(speakerPin, OUTPUT);
    
    card.init();
    Fat16::init(&card);
}

void loop()
{
    if (playlist == "") {
        playlist = "MUSIC1 MUSIC2 MUSIC3";

        delay(10000);
    }

    startPlayback();

    while (playing) {;}
}

/*
"byte"-"bit"-"default value"-"pcm value"
TCCR1B CS10 -> 1    1
TCCR0B CS00 -> 1    1
TCCR0A WGM01 -> 1    1
TCCR0A WGM00 -> 1    1
TCCR0A COM0A0 -> 0    0
TCCR0A COM0B1 -> 0    0
TCCR0A COM0B0 -> 0    0
TCCR0B WGM02 -> 0    0
TCCR0B CS02 -> 0    0
TCCR1B CS12 -> 0    0
TCCR1A WGM11 -> 0    0
TCCR1B WGM13 -> 0    0

TCCR0A COM0A1 -> 0    1
TCCR1B WGM12 -> 0    1
TIMSK1 OCIE1A -> 0    1
TCCR0B CS01 -> 1    0
TCCR1B CS11 -> 1    0
TCCR1A WGM10 -> 1    0

OCR1A -> 0    varies
*/

Yes, it can be done with only one timer. :wink:

https://github.com/Beat707/BeatVox

Wk

Also, why not adding an one sample buffer? So, in the timer, the first thing you do is set OCR0A = lastSample and them calculate the lastSample for the next timer. This adds 1 sample delay, but makes the whole thing run better, specially when using just one timer.

Wk

Hi William,

I just tried to apply 1 timer player from your work and it works. But in the background there is a constant background high frequency noise. If I change ICR1 = (726-1); to a lower value such as 255, the noise is gone but the audio is playing too fast. Do you happen to come across such a problem?

Thanks

You need to put a filter on the output, here I used a headphone that already filters it and sounds great, but depending on the speaker you need a filter.

Or you could change things, I use "ICR1 = (726-1);" so I can mix 3 x near 8-bit values. So my samples actually go from 0 to 242.

If you change ICR1 to 255 you will get a sample-rate of 16000000/255 = 62745 hz so your samples must follow that, OR, you could just divide that by 2 = 31372 hz which is still nice. To do that, just add a check on the ICR1 Timer Call, like this:

uint8_t divByTwo = false;

ISR(TIMER1_OVF_vect)
{ 
    divByTwo = !divByTwo;
    if (divByTwo) return;

This will do the trick.

But I would still use a filter on the output, this way you get a better sound and can handle more than 8 bits, your samples can go from 0 to 725 (or 726, don't recall), much nicer.

Wk

Well what I don't understand is the source of the noise since the samples are between 0 and 255. Could you elaborate ?

Sure, the number we are talking about is the one that handles the interrupt, so every 16000000/number = how many times per second that interrupt is called. So, if you set to 255, you are getting over 60khz, which the human ear can't hear. Now, at 255, you get 22khz, which is a high pitched noise. Now, a sampled-sound at 22khz can only produce a max of 11khz actual sound, so, if you filter anything above that, you get rid of any noise produced by the system.

I'm sure that some brain-guys will explain with much extra nonsense details... :grin:

Wk