I am trying to mix multiple audio files from an SD card to a PWM pin(sound quality)

Status
Not open for further replies.
Hey guys, it's me Nathan Ramanathan; that guy who made MusicWithoutDelay library:https://github.com/nathanRamaNoodles/Polyphonic-Synth-Engine.

Anyway, I've decided to up my game a bit by writing some code that can mix multiple WAV files from an SD card via PWM on a timer interrupt.

**I know the Teensy Audio library has this, but I want to challenge myself. Also, I'm gunna port this code to Arduino Uno for my library. ;)

The good news: it works and is very efficient at mixing many audio files at the same time.
The bad news: There's static noise(even with a Low Pass filter). And the audio quality is not that pretty.
But it still "sounds" possible to get better quality audio. Pun Intended 😉.

I was wondering if you guys could try and optimize my code to better quality. I've commented on my code. I've got two buffers for the SD card and the extracting of the files is done in the main loop(), while the audio is played in the timer interrupt portion.

Thanks,
-Nathan Ramanathan

P.S.
Paul, thanks for the free Teensys. I absolutely love them. Also, I am updating my library to allow playing SD files on an Arduino Uno.

Code:
Code:
/*
    SD WAV mixer via PWM
    by Nathan Ramanathan (Github: https://github.com/nathanRamaNoodles)

    Compatibility: Arduino Uno/nano/mini, Teensy 3.x
*/
//In this project, I decided to improve my c++ skills by playing audio from a SD card onto a PWM pin.
//Also, I added the ability to mix WAV file sounds, from my other library: https://github.com/nathanRamaNoodles/Polyphonic-Synth-Engine
//I know this is hard to do, but so far the quality sounds fine on a Arduino UNO and
//on a Teensy.  But now, I want to make my code more efficient and sound better.

//I'm using a microSD card adapter from Ebay.  Unfortuantely, the SD card only opens when The Teensy's CPU rate is 24 Mhz.
//On an Arduino Uno, the audio outputs at pin 3.
//On Teensy, the Audio is on pin 3(but you can change it below).
//Schematic:  https://raw.githubusercontent.com/nathanRamaNoodles/MusicWithoutDelay-LIbrary/master/MusicWithoutDelay.png


//Directions:
//You can do two things:
//1. Do nothing: the microcontroller will play one song.
//2. Type 'p' in Serial: The microcontroller will play two tracks at the same time by mixing them.


#include <SPI.h>
#include "SdFat.h" //faster SD library
SdFat SD;

char dataFilename[] = "zelda32k.wav"; //wav files
char dataFilename2[] = "guardian.wav";

#define FS_music 32E3  //Sample Rate at 32,000 Hz

#if defined(__arm__) && defined(TEENSYDUINO)
#define buffSize 1000              //Buffer Size
static void timerInterrupt();
static IntervalTimer sampleTimer;
const int SD_ChipSelectPin = 10;
const int audioPin = 3;
#else
#define buffSize 128
#define SET(x,y) (x |=(1<<y))                //-Bit set/clear macros
#define CLR(x,y) (x &= (~(1<<y)))             // |
const int SD_ChipSelectPin = 4;
#endif

File dataFile;
File guardianFile;

uint8_t *buffPointer;  //pointer
uint8_t *buffPointer_2;
uint8_t buf[buffSize];  //buffer
uint8_t buf_2[buffSize];
uint8_t backBuf[buffSize]; //backup buffer for efficiency
uint8_t backBuf_2[buffSize];
uint8_t lastValue = 0;  //most recent value from SD card played to Speaker
uint8_t lastValue_2 = 0;

uint32_t filePosition = 0; //Current file position
uint32_t filePosition_2 = 0;

bool buffTrigger = false; //Triggered when Buffer is ready.(not a good idea, due to lag)
bool backBuffFlag = false; //Tells when Speaker has finished playing buffer
bool finished = false; //song is finished
bool finished_2 = false;
bool toggle = false; //alternate between backup and main buffer

void setup() {
  Serial.begin(115200);
  //  while (!Serial);
  Serial.println(F("Hi there :)\nType 'p' to play both wav files at same time, or do nothing to let me play only one song."));
  Serial.println(F("Starting simple WAV demo\n"));


  if (!SD.begin(SD_ChipSelectPin)) {
    Serial.println("SD fail");
    return;
  }
  dataFile = SD.open(dataFilename);
  guardianFile = SD.open(dataFilename2);
  if ( !dataFile || !guardianFile) {
    Serial.println("File not opened");
  }
  dataFile = SD.open(dataFilename, FILE_READ);
  guardianFile = SD.open(dataFilename2, FILE_READ);
  dataFile.seek(44); //seek to beginning of main wav file data
  filePosition = 44;
  guardianFile.seek(44);
  filePosition_2 = 44;
  finished_2 = true; //this stops the guardianFile song
  resume(); // setup our timerInterrupts
}

void suspend()
{
#if defined(__AVR__)
  CLR(TIMSK1, OCIE1B);                            //-Stop audio interrupt
#endif
}
void resume()
{
#if defined(__arm__) && defined(TEENSYDUINO)
  sampleTimer.begin(timerInterrupt, 1000000.0 / FS_music);
  analogWriteFrequency(audioPin, FS_music);
#else
  cli();
  TCCR1A = 0x00;                                  //-Start audio interrupt
  TCCR1B = 0x09;
  OCR1A = 16000000.0 / FS_music;        //-Auto sample rate
  SET(TIMSK1, OCIE1B);                            //-Start audio interrupt
  sei();

  TCCR2A = 0xB3;                                  //-8 bit audio PWM
  TCCR2B = 0x01;                                  // |
  OCR2B = 127;
  SET(DDRD, 3);              //-PWM pin at PIN 3 on Arduino Uno
#endif
}
// Function to copy 'len' elements from 'src' to 'dst'
void copyArray(uint8_t* src, uint8_t* dst, int len) {
  memcpy(dst, src, sizeof(src[0])*len);
}
bool secondSongStarted = false;
void loop() {
  //  if (!secondSongStarted && filePosition > (dataFile.size() / 2)) {  //Starts second song halfway through first song
  //    secondSongStarted = true;
  //    finished_2 = false;
  //    filePosition_2 = 44;
  //    guardianFile.seek(filePosition_2);
  //  }
  if (Serial.available()) {
    char str = Serial.read();
    switch (str) {
      case 'p':
        //        secondSongStarted = false;
        finished = false;
        filePosition = 44;
        dataFile.seek(filePosition);
        finished_2 = false;
        filePosition_2 = 44;
        guardianFile.seek(filePosition_2);
        break;
    }
  }
  if (!backBuffFlag) {
    toggle = !toggle;
    if (toggle) {
      if (!finished)dataFile.read(backBuf, sizeof(backBuf));
      if (!finished_2)guardianFile.read(backBuf_2, sizeof(backBuf_2));
    }
    else {
      if (!finished)dataFile.read(buf, sizeof(buf));
      if (!finished_2)guardianFile.read(buf_2, sizeof(buf_2));
    }
    backBuffFlag = true;
    filePosition += buffSize;
    filePosition_2 += buffSize;
    if (!finished && (dataFile.size() < filePosition)) {
      finished = true;
      Serial.println("Done 1");
    }
    if (!finished_2 && (guardianFile.size() < filePosition_2)) {
      finished_2 = true;
      Serial.println("Done 2");
    }
  }
  else {
    if (!buffTrigger) {
      if (!finished) {
        if (toggle) {
          buffPointer = backBuf;
        }
        else {
          buffPointer = buf;
        }
      }
      if (!finished_2) {
        if (toggle) {
          buffPointer_2 = backBuf_2;
        }
        else {
          buffPointer_2 = buf_2;
        }
      }
      toggle = !toggle;
      buffTrigger = true;
      backBuffFlag = false;
    }
  }
}

int location = 0;
#if defined(__AVR__)
SIGNAL(TIMER1_COMPB_vect)
#elif defined(__arm__) && defined(TEENSYDUINO)
static void timerInterrupt()
#endif
{
  //  if (buffTrigger) { //I've commented this out because its not efficient
  if (location < buffSize) {
    int sample = ((((finished) ? lastValue : lastValue = buffPointer[location])
                   + ((finished_2) ? lastValue_2 : lastValue_2 = buffPointer_2[location])//add our outputs
                  )
                  >> 2 //Shift 2 bits to the right. This is faster than dividing by Four.
                 )
                 + 127 //add 127 to make it a bit louder
                 ;
    location++; //increment our pointer's location

    //OUTPUT to our Audio pin.
#if defined(__arm__) && defined(TEENSYDUINO)
    if (sample != 127)analogWrite(audioPin, sample);
#else
    if (sample != 127)OCR2B = sample;
#endif
  }
  else {
    buffTrigger = false;
    location = 0;
  }
  //  }
}
 
Anybody?

I used a teensy 3.2 @ 24 Mhz with a micro sd card adapter from ebay. I'm not sure how much more I can improve my code to maximize efficiency from playing audio from an SD card.
 
**I know the Teensy Audio library has this, but I want to challenge myself.
....
The bad news: There's static noise(even with a Low Pass filter). And the audio quality is not that pretty.

Maybe do it the easy audio library way, for the sake of testing whether the noise is a hardware problem or something going on within your software.
 
I tested it with your Audio library via DAC at pin A14 on my Teensy 3.2. It sounds really good. :)
So I guess this means my software is making the humming noise. How do I get rid of it? I think my sampling method isn't efficient, but I tried looking at your source code for output_dac.cpp and it looks like your using DMA? What's that, and is there a way to make my software sound better?

BTW, your code looks really advanced yet easy to read, good job. :)
 
Status
Not open for further replies.
Back
Top