├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── dac_i2s_output ├── .gitignore ├── .vscode │ └── extensions.json ├── data │ └── sample.wav ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── DACOutput.cpp │ ├── DACOutput.h │ ├── SampleSource.h │ ├── WAVFileReader.cpp │ ├── WAVFileReader.h │ └── main.cpp └── test │ └── README ├── dac_simple_output ├── .gitignore ├── .vscode │ ├── extensions.json │ └── settings.json ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ └── main.cpp └── test │ └── README ├── i2s_output ├── .gitignore ├── .vscode │ └── extensions.json ├── data │ └── sample.wav ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── I2SOutput.cpp │ ├── I2SOutput.h │ ├── SampleSource.h │ ├── SinWaveGenerator.cpp │ ├── SinWaveGenerator.h │ ├── WAVFileReader.cpp │ ├── WAVFileReader.h │ └── main.cpp └── test │ └── README ├── i2s_sampling ├── .gitignore ├── .travis.yml ├── .vscode │ ├── extensions.json │ └── settings.json ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ ├── ADCSampler.cpp │ ├── ADCSampler.h │ ├── I2SMEMSSampler.cpp │ ├── I2SMEMSSampler.h │ ├── I2SSampler.cpp │ ├── I2SSampler.h │ ├── WiFiCredentials.h │ └── main.cpp └── test │ └── README ├── loop_sampling ├── .gitignore ├── .travis.yml ├── .vscode │ └── extensions.json ├── include │ └── README ├── lib │ └── README ├── platformio.ini ├── src │ └── main.cpp └── test │ └── README ├── server ├── .gitignore ├── .prettierrc ├── javascript │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ ├── tslint.json │ └── yarn.lock └── kotlin │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── resources │ ├── application.conf │ └── logback.xml │ ├── settings.gradle.kts │ └── src │ └── Application.kt └── signal-generator ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── include └── README ├── lib └── README ├── platformio.ini ├── src ├── SignalGenerator.cpp ├── SignalGenerator.h └── main.cpp └── test └── README /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [atomic14] 4 | ko_fi: atomic14 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This code is now pretty old 2 | 3 | It's probably still useful, but I would recomment people check out the fantastic work here: https://github.com/pschatzmann/arduino-audio-tools 4 | 5 | 6 | # ESP32 I2S Audio 7 | 8 | Explanatory video of the analog mic boards here (MAX9814 and MAX4466) [here](https://www.youtube.com/watch?v=pPh3_ciEmzs) 9 | 10 | [![Demo Video](https://img.youtube.com/vi/pPh3_ciEmzs/0.jpg)](https://www.youtube.com/watch?v=pPh3_ciEmzs) 11 | 12 | And for the two I2S boards (SPH0645 and INMP441) [here](https://www.youtube.com/watch?v=3g7l5bm7fZ8) 13 | 14 | [![Demo Video](https://img.youtube.com/vi/3g7l5bm7fZ8/0.jpg)](https://www.youtube.com/watch?v=3g7l5bm7fZ8) 15 | 16 | For audio output we can use the MAX98357A boards - there's a explanatory video [here](https://youtu.be/At8PDQ3g7FQ) 17 | 18 | [![Demo Video](https://img.youtube.com/vi/At8PDQ3g7FQ/0.jpg)](https://www.youtube.com/watch?v=At8PDQ3g7FQ) 19 | 20 | This project demonstrates how to use the ESP32 built-in Analog to Digital Converters and I2S for capturing audio data and for audio output.. 21 | 22 | There are four projects in this repository: `loop_sampling`, `i2s_sampling`, `i2s_output` and `server`. 23 | 24 | ## server 25 | 26 | This is a simple `node` server that writes the samples received from the ESP32 to a file. 27 | 28 | Check the README.md file in the server folder for detailed instructsions. 29 | 30 | You will need to have [node](https://nodejs.org/en/download/) and [yarn](https://classic.yarnpkg.com/en/docs/install/#mac-stable). You may already have these on your system. 31 | 32 | Check with: 33 | 34 | ``` 35 | node --version 36 | yarn --version 37 | ``` 38 | 39 | Then just run: 40 | 41 | ``` 42 | cd server 43 | yarn 44 | ``` 45 | 46 | To install the dependencies. And 47 | 48 | ``` 49 | cd server 50 | yarn start 51 | ``` 52 | When you want to run the server. 53 | 54 | ## loop_sampling 55 | 56 | This project shows how to use the Arduino `analogRead` function and the Espressif `adc1_get_raw` function. 57 | 58 | It also demonstrates how to get a calibrated value back from the ADC to give you the actual voltage at the input. 59 | 60 | ## i2s_sampling 61 | 62 | This project handles both analogue devices (such as the MAX4466 and the MAX9814) and I2S devices (such as the SPH0645 and INMP441). 63 | 64 | This project demonstrates how to use the I2S peripheral for high-speed sampling using DMA to transfer samples directly to RAM. 65 | 66 | We can read these samples from the internal ADC or from the I2S peripheral directly. 67 | 68 | Samples are read from the DMA buffers and sent to a server running on your laptop/desktop machine. 69 | 70 | The current set of pin assignment in the code are: 71 | 72 | ### ADC 73 | 74 | | Function | GPIO Pin | Notes | 75 | | -------------- | -------- | -------------------------- | 76 | | Analogue input | GPIO35 | ADC_UNIT_1, ADC1_CHANNEL_7 | 77 | 78 | ### I2S 79 | 80 | | Function | GPIO Pin | Notes | 81 | | ----------- | ----------- | ------------------------------ | 82 | | bck_io_num | GPIO_NUM_32 | I2S - Serial clock | 83 | | ws_io_num | GPIO_NUM_25 | I2S - LRCLK - left right clock | 84 | | data_in_num | GPIO_NUM_33 | I2S - Serial data | 85 | 86 | ## i2s_output 87 | 88 | This example shows how to drive an I2S output device - I've tested this against the MAX98357 breakout board from Adafruit - https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp 89 | 90 | There is an example WAV file that can be played or you can play a simple sin wave through the output. 91 | 92 | To play the WAV file you will need to download the file to the SPIFFS file system. 93 | 94 | This is now annoyingly hard to find on platform.io - watch this video to see how to find it. 95 | 96 | [![FS Upload](https://img.youtube.com/vi/Pxpg9eZLoBI/0.jpg)](https://www.youtube.com/watch?v=Pxpg9eZLoBI) 97 | 98 | The pins currently configured are: 99 | 100 | | Function | GPIO Pin | Notes | 101 | | ------------ | ----------- | ------------------------------ | 102 | | bck_io_num | GPIO_NUM_27 | I2S - Serial clock | 103 | | ws_io_num | GPIO_NUM_14 | I2S - LRCLK - left right clock | 104 | | data_out_num | GPIO_NUM_26 | I2S - Serial data | 105 | -------------------------------------------------------------------------------- /dac_i2s_output/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /dac_i2s_output/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /dac_i2s_output/data/sample.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomic14/esp32_audio/4a39101ea0083aa12dcd3d838c3e51613ecdf3e3/dac_i2s_output/data/sample.wav -------------------------------------------------------------------------------- /dac_i2s_output/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /dac_i2s_output/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /dac_i2s_output/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32doit-devkit-v1] 12 | platform = espressif32 13 | board = esp32doit-devkit-v1 14 | framework = arduino 15 | monitor_port = /dev/cu.SLAB_USBtoUART 16 | monitor_speed = 115200 17 | upload_port = /dev/cu.SLAB_USBtoUART -------------------------------------------------------------------------------- /dac_i2s_output/src/DACOutput.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "driver/i2s.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #include "SampleSource.h" 9 | #include "DACOutput.h" 10 | 11 | // number of frames to try and send at once (a frame is a left and right sample) 12 | #define NUM_FRAMES_TO_SEND 128 13 | 14 | void i2sWriterTask(void *param) 15 | { 16 | DACOutput *output = (DACOutput *)param; 17 | int availableBytes = 0; 18 | int buffer_position = 0; 19 | Frame_t frames[128]; 20 | while (true) 21 | { 22 | // wait for some data to be requested 23 | i2s_event_t evt; 24 | if (xQueueReceive(output->m_i2sQueue, &evt, portMAX_DELAY) == pdPASS) 25 | { 26 | if (evt.type == I2S_EVENT_TX_DONE) 27 | { 28 | size_t bytesWritten = 0; 29 | do 30 | { 31 | if (availableBytes == 0) 32 | { 33 | // get some frames from the wave file - a frame consists of a 16 bit left and right sample 34 | output->m_sample_generator->getFrames(frames, NUM_FRAMES_TO_SEND); 35 | // how many bytes do we now have to send 36 | availableBytes = NUM_FRAMES_TO_SEND * sizeof(uint32_t); 37 | // reset the buffer position back to the start 38 | buffer_position = 0; 39 | } 40 | // do we have something to write? 41 | if (availableBytes > 0) 42 | { 43 | // write data to the i2s peripheral 44 | i2s_write(I2S_NUM_0, buffer_position + (uint8_t *)frames, 45 | availableBytes, &bytesWritten, portMAX_DELAY); 46 | availableBytes -= bytesWritten; 47 | buffer_position += bytesWritten; 48 | } 49 | } while (bytesWritten > 0); 50 | } 51 | } 52 | } 53 | } 54 | 55 | void DACOutput::start(SampleSource *sample_generator) 56 | { 57 | m_sample_generator = sample_generator; 58 | // i2s config for writing both channels of I2S 59 | i2s_config_t i2sConfig = { 60 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), 61 | .sample_rate = m_sample_generator->sampleRate(), 62 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 63 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 64 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB), 65 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 66 | .dma_buf_count = 4, 67 | .dma_buf_len = 64}; 68 | 69 | //install and start i2s driver 70 | i2s_driver_install(I2S_NUM_0, &i2sConfig, 4, &m_i2sQueue); 71 | // enable the DAC channels 72 | i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); 73 | // clear the DMA buffers 74 | i2s_zero_dma_buffer(I2S_NUM_0); 75 | // start a task to write samples to the i2s peripheral 76 | TaskHandle_t writerTaskHandle; 77 | xTaskCreate(i2sWriterTask, "i2s Writer Task", 4096, this, 1, &writerTaskHandle); 78 | } 79 | -------------------------------------------------------------------------------- /dac_i2s_output/src/DACOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef __sampler_base_h__ 2 | #define __sampler_base_h__ 3 | 4 | #include 5 | #include "driver/i2s.h" 6 | 7 | class SampleSource; 8 | 9 | /** 10 | * Base Class for both the ADC and I2S sampler 11 | **/ 12 | class DACOutput 13 | { 14 | private: 15 | // I2S write task 16 | TaskHandle_t m_i2sWriterTaskHandle; 17 | // i2s writer queue 18 | QueueHandle_t m_i2sQueue; 19 | // src of samples for us to play 20 | SampleSource *m_sample_generator; 21 | 22 | public: 23 | void start(SampleSource *sample_generator); 24 | 25 | friend void i2sWriterTask(void *param); 26 | }; 27 | 28 | #endif -------------------------------------------------------------------------------- /dac_i2s_output/src/SampleSource.h: -------------------------------------------------------------------------------- 1 | #ifndef __sample_source_h__ 2 | #define __sample_source_h__ 3 | 4 | #include 5 | 6 | typedef struct 7 | { 8 | uint16_t left; 9 | uint16_t right; 10 | } Frame_t; 11 | 12 | /** 13 | * Base class for our sample generators 14 | **/ 15 | class SampleSource 16 | { 17 | public: 18 | virtual int sampleRate() = 0; 19 | // This should fill the samples buffer with the specified number of frames 20 | // A frame contains a LEFT and a RIGHT sample. Each sample should be signed 16 bits 21 | virtual void getFrames(Frame_t *frames, int number_frames) = 0; 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /dac_i2s_output/src/WAVFileReader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "WAVFileReader.h" 4 | 5 | #pragma pack(push, 1) 6 | typedef struct 7 | { 8 | // RIFF Header 9 | char riff_header[4]; // Contains "RIFF" 10 | int wav_size; // Size of the wav portion of the file, which follows the first 8 bytes. File size - 8 11 | char wave_header[4]; // Contains "WAVE" 12 | 13 | // Format Header 14 | char fmt_header[4]; // Contains "fmt " (includes trailing space) 15 | int fmt_chunk_size; // Should be 16 for PCM 16 | short audio_format; // Should be 1 for PCM. 3 for IEEE Float 17 | short num_channels; 18 | int sample_rate; 19 | int byte_rate; // Number of bytes per second. sample_rate * num_channels * Bytes Per Sample 20 | short sample_alignment; // num_channels * Bytes Per Sample 21 | short bit_depth; // Number of bits per sample 22 | 23 | // Data 24 | char data_header[4]; // Contains "data" 25 | int data_bytes; // Number of bytes in data. Number of samples * num_channels * sample byte size 26 | // uint8_t bytes[]; // Remainder of wave file is bytes 27 | } wav_header_t; 28 | #pragma pack(pop) 29 | 30 | WAVFileReader::WAVFileReader(const char *file_name) 31 | { 32 | if (!SPIFFS.exists(file_name)) 33 | { 34 | Serial.println("****** Failed to open file! Have you uploaed the file system?"); 35 | return; 36 | } 37 | m_file = SPIFFS.open(file_name, "r"); 38 | // read the WAV header 39 | wav_header_t wav_header; 40 | m_file.read((uint8_t *)&wav_header, sizeof(wav_header_t)); 41 | // sanity check the bit depth 42 | if (wav_header.bit_depth != 16) 43 | { 44 | Serial.printf("ERROR: bit depth %d is not supported\n", wav_header.bit_depth); 45 | } 46 | 47 | Serial.printf("fmt_chunk_size=%d, audio_format=%d, num_channels=%d, sample_rate=%d, sample_alignment=%d, bit_depth=%d, data_bytes=%d\n", 48 | wav_header.fmt_chunk_size, wav_header.audio_format, wav_header.num_channels, wav_header.sample_rate, wav_header.sample_alignment, wav_header.bit_depth, wav_header.data_bytes); 49 | 50 | m_num_channels = wav_header.num_channels; 51 | m_sample_rate = wav_header.sample_rate; 52 | } 53 | 54 | WAVFileReader::~WAVFileReader() 55 | { 56 | m_file.close(); 57 | } 58 | 59 | void WAVFileReader::getFrames(Frame_t *frames, int number_frames) 60 | { 61 | // fill the buffer with data from the file wrapping around if necessary 62 | for (int i = 0; i < number_frames; i++) 63 | { 64 | // if we've reached the end of the file then seek back to the beginning (after the header) 65 | if (m_file.available() == 0) 66 | { 67 | m_file.seek(44); 68 | } 69 | int16_t left; 70 | int16_t right; 71 | // read in the next sample to the left channel 72 | m_file.read((uint8_t *)(&left), sizeof(int16_t)); 73 | // if we only have one channel duplicate the sample for the right channel 74 | if (m_num_channels == 1) 75 | { 76 | right = left; 77 | } 78 | else 79 | { 80 | // otherwise read in the right channel sample 81 | m_file.read((uint8_t *)(&right), sizeof(int16_t)); 82 | } 83 | // we need unsigned bytes for the ADC 84 | frames[i].left = left + 32768; 85 | frames[i].right = right + 32768; 86 | } 87 | } -------------------------------------------------------------------------------- /dac_i2s_output/src/WAVFileReader.h: -------------------------------------------------------------------------------- 1 | #ifndef __wav_file_reader_h__ 2 | #define __wav_file_reader_h__ 3 | 4 | #include 5 | #include 6 | #include "SampleSource.h" 7 | 8 | class WAVFileReader : public SampleSource 9 | { 10 | private: 11 | int m_num_channels; 12 | int m_sample_rate; 13 | File m_file; 14 | 15 | public: 16 | WAVFileReader(const char *file_name); 17 | ~WAVFileReader(); 18 | int sampleRate() { return m_sample_rate; } 19 | void getFrames(Frame_t *frames, int number_frames); 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /dac_i2s_output/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "WAVFileReader.h" 4 | #include "DACOutput.h" 5 | 6 | SampleSource *sampleSource; 7 | DACOutput *output; 8 | 9 | void setup() 10 | { 11 | Serial.begin(115200); 12 | 13 | Serial.println("Starting up"); 14 | 15 | SPIFFS.begin(); 16 | 17 | Serial.println("Created sample source"); 18 | 19 | sampleSource = new WAVFileReader("/sample.wav"); 20 | 21 | Serial.println("Starting I2S Output"); 22 | output = new DACOutput(); 23 | output->start(sampleSource); 24 | } 25 | 26 | void loop() 27 | { 28 | // nothing to do here - everything is taken care of by tasks 29 | } -------------------------------------------------------------------------------- /dac_i2s_output/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /dac_simple_output/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /dac_simple_output/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /dac_simple_output/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /dac_simple_output/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /dac_simple_output/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /dac_simple_output/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32doit-devkit-v1] 12 | platform = espressif32 13 | board = esp32doit-devkit-v1 14 | framework = arduino 15 | lib_deps = 16 | olikraus/U8g2@^2.28.8 17 | upload_port = /dev/cu.SLAB_USBtoUART 18 | monitor_port = /dev/cu.SLAB_USBtoUART 19 | monitor_speed = 115200 -------------------------------------------------------------------------------- /dac_simple_output/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/15, /* data=*/4, /* reset=*/16); 6 | 7 | void setup() 8 | { 9 | Serial.begin(115200); 10 | delay(5000); 11 | Serial.println("Started up..."); 12 | dac_output_enable(DAC_CHANNEL_1); 13 | u8x8.begin(); 14 | u8x8.setFont(u8x8_font_chroma48medium8_r); 15 | } 16 | 17 | void loop() 18 | { 19 | char line1[200]; 20 | char line2[200]; 21 | for (int i = 0; i < 256; i += 15) 22 | { 23 | Serial.printf("Sending %2d, expecting %.2f output\n", i, i * 3.3 / 255.0f); 24 | sprintf(line1, "Sending %2d", i); 25 | sprintf(line2, "expecting %.2fv", float(i) * 3.3 / 255.0f); 26 | u8x8.clearLine(0); 27 | u8x8.drawString(0, 0, line1); 28 | u8x8.clearLine(1); 29 | u8x8.drawString(0, 1, line2); 30 | dac_output_voltage(DAC_CHANNEL_1, i); 31 | delay(1000); 32 | } 33 | } -------------------------------------------------------------------------------- /dac_simple_output/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /i2s_output/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /i2s_output/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /i2s_output/data/sample.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomic14/esp32_audio/4a39101ea0083aa12dcd3d838c3e51613ecdf3e3/i2s_output/data/sample.wav -------------------------------------------------------------------------------- /i2s_output/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /i2s_output/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /i2s_output/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | upload_port = /dev/cu.SLAB_USBtoUART 16 | monitor_port = /dev/cu.SLAB_USBtoUART 17 | monitor_speed = 115200 18 | -------------------------------------------------------------------------------- /i2s_output/src/I2SOutput.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "driver/i2s.h" 4 | #include 5 | #include 6 | #include 7 | 8 | #include "SampleSource.h" 9 | #include "I2SOutput.h" 10 | 11 | // number of frames to try and send at once (a frame is a left and right sample) 12 | #define NUM_FRAMES_TO_SEND 512 13 | 14 | void i2sWriterTask(void *param) 15 | { 16 | I2SOutput *output = (I2SOutput *)param; 17 | int availableBytes = 0; 18 | int buffer_position = 0; 19 | Frame_t *frames = (Frame_t *)malloc(sizeof(Frame_t) * NUM_FRAMES_TO_SEND); 20 | while (true) 21 | { 22 | // wait for some data to be requested 23 | i2s_event_t evt; 24 | if (xQueueReceive(output->m_i2sQueue, &evt, portMAX_DELAY) == pdPASS) 25 | { 26 | if (evt.type == I2S_EVENT_TX_DONE) 27 | { 28 | size_t bytesWritten = 0; 29 | do 30 | { 31 | if (availableBytes == 0) 32 | { 33 | // get some frames from the wave file - a frame consists of a 16 bit left and right sample 34 | output->m_sample_generator->getFrames(frames, NUM_FRAMES_TO_SEND); 35 | // how maby bytes do we now have to send 36 | availableBytes = NUM_FRAMES_TO_SEND * sizeof(uint32_t); 37 | // reset the buffer position back to the start 38 | buffer_position = 0; 39 | } 40 | // do we have something to write? 41 | if (availableBytes > 0) 42 | { 43 | // write data to the i2s peripheral 44 | i2s_write(output->m_i2sPort, buffer_position + (uint8_t *)frames, 45 | availableBytes, &bytesWritten, portMAX_DELAY); 46 | availableBytes -= bytesWritten; 47 | buffer_position += bytesWritten; 48 | } 49 | } while (bytesWritten > 0); 50 | } 51 | } 52 | } 53 | } 54 | 55 | void I2SOutput::start(i2s_port_t i2sPort, i2s_pin_config_t &i2sPins, SampleSource *sample_generator) 56 | { 57 | m_sample_generator = sample_generator; 58 | // i2s config for writing both channels of I2S 59 | i2s_config_t i2sConfig = { 60 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), 61 | .sample_rate = m_sample_generator->sampleRate(), 62 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 63 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 64 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S), 65 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 66 | .dma_buf_count = 4, 67 | .dma_buf_len = 1024}; 68 | 69 | m_i2sPort = i2sPort; 70 | //install and start i2s driver 71 | i2s_driver_install(m_i2sPort, &i2sConfig, 4, &m_i2sQueue); 72 | // set up the i2s pins 73 | i2s_set_pin(m_i2sPort, &i2sPins); 74 | // clear the DMA buffers 75 | i2s_zero_dma_buffer(m_i2sPort); 76 | // start a task to write samples to the i2s peripheral 77 | TaskHandle_t writerTaskHandle; 78 | xTaskCreate(i2sWriterTask, "i2s Writer Task", 4096, this, 1, &writerTaskHandle); 79 | } 80 | -------------------------------------------------------------------------------- /i2s_output/src/I2SOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef __sampler_base_h__ 2 | #define __sampler_base_h__ 3 | 4 | #include 5 | #include "driver/i2s.h" 6 | 7 | class SampleSource; 8 | 9 | /** 10 | * Base Class for both the ADC and I2S sampler 11 | **/ 12 | class I2SOutput 13 | { 14 | private: 15 | // I2S write task 16 | TaskHandle_t m_i2sWriterTaskHandle; 17 | // i2s writer queue 18 | QueueHandle_t m_i2sQueue; 19 | // i2s port 20 | i2s_port_t m_i2sPort; 21 | // src of samples for us to play 22 | SampleSource *m_sample_generator; 23 | 24 | public: 25 | void start(i2s_port_t i2sPort, i2s_pin_config_t &i2sPins, SampleSource *sample_generator); 26 | 27 | friend void i2sWriterTask(void *param); 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /i2s_output/src/SampleSource.h: -------------------------------------------------------------------------------- 1 | #ifndef __sample_source_h__ 2 | #define __sample_source_h__ 3 | 4 | #include 5 | 6 | typedef struct 7 | { 8 | int16_t left; 9 | int16_t right; 10 | } Frame_t; 11 | 12 | /** 13 | * Base class for our sample generators 14 | **/ 15 | class SampleSource 16 | { 17 | public: 18 | virtual int sampleRate() = 0; 19 | // This should fill the samples buffer with the specified number of frames 20 | // A frame contains a LEFT and a RIGHT sample. Each sample should be signed 16 bits 21 | virtual void getFrames(Frame_t *frames, int number_frames) = 0; 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /i2s_output/src/SinWaveGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SinWaveGenerator.h" 3 | 4 | SinWaveGenerator::SinWaveGenerator(int sample_rate, int frequency, float magnitude) 5 | { 6 | m_sample_rate = sample_rate; 7 | m_frequency = frequency; 8 | m_magnitude = magnitude; 9 | m_current_position = 0; 10 | } 11 | 12 | void SinWaveGenerator::getFrames(Frame_t *frames, int number_frames) 13 | { 14 | float full_wave_samples = m_sample_rate / m_frequency; 15 | float step_per_sample = M_TWOPI / full_wave_samples; 16 | // fill the buffer with data from the file wrapping around if necessary 17 | for (int i = 0; i < number_frames; i++) 18 | { 19 | frames[i].left = frames[i].right = 16384 * m_magnitude * sin(m_current_position); 20 | m_current_position += step_per_sample; 21 | // wrap around to maintain numerical stability 22 | if (m_current_position > M_TWOPI) 23 | { 24 | m_current_position -= M_TWOPI; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /i2s_output/src/SinWaveGenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef __sin_wave_generator_h__ 2 | #define __sin_wave_generator_h__ 3 | 4 | #include "SampleSource.h" 5 | 6 | class SinWaveGenerator : public SampleSource 7 | { 8 | private: 9 | int m_sample_rate; 10 | int m_frequency; 11 | float m_magnitude; 12 | float m_current_position; 13 | 14 | public: 15 | SinWaveGenerator(int sample_rate, int frequency, float magnitude); 16 | virtual int sampleRate() { return m_sample_rate; } 17 | // This should fill the samples buffer with the specified number of frames 18 | // A frame contains a LEFT and a RIGHT sample. Each sample should be signed 16 bits 19 | virtual void getFrames(Frame_t *frames, int number_frames); 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /i2s_output/src/WAVFileReader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "WAVFileReader.h" 4 | 5 | #pragma pack(push, 1) 6 | typedef struct 7 | { 8 | // RIFF Header 9 | char riff_header[4]; // Contains "RIFF" 10 | int wav_size; // Size of the wav portion of the file, which follows the first 8 bytes. File size - 8 11 | char wave_header[4]; // Contains "WAVE" 12 | 13 | // Format Header 14 | char fmt_header[4]; // Contains "fmt " (includes trailing space) 15 | int fmt_chunk_size; // Should be 16 for PCM 16 | short audio_format; // Should be 1 for PCM. 3 for IEEE Float 17 | short num_channels; 18 | int sample_rate; 19 | int byte_rate; // Number of bytes per second. sample_rate * num_channels * Bytes Per Sample 20 | short sample_alignment; // num_channels * Bytes Per Sample 21 | short bit_depth; // Number of bits per sample 22 | 23 | // Data 24 | char data_header[4]; // Contains "data" 25 | int data_bytes; // Number of bytes in data. Number of samples * num_channels * sample byte size 26 | // uint8_t bytes[]; // Remainder of wave file is bytes 27 | } wav_header_t; 28 | #pragma pack(pop) 29 | 30 | WAVFileReader::WAVFileReader(const char *file_name) 31 | { 32 | if (!SPIFFS.exists(file_name)) 33 | { 34 | Serial.println("****** Failed to open file! Have you uploaed the file system?"); 35 | return; 36 | } 37 | m_file = SPIFFS.open(file_name, "r"); 38 | // read the WAV header 39 | wav_header_t wav_header; 40 | m_file.read((uint8_t *)&wav_header, sizeof(wav_header_t)); 41 | // sanity check the bit depth 42 | if (wav_header.bit_depth != 16) 43 | { 44 | Serial.printf("ERROR: bit depth %d is not supported\n", wav_header.bit_depth); 45 | } 46 | 47 | Serial.printf("fmt_chunk_size=%d, audio_format=%d, num_channels=%d, sample_rate=%d, sample_alignment=%d, bit_depth=%d, data_bytes=%d\n", 48 | wav_header.fmt_chunk_size, wav_header.audio_format, wav_header.num_channels, wav_header.sample_rate, wav_header.sample_alignment, wav_header.bit_depth, wav_header.data_bytes); 49 | 50 | m_num_channels = wav_header.num_channels; 51 | m_sample_rate = wav_header.sample_rate; 52 | } 53 | 54 | WAVFileReader::~WAVFileReader() 55 | { 56 | m_file.close(); 57 | } 58 | 59 | void WAVFileReader::getFrames(Frame_t *frames, int number_frames) 60 | { 61 | // fill the buffer with data from the file wrapping around if necessary 62 | for (int i = 0; i < number_frames; i++) 63 | { 64 | // if we've reached the end of the file then seek back to the beginning (after the header) 65 | if (m_file.available() == 0) 66 | { 67 | m_file.seek(44); 68 | } 69 | // read in the next sample to the left channel 70 | m_file.read((uint8_t *)(&frames[i].left), sizeof(int16_t)); 71 | // if we only have one channel duplicate the sample for the right channel 72 | if (m_num_channels == 1) 73 | { 74 | frames[i].right = frames[i].left; 75 | } 76 | else 77 | { 78 | // otherwise read in the right channel sample 79 | m_file.read((uint8_t *)(&frames[i].right), sizeof(int16_t)); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /i2s_output/src/WAVFileReader.h: -------------------------------------------------------------------------------- 1 | #ifndef __wav_file_reader_h__ 2 | #define __wav_file_reader_h__ 3 | 4 | #include 5 | #include 6 | #include "SampleSource.h" 7 | 8 | class WAVFileReader : public SampleSource 9 | { 10 | private: 11 | int m_num_channels; 12 | int m_sample_rate; 13 | File m_file; 14 | 15 | public: 16 | WAVFileReader(const char *file_name); 17 | ~WAVFileReader(); 18 | int sampleRate() { return m_sample_rate; } 19 | void getFrames(Frame_t *frames, int number_frames); 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /i2s_output/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "WAVFileReader.h" 4 | #include "SinWaveGenerator.h" 5 | #include "I2SOutput.h" 6 | 7 | // i2s pins 8 | i2s_pin_config_t i2sPins = { 9 | .bck_io_num = GPIO_NUM_27, 10 | .ws_io_num = GPIO_NUM_14, 11 | .data_out_num = GPIO_NUM_26, 12 | .data_in_num = -1}; 13 | 14 | I2SOutput *output; 15 | SampleSource *sampleSource; 16 | 17 | void setup() 18 | { 19 | Serial.begin(115200); 20 | 21 | Serial.println("Starting up"); 22 | 23 | SPIFFS.begin(); 24 | 25 | Serial.println("Created sample source"); 26 | 27 | // sampleSource = new SinWaveGenerator(40000, 10000, 0.75); 28 | 29 | sampleSource = new WAVFileReader("/sample.wav"); 30 | 31 | Serial.println("Starting I2S Output"); 32 | output = new I2SOutput(); 33 | output->start(I2S_NUM_1, i2sPins, sampleSource); 34 | } 35 | 36 | void loop() 37 | { 38 | // nothing to do here - everything is taken care of by tasks 39 | } -------------------------------------------------------------------------------- /i2s_output/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /i2s_sampling/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /i2s_sampling/.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /i2s_sampling/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /i2s_sampling/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "deque": "cpp", 5 | "string": "cpp", 6 | "unordered_map": "cpp", 7 | "unordered_set": "cpp", 8 | "vector": "cpp", 9 | "string_view": "cpp", 10 | "initializer_list": "cpp", 11 | "regex": "cpp", 12 | "*.tcc": "cpp", 13 | "bitset": "cpp", 14 | "cstdlib": "cpp" 15 | } 16 | } -------------------------------------------------------------------------------- /i2s_sampling/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /i2s_sampling/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /i2s_sampling/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | monitor_port = /dev/cu.SLAB_USBtoUART 16 | monitor_speed = 115200 17 | upload_port = /dev/cu.SLAB_USBtoUART 18 | monitor_filters = esp32_exception_decoder 19 | build_type = debug -------------------------------------------------------------------------------- /i2s_sampling/src/ADCSampler.cpp: -------------------------------------------------------------------------------- 1 | #include "ADCSampler.h" 2 | 3 | ADCSampler::ADCSampler(adc_unit_t adcUnit, adc1_channel_t adcChannel, const i2s_config_t &i2s_config) : I2SSampler(I2S_NUM_0, i2s_config) 4 | { 5 | m_adcUnit = adcUnit; 6 | m_adcChannel = adcChannel; 7 | } 8 | 9 | void ADCSampler::configureI2S() 10 | { 11 | //init ADC pad 12 | i2s_set_adc_mode(m_adcUnit, m_adcChannel); 13 | // enable the adc 14 | i2s_adc_enable(m_i2sPort); 15 | } 16 | 17 | void ADCSampler::unConfigureI2S() 18 | { 19 | // make sure ot do this or the ADC is locked 20 | i2s_adc_disable(m_i2sPort); 21 | } 22 | 23 | int ADCSampler::read(int16_t *samples, int count) 24 | { 25 | // read from i2s 26 | size_t bytes_read = 0; 27 | i2s_read(m_i2sPort, samples, sizeof(int16_t) * count, &bytes_read, portMAX_DELAY); 28 | int samples_read = bytes_read / sizeof(int16_t); 29 | for (int i = 0; i < samples_read; i++) 30 | { 31 | samples[i] = (2048 - (uint16_t(samples[i]) & 0xfff)) * 15; 32 | } 33 | return samples_read; 34 | } 35 | -------------------------------------------------------------------------------- /i2s_sampling/src/ADCSampler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "I2SSampler.h" 4 | 5 | class ADCSampler : public I2SSampler 6 | { 7 | private: 8 | adc_unit_t m_adcUnit; 9 | adc1_channel_t m_adcChannel; 10 | 11 | protected: 12 | void configureI2S(); 13 | void unConfigureI2S(); 14 | 15 | public: 16 | ADCSampler(adc_unit_t adc_unit, adc1_channel_t adc_channel, const i2s_config_t &i2s_config); 17 | virtual int read(int16_t *samples, int count); 18 | }; 19 | -------------------------------------------------------------------------------- /i2s_sampling/src/I2SMEMSSampler.cpp: -------------------------------------------------------------------------------- 1 | #include "I2SMEMSSampler.h" 2 | #include "soc/i2s_reg.h" 3 | #include 4 | 5 | I2SMEMSSampler::I2SMEMSSampler( 6 | i2s_port_t i2s_port, 7 | i2s_pin_config_t &i2s_pins, 8 | i2s_config_t i2s_config, 9 | bool fixSPH0645) : I2SSampler(i2s_port, i2s_config) 10 | { 11 | m_i2sPins = i2s_pins; 12 | m_fixSPH0645 = fixSPH0645; 13 | } 14 | 15 | void I2SMEMSSampler::configureI2S() 16 | { 17 | if (m_fixSPH0645) 18 | { 19 | // FIXES for SPH0645 20 | REG_SET_BIT(I2S_TIMING_REG(m_i2sPort), BIT(9)); 21 | REG_SET_BIT(I2S_CONF_REG(m_i2sPort), I2S_RX_MSB_SHIFT); 22 | } 23 | 24 | i2s_set_pin(m_i2sPort, &m_i2sPins); 25 | } 26 | 27 | int I2SMEMSSampler::read(int16_t *samples, int count) 28 | { 29 | int32_t raw_samples[256]; 30 | int sample_index = 0; 31 | while (count > 0) 32 | { 33 | size_t bytes_read = 0; 34 | i2s_read(m_i2sPort, (void **)raw_samples, sizeof(int32_t) * std::min(count, 256), &bytes_read, portMAX_DELAY); 35 | int samples_read = bytes_read / sizeof(int32_t); 36 | for (int i = 0; i < samples_read; i++) 37 | { 38 | samples[sample_index] = (raw_samples[i] & 0xFFFFFFF0) >> 11; 39 | sample_index++; 40 | count--; 41 | } 42 | } 43 | return sample_index; 44 | } -------------------------------------------------------------------------------- /i2s_sampling/src/I2SMEMSSampler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "I2SSampler.h" 4 | 5 | class I2SMEMSSampler : public I2SSampler 6 | { 7 | private: 8 | i2s_pin_config_t m_i2sPins; 9 | bool m_fixSPH0645; 10 | 11 | protected: 12 | void configureI2S(); 13 | 14 | public: 15 | I2SMEMSSampler( 16 | i2s_port_t i2s_port, 17 | i2s_pin_config_t &i2s_pins, 18 | i2s_config_t i2s_config, 19 | bool fixSPH0645 = false); 20 | virtual int read(int16_t *samples, int count); 21 | }; 22 | -------------------------------------------------------------------------------- /i2s_sampling/src/I2SSampler.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "I2SSampler.h" 3 | #include "driver/i2s.h" 4 | 5 | I2SSampler::I2SSampler(i2s_port_t i2sPort, const i2s_config_t &i2s_config) : m_i2sPort(i2sPort), m_i2s_config(i2s_config) 6 | { 7 | } 8 | 9 | void I2SSampler::start() 10 | { 11 | //install and start i2s driver 12 | i2s_driver_install(m_i2sPort, &m_i2s_config, 0, NULL); 13 | // set up the I2S configuration from the subclass 14 | configureI2S(); 15 | } 16 | 17 | void I2SSampler::stop() 18 | { 19 | // clear any I2S configuration 20 | unConfigureI2S(); 21 | // stop the i2S driver 22 | i2s_driver_uninstall(m_i2sPort); 23 | } 24 | -------------------------------------------------------------------------------- /i2s_sampling/src/I2SSampler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * Base Class for both the ADC and I2S sampler 8 | **/ 9 | class I2SSampler 10 | { 11 | protected: 12 | i2s_port_t m_i2sPort = I2S_NUM_0; 13 | i2s_config_t m_i2s_config; 14 | virtual void configureI2S() = 0; 15 | virtual void unConfigureI2S(){}; 16 | virtual void processI2SData(void *samples, size_t count){ 17 | // nothing to do for the default case 18 | }; 19 | 20 | public: 21 | I2SSampler(i2s_port_t i2sPort, const i2s_config_t &i2sConfig); 22 | void start(); 23 | virtual int read(int16_t *samples, int count) = 0; 24 | void stop(); 25 | int sample_rate() 26 | { 27 | return m_i2s_config.sample_rate; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /i2s_sampling/src/WiFiCredentials.h: -------------------------------------------------------------------------------- 1 | #define SSID "<>" 2 | #define PASSWORD "<>" 3 | -------------------------------------------------------------------------------- /i2s_sampling/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "WiFiCredentials.h" 5 | #include "I2SMEMSSampler.h" 6 | #include "ADCSampler.h" 7 | 8 | WiFiClient *wifiClientADC = NULL; 9 | HTTPClient *httpClientADC = NULL; 10 | WiFiClient *wifiClientI2S = NULL; 11 | HTTPClient *httpClientI2S = NULL; 12 | ADCSampler *adcSampler = NULL; 13 | I2SSampler *i2sSampler = NULL; 14 | 15 | // replace this with your machines IP Address 16 | #define ADC_SERVER_URL "http://192.168.1.72:5003/adc_samples" 17 | #define I2S_SERVER_URL "http://192.168.1.72:5003/i2s_samples" 18 | 19 | // i2s config for using the internal ADC 20 | i2s_config_t adcI2SConfig = { 21 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), 22 | .sample_rate = 16000, 23 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 24 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 25 | .communication_format = I2S_COMM_FORMAT_I2S_LSB, 26 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 27 | .dma_buf_count = 4, 28 | .dma_buf_len = 1024, 29 | .use_apll = false, 30 | .tx_desc_auto_clear = false, 31 | .fixed_mclk = 0}; 32 | 33 | // i2s config for reading from left channel of I2S 34 | i2s_config_t i2sMemsConfigLeftChannel = { 35 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), 36 | .sample_rate = 16000, 37 | .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, 38 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 39 | .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S), 40 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 41 | .dma_buf_count = 4, 42 | .dma_buf_len = 1024, 43 | .use_apll = false, 44 | .tx_desc_auto_clear = false, 45 | .fixed_mclk = 0}; 46 | 47 | // i2s pins 48 | i2s_pin_config_t i2sPins = { 49 | .bck_io_num = GPIO_NUM_32, 50 | .ws_io_num = GPIO_NUM_25, 51 | .data_out_num = I2S_PIN_NO_CHANGE, 52 | .data_in_num = GPIO_NUM_33}; 53 | 54 | // how many samples to read at once 55 | const int SAMPLE_SIZE = 16384; 56 | 57 | // send data to a remote address 58 | void sendData(WiFiClient *wifiClient, HTTPClient *httpClient, const char *url, uint8_t *bytes, size_t count) 59 | { 60 | // send them off to the server 61 | digitalWrite(2, HIGH); 62 | httpClient->begin(*wifiClient, url); 63 | httpClient->addHeader("content-type", "application/octet-stream"); 64 | httpClient->POST(bytes, count); 65 | httpClient->end(); 66 | digitalWrite(2, LOW); 67 | } 68 | 69 | // Task to write samples from ADC to our server 70 | void adcWriterTask(void *param) 71 | { 72 | I2SSampler *sampler = (I2SSampler *)param; 73 | int16_t *samples = (int16_t *)malloc(sizeof(uint16_t) * SAMPLE_SIZE); 74 | if (!samples) 75 | { 76 | Serial.println("Failed to allocate memory for samples"); 77 | return; 78 | } 79 | while (true) 80 | { 81 | int samples_read = sampler->read(samples, SAMPLE_SIZE); 82 | sendData(wifiClientADC, httpClientADC, ADC_SERVER_URL, (uint8_t *)samples, samples_read * sizeof(uint16_t)); 83 | } 84 | } 85 | 86 | // Task to write samples to our server 87 | void i2sMemsWriterTask(void *param) 88 | { 89 | I2SSampler *sampler = (I2SSampler *)param; 90 | int16_t *samples = (int16_t *)malloc(sizeof(uint16_t) * SAMPLE_SIZE); 91 | if (!samples) 92 | { 93 | Serial.println("Failed to allocate memory for samples"); 94 | return; 95 | } 96 | while (true) 97 | { 98 | int samples_read = sampler->read(samples, SAMPLE_SIZE); 99 | sendData(wifiClientI2S, httpClientI2S, I2S_SERVER_URL, (uint8_t *)samples, samples_read * sizeof(uint16_t)); 100 | } 101 | } 102 | 103 | void setup() 104 | { 105 | Serial.begin(115200); 106 | // launch WiFi 107 | Serial.printf("Connecting to WiFi"); 108 | WiFi.mode(WIFI_STA); 109 | WiFi.begin(SSID, PASSWORD); 110 | while (WiFi.waitForConnectResult() != WL_CONNECTED) 111 | { 112 | Serial.print("."); 113 | delay(1000); 114 | } 115 | Serial.println(""); 116 | Serial.println("WiFi Connected"); 117 | Serial.println("Started up"); 118 | // indicator LED 119 | pinMode(2, OUTPUT); 120 | // setup the HTTP Client 121 | wifiClientADC = new WiFiClient(); 122 | httpClientADC = new HTTPClient(); 123 | 124 | wifiClientI2S = new WiFiClient(); 125 | httpClientI2S = new HTTPClient(); 126 | 127 | // input from analog microphones such as the MAX9814 or MAX4466 128 | // internal analog to digital converter sampling using i2s 129 | // create our samplers 130 | // adcSampler = new ADCSampler(ADC_UNIT_1, ADC1_CHANNEL_7, adcI2SConfig); 131 | 132 | // set up the adc sample writer task 133 | // TaskHandle_t adcWriterTaskHandle; 134 | // adcSampler->start(); 135 | // xTaskCreatePinnedToCore(adcWriterTask, "ADC Writer Task", 4096, adcSampler, 1, &adcWriterTaskHandle, 1); 136 | 137 | // Direct i2s input from INMP441 or the SPH0645 138 | i2sSampler = new I2SMEMSSampler(I2S_NUM_0, i2sPins, i2sMemsConfigLeftChannel, false); 139 | i2sSampler->start(); 140 | // set up the i2s sample writer task 141 | TaskHandle_t i2sMemsWriterTaskHandle; 142 | xTaskCreatePinnedToCore(i2sMemsWriterTask, "I2S Writer Task", 4096, i2sSampler, 1, &i2sMemsWriterTaskHandle, 1); 143 | 144 | // // start sampling from i2s device 145 | } 146 | 147 | void loop() 148 | { 149 | // nothing to do here - everything is taken care of by tasks 150 | } -------------------------------------------------------------------------------- /i2s_sampling/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /loop_sampling/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /loop_sampling/.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /loop_sampling/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /loop_sampling/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /loop_sampling/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /loop_sampling/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | monitor_port = /dev/cu.SLAB_USBtoUART 16 | monitor_speed = 115200 17 | upload_port = /dev/cu.SLAB_USBtoUART 18 | monitor_filters = esp32_exception_decoder 19 | -------------------------------------------------------------------------------- /loop_sampling/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "esp_adc_cal.h" 4 | 5 | // calibration values for the adc 6 | #define DEFAULT_VREF 1100 7 | esp_adc_cal_characteristics_t *adc_chars; 8 | 9 | void setup() 10 | { 11 | Serial.begin(115200); 12 | Serial.println("Started up"); 13 | 14 | //Range 0-4096 15 | adc1_config_width(ADC_WIDTH_BIT_12); 16 | // full voltage range 17 | adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11); 18 | 19 | // check to see what calibration is available 20 | if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) 21 | { 22 | Serial.println("Using voltage ref stored in eFuse"); 23 | } 24 | if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) 25 | { 26 | Serial.println("Using two point values from eFuse"); 27 | } 28 | if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_DEFAULT_VREF) == ESP_OK) 29 | { 30 | Serial.println("Using default VREF"); 31 | } 32 | //Characterize ADC 33 | adc_chars = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t)); 34 | esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars); 35 | } 36 | 37 | void loop() 38 | { 39 | // for a more accurate reading you could read multiple samples here 40 | 41 | // read a sample from the adc using GPIO35 42 | int sample = adc1_get_raw(ADC1_CHANNEL_7); 43 | // get the calibrated value 44 | int milliVolts = esp_adc_cal_raw_to_voltage(sample, adc_chars); 45 | 46 | Serial.printf("Sample=%d, mV=%d\n", sample, milliVolts); 47 | 48 | delay(500); 49 | } -------------------------------------------------------------------------------- /loop_sampling/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 80 6 | } -------------------------------------------------------------------------------- /server/javascript/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This is a very simple server that will receive raw audio and write it to a file. 4 | 5 | It will write two different files depending on what it receives: 6 | 7 | - `adc.raw` - samples from the analog to digital converter. 8 | - `i2s.raw` - samples from the I2S microphone. 9 | 10 | Samples are appended to any existing file so if you want to start again from scratch you will need to delete the raw files. 11 | 12 | To import the files use the "raw" import option on Audacity - this will let you specify the size of the samples (signed 16 bit), number of channels (1 or 2 depending on if you are capturing one or two channels), and sample rate (16KHz is the default in the code). 13 | 14 | # Prerequisites 15 | 16 | You will need to have [node](https://nodejs.org/en/download/) and [yarn](https://classic.yarnpkg.com/en/docs/install/#mac-stable). You may already have these on your system. 17 | 18 | Check with: 19 | 20 | ``` 21 | node --version 22 | yarn --version 23 | ``` 24 | 25 | # Setup 26 | 27 | To install the dependencies run: 28 | 29 | ``` 30 | cd server 31 | yarn 32 | ``` 33 | 34 | # Running the server 35 | 36 | The following commands will run the server: 37 | 38 | ``` 39 | cd server 40 | yarn start 41 | ``` 42 | -------------------------------------------------------------------------------- /server/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "dist/index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@types/express": "^4.17.7", 8 | "@types/node": "^14.0.27", 9 | "body-parser": "^1.19.0", 10 | "express": "^4.17.1", 11 | "prettier": "^2.0.5", 12 | "tslint": "^6.1.2", 13 | "typescript": "^3.9.7" 14 | }, 15 | "scripts": { 16 | "prebuild": "yarn run tslint -c tslint.json -p tsconfig.json --fix", 17 | "build": "yarn run tsc", 18 | "prestart": "yarn run build", 19 | "start": "node .", 20 | "prettier": "prettier --config .prettierrc 'src/**/*.ts' --write" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/javascript/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import bodyParser from 'body-parser'; 3 | import fs from 'fs'; 4 | 5 | const app = express(); 6 | const port = 5003; // default port to listen 7 | 8 | app.use( 9 | bodyParser.raw({ 10 | // inflate: true, 11 | type: '*/*' 12 | }) 13 | ); 14 | 15 | // route to handle samples from the ADC - 16 bit single channel samples 16 | app.post('/adc_samples', (req, res) => { 17 | // tslint:disable-next-line:no-console 18 | console.log(`Got ${req.body.length} ADC bytes`); 19 | fs.appendFile('adc.raw', req.body, () => { 20 | res.send('OK'); 21 | }); 22 | }); 23 | 24 | // route to handle samples from the I2S microphones - 32 bit stereo channel samples 25 | app.post('/i2s_samples', (req, res) => { 26 | // tslint:disable-next-line:no-console 27 | console.log(`Got ${req.body.length} I2S bytes`); 28 | fs.appendFile('i2s.raw', req.body, () => { 29 | res.send('OK'); 30 | }); 31 | }); 32 | 33 | // start the Express server 34 | app.listen(port, '0.0.0.0', () => { 35 | // tslint:disable-next-line:no-console 36 | console.log(`server started at http://0.0.0.0:${port}`); 37 | }); 38 | -------------------------------------------------------------------------------- /server/javascript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "noImplicitAny": true, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "baseUrl": ".", 11 | "paths": { 12 | "*": [ 13 | "node_modules/*" 14 | ] 15 | } 16 | }, 17 | "include": [ 18 | "src/**/*" 19 | ] 20 | } -------------------------------------------------------------------------------- /server/javascript/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "trailing-comma": [ false ] 9 | }, 10 | "rulesDirectory": [] 11 | } -------------------------------------------------------------------------------- /server/javascript/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@^7.0.0": 6 | version "7.10.4" 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" 8 | integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== 9 | dependencies: 10 | "@babel/highlight" "^7.10.4" 11 | 12 | "@babel/helper-validator-identifier@^7.10.4": 13 | version "7.10.4" 14 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" 15 | integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== 16 | 17 | "@babel/highlight@^7.10.4": 18 | version "7.10.4" 19 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" 20 | integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== 21 | dependencies: 22 | "@babel/helper-validator-identifier" "^7.10.4" 23 | chalk "^2.0.0" 24 | js-tokens "^4.0.0" 25 | 26 | "@types/body-parser@*": 27 | version "1.19.0" 28 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" 29 | integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== 30 | dependencies: 31 | "@types/connect" "*" 32 | "@types/node" "*" 33 | 34 | "@types/connect@*": 35 | version "3.4.33" 36 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" 37 | integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== 38 | dependencies: 39 | "@types/node" "*" 40 | 41 | "@types/express-serve-static-core@*": 42 | version "4.17.9" 43 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" 44 | integrity sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA== 45 | dependencies: 46 | "@types/node" "*" 47 | "@types/qs" "*" 48 | "@types/range-parser" "*" 49 | 50 | "@types/express@^4.17.7": 51 | version "4.17.7" 52 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" 53 | integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== 54 | dependencies: 55 | "@types/body-parser" "*" 56 | "@types/express-serve-static-core" "*" 57 | "@types/qs" "*" 58 | "@types/serve-static" "*" 59 | 60 | "@types/mime@*": 61 | version "2.0.3" 62 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" 63 | integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== 64 | 65 | "@types/node@*", "@types/node@^14.0.27": 66 | version "14.0.27" 67 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" 68 | integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== 69 | 70 | "@types/qs@*": 71 | version "6.9.4" 72 | resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a" 73 | integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ== 74 | 75 | "@types/range-parser@*": 76 | version "1.2.3" 77 | resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" 78 | integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== 79 | 80 | "@types/serve-static@*": 81 | version "1.13.5" 82 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.5.tgz#3d25d941a18415d3ab092def846e135a08bbcf53" 83 | integrity sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ== 84 | dependencies: 85 | "@types/express-serve-static-core" "*" 86 | "@types/mime" "*" 87 | 88 | accepts@~1.3.7: 89 | version "1.3.7" 90 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 91 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 92 | dependencies: 93 | mime-types "~2.1.24" 94 | negotiator "0.6.2" 95 | 96 | ansi-styles@^3.2.1: 97 | version "3.2.1" 98 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 99 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 100 | dependencies: 101 | color-convert "^1.9.0" 102 | 103 | argparse@^1.0.7: 104 | version "1.0.10" 105 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 106 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 107 | dependencies: 108 | sprintf-js "~1.0.2" 109 | 110 | array-flatten@1.1.1: 111 | version "1.1.1" 112 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 113 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 114 | 115 | balanced-match@^1.0.0: 116 | version "1.0.0" 117 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 118 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 119 | 120 | body-parser@1.19.0, body-parser@^1.19.0: 121 | version "1.19.0" 122 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 123 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== 124 | dependencies: 125 | bytes "3.1.0" 126 | content-type "~1.0.4" 127 | debug "2.6.9" 128 | depd "~1.1.2" 129 | http-errors "1.7.2" 130 | iconv-lite "0.4.24" 131 | on-finished "~2.3.0" 132 | qs "6.7.0" 133 | raw-body "2.4.0" 134 | type-is "~1.6.17" 135 | 136 | brace-expansion@^1.1.7: 137 | version "1.1.11" 138 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 139 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 140 | dependencies: 141 | balanced-match "^1.0.0" 142 | concat-map "0.0.1" 143 | 144 | builtin-modules@^1.1.1: 145 | version "1.1.1" 146 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 147 | integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= 148 | 149 | bytes@3.1.0: 150 | version "3.1.0" 151 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 152 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 153 | 154 | chalk@^2.0.0, chalk@^2.3.0: 155 | version "2.4.2" 156 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 157 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 158 | dependencies: 159 | ansi-styles "^3.2.1" 160 | escape-string-regexp "^1.0.5" 161 | supports-color "^5.3.0" 162 | 163 | color-convert@^1.9.0: 164 | version "1.9.3" 165 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 166 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 167 | dependencies: 168 | color-name "1.1.3" 169 | 170 | color-name@1.1.3: 171 | version "1.1.3" 172 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 173 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 174 | 175 | commander@^2.12.1: 176 | version "2.20.3" 177 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 178 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 179 | 180 | concat-map@0.0.1: 181 | version "0.0.1" 182 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 183 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 184 | 185 | content-disposition@0.5.3: 186 | version "0.5.3" 187 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 188 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== 189 | dependencies: 190 | safe-buffer "5.1.2" 191 | 192 | content-type@~1.0.4: 193 | version "1.0.4" 194 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 195 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 196 | 197 | cookie-signature@1.0.6: 198 | version "1.0.6" 199 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 200 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 201 | 202 | cookie@0.4.0: 203 | version "0.4.0" 204 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 205 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== 206 | 207 | debug@2.6.9: 208 | version "2.6.9" 209 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 210 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 211 | dependencies: 212 | ms "2.0.0" 213 | 214 | depd@~1.1.2: 215 | version "1.1.2" 216 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 217 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 218 | 219 | destroy@~1.0.4: 220 | version "1.0.4" 221 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 222 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 223 | 224 | diff@^4.0.1: 225 | version "4.0.2" 226 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 227 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 228 | 229 | ee-first@1.1.1: 230 | version "1.1.1" 231 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 232 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 233 | 234 | encodeurl@~1.0.2: 235 | version "1.0.2" 236 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 237 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 238 | 239 | escape-html@~1.0.3: 240 | version "1.0.3" 241 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 242 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 243 | 244 | escape-string-regexp@^1.0.5: 245 | version "1.0.5" 246 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 247 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 248 | 249 | esprima@^4.0.0: 250 | version "4.0.1" 251 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 252 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 253 | 254 | etag@~1.8.1: 255 | version "1.8.1" 256 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 257 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 258 | 259 | express@^4.17.1: 260 | version "4.17.1" 261 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 262 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== 263 | dependencies: 264 | accepts "~1.3.7" 265 | array-flatten "1.1.1" 266 | body-parser "1.19.0" 267 | content-disposition "0.5.3" 268 | content-type "~1.0.4" 269 | cookie "0.4.0" 270 | cookie-signature "1.0.6" 271 | debug "2.6.9" 272 | depd "~1.1.2" 273 | encodeurl "~1.0.2" 274 | escape-html "~1.0.3" 275 | etag "~1.8.1" 276 | finalhandler "~1.1.2" 277 | fresh "0.5.2" 278 | merge-descriptors "1.0.1" 279 | methods "~1.1.2" 280 | on-finished "~2.3.0" 281 | parseurl "~1.3.3" 282 | path-to-regexp "0.1.7" 283 | proxy-addr "~2.0.5" 284 | qs "6.7.0" 285 | range-parser "~1.2.1" 286 | safe-buffer "5.1.2" 287 | send "0.17.1" 288 | serve-static "1.14.1" 289 | setprototypeof "1.1.1" 290 | statuses "~1.5.0" 291 | type-is "~1.6.18" 292 | utils-merge "1.0.1" 293 | vary "~1.1.2" 294 | 295 | finalhandler@~1.1.2: 296 | version "1.1.2" 297 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 298 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 299 | dependencies: 300 | debug "2.6.9" 301 | encodeurl "~1.0.2" 302 | escape-html "~1.0.3" 303 | on-finished "~2.3.0" 304 | parseurl "~1.3.3" 305 | statuses "~1.5.0" 306 | unpipe "~1.0.0" 307 | 308 | forwarded@~0.1.2: 309 | version "0.1.2" 310 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 311 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 312 | 313 | fresh@0.5.2: 314 | version "0.5.2" 315 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 316 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 317 | 318 | fs.realpath@^1.0.0: 319 | version "1.0.0" 320 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 321 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 322 | 323 | glob@^7.1.1: 324 | version "7.1.6" 325 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 326 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 327 | dependencies: 328 | fs.realpath "^1.0.0" 329 | inflight "^1.0.4" 330 | inherits "2" 331 | minimatch "^3.0.4" 332 | once "^1.3.0" 333 | path-is-absolute "^1.0.0" 334 | 335 | has-flag@^3.0.0: 336 | version "3.0.0" 337 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 338 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 339 | 340 | http-errors@1.7.2: 341 | version "1.7.2" 342 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 343 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== 344 | dependencies: 345 | depd "~1.1.2" 346 | inherits "2.0.3" 347 | setprototypeof "1.1.1" 348 | statuses ">= 1.5.0 < 2" 349 | toidentifier "1.0.0" 350 | 351 | http-errors@~1.7.2: 352 | version "1.7.3" 353 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 354 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== 355 | dependencies: 356 | depd "~1.1.2" 357 | inherits "2.0.4" 358 | setprototypeof "1.1.1" 359 | statuses ">= 1.5.0 < 2" 360 | toidentifier "1.0.0" 361 | 362 | iconv-lite@0.4.24: 363 | version "0.4.24" 364 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 365 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 366 | dependencies: 367 | safer-buffer ">= 2.1.2 < 3" 368 | 369 | inflight@^1.0.4: 370 | version "1.0.6" 371 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 372 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 373 | dependencies: 374 | once "^1.3.0" 375 | wrappy "1" 376 | 377 | inherits@2, inherits@2.0.4: 378 | version "2.0.4" 379 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 380 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 381 | 382 | inherits@2.0.3: 383 | version "2.0.3" 384 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 385 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 386 | 387 | ipaddr.js@1.9.1: 388 | version "1.9.1" 389 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 390 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 391 | 392 | js-tokens@^4.0.0: 393 | version "4.0.0" 394 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 395 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 396 | 397 | js-yaml@^3.13.1: 398 | version "3.14.0" 399 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" 400 | integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== 401 | dependencies: 402 | argparse "^1.0.7" 403 | esprima "^4.0.0" 404 | 405 | media-typer@0.3.0: 406 | version "0.3.0" 407 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 408 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 409 | 410 | merge-descriptors@1.0.1: 411 | version "1.0.1" 412 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 413 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 414 | 415 | methods@~1.1.2: 416 | version "1.1.2" 417 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 418 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 419 | 420 | mime-db@1.44.0: 421 | version "1.44.0" 422 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 423 | integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== 424 | 425 | mime-types@~2.1.24: 426 | version "2.1.27" 427 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 428 | integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== 429 | dependencies: 430 | mime-db "1.44.0" 431 | 432 | mime@1.6.0: 433 | version "1.6.0" 434 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 435 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 436 | 437 | minimatch@^3.0.4: 438 | version "3.0.4" 439 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 440 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 441 | dependencies: 442 | brace-expansion "^1.1.7" 443 | 444 | minimist@^1.2.5: 445 | version "1.2.5" 446 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 447 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 448 | 449 | mkdirp@^0.5.3: 450 | version "0.5.5" 451 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 452 | integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== 453 | dependencies: 454 | minimist "^1.2.5" 455 | 456 | ms@2.0.0: 457 | version "2.0.0" 458 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 459 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 460 | 461 | ms@2.1.1: 462 | version "2.1.1" 463 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 464 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 465 | 466 | negotiator@0.6.2: 467 | version "0.6.2" 468 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 469 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 470 | 471 | on-finished@~2.3.0: 472 | version "2.3.0" 473 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 474 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 475 | dependencies: 476 | ee-first "1.1.1" 477 | 478 | once@^1.3.0: 479 | version "1.4.0" 480 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 481 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 482 | dependencies: 483 | wrappy "1" 484 | 485 | parseurl@~1.3.3: 486 | version "1.3.3" 487 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 488 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 489 | 490 | path-is-absolute@^1.0.0: 491 | version "1.0.1" 492 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 493 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 494 | 495 | path-parse@^1.0.6: 496 | version "1.0.6" 497 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 498 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 499 | 500 | path-to-regexp@0.1.7: 501 | version "0.1.7" 502 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 503 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 504 | 505 | prettier@^2.0.5: 506 | version "2.0.5" 507 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" 508 | integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== 509 | 510 | proxy-addr@~2.0.5: 511 | version "2.0.6" 512 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 513 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== 514 | dependencies: 515 | forwarded "~0.1.2" 516 | ipaddr.js "1.9.1" 517 | 518 | qs@6.7.0: 519 | version "6.7.0" 520 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 521 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== 522 | 523 | range-parser@~1.2.1: 524 | version "1.2.1" 525 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 526 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 527 | 528 | raw-body@2.4.0: 529 | version "2.4.0" 530 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 531 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== 532 | dependencies: 533 | bytes "3.1.0" 534 | http-errors "1.7.2" 535 | iconv-lite "0.4.24" 536 | unpipe "1.0.0" 537 | 538 | resolve@^1.3.2: 539 | version "1.17.0" 540 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" 541 | integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== 542 | dependencies: 543 | path-parse "^1.0.6" 544 | 545 | safe-buffer@5.1.2: 546 | version "5.1.2" 547 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 548 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 549 | 550 | "safer-buffer@>= 2.1.2 < 3": 551 | version "2.1.2" 552 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 553 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 554 | 555 | semver@^5.3.0: 556 | version "5.7.1" 557 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 558 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 559 | 560 | send@0.17.1: 561 | version "0.17.1" 562 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 563 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== 564 | dependencies: 565 | debug "2.6.9" 566 | depd "~1.1.2" 567 | destroy "~1.0.4" 568 | encodeurl "~1.0.2" 569 | escape-html "~1.0.3" 570 | etag "~1.8.1" 571 | fresh "0.5.2" 572 | http-errors "~1.7.2" 573 | mime "1.6.0" 574 | ms "2.1.1" 575 | on-finished "~2.3.0" 576 | range-parser "~1.2.1" 577 | statuses "~1.5.0" 578 | 579 | serve-static@1.14.1: 580 | version "1.14.1" 581 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 582 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== 583 | dependencies: 584 | encodeurl "~1.0.2" 585 | escape-html "~1.0.3" 586 | parseurl "~1.3.3" 587 | send "0.17.1" 588 | 589 | setprototypeof@1.1.1: 590 | version "1.1.1" 591 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 592 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== 593 | 594 | sprintf-js@~1.0.2: 595 | version "1.0.3" 596 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 597 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 598 | 599 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 600 | version "1.5.0" 601 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 602 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 603 | 604 | supports-color@^5.3.0: 605 | version "5.5.0" 606 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 607 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 608 | dependencies: 609 | has-flag "^3.0.0" 610 | 611 | toidentifier@1.0.0: 612 | version "1.0.0" 613 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 614 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== 615 | 616 | tslib@^1.10.0, tslib@^1.8.1: 617 | version "1.13.0" 618 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" 619 | integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== 620 | 621 | tslint@^6.1.2: 622 | version "6.1.2" 623 | resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.2.tgz#2433c248512cc5a7b2ab88ad44a6b1b34c6911cf" 624 | integrity sha512-UyNrLdK3E0fQG/xWNqAFAC5ugtFyPO4JJR1KyyfQAyzR8W0fTRrC91A8Wej4BntFzcvETdCSDa/4PnNYJQLYiA== 625 | dependencies: 626 | "@babel/code-frame" "^7.0.0" 627 | builtin-modules "^1.1.1" 628 | chalk "^2.3.0" 629 | commander "^2.12.1" 630 | diff "^4.0.1" 631 | glob "^7.1.1" 632 | js-yaml "^3.13.1" 633 | minimatch "^3.0.4" 634 | mkdirp "^0.5.3" 635 | resolve "^1.3.2" 636 | semver "^5.3.0" 637 | tslib "^1.10.0" 638 | tsutils "^2.29.0" 639 | 640 | tsutils@^2.29.0: 641 | version "2.29.0" 642 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" 643 | integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== 644 | dependencies: 645 | tslib "^1.8.1" 646 | 647 | type-is@~1.6.17, type-is@~1.6.18: 648 | version "1.6.18" 649 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 650 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 651 | dependencies: 652 | media-typer "0.3.0" 653 | mime-types "~2.1.24" 654 | 655 | typescript@^3.9.7: 656 | version "3.9.7" 657 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" 658 | integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== 659 | 660 | unpipe@1.0.0, unpipe@~1.0.0: 661 | version "1.0.0" 662 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 663 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 664 | 665 | utils-merge@1.0.1: 666 | version "1.0.1" 667 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 668 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 669 | 670 | vary@~1.1.2: 671 | version "1.1.2" 672 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 673 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 674 | 675 | wrappy@1: 676 | version "1.0.2" 677 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 678 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 679 | -------------------------------------------------------------------------------- /server/kotlin/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | build/ 3 | .gradle/ 4 | 5 | -------------------------------------------------------------------------------- /server/kotlin/README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Ktor Server 2 | 3 | # Overview 4 | 5 | This is a very simple server that will receive raw audio and write it to a file. 6 | 7 | It will write two different files depending on what it receives: 8 | 9 | - `adc.raw` - samples from the analog to digital converter. 10 | - `i2s.raw` - samples from the I2S microphone. 11 | 12 | Samples are appended to any existing file so if you want to start again from scratch you will need to delete the raw files. 13 | 14 | To import the files use the "raw" import option on Audacity - this will let you specify the size of the samples (signed 16 bit), number of channels (1 or 2 depending on if you are capturing one or two channels), and sample rate (16KHz is the default in the code). 15 | 16 | # Prerequisites 17 | 18 | You will need to have [kotlin](https://github.com/JetBrains/kotlin/) installed. 19 | 20 | [Intellij](https://www.jetbrains.com/idea/) by JetBrains is a good IDE to work on this project. 21 | 22 | # Build 23 | 24 | ``` 25 | ./gradlew build 26 | ``` 27 | 28 | # Running the server 29 | 30 | ``` 31 | ./gradlew run 32 | ``` 33 | -------------------------------------------------------------------------------- /server/kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | val ktor_version: String by project 2 | val kotlin_version: String by project 3 | val logback_version: String by project 4 | 5 | plugins { 6 | application 7 | kotlin("jvm") version "1.6.0" 8 | } 9 | 10 | group = "atomic14.esp32.audio" 11 | version = "0.0.1" 12 | 13 | application { 14 | mainClassName = "atomic14.esp32.audio.ApplicationKt" 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") 23 | implementation("io.ktor:ktor-server-core:$ktor_version") 24 | implementation("io.ktor:ktor-server-netty:$ktor_version") 25 | implementation("io.ktor:ktor-server-netty:$ktor_version") 26 | implementation("ch.qos.logback:logback-classic:$logback_version") 27 | implementation("io.ktor:ktor-html-builder:$ktor_version") 28 | 29 | testImplementation("io.ktor:ktor-server-tests:$ktor_version") 30 | 31 | } 32 | 33 | kotlin.sourceSets["main"].kotlin.srcDirs("src") 34 | kotlin.sourceSets["test"].kotlin.srcDirs("test") 35 | 36 | sourceSets["main"].resources.srcDirs("resources") 37 | sourceSets["test"].resources.srcDirs("testresources") 38 | -------------------------------------------------------------------------------- /server/kotlin/gradle.properties: -------------------------------------------------------------------------------- 1 | ktor_version=1.6.6 2 | kotlin.code.style=official 3 | kotlin_version=1.6.0 4 | logback_version=1.2.7 5 | -------------------------------------------------------------------------------- /server/kotlin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomic14/esp32_audio/4a39101ea0083aa12dcd3d838c3e51613ecdf3e3/server/kotlin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /server/kotlin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /server/kotlin/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /server/kotlin/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /server/kotlin/resources/application.conf: -------------------------------------------------------------------------------- 1 | //ktor { 2 | // deployment { 3 | // port = 5003 4 | // port = ${?PORT} 5 | // } 6 | // application { 7 | // modules = [ atomic14.esp32.audio.ApplicationKt.module ] 8 | // } 9 | //} 10 | -------------------------------------------------------------------------------- /server/kotlin/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /server/kotlin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "esp32Audio" 2 | -------------------------------------------------------------------------------- /server/kotlin/src/Application.kt: -------------------------------------------------------------------------------- 1 | package atomic14.esp32.audio 2 | 3 | import io.ktor.application.call 4 | import io.ktor.application.install 5 | import io.ktor.features.CallLogging 6 | import io.ktor.features.DefaultHeaders 7 | import io.ktor.html.respondHtml 8 | import io.ktor.http.ContentType 9 | import io.ktor.http.HttpStatusCode 10 | import io.ktor.http.content.files 11 | import io.ktor.http.content.staticRootFolder 12 | import io.ktor.http.fromFilePath 13 | import io.ktor.request.path 14 | import io.ktor.request.receive 15 | import io.ktor.response.respondRedirect 16 | import io.ktor.routing.Route 17 | import io.ktor.routing.get 18 | import io.ktor.routing.post 19 | import io.ktor.routing.route 20 | import io.ktor.routing.routing 21 | import io.ktor.server.engine.embeddedServer 22 | import io.ktor.server.netty.Netty 23 | import io.ktor.util.combineSafe 24 | import java.io.File 25 | import java.io.InputStream 26 | import java.nio.file.Files 27 | import java.nio.file.StandardOpenOption 28 | import java.text.SimpleDateFormat 29 | import java.util.* 30 | import kotlinx.coroutines.Dispatchers 31 | import kotlinx.coroutines.withContext 32 | import kotlinx.html.a 33 | import kotlinx.html.body 34 | import kotlinx.html.h1 35 | import kotlinx.html.hr 36 | import kotlinx.html.style 37 | import kotlinx.html.table 38 | import kotlinx.html.tbody 39 | import kotlinx.html.td 40 | import kotlinx.html.th 41 | import kotlinx.html.thead 42 | import kotlinx.html.tr 43 | 44 | fun main() { 45 | val root = File("files").takeIf { it.exists() } ?: error("Can't locate files folder") 46 | 47 | embeddedServer(Netty, port = 5003) { 48 | install(DefaultHeaders) 49 | install(CallLogging) 50 | routing { 51 | get("/") { 52 | println("get my files") 53 | call.respondRedirect("/myfiles") 54 | } 55 | post("/adc_samples") { 56 | println("post adc_samples") 57 | val file = File("files/adc.raw") 58 | if (file.exists().not()) { 59 | file.createNewFile() 60 | } 61 | 62 | withContext (Dispatchers.IO) { 63 | val data = call.receive() 64 | Files.write(file.toPath(), data.readBytes(), StandardOpenOption.APPEND) 65 | } 66 | 67 | call.response.status(HttpStatusCode.OK) 68 | } 69 | post("/i2s_samples") { 70 | println("post i2s_samples") 71 | val file = File("files", "i2s.raw") 72 | if (file.exists().not()) { 73 | file.createNewFile() 74 | } 75 | 76 | withContext (Dispatchers.IO) { 77 | val data = call.receive() 78 | Files.write(file.toPath(), data.readBytes(), StandardOpenOption.APPEND) 79 | } 80 | call.response.status(HttpStatusCode.OK) 81 | } 82 | 83 | route("/myfiles") { 84 | println("route myfiles") 85 | files(root) 86 | listing(root) 87 | } 88 | } 89 | }.start(wait = true) 90 | } 91 | 92 | fun Route.listing(folder: File) { 93 | val dir = staticRootFolder.combine(folder) 94 | val pathParameterName = "static-content-path-parameter" 95 | val dateFormat = SimpleDateFormat("dd-MMM-YYYY HH:mm") 96 | get("{$pathParameterName...}") { 97 | val relativePath = call.parameters.getAll(pathParameterName)?.joinToString(File.separator) ?: return@get 98 | val file = dir.combineSafe(relativePath) 99 | if (file.isDirectory) { 100 | val isRoot = relativePath.trim('/').isEmpty() 101 | val files = file.listSuspend(includeParent = !isRoot) 102 | val base = call.request.path().trimEnd('/') 103 | call.respondHtml { 104 | body { 105 | h1 { 106 | +"Index of $base/" 107 | } 108 | hr {} 109 | table { 110 | style = "width: 100%;" 111 | thead { 112 | tr { 113 | for (column in listOf("Name", "Last Modified", "Size", "MimeType")) { 114 | th { 115 | style = "width: 25%; text-align: left;" 116 | +column 117 | } 118 | } 119 | } 120 | } 121 | tbody { 122 | for (finfo in files) { 123 | val rname = if (finfo.directory) "${finfo.name}/" else finfo.name 124 | tr { 125 | td { 126 | if (finfo.name == "..") { 127 | a(File(base).parent) { +rname } 128 | } else { 129 | a("$base/$rname") { +rname } 130 | } 131 | } 132 | td { 133 | +dateFormat.format(finfo.date) 134 | } 135 | td { 136 | +(if (finfo.directory) "-" else "${finfo.size}") 137 | } 138 | td { 139 | +(ContentType.fromFilePath(finfo.name).firstOrNull()?.toString() ?: "-") 140 | } 141 | } 142 | } 143 | } 144 | } 145 | hr {} 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | private fun File?.combine(file: File) = when { 153 | this == null -> file 154 | else -> resolve(file) 155 | } 156 | 157 | data class FileInfo(val name: String, val date: Date, val directory: Boolean, val size: Long) 158 | 159 | suspend fun File.listSuspend(includeParent: Boolean = false): List { 160 | val file = this 161 | return withContext(Dispatchers.IO) { 162 | listOfNotNull( 163 | if (includeParent) { 164 | FileInfo("..", Date(), true, 0L) 165 | } else { 166 | null 167 | } 168 | ) + file.listFiles().toList().map { 169 | FileInfo(it.name, Date(it.lastModified()), it.isDirectory, it.length()) 170 | }.sortedWith(comparators( 171 | Comparator { a, b -> -a.directory.compareTo(b.directory) }, 172 | Comparator { a, b -> a.name.compareTo(b.name, ignoreCase = true) } 173 | )) 174 | } 175 | } 176 | 177 | fun comparators(vararg comparators: Comparator): Comparator { 178 | return Comparator { l, r -> 179 | for (comparator in comparators) { 180 | val result = comparator.compare(l, r) 181 | if (result != 0) return@Comparator result 182 | } 183 | return@Comparator 0 184 | } 185 | } 186 | 187 | operator fun Comparator.plus(other: Comparator): Comparator = comparators(this, other) 188 | -------------------------------------------------------------------------------- /signal-generator/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /signal-generator/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /signal-generator/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } -------------------------------------------------------------------------------- /signal-generator/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /signal-generator/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /signal-generator/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32doit-devkit-v1] 12 | platform = espressif32 13 | board = esp32doit-devkit-v1 14 | framework = arduino 15 | upload_port = /dev/cu.SLAB_USBtoUART 16 | monitor_port = /dev/cu.SLAB_USBtoUART 17 | monitor_speed = 115200 -------------------------------------------------------------------------------- /signal-generator/src/SignalGenerator.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "driver/i2s.h" 4 | #include 5 | #include 6 | #include "SignalGenerator.h" 7 | 8 | void i2sWriterTask(void *param) 9 | { 10 | SignalGenerator *output = (SignalGenerator *)param; 11 | int buffer_position = 0; 12 | // create a triangle wave 13 | uint16_t sawTooth[1024]; 14 | for (int i = 0; i < 1024; i += 2) 15 | { 16 | sawTooth[i] = sawTooth[i + 1] = (i % 32) << 11; 17 | Serial.printf("%d\n", sawTooth[i]); 18 | } 19 | while (true) 20 | { 21 | // wait for some data to be requested 22 | i2s_event_t evt; 23 | if (xQueueReceive(output->m_i2sQueue, &evt, portMAX_DELAY) == pdPASS) 24 | { 25 | if (evt.type == I2S_EVENT_TX_DONE) 26 | { 27 | size_t bytesWritten = 0; 28 | do 29 | { 30 | if (buffer_position >= 1024) 31 | { 32 | // Serial.println("Resetting buffer pos"); 33 | buffer_position = 0; 34 | } 35 | int availableSamples = 1024 - buffer_position; 36 | // write data to the i2s peripheral 37 | i2s_write(I2S_NUM_0, sawTooth + buffer_position, availableSamples * 2, &bytesWritten, portMAX_DELAY); 38 | buffer_position += bytesWritten / 2; 39 | // Serial.printf("Wrote bytes %d, available bytes %d\n", bytesWritten, availableSamples * 2); 40 | } while (bytesWritten > 0); 41 | } 42 | } 43 | } 44 | } 45 | 46 | void SignalGenerator::start(long freq) 47 | { 48 | // i2s config for writing both channels of I2S 49 | i2s_config_t i2sConfig = { 50 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), 51 | .sample_rate = freq, 52 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 53 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 54 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB), 55 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 56 | .dma_buf_count = 4, 57 | .dma_buf_len = 64, 58 | .use_apll = 1 59 | }; 60 | 61 | //install and start i2s driver 62 | i2s_driver_install(I2S_NUM_0, &i2sConfig, 4, &m_i2sQueue); 63 | // enable the DAC channels 64 | i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); 65 | // clear the DMA buffers 66 | i2s_zero_dma_buffer(I2S_NUM_0); 67 | // start a task to write samples to the i2s peripheral 68 | TaskHandle_t writerTaskHandle; 69 | xTaskCreate(i2sWriterTask, "i2s Writer Task", 4096, this, 1, &writerTaskHandle); 70 | } 71 | 72 | void SignalGenerator::set_frequency(long new_freq) 73 | { 74 | i2s_set_sample_rates(I2S_NUM_0, new_freq); 75 | } 76 | -------------------------------------------------------------------------------- /signal-generator/src/SignalGenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef __sampler_base_h__ 2 | #define __sampler_base_h__ 3 | 4 | #include 5 | #include "driver/i2s.h" 6 | 7 | /** 8 | * Base Class for both the ADC and I2S sampler 9 | **/ 10 | class SignalGenerator 11 | { 12 | private: 13 | // I2S write task 14 | TaskHandle_t m_i2sWriterTaskHandle; 15 | // i2s writer queue 16 | QueueHandle_t m_i2sQueue; 17 | 18 | public: 19 | void start(long freq); 20 | void set_frequency(long new_freq); 21 | 22 | friend void i2sWriterTask(void *param); 23 | }; 24 | 25 | #endif -------------------------------------------------------------------------------- /signal-generator/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "SignalGenerator.h" 4 | 5 | U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/15, /* data=*/4, /* reset=*/16); 6 | 7 | SignalGenerator *signalGenerator; 8 | 9 | char line1[200]; 10 | long freq = 10000; 11 | 12 | void show_freq() 13 | { 14 | sprintf(line1, "Freq %ldKHz", freq / 1000); 15 | u8x8.clearLine(0); 16 | u8x8.drawString(0, 0, line1); 17 | } 18 | 19 | void setup() 20 | { 21 | Serial.begin(115200); 22 | delay(2000); 23 | Serial.println("Started up..."); 24 | u8x8.begin(); 25 | u8x8.setFont(u8x8_font_chroma48medium8_r); 26 | pinMode(0, INPUT_PULLUP); 27 | signalGenerator = new SignalGenerator(); 28 | signalGenerator->start(freq); 29 | show_freq(); 30 | } 31 | 32 | void loop() 33 | { 34 | if (digitalRead(0) == 0) 35 | { 36 | freq += 10000; 37 | signalGenerator->set_frequency(freq); 38 | show_freq(); 39 | delay(1000); 40 | } 41 | } -------------------------------------------------------------------------------- /signal-generator/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------