|
- /* Audio Library for Teensy 3.X
- * 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.
- */
-
- #include "play_sd_wav.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
- #define STATE_STOP 12
-
- void AudioPlaySdWav::begin(void)
- {
- state = STATE_STOP;
- state_play = STATE_STOP;
- data_length = 0;
- if (block_left) {
- release(block_left);
- block_left = NULL;
- }
- if (block_right) {
- release(block_right);
- block_right = NULL;
- }
- }
-
-
- bool AudioPlaySdWav::play(const char *filename)
- {
- stop();
- wavfile = SD.open(filename);
- if (!wavfile) return false;
- buffer_length = 0;
- buffer_offset = 0;
- state_play = STATE_STOP;
- data_length = 0;
- state = STATE_PARSE1;
- return true;
- }
-
- void AudioPlaySdWav::stop(void)
- {
- __disable_irq();
- if (state != STATE_STOP) {
- audio_block_t *b1 = block_left;
- block_left = NULL;
- audio_block_t *b2 = block_right;
- block_right = NULL;
- state = STATE_STOP;
- __enable_irq();
- if (b1) release(b1);
- if (b2) release(b2);
- wavfile.close();
- } else {
- __enable_irq();
- }
- }
-
-
- void AudioPlaySdWav::update(void)
- {
- int32_t n;
-
- // only update if we're playing
- if (state == STATE_STOP) return;
-
- // allocate the audio blocks to transmit
- block_left = allocate();
- if (block_left == NULL) return;
- if (state < 8 && (state & 1) == 1) {
- // if we're playing stereo, allocate another
- // block for the right channel output
- block_right = allocate();
- if (block_right == NULL) {
- release(block_left);
- return;
- }
- } else {
- // if we're playing mono or just parsing
- // the WAV file header, no right-side block
- block_right = NULL;
- }
- block_offset = 0;
-
- //Serial.println("update");
-
- // is there buffered data?
- n = buffer_length - buffer_offset;
- if (n > 0) {
- // we have buffered data
- 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...
- buffer_length = wavfile.read(buffer, 512);
- if (buffer_length == 0) goto end;
- buffer_offset = 0;
- if (!consume(buffer_length)) {
- // no audio was transmitted
- //Serial.println("consume returned false");
- if (state != STATE_STOP) goto cleanup;
- }
- return;
- }
- end: // end of file reached or other reason to stop
- wavfile.close();
- state_play = STATE_STOP;
- state = STATE_STOP;
- cleanup:
- if (block_left) {
- if (block_offset > 0) {
- while (block_offset < AUDIO_BLOCK_SAMPLES) {
- block_left->data[block_offset++] = 0;
- }
- transmit(block_left, 0);
- if (state < 8 && (state & 1) == 0) {
- transmit(block_left, 1);
- }
- }
- release(block_left);
- block_left = NULL;
- }
- if (block_right) {
- if (block_offset > 0) {
- while (block_offset < AUDIO_BLOCK_SAMPLES) {
- block_right->data[block_offset++] = 0;
- }
- transmit(block_right, 1);
- }
- release(block_right);
- block_right = NULL;
- }
- }
-
-
- // https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
-
- // Consume already buffered data. Returns true if audio transmitted.
- bool AudioPlaySdWav::consume(uint32_t size)
- {
- uint32_t len;
- uint8_t lsb, msb;
- const uint8_t *p;
-
- p = buffer + buffer_offset;
- start:
- if (size == 0) return false;
- //Serial.print("AudioPlaySdWav write, ");
- //Serial.print("size = ");
- //Serial.print(size);
- //Serial.print(", data_length = ");
- //Serial.print(data_length);
- //Serial.print(", space = ");
- //Serial.println((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 = 20 - data_length;
- if (size < len) len = size;
- memcpy((uint8_t *)header + data_length, p, len);
- data_length += len;
- if (data_length < 20) return false;
- // parse the header...
- if (header[0] == 0x46464952 && header[2] == 0x45564157
- && header[3] == 0x20746D66 && header[4] == 16) {
- //Serial.println("header ok");
- state = STATE_PARSE2;
- p += len;
- size -= len;
- data_length = 0;
- goto start;
- }
- //Serial.println("unknown WAV header");
- break;
-
- // check & extract key audio parameters
- case STATE_PARSE2:
- len = 16 - data_length;
- if (size < len) len = size;
- memcpy((uint8_t *)header + data_length, p, len);
- data_length += len;
- if (data_length < 16) return false;
- if (parse_format()) {
- //Serial.println("audio format ok");
- p += len;
- size -= len;
- data_length = 0;
- state = STATE_PARSE3;
- goto start;
- }
- //Serial.println("unknown audio format");
- break;
-
- // find the data chunk
- case STATE_PARSE3:
- len = 8 - data_length;
- if (size < len) len = size;
- memcpy((uint8_t *)header + data_length, p, len);
- data_length += len;
- if (data_length < 8) return false;
- //Serial.print("chunk id = ");
- //Serial.print(header[0], HEX);
- //Serial.print(", length = ");
- //Serial.println(header[1]);
- p += len;
- size -= len;
- data_length = header[1];
- if (header[0] == 0x61746164) {
- //Serial.print("wav: found data chunk, len=");
- //Serial.println(data_length);
- // 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 = allocate();
- if (!block_right) return false;
- }
- total_length = data_length;
- } else {
- state = STATE_PARSE4;
- }
- goto start;
-
- // ignore any extra unknown chunks (title & artist info)
- case STATE_PARSE4:
- if (size < data_length) {
- data_length -= size;
- return false;
- }
- p += data_length;
- size -= data_length;
- data_length = 0;
- state = STATE_PARSE3;
- 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
- case STATE_DIRECT_16BIT_MONO:
- if (size > data_length) size = data_length;
- data_length -= size;
- while (1) {
- lsb = *p++;
- msb = *p++;
- size -= 2;
- block_left->data[block_offset++] = (msb << 8) | lsb;
- if (block_offset >= AUDIO_BLOCK_SAMPLES) {
- transmit(block_left, 0);
- transmit(block_left, 1);
- release(block_left);
- block_left = NULL;
- data_length += size;
- buffer_offset = p - buffer;
- if (block_right) release(block_right);
- if (data_length == 0) state = STATE_STOP;
- return true;
- }
- if (size == 0) {
- if (data_length == 0) break;
- return false;
- }
- }
- //Serial.println("end of file reached");
- // 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
- case STATE_DIRECT_16BIT_STEREO:
- if (size > data_length) size = data_length;
- data_length -= size;
- if (leftover_bytes) {
- block_left->data[block_offset] = header[0];
- goto right16;
- }
- while (1) {
- lsb = *p++;
- msb = *p++;
- size -= 2;
- if (size == 0) {
- if (data_length == 0) break;
- header[0] = (msb << 8) | lsb;
- leftover_bytes = 2;
- return false;
- }
- block_left->data[block_offset] = (msb << 8) | lsb;
- right16:
- lsb = *p++;
- msb = *p++;
- size -= 2;
- block_right->data[block_offset++] = (msb << 8) | lsb;
- if (block_offset >= AUDIO_BLOCK_SAMPLES) {
- transmit(block_left, 0);
- release(block_left);
- block_left = NULL;
- transmit(block_right, 1);
- release(block_right);
- block_right = 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;
- }
- }
- // end of file reached
- 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("AudioPlaySdWav, unknown state");
- }
- state_play = STATE_STOP;
- state = STATE_STOP;
- return false;
- }
-
-
- /*
- 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 ................
- */
-
-
-
-
-
- // SD library on Teensy3 at 96 MHz
- // 256 byte chunks, speed is 443272 bytes/sec
- // 512 byte chunks, speed is 468023 bytes/sec
-
- #define B2M_44100 (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT) // 97352592
- #define B2M_22050 (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT * 2.0)
- #define B2M_11025 (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT * 4.0)
-
- bool AudioPlaySdWav::parse_format(void)
- {
- uint8_t num = 0;
- uint16_t format;
- uint16_t channels;
- uint32_t rate, b2m;
- uint16_t bits;
-
- format = header[0];
- //Serial.print(" format = ");
- //Serial.println(format);
- if (format != 1) return false;
-
- rate = header[1];
- //Serial.print(" rate = ");
- //Serial.println(rate);
- if (rate == AUDIO_SAMPLE_RATE) {
- b2m = B2M_44100;
- } else if (rate == 22050) {
- b2m = B2M_22050;
- num |= 4;
- } else if (rate == 11025) {
- b2m = B2M_11025;
- num |= 4;
- } else {
- return false;
- }
-
- channels = header[0] >> 16;
- //Serial.print(" channels = ");
- //Serial.println(channels);
- if (channels == 1) {
- } else if (channels == 2) {
- b2m >>= 1;
- num |= 1;
- } else {
- return false;
- }
-
- bits = header[3] >> 16;
- //Serial.print(" bits = ");
- //Serial.println(bits);
- if (bits == 8) {
- } else if (bits == 16) {
- b2m >>= 1;
- num |= 2;
- } else {
- return false;
- }
-
- bytes2millis = b2m;
- //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;
- }
-
-
- bool AudioPlaySdWav::isPlaying(void)
- {
- return (state < 8);
- }
-
- uint32_t AudioPlaySdWav::positionMillis(void)
- {
- if (state >= 8) return 0;
- uint32_t offset = total_length - data_length;
- return ((uint64_t)offset * bytes2millis) >> 32;
- }
-
-
- uint32_t AudioPlaySdWav::lengthMillis(void)
- {
- if (state >= 8) return 0;
- return ((uint64_t)total_length * bytes2millis) >> 32;
- }
-
-
-
-
-
|