Adding Sounds

Adding electronics to the mix will boost the cost and complexity a fair bit, but we can still squeak by for just under $100 — or about 20% the cost of the professionally-made reproductions. As with the prior steps, this figure implies one is already tooled up for soldering…with an iron, some bits of wire and a few additional minor parts and skills.
Our voice box is made around the Arduino Uno ($30) and the Adafruit Wave Shield ($22). The latter has an entire build tutorial of its own — we won’t reiterate that whole thing here — so please begin with that guide and work through the examples to make certain your soldering is good and that the board works. You can test with headphones, but for the finished piece we’re using an 8 Ohm, 1 Watt speaker from the shop ($2).

For power you can either use the same 9 Volt battery as before…connecting to the Arduino using a 9V Battery Clip w/5.5mm Plug ($3)…or plug into the wall with a 9 Volt Switching Power Adapter ($7).
Do not continue until you have a working Wave Shield. If you have the “Pi Speak” example working, that’s a good sign and you can proceed.
We’re not including any HAL 9000 sound files here, lest we run afoul of copyright issues. With just a bit of Google searching you can find an ample supply already in WAV format. The volume level on some is a bit soft; free software like Audacity can be used to boost this. Or you can watch the DVD and record some clips for yourself off the telly, the so called “analog loophole.”

The WAV files are stored in the root directory of a FAT-formatted SD card. The card wasn’t factored into the cost above, figuring it’s likely that you (or someone you know) has spares around…the Wave Shield is a great way to reuse those feeble old 64 MB cards that are probably rattling around your camera bag.
Originally (as shown below) I affixed the speaker and Arduino to the back using hot-melt adhesive and double-stick foam tape, but have since modified the files to include mounting holes for #4-40 screws. If using screws, these should be inserted from the front before the faceplate and grille are installed.
The Wave Shield includes solder holes adjacent to each Arduino header pin, allowing wires to be added. Remove the shield from the Arduino, insert wires from above and solder on the underside. The following connections are made:
  • The + side of the LED (in the button) connects to VIN on the Wave Shield / Arduino.
  • The side of the LED connects to one of the GND connections (there are two right next to VIN).
  • One terminal of the switch (either one) connects to the other GND.
  • The other switch terminal connects to Analog Pin 0 (along the same edge of the board as VIN, on the next header over).
  • The speaker (two wires) connects to the outputs on the Wave Shield board, just behind the headphone jack.
I happened to use spade terminals on the wires, but there’s no harm in soldering directly to the switch.
The 9V power source (whether battery or AC adapter) connects to the DC jack on the Arduino board.
If there’s a downside to this prop, it would be that it’s not exactly svelte. The parts hang almost 3 inches (8 cm) out the back, so you can’t just stick it on a wall. It might go on another box (a tower PC gaming rig was previously mentioned)…or you’ll be cutting holes in the wall if you plan to install it as a doorbell or such.
Here’s the Arduino sketch for speaking random quotes when the “eye” is pressed. Remember, you need to have some WAV files in the root directory of an SD card. You’ll want to edit the filename list to match what you have:
// Random HAL demo; adapted from PiSpeak sketch.  When button
// on A0 is pressed, plays a random WAV file from a list.

#include <WaveHC.h>
#include <WaveUtil.h>

// REPLACE THESE WITH YOUR ACTUAL WAVE FILE NAMES:
// These should be at the root level, not in a folder.
static const char PROGMEM
  file00[] = "0.wav",
  file01[] = "1.wav",
  file02[] = "2.wav",
  file03[] = "3.wav",
  file04[] = "4.wav",
  file05[] = "5.wav",
  file06[] = "6.wav",
  file07[] = "7.wav",
  file08[] = "8.wav",
  file09[] = "9.wav";
// If adding files above, include corresponding items here:
static const char * const filename[] PROGMEM = {
  file00, file01, file02, file03, file04,
  file05, file06, file07, file08, file09 };
// Sorry for the sillyness, but this is how PROGMEM string
// arrays are handled.

#define error(msg) error_P(PSTR(msg))
SdReader  card;
FatVolume vol;
FatReader root;
FatReader file;
WaveHC    wave;
uint8_t   debounce = 0,   // Button debounce counter
          prev     = 255; // Index of last sound played

void setup() {
  Serial.begin(9600);
  if(!card.init())        error("Card init. failed!");
  if(!vol.init(card))     error("No partition!");
  if(!root.openRoot(vol)) error("Couldn't open dir");
  // PgmPrintln("Files found:");
  // root.ls();

  digitalWrite(A0, HIGH);     // Enable pullup on button
  randomSeed(analogRead(A1)); // Randomize first sound
}

void loop() {
  if(digitalRead(A0) == HIGH) { // Button not pressed
    debounce = 0;               // Reset debounce counter
    return;                     // and nothing else
  }

  if(++debounce >= 20) { // Debounced button press
    uint8_t n;
    char    name[20];

    do { // Choose a random file...
      n = random(sizeof(filename) / sizeof(filename[0]));
    } while(n == prev); // ...but don't repeat last one

    prev     = n;                       // Save file #
    debounce = 0;                       // Reset debounce counter
    strcpy_P(name, (char *)pgm_read_word(&filename[n])); // PROGMEM->RAM
    if(wave.isplaying) wave.stop();     // Stop WAV if playing

    if(!file.open(root, name)) {
      PgmPrint("Couldn't open file ");
      Serial.print(name);
      return;
    }
    if(!wave.create(file)) {
      PgmPrintln("Not a valid WAV");
      return;
    }

    wave.play();                   // Start playing
    while(wave.isplaying);         // Wait for completion
    sdErrorCheck();                // Check for error during play
    while(digitalRead(A0) == LOW); // Wait for button release
  }
}

void error_P(const char *str) {
  PgmPrint("Error: ");
  SerialPrint_P(str);
  sdErrorCheck();
  for(;;);
}

void sdErrorCheck(void) {
  if(!card.errorCode()) return;
  PgmPrint("\r\nSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  PgmPrint(", ");
  Serial.println(card.errorData(), HEX);
  for(;;);
}
One of the cool bits here vs. a pre-made unit…being based around open source tools and hardware, you can totally hack and customize this. Instead of (or in addition to) the eye button, you could add a PIR motion sensor, or a magnetic switch so HAL speaks when you open the refrigerator door. With an Ethernet Shield you could make make an internet-connected HAL that reports on the outside world (pending emails, etc.). The sky’s the limit!
Last updated on 2015-10-12 at 03.59.49 PM Published on 2013-04-29 at 12.23.27 PM