diff --git a/AudioSDPlayer_F32.cpp b/AudioSDPlayer_F32.cpp new file mode 100644 index 0000000..032443a --- /dev/null +++ b/AudioSDPlayer_F32.cpp @@ -0,0 +1,724 @@ +/* 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; +} diff --git a/AudioSDPlayer_F32.h b/AudioSDPlayer_F32.h new file mode 100644 index 0000000..b7c257e --- /dev/null +++ b/AudioSDPlayer_F32.h @@ -0,0 +1,227 @@ +/* *** AudioSDPlayer_F32.h *** + * + * Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.comaudio_block_samples + * + * 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. +*/ + +/* WAV File Format + Bytes Meaning +1 - 4 “RIFF” Marks the file as a riff file. Characters are each 1 byte long. +5 - 8 File size (integer) Size of the overall file - 8 bytes, in bytes + (32-bit integer). +9 -12 “WAVE” File Type Header. For our purposes, it always equals “WAVE”. +13-16 “fmt " Format chunk marker. Includes trailing null +17-20 Length of format data as listed above, e.g., 16 +21-22 Type of format (1 is PCM) - 2 byte integer +23-24 Number of Channels - 2 byte integer, e.g. 2 +25-28 Sample Rate - 32 byte integer. Common values are 44100 (CD), + 48000 (DAT). Sample Rate = Number of Samples per second, or Hertz. +29-32 176400 (Sample Rate * BitsPerSample * Channels) / 8. +33-34 4 (BitsPerSample * Channels) / 8.1 - 8 bit mono2 - 8 bit + stereo/16 bit mono4 - 16 bit stereo +35-36 16 Bits per sample or other +37-40 "data" Marks the beginning of the data section. +41-44 File size (data) Size of the data section. +* +Sample of WAV file start: +00000000 52494646 66EA6903 57415645 666D7420 RIFFf.i.WAVEfmt +00000010 10000000 01000200 44AC0000 10B10200 ........D....... +00000020 04001000 4C495354 3A000000 494E464F ....LIST:...INFO +00000030 494E414D 14000000 49205761 6E742054 INAM....I Want T +00000040 6F20436F 6D65204F 76657200 49415254 o Come Over.IART +00000050 12000000 4D656C69 73736120 45746865 ....Melissa Ethe +00000060 72696467 65006461 746100EA 69030100 ridge.data..i... +00000070 FEFF0300 FCFF0400 FDFF0200 0000FEFF ................ +00000080 0300FDFF 0200FFFF 00000100 FEFF0300 ................ +00000090 FDFF0300 FDFF0200 FFFF0100 0000FFFF ................ +*/ + +/* *** SAMPLE RATES *** + * In the case of WAV files, there is a specified sample rate that is part + * of the header data. The file is a stream of numbers. If these are + * turned into voltages and played at the specified rate, everything will + * sound correct. For shorthand, we will call this the WAV rate. + * + * There is also a Teensy Sample Rate for the Audio system, set by things + * like the hardware clock for I2S. If this sample rate is the same as + * the WAV rate, life is simple and we process and output a sample + * with the WAV rate being achieved. A second case is for the WAV rate + * to be an integer sub-multiple of the Teensy Sample Rate. This would + * allow the wave file to run with a 12 ksps sample rate and the Teensy + * Sample Rate to be 48, or 96, ksps. There is a data interpolator + * included after the the data has been read from the file. This requires + * specification of the sub-multiple integer and a FIR filter to complete + * the interpolation operation. The structure sampleSubMultiple, + * provided by the .INO, communicates this design information from the INO. + * + * The third case is to have the Wave rate and the Teensy Sample rate + * related by a rational fraction. This would require a rate changet + * consisting of both a decimator and an interpolator. None of that is + * included in this class. + */ + +#ifndef AudioSDPlayer_F32_h_ +#define AudioSDPlayer_F32_h_ + +#include "Arduino.h" +#include "AudioSettings_F32.h" +#include "AudioStream_F32.h" + +#include //included in Teensy install as of Teensyduino 1.54-bete3 + +// This communicates the info for running slow WAV file sample rates. +// This one is declared in the .INO +struct subMult { + uint16_t rateRatio; // Should be 1 for no rate change, else 2, 4, 8 + uint16_t numCoeffs; // FIR filter + float32_t* firCoeffs; // FIR Filter Coeffs + float32_t* firBufferL; // pointer to 127 + numCoeffs float32_t, left ch + float32_t* firBufferR; // pointer to 127 + numCoeffs float32_t, right ch + }; + +// This communicates the important parameters of the WAV file. This is +// declared in AudioSDPlayer_F32 to provide data to the .INO. +struct wavData { + uint16_t audio_format; // Should be 1 for PCM + uint16_t num_channels; // 1 for mono, 2 for stereo + uint32_t sample_rate; // 44100, 48000, etc + uint16_t bits; // Number of bits per sample + }; + +class AudioSDPlayer_F32 : public AudioStream_F32 +{ +//GUI: inputs:0, outputs:2 //this line used for automatic generation of GUI nodes + public: + + AudioSDPlayer_F32(void) : + AudioStream_F32(0, NULL), block_left_f32(NULL), block_right_f32(NULL) + { + begin(); + } + + AudioSDPlayer_F32(const AudioSettings_F32 &settings) : + AudioStream_F32(0, NULL), block_left_f32(NULL), block_right_f32(NULL) + { + setSampleRate_Hz(settings.sample_rate_Hz); + //setBlockSize(settings.audio_block_samples); // Always 128 + begin(); + } + + void begin(void); //begins SD card + bool play(const char *filename); + void stop(void); + void togglePlayPause(void); + bool isPaused(void); + bool isStopped(void); + bool isPlaying(void); + uint32_t positionMillis(void); + uint32_t lengthMillis(void); + + // Required when WAV file is at a sub-multiple rate of audio sampling rate + void setSubMult(subMult* pSampleSubMultipleStruct) { + if(pSampleSubMultipleStruct->rateRatio == 1 || + pSampleSubMultipleStruct->rateRatio == 2 || + pSampleSubMultipleStruct->rateRatio == 4 || + pSampleSubMultipleStruct->rateRatio == 8) + { + pSampleSubMultiple = pSampleSubMultipleStruct; + if(pSampleSubMultiple->numCoeffs > 1 && + pSampleSubMultiple->firBufferL ) + { + arm_fir_init_f32(&fir_instL, + pSampleSubMultiple->numCoeffs, + (float32_t *)pSampleSubMultiple->firCoeffs, + (float32_t *)pSampleSubMultiple->firBufferL, + (uint32_t)audio_block_samples); + arm_fir_init_f32(&fir_instR, + pSampleSubMultiple->numCoeffs, + (float32_t *)pSampleSubMultiple->firCoeffs, + (float32_t *)pSampleSubMultiple->firBufferR, + (uint32_t)audio_block_samples); + } + } + else + Serial.println("Illegal sub-division multiple for WAV rate."); + } + + // Provides basic meta-data about WAV file. + wavData* getCurrentWavData(void) { + return ¤tWavData; // Pointer to structure + } + + float32_t setSampleRate_Hz(float32_t fs_Hz) { + sample_rate_Hz = fs_Hz; + updateBytes2Millis(); + return sample_rate_Hz; + } + + virtual void update(void); + + private: + File wavfile; + struct subMult* pSampleSubMultiple = &nEqOneTemp; + // Next is a dummy structure to divide by 1 when no INO structure + struct subMult nEqOneTemp = {1, 0, NULL, NULL, NULL}; + arm_fir_instance_f32 fir_instL; + arm_fir_instance_f32 fir_instR; + struct wavData currentWavData = {1, 2, 44100, 16}; + bool consume(uint32_t size); + bool parse_format(void); + uint32_t header[10]; // temporary storage of wav header data + uint32_t data_length; // number of bytes remaining in current section + uint32_t total_length; // number of audio data bytes in file + uint16_t channels = 1; //number of audio channels + uint16_t bits = 16; // number of bits per sample + uint32_t bytes2millis; + // Variables for audio library storage, float32_t + audio_block_f32_t *block_left_f32 = NULL; + audio_block_f32_t *block_right_f32 = NULL; + uint16_t block_offset; // how much data is in block_left & block_right + // Variables for buffering the WAV file read, uint8_t + uint8_t buffer[512]; // buffer one block of SD file data + uint16_t buffer_offset; // where we're at consuming "buffer" + uint16_t buffer_length; // how many data bytes are in "buffer" (512 until last read) + uint8_t header_offset; // number of bytes in header[] + // Variables to control the WAV file reading + uint8_t state; + uint8_t state_play; + uint8_t leftover_bytes; + // Variables for WAV file sampled at a sub rate from audio process + uint8_t zerosToSend = 0; + + static unsigned long update_counter; + float sample_rate_Hz = ((float)AUDIO_SAMPLE_RATE_EXACT); + uint16_t audio_block_samples = AUDIO_BLOCK_SAMPLES; + + uint32_t updateBytes2Millis(void); + //int32_t pctr = 0; +}; + +#endif diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 6a984eb..5d5c9a9 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -20,6 +20,7 @@ #include "AudioLMSDenoiseNotch_F32.h" #include "AudioMixer_F32.h" #include "AudioMultiply_F32.h" +#include "AudioSDPlayer_F32.h" #include "AudioSettings_F32.h" #include "AudioSpectralDenoise_F32.h" #include "input_i2s_f32.h" diff --git a/examples/SDWavPlayer/SDWavPlayer.ino b/examples/SDWavPlayer/SDWavPlayer.ino new file mode 100644 index 0000000..a1f81e9 --- /dev/null +++ b/examples/SDWavPlayer/SDWavPlayer.ino @@ -0,0 +1,80 @@ +/* + * SDWavPlayer + * + * Created: Chip Audette, OpenAudio, Dec 2019 + * Based On: WaveFilePlayer from Paul Stoffregen, PJRC, Teensy + * + * Play back a WAV file through the Typman. + * + * For access to WAV files, please visit https://www.pjrc.com/teensy/td_libs_AudioDataFiles.html. + * + */ + +#include "OpenAudio_ArduinoLibrary.h" +#include "AudioSDPlayer_F32.h" + +//set the sample rate and block size +const float sample_rate_Hz = 44100.0f; +const int audio_block_samples = 128; // Must be 128 for SD recording. +AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); + +//create audio objects +AudioSDPlayer_F32 audioSDPlayer(audio_settings); +AudioOutputI2S_F32 audioOutput(audio_settings); +//Tympan myTympan(TympanRev::E); //do TympanRev::D or TympanRev::E + +//create audio connections +AudioConnection_F32 patchCord1(audioSDPlayer, 0, audioOutput, 0); +AudioConnection_F32 patchCord2(audioSDPlayer, 1, audioOutput, 1); +AudioControlSGTL5000 sgtl5000_1; + + +// Use these with the Teensy 4.x Rev D Audio Shield +#define SDCARD_CS_PIN 10 +#define SDCARD_MOSI_PIN 11 +#define SDCARD_SCK_PIN 13 + +void setup() { + Serial.begin(300); delay(1000); + Serial.print("### SDWavPlayer ###"); + Serial.print("Sample Rate (Hz): "); Serial.println(audio_settings.sample_rate_Hz); + Serial.print("Audio Block Size (samples): "); Serial.println(audio_settings.audio_block_samples); + + // Audio connections require memory to work. + AudioMemory_F32(20, audio_settings); + + sgtl5000_1.enable(); + + SPI.setMOSI(SDCARD_MOSI_PIN); + SPI.setSCK(SDCARD_SCK_PIN); + if (!(SD.begin(SDCARD_CS_PIN))) { + // stop here, but print a message repetitively + while (1) { + Serial.println("*** Unable to access the SD card ***"); + delay(1000); + } + } + //prepare SD player + audioSDPlayer.begin(); + + //finish setup + delay(2000); //stall a second + Serial.println("Setup complete."); +} + +unsigned long end_millis = 0; +String filename = "SDTEST1.WAV";// filenames are always uppercase 8.3 format +void loop() { + +/* + //service the audio player + if (!audioSDPlayer.isPlaying()) { //wait until previous play is done + //start playing audio + Serial.print("Starting audio player: "); + Serial.println(filename); + audioSDPlayer.play(filename); + } + */ + + delay(500); +} diff --git a/utility/SDTEST1.WAV b/utility/SDTEST1.WAV new file mode 100644 index 0000000..f38f6b0 Binary files /dev/null and b/utility/SDTEST1.WAV differ diff --git a/utility/SDTEST2.WAV b/utility/SDTEST2.WAV new file mode 100644 index 0000000..4d2e074 Binary files /dev/null and b/utility/SDTEST2.WAV differ diff --git a/utility/SDTEST3.WAV b/utility/SDTEST3.WAV new file mode 100644 index 0000000..899f65a Binary files /dev/null and b/utility/SDTEST3.WAV differ diff --git a/utility/SDTEST4.WAV b/utility/SDTEST4.WAV new file mode 100644 index 0000000..9c39420 Binary files /dev/null and b/utility/SDTEST4.WAV differ diff --git a/utility/W9GR12.WAV b/utility/W9GR12.WAV new file mode 100644 index 0000000..3ba5237 Binary files /dev/null and b/utility/W9GR12.WAV differ diff --git a/utility/W9GR24.WAV b/utility/W9GR24.WAV new file mode 100644 index 0000000..f3843b7 Binary files /dev/null and b/utility/W9GR24.WAV differ diff --git a/utility/W9GR48.WAV b/utility/W9GR48.WAV new file mode 100644 index 0000000..4c8b297 Binary files /dev/null and b/utility/W9GR48.WAV differ diff --git a/utility/W9GR6.WAV b/utility/W9GR6.WAV new file mode 100644 index 0000000..1f9ba7d Binary files /dev/null and b/utility/W9GR6.WAV differ