├── .gitignore ├── docs └── speaker 1kHz sawtooth signal.png ├── library.properties ├── .travis.yml ├── examples ├── playFromFlash │ └── playFromFlash.ino ├── sawtooth │ └── sawtooth.ino └── playWavFromSD │ └── playWavFromSD.ino ├── compile_examples.sh ├── LICENSE ├── src ├── speaker.h └── speaker.cpp ├── tools └── raw2uint16.html └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | src/target 2 | *.bin 3 | -------------------------------------------------------------------------------- /docs/speaker 1kHz sawtooth signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monkbroc/particle-speaker/HEAD/docs/speaker 1kHz sawtooth signal.png -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=speaker 2 | version=1.2.0 3 | author=Julien Vanier 4 | sentence=Generate audio output for a speaker 5 | url=https://github.com/monkbroc/particle-speaker 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | install: 5 | - npm install -g particle-cli 6 | - particle --no-update-check login --token $PARTICLE_TOKEN 7 | script: 8 | - ./compile_examples.sh 9 | 10 | -------------------------------------------------------------------------------- /examples/playFromFlash/playFromFlash.ino: -------------------------------------------------------------------------------- 1 | #include "speaker.h" 2 | #include "sound.h" 3 | 4 | const int audioFrequency = 22050; // Hz 5 | 6 | Speaker speaker((uint16_t*)sound, sizeof(sound) / sizeof(sound[0])); 7 | void setup() { 8 | speaker.begin(audioFrequency); 9 | } 10 | -------------------------------------------------------------------------------- /compile_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Compile each library example as a test 3 | 4 | set -xe 5 | 6 | PLATFORMS="photon electron" 7 | EXAMPLES=`find examples -mindepth 1 -maxdepth 1 -not -empty -type d -not -name playWavFromSD | sort` 8 | 9 | for platform in $PLATFORMS; do 10 | for example in $EXAMPLES; do 11 | echo particle --no-update-check compile $platform $example 12 | done 13 | done 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Julien Vanier 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of the Software, 9 | and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/speaker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Speaker library by Julien Vanier 4 | // Adaptions by ScruffR 5 | // - make double buffering optinal 6 | // - option to provide "external" buffer(s) to the library 7 | // which allows to play PCM data stored in flash without copying 8 | 9 | #if PLATFORM_ID == 6 || PLATFORM_ID == 10 10 | 11 | #include "application.h" 12 | 13 | class Speaker 14 | { 15 | public: 16 | // Constructor. 17 | Speaker(uint16_t bufferSize, bool dblBuffer = true); 18 | Speaker(uint16_t *buffer, uint16_t bufferSize); // single external buffer 19 | Speaker(uint16_t *buffer0, uint16_t *buffer1, uint16_t bufferSize); // two external buffers for double buffering 20 | ~Speaker(); 21 | 22 | // Start playback 23 | void begin(uint16_t audioFrequency); 24 | 25 | // Stop playback 26 | void end(); 27 | 28 | // Is a buffer ready to be filled 29 | bool ready(); 30 | 31 | // The next buffer to fill with audio data 32 | uint16_t *getBuffer(); 33 | private: 34 | void setupHW(uint16_t audioFrequency); 35 | uint16_t timerAutoReloadValue(uint16_t audioFrequency); 36 | uint8_t currentBuffer(); 37 | 38 | bool privateBuffer; 39 | uint8_t numBuffers; 40 | uint16_t bufferSize; 41 | uint8_t lastBuffer; 42 | uint16_t audioFrequency; 43 | 44 | /* Double buffers for audio data */ 45 | uint16_t *buffer[2]; 46 | }; 47 | 48 | #else // PLATFORM_ID == 6 || PLATFORM_ID == 10 49 | 50 | #error "The speaker library is only compatible with the Photon and Electron." 51 | 52 | #endif // PLATFORM_ID == 6 || PLATFORM_ID == 10 53 | -------------------------------------------------------------------------------- /examples/sawtooth/sawtooth.ino: -------------------------------------------------------------------------------- 1 | /* Generate audio with your Photon or Electron 2 | * 3 | * Speaker library by Julien Vanier 4 | * 5 | */ 6 | #include "speaker.h" 7 | 8 | SYSTEM_THREAD(ENABLED); 9 | 10 | /* Configure audio output */ 11 | uint16_t bufferSize = 128; 12 | Speaker speaker(bufferSize); 13 | 14 | uint16_t audioFrequency = 22050; // Hz 15 | 16 | /* Configure signal parameters */ 17 | uint16_t amplitude = 50000; // signal goes from 0 to 65535 18 | uint16_t frequency = 1000; // Hz 19 | uint32_t audioSignal = 0; // current value of the audio signal 20 | 21 | /* Generate a sawtooth signal into the Speaker buffer. 22 | * A sawtooth goes up until a limit, then goes back to 0. 23 | */ 24 | void generateSawtooth(uint16_t *buffer) 25 | { 26 | uint32_t increment = ((uint32_t) frequency * amplitude) / audioFrequency; 27 | 28 | for (uint16_t i = 0; i < bufferSize; i++) { 29 | buffer[i] = audioSignal; 30 | audioSignal += increment; 31 | if (audioSignal > amplitude) { 32 | audioSignal -= amplitude; 33 | } 34 | } 35 | } 36 | 37 | /* Particle function to change the volume of the sawtooth signal by 38 | * changing the maximum value. 39 | */ 40 | int changeAmplitude(String arg) 41 | { 42 | long num = arg.toInt(); 43 | if (num >= 0 && num < 65535) 44 | { 45 | amplitude = (uint16_t) num; 46 | if (audioSignal > amplitude) { 47 | audioSignal = 0; 48 | } 49 | } 50 | return amplitude; 51 | } 52 | 53 | /* Particle function to change the sawtooth frequency */ 54 | int changeFrequency(String arg) 55 | { 56 | long num = arg.toInt(); 57 | if (num > 0 && num < audioFrequency) 58 | { 59 | frequency = (uint16_t) num; 60 | } 61 | return frequency; 62 | } 63 | 64 | /* Set up the speaker */ 65 | void setup() { 66 | Particle.function("amp", changeAmplitude); 67 | Particle.function("freq", changeFrequency); 68 | 69 | // Write initial data in the audio buffer. Not mandatory, but if you 70 | // don't do this you'll start with 1 buffer of silence. 71 | generateSawtooth(speaker.getBuffer()); 72 | 73 | // Start the audio output 74 | speaker.begin(audioFrequency); 75 | } 76 | 77 | /* Generate more audio data when needed */ 78 | void loop() { 79 | // Call ready as often as possible to avoid the speaker playing the same buffer multiple times. 80 | if (speaker.ready()) { 81 | // Write new audio into the buffer returned by getBuffer 82 | generateSawtooth(speaker.getBuffer()); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /tools/raw2uint16.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | File converter 6 | 7 | 8 | 9 |
10 | 11 |

Raw Signed Int16 PCM to uint16_t array converter

12 |
13 |

14 | File: 15 | 16 |

17 |

Options:

18 |

19 | Prefix data with variable definition 20 |
21 | Samples prefix 22 |
23 | Samples suffix 24 |
25 | Insert newlines after every samples 26 |

27 |

28 | 32 |

33 |
34 |

Output:

35 |

36 | 37 |

38 |
39 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Speaker 2 | [![Build Status](https://travis-ci.org/monkbroc/particle-speaker.svg?branch=master)](https://travis-ci.org/monkbroc/particle-speaker) 3 | 4 | Generate audio output for a speaker for Particle devices (Photon, Electron) 5 | 6 | ## Usage 7 | 8 | Connect a speaker amplifier like [this one from Adafruit](https://www.adafruit.com/product/2130) to the digital to analog converter `DAC` pin of a Photon or Electron and run this code to play a sawtooth wave. 9 | 10 | ``` 11 | #include "speaker.h" 12 | 13 | uint16_t bufferSize = 128; 14 | Speaker speaker(bufferSize); 15 | 16 | void setup() { 17 | uint16_t audioFrequency = 22050; // Hz 18 | speaker.begin(audioFrequency); 19 | } 20 | 21 | uint16_t audioSignal = 0; 22 | 23 | void loop() { 24 | if (speaker.ready()) { 25 | uint16_t *buffer = speaker.getBuffer(); 26 | // Produces a 1 kHz sawtooth wave 27 | for (uint16_t i = 0; i < bufferSize; i++) { 28 | buffer[i] = audioSignal; 29 | audioSignal += 2267; 30 | if (audioSignal > 50000) { 31 | audioSignal = 0; 32 | } 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | ![1 kHz sawtooth signal](docs/speaker%201kHz%20sawtooth%20signal.png)
39 | A 1 kHz sawtooth signal played from a Photon 40 | 41 | See [complete example](examples/sawtooth/sawtooth.ino) in the examples directory. 42 | 43 | Currently the output is mono. Since the Photon and Electron have 2 DAC outputs, the library could be extended to support stereo output. 44 | 45 | ## Documentation 46 | 47 | ### `Speaker` 48 | 49 | `Speaker speaker(bufferSize);` 50 | 51 | Creates a speaker object with 2 buffers of the same size (double buffering). 52 | 53 | While the library plays the sound in one buffer your application fills the second one. 54 | 55 | The larger the buffer, the more delay there will be in between your application filling a buffer and it being played. The shorter the buffer, the less time your code has to fill the next buffer. Short buffers work best for real-time audio synthesis and longer buffer for playback from an SD card. 56 | 57 | The application has `bufferSize / audioFrequency` seconds to fill the next buffer. For example, this is 2.9 ms at 44100 Hz with a 128 sample buffer. 58 | 59 | The copy from memory to the DAC is done using direct memory access (DMA) so the CPU is free to do other tasks. 60 | 61 | ### `begin` 62 | 63 | `speaker.begin(audioFrequency);` 64 | 65 | Sets up the `DAC` pin and `TIM6` timer to trigger at the correct audio freqency. Common frequencies are 44100 Hz, 22050 Hz, 11025 Hz and 8000 Hz. 66 | 67 | Starts playing the content of the buffer immediately so you may want to fill the audio buffer before calling `speaker.begin`. The buffer is zero by default so not filling the buffer first would still be OK. 68 | 69 | _Note: Do not call `analogWrite(DAC, ...);` when using this library since it completely takes over the DAC peripheral.`_ 70 | 71 | ### `end` 72 | 73 | `speaker.end();` 74 | 75 | Stops the audio playback. 76 | 77 | ### `ready` 78 | 79 | `bool readyForMoreAudio = speaker.ready();` 80 | 81 | Returns `true` once when the audio buffer is ready to be filled with more audio samples. Will return `false` when called again until the buffer has finished playing. 82 | 83 | ### `getBuffer` 84 | 85 | `uint16_t *buffer = speaker.getBuffer();` 86 | 87 | Returns a pointer to an array of `bufferSize` audio samples. 88 | 89 | The audio samples are 16 bit integers but the DAC on the Photon and Electron only has 12 bits to the least significant 4 bits are ignored. 90 | 91 | You must only write to this array when `speaker.ready()` is `true`. 92 | 93 | ## Resource Utilization 94 | 95 | This library uses the `DAC1` digital to analog converter, `TIM6` basic timer and `DMA1` stream 5 direct memory access. 96 | 97 | ## References 98 | 99 | Read the [STM Application note AN3126 - Audio and waveform generation using the DAC in STM32 microcontrollers](http://www.st.com/content/ccc/resource/technical/document/application_note/05/fb/41/91/39/02/4d/1e/CD00259245.pdf/files/CD00259245.pdf/jcr:content/translations/en.CD00259245.pdf) for more background on using the DAC and DMA for audio generation. 100 | 101 | ## License 102 | Copyright 2016 Julien Vanier 103 | 104 | Released under the MIT license 105 | -------------------------------------------------------------------------------- /src/speaker.cpp: -------------------------------------------------------------------------------- 1 | // Speaker library by Julien Vanier 2 | // Adaptions by ScruffR 3 | // - make double buffering optinal 4 | // - option to provide "external" buffer(s) to the library 5 | // which allows to play PCM data stored in flash without copying 6 | 7 | #include "speaker.h" 8 | 9 | #include "stm32f2xx.h" 10 | #include 11 | 12 | // The DAC symbol from the STM32 standard library is shadowed by the DAC Particle pin name 13 | #define _DAC ((DAC_TypeDef *) DAC_BASE) 14 | 15 | Speaker::Speaker(uint16_t bufferSize, bool dblBuffer) 16 | : bufferSize(bufferSize), 17 | lastBuffer(0xFF), 18 | audioFrequency(8000), 19 | privateBuffer(true) 20 | { 21 | numBuffers = dblBuffer ? 2 : 1; 22 | for (int i = 0; i < numBuffers; i++) { 23 | buffer[i] = new uint16_t[bufferSize]; 24 | std::fill(buffer[i], &buffer[i][bufferSize], 0); 25 | } 26 | if (!dblBuffer) 27 | buffer[1] = buffer[0]; 28 | } 29 | 30 | Speaker::Speaker(uint16_t *buffer, uint16_t bufferSize) 31 | : Speaker(buffer, buffer, bufferSize) 32 | { } 33 | 34 | Speaker::Speaker(uint16_t *buffer0, uint16_t *buffer1, uint16_t bufferSize) 35 | : bufferSize(bufferSize) , 36 | lastBuffer(0xFF), 37 | audioFrequency(8000), 38 | privateBuffer(false) 39 | { 40 | numBuffers = (buffer0 == buffer1) ? 1 : 2; 41 | buffer[0] = buffer0; 42 | buffer[1] = buffer1; 43 | } 44 | 45 | Speaker::~Speaker() 46 | { 47 | if (privateBuffer) { 48 | for (int i = 0; i < numBuffers; i++) { 49 | delete buffer[i]; 50 | } 51 | } 52 | } 53 | 54 | void Speaker::begin(uint16_t audioFrequency) 55 | { 56 | this->audioFrequency = audioFrequency; 57 | setupHW(audioFrequency); 58 | } 59 | 60 | void Speaker::setupHW(uint16_t audioFrequency) 61 | { 62 | /* TIM6 and DAC clocks enable */ 63 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6 | RCC_APB1Periph_DAC, ENABLE); 64 | /* DMA clock enable */ 65 | RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); 66 | 67 | /* DMA1 Stream5 channel7 configuration */ 68 | DMA_DeInit(DMA1_Stream5); 69 | 70 | DMA_InitTypeDef DMA_InitStructure; 71 | DMA_StructInit(&DMA_InitStructure); 72 | DMA_InitStructure.DMA_BufferSize = bufferSize; 73 | DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &_DAC->DHR12L1; 74 | DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer[0]; 75 | DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; 76 | DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 77 | DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 78 | DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 79 | DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; 80 | DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 81 | DMA_InitStructure.DMA_Priority = DMA_Priority_High; 82 | DMA_InitStructure.DMA_Channel = DMA_Channel_7; // Stream5 Channel7 = DAC1 83 | DMA_Init(DMA1_Stream5, &DMA_InitStructure); 84 | 85 | /* Configure DMA1 Stream5 double buffering */ 86 | DMA_DoubleBufferModeConfig(DMA1_Stream5, (uint32_t) buffer[1], DMA_Memory_1); 87 | DMA_DoubleBufferModeCmd(DMA1_Stream5, ENABLE); 88 | 89 | /* Enable DMA1 Stream5 */ 90 | DMA_Cmd(DMA1_Stream5, ENABLE); 91 | 92 | /* DAC channel1 Configuration */ 93 | DAC_DeInit(); 94 | 95 | DAC_InitTypeDef DAC_InitStructure; 96 | DAC_StructInit(&DAC_InitStructure); 97 | 98 | DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO; 99 | DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; 100 | DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable; 101 | 102 | DAC_Init(DAC_Channel_1, &DAC_InitStructure); 103 | 104 | /* Enable DAC Channel1 */ 105 | DAC_Cmd(DAC_Channel_1, ENABLE); 106 | 107 | /* Enable DMA for DAC Channel1 */ 108 | DAC_DMACmd(DAC_Channel_1, ENABLE); 109 | 110 | /* TIM6 Configuration */ 111 | TIM_DeInit(TIM6); 112 | 113 | TIM_SetAutoreload(TIM6, timerAutoReloadValue(audioFrequency)); 114 | 115 | /* TIM6 TRGO selection */ 116 | TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update); 117 | /* Enable TIM6 update interrupt */ 118 | TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); 119 | 120 | /* Start TIM6 */ 121 | TIM_Cmd(TIM6, ENABLE); 122 | } 123 | 124 | uint16_t Speaker::timerAutoReloadValue(uint16_t audioFrequency) 125 | { 126 | return SystemCoreClock / 2 / audioFrequency; 127 | } 128 | 129 | void Speaker::end() 130 | { 131 | /* Disable TIM6 update interrupt */ 132 | TIM_ITConfig(TIM6, TIM_IT_Update, DISABLE); 133 | /* Disable TIM6 */ 134 | TIM_Cmd(TIM6, DISABLE); 135 | } 136 | 137 | uint8_t Speaker::currentBuffer() 138 | { 139 | return (uint8_t) DMA_GetCurrentMemoryTarget(DMA1_Stream5); 140 | } 141 | 142 | bool Speaker::ready() 143 | { 144 | uint8_t cur = currentBuffer(); 145 | if (cur != lastBuffer) 146 | { 147 | lastBuffer = cur; 148 | return true; 149 | } 150 | else 151 | { 152 | return false; 153 | } 154 | } 155 | 156 | uint16_t *Speaker::getBuffer() 157 | { 158 | /* Return alternate buffer for DMA1 Stream5 double buffering */ 159 | return currentBuffer() ? buffer[0] : buffer[1]; 160 | } 161 | -------------------------------------------------------------------------------- /examples/playWavFromSD/playWavFromSD.ino: -------------------------------------------------------------------------------- 1 | // This #include statement was automatically added by the Particle IDE. 2 | #include 3 | 4 | // Generate audio with your Photon or Electron 5 | // 6 | // Speaker library by Julien Vanier 7 | // 8 | // Example: playWavFromSD by ScruffR 9 | // requires manual import of SdFat library 10 | // via Particle.function "playWav" the file name of any WAV in the root directory of 11 | // the SD card can be selected to play 12 | // Sending "ls" or "dir" will list the file names in root via USB Serial 13 | // 14 | 15 | //SYSTEM_THREAD(ENABLED); 16 | 17 | #include 18 | //#include // will be auto-included when the library gets imported 19 | #ifndef SdFat_h 20 | # error "library SdFat needs to imported and/or include statement above may need uncommenting" 21 | #endif 22 | 23 | SerialLogHandler logHandler(LOG_LEVEL_NONE, { // Logging level for non-app messages 24 | { "app", LOG_LEVEL_INFO } // Logging level for application messages 25 | }); 26 | 27 | // WAV header spec information: 28 | // https://web.archive.org/web/20140327141505/ 29 | // https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ 30 | // http://www.topherlee.com/software/pcm-tut-wavformat.html 31 | typedef struct wav_header { 32 | // RIFF Header 33 | char riff_header[4]; // Contains "RIFF" 34 | uint32_t wav_size; // Size of the wav portion of the file, which follows the first 8 bytes. File size - 8 35 | char wave_header[4]; // Contains "WAVE" 36 | 37 | // Format Header 38 | char fmt_header[4]; // Contains "fmt " (includes trailing space) 39 | uint32_t fmt_chunk_size; // Should be 16 for PCM 40 | uint16_t audio_format; // Should be 1 for PCM. 3 for IEEE Float 41 | uint16_t num_channels; 42 | uint32_t sample_rate; 43 | uint32_t byte_rate; // Number of bytes per second. sample_rate * num_channels * Bytes Per Sample 44 | uint16_t sample_alignment; // num_channels * Bytes Per Sample 45 | uint16_t bit_depth; // Number of bits per sample 46 | 47 | // Data 48 | char data_header[4]; // Contains "data" 49 | uint32_t data_bytes; // Number of bytes in data. Number of samples * num_channels * sample byte size 50 | // uint8_t bytes[]; // Remainder of wave file is bytes 51 | } WavHeader_t; 52 | 53 | const int SD_SS = A2; 54 | const size_t BUFFERSIZE = 1024; 55 | uint16_t data[2][BUFFERSIZE]; 56 | 57 | SdFat sd; 58 | File wavFile; 59 | WavHeader_t wh; 60 | 61 | Speaker speaker(data[0], data[1], BUFFERSIZE); 62 | 63 | void setup() { 64 | Particle.function("playWav", selectFile); 65 | 66 | if (sd.begin(SD_SS)) 67 | Log.info("SD initialised"); 68 | else 69 | Log.warn("failed to open card"); 70 | } 71 | 72 | void loop() { 73 | if (speaker.ready()) { 74 | readChunk(); 75 | } 76 | } 77 | 78 | int selectFile(const char* filename) { 79 | int retVal = 0; 80 | 81 | if (!strcmp("ls", filename) || !strcmp("dir", filename)) { 82 | sd.ls("/", LS_R); 83 | return 0; 84 | } 85 | 86 | if (wavFile.isOpen()) wavFile.close(); 87 | 88 | wavFile = sd.open(filename); 89 | if (wavFile) { 90 | memset((uint8_t*)data, 0x80, sizeof(data)); // reset buffer to bias value 0x8080 (quicker via memset() than writing 0x8000 in a loop) 91 | if (sizeof(wh) == wavFile.read((uint8_t*)&wh, sizeof(wh))) { 92 | Log.printf("%s\n\r\t%.4s\r\n\tsize: %lu\r\n\t%.4s\r\n\t%.4s\r\n\tchunk size (16?): %lu\r\n\taudio format (1?): %u\r\n\tchannels: %u" 93 | , filename 94 | , wh.riff_header 95 | , wh.wav_size 96 | , wh.wave_header 97 | , wh.fmt_header 98 | , wh.fmt_chunk_size 99 | , wh.audio_format 100 | , wh.num_channels 101 | ); 102 | // two chunks since Log only supports limited length output 103 | Log.printf("\r\n\tsample rate: %lu\r\n\tbyte rate: %lu\r\n\tsample alignment: %u\r\n\tbit depth: %u\r\n\t%.4s\r\n\tdata bytes: %u" 104 | , wh.sample_rate 105 | , wh.byte_rate 106 | , wh.sample_alignment 107 | , wh.bit_depth 108 | , wh.data_header 109 | , wh.data_bytes 110 | ); 111 | retVal = wh.data_bytes; 112 | readChunk(); 113 | speaker.begin(wh.sample_rate); 114 | } 115 | } 116 | else { 117 | Log.error("%s not found", filename); 118 | } 119 | 120 | return retVal; 121 | } 122 | 123 | int readChunk() { 124 | int retVal = 0; 125 | if (wavFile.isOpen()) { 126 | uint16_t* wav = speaker.getBuffer(); 127 | uint8_t buf[BUFFERSIZE * 2 * wh.num_channels]; 128 | int n = wavFile.read(buf, sizeof(buf)); 129 | if (n < sizeof(buf)) wavFile.close(); // when all data is read close the file 130 | 131 | memset((uint8_t*)wav, 0x80, BUFFERSIZE); // reset buffer to bias value 0x8080 132 | for(int b = 0; b < n; b += wh.sample_alignment) { 133 | wav[retVal++] = *(uint16_t*)&buf[b] + 32768; // convert int16_t to uin16_t with bias 0x8000 134 | } 135 | } 136 | else 137 | speaker.end(); 138 | 139 | return retVal; 140 | } 141 | --------------------------------------------------------------------------------