📬❄️📦Adafruit Holiday Shipping Deadlines 2019: Please place all UPS 3 Day orders by 11am ET Friday December 13 📬❄️📦
0

Waveshield + Arduino Mega -> Distorted sound
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Re: Waveshield + Arduino Mega -> Distorted sound

by adafruit_support_bill on Fri Mar 18, 2011 7:56 am

Hmmm.

Un-comment some of the Serial.print statements in check_switches(). Then open up the Serial Monitor so we can see if it is getting called.

adafruit_support_bill
 
Posts: 75298
Joined: Sat Feb 07, 2009 10:11 am

Re: Waveshield + Arduino Mega -> Distorted sound

by dy-wen on Fri Mar 18, 2011 12:52 pm

ooff!
Weird stuff going on -> see the screenshot of the serial monitor output.

Screenshot--dev-ttyUSB0.png
Screenshot--dev-ttyUSB0.png (20.25 KiB) Viewed 2012 times

When I don't take out the usb cable of the Arduino, my whole computer freezes and the data displayed in the serial monitor first fly by, then they start trickling in until it freezes. I'll check what the other Serial print stuff gives as messages.

I have only uncommented one part of the Serial print output of the check_switches:
Code: Select all | TOGGLE FULL SIZE
void check_switches()
{
  static byte previousstate[NUMBUTTONS];
  static byte currentstate[NUMBUTTONS];
  byte index;

  for (index = 0; index < NUMBUTTONS; index++) {
    currentstate[index] = digitalRead(buttons[index]);   // read the button
   
    /*     
    Serial.print(index, DEC);
    Serial.print(": cstate=");
    Serial.print(currentstate[index], DEC);
    Serial.print(", pstate=");*/
    Serial.print(previousstate[index], DEC);
    Serial.print(", press=");
   
   
    if (currentstate[index] == previousstate[index]) {
      if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
          // just pressed
          justpressed[index] = 1;
      }
      else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
          // just released
          justreleased[index] = 1;
      }
      pressed[index] = !currentstate[index];  // remember, digital HIGH means NOT pressed
    }
    //Serial.println(pressed[index], DEC);
    previousstate[index] = currentstate[index];   // keep a running tally of the buttons
  }
}


I'll check what the other Serial print stuff gives similar messages -> no change in state - if I press a button or not.

For the record, this is the code I use at the moment.

Code: Select all | TOGGLE FULL SIZE
#include <FatReader.h>
#include <SdReader.h>
#include <avr/pgmspace.h>
#include "WaveUtil.h"
#include "WaveHC.h"


SdReader card;    // This object holds the information for the card
FatVolume vol;    // This holds the information for the partition on the card
FatReader root;   // This holds the information for the filesystem on the card
FatReader f;      // This holds the information for the file we're play

WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

#define DEBOUNCE 5  // button debouncer

// here is where we define the buttons that we'll use. button "1" is the first, button "6" is the 6th, etc
byte buttons[] = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41};
// This handy macro lets us determine how big the array up above is, by checking the size
#define NUMBUTTONS sizeof(buttons)
// we will track if a button is just pressed, just released, or 'pressed' (the current state
volatile byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];

// this handy function will return the number of bytes currently free in RAM, great for debugging!   
int freeRam(void)
{
  extern int  __bss_end;
  extern int  *__brkval;
  int free_memory;
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
  }
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval);
  }
  return free_memory;
}

void sdErrorCheck(void)
{
  if (!card.errorCode()) return;
  putstring("\n\rSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  putstring(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}

void setup() {
  byte i;
 
  // set up serial port
  Serial.begin(9600);
  putstring_nl("WaveHC with ");
  Serial.print(NUMBUTTONS, DEC);
  putstring_nl("buttons");
 
  putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(freeRam());      // if this is under 150 bytes it may spell trouble!
 
  // Set the output pins for the DAC control. This pins are defined in the library
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
 
  // pin13 LED
  pinMode(13, OUTPUT);
 
  // Make input & enable pull-up resistors on switch pins
  for (i=0; i< NUMBUTTONS; i++) {
    pinMode(buttons[i], INPUT);
    digitalWrite(buttons[i], HIGH);
  }
 
  //  if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
  if (!card.init()) {         //play with 8 MHz spi (default faster!) 
    putstring_nl("Card init. failed!");  // Something went wrong, lets print out why
    sdErrorCheck();
    while(1);                            // then 'halt' - do nothing!
  }
 
  // enable optimize read - some cards may timeout. Disable if you're having problems
  card.partialBlockRead(true);
 
// Now we will look for a FAT partition!
  uint8_t part;
  for (part = 0; part < 5; part++) {     // we have up to 5 slots to look in
    if (vol.init(card, part))
      break;                             // we found one, lets bail
  }
  if (part == 5) {                       // if we ended up not finding one  :(
    putstring_nl("No valid FAT partition!");
    sdErrorCheck();      // Something went wrong, lets print out why
    while(1);                            // then 'halt' - do nothing!
  }
 
  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  putstring(", type is FAT");
  Serial.println(vol.fatType(),DEC);     // FAT16 or FAT32?
 
  // Try to open the root directory
  if (!root.openRoot(vol)) {
    putstring_nl("Can't open root dir!"); // Something went wrong,
    while(1);                             // then 'halt' - do nothing!
  }
 
  // Whew! We got past the tough parts.
  putstring_nl("Ready!");
 
  TCCR2A = 0;
  TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20;

  //Timer2 Overflow Interrupt Enable
  TIMSK2 |= 1<<TOIE2;


}

SIGNAL(TIMER2_OVF_vect) {
  check_switches();
}

void check_switches()
{
  static byte previousstate[NUMBUTTONS];
  static byte currentstate[NUMBUTTONS];
  byte index;

  for (index = 0; index < NUMBUTTONS; index++) {
    currentstate[index] = digitalRead(buttons[index]);   // read the button
   
     
    /*Serial.print(index, DEC);
    Serial.print(": cstate=");
    Serial.print(currentstate[index], DEC);
    Serial.print(", pstate=");*/
    Serial.print(previousstate[index], DEC);
    Serial.print(", press=");
   
   
    if (currentstate[index] == previousstate[index]) {
      if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
          // just pressed
          justpressed[index] = 1;
      }
      else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
          // just released
          justreleased[index] = 1;
      }
      pressed[index] = !currentstate[index];  // remember, digital HIGH means NOT pressed
    }
    //Serial.println(pressed[index], DEC);
    previousstate[index] = currentstate[index];   // keep a running tally of the buttons
  }
}


void loop() {
  byte i;
  delay(200);
  if (justpressed[0]) {
    justpressed[0] = 0;
    playfile("SOUND1.WAV");
  }
  else if (justpressed[1]) {
      justpressed[1] = 0;
      playfile("SOUND2.WAV");
  }
  else if (justpressed[2]) {
      justpressed[2] = 0;
      playfile("SOUND3.WAV");
  }
  else if (justpressed[3]) {
      justpressed[3] = 0;
      playfile("SOUND4.WAV");
  }
  else if (justpressed[4]) {
      justpressed[4] = 0;
      playfile("SOUND5.WAV");
  }
  else if (justpressed[5]) {
      justpressed[5] = 0;
      playfile("SOUND6.WAV");
  }
  else if (justpressed[6]) {
      justpressed[6] = 0;
      playfile("SOUND7.WAV");
  }
    else if (justpressed[7]) {
      justpressed[7] = 0;
      playfile("SOUND8.WAV");
  }
    else if (justpressed[8]) {
      justpressed[8] = 0;
      playfile("SOUND9.WAV");
  }
    else if (justpressed[9]) {
      justpressed[9] = 0;
      playfile("SOUND10.WAV");
  }
    else if (justpressed[10]) {
      justpressed[10] = 0;
      playfile("SOUND11.WAV");
  }
    else if (justpressed[11]) {
      justpressed[11] = 0;
      playfile("SOUND12.WAV");
  }
    else if (justpressed[12]) {
      justpressed[12] = 0;
      playfile("SOUND13.WAV");
  } 
    else if (justpressed[13]) {
      justpressed[13] = 0;
      playfile("SOUND14.WAV");
  }
    else if (justpressed[14]) {
      justpressed[14] = 0;
      playfile("SOUND15.WAV");
  } 
    else if (justpressed[15]) {
      justpressed[15] = 0;
      playfile("SOUND16.WAV");
  }
    else if (justpressed[16]) {
      justpressed[16] = 0;
      playfile("SOUND17.WAV");
  } 
    else if (justpressed[17]) {
      justpressed[17] = 0;
      playfile("SOUND18.WAV");
  } 
    else if (justpressed[18]) {
      justpressed[18] = 0;
      playfile("SOUND19.WAV");
  } 
    else if (justpressed[19]) {
      justpressed[19] = 0;
      playfile("SOUND20.WAV");
  } 
}



// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
  // call our helper to find and play this name
  playfile(name);
  while (wave.isplaying) {
  // do nothing while its playing
  }
  // now its done playing
}

void playfile(char *name) {
  // see if the wave object is currently doing something
  if (wave.isplaying) {// already playing something, so stop it!
    wave.stop(); // stop it
  }
  // look in the root directory and open the file
  if (!f.open(root, name)) {
    putstring("Couldn't open file "); Serial.print(name); return;
  }
  // OK read the file and turn it into a wave object
  if (!wave.create(f)) {
    putstring_nl("Not a valid WAV"); return;
  }
 
  // ok time to play! start playback
  wave.play();
}
dy-wen
 
Posts: 30
Joined: Wed Sep 16, 2009 11:31 am

Re: Waveshield + Arduino Mega -> Distorted sound

by adafruit_support_bill on Sat Mar 19, 2011 8:11 am

So you are still using the interrupt to check the switches. That will be generating serial output faster than it can be transmitted. That explains the freezing.

What happens if you comment out the interrupt code and move the check_switches to the loop?

adafruit_support_bill
 
Posts: 75298
Joined: Sat Feb 07, 2009 10:11 am

Re: Waveshield + Arduino Mega -> Distorted sound

by dy-wen on Wed Mar 23, 2011 7:33 am

Hey

It took me a while to respond, because everything I tried, I ran into big red compilation errors. Part of the problem was me taking your instructions to literally (I really tried to move the whole code of check_switches into the loop -> whilst just moving check_switches itself was enough.)

Two amateur coders together (someone else and me :-)) found out more about the timer aspect added to the code:

Code: Select all | TOGGLE FULL SIZE
 
  TCCR2A = 0;
  TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20;

  //Timer2 Overflow Interrupt Enable
  TIMSK2 |= 1<<TOIE2;


}

SIGNAL(TIMER2_OVF_vect) {
  check_switches();
}


--> This is what I found out about this timer: http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/ -> it uses pwm ports -> which would really explain the noise (hm or not? would it be better to use timer1? - I'm too newbie for this).

A visit to the Brussels Hackerspace https://hackerspace.be where I met the pro-coder Leandro, someone who can read and write :-), actually solved the noise problem.

Here are some screenshots of the differences (of the original code and the changed code):

Taking out the timer:
Taking_out_the_timer.png
Taking_out_the_timer.png (114.59 KiB) Viewed 1984 times


Moving check_switches to the loop and adding delay:
Moving_check_switches_and_adding_delay.png
Moving_check_switches_and_adding_delay.png (84.9 KiB) Viewed 1984 times


Final code: complete:

Code: Select all | TOGGLE FULL SIZE
#include <FatReader.h>
#include <SdReader.h>
#include <avr/pgmspace.h>
#include "WaveUtil.h"
#include "WaveHC.h"


SdReader card;    // This object holds the information for the card
FatVolume vol;    // This holds the information for the partition on the card
FatReader root;   // This holds the information for the filesystem on the card
FatReader f;      // This holds the information for the file we're play

WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

#define DEBOUNCE 5  // button debouncer

// here is where we define the buttons that we'll use. button "1" is the first, button "6" is the 6th, etc
byte buttons[] = {22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41};
// This handy macro lets us determine how big the array up above is, by checking the size
#define NUMBUTTONS sizeof(buttons)
// we will track if a button is just pressed, just released, or 'pressed' (the current state
volatile byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];

// this handy function will return the number of bytes currently free in RAM, great for debugging!   
int freeRam(void)
{
  extern int  __bss_end;
  extern int  *__brkval;
  int free_memory;
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
  }
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval);
  }
  return free_memory;
}

void sdErrorCheck(void)
{
  if (!card.errorCode()) return;
  putstring("\n\rSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  putstring(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}

void setup() {
  byte i;
 
  // set up serial port
  Serial.begin(9600);
  Serial.print("WaveHC with ");
  Serial.print(NUMBUTTONS, DEC);
  putstring_nl(" buttons");
 
  putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(freeRam());      // if this is under 150 bytes it may spell trouble!
 
  // Set the output pins for the DAC control. This pins are defined in the library
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
 
  // pin13 LED
  pinMode(13, OUTPUT);
 
  // Make input & enable pull-up resistors on switch pins
  for (i=0; i< NUMBUTTONS; i++) {
    pinMode(buttons[i], INPUT);
    digitalWrite(buttons[i], HIGH);
  }
 
  //  if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
  if (!card.init()) {         //play with 8 MHz spi (default faster!) 
    putstring_nl("Card init. failed!");  // Something went wrong, lets print out why
    sdErrorCheck();
    while(1);                            // then 'halt' - do nothing!
  }
 
  // enable optimize read - some cards may timeout. Disable if you're having problems
  card.partialBlockRead(true);
 
// Now we will look for a FAT partition!
  uint8_t part;
  for (part = 0; part < 5; part++) {     // we have up to 5 slots to look in
    if (vol.init(card, part))
      break;                             // we found one, lets bail
  }
  if (part == 5) {                       // if we ended up not finding one  :(
    putstring_nl("No valid FAT partition!");
    sdErrorCheck();      // Something went wrong, lets print out why
    while(1);                            // then 'halt' - do nothing!
  }
 
  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  putstring(", type is FAT");
  Serial.println(vol.fatType(),DEC);     // FAT16 or FAT32?
 
  // Try to open the root directory
  if (!root.openRoot(vol)) {
    putstring_nl("Can't open root dir!"); // Something went wrong,
    while(1);                             // then 'halt' - do nothing!
  }
 
  // Whew! We got past the tough parts.
  putstring_nl("Ready!");

}



void check_switches()
{
  static byte previousstate[NUMBUTTONS];
  static byte currentstate[NUMBUTTONS];
  byte index;

  for (index = 0; index < NUMBUTTONS; index++) {
    currentstate[index] = digitalRead(buttons[index]);   // read the button
     
    /*Serial.print(index, DEC);
    Serial.print(": cstate=");
    Serial.print(currentstate[index], DEC);
    Serial.print(", pstate=");
    Serial.print(previousstate[index], DEC);
    Serial.print(", press=");*/
   
   
    if (currentstate[index] == previousstate[index]) {
      if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
          // just pressed
          justpressed[index] = 1;
      }
      else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
          // just released
          justreleased[index] = 1;
      }
      pressed[index] = !currentstate[index];  // remember, digital HIGH means NOT pressed
    }
    //Serial.println(pressed[index], DEC);
    previousstate[index] = currentstate[index];   // keep a running tally of the buttons
  }
}


void loop() {
  byte i;
  check_switches();

  delay(200);
  if (justpressed[0]) {
    justpressed[0] = 0;
    playfile("SOUND1.WAV");
  }
  else if (justpressed[1]) {
      justpressed[1] = 0;
      playfile("SOUND2.WAV");
  }
  else if (justpressed[2]) {
      justpressed[2] = 0;
      playfile("SOUND3.WAV");
  }
  else if (justpressed[3]) {
      justpressed[3] = 0;
      playfile("SOUND4.WAV");
  }
  else if (justpressed[4]) {
      justpressed[4] = 0;
      playfile("SOUND5.WAV");
  }
  else if (justpressed[5]) {
      justpressed[5] = 0;
      playfile("SOUND6.WAV");
  }
  else if (justpressed[6]) {
      justpressed[6] = 0;
      playfile("SOUND7.WAV");
  }
    else if (justpressed[7]) {
      justpressed[7] = 0;
      playfile("SOUND8.WAV");
  }
    else if (justpressed[8]) {
      justpressed[8] = 0;
      playfile("SOUND9.WAV");
  }
    else if (justpressed[9]) {
      justpressed[9] = 0;
      playfile("SOUND10.WAV");
  }
    else if (justpressed[10]) {
      justpressed[10] = 0;
      playfile("SOUND11.WAV");
  }
    else if (justpressed[11]) {
      justpressed[11] = 0;
      playfile("SOUND12.WAV");
  }
    else if (justpressed[12]) {
      justpressed[12] = 0;
      playfile("SOUND13.WAV");
  } 
    else if (justpressed[13]) {
      justpressed[13] = 0;
      playfile("SOUND14.WAV");
  }
    else if (justpressed[14]) {
      justpressed[14] = 0;
      playfile("SOUND15.WAV");
  } 
    else if (justpressed[15]) {
      justpressed[15] = 0;
      playfile("SOUND16.WAV");
  }
    else if (justpressed[16]) {
      justpressed[16] = 0;
      playfile("SOUND17.WAV");
  } 
    else if (justpressed[17]) {
      justpressed[17] = 0;
      playfile("SOUND18.WAV");
  } 
    else if (justpressed[18]) {
      justpressed[18] = 0;
      playfile("SOUND19.WAV");
  } 
    else if (justpressed[19]) {
      justpressed[19] = 0;
      playfile("SOUND20.WAV");
  } 
}



// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
  // call our helper to find and play this name
  playfile(name);
  while (wave.isplaying) {
  // do nothing while its playing
  }
  // now its done playing
}

void playfile(char *name) {
  // see if the wave object is currently doing something
  if (wave.isplaying) {// already playing something, so stop it!
    wave.stop(); // stop it
  }
  // look in the root directory and open the file
  if (!f.open(root, name)) {
    putstring("Couldn't open file "); Serial.print(name); return;
  }
  // OK read the file and turn it into a wave object
  if (!wave.create(f)) {
    putstring_nl("Not a valid WAV"); return;
  }
 
  // ok time to play! start playback
  wave.play();
}


I do realize why the timer was in there. When you don't use buttons for a while, and you push one, it is not "awake" immediately, a part of the sound gets cut. I'll do some fiddling with the delay time - see to where I can push it.
Thanks a lot for your patience :-) -> I'll give a little sign to the guy who's writing code for the waveshield library.

Tomorrow (!!) I'm installing this in an interactive map/sound installation with stories: http://www.constantvzw.org/site/Stitching-Stories-reNUM.html
dy-wen
 
Posts: 30
Joined: Wed Sep 16, 2009 11:31 am

Re: Waveshield + Arduino Mega -> Distorted sound

by adafruit_support_bill on Wed Mar 23, 2011 8:11 am

Great that you got it working! :D

I do realize why the timer was in there. When you don't use buttons for a while, and you push one, it is not "awake" immediately, a part of the sound gets cut.


The delay was a simple way to get rid of the noise, but neither the timer nor the delay are optimal solutions. The best way to do it is with 'pin-change interrupts' that generate an interrupt only when a button is pressed. That will keep your buttons responsive, without having to check them thousands of times per second. http://www.arduino.cc/playground/Main/PinChangeInt

adafruit_support_bill
 
Posts: 75298
Joined: Sat Feb 07, 2009 10:11 am

Re: Waveshield + Arduino Mega -> Distorted sound

by dy-wen on Wed Mar 23, 2011 9:25 am

sweet!
and..

sweat...

If I understand it correctly (the context is giving me the meaning), this library is for regular Arduino's:
The PinChangeInt effort runs in the other direction, with clarity sacrificed for the highest performance. To that end, this implementation can handle a 4.5 KHz input signal (with a 1 KHz timer running as well) on an 8 MHz ATMega328, with significant code in both the timer and pin change interrupt handlers.


--> whilst http://arduino.cc/en/Main/ArduinoBoardMega

Summary
Microcontroller ATmega1280
Operating Voltage 5V
Input Voltage (recommended) 7-12V
Input Voltage (limits) 6-20V
Digital I/O Pins 54 (of which 14 provide PWM output)
Analog Input Pins 16
DC Current per I/O Pin 40 mA
DC Current for 3.3V Pin 50 mA
Flash Memory 128 KB of which 4 KB used by bootloader
SRAM 8 KB
EEPROM 4 KB
Clock Speed 16 MHz


Brain.. hurts.. ;-)
dy-wen
 
Posts: 30
Joined: Wed Sep 16, 2009 11:31 am

Re: Waveshield + Arduino Mega -> Distorted sound

by adafruit_support_bill on Wed Mar 23, 2011 9:36 am

Sorry, forgot you were on the Mega :oops:
I'm having trouble accessing the Arduino site at the moment, but if you search, I'm sure that somebody has done pin-change interrupts on the Mega as well.

adafruit_support_bill
 
Posts: 75298
Joined: Sat Feb 07, 2009 10:11 am

Re: Waveshield + Arduino Mega -> Distorted sound

by dy-wen on Fri Mar 25, 2011 3:33 pm

Hey

No problemo :-)

I have made a bad observation --> in a previous post I reply that the delay option sometimes bites off the beginning of the sound -> but this is not the case! I was using fancy Phonic active monitor speakers, which was set on the special option of "auto-on"
( http://www.phonic.com/images/detailed/p8a_back12645941754b602cff79390.jpg )

This was actually what was gnawing off the beginning minutes of the sound!

Now, I do wonder whether this sketch, using the delay, could become problematic. Will it uhm, disintegrate? Do damage? Stop working?
PinChangeInt is better - because it's more stable?
Going abstract here :-)
Greetz and big thanks
dy-wen
 
Posts: 30
Joined: Wed Sep 16, 2009 11:31 am

Re: Waveshield + Arduino Mega -> Distorted sound

by adafruit_support_bill on Fri Mar 25, 2011 4:42 pm

The delay won't hurt anything. If it isn't broken, there is no need to fix it :D . Interrupts are just a more elegant and responsive solution.

adafruit_support_bill
 
Posts: 75298
Joined: Sat Feb 07, 2009 10:11 am

Please be positive and constructive with your questions and comments.