Playing sounds on an AVR MCU
I used an ATtiny85 to drive a speaker through a LPF via two complementary PWM outputs (OC1B/nOC1B) upon pressing a button. Less than 0.5k of code leaves 7.5k for the audio. Larger chips leave more space for audio or the ability to use real SPI or I2C for storage. Volume depends on the supply voltage, but you can still get decent performance from a half-dead Li-ion cell.

What the heck, I'm feeling generous. Note that none of this attempts to be very power-conscious though; see the license for modification details. And don't try to drive too large a speaker through it. And beware the crappy commenting.

/***
 * Copyright © 2014 Ignacio Vazquez-Abrams
 * This work is free. You can redistribute it and/or modify it under the
 * terms of the WTFPL, Version 2,
 * as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
 */

#define F_CPU 16000000

#include &lt;avr/io.h&gt;
#include &lt;avr/interrupt.h&gt;
#include &lt;avr/pgmspace.h&gt;
#include &lt;avr/sleep.h&gt;
#include &lt;avr/power.h&gt;

#include "audio8k.h"

// Generate audio using PWM on OC1B/~OC1B H-bridge on ATtinyX5
// This leaves the USI pins available for expansion
// Timer 1 is used to drive PWM, timer 0 is used to load the next
// sample

// References:
// http://playground.arduino.cc/Code/PCMAudio
// http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_binarydata

// play state information
typedef enum {
  STATE_STOPPED,
  STATE_STARTING,
  STATE_PLAYING,
  STATE_STOPPING
} playState;

playState currentState __attribute__ ((section (".noinit")));

// how far into the audio
unsigned long sampleCount __attribute__ ((section (".noinit")));

static void start_playing();
static void ramp_up();
static void play();
static void ramp_down();
static void stop_playing();

void start_playing()
{
  cli();

  // connect OC1B
  GTCCR |= (_BV(PWM1B) | _BV(COM1B0));
  DDRB |= (_BV(PB4) | _BV(PB3));

  // clki/o/8 (2MHz) prescaling for timer 0
  TCCR0B = (_BV(CS01));

  // tune timer 0 to 32kHz
  OCR0A = 63;

  // pck (64MHz, 500kHz PWM) prescaling for timer 1
  TCCR1 = (_BV(CS10));

  // prime the PWM
  OCR1B = 0x00;

  // unprescale clock
  clock_prescale_set(clock_div_1);

  // prepare for liftoff
  sampleCount = 0;
  currentState = STATE_STARTING;

  sei();
}

void ramp_up()
{
  unsigned char firstSample = pgm_read_byte(&amp;audio8k[0]);
  OCR1B += 1;
  if (OCR1B &gt;= firstSample)
  {
    OCR1B = firstSample;
    currentState = STATE_PLAYING;
  }
}

void play()
{
  // audio from PROGMEM
  OCR1B = pgm_read_byte(&amp;audio8k[sampleCount++ &gt;&gt; 2]);
  if ((sampleCount &gt;&gt; 2) &gt;= (audio8k_end - audio8k))
  {
    currentState = STATE_STOPPING;
  }
}

void ramp_down()
{
  if (OCR1B &gt; 1)
  {
    OCR1B -= 1;
  }
  else
  {
    OCR1B = 0;
    stop_playing();
  }
}

void stop_playing()
{
  cli();

  // clki/o/8 (2MHz) prescaling for timer 0
  TCCR0B = (_BV(CS01));

  // tune timer 0 to 7812.5Hz
  OCR0A = 0x00;

  // prescale clock by 256
//XX UNCOMMENT FOR PRODUCTION  clock_prescale_set(clock_div_256);

  // kthxbye
  currentState = STATE_STOPPED;
  sampleCount = 0;

  sei();
}

void setup()
{
  // ssshhhh....
  cli();

  // speaker is connected to pins 3 and 2, PB4/PB3 (OC1B/~OC1B)
  DDRB |= (_BV(PB4) | _BV(PB3));

  // button pullup goes here
  PORTB |= _BV(PB2);

  // use CTC on timer 0 for finer tuning
  TCCR0A = _BV(WGM01);

  // enable the timer 0 output compare A interrupt
  TIMSK |= _BV(OCIE0A);

  // enable async clock (PCK) for timer 1
  PLLCSR |= _BV(PCKE);

  // set sleep mode for later, leave i/o running
  set_sleep_mode(SLEEP_MODE_IDLE);

  // sane defaults
  stop_playing();

  // and... go!
  sei();
}

// state machine dispatch
ISR(TIM0_COMPA_vect)
{
  switch (currentState)
  {
    case STATE_STOPPED:
    {
      if (!(PINB &amp; _BV(PB2)))
      {
        start_playing();
      }

      break;
    }
    case STATE_STARTING:
    {
      ramp_up();

      break;
    }
    case STATE_PLAYING:
    {
      play();

      break;
    }
    case STATE_STOPPING:
    {
      ramp_down();

      break;
    }
  }
}

void loop()
{
// this loop should do nothing; everything is handled by interrupts
  sleep_enable();
  sleep_cpu();
  sleep_disable();
}

int main()
{
  setup();
  while (1)
    loop();
}