/* Extended from Audio Library for Teensy which is * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com * * Development of this audio library was funded by PJRC.COM, LLC by sales of * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop * open source software by purchasing Teensy or other PJRC products. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice, development funding notice, and this permission * notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * Extended by Chip Audette, OpenAudio, Dec 2019 * Converted to F32 and to variable audio block length * The F32 conversion is under the MIT License. Use at your own risk. * * Further extensions to sub multiple WAV sample rates are copyright * (c) 2023 Bob Larkin under the MIT License. */ #include #include "AudioSDPlayer_F32.h" #include "spi_interrupt.h" #define STATE_DIRECT_8BIT_MONO 0 // playing mono at native sample rate #define STATE_DIRECT_8BIT_STEREO 1 // playing stereo at native sample rate #define STATE_DIRECT_16BIT_MONO 2 // playing mono at native sample rate #define STATE_DIRECT_16BIT_STEREO 3 // playing stereo at native sample rate #define STATE_CONVERT_8BIT_MONO 4 // playing mono, converting sample rate #define STATE_CONVERT_8BIT_STEREO 5 // playing stereo, converting sample rate #define STATE_CONVERT_16BIT_MONO 6 // playing mono, converting sample rate #define STATE_CONVERT_16BIT_STEREO 7 // playing stereo, converting sample rate #define STATE_PARSE1 8 // looking for 20 byte ID header #define STATE_PARSE2 9 // looking for 16 byte format header #define STATE_PARSE3 10 // looking for 8 byte data header #define STATE_PARSE4 11 // ignoring unknown chunk after "fmt " #define STATE_PARSE5 12 // ignoring unknown chunk before "fmt " #define STATE_PAUSED 13 #define STATE_STOP 14 void AudioSDPlayer_F32::begin(void) { state = STATE_STOP; state_play = STATE_STOP; data_length = 0; if (block_left_f32) { AudioStream_F32::release(block_left_f32); block_left_f32 = NULL; } if (block_right_f32) { AudioStream_F32::release(block_right_f32); block_right_f32 = NULL; } } bool AudioSDPlayer_F32::play(const char *filename) { stop(); bool irq = false; if (NVIC_IS_ENABLED(IRQ_SOFTWARE)) { NVIC_DISABLE_IRQ(IRQ_SOFTWARE); irq = true; } #if defined(HAS_KINETIS_SDHC) if (!(SIM_SCGC3 & SIM_SCGC3_SDHC)) AudioStartUsingSPI(); #else AudioStartUsingSPI(); #endif wavfile = SD.open(filename); if (!wavfile) { #if defined(HAS_KINETIS_SDHC) if (!(SIM_SCGC3 & SIM_SCGC3_SDHC)) AudioStopUsingSPI(); #else AudioStopUsingSPI(); #endif if (irq) NVIC_ENABLE_IRQ(IRQ_SOFTWARE); return false; } buffer_length = 0; buffer_offset = 0; state_play = STATE_STOP; data_length = 20; header_offset = 0; state = STATE_PARSE1; if (irq) NVIC_ENABLE_IRQ(IRQ_SOFTWARE); return true; } void AudioSDPlayer_F32::stop(void) { bool irq = false; if (NVIC_IS_ENABLED(IRQ_SOFTWARE)) { NVIC_DISABLE_IRQ(IRQ_SOFTWARE); irq = true; } if (state != STATE_STOP) { audio_block_f32_t *b1 = block_left_f32; block_left_f32 = NULL; audio_block_f32_t *b2 = block_right_f32; block_right_f32 = NULL; state = STATE_STOP; if (b1) AudioStream_F32::release(b1); if (b2) AudioStream_F32::release(b2); wavfile.close(); #if defined(HAS_KINETIS_SDHC) if (!(SIM_SCGC3 & SIM_SCGC3_SDHC)) AudioStopUsingSPI(); #else AudioStopUsingSPI(); #endif } if (irq) NVIC_ENABLE_IRQ(IRQ_SOFTWARE); } void AudioSDPlayer_F32::togglePlayPause(void) { // take no action if wave header is not parsed OR // state is explicitly STATE_STOP if(state_play >= 8 || state == STATE_STOP) return; // toggle back and forth between state_play and STATE_PAUSED if(state == state_play) { state = STATE_PAUSED; } else if(state == STATE_PAUSED) { state = state_play; } } void AudioSDPlayer_F32::update(void) { int32_t n; // only update if we're playing and not paused if (state == STATE_STOP || state == STATE_PAUSED) return; // allocate the audio blocks to transmit block_left_f32 = AudioStream_F32::allocate_f32(); if (block_left_f32 == NULL) return; if (state < 8 && (state & 1) == 1) { // if we're playing stereo, allocate another // block for the right channel output block_right_f32 = AudioStream_F32::allocate_f32(); if (block_right_f32 == NULL) { AudioStream_F32::release(block_left_f32); return; } } else { // if we're playing mono or just parsing // the WAV file header, no right-side block block_right_f32 = NULL; } block_offset = 0; // is there buffered data? n = buffer_length - buffer_offset; if (n > 0) { // Have buffered data. consume(n) returns true if audio transmitted. if (consume(n)) return; // it was enough to transmit audio } // we only get to this point when buffer[512] is empty if (state != STATE_STOP && wavfile.available()) { // we can read more data from the file... readagain: buffer_length = wavfile.read(buffer, 512); if (buffer_length == 0) goto end; buffer_offset = 0; bool parsing = (state >= 8); bool txok = consume(buffer_length); if (txok) { if (state != STATE_STOP) return; } else { if (state != STATE_STOP) { if (parsing && state < 8) goto readagain; else goto cleanup; } } } end: // end of file reached or other reason to stop wavfile.close(); #if defined(HAS_KINETIS_SDHC) if (!(SIM_SCGC3 & SIM_SCGC3_SDHC)) AudioStopUsingSPI(); #else AudioStopUsingSPI(); #endif state_play = STATE_STOP; state = STATE_STOP; cleanup: if (block_left_f32) { if (block_offset > 0) { for (uint32_t i=block_offset; i < audio_block_samples; i++) { block_left_f32->data[i] = 0.0f; } transmit(block_left_f32, 0); if (state < 8 && (state & 1) == 0) { transmit(block_left_f32, 1); } } AudioStream_F32::release(block_left_f32); block_left_f32 = NULL; } if (block_right_f32) { if (block_offset > 0) { for (uint32_t i=block_offset; i < audio_block_samples; i++) { block_right_f32->data[i] = 0.0f; } transmit(block_right_f32, 1); } AudioStream_F32::release(block_right_f32); block_right_f32 = NULL; } } // Consume already buffered WAV file data. Returns true if audio transmitted. bool AudioSDPlayer_F32::consume(uint32_t size) { uint32_t len; uint8_t lsb, msb; const uint8_t *p; int16_t val_int16; float32_t rateRatioF; rateRatioF = (float32_t) pSampleSubMultiple->rateRatio; p = buffer + buffer_offset; start: if (size == 0) return false; /* Serial.print("AudioSDPlayer_F32 consume, "); Serial.print("size = "); Serial.print(size); Serial.print(", buffer_offset = "); Serial.print(buffer_offset); Serial.print(", data_length = "); Serial.print(data_length); Serial.print(", space = "); Serial.print((audio_block_samples - block_offset) * 2); Serial.print(", state = "); Serial.println(state); */ switch (state) { // parse wav file header, is this really a .wav file? case STATE_PARSE1: len = data_length; if (size < len) len = size; memcpy((uint8_t *)header + header_offset, p, len); header_offset += len; buffer_offset += len; data_length -= len; if (data_length > 0) return false; // parse the header... if (header[0] == 0x46464952 && header[2] == 0x45564157) { //Serial.println("is wav file"); if (header[3] == 0x20746D66) { // "fmt " header if (header[4] < 16) { // WAV "fmt " info must be at least 16 bytes break; } if (header[4] > sizeof(header)) { // if such .wav files exist, increasing the // size of header[] should accomodate them... //Serial.println("WAVEFORMATEXTENSIBLE too long"); break; } //Serial.println("header ok"); header_offset = 0; state = STATE_PARSE2; } else { // first chuck is something other than "fmt " //Serial.print("skipping \""); //Serial.printf("\" (%08X), ", __builtin_bswap32(header[3])); //Serial.print(header[4]); //Serial.println(" bytes"); header_offset = 12; state = STATE_PARSE5; } p += len; size -= len; data_length = header[4]; goto start; } //Serial.println("unknown WAV header"); break; // check & extract key audio parameters case STATE_PARSE2: len = data_length; if (size < len) len = size; memcpy((uint8_t *)header + header_offset, p, len); header_offset += len; buffer_offset += len; data_length -= len; if (data_length > 0) return false; if (parse_format()) { //Serial.println("audio format ok"); p += len; size -= len; data_length = 8; header_offset = 0; state = STATE_PARSE3; goto start; } //Serial.println("unknown audio format"); break; // find the data chunk case STATE_PARSE3: // 10 len = data_length; if (size < len) len = size; memcpy((uint8_t *)header + header_offset, p, len); header_offset += len; buffer_offset += len; data_length -= len; if (data_length > 0) return false; p += len; size -= len; data_length = header[1]; if (header[0] == 0x61746164) { // TODO: verify offset in file is an even number // as required by WAV format. abort if odd. Code // below will depend upon this and fail if not even. leftover_bytes = 0; state = state_play; if (state & 1) { // if we're going to start stereo // better allocate another output block block_right_f32 = AudioStream_F32::allocate_f32(); if (!block_right_f32) return false; } total_length = data_length; } else { state = STATE_PARSE4; } goto start; // ignore any extra unknown chunks (title & artist info) case STATE_PARSE4: // 11 if (size < data_length) { data_length -= size; buffer_offset += size; return false; } p += data_length; size -= data_length; buffer_offset += data_length; data_length = 8; header_offset = 0; state = STATE_PARSE3; goto start; // skip past "junk" data before "fmt " header case STATE_PARSE5: len = data_length; if (size < len) len = size; buffer_offset += len; data_length -= len; if (data_length > 0) return false; p += len; size -= len; data_length = 8; state = STATE_PARSE1; goto start; // playing mono at native sample rate case STATE_DIRECT_8BIT_MONO: return false; // playing stereo at native sample rate case STATE_DIRECT_8BIT_STEREO: return false; // Playing Mono at native sample rate ****** 16-BIT MONO ****** case STATE_DIRECT_16BIT_MONO: if (size > data_length) // End of WAV file size = data_length; data_length -= size; while (1) { if(zerosToSend > 0) { block_left_f32->data[block_offset++] = 0.0f; // Zeros for interpolation zerosToSend--; if (block_offset >= audio_block_samples) { if(pSampleSubMultiple->numCoeffs > 1 && // i.e., using FIR pSampleSubMultiple->firBufferL ) { arm_fir_f32(&fir_instL, block_left_f32->data, block_left_f32->data, block_left_f32->length); } transmit(block_left_f32, 0); // Mono sends same to L&R transmit(block_left_f32, 1); AudioStream_F32::release(block_left_f32); block_left_f32 = NULL; data_length += size; buffer_offset = p - buffer; if (block_right_f32) AudioStream_F32::release(block_right_f32); if (data_length == 0) state = STATE_STOP; return true; } } else // Not zeros, but data { lsb = *p++; // Little endian msb = *p++; size -= 2; // 2 bytes per word // Convert to F32 val_int16 = (msb << 8) | lsb; // Scale up by rateRatioF to account for zeros block_left_f32->data[block_offset++] = rateRatioF*((float)val_int16)/(32768.0); // For interpolation, each data point is followed by 0.0f's zerosToSend = pSampleSubMultiple->rateRatio - 1; // 0, 1, 3, 7 if (block_offset >= audio_block_samples) { // The FIR update if(pSampleSubMultiple->numCoeffs > 1 && pSampleSubMultiple->firBufferL ) { arm_fir_f32(&fir_instL, block_left_f32->data, block_left_f32->data, block_left_f32->length); } transmit(block_left_f32, 0); // Mono sends same to L&R transmit(block_left_f32, 1); AudioStream_F32::release(block_left_f32); block_left_f32 = NULL; data_length += size; buffer_offset = p - buffer; if (block_right_f32) AudioStream_F32::release(block_right_f32); if (data_length == 0) state = STATE_STOP; return true; } } } // End while(1) if (size == 0) { if (data_length == 0) break; return false; } // End of file reached if (block_offset > 0) { // TODO: fill remainder of last block with zero and transmit } state = STATE_STOP; return false; // Playing stereo at native sample rate ****** 16-BIT STEREO ****** case STATE_DIRECT_16BIT_STEREO: if (size > data_length) size = data_length; data_length -= size; if (leftover_bytes) { block_left_f32->data[block_offset] = header[0]; //PAH fix problem with left+right channels being swapped //RSL Is this actually the CODEC L/R problem? leftover_bytes = 0; // goto right16; // RSL What is the deal??? } while (1) { if(zerosToSend > 0) { block_left_f32->data[block_offset] = 0.0f; // Zeros for interpolation block_right_f32->data[block_offset++] = 0.0f; zerosToSend--; if (block_offset >= audio_block_samples) { if(pSampleSubMultiple->numCoeffs > 1 && // i.e., using FIR pSampleSubMultiple->firBufferL) { arm_fir_f32(&fir_instL, block_left_f32->data, block_left_f32->data, block_left_f32->length); arm_fir_f32(&fir_instR, block_right_f32->data, block_right_f32->data, block_right_f32->length); } transmit(block_left_f32, 0); transmit(block_right_f32, 1); AudioStream_F32::release(block_left_f32); block_left_f32 = NULL; data_length += size; buffer_offset = p - buffer; if (block_right_f32) AudioStream_F32::release(block_right_f32); if (data_length == 0) state = STATE_STOP; return true; } } else // Not zeros, but data { lsb = *p++; // Little endian msb = *p++; size -= 2; if (size == 0) { if (data_length == 0) break; header[0] = (msb << 8) | lsb; leftover_bytes = 2; return false; } val_int16 = (int16_t)((msb << 8) | lsb); //convert from int16 to float32 spanning +/-1.0 // Scale up by rateRatioF to account for zeros block_left_f32->data[block_offset] = rateRatioF*((float)val_int16)/(32768.0); // right16: See about 15 lines above lsb = *p++; msb = *p++; size -= 2; val_int16 = (int16_t)((msb << 8) | lsb); // Convert from int16 to float32 spanning +/-1.0 // Scale up by rateRatioF to account for zeros block_right_f32->data[block_offset++] = rateRatioF*((float)val_int16)/(32768.0); // For stereo, the number of zeros to send refers to // the number of *pairs* of zeros. // For interpolation, each data point is followed by 0.0f's zerosToSend = pSampleSubMultiple->rateRatio - 1; // 0, 1, 3, 7 if (block_offset >= audio_block_samples) { if(pSampleSubMultiple->numCoeffs > 1 && // i.e., using FIR pSampleSubMultiple->firBufferL ) { arm_fir_f32(&fir_instL, block_left_f32->data, block_left_f32->data, block_left_f32->length); arm_fir_f32(&fir_instR, block_right_f32->data, block_right_f32->data, block_right_f32->length); } transmit(block_left_f32, 0); AudioStream_F32::release(block_left_f32); block_left_f32 = NULL; transmit(block_right_f32, 1); AudioStream_F32::release(block_right_f32); block_right_f32 = NULL; data_length += size; buffer_offset = p - buffer; if (data_length == 0) state = STATE_STOP; return true; } if (size == 0) { if (data_length == 0) break; leftover_bytes = 0; return false; } } // Sending data, not zeros // end of file reached } // End while(1) if (block_offset > 0) { // TODO: fill remainder of last block with zero and transmit } state = STATE_STOP; return false; // playing mono, converting sample rate case STATE_CONVERT_8BIT_MONO : return false; // playing stereo, converting sample rate case STATE_CONVERT_8BIT_STEREO: return false; // playing mono, converting sample rate case STATE_CONVERT_16BIT_MONO: return false; // playing stereo, converting sample rate case STATE_CONVERT_16BIT_STEREO: return false; // ignore any extra data after playing // or anything following any error case STATE_STOP: return false; // this is not supposed to happen! //default: //Serial.println("AudioSDPlayer_F32, unknown state"); } state_play = STATE_STOP; state = STATE_STOP; return false; } bool AudioSDPlayer_F32::parse_format(void) { uint8_t num = 0; uint16_t format; uint16_t channels; uint32_t rate, b2m; uint16_t bits; format = header[0]; currentWavData.audio_format = header[0]; // uint16_t //Serial.print(" format = "); //Serial.println(format); if (format != 1) return false; rate = header[1]; currentWavData.sample_rate = header[1]; // uint32_t // Serial.print("WAV file sample rate = "); Serial.println(rate); // b2m is used to determine playing time. We base it on the WAV // file meta data. It is allowed to be played at a different rate // but all we do is to make the info available via the // struct currentWavData The INO needs to deal with differences. // 4294967296000.0 = 2^32 * 1000 b2m = (uint32_t)((double)4294967296000.0 / (double)rate); channels = header[0] >> 16; currentWavData.num_channels = header[0] >> 16; // uint16_t //Serial.print(" channels = "); //Serial.println(channels); if (channels == 1) { } else if (channels == 2) { b2m >>= 1; // Divide b2m by 2 num |= 1; } else return false; bits = header[3] >> 16; currentWavData.bits = header[3] >> 16; // uint16_t //Serial.print(" bits = "); //Serial.println(bits); if (bits == 8) { } else if (bits == 16) { b2m >>= 1; // Again divide b2m by 2 num |= 2; } else {return false;} bytes2millis = b2m; // Transfer to global // Serial.print(" bytes2Millis = "); Serial.println(b2m); // we're not checking the byte rate and block align fields // if they're not the expected values, all we could do is // return false. Do any real wav files have unexpected // values in these other fields? state_play = num; return true; } uint32_t AudioSDPlayer_F32::updateBytes2Millis(void) { double b2m; //account for sample rate b2m = ((double)4294967296000.0 / ((double)sample_rate_Hz)); //account for channels b2m = b2m / ((double)channels); //account for bits per second if (bits == 16) b2m = b2m / 2; else if (bits == 24) b2m = b2m / 3; //can we handle 24 bits? I don't think that we can. // if 8-bits, fall through return bytes2millis = (uint32_t)b2m; } bool AudioSDPlayer_F32::isPlaying(void) { uint8_t s = *(volatile uint8_t *)&state; return (s < 8); } bool AudioSDPlayer_F32::isPaused(void) { uint8_t s = *(volatile uint8_t *)&state; return (s == STATE_PAUSED); } bool AudioSDPlayer_F32::isStopped(void) { uint8_t s = *(volatile uint8_t *)&state; return (s == STATE_STOP); } uint32_t AudioSDPlayer_F32::positionMillis(void) { uint8_t s = *(volatile uint8_t *)&state; if (s >= 8 && s != STATE_PAUSED) return 0; uint32_t tlength = *(volatile uint32_t *)&total_length; uint32_t dlength = *(volatile uint32_t *)&data_length; uint32_t offset = tlength - dlength; uint32_t b2m = *(volatile uint32_t *)&bytes2millis; return ((uint64_t)offset * b2m) >> 32; } uint32_t AudioSDPlayer_F32::lengthMillis(void) { uint8_t s = *(volatile uint8_t *)&state; if (s >= 8 && s != STATE_PAUSED) return 0; uint32_t tlength = *(volatile uint32_t *)&total_length; uint32_t b2m = *(volatile uint32_t *)&bytes2millis; return ((uint64_t)tlength * b2m) >> 32; }