Streaming audio to Arduino: looping issue

I want to stream 8-bit 8kHz PCM data to an Arduino board using the serial port. It almost works, but whenever I reset the Arduino and start the Processing app, the appropriate sample gets sent over to the Arduino, and it strangely loops for a couple of seconds. Here is my Arduino code:

#define SAMPLE_RATE 8000
#define BUFFER_SIZE 1024

unsigned long sounddata_length=0;
unsigned char sounddata_data[BUFFER_SIZE];
int BufferHead=0;
int BufferTail=0;
unsigned long sample=0;
unsigned long BytesReceived=0;

unsigned long Temp=0;
unsigned long NewTemp=0;

int ledPin = 13;
int speakerPin = 11;
int Playing = 0;

//Interrupt Service Routine (ISR)
// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect)
{
   //If not at the end of audio
   if (sample < sounddata_length)  
   {
       //Set the PWM Freq.
       OCR2A = sounddata_data[BufferTail];
       //If circular buffer is not empty
       if (BufferTail != BufferHead)  
       {
       //Increment Buffer's tail index.
       BufferTail = ((BufferTail+1) % BUFFER_SIZE);
       //Increment sample number.
       sample++;
       }//End if
   }//End if
   else //We are at the end of audio
   {
       //Stop playing.
       stopPlayback();
   }//End Else

}//End Interrupt

void startPlayback()
{

   //---------------TIMER 2-------------------------------------
   // Set up Timer 2 to do pulse width modulation on the speaker
   // pin.  
   //This plays the music at the frequency of the audio sample.

   // Use internal clock (datasheet p.160)
   //ASSR = Asynchronous Status Register
   ASSR &= ~(_BV(EXCLK) | _BV(AS2));

   // Set fast PWM mode  (p.157)
   //Timer/Counter Control Register A/B for Timer 2
   TCCR2A |= _BV(WGM21) | _BV(WGM20);
   TCCR2B &= ~_BV(WGM22);

   // Do non-inverting PWM on pin OC2A (p.155)
   // On the Arduino this is pin 11.
   TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
   TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));

   // No prescaler (p.158)
   TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

   //16000000 cycles       1 increment    2000000 increments
   //--------        *  ----            = -------
   //       1 second       8 cycles             1 second

   //Continued...
   //2000000 increments     1 overflow      7812 overflows
   //-------            * ---            = -----
   //      1 second       256 increments       1 second




   // Set PWM Freq to the sample at the end of the buffer.
   OCR2A = sounddata_data[BufferTail];






   //--------TIMER 1----------------------------------
   // Set up Timer 1 to send a sample every interrupt.
   // This will interrupt at the sample rate (8000 hz)
   //

   cli();

   // Set CTC mode (Clear Timer on Compare Match) (p.133)
   // Have to set OCR1A *after*, otherwise it gets reset to 0!
   TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
   TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));

   // No prescaler (p.134)
   TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

   // Set the compare register (OCR1A).
   // OCR1A is a 16-bit register, so we have to do this with
   // interrupts disabled to be safe.
   OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 8000 = 2000

   //Timer/Counter Interrupt Mask Register
   // Enable interrupt when TCNT1 == OCR1A (p.136)
   TIMSK1 |= _BV(OCIE1A);


   //Init Sample.  Start from the beginning of audio.
   sample = 0;
   
   //Enable Interrupts
   sei();  
}//End StartPlayback

void stopPlayback()
{
   // Disable playback per-sample interrupt.
   TIMSK1 &= ~_BV(OCIE1A);

   // Disable the per-sample timer completely.
   TCCR1B &= ~_BV(CS10);

   // Disable the PWM timer.
   TCCR2B &= ~_BV(CS10);

   digitalWrite(speakerPin, LOW);
}//End StopPlayback



   //Use the custom powlong() function because the standard
   //pow() function uses floats and has rounding errors.
   //This powlong() function does only integer powers.
   //Be careful not to use powers that are too large, otherwise
   //this function could take a really long time.
long powlong(long x, long y)
{
 //Base case for recursion
 if (y==0)
 {
   return(1);
 }//End if
 else
 {
   //Do recursive call.
   return(powlong(x,y-1)*x);
 }//End Else
}


void setup()
{
   //Set pin for OUTPUT mode.
   pinMode(speakerPin, OUTPUT);
   
   //Set LED for OUTPUT mode
   pinMode(ledPin, OUTPUT);
   
   //Start Serial port.  If your application can handle a
   //faster baud rate, that would increase your bandwidth
   //115200 only allows for 14,400 Bytes/sec.  Audio will
   //require 8000 bytes / sec to play at the correct speed.
   //This only leaves 44% of the time free for processing
   //bytes.
   Serial.begin(115200);

   Serial.print("G");
   
   //PC sends audio length as 10-digit ASCII
   //While audio length hasn't arrived yet
   while (Serial.available()<10)
   {
   //Blink the LED on pin 13.
   digitalWrite(ledPin,!digitalRead(ledPin));
   delay(100);
   }
   
   //Init number of audio samples.
   sounddata_length=0;
   
   //Convert 10 ASCII digits to an unsigned long.
   for (int i=0;i<10;i++)
   {
   //Convert from ASCII to int
   Temp=Serial.read()-'0'; 
   //Shift the digit the correct location.
   NewTemp = Temp * powlong(10,9-i);  
   //Add the current digit to the total.
   sounddata_length = sounddata_length + NewTemp;
   }//End for

   //Tell the remote PC/device that the Arduino is ready
   //to begin receiving samples.
   Serial.println('S');
     
   //There's data now, so start playing.
   //startPlayback();
   Playing = 0;
}//End Setup

void loop()
{
 //If audio not started yet...
 if (Playing == 0)
 {
   //Check to see if the first 1000 bytes are buffered.
   if (BufferHead == 1023)
   {
     Playing=1;
     startPlayback();
   }//End if
 }//End if
 
 //While the serial port buffer has data
 while (Serial.available()>0)
 {
   //If the sample buffer isn't full
   if (((BufferHead+1) % BUFFER_SIZE) != BufferTail)
   {
     //Increment the buffer's head index.
     BufferHead = (BufferHead+1) % BUFFER_SIZE;
     //Store the sample freq.
     sounddata_data[BufferHead] = Serial.read();
     //Increment the bytes received
     BytesReceived++;
     }//End if

   //if the Serial port buffer has room
   if ((BytesReceived % 128) == 0)
   {
     //Tell the remote PC how much space you have.
     Serial.println('S');
   }//End if
 }//End While
}//End Loop

and here is my processing code:

import processing.serial.*;
import java.util.*;

Serial myPort;  // Create object from Serial class
char val;      // Data received from the serial port
int index = 0;

byte[] sound;

void setup()
{
  size(200, 200);
  // I know that the first port in the serial list on my mac
  // is always my  FTDI adaptor, so I open Serial.list()[0].
  // On Windows machines, this generally opens COM1.
  // Open whatever port is the one you're using.
  myPort = new Serial(this, "COM6", 115200);
  
  background(255);             // Set background to white

  String[] loadedSfx = loadStrings("sfx.txt");
  sound = byte(int(loadedSfx[1].split(", "))); //extremely ugly, cannot cast string to char/byte :(

  while ( myPort.available() > 0/* || myPort.readChar() != 'G'*/);  // wait for G command
  println("GO");


  myPort.write(nf(sound.length, 10)); //send num of samples as decimal num
}

void draw()
{
  if ( myPort.available() > 0) {  // If data is available,
    val = myPort.readChar();      // read it and store it in val
  }
  if (val == 'S') {              // If the serial value is S,
    myPort.write(subset(sound, index, index+128)); //send sound data
    index += 128;
    //i measured that this gets executed every 216-228 ms and that it's probably related to the problem
    val = 0;
  } else print(val);
}

void keyPressed()
{
  myPort.write(subset(sound, index, index+128)); //send sound data
  index += 128;
}

Why does it loop like this? How can I fix it?

I guess you are never reading the next serial byte when you know one is available. Can you test for that possibility?
Paul

I found out that it is this issue, but I still wonder how I can fix it.

arduino_uno_cool:
I found out that it is this issue, but I still wonder how I can fix it.

You can't fix it until you find the problem. Begin using by using serialprint() to display possible locations and values that may be causing the problem.
Paul

maybe there is an issue with the timer not updating the buffer tail, but i don't really see how and why

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.