aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/lib/mp3/src/Adafruit_MP3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'circuitpython/lib/mp3/src/Adafruit_MP3.cpp')
-rw-r--r--circuitpython/lib/mp3/src/Adafruit_MP3.cpp532
1 files changed, 532 insertions, 0 deletions
diff --git a/circuitpython/lib/mp3/src/Adafruit_MP3.cpp b/circuitpython/lib/mp3/src/Adafruit_MP3.cpp
new file mode 100644
index 0000000..2ef737c
--- /dev/null
+++ b/circuitpython/lib/mp3/src/Adafruit_MP3.cpp
@@ -0,0 +1,532 @@
+#include "Adafruit_MP3.h"
+#include "mp3common.h"
+
+#if defined(__SAMD51__) // feather/metro m4
+
+#define WAIT_TC16_REGS_SYNC(x) while(x->COUNT16.SYNCBUSY.bit.ENABLE);
+
+#elif defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) // teensy 3.6, 3.5. or 3.1/2
+
+IntervalTimer Adafruit_MP3::_MP3Timer;
+uint32_t Adafruit_MP3::currentPeriod;
+static void MP3_Handler();
+
+#endif
+
+volatile bool activeOutbuf;
+Adafruit_MP3_outbuf outbufs[2];
+volatile int16_t *outptr;
+static void (*sampleReadyCallback)(int16_t, int16_t);
+
+uint8_t Adafruit_MP3::numChannels = 0;
+/**
+*****************************************************************************************
+* @brief enable the playback timer
+*
+* @return none
+****************************************************************************************/
+static inline void enableTimer()
+{
+#if defined(__SAMD51__) // feather/metro m4
+ MP3_TC->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
+ WAIT_TC16_REGS_SYNC(MP3_TC)
+#elif defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) // teensy 3.6, 3.5. or 3.1/2
+ Adafruit_MP3::_MP3Timer.begin(MP3_Handler, Adafruit_MP3::currentPeriod);
+#elif defined(NRF52)
+ MP3_TIMER->TASKS_START = 1;
+#endif
+}
+
+/**
+*****************************************************************************************
+* @brief disable the playback timer
+*
+* @return none
+****************************************************************************************/
+static inline void disableTimer()
+{
+#if defined(__SAMD51__) // feather/metro m4
+ MP3_TC->COUNT16.CTRLA.bit.ENABLE = 0;
+ WAIT_TC16_REGS_SYNC(MP3_TC)
+#elif defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) // teensy 3.6, 3.5. or 3.1/2
+ Adafruit_MP3::_MP3Timer.end();
+#elif defined(NRF52)
+ MP3_TIMER->TASKS_STOP = 1;
+#endif
+}
+
+/**
+*****************************************************************************************
+* @brief reset the playback timer
+*
+* @return none
+****************************************************************************************/
+#if defined(__SAMD51__) // feather/metro m4
+static inline void resetTC (Tc* TCx)
+{
+
+ // Disable TCx
+ TCx->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
+ WAIT_TC16_REGS_SYNC(TCx)
+
+ // Reset TCx
+ TCx->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
+ WAIT_TC16_REGS_SYNC(TCx)
+ while (TCx->COUNT16.CTRLA.bit.SWRST);
+
+}
+#endif
+
+static inline void configureTimer()
+{
+#if defined(__SAMD51__) // feather/metro m4
+
+ NVIC_DisableIRQ(MP3_IRQn);
+ NVIC_ClearPendingIRQ(MP3_IRQn);
+ NVIC_SetPriority(MP3_IRQn, 0);
+
+ GCLK->PCHCTRL[MP3_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK0_Val | (1 << GCLK_PCHCTRL_CHEN_Pos);
+
+ resetTC(MP3_TC);
+
+ //configure timer for 44.1khz
+ MP3_TC->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ; // Set TONE_TC mode as match frequency
+
+ MP3_TC->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16 | TC_CTRLA_PRESCALER_DIV4;
+ WAIT_TC16_REGS_SYNC(MP3_TC)
+
+ MP3_TC->COUNT16.CC[0].reg = (uint16_t)( (SystemCoreClock >> 2) / MP3_SAMPLE_RATE_DEFAULT);
+ WAIT_TC16_REGS_SYNC(MP3_TC)
+
+ // Enable the TONE_TC interrupt request
+ MP3_TC->COUNT16.INTENSET.bit.MC0 = 1;
+
+#elif defined(NRF52)
+
+ NVIC_DisableIRQ(MP3_IRQn);
+ NVIC_ClearPendingIRQ(MP3_IRQn);
+ NVIC_SetPriority(MP3_IRQn, 15);
+
+ disableTimer();
+ MP3_TIMER->MODE = 0; //timer mode
+ MP3_TIMER->BITMODE = 0; //16 bit mode
+ MP3_TIMER->PRESCALER = 0; //no prescale
+ MP3_TIMER->CC[0] = 16000000UL/MP3_SAMPLE_RATE_DEFAULT;
+ MP3_TIMER->EVENTS_COMPARE[0] = 0;
+ MP3_TIMER->TASKS_CLEAR = 1;
+
+ MP3_TIMER->INTENSET = (1UL << 16); //enable compare 0 interrupt
+
+#elif defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) // teensy 3.6, 3.5. or 3.1/2
+ float sec = 1.0 / (float)MP3_SAMPLE_RATE_DEFAULT;
+ Adafruit_MP3::currentPeriod = sec * 1000000UL;
+#endif
+}
+
+static inline void acknowledgeInterrupt()
+{
+#if defined(__SAMD51__) // feather/metro m4
+ if (MP3_TC->COUNT16.INTFLAG.bit.MC0 == 1) {
+ MP3_TC->COUNT16.INTFLAG.bit.MC0 = 1;
+ }
+
+#elif defined(NRF52)
+ MP3_TIMER->EVENTS_COMPARE[0] = 0;
+ MP3_TIMER->TASKS_CLEAR = 1;
+#endif
+}
+
+static inline void updateTimerFreq(uint32_t freq)
+{
+ disableTimer();
+
+#if defined(__SAMD51__) // feather/metro m4
+ MP3_TC->COUNT16.CC[0].reg = (uint16_t)( (SystemCoreClock >> 2) / freq);
+ WAIT_TC16_REGS_SYNC(MP3_TC);
+#elif defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) // teensy 3.6, 3.5. or 3.1/2
+ float sec = 1.0 / (float)freq;
+ Adafruit_MP3::currentPeriod = sec * 1000000UL;
+#elif defined(NRF52)
+ MP3_TIMER->CC[0] = 16000000UL/freq;
+#endif
+
+ enableTimer();
+}
+
+/**
+*****************************************************************************************
+* @brief Begin the mp3 player. This initializes the playback timer and necessary interrupts.
+*
+* @return none
+****************************************************************************************/
+bool Adafruit_MP3::begin()
+{
+ sampleReadyCallback = NULL;
+ bufferCallback = NULL;
+
+ configureTimer();
+
+ if ((hMP3Decoder = MP3InitDecoder()) == 0)
+ {
+ return false;
+ }
+ else return true;
+}
+
+/**
+*****************************************************************************************
+* @brief Set the function the player will call when it's buffers need to be filled.
+* Care must be taken to ensure that the callback function is efficient.
+* If the callback takes too long to fill the buffer, playback will be choppy
+*
+* @param fun_ptr the pointer to the callback function. This function must take a pointer
+* to the location the bytes will be written, as well as an integer that represents
+* the maximum possible bytes that can be written. The function should return the
+* actual number of bytes that were written.
+*
+* @return none
+****************************************************************************************/
+void Adafruit_MP3::setBufferCallback(int (*fun_ptr)(uint8_t *, int)){ bufferCallback = fun_ptr; }
+
+/**
+*****************************************************************************************
+* @brief Set the function that the player will call when the playback timer fires.
+* The callback is called inside of an ISR, so it should be short and efficient.
+* This will usually just be writing samples to the DAC.
+*
+* @param fun_ptr the pointer to the callback function. The function must take two
+* unsigned 16 bit integers. The first argument to the callback will be the
+* left channel sample, and the second channel will be the right channel sample.
+* If the played file is mono, only the left channel data is used.
+*
+* @return none
+****************************************************************************************/
+void Adafruit_MP3::setSampleReadyCallback(void (*fun_ptr)(int16_t, int16_t)) { sampleReadyCallback = fun_ptr; }
+
+/**
+*****************************************************************************************
+* @brief Play an mp3 file. This function resets the buffers and should only be used
+* when beginning playback of a new mp3 file. If playback has been stopped
+* and you would like to resume playback at the current location, use Adafruit_MP3::resume instead.
+*
+* @return none
+****************************************************************************************/
+void Adafruit_MP3::play()
+{
+ bytesLeft = 0;
+ activeOutbuf = 0;
+ readPtr = inBuf;
+ writePtr = inBuf;
+
+ outbufs[0].count = 0;
+ outbufs[1].count = 0;
+ playing = false;
+
+ //start the playback timer
+ enableTimer();
+
+#if defined(__SAMD51__) || defined(NRF52) // feather/metro m4
+ NVIC_EnableIRQ(MP3_IRQn);
+#endif
+}
+
+void Adafruit_MP3_DMA::play()
+{
+#if defined(__SAMD51__)
+ //don't need an interrupt here
+ MP3_TC->COUNT16.INTENCLR.bit.MC0 = 1;
+#endif
+ Adafruit_MP3::play();
+#if defined(__SAMD51__) || defined(NRF52) // feather/metro m4
+ NVIC_DisableIRQ(MP3_IRQn); //we don't need the interrupt
+#endif
+ leftoverSamples = 0;
+
+ //fill both buffers
+ fill();
+}
+
+/**
+*****************************************************************************************
+* @brief pause playback. This function stops the playback timer.
+*
+* @return none
+****************************************************************************************/
+void Adafruit_MP3::pause()
+{
+ disableTimer();
+}
+
+/**
+*****************************************************************************************
+* @brief Resume playback. This function re-enables the playback timer. If you are
+* starting playback of a new file, use Adafruit_MP3::play instead
+*
+* @return none
+****************************************************************************************/
+void Adafruit_MP3::resume()
+{
+ enableTimer();
+}
+
+/**
+*****************************************************************************************
+* @brief Get the number of bytes until the end of the ID3 tag.
+*
+* @param readPtr current read pointer
+*
+* @return none
+****************************************************************************************/
+int Adafruit_MP3::findID3Offset(uint8_t *readPtr)
+{
+ char header[10];
+ memcpy(header, readPtr, 10);
+ //http://id3.org/id3v2.3.0#ID3v2_header
+ if(header[0] == 0x49 && header[1] == 0x44 && header[2] == 0x33 && header[3] < 0xFF){
+ //this is a tag
+ uint32_t sz = ((uint32_t)header[6] << 23) | ((uint32_t)header[7] << 15) | ((uint32_t)header[8] << 7) | header[9];
+ return sz;
+ }
+ else{
+ //this is not a tag
+ return 0;
+ }
+}
+
+void Adafruit_MP3_DMA::getBuffers(int16_t **ping, int16_t **pong){
+ *pong = outbufs[0].buffer;
+ *ping = outbufs[1].buffer;
+}
+
+/**
+*****************************************************************************************
+* @brief The main loop of the mp3 player. This function should be called as fast as
+* possible in the loop() function of your sketch. This checks to see if the
+* buffers need to be filled, and calls the buffer callback function if necessary.
+* It also calls the functions to decode another frame of mp3 data.
+*
+* @return none
+****************************************************************************************/
+int Adafruit_MP3::tick(){
+ noInterrupts();
+ if (outbufs[activeOutbuf].count == 0 && outbufs[!activeOutbuf].count > 0) {
+ //time to swap the buffers
+ activeOutbuf = !activeOutbuf;
+ outptr = outbufs[activeOutbuf].buffer;
+ }
+ interrupts();
+
+ //if we are running out of samples, and don't yet have another buffer ready, get busy.
+ if ((outbufs[activeOutbuf].count < BUFFER_LOWER_THRESH) &&
+ (outbufs[!activeOutbuf].count < (MP3_OUTBUF_SIZE/2))) {
+ //Serial.println("running low");
+ //dumb, but we need to move any bytes to the beginning of the buffer
+ if ((readPtr != inBuf) && (bytesLeft > 0) && (bytesLeft < BUFFER_LOWER_THRESH)) {
+ /*
+ Serial.print("move bytes: ");
+ Serial.print((uint32_t)&inBuf, HEX); Serial.print(", ");
+ Serial.print((uint32_t)&readPtr, HEX); Serial.print(", ");
+ Serial.println(bytesLeft);
+ */
+ memmove(inBuf, readPtr, bytesLeft);
+ //Serial.println("moved");
+ readPtr = inBuf;
+ writePtr = inBuf + bytesLeft;
+ }
+
+ //get more data from the user application
+ if (bufferCallback != NULL){
+ //Serial.println("ask!");
+ if ( (inbufend - writePtr) > 0) {
+ int bytesRead = bufferCallback(writePtr, inbufend - writePtr);
+ writePtr += bytesRead;
+ bytesLeft += bytesRead;
+ }
+ }
+
+ MP3FrameInfo frameInfo;
+ int err, offset;
+
+ if (!playing) {
+ Serial.println("Starting playback");
+
+ /* Find start of next MP3 frame. Assume EOF if no sync found. */
+ offset = MP3FindSyncWord(readPtr, bytesLeft);
+ Serial.print("Offset: "); Serial.println(offset);
+ if (offset >= 0) {
+ readPtr += offset;
+ bytesLeft -= offset;
+ }
+
+ err = MP3GetNextFrameInfo(hMP3Decoder, &frameInfo, readPtr);
+ if (err == ERR_MP3_INVALID_FRAMEHEADER) {
+ readPtr += 1;
+ bytesLeft -= 1;
+ } else {
+ Serial.print("Setting timer sample rate to: "); Serial.println(frameInfo.samprate);
+ if (frameInfo.samprate != MP3_SAMPLE_RATE_DEFAULT) {
+ updateTimerFreq(frameInfo.samprate);
+ }
+ playing = true;
+ numChannels = frameInfo.nChans;
+ }
+ return 1;
+ }
+
+ offset = MP3FindSyncWord(readPtr, bytesLeft);
+ if (offset >= 0) {
+ //Serial.print("found sync @ "); Serial.println(offset);
+ readPtr += offset;
+ bytesLeft -= offset;
+
+ //fill the inactive outbuffer
+ err = MP3Decode(hMP3Decoder, &readPtr, (int*) &bytesLeft, outbufs[!activeOutbuf].buffer + outbufs[!activeOutbuf].count, 0);
+ if (err) {
+ // sometimes we have a bad frame, lets just nudge forward one byte
+ if (err == ERR_MP3_INVALID_FRAMEHEADER) {
+ readPtr += 1;
+ bytesLeft -= 1;
+ }
+ return err;
+ }
+ MP3DecInfo *mp3DecInfo = (MP3DecInfo *)hMP3Decoder;
+ outbufs[!activeOutbuf].count += mp3DecInfo->nGrans * mp3DecInfo->nGranSamps * mp3DecInfo->nChans;
+ }
+ }
+ return 0;
+}
+
+//fill a buffer with data
+int Adafruit_MP3_DMA::fill(){
+
+ int ret = 0;
+ int16_t *curBuf = outbufs[activeOutbuf].buffer;
+
+ //put any leftover samples in the new buffer
+ if(leftoverSamples > 0){
+ memcpy(outbufs[activeOutbuf].buffer, leftover, leftoverSamples*sizeof(int16_t));
+ outbufs[activeOutbuf].count = leftoverSamples;
+ leftoverSamples = 0;
+ }
+
+ while(outbufs[activeOutbuf].count < MP3_OUTBUF_SIZE){
+ loopstart:
+ //dumb, but we need to move any bytes to the beginning of the buffer
+ if(readPtr != inBuf && bytesLeft < BUFFER_LOWER_THRESH){
+ memmove(inBuf, readPtr, bytesLeft);
+ readPtr = inBuf;
+ writePtr = inBuf + bytesLeft;
+ }
+
+ //get more data from the user application
+ if(bufferCallback != NULL){
+ if(inbufend - writePtr > 0){
+ int bytesRead = bufferCallback(writePtr, inbufend - writePtr);
+ if(bytesRead == 0){
+ ret = 1;
+ break;
+ }
+ writePtr += bytesRead;
+ bytesLeft += bytesRead;
+ }
+ }
+
+ int err, offset;
+
+ if(!playing){
+ /* Find start of next MP3 frame. Assume EOF if no sync found. */
+ offset = MP3FindSyncWord(readPtr, bytesLeft);
+ if(offset >= 0){
+ readPtr += offset;
+ bytesLeft -= offset;
+ }
+
+ err = MP3GetNextFrameInfo(hMP3Decoder, &frameInfo, readPtr);
+ if(err != ERR_MP3_INVALID_FRAMEHEADER){
+ if(frameInfo.samprate != MP3_SAMPLE_RATE_DEFAULT)
+ {
+ updateTimerFreq(frameInfo.samprate);
+ }
+ playing = true;
+ Adafruit_MP3::numChannels = frameInfo.nChans;
+ }
+ if(framebuf != NULL) free(framebuf);
+ framebuf = (int16_t *)malloc(frameInfo.outputSamps*sizeof(int16_t));
+ goto loopstart;
+ }
+
+ offset = MP3FindSyncWord(readPtr, bytesLeft);
+ if(offset >= 0){
+ readPtr += offset;
+ bytesLeft -= offset;
+
+ MP3DecInfo *mp3DecInfo = (MP3DecInfo *)hMP3Decoder;
+ int toRead = mp3DecInfo->nGrans * mp3DecInfo->nGranSamps * mp3DecInfo->nChans;
+ if(outbufs[activeOutbuf].count + toRead < MP3_OUTBUF_SIZE){
+ //we can read directly into the output buffer so lets do that
+ err = MP3Decode(hMP3Decoder, &readPtr, (int*) &bytesLeft, outbufs[activeOutbuf].buffer + outbufs[activeOutbuf].count, 0);
+ outbufs[activeOutbuf].count += toRead;
+ }
+ else{
+ //the frame would cross byte boundaries, we need to split manually
+ err = MP3Decode(hMP3Decoder, &readPtr, (int*) &bytesLeft, framebuf, 0);
+ int remainder = MP3_OUTBUF_SIZE - outbufs[activeOutbuf].count;
+ memcpy(outbufs[activeOutbuf].buffer + outbufs[activeOutbuf].count, framebuf, remainder*sizeof(int16_t));
+ leftover = framebuf + remainder;
+ leftoverSamples = (toRead-remainder);
+
+ //swap buffers
+ activeOutbuf = !activeOutbuf;
+ outbufs[activeOutbuf].count = 0;
+ ret = 0;
+ break;
+ }
+
+ if (err) {
+ return err;
+ }
+ }
+ }
+
+ if(decodeCallback != NULL) decodeCallback(curBuf, MP3_OUTBUF_SIZE);
+
+
+ return ret;
+}
+
+
+
+/**
+ *****************************************************************************************
+ * @brief The IRQ function that gets called whenever the playback timer fires.
+ *
+ * @return none
+ ****************************************************************************************/
+#if defined(NRF52)
+extern "C" {
+#endif
+void MP3_Handler()
+{
+ //disableTimer();
+
+ if(outbufs[activeOutbuf].count >= Adafruit_MP3::numChannels){
+ //it's sample time!
+ if(sampleReadyCallback != NULL){
+ if(Adafruit_MP3::numChannels == 1)
+ sampleReadyCallback(*outptr, 0);
+ else
+ sampleReadyCallback(*outptr, *(outptr + 1));
+
+ //increment the read position and decrement the remaining sample count
+ outptr += Adafruit_MP3::numChannels;
+ outbufs[activeOutbuf].count -= Adafruit_MP3::numChannels;
+ }
+ }
+
+ //enableTimer();
+
+ acknowledgeInterrupt();
+}
+#if defined(NRF52)
+}
+#endif