/*************************************************** This is a class to model a musical Christmas tree. Notable member variables: 1. a music player - Adafruit_VS1053_FilePlayer. 2. a strip containing 120 Neopixels + a 12 neopixel ring - Adafruit_NeoPixel The hardware uses an "Adafruit music maker" shield which has an SD card, amplifier, sound player, and GPIO pins. All are used. NOTE: the SD object is global and comes from the music player code. Perhaps it should have been placed in the MusicTree as a member variable, but the effort didn't seem worth any questionable benefits. The music shield's GPIO pins are used for stop, prev, play, and next buttons, which control the music. Songs should be stored on the SD card in the root folder and numbered from 0000.mp3 to 9999.mp3, left-padded zeros are required. Set NUM_TRACKS to the proper value below. Set the various RING/STRIP count and start values below as needed. Written by Mark Giovannetti 2016-03-12. BSD license, all text above must be included in any redistribution ****************************************************/ /* * Debounce code retrieed from: * http://www.arduino.cc/en/Tutorial/Debounce * It is not in use yet for interrupt timing reasons. */ #include "MusicTree.h" //include the declaration for this class // Default constructor MusicTree::MusicTree() { // create a music player using the shield musicPlayer = new Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS); // Parameter 1 = number of pixels in strip, neopixel stick has 8 // Parameter 2 = pin number (most are valid) // Parameter 3 = pixel type flags, add together as needed: // NEO_RGB Pixels are wired for RGB bitstream // NEO_GRB Pixels are wired for GRB bitstream, correct for neopixel stick // NEO_KHZ400 400 KHz bitstream (e.g. FLORA pixels) // NEO_KHZ800 800 KHz bitstream (e.g. High Density LED strip), correct for neopixel stick strip = new Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800); paused = false; stopped = false; currentTrack = 0; numTracks = NUM_TRACKS; buttonState = LOW; // the current reading from the input pin lastButtonState = LOW; // the previous reading from the input pin // the following variables are long's because the time, measured in miliseconds, // will quickly become a bigger number than can be stored in an int. lastDebounceTime = 0; // the last time the output pin was toggled debounceDelay = 20; // the debounce time; increase if music output gets strange at a push event ringPixelStart = RING_PIXEL_START; ringPixelCount = RING_PIXEL_COUNT; stripPixelStart = STRIP_PIXEL_START; stripPixelCount = STRIP_PIXEL_COUNT; } // end ctor boolean MusicTree::init() { // initialise the music player // This must be called after the constructor, since the constructor // is limited in what it can do. pinMode(VOL_PIN, INPUT); // make the volume pin an INPUT if (! musicPlayer->begin()) { // initialise the music player Serial.println(F("Couldn't find VS1053, do you have the right pins defined?")); return false; } // GPIO Buttons on the music shield. Buttons aren't generally // responsive and need long presses due to the constant interrupts // from the music player shield while songs are playing. musicPlayer->GPIO_pinMode(BUTTONNEXT, INPUT); musicPlayer->GPIO_pinMode(BUTTONSTOP, INPUT); musicPlayer->GPIO_pinMode(BUTTONPLAY, INPUT); musicPlayer->GPIO_pinMode(BUTTONPREV, INPUT); //beep(); /***** Two interrupt options! We use the PIN_INT *******/ // This option uses timer0, this means timer1 & t2 are not required // (so you can use 'em for Servos, etc) BUT millis() can lose time // since we're hitchhiking on top of the millis() tracker //musicPlayer->useInterrupt(VS1053_FILEPLAYER_TIMER0_INT); // This option uses a pin interrupt. No timers required! But DREQ // must be on an interrupt pin. For Uno/Duemilanove/Diecimilla // that's Digital #2 or #3 // See http://arduino.cc/en/Reference/attachInterrupt for other pins // *** This method is preferred if (! musicPlayer->useInterrupt(VS1053_FILEPLAYER_PIN_INT)) Serial.println(F("DREQ pin is not an interrupt pin")); EEPROM.get(0, currentTrack); currentTrack--; if (currentTrack > numTracks || currentTrack < 0) currentTrack = 0; setTrackFileName(); // Start the pixel strip Serial.print(F("Init num pixels: ")); Serial.println(strip->numPixels()); strip->begin(); strip->setBrightness(128); // Save some amps (may not be effective) strip->show(); // Initialize all pixels to 'off' //uint32_t magenta = strip->Color(255,0,255); return true; } // end init //<> MusicTree::~MusicTree() { /* destroy file player? */ delete musicPlayer; delete strip; } // dtor void MusicTree::beep() { musicPlayer->sineTest(0x44, 500); // Make a tone } // end beep // stop playing music void MusicTree::stopTrack(){ stopped = true; musicPlayer->stopPlaying(); } // stopTrack // start playing music, used by prevTrack and nextTrack void MusicTree::playTrack(){ setTrackFileName(); stopTrack(); musicPlayer->startPlayingFile(currentTrackFileName.c_str()); EEPROM.put(0, currentTrack); stopped = false; paused = false; } // playTrack // pause the player if playing, otherwise play if paused or stopped void MusicTree::playPause() { if (stopped) { Serial.print("my play, stopped, calling musicPlayer startPlayingFile with: "); Serial.println(currentTrackFileName); musicPlayer->startPlayingFile(currentTrackFileName.c_str()); stopped = false; paused = false; return; } // end if stopped if (paused) { Serial.println("my pausing, calling musicPlayer pausePlaying with false"); musicPlayer->pausePlaying(false); } else { Serial.println("my pausing, calling musicPlayer pausePlaying with true"); musicPlayer->pausePlaying(true); } // end if paused paused = !paused; } // end playPause // retrieve track number uint16_t MusicTree::getCurrentTrack() { return currentTrack; } // end getCurrentTrack // set track name void MusicTree::setTrackFileName() { Serial.println(F("Setting file name")); if (currentTrack < 10) { currentTrackFileName = "000" + String(currentTrack) + ".mp3"; } else if (currentTrack < 100) { currentTrackFileName = "00" + String(currentTrack) + ".mp3"; } else if (currentTrack < 1000) { currentTrackFileName = "0" + String(currentTrack) + ".mp3"; } else if (currentTrack < 10000) { currentTrackFileName = String(currentTrack) + ".mp3"; } else { currentTrackFileName = "0001.mp3"; } Serial.println(currentTrackFileName); } // end setTrackFileName // retrieve stopped status boolean MusicTree::isStopped() { return stopped; } // sStopped // retrieve paused status boolean MusicTree::isPaused() { return paused; } // isPaused // retrieve playing status boolean MusicTree::isPlaying() { return musicPlayer->playingMusic; } // isPlaying void MusicTree::setVolume() { int newVolume; // Set volume for left, right channels. lower numbers == louder volume! newVolume = analogRead(VOL_PIN); if (newVolume != currentVolume) { Serial.print(F(" VOL_PIN= ")); Serial.print(newVolume); newVolume = constrain(newVolume, 0, 1023); currentVolume = newVolume; newVolume = map(newVolume, 0, 1023, 0, 100); newVolume = constrain(newVolume, 0, 254); Serial.print(F(", chip volume= ")); Serial.println(newVolume); musicPlayer->setVolume(newVolume, newVolume); } // end if volume changed } // setVolume boolean MusicTree::testButton(int buttonPin) { /* Debounce code from: http://www.arduino.cc/en/Tutorial/Debounce (not used yet) // read the state of the switch into a local variable: int reading = digitalRead(buttonPin); // check to see if you just pressed the button // (i.e. the input went from LOW to HIGH), and you've waited // long enough since the last press to ignore any noise: // If the switch changed, due to noise or pressing: if (reading != lastButtonState) { // reset the debouncing timer lastDebounceTime = millis(); } if ((millis() - lastDebounceTime) > debounceDelay) { // whatever the reading is at, it's been there for longer // than the debounce delay, so take it as the actual current state: // if the button state has changed: if (reading != buttonState) { buttonState = reading; // only toggle the LED if the new button state is HIGH if (buttonState == HIGH) { ledState = !ledState; } } } // save the reading. Next time through the loop, // it'll be the lastButtonState: lastButtonState = reading; */ if( musicPlayer->GPIO_digitalRead(buttonPin) == HIGH) { return true; delay(debounceDelay); // debounce delay if( musicPlayer->GPIO_digitalRead(buttonPin) == HIGH) { return true; } } return false; } // testButton void MusicTree::checkButtons() { if (testButton(BUTTONSTOP)) { // stop player Serial.println(F("STOP pressed.")); stopTrack(); } else if (testButton(BUTTONPLAY)) { // pause, start playing Serial.println(F("PLAY/PAUSE pressed.")); playPause(); } else if (testButton(BUTTONNEXT)) { // next song requested Serial.println(F("NEXT pressed.")); nextTrack(); } else if (testButton(BUTTONPREV)) { // prev song requested Serial.println(F("PREV pressed.")); prevTrack(); } else { // No buttons pressed } // end if buttons pressed //delay(30); } // end checkButtons // decrement track number and play void MusicTree::prevTrack(){ currentTrack--; if (currentTrack == 0) currentTrack = numTracks; // circular playTrack(); } // end prevTrack // increment track number and play void MusicTree::nextTrack(){ currentTrack++; if (currentTrack > numTracks) currentTrack = 1; // circular playTrack(); } // end nextTrack void MusicTree::colourWipeRange(int startRange, int endRange, uint32_t c, uint8_t wait) { if (patternDirection) { for(uint16_t i=startRange; i < endRange; i++) { strip->setPixelColor(i, c); strip->show(); delay(wait); } // end for } else { for(int16_t i=endRange-1; i >= startRange; i--) { strip->setPixelColor(i, c); strip->show(); delay(wait); } // end for } // end if patternDirection } // colourWipeRange void MusicTree::colourWipe(uint32_t c, uint8_t wait) { colourWipeRange(0, strip->numPixels(), c, wait); } // colourWipe void MusicTree::randomWipeRange(int startRange, int endRange, uint8_t wait) { uint8_t red, green, blue; if (patternDirection) { for(uint16_t i=startRange; i < endRange; i++) { red = random(256); green = random(256); blue = random(256); strip->setPixelColor(i, Adafruit_NeoPixel::Color(red, green, blue)); strip->show(); delay(wait); } // end for } else { for(int16_t i=endRange-1; i >= startRange; i--) { red = random(256); green = random(256); blue = random(256); strip->setPixelColor(i, Adafruit_NeoPixel::Color(red, green, blue)); strip->show(); delay(wait); } // end for } // end if patternDirection } // randomWipeRange void MusicTree::randomWipe(uint8_t wait) { randomWipeRange(0, strip->numPixels(), wait); } // randomWipe void MusicTree::showPattern() { Serial.println(F("start showPattern")); uint8_t red, green, blue, wait; red = random(256); green = random(256); blue = random(256); /* Serial.print(F("red= ")); Serial.print(red); Serial.print(F(" green= ")); Serial.print(green); Serial.print(F(" blue= ")); Serial.print(blue); Serial.println(F(".")); */ Serial.println(F("numPixels: ")); Serial.println(strip->numPixels()); wait = random(5); switch (patternType) { case SINGLECOLOURALL: colourWipe(Adafruit_NeoPixel::Color(red, green, blue), wait); break; case RANDOMCOLOURALL: randomWipe(wait); break; case SINGLECOLOURSTRIP: colourWipeRange(getRingPixelStart(), getRingPixelEnd(), Adafruit_NeoPixel::Color(0, 0, 255), wait); colourWipeRange(getStripPixelStart(), getStripPixelEnd(), Adafruit_NeoPixel::Color(255, 0, 0), wait); break; case SINGLECOLOURRING: colourWipeRange(getRingPixelStart(), getRingPixelEnd(), Adafruit_NeoPixel::Color(0, 255, 255), wait); colourWipeRange(getStripPixelStart(), getStripPixelEnd(), Adafruit_NeoPixel::Color(0, 255, 0), wait); break; case RANDOMCOLOURSTRIP: colourWipeRange(getRingPixelStart(), getRingPixelEnd(), Adafruit_NeoPixel::Color(0, 0, 255), wait); randomWipeRange(getStripPixelStart(), getStripPixelEnd(), wait); break; case RANDOMCOLOURRING: randomWipeRange(getRingPixelStart(), getRingPixelEnd(), wait); colourWipeRange(getStripPixelStart(), getStripPixelEnd(), Adafruit_NeoPixel::Color(255, 0, 255), wait); break; default: colourWipe(Adafruit_NeoPixel::Color(255, 255, 255), wait); break; } // switch Serial.println(F("end showPattern")); } // showPattern