Documentation


AMBIENT MUSIC PLAYER


 

  


 

I'll be completely honest, my project's demographic was me, but I suppose many others can say the same.

I wanted an mp3 player that was ambient and would sort of relax while I did work; it is heavily aesthetics

oriented. The main issue I had with building this player is the fact that my parts came in on the Monday of

the last week; in the end, I had only six days to build my player!

 

Wow I didn't notice how similar my player was to one of the example lab documentations. Anyways, I

wanted my player to be very sleek, clean and somewhat minimalist. ... and I just read what the other guy

wrote and it's almost the same. Great minds think alike, I guess?

 

design

-----

 

Paper prototype

-----

Well to be honest... my paper prototype was kind of just a square of paper. I had no idea how to put

what I envisioned as a sheet of paper :D


 


 

parts (bold are purchased)

-----

https://www.adafruit.com/products/1086 | arduino micro

https://www.adafruit.com/products/1586 | neopixel ring [24]

https://www.adafruit.com/products/987 | audio amplifier

https://www.adafruit.com/products/1669 | a pair of speakers

https://www.adafruit.com/products/1063 | electret mic & amplifier

https://www.adafruit.com/products/254 | just to make it easier... microsd breakout

https://www.sparkfun.com/products/10608 | mp3 breakout board

> uhhhhhhhhhhhhhh where do you buy this? | HEF 4050 BP

> misc. | perfboarding material, 1000uf capacitor, wires, solder

 

p.s. don't ship with USPS... so much stress from late shipment

 

case 

-----

> 2 pieces of 6x6 frosted white acrylic

> standoffs

> screws and nuts

> a laser cutter, preferably

> hot glue

 

wiring

-----

honestly I'm pretty dang afraid of messing up my player by taking it apart so I'll put the wiring that

isn't already written in labs 5 and 6 here:

 

change audio decoder chip select (CS) to arduino pin A5 
change audio decoder right to MAX98306 (amp) R+
change audio decoder left to MAX98306 (amp) L+
change audio decoder gbuf to MAX98306 R- & L-
add neopixel data input to arduino pin 3 
add neopixel VCC to 1000uf capacitor (+)
add neopixel GND  to 1000uf capacitor ( - ) 
add 1000uf capacitor (+)  to 5V 
add 1000uf capacitor ( - )   to GND 
add MAX4466 (mic amp) output  to arduino pin A0 
add MAX4466 PWR  to

3.3V and arduino AREF

add  MAX4466 GND  to  GND
add MAX98306 LOUT- & ROUT- to speaker-
add MAX98306 LOUT & ROUT to speaker+

 

references

-----

lab 5

lab 6

 

microphone reading

 

code

-----

GitHub for readability


 

final note before I paste the code into this page

Thank you all so much for this amazing summer. This class is my favorite class I've ever taken!


 

/*

 * originally based on frank zhao's player: http://frank.circleofcurrent.com/

 * utilities adapted from previous versions of the functions by matthew seal.

 *

 * (c) 2011, 2012 david sirkin sirkin@cdr.stanford.edu

 *                & akil srinivasan akils@stanford.edu

 */

 

// ---- includes and defines ------------------------------------------------

 

// first step is to include (arduino) sd, eeprom and (our own) mp3 libraries.

 

#include <SD.h>

#include <EEPROM.h>

 

#include <mp3.h>

#include <mp3conf.h>

#include <Adafruit_NeoPixel.h>

 

#define PIN 3

// include the adafruit pcd8544 & gfx libraries for a nokia 5110 graphic lcd.

//----------------------------------------

// Parameter 1 = number of pixels in strip

// Parameter 2 = Arduino pin number (most are valid)

// Parameter 3 = pixel type flags, add together as needed:

//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)

//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)

//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)

//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)

Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, NEO_GRB + NEO_KHZ800);

 

// setup microsd and decoder chip select pins, and the decoder-specific pins.

 

#define sd_cs         17        // 'chip select' line for the microsd card

 

#define mp3_cs        A5        // 'command chip select' connect to cs pin

#define mp3_dcs       A1        // 'data chip select' connect to bsync pin

#define mp3_rst       -1        // 'reset' connects to decoder's reset pin

#define mp3_dreq      A2        // 'data request line' connect to dreq pin

 

// now assign pins for the graphic lcd (carried over from the etch-a-sketch).

 

#define lcd_clk        7        // 'serial clock' connect to lcd's clk pin

#define lcd_din        6        // 'serial data input' connects to din pin

#define lcd_dc         5        // 'data/command input' connect to d/c pin

#define lcd_cs        -1        // 'slave chip select' connects to  cs pin

#define lcd_rst        4        // 'reset' connects to graphic lcd rst pin

 

// 'read_buffer' is the amount of data read from microsd and sent to decoder.

// it's probably best to keep this a factor of 2, up to about 1kb (2kb is the

// max). you might change this if you experienced skips during song playback.

 

#define read_buffer  128        // size (bytes) of the microsd read buffer

#define mp3_vol      237        // default volume. range min=0 and max=254 (a

                                // good range is from 200-235)

 

// file names are 13 bytes max (8 + '.' + 3 + '\0'), and the file list should

// fit into the eeprom. for example, 13 * 40 = 520 bytes of eeprom are needed

// to store a list of 40 songs. if you use shorter file names, or if your mcu

// has more eeprom, you can change these.

 

#define max_name_len  13

#define max_num_songs 40

 

// id3v2 tags have variable-length song titles. that length is indicated in 4

// bytes within the tag. id3v1 tags also have variable-length song titles, up

// to 30 bytes maximum, but the length is not indicated within the tag. using

// 60 bytes here is a compromise between holding most titles and saving sram.

 

// if you increase this above 255, look for and change 'for' loop index types

// so as to not to overflow the unsigned char data type.

 

#define max_title_len 60

 

const int sampleWindow = 3; // Sample window width in mS (50 mS = 20Hz)

unsigned int sample;

 

// ---- global variables ----------------------------------------------------

 

// instantiate a graphic lcd object using the pins that we #define'd earlier.

// comment out the graphics lines to save memory if you're not using the lcd.

 

//Adafruit_PCD8544 lcd = Adafruit_PCD8544(lcd_clk, lcd_din, lcd_dc, lcd_cs, lcd_rst);

 

// 'File' is a wrapper of the 'SdFile' data type from the sd utility library.

 

File sd_file;                   // object to represent a file on a microsd

 

// store the number of songs in this directory, and the current song to play.

 

unsigned char num_songs = 0, current_song = 0;

 

// an array to hold the current_song's file name in ram. every file's name is

// stored longer-term in the eeprom. this array is used in 'sd_file.open()'.

 

char fn[max_name_len];

 

// an array to hold the current_song's title in ram. it needs 1 extra char to

// hold the '\0' that indicates the end of a character string. the song title

// is found in 'get_title_from_id3tag()'.

 

char title[max_title_len + 1];

 

// the program runs as a state machine. the 'state' enum includes the states.

// 'current_state' is the default as the program starts. add new states here.

 

enum state { DIR_PLAY, MP3_PLAY, PAUSED };

state current_state = DIR_PLAY;

 

//---- module functions -----------------------------------------------------

 

// you must open a song file that you want to play using 'sd_file_open' prior

// to fetching song data from the file. you can only open one file at a time.

void sd_file_open() {  

  // first, find the file name (that's stored in eeprom) of the current song.

  get_current_song_as_fn();

 

  // then open the file using the name we just found (stored in 'fn' global).

  sd_file = SD.open(fn, FILE_READ);

 

  // find the current song's title tag (if present) then print it to the lcd. 

  print_title_to_lcd();

}

 

// read a number of bytes from the microsd card, then forward them to the Mp3

// library's 'play' function, which streams them out to the decoder chip.

void mp3_play() {

  unsigned char bytes[read_buffer]; // buffer to read and send to the decoder

  unsigned int bytes_to_read;       // number of bytes read from microsd card

 

  // first fill the 'bytes' buffer with (up to) 'read_buffer' count of bytes.

  // that happens through the 'sd_file.read()' call, which returns the actual

  // number of bytes that were read (which can be fewer than 'read_buffer' if

  // at the end of the file). then send the retrieved bytes out to be played.

 

  // 'sd_file.read()' manages the index pointer into the file and knows where

  // to start reading the next batch of bytes. 'Mp3.play()' manages the index

  // pointer into the 'bytes' buffer and knows how to send it to the decoder.

  bytes_to_read = sd_file.read(bytes, read_buffer);

  Mp3.play(bytes, bytes_to_read);

 

  // 'bytes_to_read' is only smaller than 'read_buffer' when the song's over.

  if (bytes_to_read < read_buffer) {

    sd_file.close();

 

    // if we've been in the MP3_PLAY state, then we want to pause the player.

    if (current_state == MP3_PLAY) {

      current_state == PAUSED;

    }

  }

}

 

// continue to play the current (playing) song, until there are no more songs

// in the directory to play. 2 other sd library methods (that we haven't used

// here) can help track your progress while playing songs: 'sd_file.size()' &

// 'sd_file.position()'. you can use these to show say, the percent of a song

// that has already played.

 

void dir_play() {

  if (sd_file) {

    mp3_play();

  }

  else {

    // since 'sd_file' isn't open, the recently playing song must have ended.

    // increment the index, and open the next song, unless it's the last song

    // in the directory. in that case, just set the state to PAUSED.

 

    if (current_song < (num_songs - 1)) {

      current_song++;

      sd_file_open();      

    }

    else {

      current_state = PAUSED;

    }

  }

}   

 

// ---- setup and loop ------------------------------------------------------

 

// setup is pretty straightforward. initialize serial communication (used for

// the following error messages), mp3 library, microsd card objects, then the

// graphic lcd. then open the first song in the root library to play.

 

void setup() {

  analogReference(EXTERNAL);

 

  strip.begin();

  strip.show(); // Initialize all pixels to 'off'

 

  attachInterrupt(2, interrupt_Skip, RISING);

  attachInterrupt(3, interrupt_Back, RISING); 

  attachInterrupt(4, interrupt_Pause, RISING);

 

  // write a 0 to all 512 bytes of the EEPROM

  for (int i = 0; i < 512; i++)

    EEPROM.write(i, 0);

 

  // initialize the mp3 library, and set default volume. 'mp3_cs' is the chip

  // select, 'dcs' is data chip select, 'rst' is reset and 'dreq' is the data

  // request. the decoder sets the 'dreq' line (automatically) to signal that

  // its input buffer can accommodate 32 more bytes of incoming song data.

 

  // the decoder's default state prevents the spi bus from working with other

  // spi devices, so we initialize it first.

  Mp3.begin(mp3_cs, mp3_dcs, mp3_rst, mp3_dreq);

  Mp3.volume(mp3_vol);

 

  // initialize the microsd (which checks the card, volume and root objects).

  sd_card_setup();

 

  // putting all of the root directory's songs into eeprom saves flash space.

  sd_dir_setup();

 

  // the program is setup to enter DIR_PLAY mode immediately, so this call to

  // open the root directory before reaching the state machine is needed.  

  sd_file_open();

}

 

// the state machine is setup (at least, at first) to open the microsd card's

// root directory, play all of the songs within it, close the root directory,

// and then stop playing. change these, or add new actions here.

 

// the DIR_PLAY state plays all of the songs in a directory and then switches

// into PAUSED when done. the MP3_PLAY state plays one specific song and then

// switches into PAUSED. this sample player doesn't enter the MP3_PLAY state,

// as its goal (for now) is just to play all the songs. you can change that.

 

void loop() {

  switch(current_state) {

    case DIR_PLAY:

      dir_play();

      break;

 

    case MP3_PLAY:

      mp3_play();

      break;

 

    case PAUSED:

      break;

  }

 

  static int i = 0;

   unsigned long startMillis= millis();  // Start of sample window

   unsigned int value = 0;   // peak-to-peak level

 

   unsigned int signalMax = 0;

   unsigned int signalMin = 1024;

 

   while (millis() - startMillis < sampleWindow) {

      sample = analogRead(0); 

      if (sample < 1024)  // toss out spurious readings

      {

         if (sample > signalMax)

         {

            signalMax = sample;  // save just the max levels

         }

         else if (sample < signalMin)

         {

            signalMin = sample;  // save just the min levels

         }

      }

   }

 

   value = map(signalMax - signalMin, 0, 1023, 0, 128);

   if (value < 32){

     value = value/3;

   }

   if (32 <= value < 64){

     value = value/2;

   }

   strip.setPixelColor(i, value*.9, value/2, 0);

   strip.show();

   if (i == 23){

     i=0;

   }

   else {

     i++;

   }

}

 

void pause_play() {

  if (current_state == DIR_PLAY){

    current_state = PAUSED;

  }

  else{

    current_state =  DIR_PLAY;

  }

}

 

void interrupt_Pause() {

  static unsigned long last_interrupt_time = 0;

  unsigned long interrupt_time = millis();

  // If interrupts come faster than 200ms, assume it's a bounce and ignore

  if (interrupt_time - last_interrupt_time > 200) {

    pause_play () ;

  }

  last_interrupt_time = interrupt_time;

}

 

void interrupt_Skip() {

  static unsigned long last_interrupt_time = 0;

  unsigned long interrupt_time = millis();

  // If interrupts come faster than 200ms, assume it's a bounce and ignore

  if (interrupt_time - last_interrupt_time > 200) {

    if (current_song != num_songs - 1) {

       Mp3.cancel_playback();

       sd_file.close();

       current_song++;

       sd_file_open();

    }

    last_interrupt_time = interrupt_time;

  }

}

 

void interrupt_Back() {

  static unsigned long last_interrupt_time = 0;

  unsigned long interrupt_time = millis();

  // If interrupts come faster than 200ms, assume it's a bounce and ignore

  if (interrupt_time - last_interrupt_time > 200) {

      if (current_song != 0) {

        Mp3.cancel_playback();

        sd_file.close();

        current_song--;

        sd_file_open();

    }

  }

  last_interrupt_time = interrupt_time;

}