├── .gitignore ├── LICENSE ├── README.md ├── data ├── T0.wav ├── T1.wav ├── T2.wav ├── T3.wav ├── T4.wav ├── T5.wav ├── T6.wav ├── T7.wav ├── T8.wav └── T9.wav ├── espi2s.ino ├── images └── sfx-i2s.png ├── wavspiffs.cpp └── wavspiffs.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 by bbx10node@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SFX-I2S-web-trigger 2 | ESP8266 Arduino Sound F/X I2S web trigger 3 | 4 | ![ESP8266, I2S DAC, speaker, battery](/images/sfx-i2s.png) 5 | 6 | Press the 0 button to play file T0.wav, press the 1 button to play file T1.wav, 7 | etc. The web interface is identical the related project 8 | [SFX-web-trigger](https://github.com/bbx10/SFX-web-trigger). The other project 9 | uses a much more expensive MP3/OGG decoder board. This project uses a $6 I2S 10 | DAC with 3W audio amplifier board. The WAV audio files are stored in the ESP8266 11 | Flash. 12 | 13 | Most ESP-12s boards and modules come with 4 Mbytes of Flash of which 3 MBbytes is used as a Flash 14 | file system named SPIFFS. To store the WAV files in the SPIFFS file system, 15 | create a data directory in the same directory with the .INO file. Copy WAV 16 | files to the directory with names T0.wav, T1.wav, ... T9.wav. Install the 17 | [SPIFFS upload tool](http://www.esp8266.com/viewtopic.php?f=32&t=10081). Use 18 | the tool to upload the WAV files to the ESP8266 Flash. 19 | 20 | The WAV files should be mono (number of channels = 1) and contain uncompressed 21 | 16-bit PCM audio. Samples rates 11025, 22050, and 44100 have been verified to 22 | work. The included test WAV files were generated using Festival (text to 23 | speech) software. 24 | 25 | Upload the ESPI2S.INO application. Connect any web browser to the ESP8266 web 26 | server at http://espsfxtrigger.local. If this does not work use the ESP8266 27 | IP address. This should bring up the web page shown above. Press buttons 28 | 0...9 to play WAV files T0.wav ... T9.wav. 29 | 30 | If the ESP8266 is unable to connect to an Access Point, it becomes an Access 31 | Point. Connect to it by going into your wireless settings. The AP name should 32 | look like ESP\_SFX\_TRIGGER. Once connected the ESP will bring up a web page 33 | where you can enter the SSID and password of your WiFi router. 34 | 35 | How might this device be used? Load up your favorite sound samples such as 36 | elephant trumpet, minion laugh, cat meow, etc. Bury the device in the couch 37 | cushions. Wait for your victim to sit down. While you appear to play Candy 38 | Crush on your phone, remotely trigger your favorite sound F/X! 39 | 40 | ## Hardware components 41 | 42 | * [Adafruit I2S 3W Class D Amplifier Breakout - MAX98357A](https://www.adafruit.com/products/3006) 43 | This board converts the digital audio data from the ESP8266 I2S controller to 44 | analog audio and amplifies the signal to drive a speaker. This breakout board is 45 | mono instead of stereo but it is only $6! 46 | 47 | * [Adafruit Feather Huzzah with ESP8266](https://www.adafruit.com/products/2821) 48 | This ESP8266 board was chosen because it has a lithium battery charger. Other 49 | ESP8266 boards should also work. 50 | 51 | * 3 Watt, 4 Ohm speaker 52 | 53 | * Lithium battery 54 | 55 | ## Connection Diagram 56 | 57 | Adafruit I2S DAC |ESP8266 | Description 58 | -----------------|-------------------|------------- 59 | LRC |GPIO2/TX1 LRCK | Left/Right audio 60 | BCLK |GPIO15 BCLK | I2S Clock 61 | DIN |GPIO03/RX0 DATA | I2S Data 62 | GAIN |not connected | 9 dB gain 63 | SD |not connected | Stereo average 64 | GND |GND | Ground 65 | Vin |BAT | 3.7V battery power 66 | 67 | If you need more volume you could use a booster to generate 5V from the battery 68 | and drive the DAC board Vin with 5V. Also experiment with the GAIN pin. 69 | 70 | ## Software components 71 | 72 | * [WiFiManager](https://github.com/tzapu/WiFiManager) eliminates 73 | the need to store the WiFi SSID and password in the source code. WiFiManager 74 | will switch the ESP8266 to an access point and web server to get the SSID and 75 | password when needed. 76 | 77 | * [SPIFFS](http://esp8266.github.io/Arduino/versions/2.3.0/doc/filesystem.html) 78 | The SPIFFS Flash file system is included with the ESP8266 Arduino board support 79 | package. The header file name is FS.h. 80 | 81 | * ESP8266 I2S support is included with the ESP8266 Arduino board support package. 82 | The header file names are i2s.h and i2s\_reg.h. 83 | 84 | * [WebSockets](https://github.com/Links2004/arduinoWebSockets) 85 | 86 | * Other components are included with the ESP8266 Arduino package. 87 | -------------------------------------------------------------------------------- /data/T0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T0.wav -------------------------------------------------------------------------------- /data/T1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T1.wav -------------------------------------------------------------------------------- /data/T2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T2.wav -------------------------------------------------------------------------------- /data/T3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T3.wav -------------------------------------------------------------------------------- /data/T4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T4.wav -------------------------------------------------------------------------------- /data/T5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T5.wav -------------------------------------------------------------------------------- /data/T6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T6.wav -------------------------------------------------------------------------------- /data/T7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T7.wav -------------------------------------------------------------------------------- /data/T8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T8.wav -------------------------------------------------------------------------------- /data/T9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/data/T9.wav -------------------------------------------------------------------------------- /espi2s.ino: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 by bbx10node@gmail.com 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | **************************************************************************/ 24 | #include 25 | #include 26 | #include 27 | #include // https://github.com/Links2004/arduinoWebSockets 28 | #include 29 | 30 | #include 31 | #include 32 | #include "wavspiffs.h" 33 | 34 | // Non-blocking I2S write for left and right 16-bit PCM 35 | bool ICACHE_FLASH_ATTR i2s_write_lr_nb(int16_t left, int16_t right){ 36 | int sample = right & 0xFFFF; 37 | sample = sample << 16; 38 | sample |= left & 0xFFFF; 39 | return i2s_write_sample_nb(sample); 40 | } 41 | 42 | struct I2S_status_s { 43 | wavFILE_t wf; 44 | int16_t buffer[512]; 45 | int bufferlen; 46 | int buffer_index; 47 | int playing; 48 | } I2S_WAV; 49 | 50 | void wav_stopPlaying() 51 | { 52 | i2s_end(); 53 | I2S_WAV.playing = false; 54 | wavClose(&I2S_WAV.wf); 55 | } 56 | 57 | bool wav_playing() 58 | { 59 | return I2S_WAV.playing; 60 | } 61 | 62 | void wav_setup() 63 | { 64 | Serial.println(F("wav_setup")); 65 | I2S_WAV.bufferlen = -1; 66 | I2S_WAV.buffer_index = 0; 67 | I2S_WAV.playing = false; 68 | } 69 | 70 | void wav_loop() 71 | { 72 | bool i2s_full = false; 73 | int rc; 74 | 75 | while (I2S_WAV.playing && !i2s_full) { 76 | while (I2S_WAV.buffer_index < I2S_WAV.bufferlen) { 77 | int16_t pcm = I2S_WAV.buffer[I2S_WAV.buffer_index]; 78 | if (i2s_write_lr_nb(pcm, pcm)) { 79 | I2S_WAV.buffer_index++; 80 | } 81 | else { 82 | i2s_full = true; 83 | break; 84 | } 85 | if ((I2S_WAV.buffer_index & 0x3F) == 0) yield(); 86 | } 87 | if (i2s_full) break; 88 | 89 | rc = wavRead(&I2S_WAV.wf, I2S_WAV.buffer, sizeof(I2S_WAV.buffer)); 90 | if (rc > 0) { 91 | //Serial.printf("wavRead %d\r\n", rc); 92 | I2S_WAV.bufferlen = rc / sizeof(I2S_WAV.buffer[0]); 93 | I2S_WAV.buffer_index = 0; 94 | } 95 | else { 96 | Serial.println(F("Stop playing")); 97 | wav_stopPlaying(); 98 | break; 99 | } 100 | } 101 | } 102 | 103 | void wav_startPlayingFile(const char *wavfilename) 104 | { 105 | wavProperties_t wProps; 106 | int rc; 107 | 108 | Serial.printf("wav_starPlayingFile(%s)\r\n", wavfilename); 109 | i2s_begin(); 110 | rc = wavOpen(wavfilename, &I2S_WAV.wf, &wProps); 111 | Serial.printf("wavOpen %d\r\n", rc); 112 | if (rc != 0) { 113 | Serial.println("wavOpen failed"); 114 | return; 115 | } 116 | Serial.printf("audioFormat %d\r\n", wProps.audioFormat); 117 | Serial.printf("numChannels %d\r\n", wProps.numChannels); 118 | Serial.printf("sampleRate %d\r\n", wProps.sampleRate); 119 | Serial.printf("byteRate %d\r\n", wProps.byteRate); 120 | Serial.printf("blockAlign %d\r\n", wProps.blockAlign); 121 | Serial.printf("bitsPerSample %d\r\n", wProps.bitsPerSample); 122 | 123 | i2s_set_rate(wProps.sampleRate); 124 | 125 | I2S_WAV.bufferlen = -1; 126 | I2S_WAV.buffer_index = 0; 127 | I2S_WAV.playing = true; 128 | wav_loop(); 129 | } 130 | 131 | /**************************************************************************************/ 132 | 133 | /* 134 | * index.html 135 | */ 136 | // This string holds HTML, CSS, and Javascript for the HTML5 UI. 137 | // The browser must support HTML5 WebSockets which is true for all modern browsers. 138 | static const char PROGMEM INDEX_HTML[] = R"rawliteral( 139 | 140 | 141 | 142 | 143 | 144 | ESP8266 Sound Effects Web Trigger 145 | 153 | 249 | 250 | 251 |

ESP SF/X Web Trigger

252 |
Now Playing
253 | 254 | 255 | 257 | 258 | 259 | 260 | 262 | 263 | 264 | 265 | 267 | 268 | 269 | 270 | 272 | 273 | 274 | 275 | 277 | 278 | 279 | 280 | 282 | 283 | 284 | 285 | 287 | 288 | 289 | 290 | 292 | 293 | 294 | 295 | 297 | 298 | 299 | 300 | 302 | 303 | 304 |
Track 0
Track 1
Track 2
Track 3
Track 4
Track 5
Track 6
Track 7
Track 8
Track 9
305 |

306 | 307 | 308 | )rawliteral"; 309 | 310 | /* 311 | * Web server and websocket server 312 | */ 313 | ESP8266WebServer server(80); 314 | WebSocketsServer webSocket = WebSocketsServer(81); 315 | 316 | void startPlaying(const char *filename) 317 | { 318 | char nowPlaying[80] = "nowPlaying="; 319 | 320 | wav_startPlayingFile(filename); 321 | strncat(nowPlaying, filename, sizeof(nowPlaying)-strlen(nowPlaying)-1); 322 | webSocket.broadcastTXT(nowPlaying); 323 | } 324 | 325 | void update_browser() { 326 | if (!wav_playing()) { 327 | webSocket.broadcastTXT("nowPlaying="); 328 | } 329 | } 330 | 331 | void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { 332 | 333 | switch(type) { 334 | case WStype_DISCONNECTED: 335 | Serial.printf("[%u] Disconnected!\r\n", num); 336 | break; 337 | case WStype_CONNECTED: 338 | { 339 | IPAddress ip = webSocket.remoteIP(num); 340 | Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\r\n", num, ip[0], ip[1], ip[2], ip[3], payload); 341 | 342 | // send message to client 343 | webSocket.sendTXT(num, "Connected"); 344 | } 345 | break; 346 | case WStype_TEXT: 347 | Serial.printf("[%u] get Text: %s\r\n", num, payload); 348 | 349 | // Looks for button press "bSFXn=1" messages where n='0'..'9' 350 | if ((length == 7) && 351 | (memcmp((const char *)payload, "bSFX", 4) == 0) && 352 | (payload[6] == '1')) { 353 | switch (payload[4]) { 354 | case '0': 355 | startPlaying("/T0.wav"); 356 | break; 357 | case '1': 358 | startPlaying("/T1.wav"); 359 | break; 360 | case '2': 361 | startPlaying("/T2.wav"); 362 | break; 363 | case '3': 364 | startPlaying("/T3.wav"); 365 | break; 366 | case '4': 367 | startPlaying("/T4.wav"); 368 | break; 369 | case '5': 370 | startPlaying("/T5.wav"); 371 | break; 372 | case '6': 373 | startPlaying("/T6.wav"); 374 | break; 375 | case '7': 376 | startPlaying("/T7.wav"); 377 | break; 378 | case '8': 379 | startPlaying("/T8.wav"); 380 | break; 381 | case '9': 382 | startPlaying("/T9.wav"); 383 | break; 384 | } 385 | } 386 | else { 387 | Serial.printf("Unknown message from client [%s]\r\n", payload); 388 | } 389 | 390 | // send message to client 391 | // webSocket.sendTXT(num, "message here"); 392 | 393 | // send data to all connected clients 394 | // webSocket.broadcastTXT("message here"); 395 | break; 396 | case WStype_BIN: 397 | Serial.printf("[%u] get binary length: %u\r\n", num, length); 398 | hexdump(payload, length); 399 | 400 | // send message to client 401 | // webSocket.sendBIN(num, payload, length); 402 | break; 403 | } 404 | } 405 | 406 | void webserver_setup(void) 407 | { 408 | webSocket.begin(); 409 | webSocket.onEvent(webSocketEvent); 410 | Serial.println("websocket server started"); 411 | 412 | if(MDNS.begin("espsfxtrigger")) { 413 | Serial.println("MDNS responder started. Connect to http://espsfxtrigger.local/"); 414 | } 415 | else { 416 | Serial.println("MDNS responder failed"); 417 | } 418 | 419 | // handle "/" 420 | server.on("/", []() { 421 | server.send_P(200, "text/html", INDEX_HTML); 422 | }); 423 | 424 | server.begin(); 425 | Serial.println("web server started"); 426 | 427 | // Add service to MDNS 428 | MDNS.addService("http", "tcp", 80); 429 | MDNS.addService("ws", "tcp", 81); 430 | } 431 | 432 | inline void webserver_loop(void) { 433 | webSocket.loop(); 434 | server.handleClient(); 435 | update_browser(); 436 | } 437 | 438 | /**************************************************************************************/ 439 | 440 | /* 441 | * WiFiManager (https://github.com/tzapu/WiFiManager) 442 | */ 443 | #include 444 | #include 445 | #include // https://github.com/tzapu/WiFiManager 446 | 447 | void wifiman_setup(void) 448 | { 449 | //WiFiManager 450 | //Local intialization. Once its business is done, there is no need to keep it around 451 | WiFiManager wifiManager; 452 | //reset saved settings SSID and password 453 | //wifiManager.resetSettings(); 454 | 455 | //fetches ssid and pass from eeprom and tries to connect 456 | //if it does not connect it starts an access point with the specified name 457 | //here "AutoConnectAP" 458 | //and goes into a blocking loop awaiting configuration 459 | wifiManager.autoConnect("ESP_SFX_Trigger"); 460 | 461 | //if you get here you have connected to the WiFi 462 | Serial.println(F("connected")); 463 | } 464 | 465 | void showDir(void) 466 | { 467 | wavFILE_t wFile; 468 | wavProperties_t wProps; 469 | int rc; 470 | 471 | Dir dir = SPIFFS.openDir("/"); 472 | while (dir.next()) { 473 | Serial.println(dir.fileName()); 474 | rc = wavOpen(dir.fileName().c_str(), &wFile, &wProps); 475 | if (rc == 0) { 476 | Serial.printf(" audioFormat %d\r\n", wProps.audioFormat); 477 | Serial.printf(" numChannels %d\r\n", wProps.numChannels); 478 | Serial.printf(" sampleRate %d\r\n", wProps.sampleRate); 479 | Serial.printf(" byteRate %d\r\n", wProps.byteRate); 480 | Serial.printf(" blockAlign %d\r\n", wProps.blockAlign); 481 | Serial.printf(" bitsPerSample %d\r\n", wProps.bitsPerSample); 482 | Serial.println(); 483 | wavClose(&wFile); 484 | } 485 | } 486 | } 487 | 488 | void setup() 489 | { 490 | Serial.begin(115200); Serial.println(); 491 | Serial.println(F("\nESP8266 Sound Effects Web Trigger")); 492 | 493 | if (!SPIFFS.begin()) { 494 | Serial.println("SPIFFS.begin() failed"); 495 | return; 496 | } 497 | // Confirm track files are present in SPIFFS 498 | showDir(); 499 | 500 | wifiman_setup(); 501 | wav_setup(); 502 | webserver_setup(); 503 | } 504 | 505 | void loop() 506 | { 507 | wav_loop(); 508 | webserver_loop(); 509 | } 510 | -------------------------------------------------------------------------------- /images/sfx-i2s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbx10/SFX-I2S-web-trigger/b5ddd9eea5ea7ad937fd0ab6e067c186503769f7/images/sfx-i2s.png -------------------------------------------------------------------------------- /wavspiffs.cpp: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 by bbx10node@gmail.com 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | **************************************************************************/ 24 | 25 | /* 26 | * Read WAV/RIFF audio files. Parses header and returns properties. This is 27 | * mostly designed for uncompressed PCM audio. The WAV files must be stored 28 | * in the ESP8266 SPIFFS file system. 29 | */ 30 | 31 | #include "wavspiffs.h" 32 | 33 | #define CCCC(c1, c2, c3, c4) ((c4 << 24) | (c3 << 16) | (c2 << 8) | c1) 34 | 35 | static int readuint32(wavFILE_t *wf, uint32_t *ui32) 36 | { 37 | int rc; 38 | 39 | rc = wf->f.read((uint8_t *)ui32, sizeof(*ui32)); 40 | //Serial.printf("readuint32 rc=%d val=0x%X\r\n", rc, *ui32); 41 | return rc; 42 | } 43 | 44 | int wavOpen(const char *wavname, wavFILE_t *wf, wavProperties_t *wavProps) 45 | { 46 | typedef enum headerState_e { 47 | HEADER_INIT, HEADER_RIFF, HEADER_FMT, HEADER_DATA 48 | } headerState_t; 49 | headerState_t state = HEADER_INIT; 50 | uint32_t chunkID, chunkSize; 51 | int retval; 52 | 53 | wf->f = SPIFFS.open(wavname, "r"); 54 | if (!wf->f) { 55 | retval = -1; 56 | goto closeExit; 57 | } 58 | Serial.println("SPIFFS.open ok"); 59 | 60 | while (state != HEADER_DATA) { 61 | if (readuint32(wf, &chunkID) != 4) { 62 | retval = -2; 63 | goto closeExit; 64 | } 65 | if (readuint32(wf, &chunkSize) != 4) { 66 | retval = -3; 67 | goto closeExit; 68 | } 69 | switch (chunkID) { 70 | case CCCC('R', 'I', 'F', 'F'): 71 | if (readuint32(wf, &chunkID) != 4) { 72 | retval = -4; 73 | goto closeExit; 74 | } 75 | if (chunkID != CCCC('W', 'A', 'V', 'E')) { 76 | retval = -5; 77 | goto closeExit; 78 | } 79 | state = HEADER_RIFF; 80 | Serial.printf("RIFF %d\r\n", chunkSize); 81 | break; 82 | 83 | case CCCC('f', 'm', 't', ' '): 84 | if (wf->f.read((uint8_t *)wavProps, sizeof(*wavProps)) != 85 | sizeof(*wavProps)) { 86 | retval = -6; 87 | goto closeExit; 88 | } 89 | state = HEADER_FMT; 90 | Serial.printf("fmt %d\r\n", chunkSize); 91 | if (chunkSize > sizeof(*wavProps)) { 92 | wf->f.seek(chunkSize - sizeof(*wavProps), SeekCur); 93 | } 94 | break; 95 | 96 | case CCCC('d', 'a', 't', 'a'): 97 | state = HEADER_DATA; 98 | Serial.printf("data %d\r\n", chunkSize); 99 | break; 100 | default: 101 | Serial.printf("%08X %d\r\n", chunkID, chunkSize); 102 | if (!wf->f.seek(chunkSize, SeekCur)) { 103 | retval = -7; 104 | goto closeExit; 105 | } 106 | } 107 | } 108 | if (state == HEADER_DATA) return 0; 109 | retval = -8; 110 | closeExit: 111 | wf->f.close(); 112 | return retval; 113 | } 114 | 115 | int wavRead(wavFILE_t *wf, void *buffer, size_t buflen) 116 | { 117 | return wf->f.read((uint8_t *)buffer, buflen); 118 | } 119 | 120 | int wavClose(wavFILE_t *wf) 121 | { 122 | wf->f.close(); 123 | return 0; 124 | } 125 | -------------------------------------------------------------------------------- /wavspiffs.h: -------------------------------------------------------------------------------- 1 | #ifndef __WAVFILE_H__ 2 | 3 | #include 4 | 5 | typedef struct wavFILE_s { 6 | File f; 7 | } wavFILE_t; 8 | 9 | typedef struct wavProperties_s { 10 | uint16_t audioFormat; 11 | uint16_t numChannels; 12 | uint32_t sampleRate; 13 | uint32_t byteRate; 14 | uint16_t blockAlign; 15 | uint16_t bitsPerSample; 16 | } wavProperties_t; 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | int wavOpen(const char *wavname, wavFILE_t *, wavProperties_t *wavProps); 23 | int wavRead(wavFILE_t *wf, void *buffer, size_t buflen); 24 | int wavClose(wavFILE_t *wf); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif 31 | #define __WAVFILE_H__ 1 32 | --------------------------------------------------------------------------------