├── WAVRecorder.zip ├── library ├── fritizing │ └── sketch.fzz ├── src │ ├── AudioTimer.h │ ├── WAVRecorder.h │ ├── AudioTimer.cpp │ ├── SoundActivityDetector.h │ ├── AudioSystem.h │ ├── WAVGenerator.h │ ├── AudioSystem.cpp │ ├── SoundActivityDetector.cpp │ ├── WAVRecorder.cpp │ └── WAVGenerator.cpp └── library.ino └── README.md /WAVRecorder.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlirezaSalehy/WAVRecorder/HEAD/WAVRecorder.zip -------------------------------------------------------------------------------- /library/fritizing/sketch.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlirezaSalehy/WAVRecorder/HEAD/library/fritizing/sketch.fzz -------------------------------------------------------------------------------- /library/src/AudioTimer.h: -------------------------------------------------------------------------------- 1 | #ifndef WALLE_TIMER_LIBRARY 2 | #define WALLE_TIMER_LIBRARY 3 | 4 | #ifdef ESP32 5 | #include "esp32-hal.h" 6 | #endif 7 | 8 | #ifdef ESP8266 9 | // Here we are to use timer 1 10 | extern "C" { 11 | #include "user_interface.h" 12 | } 13 | 14 | #include 15 | 16 | #endif 17 | 18 | #ifdef ARDUINO_SAM_DUE 19 | // Here we are going to use timer 4 20 | #include 21 | #endif 22 | 23 | class AudioTimer { 24 | private: 25 | 26 | public: 27 | void setup(uint32_t frequency, void (*f)()); 28 | void start(); 29 | void stop(); 30 | }; 31 | 32 | 33 | #endif -------------------------------------------------------------------------------- /library/src/WAVRecorder.h: -------------------------------------------------------------------------------- 1 | #ifndef WAV_RECORDER_LIBRARY 2 | #define WAV_RECORDER_LIBRARY 3 | 4 | #include 5 | #include 6 | 7 | 8 | #include "SoundActivityDetector.h" 9 | #include "WAVGenerator.h" 10 | #include "AudioTimer.h" 11 | 12 | struct channel_t { 13 | // The ADC pin of the microphone 14 | uint8_t ADCPin; 15 | }; 16 | 17 | // Note that WAVRecorder uses timerX 18 | class WAVRecorder { 19 | 20 | private: 21 | //uint32_t samplingRate = 0; 22 | //HardwareSerial* serial = 0; 23 | //WAVGenerator* wg = 0; 24 | 25 | //volatile uint32_t counter = 0; 26 | 27 | AudioTimer* timer = 0; 28 | 29 | public: 30 | WAVRecorder(uint8_t adcDatLen, channel_t* channels, uint16_t numChannels, uint32_t samplingRate, uint16_t sampleLength, HardwareSerial* serial); 31 | WAVRecorder(uint8_t adcDatLen, channel_t* channels, uint16_t numChannels, File* dataFile, uint32_t samplingRate, uint16_t sampleLength, HardwareSerial* serial); 32 | void start(uint32_t time); 33 | void start(); 34 | void start(SoundActivityDetector* sad_arg); 35 | void startBlocking(uint32_t time); 36 | void startBlocking(SoundActivityDetector* sad_arg); 37 | void stop(); 38 | 39 | void setFile(File* dataFile); 40 | //~WAVRecorder(); 41 | }; 42 | 43 | #endif -------------------------------------------------------------------------------- /library/src/AudioTimer.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioTimer.h" 2 | 3 | #ifdef ESP32 4 | hw_timer_t* timer = NULL; 5 | #endif 6 | 7 | void AudioTimer::setup(uint32_t frequency, void (*f)(void)) { 8 | stop(); 9 | 10 | #ifdef ESP8266 11 | timer1_attachInterrupt(f); 12 | uint32_t tick = (80 * 1000000) / frequency; 13 | timer1_write(tick); /* ( 80 / 1 ) * 125 */ 14 | #endif 15 | 16 | #ifdef ESP32 17 | timer = timerBegin(0, 20, true); 18 | timerAttachInterrupt(timer, f, true); 19 | timerAlarmWrite(timer, 4000000 / frequency, true); // 500 for 8Khz, 250 for 16Khz, 125 for 32Khz 20 | #endif 21 | 22 | 23 | #ifdef ARDUINO_SAM_DUE 24 | // Here we are going to use timer 4 25 | Timer4.attachInterrupt(f).setFrequency(frequency); 26 | #endif 27 | 28 | 29 | } 30 | 31 | void AudioTimer::start() { 32 | #ifdef ESP8266 33 | timer1_enable(TIM_DIV1/*from 80 Mhz*/, TIM_EDGE/*TIM_LEVEL*/, TIM_LOOP/*TIM_SINGLE*/); 34 | #endif 35 | 36 | #ifdef ESP32 37 | timerAlarmEnable(timer); 38 | #endif 39 | 40 | #ifdef ARDUINO_SAM_DUE 41 | Timer4.start(); 42 | #endif 43 | } 44 | 45 | void AudioTimer::stop() { 46 | #ifdef ESP8266 47 | timer1_disable(); 48 | #endif 49 | 50 | #ifdef ESP32 51 | if (timer) 52 | timerAlarmDisable(timer); 53 | #endif 54 | 55 | #ifdef ARDUINO_SAM_DUE 56 | Timer4.stop(); 57 | #endif 58 | } -------------------------------------------------------------------------------- /library/src/SoundActivityDetector.h: -------------------------------------------------------------------------------- 1 | #ifndef SOUND_ACTIVITY_DETECTION_LIBRARY 2 | #define SOUND_ACTIVITY_DETECTION_LIBRARY 3 | 4 | #include 5 | 6 | #ifdef ESP8266 7 | // Here we are to use timer 1 8 | extern "C" { 9 | #include "user_interface.h" 10 | } 11 | 12 | #elif ESP32 13 | #include 14 | #endif 15 | 16 | #include "AudioTimer.h" 17 | //#include "WAVRecorder.h" 18 | 19 | 20 | // Note that SoundActivityDetection uses timerX 21 | class SoundActivityDetector { 22 | 23 | private: 24 | uint8_t upper_threshold = 0; 25 | uint8_t lower_threshold = 0; 26 | 27 | uint16_t minimumLengthNS = 0; 28 | uint16_t maximumRewardNS = 0; 29 | 30 | public: 31 | SoundActivityDetector(uint8_t adcPin, uint16_t num_samples, uint16_t minimumLengthNS, uint16_t maximumRewardNS, HardwareSerial* serial); 32 | 33 | // This should be called at first, for example when constructor is called 34 | void calibrate(uint16_t num_samples); 35 | void calibrate(uint16_t averageVector, uint16_t averageValue); 36 | 37 | void setThresholds(uint8_t lower, uint8_t upper); 38 | 39 | // This should be called in the ISR, it should be implemented in way that has some memory 40 | // This could be called when buffer is going to be writen, too. 41 | bool detect(uint16_t dat); 42 | bool detect(uint16_t* buffer, uint16_t len); 43 | 44 | uint16_t getMinimumLengthNS(); 45 | }; 46 | 47 | #endif -------------------------------------------------------------------------------- /library/src/AudioSystem.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_SYSTEM_LIBRARY 2 | #define AUDIO_SYSTEM_LIBRARY 3 | 4 | #if defined(ESP32) || defined(ESP8266) 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifdef ESP8266 19 | #define SPDIF_OUT_PIN 27 20 | #endif 21 | 22 | #ifdef ESP32 23 | #include 24 | #include "esp32-hal.h" 25 | #endif 26 | 27 | 28 | #define EXTERNAL_DAC 0 29 | #define INTERNAL_DAC 1 30 | #define INTERNAL_PDM 2 31 | 32 | // A wrapper class for ESP8266Audio library 33 | class AudioSystem { 34 | 35 | private: 36 | AudioFileSourceSD *source = NULL; 37 | AudioOutput *output = NULL; 38 | AudioGenerator *decoder = NULL; 39 | 40 | AudioSystem(); 41 | 42 | //void ICACHE_RAM_ATTR onTimerISR(); 43 | 44 | public: 45 | // Chip Select pin == csPin 46 | AudioSystem(uint8_t csPin); 47 | 48 | // API for playing audio in blocking manner 49 | void playAudioBlocking(char* fileName); 50 | 51 | // API for recording voice in blocking manner 52 | File recordAudioBlocking(uint32_t milisec, uint16_t frquency); 53 | 54 | void playAudioLoop(); 55 | }; 56 | 57 | #endif 58 | 59 | #endif -------------------------------------------------------------------------------- /library/src/WAVGenerator.h: -------------------------------------------------------------------------------- 1 | #ifndef WAV_GENERATOR_LIBRARY 2 | #define WAV_GENERATOR_LIBRARY 3 | 4 | #include 5 | #include 6 | 7 | #define CHUNK_SIZE 512 8 | #define NUM_CHUNK 96 9 | #define BUFFDER_LEN NUM_CHUNK * CHUNK_SIZE 10 | 11 | #if defined(ESP8266) || defined(ESP32) 12 | #define File File 13 | #endif 14 | 15 | 16 | #ifdef ARDUINO_SAM_DUE 17 | #define File SDLib::File 18 | #endif 19 | 20 | 21 | struct audio_t { 22 | File* dataFile; 23 | 24 | uint32_t samplingRate; 25 | uint16_t sampleLength; 26 | 27 | // This should be at most 2, which for stereo audio 28 | uint16_t numberOfChannels; 29 | }; 30 | 31 | 32 | class WAVGenerator { 33 | 34 | private: 35 | uint8_t buffer[BUFFDER_LEN];// = {0}; 36 | 37 | uint8_t toMarkFill[NUM_CHUNK];// = {0}; 38 | uint8_t isFilled[NUM_CHUNK];// = {0}; 39 | uint8_t* chunk = buffer; 40 | uint16_t index = 0; 41 | 42 | volatile uint8_t stopped = 0; 43 | 44 | uint8_t toWriteChunk = 0; 45 | uint32_t sampleChunkSize = 0; 46 | 47 | audio_t audio; 48 | HardwareSerial* serial = 0; 49 | File* dataFile = 0; 50 | 51 | void putInArray(uint8_t* dest, uint32_t src, uint8_t len); 52 | void flush(); 53 | 54 | void writeHeader(); 55 | void appenedFileSize(); 56 | void closeFile(); 57 | 58 | public: 59 | WAVGenerator(HardwareSerial* serial); 60 | WAVGenerator(audio_t audio, HardwareSerial* serial); 61 | void setAudio(audio_t audio); 62 | 63 | void appendBuffer(uint8_t* data, uint16_t len, uint8_t isMarked); 64 | void writeChunks(); 65 | bool isBufferFull(); 66 | void create(); 67 | 68 | void markCurrentChunk(); 69 | void markIgnoredChunks(uint16_t numSamples); 70 | void markAllChunks(); 71 | 72 | //~WAVGenerator(); 73 | //void appendBufferOverwrite(uint8_t* data, uint16_t len, uint8_t stat); 74 | 75 | }; 76 | 77 | #endif -------------------------------------------------------------------------------- /library/src/AudioSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioSystem.h" 2 | 3 | #if defined(ESP32) || defined(ESP8266) 4 | 5 | AudioSystem::AudioSystem() { 6 | audioLogger = &Serial; 7 | source = new AudioFileSourceSD(); 8 | 9 | #ifdef ESP8266 10 | output = new AudioOutputI2S(SPDIF_OUT_PIN); 11 | #elif ESP32 12 | output = new AudioOutputI2S(0, INTERNAL_DAC); 13 | #endif 14 | } 15 | 16 | AudioSystem::AudioSystem(uint8_t csPin) : AudioSystem::AudioSystem() { 17 | //if (!SD.begin(csPin)) { 18 | // Serial.println("SD initialization failed"); 19 | //} 20 | } 21 | 22 | void AudioSystem::playAudioBlocking(char *fileName) { 23 | bool toReturn = false; 24 | 25 | if (decoder && decoder->isRunning()) { 26 | decoder->stop(); 27 | } 28 | 29 | 30 | File audioFile = SD.open(fileName, FILE_READ); 31 | if (!audioFile) { 32 | Serial.println("File can not be read!"); 33 | toReturn = true; 34 | } 35 | 36 | if (String(audioFile.name()).endsWith(".wav")) { 37 | decoder = new AudioGeneratorWAV(); 38 | } 39 | else if (String(audioFile.name()).endsWith(".mp3")) { 40 | decoder = new AudioGeneratorMP3(); 41 | } 42 | else { 43 | Serial.println("File does not end with .wav or .mp3"); 44 | toReturn = true; 45 | } 46 | 47 | if (toReturn == true) { 48 | return; 49 | } 50 | 51 | source->close(); 52 | if (source->open(audioFile.name())) { 53 | decoder->begin(source, output); 54 | if (!decoder->isRunning()) { 55 | Serial.println("Decoder is not running"); 56 | } 57 | else { 58 | Serial.printf_P(PSTR("Playing in from the file %s\n"), audioFile.name()); 59 | } 60 | 61 | while (decoder->loop()) {} 62 | 63 | decoder->stop(); 64 | } 65 | else { 66 | Serial.println("Error playing file"); 67 | } 68 | 69 | } 70 | 71 | void AudioSystem::playAudioLoop() { 72 | decoder->loop(); 73 | } 74 | 75 | #endif -------------------------------------------------------------------------------- /library/library.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "src/WAVRecorder.h" 5 | #include "src/AudioSystem.h" 6 | #include "src/SoundActivityDetector.h" 7 | 8 | #define SAMPLE_RATE 16000 9 | #define SAMPLE_LEN 8 10 | 11 | // Hardware SPI's CS pin which is different in each board 12 | #ifdef ESP8266 13 | #define CS_PIN 16 14 | #elif ARDUINO_SAM_DUE 15 | #define CS_PIN 4 16 | #elif ESP32 17 | #define CS_PIN 5 18 | #endif 19 | 20 | // The analog pins (ADC inputs) which microphone outputs are connected to. 21 | #define MIC_PIN_1 34 22 | #define MIC_PIN_2 35 23 | 24 | #define NUM_CHANNELS 1 25 | channel_t channels[] = {{MIC_PIN_1}}; 26 | 27 | char file_name[] = "/sample.wav"; 28 | File dataFile; 29 | 30 | #if defined(ESP32) || defined(ESP8266) 31 | AudioSystem* as; 32 | #endif 33 | WAVRecorder* wr; 34 | //SoundActivityDetector* sadet; 35 | 36 | void recordAndPlayBack(); 37 | 38 | void setup() { 39 | for (int i = 0; i < sizeof(channels)/sizeof(channel_t); i++) 40 | pinMode(channels[i].ADCPin, INPUT); 41 | //analogReadResolution(12); for ESP32 42 | 43 | pinMode(LED_BUILTIN, OUTPUT); 44 | Serial.begin(115200); 45 | Serial.println(); 46 | 47 | // put your setup code here, to run once: 48 | if (!SD.begin(CS_PIN)) { 49 | Serial.println("Failes to initialize SD!"); 50 | } 51 | else { 52 | Serial.println("SD opened successfuly"); 53 | } 54 | SPI.setClockDivider(SPI_CLOCK_DIV2); // This is becuase feeding SD Card with more than 40 Mhz, leads to unstable operation. 55 | // (Also depends on SD class) ESP8266 & ESP32 SPI clock with no division is 80 Mhz. 56 | 57 | #if defined(ESP32) || defined(ESP8266) 58 | as = new AudioSystem(CS_PIN); 59 | #endif 60 | //sadet = new SoundActivityDetector(channels[0].ADCPin, 2000, 10 * 512, 6 * 512, &Serial); 61 | wr = new WAVRecorder(12, channels, NUM_CHANNELS, SAMPLE_RATE, SAMPLE_LEN, &Serial); 62 | 63 | } 64 | 65 | void loop() { 66 | // put your main code here, to run repeatedly: 67 | recordAndPlayBack(); 68 | } 69 | 70 | void recordAndPlayBack() { 71 | if (SD.exists(file_name)) { 72 | SD.remove(file_name); 73 | Serial.println("File removed!"); 74 | } 75 | 76 | dataFile = SD.open(file_name, FILE_WRITE); 77 | if (!dataFile) { 78 | Serial.println("Failed to open the file!"); 79 | return; 80 | } 81 | 82 | // Setting file to store recodring 83 | wr->setFile(&dataFile); 84 | 85 | Serial.println("Started"); 86 | // With checks Sound power level and it exceeds a threshold recording starts and stops recording when power fall behind another threshold. 87 | //wr->startBlocking(sadet); 88 | 89 | // Recording for 3000 ms 90 | wr->startBlocking(3000); 91 | Serial.println("File Created"); 92 | 93 | Serial.println("Playing file"); 94 | 95 | #if defined(ESP32) || defined(ESP8266) 96 | as->playAudioBlocking(file_name); 97 | #endif 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WAVRecorder 2 | Arduino Library for voice recording using Electret Microphones for ESP32, ESP8266 and Arduino Due. 3 | 4 | The library has some components but the user interface component is WAVRecorder class, two methods of the class are implemented until now for recording, startBlocking(uint32_t time_ms) and startBlocking(SoundActivityDetector* sad_arg). 5 | startBlocking(uint32_t time_ms) records for specified amount of time in milliseconds. and the other one starts recording as soon as sound power exceeds specific threshold and stops recording as fall behind a specific threshold. 6 | 7 | you can use SD card to store recording file which is a WAV. file. or you can use external flash if you're using ESP32 or ESP8266. The library is capable of stereo recording which is only possible if using ESP32 and Due because ESP8266 has only one ADC input pin. 8 | 9 | You should specify channels before being able to record, each channel is defined by ADC pin number which connected to MIC output. no need to say that for stereo recording you need to define two channels. 10 | 11 | There is other component in library to play recorded sound which is AudioSystem which is just a wrapper for cool [ESP8266Audio](https://github.com/earlephilhower/ESP8266Audio) library. 12 | 13 | # Example using SD card and a single microphone (single channel) 14 | Below is a simple example for recording for 3000 ms and playing it back repeatedly. 15 | ```cpp 16 | #include 17 | #include 18 | 19 | #include "src/WAVRecorder.h" 20 | #include "src/AudioSystem.h" 21 | #include "src/SoundActivityDetector.h" 22 | 23 | #define SAMPLE_RATE 16000 24 | #define SAMPLE_LEN 8 25 | 26 | // Hardware SPI's CS pin which is different in each board 27 | #ifdef ESP8266 28 | #define CS_PIN 16 29 | #elif ARDUINO_SAM_DUE 30 | #define CS_PIN 4 31 | #elif ESP32 32 | #define CS_PIN 5 33 | #endif 34 | 35 | // The analog pins (ADC inputs) which microphone outputs are connected to. 36 | #define MIC_PIN_1 34 37 | #define MIC_PIN_2 35 38 | 39 | #define NUM_CHANNELS 1 40 | channel_t channels[] = {{MIC_PIN_1}}; 41 | 42 | char file_name[] = "/sample.wav"; 43 | File dataFile; 44 | 45 | #if defined(ESP32) || defined(ESP8266) 46 | AudioSystem* as; 47 | #endif 48 | WAVRecorder* wr; 49 | //SoundActivityDetector* sadet; 50 | 51 | void recordAndPlayBack(); 52 | 53 | void setup() { 54 | for (int i = 0; i < sizeof(channels)/sizeof(channel_t); i++) 55 | pinMode(channels[i].ADCPin, INPUT); 56 | //analogReadResolution(12); for ESP32 57 | 58 | pinMode(LED_BUILTIN, OUTPUT); 59 | Serial.begin(115200); 60 | Serial.println(); 61 | 62 | // put your setup code here, to run once: 63 | if (!SD.begin(CS_PIN)) { 64 | Serial.println("Failes to initialize SD!"); 65 | } 66 | else { 67 | Serial.println("SD opened successfuly"); 68 | } 69 | SPI.setClockDivider(SPI_CLOCK_DIV2); // This is becuase feeding SD Card with more than 40 Mhz, leads to unstable operation. 70 | // (Also depends on SD class) ESP8266 & ESP32 SPI clock with no division is 80 Mhz. 71 | 72 | #if defined(ESP32) || defined(ESP8266) 73 | as = new AudioSystem(CS_PIN); 74 | #endif 75 | //sadet = new SoundActivityDetector(channels[0].ADCPin, 2000, 10 * 512, 6 * 512, &Serial); 76 | wr = new WAVRecorder(12, channels, NUM_CHANNELS, SAMPLE_RATE, SAMPLE_LEN, &Serial); 77 | 78 | } 79 | 80 | void loop() { 81 | // put your main code here, to run repeatedly: 82 | recordAndPlayBack(); 83 | } 84 | 85 | void recordAndPlayBack() { 86 | if (SD.exists(file_name)) { 87 | SD.remove(file_name); 88 | Serial.println("File removed!"); 89 | } 90 | 91 | dataFile = SD.open(file_name, FILE_WRITE); 92 | if (!dataFile) { 93 | Serial.println("Failed to open the file!"); 94 | return; 95 | } 96 | 97 | // Setting file to store recodring 98 | wr->setFile(&dataFile); 99 | 100 | Serial.println("Started"); 101 | // With checks Sound power level and it exceeds a threshold recording starts and stops recording when power fall behind another threshold. 102 | //wr->startBlocking(sadet); 103 | 104 | // Recording for 3000 ms 105 | wr->startBlocking(3000); 106 | Serial.println("File Created"); 107 | 108 | Serial.println("Playing file"); 109 | 110 | #if defined(ESP32) || defined(ESP8266) 111 | as->playAudioBlocking(file_name); 112 | #endif 113 | } 114 | ``` 115 | 116 | # Schematic (Only for recording) 117 | 118 | ![ESP32_SD_MIC_Modules](https://user-images.githubusercontent.com/49995349/113865590-81608200-97c1-11eb-8358-1e5240453541.png) 119 | 120 | ![ESP32_SD_MIC_Schematic](https://user-images.githubusercontent.com/49995349/113865596-8291af00-97c1-11eb-9c91-3bbae1eda749.png) 121 | 122 | -------------------------------------------------------------------------------- /library/src/SoundActivityDetector.cpp: -------------------------------------------------------------------------------- 1 | #include "SoundActivityDetector.h" 2 | 3 | volatile bool isCalibrated = false; 4 | uint16_t numSamples = 0; 5 | bool isAverageSet = false; 6 | bool isVectorSet = false; 7 | uint32_t samplesCounter = 0; 8 | uint32_t samplesAddition = 0; 9 | 10 | static AudioTimer* timer = 0; 11 | HardwareSerial* s = 0; 12 | 13 | uint16_t averageVector = 0; 14 | uint16_t averageValue = 0; 15 | 16 | uint8_t ADCPin = 0; 17 | 18 | 19 | #if defined(ESP8266) || defined(ESP32) 20 | void ICACHE_RAM_ATTR timerISR(void); 21 | #else 22 | void timerISR(void); 23 | #endif 24 | 25 | // WAVRecorder buffer must be atleast equal to minimumLengthNS 26 | SoundActivityDetector::SoundActivityDetector(uint8_t ADCPin_arg, uint16_t num_samples, uint16_t minimumLengthNS, uint16_t maximumRewardNS, HardwareSerial* serial_arg) { 27 | //audioChannels = channels; 28 | //numChannels = numChannels_arg; 29 | ADCPin = ADCPin_arg; 30 | 31 | this->minimumLengthNS = minimumLengthNS; 32 | this->maximumRewardNS = maximumRewardNS; 33 | setThresholds(3, 10); 34 | 35 | s = serial_arg; 36 | 37 | timer = new AudioTimer(); 38 | 39 | #ifdef ESP8266 40 | wifi_set_opmode(NULL_MODE); 41 | #endif 42 | 43 | calibrate(num_samples); 44 | } 45 | 46 | void SoundActivityDetector::calibrate(uint16_t num_samples) { 47 | numSamples = num_samples; 48 | timer->setup(8000, timerISR); 49 | timer->start(); 50 | 51 | while (!isCalibrated) {} 52 | s->println("Done lets have fun!"); 53 | } 54 | 55 | void SoundActivityDetector::calibrate(uint16_t averageVector, uint16_t averageValue) { 56 | averageValue = averageValue; 57 | averageVector = averageVector; 58 | } 59 | 60 | void SoundActivityDetector::setThresholds(uint8_t lower, uint8_t upper) { 61 | this->lower_threshold = lower; 62 | this->upper_threshold = upper; 63 | } 64 | 65 | // detect can be implemented in way that be called by Timer routine or the SD write function. 66 | bool SoundActivityDetector::detect(uint16_t result16) { 67 | static uint32_t rewardCounter = 0, sampleCounter = 0; 68 | 69 | uint32_t vectorDeviation = abs(abs(result16 - averageValue) - averageVector); 70 | if (rewardCounter <= maximumRewardNS && vectorDeviation > this->upper_threshold) 71 | rewardCounter += 4; 72 | 73 | else if (vectorDeviation < this->lower_threshold) 74 | if (rewardCounter >= 3) 75 | rewardCounter -= 3; 76 | else 77 | if (rewardCounter >= 1) 78 | rewardCounter -= 1; 79 | 80 | if (rewardCounter >= 30) 81 | sampleCounter++; 82 | 83 | else 84 | sampleCounter = 0; 85 | 86 | if (sampleCounter > minimumLengthNS) 87 | return true; 88 | 89 | else 90 | return false; 91 | } 92 | 93 | // detect can be implemented in way that be called by Timer routine or the SD write function. 94 | inline bool SoundActivityDetector::detect(uint16_t* buffer, uint16_t len) { 95 | static uint32_t rewardCounter = 0, sampleCounter = 0; 96 | 97 | for (size_t i = 0; i < len; i++) { 98 | uint32_t vectorDeviation = abs(abs(buffer[i] - averageValue) - averageVector); 99 | if (rewardCounter <= maximumRewardNS && vectorDeviation > this->upper_threshold) 100 | rewardCounter += 4; 101 | 102 | else if (vectorDeviation < this->lower_threshold) 103 | if (rewardCounter >= 3) 104 | rewardCounter -= 3; 105 | else 106 | if (rewardCounter >= 1) 107 | rewardCounter -= 1; 108 | 109 | // 110 | if (rewardCounter >= 30) 111 | sampleCounter++; 112 | 113 | else 114 | sampleCounter = 0; 115 | 116 | if (sampleCounter > minimumLengthNS) 117 | return true; 118 | } 119 | 120 | return false; 121 | } 122 | 123 | uint16_t SoundActivityDetector::getMinimumLengthNS() { 124 | return this->minimumLengthNS; 125 | } 126 | 127 | 128 | 129 | #if defined(ESP8266) || defined(ESP32) 130 | void ICACHE_RAM_ATTR timerISR(void){ 131 | #else 132 | void timerISR(void){ 133 | #endif 134 | 135 | uint16_t result16; 136 | 137 | #if defined(ARDUINO_SAM_DUE) || defined(ESP32) 138 | result16 = analogRead(ADCPin); 139 | #endif 140 | 141 | #if ESP8266 142 | system_adc_read_fast(&result16, 1, 8); 143 | #endif 144 | 145 | if (!isAverageSet) 146 | samplesAddition += result16; 147 | else 148 | samplesAddition += abs(result16 - averageValue); 149 | 150 | samplesCounter++; 151 | if (samplesCounter >= numSamples) { 152 | if (!isAverageSet) { 153 | averageValue = samplesAddition / samplesCounter; 154 | s->print("This is the average value: "); 155 | s->println(averageValue); 156 | isAverageSet = true; 157 | } 158 | else if (!isVectorSet) { 159 | averageVector = samplesAddition / samplesCounter; 160 | s->print("This is the average vector: "); 161 | s->println(averageVector); 162 | isVectorSet = true; 163 | 164 | isCalibrated = true; 165 | isAverageSet = false; 166 | isVectorSet = false; 167 | 168 | timer->stop(); 169 | } 170 | 171 | samplesAddition = 0; 172 | samplesCounter = 0; 173 | } 174 | } -------------------------------------------------------------------------------- /library/src/WAVRecorder.cpp: -------------------------------------------------------------------------------- 1 | #include "WAVRecorder.h" 2 | 3 | HardwareSerial* serial = 0; 4 | uint32_t samplingRate = 0; 5 | volatile uint32_t counter = 0; 6 | 7 | WAVGenerator* wg = 0; 8 | 9 | SoundActivityDetector* sad = 0; 10 | 11 | /* 12 | uint16_t soundBuffer[256]; 13 | volatile uint8_t lastIndex = 0; 14 | */ 15 | 16 | volatile uint8_t isStopped = 0; 17 | volatile uint8_t isStarted = 0; 18 | 19 | channel_t* audioChannels = 0; 20 | uint16_t numChannels = 0; 21 | 22 | uint8_t ADCDataLen = 0; 23 | uint8_t devideFactor = 0; 24 | uint8_t sampleLength = 0; 25 | 26 | void 27 | #if defined(ESP8266) || defined(ESP32) 28 | ICACHE_RAM_ATTR 29 | #endif 30 | sampleAndBuffer(){ 31 | uint8_t result[numChannels]; 32 | 33 | if (wg->isBufferFull()) { 34 | serial->println("Stop"); 35 | return; 36 | } 37 | 38 | for (int i = 0; i < numChannels; i++) 39 | { 40 | uint16_t result16; 41 | #ifdef ESP8266 42 | //system_soft_wdt_stop(); 43 | //ets_intr_lock( ); //close interrupt 44 | //noInterrupts(); 45 | 46 | system_adc_read_fast(&result16, 1, 8); 47 | 48 | //interrupts(); 49 | //ets_intr_unlock(); //open interrupt 50 | //system_soft_wdt_restart(); 51 | 52 | #elif defined(ARDUINO_SAM_DUE) || defined(ESP32) 53 | result16 = analogRead(audioChannels[i].ADCPin); //averageADCSample(audioChannels[i].ADCPin, 1); 54 | #endif 55 | 56 | result[i] = ((result16 + 1) / devideFactor) - 1; 57 | 58 | } 59 | 60 | wg->appendBuffer(result, numChannels * 1, 1); 61 | 62 | counter++; 63 | if (counter % samplingRate == 0) { 64 | serial->println("1"); 65 | } 66 | } 67 | 68 | void 69 | #if defined(ESP8266) || defined(ESP32) 70 | ICACHE_RAM_ATTR 71 | #endif 72 | sampleAndBufferAutoDetection(){ 73 | static uint8_t recordingStarted = 0; 74 | 75 | if (wg->isBufferFull()) { 76 | serial->println("Stop"); 77 | return; 78 | } 79 | 80 | uint16_t result16 = 0; 81 | #ifdef ESP8266 82 | system_adc_read_fast(&result16, 1, 8); 83 | #endif 84 | 85 | #if defined(ARDUINO_SAM_DUE) || defined(ESP32) 86 | result16 = analogRead(audioChannels[0].ADCPin); 87 | #endif 88 | 89 | uint8_t stat = sad->detect(result16); 90 | if (!recordingStarted && stat) { 91 | recordingStarted = 1; 92 | wg->markIgnoredChunks(sad->getMinimumLengthNS()); 93 | isStarted = 1; 94 | } else if (recordingStarted && !stat) { 95 | recordingStarted = 0; 96 | isStopped = 1; 97 | } 98 | 99 | uint8_t result = ((result16 + 1) / devideFactor) - 1; 100 | wg->appendBuffer(&result, 1, stat); 101 | 102 | counter++; 103 | if (counter % samplingRate == 0) { 104 | serial->println("1"); 105 | } 106 | } 107 | 108 | WAVRecorder::WAVRecorder(uint8_t ADCDataLen_arg, channel_t* channels_arg, uint16_t numChannels_arg, uint32_t samplingRate_arg, uint16_t sampleLength_arg, HardwareSerial* serial_arg) { 109 | audioChannels = channels_arg; 110 | numChannels = numChannels_arg; 111 | ADCDataLen = ADCDataLen_arg; 112 | serial = serial_arg; 113 | samplingRate = samplingRate_arg; 114 | sampleLength = sampleLength_arg; 115 | counter = 0; 116 | 117 | devideFactor = pow(2, ADCDataLen / sampleLength_arg); 118 | } 119 | 120 | WAVRecorder::WAVRecorder(uint8_t ADCDatLen_arg, channel_t* channels, uint16_t numChannels, File* dataFile, uint32_t samplingRate_arg, uint16_t sampleLength, HardwareSerial* serial_arg) 121 | : WAVRecorder(ADCDatLen_arg, channels, numChannels, samplingRate_arg, sampleLength, serial_arg) 122 | { 123 | wg = new WAVGenerator({dataFile, samplingRate, sampleLength, numChannels} , serial); 124 | 125 | // The code bellow is moved to the start() 126 | //timer = new AudioTimer(); 127 | //timer->setup(sampleRate, sampleAndBuffer); 128 | } 129 | 130 | void WAVRecorder::setFile(File* dataFile) { 131 | counter = 0; 132 | isStarted = 0; 133 | isStopped = 0; 134 | 135 | if (wg) 136 | wg->setAudio({dataFile, samplingRate, sampleLength, numChannels}); 137 | else 138 | wg = new WAVGenerator({dataFile, samplingRate, sampleLength, numChannels} , serial); 139 | } 140 | 141 | // Not yet implemented, but if we want it to be non blocking we should put the end clause in the 142 | // ISR of the timer 143 | void WAVRecorder::start(uint32_t time) { 144 | 145 | } 146 | 147 | void WAVRecorder::start(SoundActivityDetector* sad_arg) { 148 | sad = sad_arg; 149 | timer = new AudioTimer(); 150 | timer->setup(samplingRate, sampleAndBufferAutoDetection); 151 | timer->start(); 152 | } 153 | 154 | void WAVRecorder::start() { 155 | timer = new AudioTimer(); 156 | timer->setup(samplingRate, sampleAndBuffer); 157 | 158 | 159 | timer->start(); 160 | } 161 | 162 | void WAVRecorder::stop() { 163 | timer->stop(); 164 | wg->create(); 165 | } 166 | 167 | void WAVRecorder::startBlocking(uint32_t time_ms) { 168 | start(); 169 | 170 | while (counter <= ((time_ms / 1000) * samplingRate)) { 171 | wg->writeChunks(); 172 | } 173 | 174 | stop(); 175 | } 176 | 177 | 178 | void WAVRecorder::startBlocking(SoundActivityDetector* sad_arg) { 179 | start(sad_arg); 180 | 181 | digitalWrite(2, 1); 182 | while (!isStopped) { 183 | wg->writeChunks(); 184 | 185 | if (isStarted) 186 | digitalWrite(2, 0); 187 | 188 | yield(); 189 | } 190 | digitalWrite(2, 1); 191 | 192 | stop(); 193 | } -------------------------------------------------------------------------------- /library/src/WAVGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "WAVGenerator.h" 2 | 3 | unsigned char WAVTemplate[] = { 4 | 0x52, 0x49, 0x46, 0x46, // Chunk ID (RIFF) 'R', 'I', 'F', 'F', 5 | 0x24, 0x71, 0x02, 0x00, // Chunk payload => size 36 + subchunk2size 160036 6 | 0x57, 0x41, 0x56, 0x45, // RIFF resource format type 'W', 'A', 'V', 'E' 7 | 8 | 0x66, 0x6D, 0x74, 0x20, // Chunk ID (fmt) 'f' , 'm' , 't' , ' ' , 9 | 0x10, 0x00, 0x00, 0x00, // Chunk payload size (0x10 = 16 bytes) 10 | 0x01, 0x00, // Format Tag (IMA ADPCM) 11 | 0x01, 0x00, // Channels (1) 12 | 0x40, 0x1F, 0x00, 0x00, // Sample Rate, 0x3e80 = 16.0kHz // 1F40 13 | 0x80, 0x3E, 0x00, 0x00, // Average Bytes Per Second 14 | 0x02, 0x00, // Data Block Size (2 bytes) 15 | 0x10, 0x00, // ADPCM encoded bits per sample (16 bits) 16 | 17 | 0x64, 0x61, 0x74, 0x61, // Chunk ID (data) 'd' , 'a' , 't' , 'a' 18 | 0x00, 0x71, 0x02, 0x00 // Chunk payload size (calculate after rec!) 160000 0x27100 19 | }; 20 | 21 | WAVGenerator::WAVGenerator(HardwareSerial* serial) { 22 | this->serial = serial; 23 | this->sampleChunkSize = 0; 24 | this->index = 0; 25 | //this->chunk = new uint8_t(512); 26 | 27 | } 28 | 29 | WAVGenerator::WAVGenerator(audio_t audio, HardwareSerial* serial) : 30 | WAVGenerator::WAVGenerator(serial) { 31 | if (audio.numberOfChannels > 2) 32 | serial->println("Audio wave format has at most 2 channels"); 33 | this->audio = audio; 34 | 35 | writeHeader(); 36 | } 37 | 38 | void WAVGenerator::writeHeader() { 39 | uint32_t samplingRate = audio.samplingRate; 40 | uint16_t sampleLength = audio.sampleLength; 41 | 42 | // Num Channels : //num of channels is set to 1 by default 43 | uint16_t nch = audio.numberOfChannels; //1; 44 | putInArray((uint8_t*)&WAVTemplate[22], nch, sizeof(nch)); 45 | 46 | // Sample Rate 47 | putInArray((uint8_t*)&WAVTemplate[24], samplingRate, sizeof(samplingRate)); 48 | 49 | // Average bytes per second : samplingRate * NumChannels * BitsPerSample/8 50 | uint32_t bps = audio.samplingRate * nch * sampleLength/8; 51 | putInArray((uint8_t*)&WAVTemplate[28], bps, sizeof(bps)); 52 | 53 | // Block Align : NumChannels * BitsPerSample/8 54 | uint16_t blockAlign = nch * sampleLength / 2; 55 | putInArray((uint8_t*)&WAVTemplate[32], blockAlign, sizeof(blockAlign)); 56 | 57 | // Bits per sample 58 | putInArray((uint8_t*)&WAVTemplate[34], sampleLength, sizeof(sampleLength)); 59 | 60 | audio.dataFile->write((uint8_t*)WAVTemplate, sizeof(WAVTemplate)); 61 | } 62 | 63 | void WAVGenerator::setAudio(audio_t audio) { 64 | this->audio = audio; 65 | 66 | writeHeader(); 67 | chunk = buffer; 68 | index = 0; 69 | sampleChunkSize = 0; 70 | toWriteChunk = 0; 71 | for (int i = 0; i < NUM_CHUNK; i++) { 72 | toMarkFill[i] = 0; 73 | isFilled[i] = 0; 74 | } 75 | } 76 | 77 | /* If the next buffer is full the new data is not buffered so they are lost. in this case stopped is set then in write buffers 78 | This condition is checked and pull the buffering out of stopped mode. 79 | 80 | buffer is treated as circular. data is never overwritten but thrown away. 81 | */ 82 | void WAVGenerator::appendBuffer(uint8_t* data, uint16_t len, uint8_t isMarked) { 83 | uint16_t remainder = len; 84 | uint16_t endIndex = 0; 85 | 86 | // If the chunk is marked once it could not be cancelled by isMarked 87 | int chunkIndex = ((int)(chunk - buffer)) / CHUNK_SIZE; 88 | toMarkFill[chunkIndex] |= isMarked; 89 | 90 | // An improvement here is that we can write data to file directly from data pointer if 91 | // it has a size larger than 512 92 | do { 93 | if (CHUNK_SIZE - index > remainder) { 94 | endIndex = remainder; 95 | } 96 | else if (index == 0){ 97 | dataFile->write(data, CHUNK_SIZE); 98 | remainder -= CHUNK_SIZE; 99 | 100 | continue; 101 | } 102 | else { 103 | endIndex = CHUNK_SIZE - index; 104 | } 105 | 106 | for (int i = 0; i < endIndex; i++) { 107 | chunk[index++] = data[i]; 108 | } 109 | 110 | if (index >= CHUNK_SIZE) { 111 | if (toMarkFill[chunkIndex]) { 112 | isFilled[chunkIndex] = 1; 113 | toMarkFill[chunkIndex] = 0; 114 | } 115 | 116 | chunkIndex++; 117 | if (chunkIndex >= NUM_CHUNK) { 118 | chunkIndex = 0; 119 | 120 | } 121 | 122 | if (!isFilled[chunkIndex]) { 123 | chunk = buffer + (chunkIndex * CHUNK_SIZE); 124 | index = 0; 125 | } else { 126 | stopped = 1; 127 | } 128 | 129 | /* 130 | for (size_t i = 0; i < NUM_CHUNK; i++) { 131 | if (isFilled[i] == 0) { 132 | chunk = buffer + (i * CHUNK_SIZE); 133 | index = 0; 134 | 135 | break; 136 | } 137 | } 138 | */ 139 | } 140 | 141 | remainder -= endIndex; 142 | } 143 | while (remainder != 0); 144 | 145 | } 146 | 147 | /* because of never overwriting the data so we are sure if stopped happened and 148 | This method called then the chunk should be equal to the next chunk, chunks are 149 | filled sequentially. 150 | */ 151 | void WAVGenerator::writeChunks() { 152 | int fillingChunk = ((int)(chunk - buffer)) / CHUNK_SIZE; 153 | 154 | while (toWriteChunk != fillingChunk) { 155 | if (isFilled[toWriteChunk]) { 156 | //serial->print("This chunk: "); 157 | //serial->println(toWriteChunk); 158 | 159 | audio.dataFile->write(buffer + (CHUNK_SIZE * toWriteChunk), CHUNK_SIZE); 160 | isFilled[toWriteChunk] = 0; 161 | sampleChunkSize += CHUNK_SIZE; 162 | } 163 | else { // This should only happen when stopped is no happened 164 | break; 165 | } 166 | 167 | toWriteChunk++; 168 | if (toWriteChunk >= NUM_CHUNK) { 169 | toWriteChunk = 0; 170 | } 171 | } 172 | 173 | if (stopped) { 174 | stopped = 0; 175 | index = 0; 176 | 177 | fillingChunk++; 178 | if (fillingChunk >= NUM_CHUNK) { 179 | fillingChunk = 0; 180 | } 181 | 182 | chunk = buffer + (fillingChunk * CHUNK_SIZE); 183 | } 184 | } 185 | 186 | bool WAVGenerator::isBufferFull() { 187 | return index >= CHUNK_SIZE; 188 | } 189 | 190 | void WAVGenerator::flush() { 191 | writeChunks(); 192 | 193 | int fillingChunk = ((int)(chunk - buffer)) / CHUNK_SIZE; 194 | if (index != 0 && toMarkFill[fillingChunk]) { 195 | audio.dataFile->write(chunk, index); 196 | } 197 | 198 | index = 0; 199 | } 200 | 201 | void WAVGenerator::create() { 202 | flush(); 203 | 204 | appenedFileSize(); 205 | 206 | 207 | closeFile(); 208 | } 209 | 210 | void WAVGenerator::appenedFileSize() { 211 | // 36 + SubChunk2Size, or more precisely: 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size) @4 212 | uint32_t chunkSizeTotal = sampleChunkSize + 36; 213 | audio.dataFile->seek(4); 214 | audio.dataFile->write((uint8_t*) &chunkSizeTotal, sizeof(chunkSizeTotal)); 215 | 216 | // Subchunk2Size @40 217 | uint32_t sub2ChunkSize = sampleChunkSize; 218 | audio.dataFile->seek(40); 219 | audio.dataFile->write((uint8_t*) &sub2ChunkSize, sizeof(sub2ChunkSize)); 220 | } 221 | 222 | void WAVGenerator::closeFile() { 223 | audio.dataFile->close(); 224 | audio.dataFile = 0; 225 | } 226 | 227 | 228 | void WAVGenerator::putInArray(uint8_t* dest, uint32_t src, uint8_t len) { 229 | for (int i = 0; i < len; i++) { 230 | dest[i] = (src >> (i * 8)) & (uint32_t)0x000000FF; 231 | } 232 | } 233 | 234 | /* 235 | void WAVGenerator::markCurrentChunk() { 236 | int fillingChunk = ((int)(chunk - buffer)) / CHUNK_SIZE; 237 | toMarkFill[fillingChunk] = 1; 238 | } 239 | */ 240 | 241 | // This should be debugged 242 | void WAVGenerator::markIgnoredChunks(uint16_t numSamples) { 243 | int fillingChunk = ((int)(chunk - buffer)) / CHUNK_SIZE; 244 | //toMarkFill[fillingChunk] = 1; 245 | 246 | int8_t minChunks = ((numSamples + (numSamples % CHUNK_SIZE)) / CHUNK_SIZE) + 12; 247 | while (minChunks >= 0) { 248 | if (fillingChunk >= minChunks /*&& isFilled[fillingChunk - minChunks]*/) { 249 | isFilled[fillingChunk - minChunks] = 1; 250 | } 251 | else if (fillingChunk < minChunks /*&& isFilled[(NUM_CHUNK + fillingChunk) - minChunks]*/) { 252 | isFilled[(NUM_CHUNK + fillingChunk) - minChunks] = 1; 253 | } 254 | 255 | minChunks--; 256 | } 257 | } 258 | 259 | void WAVGenerator::markAllChunks() { 260 | for (int i = 0; i < NUM_CHUNK; i++) 261 | toMarkFill[i] = 1; 262 | } 263 | 264 | /* 265 | inline void WAVGenerator::appendBufferOverwrite(uint8_t* data, uint16_t len, uint8_t stat) { 266 | uint16_t remainder = len; 267 | uint16_t endIndex = 0; 268 | 269 | // An improvement here is that we can write data to file directly from data pointer if 270 | // it has a size larger than 512 271 | do { 272 | if (CHUNK_SIZE - index > remainder) { 273 | endIndex = remainder; 274 | } 275 | else if (index == 0){ 276 | audio.dataFile->write(data, CHUNK_SIZE); 277 | remainder -= CHUNK_SIZE; 278 | 279 | continue; 280 | } 281 | else { 282 | endIndex = CHUNK_SIZE - index; 283 | } 284 | 285 | for (int i = 0; i < endIndex; i++) { 286 | chunk[index++] = data[i]; 287 | } 288 | 289 | if (index >= CHUNK_SIZE) { 290 | int chunkIndex = ((int)(chunk - buffer)) / CHUNK_SIZE; 291 | if (toMarkFill[chunkIndex]) { 292 | isFilled[chunkIndex] = 1; 293 | } 294 | 295 | chunkIndex++; 296 | if (chunkIndex >= NUM_CHUNK) { 297 | chunkIndex = 0; 298 | } 299 | 300 | 301 | chunk = buffer + (chunkIndex * CHUNK_SIZE); 302 | index = 0; 303 | } 304 | 305 | remainder -= endIndex; 306 | } 307 | while (remainder != 0); 308 | } 309 | */ 310 | 311 | /* 312 | void WAVGenerator::writeMarkedChunks() { 313 | int fillingChunk = ((int)(chunk - buffer)) / CHUNK_SIZE; 314 | 315 | while (toWriteChunk != fillingChunk) { 316 | if (isMarked[toWriteChunk]) { 317 | audio.dataFile->write(buffer + (CHUNK_SIZE * toWriteChunk), CHUNK_SIZE); 318 | isMarked[toWriteChunk] = 0; 319 | isFilled[toWriteChunk] = 0; 320 | sampleChunkSize += CHUNK_SIZE; 321 | } 322 | else { // This should only happen when stopped is no happened 323 | break; 324 | } 325 | 326 | toWriteChunk++; 327 | if (toWriteChunk >= NUM_CHUNK) { 328 | toWriteChunk = 0; 329 | } 330 | } 331 | 332 | if (stopped) { 333 | stopped = 0; 334 | index = 0; 335 | 336 | fillingChunk++; 337 | if (fillingChunk >= NUM_CHUNK) { 338 | fillingChunk = 0; 339 | } 340 | 341 | chunk = buffer + (fillingChunk * CHUNK_SIZE); 342 | } 343 | } 344 | */ --------------------------------------------------------------------------------