Mixing of sound files

Hii all,

I want to mix two sound files as well as play each sound files independently. I have used switch statment mechanism (for eg. If I press 1 on serial monitor, play 1.wav, press 2, play 2.wav, press 3, mix 1.wav and 2.wav). For that, I gave commands on serial monitor.

Now, I am not able to hear mixed sound nor independent sound. I am using timer interrupt and my samples are (16 bit, 11025khz,mono). I also observed that, interrupt does not occur at regular interval. I analysed the code but coud not trace the problem. Please help and thanks in advance.
Please find the attachment of my code.
Note that variable number1 is for switch case and number2 is used for timer interrupt.

#include <SD.h>
#include <SPI.h>
#include <stdlib.h>
#include <stdio.h>
#include "sampler1.h"


Sampler1 sampler1;


// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;
File folder;
int  charno;
int state = 0;

int number1,number2;
String str;
float v, number, m11, m22, m1, m2;



void setup() {
  // debug output at 9600 baud
  Serial.begin(9600);

  // setup SD-card
  Serial.print(F("Initializing SD card..."));
  if (!SD.begin(4)) {
    Serial.println(F(" failed!"));
    while (true);
  }

  Serial.println(F(" done."));



  //turn on the timer clock in the power management controller
  pmc_set_writeprotect(false);

  //we want wavesel 01 with RC
  TC_Configure(TC1, 1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
  TC_SetRC(TC1, 1, 953); // sets <> 11.1 Khz interrupt rate
  TC_Start(TC1, 1);

  // enable timer interrupts on the timer
  TC1->TC_CHANNEL[1].TC_IER = TC_IER_CPCS;
  TC1->TC_CHANNEL[1].TC_IDR = ~TC_IER_CPCS;

  //Enable the interrupt in the nested vector interrupt controller
  //TC4_IRQn where 4 is the timer number * timer channels (3) + the channel
  //number (=(1*3)+1) for timer1 channel1
  NVIC_EnableIRQ(TC4_IRQn);

  root.openRoot(volume);
  folder = SD.open("/SoundP~1/"); //1
  Serial.println(F("Sound directory"));

  if (printDirectory(folder, 0) == 0) {
    Serial.println(F("No wav files, nothing to do!"));
    while (1);
  }

  // Enable DAC1 Port
  analogWrite(DAC1, 0);

}

void loop() {

  getFile();

  // Speed
  Serial.println(F("Speed: "));
  while (Serial.available() == 0) {
  }
  number = Serial.parseFloat();
  Serial.println(number);

  // modification of speed of wav file
  sampler1.setSpeed1(number);
  sampler1.setSpeed2(number);
  // Volume
  Serial.println(F("V: "));
  while (Serial.available() == 0) {

  }

  v = Serial.parseFloat();
  Serial.println(v);

  // Modification of volume
  sampler1.setVol(v);

}

void getFile() {


  boolean gotname = false;
  char junk = ' ';
  Serial.println();

  while (Serial.available() > 0) {

    junk = Serial.read();
  }

  Serial.println(F("File: "));
  while (Serial.available() == 0) {

  }
  number1 = Serial.parseInt();
  Serial.println(number1);
number2 = number1;
  switch (number1) {

    case 1: { // Play 1 .wav

        if (number1 != state) {
           sampler1.sstop();
          sampler1.sstop2();         
          sampler1.init();
          sampler1.load();
          sampler1.splay();
          sampler1.buffill();
          state = number1;

        } else {

          sampler1.splay();
          sampler1.buffill();   
          state = number1;
        }
        ////mixing factor
        Serial.println(F("m1: "));
        while (Serial.available() == 0) {

        }

        m1 = Serial.parseFloat();
        Serial.println(m1);
       

        break;
      }

    case 2 : { // play 2.wav

        if (number1 != state) {
          sampler1.sstop();
          sampler1.sstop2();
          sampler1.init();
          sampler1.load();
          sampler1.splay();
          sampler1.buffill2();
          state = number1;
        } else {

          sampler1.splay();
          sampler1.buffill2();   
          state = number1;
        }
        Serial.println(F("m2: "));
        while (Serial.available() == 0) {

        }

        m2 = Serial.parseFloat();
        Serial.println(m2);
        
        break;
      }

    
    case 3: { // play 1.wav and 2.wav


        if (number1 != state) {
          sampler1.sstop();
          sampler1.sstop2();
          sampler1.init();
          sampler1.load();
          sampler1.splay();
          sampler1.buffill();   
           sampler1.init();
          sampler1.load();
           sampler1.splay();
          sampler1.buffill2();   
          state = number1;
      
        } else {

          sampler1.splay();
          sampler1.buffill();
            sampler1.splay();
          sampler1.buffill2();
          state = number1;
        }

        Serial.println(F("m1: "));
        while (Serial.available() == 0) {

        }

        m1 = Serial.parseFloat();
        Serial.println(m1);

        Serial.println(F("m2: "));
        while (Serial.available() == 0) {

        }

        m2 = Serial.parseFloat();
        Serial.println(m2);

        break;
      }


      default:

      Serial.println(F("No such wav file"));
      break;

  }
 

}

int printDirectory(File dir, int numTabs) {
  String temp;
  int fcount = 0;
  while (true) {
    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      Serial.println(F("*****************************************"));
      return (fcount);
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) Serial.print(F("          "));
    int mylen = strlen(entry.name()) - 4; // compare last 4 characters
    if ((strcmp(entry.name() + mylen, ".WAV") == 0) || (strcmp(entry.name() + mylen, ".wav") == 0)) {
      fcount++;
      temp = entry.name();
      temp.toLowerCase();
      Serial.print(temp);
      for (uint8_t i = mylen; i < 30; i++) Serial.print(F(" "));
      Serial.println(entry.size(), DEC);
    }

    if (entry.isDirectory()) {
      Serial.println(F("/"));
      printDirectory(entry, numTabs + 1);
    }

  }

}


void TC4_Handler() //Interrupt at 11KHz rate 
{ 
   TC_GetStatus(TC1, 1); 

// 2048 is the 0 value of audio out.

  int16_t ulOutput;
  int16_t ulOutput1 = 2048;
  int16_t ulOutput2 = 2048;
  int16_t ulOutput3 = 2048;     
    

    //mix factor in %
    m11 = m1/100;
    m22 = m2/100;
   

    // only 1.wav
  if(number2 == 1){

    ulOutput1 += sampler1.output();
    ulOutput = (ulOutput1 * m11 );    
    if(ulOutput>4095) ulOutput=4095;
      
  }


// only 2.wav
 if(number2 == 2){
    
    ulOutput2 += sampler1.output2();
    ulOutput = (ulOutput2 * m22 );    
    if(ulOutput>4095) ulOutput=4095;
     

  }


// mix 1.wav and 2 .wav
 if(number2 == 3){

    ulOutput1 += sampler1.output();
    ulOutput2 += sampler1.output2();
    ulOutput = (ulOutput1 * m11) + (ulOutput2 * m22) ;
    
    if(ulOutput>4095) ulOutput=4095;
     

  }

 dacc_write_conversion_data(DACC_INTERFACE, ulOutput);    

  
}

I assume you're using an Arduino Due or something with a DAC?

I'm not really following your code, but...

I don't see you writing any audio data to the DAC. Is that handled by a library or something?

...nor independent sound

Does that mean you're not getting any sound? If you're trying to mix, obviously you shouldn't be trying to do that before you can play one file.

Digital mixing is done with sample-by-sample addition* (summation). That is - Sound1, sample1 is added to sound2, sample1 at 11,025 samples per second, and at the end of 1 second, it's sound1, sample 11025 + sound2, sample 11025.

But to avoid clipping, you'll actually be averaging the samples (divide by 2 before summing or before sending the sum to the DAC.) There's no requirement that they be mixed 50/50 and there's no requirement that they sum to 100%, but you can't exceed 100% (0dBFS) or you'll clip (distort). (If the files are not exactly the same length, you'll have to sum zeros or start summing-in another file, etc.)

  • Analog mixers are built-around analog summing amplifiers.

The other thing is that you can't open two files at the same time on an SD card without a lot of messing about.
You need to have a buffer so you can fill it with samples from one file, close it and fill another buffer with samples from the other. Then when the buffer runs out you need to open the first file again go to the place you finished reading it and then extract a fresh buffers worth, and the same with the second file. All this while not skipping any sample output.

Hii,

I am using Arduino Due and I have created a class where I have open the wave files from SD card and stored it in buffer. I didn't attach the another code because the code will be too big. I just want to know my switch cases and Interrupt handler written in the code are right or not? I will still attach my another file which contains samples. This will be far more better to understand what I actually want to do.

#include <arduino.h>
#include <string.h>
#include <stdint.h> // for int16_t and int32_t
#include <stdlib.h>
#include <stdio.h>

// Initialization
unsigned char buffer4[4]; // chunk Size
unsigned char buffer2[2];

unsigned char buffer24[4]; // chunk Size
unsigned char buffer22[2];

long i, i2 = 1;
int y, y2 = 0; // additional chunk
long num_samples, num_samples2;

int data_in_channel, data_in_channel2;
const int bufsize = 4096;          // buffer size in bytes. You can change this data.
const int bufsize2 = 4096;

// WAVE header structure

// Header of a wave file.
// Num_channels will tell us if we are in stereo (2) or mono (1)
typedef struct {
  char RIFF[5];
  int32_t chunk_size;
  char WAVE[5];
  char fmt_[5];
  ---
  ---
  ---
} wave_header;



// Here is our sampler class
class Sampler1
{
  public:
    float volglobal = 1;            // volume max of the sample (when sustain)
    boolean play;               // is the sample playing or not ?
    int16_t buf[bufsize];       
    
    float acc = 0;
    float factor = 1;

    float acc2 = 0;
    float factor2 = 1;

    File myFile, dataFile;                // The wave file
    wave_header header, header2;        // The wave header
    size_t length1;
    boolean openfile ;          // Are we opening file ?
    boolean closefile ;         // Are we closing file ?
    const char* samplen;        // Storing the sample name


    // Init of the sampler object. Here you can put your default values.
    void init()
    {
      play = false;
      volglobal = 1.0;
      openfile = false;
      closefile = false;
    }

    // Play the sample, giving volume and note (volume 0-1024; note 0-128)
    void splay()
    {
      play = true;
    }

    // load the wave header in the header data, and the beginning of the wave file in the buffer.
    void load()
    {

      closefile = false;
      openfile = true;
    }

    // Set the volume of the sample
    void setVol(float vol)
    {
      volglobal = vol / 100;


    }



    void setSpeed1(float num) {

      factor = num / 100;

    }

    void setSpeed2(float num) {

      factor2 = num / 100;

    }



    void sstop()
    {
      if (play)
      {
        play = false;
        for (i = 0; i < num_samples; i++) {
          buf[i] = 0;
        }
        volglobal = 0;
        acc = 0;
        factor = 0;
        closefile = true;
      }
    }


    void sstop2()
    {
      if (play)
      {
        play = false;

        for (i2 = 0; i2 < num_samples2; i2++) {
          buf2[i2] = 0;
        }
        volglobal = 0;
        acc2 = 0;
        factor2 = 0;
        closefile = true;
      }
    }


    // Fill the buffer if it has to be.
    // This method must not be called in the main program loop.
    // The return of the method indicates that it is reading a sound file
    boolean buffill()
    {
      boolean ret = false;
      if (play)
      {
        ret = true;

        // OPEN FILE
        if (openfile)
        {


         myFile = SD.open("/SoundP~1/1.wav", O_READ);


          if (myFile == NULL) {
            printf("Error opening in file\n");
            exit(1);
          }



          length1 = 0;


          // Read header parts

          //RIFF for 1.wav
          length1 =  myFile.read(header.RIFF, sizeof(header.RIFF-1));

//          printf("%s\t%d\n", header.RIFF,sizeof(header.RIFF));
//int pos = myFile.position();
//Serial.println(pos);


          // Compare RIFF
          if (!strcmp(header.RIFF, "RIFF")) {
            printf("(1-4): %s \n", header.RIFF);

            //chunk size
            length1 = myFile.read(buffer4, sizeof(buffer4));
            printf("%u %u %u %u\n", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);

            // convert little endian to big endian 4 byte int
            header.chunk_size  = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
            printf("(5-8) Overall size: bytes:%u, Kb:%u \n", header.chunk_size, header.chunk_size / 1024);

            //WAVE
            length1 =  myFile.read(header.WAVE, sizeof(header.WAVE-1));
          }



          // Comparer WAVE for 1.wav
          if (!strcmp(header.WAVE, "WAVE")) {
           
          -------
          -------
          -------

            length1 =  myFile.read(header.DATA, sizeof(header.DATA-1));
          }



          // Compare if it is data and then read size of samples and read the samples

          printf(" DATA marker: %s\n", header.DATA);

          length1 = myFile.read(buffer4, sizeof(buffer4));
          printf("%u %u %u %u\n", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);
          // convert little endian to big endian 4 byte int
          header.subchunk2_size  = buffer4[0] | (buffer4[1] << 8) | (buffer4[2] << 16) | (buffer4[3] << 24);
          printf("(41-44) Overall size: bytes:%u, Kb:%u \n", header.subchunk2_size, header.subchunk2_size);

          //Calculate no of samples
          num_samples = (8 * header.subchunk2_size) / (header.num_channels * header.bits_per_sample);
          printf("Number of sample: %lu", num_samples);

          //size of each sample
          long size_of_each_sample = (header.num_channels * header.bits_per_sample) / 8;
          printf("\nSize of each sample:%ld bytes\n", size_of_each_sample);
          char data_buffer[size_of_each_sample];
      


          for (i = 1; i <= num_samples; i++) {
            //           printf("==========Sample %ld / %ld============= \n", i, num_samples);
            length1 = myFile.read(data_buffer, sizeof(data_buffer));
            // convert data from little endian to big endian based on bytes in each channel sample
            data_in_channel =  data_buffer[0] | (data_buffer[1] << 8);

//               printf(" %d\n ", data_in_channel);

            //  Data stored in buffer
            buf[i] = (data_in_channel >> 4);

            //            printf("%d\n", buf[i]);
          }

          myFile.close();
          openfile = false;



        }

        else {
          //
          

                Serial.println("In Buffil1");
          play = true;

        }

      }
      return ret;

    }


    
    boolean buffill2()
    {
          
      //Same for 2.wav


    }


   

    // Read the wave file at a position. Returns the volume (12 bits)
    int16_t output()
    {
      int16_t ret1 = 0;

      if (play)
      {


        if (acc == 0) {
          acc = factor;
        }

        ret1 = ((buf[int(acc)]) * volglobal);

        acc =  acc + factor; //1.5+1.5=3

        if (acc > num_samples) {

          acc -= num_samples;

        }

        return ret1;

      }

    }

    // Read the wave file at a position. Returns the volume (12 bits)
    int16_t output2()
    {
      int16_t ret2 = 0;

      if (play)
      {


        if (acc2 == 0) {
          acc2 = factor2;
        }

        ret2 = ((buf2[int(acc2)]) * volglobal);

        acc2 =  acc2 + factor2; //1.5+1.5=3

        if (acc2 > num_samples2) {

          acc2 -= num_samples2;

        }

        return ret2;

      }



    }

Hii Mike,

I understood what you want to say, my samples are too short so one buffer of size 4096 is enough for my samples. I am opening the wave file from SD card read the wave file header and only store the data in buffer. The main problem is Timer interrupt does not occur at interval. I don't know why? I just need the confirmation of my switch case and Timer interrupt code because I already worked on increasing and decreasing the volume that time each independent wav files were running properly. Problem arised only when mixing the samples.

Hii Grumpy and DVDdough,

I am able to hear the sound I missed one instruction of timer interrupt " pmc_enable_periph_clk(ID_TC4);".

I am not able to hear clear sound.

so one buffer of size 4096 is enough for my samples.

Then I would fill one buffer with one sample and shift each sample one place to the right. Open the second file, read each sample, shift it one place to the right and add it to the buffer. Then play the buffer. This will be a lot simpler than trying to do it during the playing and will not take very long.

Just make sure that your 16 bit samples in the wav files do not exceed a 12 bit size of 4095 because the D/A in the Due is only 12 bits. You can save on the shifting if you scale the samples so they don't exceed an 11 bit sample size of 2047.