/ Gordon_Cole_MP3_Player / Gordon_Cole_MP3_Player.ino
Gordon_Cole_MP3_Player.ino
  1  // SPDX-FileCopyrightText: 2017 Carter Nelson for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  //
  6  // Gordon Cole MP3 Player
  7  //
  8  
  9  #include <SPI.h>
 10  #include <SD.h>
 11  #include <Adafruit_VS1053.h>
 12  
 13  // Guide is based on Feather M0 Express
 14  // ARDUINO_SAMD_FEATHER_M0 defines only
 15  // VS1053 Pins
 16  #define VS1053_RESET   -1     // VS1053 reset pin (not used!)
 17  #define VS1053_CS       6     // VS1053 chip select pin (output)
 18  #define VS1053_DCS      10    // VS1053 Data/command select pin (output)
 19  #define CARDCS          5     // Card chip select pin
 20  #define VS1053_DREQ     9     // VS1053 Data request, ideally an Interrupt pin
 21  // Button Pins
 22  #define BUTTON_PLAY     13    // PLAY / STOP button
 23  #define BUTTON_PAUSE    12    // PAUSE / RESUME button
 24  #define BUTTON_NEXT     11    // NEXT button
 25  // Status LED
 26  #define LED_STATUS      19    // status LED
 27  #define BLINK_RATE      500   // blink rate in ms
 28  // Volume Control
 29  #define KNOB_VOLUME     0     // volume knob
 30  #define KNOB_MIN        0     // min ADC value
 31  #define KNOB_MAX        1023  // max ADC value
 32  #define VOL_MIN         0     // min volume (most loud)
 33  #define VOL_MAX         50    // max volume (most quiet)
 34  #define VOL_UPDATE      250   // update rate in ms
 35  #define VOL_SAMPLES     10    // number of reads for average
 36  #define VOL_SAMPLE_RATE 5     // ms delay per sample
 37  #define VOL_THRESHOLD   20    // vol must change by this many counts
 38  // Maximum number of files (tracks) to load
 39  #define TRACKS_MAX      100
 40  // Player behavior
 41  #define AUTO_PLAY_NEXT  true  // true to automatically go to next track
 42  
 43  unsigned long currentMillis;
 44  unsigned long previousBlinkMillis, previousVolMillis;
 45  int currentKnob, previousKnob;
 46  int volume;
 47  int currentTrack, totalTracks;
 48  char trackListing[TRACKS_MAX][13] = {' '};
 49  enum mode {
 50    PLAYING,
 51    PAUSED,
 52    STOPPED
 53  } currentMode = STOPPED;
 54  
 55  Adafruit_VS1053_FilePlayer musicPlayer = 
 56    Adafruit_VS1053_FilePlayer(VS1053_RESET, VS1053_CS, VS1053_DCS, VS1053_DREQ, CARDCS);
 57  
 58  //-----------------------------------------------------------------------------
 59  void setup() {
 60    Serial.begin(9600);
 61    // Leave commented for standalone operation, uncomment for troubleshooting
 62    //while (!Serial) ;
 63  
 64    // Initialize pins
 65    pinMode(BUTTON_PLAY, INPUT_PULLUP);
 66    pinMode(BUTTON_PAUSE, INPUT_PULLUP);
 67    pinMode(BUTTON_NEXT, INPUT_PULLUP);
 68    pinMode(LED_STATUS, OUTPUT);
 69  
 70    // Initialize status LED
 71    previousBlinkMillis = millis();
 72    digitalWrite(LED_STATUS, LOW);
 73  
 74    Serial.println("\n\nGordon Cole MP3 Player");
 75  
 76    // Initialize the music player  
 77    if (! musicPlayer.begin()) { 
 78       Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
 79       while (1) {
 80         digitalWrite(LED_STATUS, !digitalRead(LED_STATUS));
 81         delay(100);        
 82       }
 83    }
 84    Serial.println(F("VS1053 found"));
 85    musicPlayer.softReset();
 86  
 87    // Make a tone to indicate VS1053 is working 
 88    musicPlayer.sineTest(0x44, 500);    
 89  
 90    // Set volume for left, right channels. lower numbers == louder volume!
 91    previousVolMillis = millis();  
 92    previousKnob = analogRead(KNOB_VOLUME);
 93    volume = map(previousKnob, KNOB_MIN, KNOB_MAX, VOL_MIN, VOL_MAX);
 94    Serial.print("Volume = "); Serial.println(volume);
 95    musicPlayer.setVolume(volume, volume);
 96  
 97    // Initialize the SD card
 98    if (!SD.begin(CARDCS)) {
 99      Serial.println(F("SD failed, or not present"));
100      while (1) {
101         digitalWrite(LED_STATUS, !digitalRead(LED_STATUS));
102         delay(100);        
103      }
104    }
105    Serial.println("SD OK!");
106  
107    // Load list of tracks
108    Serial.println("Track Listing");
109    Serial.println("=============");  
110    totalTracks = 0;
111    loadTracks(SD.open("/"), 0);
112    currentTrack = 0;
113  
114    // Setup interrupts (DREQ) for playback 
115    musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT); 
116  }
117  
118  //-----------------------------------------------------------------------------
119  void loop() {
120    // Check and set volume
121    updateVolume();
122  
123    // Update status LED
124    updateStatusLED();
125  
126    // Auto play next track if feature enabled
127    if (AUTO_PLAY_NEXT) {
128      if (currentMode==PLAYING && musicPlayer.stopped()) {
129        currentTrack = ++currentTrack < totalTracks ? currentTrack : 0; 
130        Serial.print("Next ");
131        Serial.print(currentTrack); Serial.print("=");
132        Serial.println(trackListing[currentTrack]);
133        musicPlayer.startPlayingFile(trackListing[currentTrack]);
134        currentMode = PLAYING;
135      }
136    }
137  
138    // Start / Stop
139    if (!digitalRead(BUTTON_PLAY)) {
140      if (musicPlayer.stopped()) {
141        Serial.print("Start ");
142        Serial.print(currentTrack); Serial.print("=");
143        Serial.println(trackListing[currentTrack]);
144        musicPlayer.startPlayingFile(trackListing[currentTrack]);
145        currentMode = PLAYING;
146      } else {
147        Serial.println("Stopped.");
148        musicPlayer.stopPlaying();
149        currentMode = STOPPED;      
150      }
151      delay(250);
152    }
153  
154    // Pause / Resume
155    if (!digitalRead(BUTTON_PAUSE)) {
156      if (!musicPlayer.stopped()) {
157        if (musicPlayer.paused()) {
158          Serial.println("Resumed");
159          musicPlayer.pausePlaying(false);
160          currentMode = PLAYING;
161        } else { 
162          Serial.println("Paused");
163          musicPlayer.pausePlaying(true);
164          currentMode = PAUSED;
165        }    
166      }
167      delay(250);
168    }
169  
170    // Next
171    if (!digitalRead(BUTTON_NEXT)) {
172      if (!musicPlayer.stopped()) {
173        Serial.println("Stopping current playback.");
174        musicPlayer.stopPlaying();
175      }
176      currentTrack = ++currentTrack < totalTracks ? currentTrack : 0; 
177      Serial.print("Next ");
178      Serial.print(currentTrack); Serial.print("=");
179      Serial.println(trackListing[currentTrack]);
180      musicPlayer.startPlayingFile(trackListing[currentTrack]);
181      currentMode = PLAYING;
182      delay(250);
183    }
184  }
185  
186  //-----------------------------------------------------------------------------
187  void updateVolume() {
188    // Rate limit
189    currentMillis = millis();
190    if (currentMillis - previousVolMillis < VOL_UPDATE) return;
191    previousVolMillis = currentMillis;
192    // Get an average reading
193    currentKnob = 0;
194    for (int i=0; i<VOL_SAMPLES; i++) {
195      currentKnob += analogRead(KNOB_VOLUME);
196      delay(VOL_SAMPLE_RATE);
197    }
198    currentKnob /= VOL_SAMPLES;
199    // Only update if it's changed
200    if (abs(currentKnob-previousKnob) > VOL_THRESHOLD) {
201      Serial.print("["); Serial.print(currentKnob);
202      Serial.print(","); Serial.print(previousKnob);
203      Serial.print("] ");
204      previousKnob = currentKnob;
205      volume = map(currentKnob, KNOB_MIN, KNOB_MAX, VOL_MIN, VOL_MAX);
206      Serial.print("Volume set to: "); Serial.println(volume);
207      musicPlayer.setVolume(volume, volume);  
208    }  
209  }
210  
211  //-----------------------------------------------------------------------------
212  void updateStatusLED() {
213    if (musicPlayer.paused()) {
214      // Blink it like a polaroid
215      currentMillis = millis();
216      if (currentMillis - previousBlinkMillis > BLINK_RATE) {
217         previousBlinkMillis = currentMillis;
218         digitalWrite(LED_STATUS, !digitalRead(LED_STATUS));
219      }
220    } else if (!musicPlayer.stopped()) {
221      // It's so on again
222      digitalWrite(LED_STATUS, HIGH);
223    } else {
224      // It's so off again
225      digitalWrite(LED_STATUS, LOW);
226    }
227  }
228  
229  //-----------------------------------------------------------------------------
230  void loadTracks(File dir, int level) {
231    while (true) {
232      File entry = dir.openNextFile();
233      if (!entry) return;
234      if (entry.isDirectory()) {
235        // Recursive call to scan next dir level
236        loadTracks(entry, level + 1);
237      } else {
238        // Only add files in root dir
239        if (level == 0) {
240          // And only if they have good names
241          if (nameCheck(entry.name())) {
242            strncpy(trackListing[totalTracks], entry.name(), 12);
243            Serial.print(totalTracks); Serial.print("=");
244            Serial.println(trackListing[totalTracks]);
245            totalTracks++;
246          }
247        }
248      }
249      entry.close();
250      // Stop scanning if we hit max
251      if (totalTracks >= TRACKS_MAX) return;
252    } 
253  }
254  
255  //-----------------------------------------------------------------------------
256  bool nameCheck(char* name) {
257    int len = strlen(name);
258    // Check length
259    if (len <= 4) return false;
260    // Check extension
261    char* ext = strrchr(name,'.');
262    if (!(
263      strcmp(ext,".MP3") == 0  ||
264      strcmp(ext,".OGG") == 0
265      )) return false;
266    // Check first character
267    switch(name[0]) {
268      case '_': return false;
269    }
270    return true;
271  }