Using WaveShield DAC in custom application

Adafruit Ethernet, Motor, Proto, Wave, Datalogger, GPS Shields - etc!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
FalconFour
 
Posts: 14
Joined: Sun Jun 12, 2011 9:49 am

Using WaveShield DAC in custom application

Post by FalconFour »

So, I thought the WaveShield may be useful for more than just playing one file at a time out of the (quite complicated) SD library... why not take the SD out of the equation completely, and play our own data? Simple, right? It's just a DAC... digital in, analog out.

... that was about 5 hours ago. :(

I can't even get this thing to play intelligible audio via 115kbps serial. In the best mode I could find - 11050Hz, 8 bit audio " << 3" padded - it made a loud and scratchy noise that barely even resembled the original audio playing slowly.

It was kinda like it was--... hmm. Like, playing 2 bytes per sample, somehow broken up or otherwise distorted by the transmitter. Or misinterpreted by the receiver. OK, so that's a possibility. 115Kbps serial maybe dropping something? Maybe my terminal software (Tera Term, to send the binary data) was scrambling the data. Maybe the Serial.available() was seeing 2 bytes (data, then null to end?)...? So I made a simple "LCD console" sketch that spits the hex values onto screen as they come in over serial (using 2 line buffers so it can do a scrolling-line-break when it reaches the end). That worked pretty well after a little debugging. Every button I pressed resulted in the correct ASCII value on screen (1 = "31", 9 = "39", etc). Cool...

... Then I sent my test "ffff.bin" file to test it. "ffff.bin" is just what it implies, a big chunk (of unknown Ctrl+C + Ctrl+VVV length) of FF bytes. What'd the LCD do? "3F3F3F3F"... WHAT?! So I poked through the options. And right on the file send dialog (which looks just like a standard Open dialog) there was an extra checkbox: "Binary". SERIOUSLY?! It's 2011, and we have a "Binary" on/off selection? Honestly, I'd always wished that old 7-bit encodings would disappear completely. FTP doesn't need it (a huge PITA of data corruption), and serial sure doesn't need it unless 7-bit mode is selected in the connection options. /facepalm. There goes about 4 hours of debugging.

In glee, I re-uploaded the SerialSound sketch, and selected my file ("Fischerspooner - Emerge (Naughty's Chiefrocker Remix).wav" - eat that, vinyl collectors!) to send. Bnchh! There goes the song, in perfect tune, out my test board's little speaker.

So yeah, the WaveShield can indeed be used for generated audio. Next stop to play with: real-time mixing and sequencing with Arduino! :D
(well, first I need to find a way to reduce the number of clock cycles used by the writer, determine how much "free time" it has after sending the data bits, and figure out how to *very quickly* read from some kind of storage!)

Here's the SerialAudio:

Code: Select all

#include <mcpDac.h> // part of WaveHC library "included with" WaveShield
byte audioData;
// byte lohi = 0;
void setup() {
  mcpDacInit();
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  Serial.begin(115200);
  mcpDacSend(0); // clear it, in case it... well... yeah.
}
void loop() {
  if (Serial.available()) {
    // lo byte or hi byte?
//    if (lohi ^= 0xFF) {
//      audioData = Serial.read(); // lo byte
//      return;
//    } else {
//      audioData |= Serial.read() << 8; // hi byte
//      Serial.println(audioData,HEX);
//    }
    audioData = Serial.read();
  } else {
    mcpDacSend(audioData << 4);
  }
}
(commented out: my attempt at 16-bit audio, rather hackish but it produced the same gurggly results as 8-bit so I fell back to 8-bit for debugging... after I heard it playing slow at 11KHz, I said screw that.)

showSerialOnLCD:

Code: Select all

#include <LiquidCrystal.h>

LiquidCrystal lcd(A5, A4, A3, A2, A1, A0); // was originally using full WaveShield with SD and audio pins... oh, BTW, did I mention I'm using a BoArduino with wires hanging all over the place through a ScrewShield to the WaveShield? =P

char lcdline1[17] = "                ";
char lcdline2[17] = "                ";
char valbuf[6];
byte bufptr = 0;

void lcdPrint(char *data) {
  if (bufptr >= 15){
    strcpy(lcdline1, lcdline2);
    bufptr = 0;
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print(lcdline1);
  }
  lcd.setCursor(0,1);
  strcpy(lcdline2 + bufptr,data);
  lcd.print(lcdline2);
  bufptr += strlen(data);
}

void lcdPrint(uint16_t val, uint8_t base) {
  utoa(val,valbuf,base);
  lcdPrint(valbuf);
}

void setup() {
  lcd.begin(16,2);
  Serial.begin(115200);
}

void loop() {
  if (Serial.available()) {
    lcdPrint(Serial.read(), HEX);
  }
}
Hope someone out there can use this info. :D

edit: WOW. With a little combination of the two above programs (dump value to LCD with scrolling, and playing audio from serial), I see that there are only 5 "mcpDacSend" iterations between audio samples... if there is no serial data, it sends the same sample again, and increments a counter. When it gets a new sample, it resets the counter. Timer1 is set to 256 prescaler and has an overflow interrupt that sets "doLCDUpdate = 1;". If doLCDUpdate is set when a new sample is grabbed, it updates the LCD with the count and resets doLCDUpdate. And it shows I don't really have much room to move unless I optimize the data transfer a bit ;)

editedit: As an example of what I could do with generated values... I have quite the nice-sounding "echo" going on right now. I created a 1.5KB audio buffer in RAM (after pushing aside much of the garbage static functions added by including mcpDac.h, which ate up 2 512-byte buffers discovered using "avr-nm"), then wrote/read to the rolling buffer and averaged the N + (N+1/2buf) samples whenever a new sample is read via serial. Amusingly, this processing added almost zero size at all (program actually shrunk from 6000 bytes to about 3,400 after integrating the mcpDac.h definitions into the code and removing the #include), and the timing is still about the same. Definitely the most action my AVR chip has ever seen in RAM ;)

FalconFour
 
Posts: 14
Joined: Sun Jun 12, 2011 9:49 am

Re: Using WaveShield DAC in custom application

Post by FalconFour »

OK, I figure this is significant enough to post. ;)

4-level echo/reverb with a dash of echo-volume processing. ;)

The <<1 at the end is a cheap-shot compensation for the average that takes the shifting of the last 2 values into its calculation. The result? Oh man, it sounds nice. :D
(This is "if (Serial.available())". The "else" for that actually does the barfing of the audioData value onto the DAC using "mcpDacSend(audioData << 4)".)

Code: Select all

    audBuf[audBufPtr] = Serial.read();
    if (audBufPtr > 1151) {
      audioData = ((unsigned int)(audBuf[audBufPtr] + audBuf[audBufPtr-384] + (audBuf[audBufPtr-768]>>1) + (audBuf[audBufPtr-1152]>>2))/4) << 1;
    } else if (audBufPtr > 767) {
      audioData = ((unsigned int)(audBuf[audBufPtr] + audBuf[audBufPtr+384] + (audBuf[audBufPtr-384]>>1) + (audBuf[audBufPtr-768]>>2))/4) << 1;
    } else if (audBufPtr > 383) {
      audioData = ((unsigned int)(audBuf[audBufPtr] + audBuf[audBufPtr+384] + (audBuf[audBufPtr+768]>>1) + (audBuf[audBufPtr-384]>>2))/4) << 1;
    } else {
      audioData = ((unsigned int)(audBuf[audBufPtr] + audBuf[audBufPtr+384] + (audBuf[audBufPtr+768]>>1) + (audBuf[audBufPtr+1152]>>2))/4) << 1;
    }
    if (++audBufPtr > 1535) audBufPtr = 0;

TomLouie
 
Posts: 1
Joined: Mon Mar 26, 2012 11:43 am

Re: Using WaveShield DAC in custom application

Post by TomLouie »

I know this is an old thread, but I'm curious about whether you've done any more work on this. Thanks.

Locked
Please be positive and constructive with your questions and comments.

Return to “Arduino Shields from Adafruit”