├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── include └── README ├── lib ├── README ├── audio_input │ ├── library.json │ └── src │ │ ├── ADCSampler.cpp │ │ ├── ADCSampler.h │ │ ├── I2SMEMSSampler.cpp │ │ ├── I2SMEMSSampler.h │ │ ├── I2SSampler.cpp │ │ ├── I2SSampler.h │ │ └── SampleSink.h ├── audio_output │ ├── library.json │ └── src │ │ ├── DACOutput.cpp │ │ ├── DACOutput.h │ │ ├── I2SOutput.cpp │ │ ├── I2SOutput.h │ │ ├── Output.cpp │ │ ├── Output.h │ │ └── OutputBuffer.h ├── indicator_led │ └── src │ │ ├── GenericDevBoardIndicatorLed copy.cpp │ │ ├── GenericDevBoardIndicatorLed.h │ │ ├── IndicatorLed.cpp │ │ ├── IndicatorLed.h │ │ ├── TinyPICOIndicatorLed.cpp │ │ └── TinyPICOIndicatorLed.h └── transport │ └── src │ ├── EspNowTransport.cpp │ ├── EspNowTransport.h │ ├── Transport.cpp │ ├── Transport.h │ ├── UdpTransport.cpp │ └── UdpTransport.h ├── platformio.ini ├── src ├── Application.cpp ├── Application.h ├── config.h └── main.cpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.rb": "ruby", 4 | "SP_ENC.C": "cpp", 5 | "SP_DEC.C": "cpp", 6 | "DTX.C": "cpp", 7 | "HOST.C": "cpp", 8 | "VAD.C": "cpp" 9 | } 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 atomic14 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Overview 3 | 4 | THis is of fork from the Atomic14 project adapted for the Raspiaudio ESP MUSE PROTO board it could be purchased here raspiaudio.com/muse 5 | You do not need external components or wire as the board includes a built-in speaker and microphone, an external battery could be added. 6 | 7 | By default it is configured to use the ESPRESSIF protocol call ESPNOW so it does not requires any wifi router so it could be used outside. 8 | With the test I have made I could only achieve 50m range, this is not great I think it could be improved but I have not spent the time yet to do it. 9 | 10 | 11 | [demo video](https://youtu.be/eW-6VS1XR2Y) 12 | 13 | 14 | # Setup 15 | 16 | Everything is configured from the `src/config.h` file. 17 | 18 | The pins for the microphone and the amplifier board are all setup in the same `config.h` file. 19 | 20 | # Building and Running 21 | 22 | I'm using PlatformIO for this project so you will need to have that installed. Open up the project and connect your ESP32. You should be able to just hit build and run. 23 | 24 | Obviously, you'll need two ESP32 boards and components to do anything :) 25 | 26 | #useage 27 | 28 | to talk press the IO0 button, then release, of course you need 2 of them... 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/audio_input/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "flags": "-Ofast" 4 | } 5 | } -------------------------------------------------------------------------------- /lib/audio_input/src/ADCSampler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "driver/i2s.h" 3 | #include "driver/adc.h" 4 | #include "ADCSampler.h" 5 | #include "SampleSink.h" 6 | 7 | ADCSampler::ADCSampler(adc_unit_t adcUnit, adc1_channel_t adcChannel) 8 | { 9 | m_adcUnit = adcUnit; 10 | m_adcChannel = adcChannel; 11 | } 12 | 13 | void ADCSampler::configureI2S() 14 | { 15 | //init ADC pad 16 | i2s_set_adc_mode(m_adcUnit, m_adcChannel); 17 | // enable the adc 18 | i2s_adc_enable(getI2SPort()); 19 | } 20 | 21 | /** 22 | * Process the raw data that have been read from the I2S peripherals into samples 23 | **/ 24 | void ADCSampler::processI2SData(uint8_t *i2sData, size_t bytesRead) 25 | { 26 | uint16_t *rawSamples = (uint16_t *)i2sData; 27 | for (int i = 0; i < bytesRead / 2; i++) 28 | { 29 | m_sample_sink->add_sample((2048 - (rawSamples[i] & 0xfff)) * 15); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/audio_input/src/ADCSampler.h: -------------------------------------------------------------------------------- 1 | #ifndef __adc_sampler_h__ 2 | #define __adc_sampler_h__ 3 | 4 | #include "I2SSampler.h" 5 | 6 | class ADCSampler : public I2SSampler 7 | { 8 | private: 9 | adc_unit_t m_adcUnit; 10 | adc1_channel_t m_adcChannel; 11 | 12 | protected: 13 | void configureI2S(); 14 | void processI2SData(uint8_t *i2sData, size_t bytesRead); 15 | 16 | public: 17 | ADCSampler(adc_unit_t adc_unit, adc1_channel_t adc_channel); 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /lib/audio_input/src/I2SMEMSSampler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "driver/i2s.h" 3 | #include "soc/i2s_reg.h" 4 | #include "I2SMEMSSampler.h" 5 | #include "SampleSink.h" 6 | 7 | I2SMEMSSampler::I2SMEMSSampler(i2s_pin_config_t &i2sPins, bool fixSPH0645) 8 | { 9 | m_i2sPins = i2sPins; 10 | m_fixSPH0645 = fixSPH0645; 11 | } 12 | 13 | void I2SMEMSSampler::configureI2S() 14 | { 15 | if (m_fixSPH0645) 16 | { 17 | // FIXES for SPH0645 18 | REG_SET_BIT(I2S_TIMING_REG(getI2SPort()), BIT(9)); 19 | REG_SET_BIT(I2S_CONF_REG(getI2SPort()), I2S_RX_MSB_SHIFT); 20 | } 21 | 22 | i2s_set_pin(getI2SPort(), &m_i2sPins); 23 | } 24 | 25 | void I2SMEMSSampler::processI2SData(uint8_t *i2sData, size_t bytesRead) 26 | { 27 | 28 | int32_t *samples = (int32_t *)i2sData; 29 | for (int i = 0; i < bytesRead / 4; i++) 30 | { 31 | m_sample_sink->add_sample(samples[i] >> 14); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /lib/audio_input/src/I2SMEMSSampler.h: -------------------------------------------------------------------------------- 1 | #ifndef __i2s_sampler_h__ 2 | #define __i2s_sampler_h__ 3 | 4 | #include "I2SSampler.h" 5 | 6 | class I2SMEMSSampler : public I2SSampler 7 | { 8 | private: 9 | i2s_pin_config_t m_i2sPins; 10 | bool m_fixSPH0645; 11 | 12 | protected: 13 | void configureI2S(); 14 | void processI2SData(uint8_t *i2sData, size_t bytesRead); 15 | 16 | public: 17 | I2SMEMSSampler(i2s_pin_config_t &i2sPins, bool fixSPH0645 = false); 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /lib/audio_input/src/I2SSampler.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "I2SSampler.h" 4 | #include "driver/i2s.h" 5 | #include "SampleSink.h" 6 | 7 | void i2s_reader_task(void *param) 8 | { 9 | I2SSampler *sampler = (I2SSampler *)param; 10 | 11 | while (true) 12 | { 13 | // wait for some data to arrive on the queue 14 | // i2s_event_t evt; 15 | // if (xQueueReceive(sampler->m_i2sQueue, &evt, portMAX_DELAY) == pdPASS) 16 | // if (xQueueGenericReceive(sampler->m_i2sQueue, &evt, portMAX_DELAY, pdFALSE) == pdPASS) 17 | // { 18 | // if (evt.type == I2S_EVENT_RX_DONE) 19 | // { 20 | size_t bytesRead = 0; 21 | do 22 | { 23 | // read data from the I2S peripheral 24 | uint8_t i2sData[1024]; 25 | // read from i2s 26 | i2s_read(sampler->getI2SPort(), i2sData, 1024, &bytesRead, 10); 27 | /* 28 | for(int i=0;iprocessI2SData(i2sData, bytesRead); 36 | } while (bytesRead > 0); 37 | // } 38 | // } 39 | delay(1); 40 | } 41 | } 42 | 43 | void I2SSampler::start(i2s_port_t i2sPort, i2s_config_t &i2sConfig, SampleSink *sample_sink) 44 | { 45 | m_i2sPort = i2sPort; 46 | m_sample_sink = sample_sink; 47 | 48 | //install and start i2s driver 49 | //i2s_driver_install(m_i2sPort, &i2sConfig, 4, &m_i2sQueue); 50 | // set up the I2S configuration from the subclass 51 | //configureI2S(); 52 | if (!m_i2s_reader_task_handle) 53 | { 54 | // start a task to read samples from the ADC 55 | // xTaskCreatePinnedToCore(i2s_reader_task, "i2s Reader Task", 4096, this, 1, &m_i2s_reader_task_handle, 1); 56 | xTaskCreate(i2s_reader_task, "i2s Reader Task", 4096, this, 1, &m_i2s_reader_task_handle); 57 | } 58 | } 59 | 60 | void I2SSampler::stop() 61 | { 62 | vTaskDelete(m_i2s_reader_task_handle); 63 | // stop the i2S driver 64 | //i2s_driver_uninstall(m_i2sPort); 65 | // NOTE this leaves the task running - there's not really a clean way of terminating tasks 66 | } 67 | -------------------------------------------------------------------------------- /lib/audio_input/src/I2SSampler.h: -------------------------------------------------------------------------------- 1 | #ifndef __sampler_base_h__ 2 | #define __sampler_base_h__ 3 | 4 | #include 5 | #include 6 | 7 | class SampleSink; 8 | /** 9 | * Base Class for both the ADC and I2S sampler 10 | **/ 11 | class I2SSampler 12 | { 13 | private: 14 | // I2S reader task 15 | TaskHandle_t m_i2s_reader_task_handle = NULL; 16 | // i2s reader queue 17 | QueueHandle_t m_i2sQueue = NULL; 18 | // i2s port 19 | i2s_port_t m_i2sPort = I2S_NUM_0; 20 | 21 | protected: 22 | // the input buffer to store samples in 23 | SampleSink *m_sample_sink; 24 | 25 | virtual void configureI2S() = 0; 26 | virtual void processI2SData(uint8_t *i2sData, size_t bytesRead) = 0; 27 | i2s_port_t getI2SPort() 28 | { 29 | return m_i2sPort; 30 | } 31 | 32 | public: 33 | void start(i2s_port_t i2sPort, i2s_config_t &i2sConfig, SampleSink *sample_sink); 34 | void stop(); 35 | friend void i2s_reader_task(void *param); 36 | }; 37 | 38 | #endif -------------------------------------------------------------------------------- /lib/audio_input/src/SampleSink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * @brief Destination for audio data 7 | * 8 | */ 9 | class SampleSink 10 | { 11 | public: 12 | virtual void add_sample(int16_t sample) = 0; 13 | }; 14 | 15 | /** 16 | * @brief Double buffered samples - we can be processing one 17 | * set of samples while the other samples are being read. 18 | * 19 | */ 20 | class DoubleBuffer : public SampleSink 21 | { 22 | private: 23 | // double buffer so we can be capturing samples while sending data 24 | uint8_t *m_audioBuffer1; 25 | uint8_t *m_audioBuffer2; 26 | // current position in the audio buffer 27 | int32_t m_audioBufferPos = 0; 28 | // current audio buffer 29 | uint8_t *m_currentAudioBuffer; 30 | // buffer containing samples that have been captured already 31 | uint8_t *m_capturedAudioBuffer; 32 | // size of the audio buffer in samples 33 | int32_t m_bufferSize; 34 | // processing task handle - notified every time we reach the 35 | // end of a buffer 36 | TaskHandle_t m_processing_task_handle; 37 | 38 | public: 39 | DoubleBuffer(int buffer_size, TaskHandle_t processing_task_handle = NULL) 40 | { 41 | m_processing_task_handle = processing_task_handle; 42 | m_bufferSize = buffer_size; 43 | m_audioBuffer1 = (uint8_t *)malloc(m_bufferSize); 44 | m_audioBuffer2 = (uint8_t *)malloc(m_bufferSize); 45 | 46 | m_currentAudioBuffer = m_audioBuffer1; 47 | m_capturedAudioBuffer = m_audioBuffer2; 48 | } 49 | int32_t get_buffer_size() 50 | { 51 | return m_bufferSize; 52 | }; 53 | uint8_t *get_captured_audio_buffer() 54 | { 55 | return m_capturedAudioBuffer; 56 | } 57 | void add_sample(int16_t sample) 58 | { 59 | // add the sample to the current audio buffer converting to unsigned 8 bit PCM 60 | m_currentAudioBuffer[m_audioBufferPos] = (sample + 32768) >> 8; 61 | // m_currentAudioBuffer[m_audioBufferPos] = (uint8_t)((sample >> 8) + 128); 62 | m_audioBufferPos++; 63 | // have we filled the buffer with data? 64 | if (m_audioBufferPos == m_bufferSize) 65 | { 66 | // swap to the other buffer 67 | std::swap(m_currentAudioBuffer, m_capturedAudioBuffer); 68 | // reset the buffer position 69 | m_audioBufferPos = 0; 70 | // tell the writer task to save the data 71 | xTaskNotify(m_processing_task_handle, 1, eIncrement); 72 | } 73 | } 74 | }; -------------------------------------------------------------------------------- /lib/audio_output/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "flags": "-Ofast" 4 | } 5 | } -------------------------------------------------------------------------------- /lib/audio_output/src/DACOutput.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "DACOutput.h" 6 | 7 | void DACOutput::start(OutputBuffer *output_buffer) 8 | { 9 | Serial.println("Starting DAC output"); 10 | // i2s config for writing both channels of I2S 11 | i2s_config_t i2s_config = { 12 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), 13 | .sample_rate = 16000, 14 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 15 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 16 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB), 17 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 18 | .dma_buf_count = 2, 19 | .dma_buf_len = 1024, 20 | .use_apll = false, 21 | .tx_desc_auto_clear = true, 22 | .fixed_mclk = 0}; 23 | //install and start i2s driver 24 | i2s_driver_install(I2S_NUM_0, &i2s_config, 4, &m_i2s_queue); 25 | // enable the DAC channels 26 | i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN); 27 | // clear the DMA buffers 28 | i2s_zero_dma_buffer(I2S_NUM_0); 29 | // start sending samples 30 | Output::start(I2S_NUM_0, output_buffer); 31 | } 32 | -------------------------------------------------------------------------------- /lib/audio_output/src/DACOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef __dac_output_h__ 2 | #define __dac_output_h__ 3 | 4 | #include 5 | #include "Output.h" 6 | #include 7 | 8 | class OutputBuffer; 9 | /** 10 | * Base Class for both the ADC and I2S sampler 11 | **/ 12 | class DACOutput : public Output 13 | { 14 | public: 15 | void start(OutputBuffer *output_buffer); 16 | virtual int16_t process_sample(uint8_t sample) 17 | { 18 | // DAC needs unsigned 16 bit samples 19 | return sample << 8; 20 | } 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /lib/audio_output/src/I2SOutput.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "I2SOutput.h" 6 | 7 | void I2SOutput::start(i2s_port_t i2s_port, i2s_pin_config_t &i2s_pins, OutputBuffer *output_buffer) 8 | { 9 | // i2s config for writing both channels of I2S 10 | i2s_config_t i2s_config = { 11 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX), 12 | .sample_rate = 16000, 13 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 14 | .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, 15 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), 16 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 17 | .dma_buf_count = 4, 18 | .dma_buf_len = 128, 19 | .use_apll = false, 20 | .tx_desc_auto_clear = true, 21 | .fixed_mclk = 0}; 22 | //install and start i2s driver 23 | i2s_driver_install(i2s_port, &i2s_config, 4, &m_i2s_queue); 24 | // set up the i2s pins 25 | i2s_set_pin(i2s_port, &i2s_pins); 26 | // clear the DMA buffers 27 | i2s_zero_dma_buffer(i2s_port); 28 | // start sending data 29 | Output::start(i2s_port, output_buffer); 30 | } 31 | -------------------------------------------------------------------------------- /lib/audio_output/src/I2SOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef __i2s_output_h__ 2 | #define __i2s_output_h__ 3 | 4 | #include 5 | #include 6 | #include "Output.h" 7 | 8 | class OutputBuffer; 9 | /** 10 | * Base Class for both the ADC and I2S sampler 11 | **/ 12 | class I2SOutput : public Output 13 | { 14 | public: 15 | void start(i2s_port_t i2sPort, i2s_pin_config_t &i2sPins, OutputBuffer *output_buffer); 16 | virtual int16_t process_sample(uint8_t sample) 17 | { 18 | // I2S needs signed 16 bit samples 19 | int16_t processed = sample; 20 | return (processed - 128) << 7; 21 | } 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /lib/audio_output/src/Output.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include "Output.h" 6 | #include "OutputBuffer.h" 7 | 8 | // number of frames to try and send at once (a frame is a left and right sample) 9 | const int NUM_FRAMES_TO_SEND = 64; 10 | 11 | void i2s_writer_task(void *param) 12 | { 13 | Output *output = (Output *)param; 14 | int available_bytes = 0; 15 | int buffer_position = 0; 16 | // the raw samples 17 | uint8_t *raw_samples = (uint8_t *)malloc(NUM_FRAMES_TO_SEND); 18 | // this will contained the prepared samples for sending to the I2S device 19 | int16_t *frames = (int16_t *)malloc(2 * sizeof(int16_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_i2s_queue, &evt, portMAX_DELAY) == pdPASS) 25 | { 26 | if (evt.type == I2S_EVENT_TX_DONE) 27 | { 28 | size_t bytes_written = 0; 29 | do 30 | { 31 | if (available_bytes == 0) 32 | { 33 | // fill the output buffer with more data 34 | output->m_output_buffer->remove_samples(raw_samples, NUM_FRAMES_TO_SEND); 35 | for (int i = 0; i < NUM_FRAMES_TO_SEND; i++) 36 | { 37 | int16_t sample = output->process_sample(raw_samples[i]); 38 | 39 | frames[i * 2] = sample; 40 | frames[i * 2 + 1] = sample; 41 | } 42 | //printf("eof\n"); 43 | available_bytes = NUM_FRAMES_TO_SEND * 2 * sizeof(uint16_t); 44 | buffer_position = 0; 45 | } 46 | // write data to the i2s peripheral 47 | i2s_write(output->m_i2s_port, buffer_position + (uint8_t *)frames, 48 | available_bytes, &bytes_written, portMAX_DELAY); 49 | available_bytes -= bytes_written; 50 | buffer_position += bytes_written; 51 | // Serial.println(bytes_written); 52 | } while (bytes_written > 0); 53 | // } while (available_bytes > 0); 54 | } 55 | } 56 | } 57 | } 58 | 59 | void Output::start(i2s_port_t i2s_port, OutputBuffer *output_buffer) 60 | { 61 | m_i2s_port = i2s_port; 62 | m_output_buffer = output_buffer; 63 | // start a task to write samples to the i2s peripheral 64 | if (m_i2s_sample_task_handle == NULL) 65 | { 66 | xTaskCreate(i2s_writer_task, "i2s Writer Task", 4096, this, 1, &m_i2s_sample_task_handle); 67 | } 68 | } 69 | 70 | void Output::stop() 71 | { 72 | // stop the i2S driver 73 | //i2s_driver_uninstall(m_i2s_port); 74 | // NOTE this leaves the task running - there's not really a clean way of terminating tasks 75 | } -------------------------------------------------------------------------------- /lib/audio_output/src/Output.h: -------------------------------------------------------------------------------- 1 | #ifndef __output_h__ 2 | #define __output_h__ 3 | 4 | #include 5 | #include 6 | 7 | class OutputBuffer; 8 | /** 9 | * Base Class for both the DAC and I2S output 10 | **/ 11 | class Output 12 | { 13 | protected: 14 | i2s_port_t m_i2s_port = I2S_NUM_0; 15 | // I2S write task 16 | TaskHandle_t m_i2s_sample_task_handle = NULL; 17 | // i2s writer queue 18 | QueueHandle_t m_i2s_queue = NULL; 19 | // output buffer 20 | OutputBuffer *m_output_buffer = NULL; 21 | 22 | void start(i2s_port_t i2sPort, OutputBuffer *output_buffer); 23 | 24 | public: 25 | void stop(); 26 | // override this in derived classes to turn the 8 bit PCM sample into 27 | // something the output device expects 28 | virtual int16_t process_sample(uint8_t sample) = 0; 29 | // give the task function access to our data 30 | friend void i2s_writer_task(void *param); 31 | }; 32 | 33 | #endif -------------------------------------------------------------------------------- /lib/audio_output/src/OutputBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @brief Circular buffer for 8 bit unsigned PCM samples 8 | * 9 | */ 10 | class OutputBuffer 11 | { 12 | private: 13 | // how many samples should we buffer before outputting data? 14 | int m_number_samples_to_buffer; 15 | // where are we reading from 16 | int m_read_head; 17 | // where are we writing to 18 | int m_write_head; 19 | // keep track of how many samples we have 20 | int m_available_samples; 21 | // the total size of the buffer 22 | int m_buffer_size; 23 | // are we currently buffering samples? 24 | bool m_buffering; 25 | // the sample buffer 26 | uint8_t *m_buffer; 27 | // thread safety 28 | SemaphoreHandle_t m_semaphore; 29 | 30 | public: 31 | OutputBuffer(int number_samples_to_buffer) : m_number_samples_to_buffer(number_samples_to_buffer) 32 | { 33 | // create a semaphore and make it available for locking 34 | m_semaphore = xSemaphoreCreateBinary(); 35 | xSemaphoreGive(m_semaphore); 36 | // set reading and writing to the beginning of the buffer 37 | m_read_head = 0; 38 | m_write_head = 0; 39 | m_available_samples = 0; 40 | // we'll start off buffering data as we have no samples yet 41 | m_buffering = true; 42 | // make sufficient space for the bufferring and incoming data 43 | m_buffer_size = 3 * number_samples_to_buffer; 44 | m_buffer = (uint8_t *)malloc(m_buffer_size); 45 | memset(m_buffer, 0, m_buffer_size); 46 | if (!m_buffer) 47 | { 48 | Serial.println("Failed to allocate buffer"); 49 | } 50 | } 51 | 52 | void add_samples(const uint8_t *samples, int count) 53 | { 54 | xSemaphoreTake(m_semaphore, portMAX_DELAY); 55 | // copy the samples into the buffer wrapping around as needed 56 | for (int i = 0; i < count; i++) 57 | { 58 | m_buffer[m_write_head] = samples[i]; 59 | m_write_head = (m_write_head + 1) % m_buffer_size; 60 | } 61 | m_available_samples += count; 62 | xSemaphoreGive(m_semaphore); 63 | } 64 | 65 | void remove_samples(uint8_t *samples, int count) 66 | { 67 | xSemaphoreTake(m_semaphore, portMAX_DELAY); 68 | for (int i = 0; i < count; i++) 69 | { 70 | samples[i] = 128; 71 | // if we have no samples and we aren't already buffering then we need to start buffering 72 | if (m_available_samples == 0 && !m_buffering) 73 | { 74 | Serial.println("Buffering"); 75 | m_buffering = true; 76 | samples[i] = 128; 77 | } 78 | // are we buffering? 79 | if (m_buffering && m_available_samples < m_number_samples_to_buffer) 80 | { 81 | // just return 0 as we don't have enough samples yet 82 | samples[i] = 128; 83 | } 84 | else 85 | { 86 | // we've buffered enough samples so no need to buffer anymore 87 | m_buffering = false; 88 | // just send back the samples we've got and move the read head forward 89 | samples[i] = m_buffer[m_read_head]; 90 | m_read_head = (m_read_head + 1) % m_buffer_size; 91 | m_available_samples--; 92 | } 93 | } 94 | xSemaphoreGive(m_semaphore); 95 | } 96 | }; -------------------------------------------------------------------------------- /lib/indicator_led/src/GenericDevBoardIndicatorLed copy.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "GenericDevBoardIndicatorLed.h" 3 | 4 | const uint8_t BUILT_IN_LED = GPIO_NUM_2; 5 | 6 | GenericDevBoardIndicatorLed::GenericDevBoardIndicatorLed() 7 | { 8 | pinMode(BUILT_IN_LED, OUTPUT); 9 | } 10 | 11 | // we don't really have any colors so just use the built in LED 12 | void GenericDevBoardIndicatorLed::set_led_rgb(uint32_t color) 13 | { 14 | if (color == 0) 15 | { 16 | digitalWrite(BUILT_IN_LED, LOW); 17 | } 18 | else 19 | { 20 | digitalWrite(BUILT_IN_LED, HIGH); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/indicator_led/src/GenericDevBoardIndicatorLed.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "IndicatorLed.h" 4 | 5 | class GenericDevBoardIndicatorLed : public IndicatorLed 6 | { 7 | protected: 8 | void set_led_rgb(uint32_t color); 9 | 10 | public: 11 | GenericDevBoardIndicatorLed(); 12 | }; -------------------------------------------------------------------------------- /lib/indicator_led/src/IndicatorLed.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "IndicatorLed.h" 3 | 4 | void update_indicator_task(void *param) 5 | { 6 | IndicatorLed *indicator = reinterpret_cast(param); 7 | while (true) 8 | { 9 | if (indicator->m_is_flashing) 10 | { 11 | indicator->set_led_rgb(indicator->m_flash_color); 12 | vTaskDelay(100); 13 | } 14 | indicator->set_led_rgb(indicator->m_default_color); 15 | vTaskDelay(100); 16 | } 17 | } 18 | 19 | void IndicatorLed::begin() 20 | { 21 | TaskHandle_t task_handle; 22 | xTaskCreate(update_indicator_task, "Indicator LED Task", 4096, this, 0, &task_handle); 23 | } 24 | 25 | void IndicatorLed::set_is_flashing(bool is_flashing, uint32_t flash_color) 26 | { 27 | m_is_flashing = is_flashing; 28 | m_flash_color = flash_color; 29 | } 30 | void IndicatorLed::set_default_color(uint32_t color) 31 | { 32 | m_default_color = color; 33 | } -------------------------------------------------------------------------------- /lib/indicator_led/src/IndicatorLed.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class IndicatorLed 4 | { 5 | private: 6 | bool m_is_flashing = false; 7 | uint32_t m_default_color = 0; 8 | uint32_t m_flash_color = 0; 9 | 10 | protected: 11 | virtual void set_led_rgb(uint32_t color) = 0; 12 | 13 | public: 14 | void begin(); 15 | void set_is_flashing(bool is_flashing, uint32_t flash_color); 16 | void set_default_color(uint32_t color); 17 | 18 | friend void update_indicator_task(void *param); 19 | }; 20 | -------------------------------------------------------------------------------- /lib/indicator_led/src/TinyPICOIndicatorLed.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "TinyPICOIndicatorLed.h" 4 | 5 | TinyPICOIndicatorLed::TinyPICOIndicatorLed() 6 | { 7 | m_tp = new TinyPICO(); 8 | } 9 | void TinyPICOIndicatorLed::set_led_rgb(uint32_t color) 10 | { 11 | m_tp->DotStar_SetPixelColor(color); 12 | } 13 | -------------------------------------------------------------------------------- /lib/indicator_led/src/TinyPICOIndicatorLed.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "IndicatorLed.h" 4 | 5 | class TinyPICO; 6 | class TinyPICOIndicatorLed : public IndicatorLed 7 | { 8 | private: 9 | TinyPICO *m_tp = NULL; 10 | 11 | protected: 12 | void set_led_rgb(uint32_t color); 13 | 14 | public: 15 | TinyPICOIndicatorLed(); 16 | }; -------------------------------------------------------------------------------- /lib/transport/src/EspNowTransport.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "OutputBuffer.h" 5 | #include "EspNowTransport.h" 6 | 7 | const int MAX_ESP_NOW_PACKET_SIZE = 250; 8 | const uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 9 | 10 | static EspNowTransport *instance = NULL; 11 | 12 | void receiveCallback(const uint8_t *macAddr, const uint8_t *data, int dataLen) 13 | { 14 | // annoyingly we can't pass an param into this so we need to do a bit of hack to access the EspNowTransport instance 15 | instance->m_last_packet_received = millis(); 16 | instance->m_output_buffer->add_samples(data, dataLen); 17 | //for(int i=0; i> 8; 14 | m_index++; 15 | // have we reached a full packet? 16 | if (m_index == m_buffer_size) 17 | { 18 | if (m_should_send) 19 | { 20 | send(); 21 | } 22 | m_index = 0; 23 | } 24 | } -------------------------------------------------------------------------------- /lib/transport/src/Transport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "SampleSink.h" 4 | 5 | class OutputBuffer; 6 | 7 | class Transport : public SampleSink 8 | { 9 | protected: 10 | // audio buffer for samples we need to send 11 | uint8_t *m_buffer = NULL; 12 | int m_buffer_size = 0; 13 | int m_index = 0; 14 | // should we send 15 | bool m_should_send = false; 16 | // when was the last time we recieved a packed? 17 | unsigned long m_last_packet_received = 0; 18 | 19 | OutputBuffer *m_output_buffer = NULL; 20 | 21 | virtual void send() = 0; 22 | 23 | public: 24 | Transport(OutputBuffer *output_buffer, size_t buffer_size); 25 | void add_sample(int16_t sample); 26 | void should_send(bool should_send) 27 | { 28 | m_should_send = should_send; 29 | } 30 | unsigned long get_last_packet_received() 31 | { 32 | return m_last_packet_received; 33 | } 34 | virtual bool begin() = 0; 35 | }; -------------------------------------------------------------------------------- /lib/transport/src/UdpTransport.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "UdpTransport.h" 4 | #include "OutputBuffer.h" 5 | 6 | const int MAX_UDP_SIZE = 1436; 7 | 8 | UdpTransport::UdpTransport(OutputBuffer *output_buffer) : Transport(output_buffer, MAX_UDP_SIZE) 9 | { 10 | } 11 | 12 | unsigned long last_packet; 13 | bool UdpTransport::begin() 14 | { 15 | udp = new AsyncUDP(); 16 | last_packet = millis(); 17 | if (udp->listen(8192)) 18 | { 19 | udp->onPacket([this](AsyncUDPPacket packet) { 20 | // our packets contain unsigned 8 bit PCM samples 21 | // so we can push them straight into the output buffer 22 | this->m_last_packet_received = millis(); 23 | this->m_output_buffer->add_samples(packet.data(), packet.length()); 24 | }); 25 | return true; 26 | } 27 | Serial.println("Failed to listen"); 28 | return false; 29 | } 30 | 31 | void UdpTransport::send() 32 | { 33 | udp->broadcast(m_buffer, m_index); 34 | } -------------------------------------------------------------------------------- /lib/transport/src/UdpTransport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Transport.h" 4 | 5 | class OutputBuffer; 6 | class AsyncUDP; 7 | 8 | class UdpTransport : public Transport 9 | { 10 | private: 11 | AsyncUDP *udp; 12 | 13 | protected: 14 | void send(); 15 | 16 | public: 17 | UdpTransport(OutputBuffer *output_buffer); 18 | bool begin() override; 19 | }; -------------------------------------------------------------------------------- /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:esp-wrover-kit] 12 | platform = espressif32 13 | board = esp-wrover-kit 14 | framework = arduino 15 | ; upload_port = /dev/cu.SLAB_USBtoUART 16 | ; monitor_port = /dev/cu.SLAB_USBtoUART 17 | upload_port = /dev/ttyUSB0 18 | monitor_port = /dev/ttyUSB0 19 | monitor_speed = 115200 20 | monitor_filters = esp32_exception_decoder 21 | build_flags = -Ofast 22 | lib_deps = tinypico/TinyPICO Helper Library@^1.4.0 23 | -------------------------------------------------------------------------------- /src/Application.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "Application.h" 6 | #include "I2SMEMSSampler.h" 7 | #include "ADCSampler.h" 8 | #include "I2SOutput.h" 9 | #include "UdpTransport.h" 10 | #include "EspNowTransport.h" 11 | #include "OutputBuffer.h" 12 | #include "SampleSink.h" 13 | #include "TinyPICOIndicatorLed.h" 14 | #include "config.h" 15 | 16 | // i2s config for using the internal ADC 17 | i2s_config_t adcI2SConfig = { 18 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), 19 | .sample_rate = 16000, 20 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 21 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 22 | .communication_format = I2S_COMM_FORMAT_I2S_LSB, 23 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 24 | .dma_buf_count = 4, 25 | .dma_buf_len = 64, 26 | .use_apll = false, 27 | .tx_desc_auto_clear = false, 28 | .fixed_mclk = 0}; 29 | 30 | // i2s config for reading from both channels of I2S 31 | i2s_config_t i2sMemsConfig = { 32 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), 33 | .sample_rate = 16000, 34 | .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, 35 | .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, 36 | .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S), 37 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 38 | .dma_buf_count = 4, 39 | .dma_buf_len = 64, 40 | .use_apll = false, 41 | .tx_desc_auto_clear = false, 42 | .fixed_mclk = 0}; 43 | 44 | // i2s microphone pins 45 | i2s_pin_config_t i2s_mic_pins = { 46 | .bck_io_num = I2S_MIC_SERIAL_CLOCK, 47 | .ws_io_num = I2S_MIC_LEFT_RIGHT_CLOCK, 48 | .data_out_num = I2S_PIN_NO_CHANGE, 49 | .data_in_num = I2S_MIC_SERIAL_DATA}; 50 | 51 | // i2s speaker pins 52 | i2s_pin_config_t i2s_speaker_pins = { 53 | .bck_io_num = I2S_SPEAKER_SERIAL_CLOCK, 54 | .ws_io_num = I2S_SPEAKER_LEFT_RIGHT_CLOCK, 55 | .data_out_num = I2S_SPEAKER_SERIAL_DATA, 56 | .data_in_num = I2S_MIC_SERIAL_DATA}; 57 | 58 | Application::Application() 59 | { 60 | m_output_buffer = new OutputBuffer(300 * 16); 61 | #ifdef USE_I2S_MIC_INPUT 62 | m_input = new I2SMEMSSampler(i2s_mic_pins); 63 | #else 64 | m_input = new ADCSampler(ADC_UNIT_1, ADC1_CHANNEL_7); 65 | #endif 66 | m_output = new I2SOutput(); 67 | #ifdef USE_ESP_NOW 68 | m_transport = new EspNowTransport(m_output_buffer); 69 | #else 70 | m_transport = new UdpTransport(m_output_buffer); 71 | #endif 72 | m_indicator_led = new TinyPICOIndicatorLed(); 73 | } 74 | 75 | void Application::begin() 76 | { 77 | //Serial.print(I2S_MIC_SERIAL_CLOCK); Serial.print(I2S_MIC_LEFT_RIGHT_CLOCK);Serial.println(I2S_MIC_SERIAL_DATA); 78 | //Serial.print(I2S_SPEAKER_SERIAL_CLOCK); Serial.print(I2S_SPEAKER_LEFT_RIGHT_CLOCK); Serial.println(I2S_SPEAKER_SERIAL_DATA); 79 | // show a flashing indicator that we are trying to connect 80 | m_indicator_led->set_default_color(0); 81 | m_indicator_led->set_is_flashing(true, 0xff0000); 82 | m_indicator_led->begin(); 83 | // bring up WiFi 84 | WiFi.mode(WIFI_STA); 85 | // but don't connect if we're using ESP NOW 86 | #ifndef USE_ESP_NOW 87 | WiFi.begin(WIFI_SSID, WIFI_PSWD); 88 | if (WiFi.waitForConnectResult() != WL_CONNECTED) 89 | { 90 | Serial.println("Connection Failed! Rebooting..."); 91 | delay(5000); 92 | ESP.restart(); 93 | } 94 | // this has a dramatic effect on packet RTT 95 | WiFi.setSleep(WIFI_PS_NONE); 96 | Serial.print("My IP Address is: "); 97 | Serial.println(WiFi.localIP()); 98 | #else 99 | WiFi.disconnect(); 100 | #endif 101 | Serial.print("My MAC Address is: "); 102 | Serial.println(WiFi.macAddress()); 103 | // do any setup of the transport 104 | m_transport->begin(); 105 | // connected so show a solid green light 106 | m_indicator_led->set_default_color(0x00ff00); 107 | m_indicator_led->set_is_flashing(false, 0x00ff00); 108 | // setup the transmit button 109 | //pinMode(GPIO_TRANSMIT_BUTTON, INPUT_PULLDOWN); 110 | gpio_reset_pin((gpio_num_t)GPIO_TRANSMIT_BUTTON); 111 | gpio_set_direction( (gpio_num_t)GPIO_TRANSMIT_BUTTON, GPIO_MODE_INPUT); 112 | gpio_set_pull_mode( (gpio_num_t)GPIO_TRANSMIT_BUTTON ,GPIO_PULLUP_ONLY); 113 | // Power enable 114 | gpio_reset_pin(PW); 115 | gpio_set_direction(PW, GPIO_MODE_OUTPUT); 116 | gpio_set_level(PW, 1); 117 | // start both the input and output I2S devices 118 | Serial.println("Starting I2S Output"); 119 | m_output->start(I2S_NUM_0, i2s_speaker_pins, m_output_buffer); 120 | Serial.println("Starting I2S Input"); 121 | m_input->start(I2S_NUM_0, i2sMemsConfig, m_transport); 122 | } 123 | 124 | void Application::loop() 125 | { 126 | unsigned long current_time = millis(); 127 | // run the application state machine 128 | bool transmit_pushed = !digitalRead(GPIO_TRANSMIT_BUTTON); 129 | // Serial.println(transmit_pushed); 130 | //Serial.println(transmit_pushed); 131 | switch (m_current_state) 132 | { 133 | // the application is waiting for the user to push the transmit button or for audio packets to arrive 134 | case IDLE: 135 | { 136 | // have we started receiving packets? 137 | if (current_time - m_transport->get_last_packet_received() < 200) 138 | { 139 | m_indicator_led->set_is_flashing(true, 0xff0000); 140 | m_current_state = RECEIVING; 141 | } 142 | else 143 | { 144 | if (transmit_pushed) 145 | { 146 | m_indicator_led->set_is_flashing(true, 0xff0000); 147 | m_current_state = TRANSMITTING; 148 | Serial.println("transmitting"); 149 | m_transport->should_send(true); 150 | } 151 | } 152 | } 153 | break; 154 | // the user has pushed the transmit button 155 | case TRANSMITTING: 156 | { 157 | if (!transmit_pushed) 158 | { 159 | m_indicator_led->set_is_flashing(false, 0); 160 | m_current_state = IDLE; 161 | m_transport->should_send(false); 162 | Serial.println("Finished transmitting"); 163 | } 164 | } 165 | break; 166 | // we are receiving audio packets 167 | case RECEIVING: 168 | { 169 | // have we stopped receiving packets? 170 | if (current_time - m_transport->get_last_packet_received() > 200) 171 | { 172 | // haven't received any packets - switch back to idle 173 | m_indicator_led->set_is_flashing(false, 0); 174 | m_current_state = IDLE; 175 | Serial.println("Finished receiving"); 176 | } 177 | } 178 | break; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Application.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class I2SOutput; 4 | class I2SSampler; 5 | class Transport; 6 | class OutputBuffer; 7 | class IndicatorLed; 8 | 9 | typedef enum 10 | { 11 | IDLE, // the application is waiting for the user to push the transmit button or for audio packets to arrive 12 | TRANSMITTING, // the user has pushed the transmit button 13 | RECEIVING, // we are receiving audio packets 14 | } Application_State_t; 15 | 16 | class Application 17 | { 18 | private: 19 | I2SOutput *m_output; 20 | I2SSampler *m_input; 21 | Transport *m_transport; 22 | IndicatorLed *m_indicator_led; 23 | OutputBuffer *m_output_buffer; 24 | 25 | Application_State_t m_current_state = IDLE; 26 | 27 | void service(); 28 | 29 | public: 30 | Application(); 31 | void begin(); 32 | void loop(); 33 | friend void samples_task(void *param); 34 | }; -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | // WiFi credentials 2 | #define WIFI_SSID "wifilr" 3 | #define WIFI_PSWD "casanice" 4 | 5 | // are you using an I2S microphone - comment this out if you want to use an analog mic and ADC input 6 | #define USE_I2S_MIC_INPUT 7 | 8 | // I2S Microphone Settings 9 | 10 | // Which channel is the I2S microphone on? I2S_CHANNEL_FMT_ONLY_LEFT or I2S_CHANNEL_FMT_ONLY_RIGHT 11 | // Generally they will default to LEFT - but you may need to attach the L/R pin to GND 12 | #define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT 13 | // #define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT 14 | #define I2S_MIC_SERIAL_CLOCK GPIO_NUM_5 15 | #define I2S_MIC_LEFT_RIGHT_CLOCK GPIO_NUM_25 16 | #define I2S_MIC_SERIAL_DATA GPIO_NUM_35 17 | 18 | // Analog Microphone Settings - ADC1_CHANNEL_7 is GPIO35 19 | #define ADC_MIC_CHANNEL ADC1_CHANNEL_7 20 | 21 | // speaker settings 22 | #define I2S_SPEAKER_SERIAL_CLOCK GPIO_NUM_5 23 | #define I2S_SPEAKER_LEFT_RIGHT_CLOCK GPIO_NUM_25 24 | #define I2S_SPEAKER_SERIAL_DATA GPIO_NUM_26 25 | 26 | // transmit button 27 | #define GPIO_TRANSMIT_BUTTON 0 28 | 29 | // Which transport do you want to use? ESP_NOW or UDP? 30 | // comment out this line to use UDP 31 | #define USE_ESP_NOW 32 | // Output power enable 33 | #define PW GPIO_NUM_21 -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Application.h" 3 | 4 | // our application 5 | Application *application; 6 | 7 | void setup() 8 | { 9 | Serial.begin(115200); 10 | // start up the application 11 | application = new Application(); 12 | application->begin(); 13 | Serial.println("Application started"); 14 | } 15 | 16 | void loop() 17 | { 18 | application->loop(); 19 | delay(50); 20 | } 21 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------