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
-----
code
-----
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;
}