├── src ├── ggwave.h └── ggwave │ ├── reed-solomon │ ├── LICENSE │ ├── poly.hpp │ ├── gf.hpp │ └── rs.hpp │ ├── fft.h │ └── ggwave.h ├── examples ├── arduino-rx │ ├── fritzing-sketch.fzz │ ├── fritzing-sketch_bb.png │ ├── README.md │ └── arduino-rx.ino ├── arduino-tx │ ├── fritzing-sketch.fzz │ ├── fritzing-sketch_bb.png │ ├── README.md │ └── arduino-tx.ino ├── esp32-rx │ ├── fritzing-sketch.fzz │ ├── fritzing-sketch_bb.png │ ├── README.md │ └── esp32-rx.ino └── rp2040-rx │ ├── fritzing-sketch.fzz │ ├── fritzing-sketch_bb.png │ ├── mic-analog.h │ ├── README.md │ ├── mic-analog.cpp │ └── rp2040-rx.ino ├── library.properties ├── LICENSE ├── extras └── sync.sh └── README.md /src/ggwave.h: -------------------------------------------------------------------------------- 1 | #ifndef ggwave_h 2 | #define ggwave_h 3 | 4 | #include 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /examples/arduino-rx/fritzing-sketch.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/arduino-rx/fritzing-sketch.fzz -------------------------------------------------------------------------------- /examples/arduino-tx/fritzing-sketch.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/arduino-tx/fritzing-sketch.fzz -------------------------------------------------------------------------------- /examples/esp32-rx/fritzing-sketch.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/esp32-rx/fritzing-sketch.fzz -------------------------------------------------------------------------------- /examples/rp2040-rx/fritzing-sketch.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/rp2040-rx/fritzing-sketch.fzz -------------------------------------------------------------------------------- /examples/esp32-rx/fritzing-sketch_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/esp32-rx/fritzing-sketch_bb.png -------------------------------------------------------------------------------- /examples/rp2040-rx/fritzing-sketch_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/rp2040-rx/fritzing-sketch_bb.png -------------------------------------------------------------------------------- /examples/arduino-rx/fritzing-sketch_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/arduino-rx/fritzing-sketch_bb.png -------------------------------------------------------------------------------- /examples/arduino-tx/fritzing-sketch_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ggerganov/ggwave-arduino/HEAD/examples/arduino-tx/fritzing-sketch_bb.png -------------------------------------------------------------------------------- /examples/arduino-tx/README.md: -------------------------------------------------------------------------------- 1 | # arduino-tx 2 | 3 | This is a sample project for transmitting data via sound using [Arduino Uno](https://store.arduino.cc/products/arduino-uno-rev3) microcontroller. 4 | 5 | ## Setup 6 | 7 | - Arduino Uno R3 8 | - Generic buzzer 9 | 10 | ![Sketch-Breadboard](fritzing-sketch_bb.png) 11 | ![IMG_0257](https://user-images.githubusercontent.com/1991296/173232151-c01d01e9-8b36-4705-83a9-fb52b58382c7.jpg) 12 | 13 | ## Demo 14 | 15 | https://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4 16 | 17 | [Watch high quality on Youtube](https://youtu.be/qbzKo3zbQcI) 18 | -------------------------------------------------------------------------------- /examples/rp2040-rx/mic-analog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | */ 7 | 8 | #ifndef _PICO_ANALOG_MICROPHONE_H_ 9 | #define _PICO_ANALOG_MICROPHONE_H_ 10 | 11 | #include 12 | #include 13 | 14 | typedef void (*analog_samples_ready_handler_t)(void); 15 | 16 | struct analog_microphone_config { 17 | uint32_t gpio; 18 | float bias_voltage; 19 | uint32_t sample_rate; 20 | uint32_t sample_buffer_size; 21 | }; 22 | 23 | int analog_microphone_init(const struct analog_microphone_config* config); 24 | void analog_microphone_deinit(); 25 | 26 | int analog_microphone_start(); 27 | void analog_microphone_stop(); 28 | 29 | void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler); 30 | 31 | int analog_microphone_read(int16_t* buffer, size_t samples); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /examples/rp2040-rx/README.md: -------------------------------------------------------------------------------- 1 | # rp2040-rx 2 | 3 | This is a sample project for receiving audio data using [RP2040](https://www.espressif.com/en/products/socs/esp32) microcontroller. 4 | The chip has a built-in 12-bit ADC which is used to process the analog audio from the external microphone module in real-time. 5 | 6 | ## Setup 7 | 8 | - Raspberry Pi Pico (or other RP2040 board) 9 | - Microphone, tested with the following, but others could be also supported: 10 | - Analog: 11 | - MAX9814 12 | - KY-037 13 | - KY-038 14 | - WS Sound sensor 15 | 16 | ## Pinout 17 | 18 | ### Analog Microphone 19 | 20 | | MCU | Mic | 21 | | ------- | --------- | 22 | | GND | GND | 23 | | 3.3V | VCC / VDD | 24 | | GPIO 26 | Out | 25 | 26 | ![Sketch-Breadboard](fritzing-sketch_bb.png) 27 | 28 | ![1658510571716150](https://user-images.githubusercontent.com/1991296/180506853-01954beb-ccd4-4b71-ac20-232899d99abf.jpg) 29 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ggwave 2 | version=0.1.10 3 | author=@ggerganov 4 | maintainer=@ggerganov 5 | sentence=Tiny data-over-sound library

6 | paragraph=This library allows you to transmit and receive short data messages via sound. Broadcast to multiple nearby devices at once. Send data by simply attaching a piezo buzzer to a pin. Pair devices via audio QR codes.

Use the free Waver application to easily send and receive data from your microcontroller: Web | Android | iOS

Watch a short video demonstration of ggwave
7 | category=Communication 8 | url=https://github.com/ggerganov/ggwave-arduino 9 | architectures=* 10 | includes=ggwave.h 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Georgi Gerganov 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 | -------------------------------------------------------------------------------- /src/ggwave/reed-solomon/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2015 Mike Lubinets, github.com/mersinvald 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation files 5 | (the “Software”), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/arduino-rx/README.md: -------------------------------------------------------------------------------- 1 | # arduino-rx 2 | 3 | This is a sample project for receiving and transmitting audio data using [Arduino RP2040](https://docs.arduino.cc/hardware/nano-rp2040-connect) microcontroller. 4 | The development board has a built-in microphone which makes this device very suitable for data-over-sound projects. 5 | 6 | ## Setup 7 | 8 | - Arduino RP2040 Connect 9 | - OLED SSD1306 10 | - Generic speaker 11 | 12 | ## Pinout for Arduino Nano RP2040 Connect 13 | 14 | ### I2C Display (optional) 15 | 16 | | MCU | Display | 17 | | ------------- | --------- | 18 | | GND | GND | 19 | | 3.3V | VCC / VDD | 20 | | D18 / GPIO 12 | SDA | 21 | | D19 / GPIO 13 | SCL | 22 | 23 | ### Peripherals (optional) 24 | 25 | | MCU | Periph. | 26 | | ------------- | ------- | 27 | | D5 / GPIO 17 | Button | 28 | | D10 / GPIO 5 | Speaker | 29 | 30 | ![Sketch-Breadboard](fritzing-sketch_bb.png) 31 | 32 | ![Sketch-Photo](https://user-images.githubusercontent.com/1991296/177850326-e5fefde3-93ee-4cf9-8fa5-861eef9565f7.JPEG) 33 | 34 | ## Demo 35 | 36 | https://user-images.githubusercontent.com/1991296/177210657-3c7421ce-5c12-4caf-a86c-251191eefe50.mp4 37 | 38 | [Watch high quality on Youtube](https://youtu.be/HiDpGvnxPLs) 39 | -------------------------------------------------------------------------------- /extras/sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Sync source files from the main ggwave repo: 4 | # 5 | # https://github.com/ggerganov/ggwave 6 | # 7 | 8 | cd "$(dirname "$0")" 9 | cd .. 10 | 11 | mkdir -p src/ggwave 12 | 13 | cp -v ../ggwave/include/ggwave/ggwave.h ./src/ggwave/ 14 | cp -v ../ggwave/src/fft.h ./src/ggwave/ 15 | cp -v ../ggwave/src/ggwave.cpp ./src/ggwave/ 16 | cp -vrp ../ggwave/src/reed-solomon/* ./src/ggwave/reed-solomon/ 17 | 18 | mkdir -p examples/arduino-rx 19 | 20 | cp -v ../ggwave/examples/arduino-rx/arduino-rx.ino ./examples/arduino-rx/ 21 | cp -v ../ggwave/examples/arduino-rx/README.md ./examples/arduino-rx/ 22 | cp -v ../ggwave/examples/arduino-rx/fritzing* ./examples/arduino-rx/ 23 | 24 | mkdir -p examples/esp32-rx 25 | 26 | cp -v ../ggwave/examples/esp32-rx/esp32-rx.ino ./examples/esp32-rx/ 27 | cp -v ../ggwave/examples/esp32-rx/README.md ./examples/esp32-rx/ 28 | cp -v ../ggwave/examples/esp32-rx/fritzing* ./examples/esp32-rx/ 29 | 30 | mkdir -p examples/arduino-tx 31 | 32 | cp -v ../ggwave/examples/arduino-tx/arduino-tx.ino ./examples/arduino-tx/ 33 | cp -v ../ggwave/examples/arduino-tx/README.md ./examples/arduino-tx/ 34 | cp -v ../ggwave/examples/arduino-tx/fritzing* ./examples/arduino-tx/ 35 | 36 | mkdir -p examples/rp2040-rx 37 | 38 | cp -v ../ggwave/examples/rp2040-rx/rp2040-rx.ino ./examples/rp2040-rx/ 39 | cp -v ../ggwave/examples/rp2040-rx/*.h ./examples/rp2040-rx/ 40 | cp -v ../ggwave/examples/rp2040-rx/*.cpp ./examples/rp2040-rx/ 41 | cp -v ../ggwave/examples/rp2040-rx/README.md ./examples/rp2040-rx/ 42 | cp -v ../ggwave/examples/rp2040-rx/fritzing* ./examples/rp2040-rx/ 43 | 44 | -------------------------------------------------------------------------------- /examples/esp32-rx/README.md: -------------------------------------------------------------------------------- 1 | # esp32-rx 2 | 3 | This is a sample project for receiving audio data using [ESP32](https://www.espressif.com/en/products/socs/esp32) microcontroller. 4 | The chip has a built-in 12-bit ADC which is used to process the analog audio from the external microphone module in real-time. 5 | The program also support input from I2S MEMS microphones that does not require the usage of the ADC. 6 | The received messages are optionally displayed on the attached OLED display. 7 | 8 | ## Setup 9 | 10 | - NodeMCU-ESP32 11 | - OLED SSD1306 12 | - Microphone, tested with the following, but others could be also supported: 13 | - Analog: 14 | - MAX9814 15 | - KY-037 16 | - KY-038 17 | - WS Sound sensor 18 | - I2S MEMS: 19 | - SPH0645 20 | 21 | ## Pinout 22 | 23 | ### Analog Microphone 24 | 25 | | MCU | Mic | 26 | | ------- | --------- | 27 | | GND | GND | 28 | | 3.3V | VCC / VDD | 29 | | GPIO 35 | Out | 30 | 31 | ### Digital (I2S) Microphone 32 | 33 | | MCU | Mic | 34 | | ------- | ----------- | 35 | | GND | GND | 36 | | 3.3V | VCC / VDD | 37 | | GPIO 26 | BCLK | 38 | | GPIO 33 | Data / DOUT | 39 | | GPIO 25 | LRCL | 40 | 41 | ### I2C Display (optional) 42 | 43 | | MCU | Display | 44 | | ------- | ----------- | 45 | | GND | GND | 46 | | 3.3V | VCC / VDD | 47 | | GPIO 21 | SDA | 48 | | GPIO 22 | SCL | 49 | 50 | ![Sketch-Breadboard](fritzing-sketch_bb.png) 51 | 52 | ![Sketch-photo](https://user-images.githubusercontent.com/1991296/177842221-411c77a4-09cd-43b7-988f-44eebbad8f8c.JPEG) 53 | 54 | ## Demo 55 | 56 | https://user-images.githubusercontent.com/1991296/177211906-2102e9fa-8203-4b80-82e6-4839bf66f01f.mp4 57 | 58 | [Watch high quality on Youtube](https://youtu.be/38JoMwdpH6I) 59 | -------------------------------------------------------------------------------- /src/ggwave/reed-solomon/poly.hpp: -------------------------------------------------------------------------------- 1 | /* Author: Mike Lubinets (aka mersinvald) 2 | * Date: 29.12.15 3 | * 4 | * See LICENSE */ 5 | 6 | #ifndef POLY_H 7 | #define POLY_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace RS { 14 | 15 | struct Poly { 16 | Poly() 17 | : length(0), _memory(NULL) {} 18 | 19 | Poly(uint8_t id, uint16_t offset, uint8_t size) \ 20 | : length(0), _id(id), _size(size), _offset(offset), _memory(NULL) {} 21 | 22 | /* @brief Append number at the end of polynomial 23 | * @param num - number to append 24 | * @return false if polynomial can't be stretched */ 25 | inline bool Append(uint8_t num) { 26 | assert(length < _size); 27 | ptr()[length++] = num; 28 | return true; 29 | } 30 | 31 | /* @brief Polynomial initialization */ 32 | inline void Init(uint8_t id, uint16_t offset, uint8_t size, uint8_t** memory_ptr) { 33 | this->_id = id; 34 | this->_offset = offset; 35 | this->_size = size; 36 | this->length = 0; 37 | this->_memory = memory_ptr; 38 | } 39 | 40 | /* @brief Polynomial memory zeroing */ 41 | inline void Reset() { 42 | memset((void*)ptr(), 0, this->_size); 43 | } 44 | 45 | /* @brief Copy polynomial to memory 46 | * @param src - source byte-sequence 47 | * @param size - size of polynomial 48 | * @param offset - write offset */ 49 | inline void Set(const uint8_t* src, uint8_t len, uint8_t offset = 0) { 50 | assert(src && len <= this->_size-offset); 51 | memcpy(ptr()+offset, src, len * sizeof(uint8_t)); 52 | length = len + offset; 53 | } 54 | 55 | #define poly_max(a, b) ((a > b) ? (a) : (b)) 56 | 57 | inline void Copy(const Poly* src) { 58 | length = poly_max(length, src->length); 59 | Set(src->ptr(), length); 60 | } 61 | 62 | inline uint8_t& at(uint8_t i) const { 63 | assert(i < _size); 64 | return ptr()[i]; 65 | } 66 | 67 | inline uint8_t id() const { 68 | return _id; 69 | } 70 | 71 | inline uint8_t size() const { 72 | return _size; 73 | } 74 | 75 | // Returns pointer to memory of this polynomial 76 | inline uint8_t* ptr() const { 77 | assert(_memory && *_memory); 78 | return (*_memory) + _offset; 79 | } 80 | 81 | uint8_t length; 82 | 83 | protected: 84 | 85 | uint8_t _id; 86 | uint8_t _size; // Size of reserved memory for this polynomial 87 | uint16_t _offset; // Offset in memory 88 | uint8_t** _memory; // Pointer to pointer to memory 89 | }; 90 | 91 | 92 | } 93 | 94 | #endif // POLY_H 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ggwave-arduino 2 | 3 | Tiny data-over-sound library for microcontrollers 4 | 5 | ### [Main repo: ggwave](https://github.com/ggerganov/ggwave) 6 | 7 | Tested microcontrollers: 8 | - [Arduino Nano RP2040 Connect](https://docs.arduino.cc/hardware/nano-rp2040-connect) 9 | - [NodeMCU-32S, ESP32](https://www.waveshare.com/nodemcu-32s.htm) 10 | - [Teensy 4.0 board](https://www.pjrc.com/store/teensy40.html) 11 | - [Arduino Uno Rev3](https://store-usa.arduino.cc/products/arduino-uno-rev3) (Tx only) 12 | 13 | The easiest way to send and receive audio data is via the [Waver](https://github.com/ggerganov/ggwave/tree/master/examples/waver) application. It is freely available for Android, iOS and can also run directly in the browser: https://waver.ggerganov.com 14 | 15 | Download on the App Store 16 | Get it on Google Play 17 | 18 | The following settings will allow you to test the provided examples without having to modify any of the parameters in the sketches: 19 | 20 |

21 | 22 | 23 | 26 | 29 | 32 | 33 |
24 | Talking buttons 25 | 27 | Talking buttons 28 | 30 | Talking buttons 31 |
34 |

35 |

36 | Img. Using the Waver app. Left: settings for fixed-length transmission. Center: Enable DSS option. Right: Transmit messages 37 |

38 | 39 | ### Short demonstration of ggwave 40 | 41 | 42 | 43 | ### Using with Arduino IDE 44 | 45 | Install the `ggwave` library via `Tools` -> `Manage Libraries` and select one of the available examples from `File` -> `Examples` -> `ggwave`: 46 | 47 | image 48 | 49 | See the [examples](examples) folder for sample circuits. Feel free to report any problems or feedback by opening an issue in this repo. 50 | 51 | ### ggwave-cli 52 | 53 | Another way to communicate with the microcontrollers is via the [ggwave-cli](https://github.com/ggerganov/ggwave/tree/master/examples/ggwave-cli) command line tool, available in the main [ggwave](https://github.com/ggerganov/ggwave) repository. For example, run the following command to transmit a 16-byte `fixed-length` message, using Direct Sequence Spread (DSS) with `[MT] Fastest` protocol: 54 | 55 | ```bash 56 | $ ./bin/ggwave-cli -l16 -s -t11 57 | ``` 58 | 59 | ![image](https://user-images.githubusercontent.com/1991296/177856175-d43fc9aa-1a10-4270-80e4-765a361b30d7.png) 60 | 61 | When sending and receiving messages, make sure that both the sender and the receiver have the `fixed-length` option enabled and that the number of bytes for `payloadLength` is the same. Also note that microcontrollers currently support only the mono-tone `[MT]` and dual-tone `[DT]` transmission protocols. 62 | -------------------------------------------------------------------------------- /examples/arduino-tx/arduino-tx.ino: -------------------------------------------------------------------------------- 1 | // arduino-tx 2 | // 3 | // Sample sketch for transmitting data using "ggwave" 4 | // 5 | // Tested with: 6 | // - Arduino Uno R3 7 | // - Arduino Nano RP2040 Connect 8 | // - NodeMCU-ESP32-S 9 | // - NodeMCU-ESP8266EX 10 | // 11 | // If you want to perform a quick test, you can use the free "Waver" application: 12 | // - Web: https://waver.ggerganov.com 13 | // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver 14 | // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 15 | // 16 | // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of 17 | // bytes to be equal to "payloadLength" used in the sketch. 18 | // 19 | // Demo: https://youtu.be/qbzKo3zbQcI 20 | // 21 | // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx 22 | // 23 | 24 | #include 25 | 26 | // Pin configuration 27 | const int kPinLed0 = 13; 28 | const int kPinSpeaker = 10; 29 | const int kPinButton0 = 2; 30 | const int kPinButton1 = 4; 31 | 32 | const int samplesPerFrame = 128; 33 | const int sampleRate = 6000; 34 | 35 | // Global GGwave instance 36 | GGWave ggwave; 37 | 38 | char txt[64]; 39 | #define P(str) (strcpy_P(txt, PSTR(str)), txt) 40 | 41 | // Helper function to output the generated GGWave waveform via a buzzer 42 | void send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) { 43 | Serial.print(F("Sending text: ")); 44 | Serial.println(text); 45 | 46 | ggwave.init(text, protocolId); 47 | ggwave.encode(); 48 | 49 | const auto & protocol = GGWave::Protocols::tx()[protocolId]; 50 | const auto tones = ggwave.txTones(); 51 | const auto duration_ms = protocol.txDuration_ms(ggwave.samplesPerFrame(), ggwave.sampleRateOut()); 52 | for (auto & curTone : tones) { 53 | const auto freq_hz = (protocol.freqStart + curTone)*ggwave.hzPerSample(); 54 | tone(pin, freq_hz); 55 | delay(duration_ms); 56 | } 57 | 58 | noTone(pin); 59 | digitalWrite(pin, LOW); 60 | } 61 | 62 | void setup() { 63 | Serial.begin(57600); 64 | while (!Serial); 65 | 66 | pinMode(kPinLed0, OUTPUT); 67 | pinMode(kPinSpeaker, OUTPUT); 68 | pinMode(kPinButton0, INPUT); 69 | pinMode(kPinButton1, INPUT); 70 | 71 | // Initialize "ggwave" 72 | { 73 | Serial.println(F("Trying to initialize the ggwave instance")); 74 | 75 | ggwave.setLogFile(nullptr); 76 | 77 | auto p = GGWave::getDefaultParameters(); 78 | 79 | // Adjust the "ggwave" parameters to your needs. 80 | // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. 81 | p.payloadLength = 16; 82 | p.sampleRateInp = sampleRate; 83 | p.sampleRateOut = sampleRate; 84 | p.sampleRate = sampleRate; 85 | p.samplesPerFrame = samplesPerFrame; 86 | p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; 87 | p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; 88 | p.operatingMode = GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES | GGWAVE_OPERATING_MODE_USE_DSS; 89 | 90 | // Protocols to use for TX 91 | GGWave::Protocols::tx().only(GGWAVE_PROTOCOL_MT_FASTEST); 92 | 93 | // Sometimes, the speaker might not be able to produce frequencies in the Mono-tone range of 1-2 kHz. 94 | // In such cases, you can shift the base frequency up by changing the "freqStart" parameter of the protocol. 95 | // Make sure that in the receiver (for example, the "Waver" app) the base frequency is shifted by the same amount. 96 | // Here we shift the frequency by +48 bins. Each bin is equal to 48000/1024 = 46.875 Hz. 97 | // So the base frequency is shifted by +2250 Hz. 98 | //GGWave::Protocols::tx()[GGWAVE_PROTOCOL_MT_FASTEST].freqStart += 48; 99 | 100 | // Initialize the ggwave instance and print the memory usage 101 | ggwave.prepare(p); 102 | Serial.println(ggwave.heapSize()); 103 | 104 | Serial.println(F("Instance initialized successfully!")); 105 | } 106 | } 107 | 108 | // Button state 109 | int pressed = 0; 110 | bool isDown = false; 111 | 112 | void loop() { 113 | delay(1000); 114 | 115 | digitalWrite(kPinLed0, HIGH); 116 | send_text(ggwave, kPinSpeaker, P("Hello!"), GGWAVE_PROTOCOL_MT_FASTEST); 117 | digitalWrite(kPinLed0, LOW); 118 | 119 | delay(2000); 120 | 121 | digitalWrite(kPinLed0, HIGH); 122 | send_text(ggwave, kPinSpeaker, P("This is a"), GGWAVE_PROTOCOL_MT_FASTEST); 123 | send_text(ggwave, kPinSpeaker, P("ggwave demo"), GGWAVE_PROTOCOL_MT_FASTEST); 124 | digitalWrite(kPinLed0, LOW); 125 | 126 | delay(2000); 127 | 128 | digitalWrite(kPinLed0, HIGH); 129 | send_text(ggwave, kPinSpeaker, P("The arduino"), GGWAVE_PROTOCOL_MT_FASTEST); 130 | delay(200); 131 | send_text(ggwave, kPinSpeaker, P("transmits data"), GGWAVE_PROTOCOL_MT_FASTEST); 132 | delay(200); 133 | send_text(ggwave, kPinSpeaker, P("using sound"), GGWAVE_PROTOCOL_MT_FASTEST); 134 | delay(200); 135 | send_text(ggwave, kPinSpeaker, P("through a buzzer"), GGWAVE_PROTOCOL_MT_FASTEST); 136 | digitalWrite(kPinLed0, LOW); 137 | 138 | delay(1000); 139 | 140 | digitalWrite(kPinLed0, HIGH); 141 | send_text(ggwave, kPinSpeaker, P("The sound is"), GGWAVE_PROTOCOL_MT_FASTEST); 142 | delay(200); 143 | send_text(ggwave, kPinSpeaker, P("decoded in a"), GGWAVE_PROTOCOL_MT_FASTEST); 144 | delay(200); 145 | send_text(ggwave, kPinSpeaker, P("web page."), GGWAVE_PROTOCOL_MT_FASTEST); 146 | digitalWrite(kPinLed0, LOW); 147 | 148 | delay(1000); 149 | 150 | digitalWrite(kPinLed0, HIGH); 151 | send_text(ggwave, kPinSpeaker, P("Press the button!"), GGWAVE_PROTOCOL_MT_FASTEST); 152 | digitalWrite(kPinLed0, LOW); 153 | 154 | Serial.println(F("Starting main loop")); 155 | 156 | while (true) { 157 | int but0 = digitalRead(kPinButton0); 158 | int but1 = digitalRead(kPinButton1); 159 | 160 | if (but1 == LOW && isDown == false) { 161 | delay(200); 162 | ++pressed; 163 | isDown = true; 164 | } else if (but1 == HIGH) { 165 | isDown = false; 166 | } 167 | 168 | if (but0 == LOW) { 169 | snprintf(txt, 16, "Pressed: %d", pressed); 170 | 171 | digitalWrite(kPinLed0, HIGH); 172 | send_text(ggwave, kPinSpeaker, txt, GGWAVE_PROTOCOL_MT_FASTEST); 173 | digitalWrite(kPinLed0, LOW); 174 | pressed = 0; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /examples/rp2040-rx/mic-analog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | */ 7 | 8 | #include "mic-analog.h" 9 | 10 | #include "hardware/adc.h" 11 | #include "hardware/clocks.h" 12 | #include "hardware/dma.h" 13 | #include "hardware/irq.h" 14 | 15 | #include 16 | #include 17 | 18 | #define ANALOG_RAW_BUFFER_COUNT 2 19 | 20 | static struct { 21 | int dma_channel; 22 | uint16_t* raw_buffer[ANALOG_RAW_BUFFER_COUNT]; 23 | uint32_t buffer_size; 24 | int16_t bias; 25 | uint32_t dma_irq; 26 | 27 | volatile int raw_buffer_write_index; 28 | volatile int raw_buffer_read_index; 29 | 30 | analog_microphone_config config; 31 | analog_samples_ready_handler_t samples_ready_handler; 32 | } analog_mic; 33 | 34 | static void analog_dma_handler(); 35 | 36 | int analog_microphone_init(const struct analog_microphone_config* config) { 37 | memset(&analog_mic, 0x00, sizeof(analog_mic)); 38 | memcpy(&analog_mic.config, config, sizeof(analog_mic.config)); 39 | 40 | if (config->gpio < 26 || config->gpio > 29) { 41 | return -1; 42 | } 43 | 44 | size_t raw_buffer_size = config->sample_buffer_size * sizeof(analog_mic.raw_buffer[0][0]); 45 | 46 | analog_mic.buffer_size = config->sample_buffer_size; 47 | analog_mic.bias = ((int16_t)((config->bias_voltage * 4095) / 3.3)); 48 | 49 | for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) { 50 | analog_mic.raw_buffer[i] = (uint16_t* )malloc(raw_buffer_size); 51 | if (analog_mic.raw_buffer[i] == NULL) { 52 | analog_microphone_deinit(); 53 | 54 | return -1; 55 | } 56 | } 57 | 58 | analog_mic.dma_channel = dma_claim_unused_channel(true); 59 | if (analog_mic.dma_channel < 0) { 60 | analog_microphone_deinit(); 61 | 62 | return -1; 63 | } 64 | 65 | float clk_div = (clock_get_hz(clk_adc) / (1.0 * config->sample_rate)) - 1; 66 | 67 | dma_channel_config dma_channel_cfg = dma_channel_get_default_config(analog_mic.dma_channel); 68 | 69 | channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_16); 70 | channel_config_set_read_increment(&dma_channel_cfg, false); 71 | channel_config_set_write_increment(&dma_channel_cfg, true); 72 | channel_config_set_dreq(&dma_channel_cfg, DREQ_ADC); 73 | 74 | analog_mic.dma_irq = DMA_IRQ_0; 75 | 76 | dma_channel_configure( 77 | analog_mic.dma_channel, 78 | &dma_channel_cfg, 79 | analog_mic.raw_buffer[0], 80 | &adc_hw->fifo, 81 | analog_mic.buffer_size, 82 | false 83 | ); 84 | 85 | adc_gpio_init(config->gpio); 86 | 87 | adc_init(); 88 | adc_select_input(config->gpio - 26); 89 | adc_fifo_setup( 90 | true, // Write each completed conversion to the sample FIFO 91 | true, // Enable DMA data request (DREQ) 92 | 1, // DREQ (and IRQ) asserted when at least 1 sample present 93 | false, // We won't see the ERR bit because of 8 bit reads; disable. 94 | false // Don't shift each sample to 8 bits when pushing to FIFO 95 | ); 96 | 97 | adc_set_clkdiv(clk_div); 98 | 99 | return 0; 100 | } 101 | 102 | void analog_microphone_deinit() { 103 | for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) { 104 | if (analog_mic.raw_buffer[i]) { 105 | free(analog_mic.raw_buffer[i]); 106 | 107 | analog_mic.raw_buffer[i] = NULL; 108 | } 109 | } 110 | 111 | if (analog_mic.dma_channel > -1) { 112 | dma_channel_unclaim(analog_mic.dma_channel); 113 | 114 | analog_mic.dma_channel = -1; 115 | } 116 | } 117 | 118 | int analog_microphone_start() { 119 | irq_set_enabled(analog_mic.dma_irq, true); 120 | irq_set_exclusive_handler(analog_mic.dma_irq, analog_dma_handler); 121 | 122 | if (analog_mic.dma_irq == DMA_IRQ_0) { 123 | dma_channel_set_irq0_enabled(analog_mic.dma_channel, true); 124 | } else if (analog_mic.dma_irq == DMA_IRQ_1) { 125 | dma_channel_set_irq1_enabled(analog_mic.dma_channel, true); 126 | } else { 127 | return -1; 128 | } 129 | 130 | analog_mic.raw_buffer_write_index = 0; 131 | analog_mic.raw_buffer_read_index = 0; 132 | 133 | dma_channel_transfer_to_buffer_now( 134 | analog_mic.dma_channel, 135 | analog_mic.raw_buffer[0], 136 | analog_mic.buffer_size 137 | ); 138 | 139 | adc_run(true); // start running the adc 140 | // 141 | return 0; 142 | } 143 | 144 | void analog_microphone_stop() { 145 | adc_run(false); // stop running the adc 146 | 147 | dma_channel_abort(analog_mic.dma_channel); 148 | 149 | if (analog_mic.dma_irq == DMA_IRQ_0) { 150 | dma_channel_set_irq0_enabled(analog_mic.dma_channel, false); 151 | } else if (analog_mic.dma_irq == DMA_IRQ_1) { 152 | dma_channel_set_irq1_enabled(analog_mic.dma_channel, false); 153 | } 154 | 155 | irq_set_enabled(analog_mic.dma_irq, false); 156 | } 157 | 158 | static void analog_dma_handler() { 159 | // clear IRQ 160 | if (analog_mic.dma_irq == DMA_IRQ_0) { 161 | dma_hw->ints0 = (1u << analog_mic.dma_channel); 162 | } else if (analog_mic.dma_irq == DMA_IRQ_1) { 163 | dma_hw->ints1 = (1u << analog_mic.dma_channel); 164 | } 165 | 166 | // get the current buffer index 167 | analog_mic.raw_buffer_read_index = analog_mic.raw_buffer_write_index; 168 | 169 | // get the next capture index to send the dma to start 170 | analog_mic.raw_buffer_write_index = (analog_mic.raw_buffer_write_index + 1) % ANALOG_RAW_BUFFER_COUNT; 171 | 172 | // give the channel a new buffer to write to and re-trigger it 173 | dma_channel_transfer_to_buffer_now( 174 | analog_mic.dma_channel, 175 | analog_mic.raw_buffer[analog_mic.raw_buffer_write_index], 176 | analog_mic.buffer_size 177 | ); 178 | 179 | if (analog_mic.samples_ready_handler) { 180 | analog_mic.samples_ready_handler(); 181 | } 182 | } 183 | 184 | void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler) { 185 | analog_mic.samples_ready_handler = handler; 186 | } 187 | 188 | int analog_microphone_read(int16_t* buffer, size_t samples) { 189 | if (samples > analog_mic.config.sample_buffer_size) { 190 | samples = analog_mic.config.sample_buffer_size; 191 | } 192 | 193 | if (analog_mic.raw_buffer_write_index == analog_mic.raw_buffer_read_index) { 194 | return 0; 195 | } 196 | 197 | uint16_t* in = analog_mic.raw_buffer[analog_mic.raw_buffer_read_index]; 198 | int16_t* out = buffer; 199 | int16_t bias = analog_mic.bias; 200 | 201 | analog_mic.raw_buffer_read_index++; 202 | 203 | for (int i = 0; i < samples; i++) { 204 | *out++ = *in++ - bias; 205 | } 206 | 207 | return samples; 208 | } 209 | -------------------------------------------------------------------------------- /examples/rp2040-rx/rp2040-rx.ino: -------------------------------------------------------------------------------- 1 | // rp2040-rx 2 | // 3 | // Sample sketch for receiving sound data using "ggwave" 4 | // 5 | // Tested MCU boards: 6 | // - Raspberry Pi Pico 7 | // - Arduino Nano RP2040 Connect 8 | // 9 | // Tested analog microphones: 10 | // - MAX9814 11 | // - KY-037 12 | // - KY-038 13 | // - WS Sound sensor 14 | // 15 | // The RP2040 microcontroller has a built-in 12-bit ADC which is used to digitalize the analog signal 16 | // from the external analog microphone. The MCU supports sampling rates up to 500kHz which makes it 17 | // capable of even recording audio in the ultrasound range, given that your microphone's sensitivity 18 | // supports it. 19 | // 20 | // If you want to perform a quick test, you can use the free "Waver" application: 21 | // - Web: https://waver.ggerganov.com 22 | // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver 23 | // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 24 | // 25 | // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of 26 | // bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is 27 | // listed as Rx in the current sketch. 28 | // 29 | // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx 30 | // 31 | // ## Pinout 32 | // 33 | // ### Analog Microphone 34 | // 35 | // | MCU | Mic | 36 | // | ------- | --------- | 37 | // | GND | GND | 38 | // | 3.3V | VCC / VDD | 39 | // | GPIO 26 | Out | 40 | // 41 | 42 | // Uncomment the line coresponding to your microhpone 43 | #define MIC_ANALOG 44 | 45 | // Uncoment this line to enable long-range transmission 46 | // The used protocols are slower and use more memory to decode, but are much more robust 47 | //#define LONG_RANGE 1 48 | 49 | #include 50 | 51 | // Audio capture configuration 52 | using TSample = int16_t; 53 | 54 | const size_t kSampleSize_bytes = sizeof(TSample); 55 | 56 | // High sample rate - better quality, but more CPU/Memory usage 57 | const int sampleRate = 48000; 58 | const int samplesPerFrame = 1024; 59 | 60 | // Low sample rate 61 | //const int sampleRate = 24000; 62 | //const int samplesPerFrame = 512; 63 | 64 | TSample sampleBuffer[samplesPerFrame]; 65 | 66 | #if defined(MIC_ANALOG) 67 | 68 | #include "mic-analog.h" 69 | 70 | volatile int samplesRead = 0; 71 | 72 | const struct analog_microphone_config config = { 73 | // GPIO to use for input, must be ADC compatible (GPIO 26 - 28) 74 | .gpio = 26, 75 | 76 | // bias voltage of microphone in volts 77 | .bias_voltage = 1.25, 78 | 79 | // sample rate in Hz 80 | .sample_rate = sampleRate, 81 | 82 | // number of samples to buffer 83 | .sample_buffer_size = samplesPerFrame, 84 | }; 85 | 86 | void on_analog_samples_ready() 87 | { 88 | // callback from library when all the samples in the library 89 | // internal sample buffer are ready for reading 90 | samplesRead = analog_microphone_read(sampleBuffer, samplesPerFrame); 91 | } 92 | 93 | #endif 94 | 95 | // Global GGwave instance 96 | GGWave ggwave; 97 | 98 | void setup() { 99 | Serial.begin(115200); 100 | while (!Serial); 101 | 102 | // Initialize "ggwave" 103 | { 104 | Serial.println(F("Trying to initialize the ggwave instance")); 105 | 106 | ggwave.setLogFile(nullptr); 107 | 108 | auto p = GGWave::getDefaultParameters(); 109 | 110 | // Adjust the "ggwave" parameters to your needs. 111 | // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. 112 | #ifdef LONG_RANGE 113 | // The "FAST" protocols require 2x more memory, so we reduce the payload length to compensate: 114 | p.payloadLength = 8; 115 | #else 116 | p.payloadLength = 16; 117 | #endif 118 | Serial.print(F("Using payload length: ")); 119 | Serial.println(p.payloadLength); 120 | 121 | p.sampleRateInp = sampleRate; 122 | p.sampleRateOut = sampleRate; 123 | p.sampleRate = sampleRate; 124 | p.samplesPerFrame = samplesPerFrame; 125 | p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; 126 | p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; 127 | p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; 128 | 129 | // Protocols to use for TX 130 | // Remove the ones that you don't need to reduce memory usage 131 | GGWave::Protocols::tx().disableAll(); 132 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); 133 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); 134 | GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); 135 | 136 | // Protocols to use for RX 137 | // Remove the ones that you don't need to reduce memory and CPU usage 138 | GGWave::Protocols::rx().disableAll(); 139 | 140 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_NORMAL, true); 141 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); 142 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); 143 | 144 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_NORMAL, true); 145 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); 146 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); 147 | 148 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_NORMAL, true); 149 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FAST, true); 150 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, true); 151 | 152 | #ifdef LONG_RANGE 153 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FAST, true); 154 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); 155 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); 156 | #endif 157 | 158 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, true); 159 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); 160 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); 161 | 162 | // Print the memory required for the "ggwave" instance: 163 | ggwave.prepare(p, false); 164 | 165 | Serial.print(F("Required memory by the ggwave instance: ")); 166 | Serial.print(ggwave.heapSize()); 167 | Serial.println(F(" bytes")); 168 | 169 | // Initialize the "ggwave" instance: 170 | ggwave.prepare(p, true); 171 | Serial.print(F("Instance initialized successfully! Memory used: ")); 172 | } 173 | 174 | // initialize the analog microphone 175 | if (analog_microphone_init(&config) < 0) { 176 | Serial.println(F("analog microphone initialization failed!")); 177 | while (1) { tight_loop_contents(); } 178 | } 179 | 180 | // set callback that is called when all the samples in the library 181 | // internal sample buffer are ready for reading 182 | analog_microphone_set_samples_ready_handler(on_analog_samples_ready); 183 | 184 | // start capturing data from the analog microphone 185 | if (analog_microphone_start() < 0) { 186 | Serial.println(F("Analog microphone start failed!")); 187 | while (1) { tight_loop_contents(); } 188 | } 189 | 190 | Serial.println(F("setup() done")); 191 | } 192 | 193 | int niter = 0; 194 | int tLastReceive = -10000; 195 | 196 | GGWave::TxRxData result; 197 | 198 | void loop() { 199 | // wait for new samples 200 | while (samplesRead == 0) { tight_loop_contents(); } 201 | 202 | // store and clear the samples read from the callback 203 | int nSamples = samplesRead; 204 | samplesRead = 0; 205 | 206 | // Use this with the serial plotter to observe real-time audio signal 207 | //for (int i = 0; i < nSamples; i++) { 208 | // Serial.printf("%d\n", sampleBuffer[i]); 209 | //} 210 | 211 | // Try to decode any "ggwave" data: 212 | auto tStart = millis(); 213 | 214 | if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) { 215 | Serial.println("Failed to decode"); 216 | } 217 | 218 | auto tEnd = millis(); 219 | 220 | if (++niter % 10 == 0) { 221 | // print the time it took the last decode() call to complete 222 | // should be smaller than samplesPerFrame/sampleRate seconds 223 | // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms 224 | Serial.println(tEnd - tStart); 225 | if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { 226 | Serial.println(F("Warning: decode() took too long to execute!")); 227 | } 228 | } 229 | 230 | // Check if we have successfully decoded any data: 231 | int nr = ggwave.rxTakeData(result); 232 | if (nr > 0) { 233 | Serial.println(tEnd - tStart); 234 | Serial.print(F("Received data with length ")); 235 | Serial.print(nr); // should be equal to p.payloadLength 236 | Serial.println(F(" bytes:")); 237 | 238 | Serial.println((char *) result.data()); 239 | 240 | tLastReceive = tEnd; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/ggwave/reed-solomon/gf.hpp: -------------------------------------------------------------------------------- 1 | /* Author: Mike Lubinets (aka mersinvald) 2 | * Date: 29.12.15 3 | * 4 | * See LICENSE */ 5 | 6 | #ifndef GF_H 7 | #define GF_H 8 | 9 | #include "poly.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace RS { 16 | 17 | namespace gf { 18 | 19 | 20 | /* GF tables pre-calculated for 0x11d primitive polynomial */ 21 | 22 | const uint8_t exp[512] PROGMEM = { 23 | 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 24 | 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d, 25 | 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 26 | 0x8c, 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 27 | 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 28 | 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 29 | 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 30 | 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 31 | 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 32 | 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 33 | 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 34 | 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 35 | 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 36 | 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12, 37 | 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c, 38 | 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2, 39 | 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98, 40 | 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d, 0x27, 41 | 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c, 42 | 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe, 43 | 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7, 44 | 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf, 45 | 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f, 46 | 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17, 47 | 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d, 48 | 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1, 49 | 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb, 50 | 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19, 51 | 0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2, 52 | 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12, 0x24, 53 | 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c, 0x58, 54 | 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2 55 | }; 56 | 57 | const uint8_t log[256] PROGMEM = { 58 | 0x0, 0x0, 0x1, 0x19, 0x2, 0x32, 0x1a, 0xc6, 0x3, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x4, 59 | 0x64, 0xe0, 0xe, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x8, 0x4c, 0x71, 0x5, 60 | 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0xf, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d, 61 | 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x9, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 0x6, 62 | 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36, 63 | 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e, 64 | 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca, 65 | 0x5e, 0x9b, 0x9f, 0xa, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x7, 66 | 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0xd, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3, 67 | 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37, 68 | 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2, 69 | 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f, 70 | 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0xc, 0x6f, 0xf6, 0x6c, 71 | 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb, 72 | 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0xb, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 0x4f, 73 | 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf 74 | }; 75 | 76 | 77 | 78 | /* ################################ 79 | * # OPERATIONS OVER GALUA FIELDS # 80 | * ################################ */ 81 | 82 | /* @brief Addition in Galua Fields 83 | * @param x - left operand 84 | * @param y - right operand 85 | * @return x + y */ 86 | inline uint8_t add(uint8_t x, uint8_t y) { 87 | return x^y; 88 | } 89 | 90 | /* ##### GF substraction ###### */ 91 | /* @brief Substraction in Galua Fields 92 | * @param x - left operand 93 | * @param y - right operand 94 | * @return x - y */ 95 | inline uint8_t sub(uint8_t x, uint8_t y) { 96 | return x^y; 97 | } 98 | 99 | /* @brief Multiplication in Galua Fields 100 | * @param x - left operand 101 | * @param y - rifht operand 102 | * @return x * y */ 103 | inline uint8_t mul(uint16_t x, uint16_t y){ 104 | if (x == 0 || y == 0) 105 | return 0; 106 | #ifdef ARDUINO 107 | return pgm_read_byte(exp + pgm_read_byte(log + x) + pgm_read_byte(log + y)); 108 | #else 109 | return exp[log[x] + log[y]]; 110 | #endif 111 | } 112 | 113 | /* @brief Division in Galua Fields 114 | * @param x - dividend 115 | * @param y - divisor 116 | * @return x / y */ 117 | inline uint8_t div(uint8_t x, uint8_t y){ 118 | assert(y != 0); 119 | if(x == 0) return 0; 120 | #ifdef ARDUINO 121 | return pgm_read_byte(exp + (pgm_read_byte(log + x) + 255 - pgm_read_byte(log + y)) % 255); 122 | #else 123 | return exp[(log[x] + 255 - log[y]) % 255]; 124 | #endif 125 | } 126 | 127 | /* @brief X in power Y w 128 | * @param x - operand 129 | * @param power - power 130 | * @return x^power */ 131 | inline uint8_t pow(uint8_t x, intmax_t power){ 132 | #ifdef ARDUINO 133 | intmax_t i = pgm_read_byte(log + x); 134 | #else 135 | intmax_t i = log[x]; 136 | #endif 137 | i *= power; 138 | i %= 255; 139 | if(i < 0) i = i + 255; 140 | #ifdef ARDUINO 141 | return pgm_read_byte(exp + i); 142 | #else 143 | return exp[i]; 144 | #endif 145 | } 146 | 147 | /* @brief Inversion in Galua Fields 148 | * @param x - number 149 | * @return inversion of x */ 150 | inline uint8_t inverse(uint8_t x){ 151 | #ifdef ARDUINO 152 | return pgm_read_byte(exp + 255 - pgm_read_byte(log + x)); /* == div(1, x); */ 153 | #else 154 | return exp[255 - log[x]]; /* == div(1, x); */ 155 | #endif 156 | } 157 | 158 | /* ########################## 159 | * # POLYNOMIALS OPERATIONS # 160 | * ########################## */ 161 | 162 | /* @brief Multiplication polynomial by scalar 163 | * @param &p - source polynomial 164 | * @param &newp - destination polynomial 165 | * @param x - scalar */ 166 | inline void 167 | poly_scale(const Poly *p, Poly *newp, uint16_t x) { 168 | newp->length = p->length; 169 | for(uint16_t i = 0; i < p->length; i++){ 170 | newp->at(i) = mul(p->at(i), x); 171 | } 172 | } 173 | 174 | /* @brief Addition of two polynomials 175 | * @param &p - right operand polynomial 176 | * @param &q - left operand polynomial 177 | * @param &newp - destination polynomial */ 178 | inline void 179 | poly_add(const Poly *p, const Poly *q, Poly *newp) { 180 | newp->length = poly_max(p->length, q->length); 181 | memset(newp->ptr(), 0, newp->length * sizeof(uint8_t)); 182 | 183 | for(uint8_t i = 0; i < p->length; i++){ 184 | newp->at(i + newp->length - p->length) = p->at(i); 185 | } 186 | 187 | for(uint8_t i = 0; i < q->length; i++){ 188 | newp->at(i + newp->length - q->length) ^= q->at(i); 189 | } 190 | } 191 | 192 | 193 | /* @brief Multiplication of two polynomials 194 | * @param &p - right operand polynomial 195 | * @param &q - left operand polynomial 196 | * @param &newp - destination polynomial */ 197 | inline void 198 | poly_mul(const Poly *p, const Poly *q, Poly *newp) { 199 | newp->length = p->length + q->length - 1; 200 | memset(newp->ptr(), 0, newp->length * sizeof(uint8_t)); 201 | /* Compute the polynomial multiplication (just like the outer product of two vectors, 202 | * we multiply each coefficients of p with all coefficients of q) */ 203 | for(uint8_t j = 0; j < q->length; j++){ 204 | for(uint8_t i = 0; i < p->length; i++){ 205 | newp->at(i+j) ^= mul(p->at(i), q->at(j)); /* == r[i + j] = gf_add(r[i+j], gf_mul(p[i], q[j])) */ 206 | } 207 | } 208 | } 209 | 210 | /* @brief Division of two polynomials 211 | * @param &p - right operand polynomial 212 | * @param &q - left operand polynomial 213 | * @param &newp - destination polynomial */ 214 | inline void 215 | poly_div(const Poly *p, const Poly *q, Poly *newp) { 216 | if(p->ptr() != newp->ptr()) { 217 | memcpy(newp->ptr(), p->ptr(), p->length*sizeof(uint8_t)); 218 | } 219 | 220 | newp->length = p->length; 221 | 222 | uint8_t coef; 223 | 224 | for(int i = 0; i < (p->length-(q->length-1)); i++){ 225 | coef = newp->at(i); 226 | if(coef != 0){ 227 | for(uint8_t j = 1; j < q->length; j++){ 228 | if(q->at(j) != 0) 229 | newp->at(i+j) ^= mul(q->at(j), coef); 230 | } 231 | } 232 | } 233 | 234 | size_t sep = p->length-(q->length-1); 235 | memmove(newp->ptr(), newp->ptr()+sep, (newp->length-sep) * sizeof(uint8_t)); 236 | newp->length = newp->length-sep; 237 | } 238 | 239 | /* @brief Evaluation of polynomial in x 240 | * @param &p - polynomial to evaluate 241 | * @param x - evaluation point */ 242 | inline int8_t 243 | poly_eval(const Poly *p, uint16_t x) { 244 | uint8_t y = p->at(0); 245 | for(uint8_t i = 1; i < p->length; i++){ 246 | y = mul(y, x) ^ p->at(i); 247 | } 248 | return y; 249 | } 250 | 251 | } /* end of gf namespace */ 252 | 253 | } 254 | #endif // GF_H 255 | 256 | -------------------------------------------------------------------------------- /examples/arduino-rx/arduino-rx.ino: -------------------------------------------------------------------------------- 1 | // arduino-rx 2 | // 3 | // Sample sketch for receiving data using "ggwave" 4 | // 5 | // Tested with: 6 | // - Arduino Nano RP2040 Connect 7 | // 8 | // The Arduino Nano RP2040 Connect board has a built-in microphone which is used 9 | // in this example to capture audio data. 10 | // 11 | // The sketch optionally supports displaying the received "ggwave" data on an OLED display. 12 | // Use the DISPLAY_OUTPUT macro to enable or disable this functionality. 13 | // 14 | // If you don't have a display, you can simply observe the decoded data in the serial monitor. 15 | // 16 | // If you want to perform a quick test, you can use the free "Waver" application: 17 | // - Web: https://waver.ggerganov.com 18 | // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver 19 | // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 20 | // 21 | // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of 22 | // bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is 23 | // listed as Rx in the current sketch. 24 | // 25 | // Demo: https://youtu.be/HiDpGvnxPLs 26 | // 27 | // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx 28 | // 29 | // ## Pinout for Arduino Nano RP2040 Connect 30 | // 31 | // ### I2C Display (optional) 32 | // 33 | // | MCU | Display | 34 | // | ------------- | --------- | 35 | // | GND | GND | 36 | // | 3.3V | VCC / VDD | 37 | // | D18 / GPIO 12 | SDA | 38 | // | D19 / GPIO 13 | SCL | 39 | // 40 | // ### Peripherals (optional) 41 | // 42 | // | MCU | Periph. | 43 | // | ------------- | ------- | 44 | // | D5 / GPIO 17 | Button | 45 | // | D10 / GPIO 5 | Speaker | 46 | // 47 | 48 | // Uncoment this line to enable SSD1306 display output 49 | //#define DISPLAY_OUTPUT 1 50 | 51 | // Uncoment this line to enable long-range transmission 52 | // The used protocols are slower and use more memory to decode, but are much more robust 53 | //#define LONG_RANGE 1 54 | 55 | #include 56 | 57 | #include 58 | 59 | // Pin configuration 60 | const int kPinLED0 = 2; 61 | const int kPinButton0 = 5; 62 | const int kPinSpeaker = 10; 63 | 64 | // Audio capture configuration 65 | using TSample = int16_t; 66 | const size_t kSampleSize_bytes = sizeof(TSample); 67 | 68 | const char channels = 1; 69 | const int sampleRate = 6000; 70 | const int samplesPerFrame = 128; 71 | 72 | // Audio capture ring-buffer 73 | const int qpow = 9; 74 | const int qmax = 1 << qpow; 75 | 76 | volatile int qhead = 0; 77 | volatile int qtail = 0; 78 | volatile int qsize = 0; 79 | 80 | TSample sampleBuffer[qmax]; 81 | 82 | // Error handling 83 | volatile int err = 0; 84 | 85 | // Global GGwave instance 86 | GGWave ggwave; 87 | 88 | #ifdef DISPLAY_OUTPUT 89 | 90 | #include 91 | #include 92 | #include 93 | #include 94 | 95 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 96 | #define SCREEN_HEIGHT 32 // OLED display height, in pixels 97 | 98 | // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) 99 | // The pins for I2C are defined by the Wire-library. 100 | // On an arduino UNO: A4(SDA), A5(SCL) 101 | // On an arduino MEGA 2560: 20(SDA), 21(SCL) 102 | // On an arduino LEONARDO: 2(SDA), 3(SCL), ... 103 | #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) 104 | #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 105 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 106 | 107 | #endif 108 | 109 | // Helper function to output the generated GGWave waveform via a buzzer 110 | void send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) { 111 | Serial.print(F("Sending text: ")); 112 | Serial.println(text); 113 | 114 | ggwave.init(text, protocolId); 115 | ggwave.encode(); 116 | 117 | const auto & protocol = GGWave::Protocols::tx()[protocolId]; 118 | const auto tones = ggwave.txTones(); 119 | const auto duration_ms = protocol.txDuration_ms(ggwave.samplesPerFrame(), ggwave.sampleRateOut()); 120 | for (auto & curTone : tones) { 121 | const auto freq_hz = (protocol.freqStart + curTone)*ggwave.hzPerSample(); 122 | tone(pin, freq_hz); 123 | delay(duration_ms); 124 | } 125 | 126 | noTone(pin); 127 | digitalWrite(pin, LOW); 128 | } 129 | 130 | void setup() { 131 | Serial.begin(57600); 132 | while (!Serial); 133 | 134 | pinMode(kPinLED0, OUTPUT); 135 | pinMode(kPinSpeaker, OUTPUT); 136 | pinMode(kPinButton0, INPUT_PULLUP); 137 | 138 | digitalWrite(kPinLED0, LOW); 139 | 140 | #ifdef DISPLAY_OUTPUT 141 | { 142 | Serial.println(F("Initializing display...")); 143 | 144 | // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally 145 | if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { 146 | Serial.println(F("SSD1306 allocation failed")); 147 | for(;;); // Don't proceed, loop forever 148 | } 149 | 150 | // Show initial display buffer contents on the screen -- 151 | // the library initializes this with an Adafruit splash screen. 152 | //display.display(); 153 | //delay(2000); // Pause for 2 seconds 154 | 155 | // Clear the buffer 156 | display.clearDisplay(); 157 | 158 | display.setTextSize(2); 159 | display.setTextColor(SSD1306_WHITE); // Draw white text 160 | display.setCursor(0, 0); // Start at top-left corner 161 | display.println(F("GGWave!")); 162 | display.setTextSize(1); 163 | display.println(F("")); 164 | display.println(F("Listening...")); 165 | 166 | display.display(); 167 | } 168 | #endif 169 | 170 | // Initialize "ggwave" 171 | { 172 | Serial.println(F("Trying to initialize the ggwave instance")); 173 | 174 | ggwave.setLogFile(nullptr); 175 | 176 | auto p = GGWave::getDefaultParameters(); 177 | 178 | // Adjust the "ggwave" parameters to your needs. 179 | // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. 180 | #ifdef LONG_RANGE 181 | // The "FAST" protocols require 2x more memory, so we reduce the payload length to compensate: 182 | p.payloadLength = 8; 183 | #else 184 | p.payloadLength = 16; 185 | #endif 186 | Serial.print(F("Using payload length: ")); 187 | Serial.println(p.payloadLength); 188 | 189 | p.sampleRateInp = sampleRate; 190 | p.sampleRateOut = sampleRate; 191 | p.sampleRate = sampleRate; 192 | p.samplesPerFrame = samplesPerFrame; 193 | p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; 194 | p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; 195 | p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; 196 | 197 | // Protocols to use for TX 198 | // Remove the ones that you don't need to reduce memory usage 199 | GGWave::Protocols::tx().disableAll(); 200 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); 201 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); 202 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); 203 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); 204 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); 205 | GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); 206 | //GGWave::Protocols::tx()[GGWAVE_PROTOCOL_MT_FASTEST].freqStart += 48; 207 | 208 | // Protocols to use for RX 209 | // Remove the ones that you don't need to reduce memory usage 210 | GGWave::Protocols::rx().disableAll(); 211 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); 212 | #ifdef LONG_RANGE 213 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); 214 | #endif 215 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); 216 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); 217 | #ifdef LONG_RANGE 218 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); 219 | #endif 220 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); 221 | 222 | // Print the memory required for the "ggwave" instance: 223 | ggwave.prepare(p, false); 224 | 225 | Serial.print(F("Required memory by the ggwave instance: ")); 226 | Serial.print(ggwave.heapSize()); 227 | Serial.println(F(" bytes")); 228 | 229 | // Initialize the "ggwave" instance: 230 | ggwave.prepare(p, true); 231 | Serial.print(F("Instance initialized successfully! Memory used: ")); 232 | } 233 | 234 | // Start capturing audio 235 | { 236 | // Configure the data receive callback 237 | PDM.onReceive(onPDMdata); 238 | 239 | // Optionally set the gain 240 | // Defaults to 20 on the BLE Sense and -10 on the Portenta Vision Shields 241 | //PDM.setGain(30); 242 | 243 | // Initialize PDM: 244 | if (!PDM.begin(channels, sampleRate)) { 245 | Serial.println(F("Failed to start PDM!")); 246 | while (1); 247 | } 248 | } 249 | } 250 | 251 | void loop() { 252 | int nr = 0; 253 | int niter = 0; 254 | int but0Prev = HIGH; 255 | 256 | GGWave::TxRxData result; 257 | 258 | char resultLast[17]; 259 | int tLastReceive = -10000; 260 | 261 | // Main loop .. 262 | while (true) { 263 | while (qsize >= samplesPerFrame) { 264 | // Use this with the serial plotter to observe real-time audio signal 265 | //for (int i = 0; i < samplesPerFrame; i++) { 266 | // Serial.println(sampleBuffer[qhead + i]); 267 | //} 268 | 269 | // We have enough captured samples - try to decode any "ggwave" data: 270 | auto tStart = millis(); 271 | 272 | ggwave.decode(sampleBuffer + qhead, samplesPerFrame*kSampleSize_bytes); 273 | qsize -= samplesPerFrame; 274 | qhead += samplesPerFrame; 275 | if (qhead >= qmax) { 276 | qhead = 0; 277 | } 278 | 279 | auto tEnd = millis(); 280 | if (++niter % 10 == 0) { 281 | // Print the time it took the last decode() call to complete. 282 | // The time should be smaller than samplesPerFrame/sampleRate seconds 283 | // For example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms 284 | Serial.println(tEnd - tStart); 285 | if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { 286 | Serial.println(F("Warning: decode() took too long to execute!")); 287 | } 288 | } 289 | 290 | // Check if we have successfully decoded any data: 291 | nr = ggwave.rxTakeData(result); 292 | if (nr > 0) { 293 | Serial.println(tEnd - tStart); 294 | Serial.print(F("Received data with length ")); 295 | Serial.print(nr); // should be equal to p.payloadLength 296 | Serial.println(F(" bytes:")); 297 | 298 | Serial.println((char *) result.data()); 299 | 300 | strcpy(resultLast, (char *) result.data()); 301 | tLastReceive = tEnd; 302 | } 303 | 304 | #ifdef DISPLAY_OUTPUT 305 | const auto t = millis(); 306 | 307 | static GGWave::Spectrum rxSpectrum; 308 | if (ggwave.rxTakeSpectrum(rxSpectrum) && t > 2000) { 309 | const bool isNew = t - tLastReceive < 2000; 310 | 311 | if (isNew) { 312 | digitalWrite(kPinLED0, HIGH); 313 | } else { 314 | digitalWrite(kPinLED0, LOW); 315 | } 316 | 317 | display.clearDisplay(); 318 | 319 | display.setTextSize(isNew ? 2 : 1); 320 | display.setTextColor(SSD1306_WHITE); 321 | display.setCursor(0, 0); 322 | display.println(resultLast); 323 | 324 | const int nBin0 = 16; 325 | const int nBins = 64; 326 | const int dX = SCREEN_WIDTH/nBins; 327 | 328 | float smax = 0.0f; 329 | for (int x = 0; x < nBins; x++) { 330 | smax = std::max(smax, rxSpectrum[nBin0 + x]); 331 | } 332 | smax = smax == 0.0f ? 1.0f : 1.0f/smax; 333 | 334 | const float h = isNew ? 0.25f: 0.75f; 335 | for (int x = 0; x < nBins; x++) { 336 | const int x0 = x*dX; 337 | const int x1 = x0 + dX; 338 | const int y = (int) (h*SCREEN_HEIGHT*(rxSpectrum[nBin0 + x]*smax)); 339 | display.fillRect(x0, SCREEN_HEIGHT - y, dX, y, SSD1306_WHITE); 340 | } 341 | 342 | display.display(); 343 | } 344 | #endif 345 | } 346 | 347 | // This should never happen. 348 | // If it does - there is something wrong with the audio capturing callback. 349 | // For example, the microcontroller is not able to process the captured data in real-time. 350 | if (err > 0) { 351 | Serial.println(F("ERRROR")); 352 | Serial.println(err); 353 | err = 0; 354 | } 355 | 356 | // If the button has been presse - transmit the last received data: 357 | int but0 = digitalRead(kPinButton0); 358 | if (but0 == LOW && but0Prev == HIGH) { 359 | Serial.println(F("Button 0 pressed - transmitting ..")); 360 | 361 | { 362 | // pause microphone capture while transmitting 363 | PDM.end(); 364 | delay(500); 365 | 366 | send_text(ggwave, kPinSpeaker, resultLast, GGWAVE_PROTOCOL_MT_FASTEST); 367 | 368 | // resume microphone capture 369 | if (!PDM.begin(channels, sampleRate)) { 370 | Serial.println(F("Failed to start PDM!")); 371 | while (1); 372 | } 373 | } 374 | 375 | Serial.println(F("Done")); 376 | 377 | but0Prev = LOW; 378 | } else if (but0 == HIGH && but0Prev == LOW) { 379 | but0Prev = HIGH; 380 | } 381 | } 382 | } 383 | 384 | /** 385 | Callback function to process the data from the PDM microphone. 386 | NOTE: This callback is executed as part of an ISR. 387 | Therefore using `Serial` to print messages inside this function isn't supported. 388 | * */ 389 | void onPDMdata() { 390 | const int bytesAvailable = PDM.available(); 391 | const int nSamples = bytesAvailable/kSampleSize_bytes; 392 | 393 | if (qsize + nSamples > qmax) { 394 | // If you hit this error, try to increase qmax 395 | err += 10; 396 | 397 | qhead = 0; 398 | qtail = 0; 399 | qsize = 0; 400 | } 401 | 402 | PDM.read(sampleBuffer + qtail, bytesAvailable); 403 | 404 | qtail += nSamples; 405 | qsize += nSamples; 406 | 407 | if (qtail > qmax) { 408 | // If you hit this error, qmax is probably not a multiple of the recorded samples 409 | err += 1; 410 | } 411 | 412 | if (qtail >= qmax) { 413 | qtail -= qmax; 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /examples/esp32-rx/esp32-rx.ino: -------------------------------------------------------------------------------- 1 | // esp32-rx 2 | // 3 | // Sample sketch for receiving sound data using "ggwave" 4 | // 5 | // Tested MCU boards: 6 | // - NodeMCU-ESP32-S 7 | // 8 | // Tested analog microphones: 9 | // - MAX9814 10 | // - KY-037 11 | // - KY-038 12 | // - WS Sound sensor 13 | // 14 | // Tested I2S microphones: 15 | // - Adafruit I2S SPH0645 16 | // 17 | // The ESP32 microcontroller has a built-in 12-bit ADC which is used to digitalize the analog signal 18 | // from the external analog microphone. When I2S microphone is used, the ADC is not used. 19 | // 20 | // The sketch optionally supports displaying the received "ggwave" data on an OLED display. 21 | // Use the DISPLAY_OUTPUT macro to enable or disable this functionality. 22 | // 23 | // If you don't have a display, you can simply observe the decoded data in the serial monitor. 24 | // 25 | // If you want to perform a quick test, you can use the free "Waver" application: 26 | // - Web: https://waver.ggerganov.com 27 | // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver 28 | // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 29 | // 30 | // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of 31 | // bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is 32 | // listed as Rx in the current sketch. 33 | // 34 | // Demo: https://youtu.be/38JoMwdpH6I 35 | // 36 | // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx 37 | // 38 | // ## Pinout 39 | // 40 | // ### Analog Microphone 41 | // 42 | // | MCU | Mic | 43 | // | ------- | --------- | 44 | // | GND | GND | 45 | // | 3.3V | VCC / VDD | 46 | // | GPIO 35 | Out | 47 | // 48 | // ### Digital (I2S) Microphone 49 | // 50 | // | MCU | Mic | 51 | // | ------- | ----------- | 52 | // | GND | GND | 53 | // | 3.3V | VCC / VDD | 54 | // | GPIO 26 | BCLK | 55 | // | GPIO 33 | Data / DOUT | 56 | // | GPIO 25 | LRCL | 57 | // 58 | // ### I2C Display (optional) 59 | // 60 | // | MCU | Display | 61 | // | ------- | --------- | 62 | // | GND | GND | 63 | // | 3.3V | VCC / VDD | 64 | // | GPIO 21 | SDA | 65 | // | GPIO 22 | SCL | 66 | // 67 | 68 | // Uncomment the line coresponding to your microhpone 69 | #define MIC_ANALOG 70 | //#define MIC_I2S 71 | //#define MIC_I2S_SPH0645 72 | 73 | // Uncoment this line to enable SSD1306 display output 74 | //#define DISPLAY_OUTPUT 1 75 | 76 | // Uncoment this line to enable long-range transmission 77 | // These protocols are slower and use more memory to decode, but are much more robust 78 | //#define LONG_RANGE 1 79 | 80 | #include 81 | 82 | #include 83 | #include 84 | 85 | // Pin configuration 86 | const int kPinLED0 = 2; 87 | 88 | // Global GGwave instance 89 | GGWave ggwave; 90 | 91 | // Audio capture configuration 92 | using TSample = int16_t; 93 | #if defined(MIC_ANALOG) 94 | using TSampleInput = int16_t; 95 | #elif defined(MIC_I2S) || defined(MIC_I2S_SPH0645) 96 | using TSampleInput = int32_t; 97 | #endif 98 | 99 | const size_t kSampleSize_bytes = sizeof(TSample); 100 | 101 | // High sample rate - better quality, but more CPU/Memory usage 102 | const int sampleRate = 24000; 103 | const int samplesPerFrame = 512; 104 | 105 | // Low sample rate 106 | // Only MT protocols will work in this mode 107 | //const int sampleRate = 12000; 108 | //const int samplesPerFrame = 256; 109 | 110 | TSample sampleBuffer[samplesPerFrame]; 111 | 112 | // helper buffer for data input in different formats: 113 | #if defined(MIC_ANALOG) 114 | TSampleInput * sampleBufferRaw = sampleBuffer; 115 | #elif defined(MIC_I2S) || defined(MIC_I2S_SPH0645) 116 | TSampleInput sampleBufferRaw[samplesPerFrame]; 117 | #endif 118 | 119 | const i2s_port_t i2s_port = I2S_NUM_0; 120 | 121 | #if defined(MIC_ANALOG) 122 | // ADC configuration 123 | const adc_unit_t adc_unit = ADC_UNIT_1; 124 | const adc1_channel_t adc_channel = ADC1_GPIO35_CHANNEL; 125 | 126 | // i2s config for using the internal ADC 127 | const i2s_config_t i2s_config = { 128 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), 129 | .sample_rate = sampleRate, 130 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 131 | .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, 132 | .communication_format = I2S_COMM_FORMAT_I2S_LSB, 133 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 134 | .dma_buf_count = 4, 135 | .dma_buf_len = samplesPerFrame, 136 | .use_apll = false, 137 | .tx_desc_auto_clear = false, 138 | .fixed_mclk = 0 139 | }; 140 | #endif 141 | 142 | #if defined(MIC_I2S) || defined(MIC_I2S_SPH0645) 143 | // i2s config for using I2S mic input from RIGHT channel 144 | const i2s_config_t i2s_config = { 145 | .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), 146 | .sample_rate = sampleRate, 147 | .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, 148 | .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, 149 | .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), 150 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 151 | .dma_buf_count = 4, 152 | .dma_buf_len = samplesPerFrame, 153 | .use_apll = false, 154 | .tx_desc_auto_clear = false, 155 | .fixed_mclk = 0 156 | }; 157 | 158 | // The pin config as per the setup 159 | const i2s_pin_config_t pin_config = { 160 | .bck_io_num = 26, // Serial Clock (SCK) 161 | .ws_io_num = 25, // Word Select (WS) 162 | .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers) 163 | .data_in_num = 33 // Serial Data (SD) 164 | }; 165 | #endif 166 | 167 | #ifdef DISPLAY_OUTPUT 168 | 169 | #include 170 | #include 171 | #include 172 | #include 173 | 174 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 175 | #define SCREEN_HEIGHT 32 // OLED display height, in pixels 176 | 177 | // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) 178 | // The pins for I2C are defined by the Wire-library. 179 | // On an arduino UNO: A4(SDA), A5(SCL) 180 | // On an arduino MEGA 2560: 20(SDA), 21(SCL) 181 | // On an arduino LEONARDO: 2(SDA), 3(SCL), ... 182 | #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) 183 | #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 184 | Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); 185 | 186 | #endif 187 | 188 | void setup() { 189 | Serial.begin(115200); 190 | while (!Serial); 191 | 192 | pinMode(kPinLED0, OUTPUT); 193 | digitalWrite(kPinLED0, LOW); 194 | 195 | #ifdef DISPLAY_OUTPUT 196 | { 197 | Serial.println(F("Initializing display...")); 198 | 199 | // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally 200 | if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { 201 | Serial.println(F("SSD1306 allocation failed")); 202 | for(;;); // Don't proceed, loop forever 203 | } 204 | 205 | // Show initial display buffer contents on the screen -- 206 | // the library initializes this with an Adafruit splash screen. 207 | //display.display(); 208 | //delay(2000); // Pause for 2 seconds 209 | 210 | // Clear the buffer 211 | display.clearDisplay(); 212 | 213 | display.setTextSize(2); 214 | display.setTextColor(SSD1306_WHITE); // Draw white text 215 | display.setCursor(0, 0); // Start at top-left corner 216 | display.println(F("GGWave!")); 217 | display.setTextSize(1); 218 | display.println(F("")); 219 | display.println(F("Listening...")); 220 | 221 | display.display(); 222 | } 223 | #endif 224 | 225 | // Initialize "ggwave" 226 | { 227 | Serial.println(F("Trying to initialize the ggwave instance")); 228 | 229 | ggwave.setLogFile(nullptr); 230 | 231 | auto p = GGWave::getDefaultParameters(); 232 | 233 | // Adjust the "ggwave" parameters to your needs. 234 | // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. 235 | #ifdef LONG_RANGE 236 | // The "FAST" protocols require 2x more memory, so we reduce the payload length to compensate: 237 | p.payloadLength = 8; 238 | #else 239 | p.payloadLength = 16; 240 | #endif 241 | Serial.print(F("Using payload length: ")); 242 | Serial.println(p.payloadLength); 243 | 244 | p.sampleRateInp = sampleRate; 245 | p.sampleRateOut = sampleRate; 246 | p.sampleRate = sampleRate; 247 | p.samplesPerFrame = samplesPerFrame; 248 | p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; 249 | p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; 250 | p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; 251 | 252 | // Protocols to use for TX 253 | // Remove the ones that you don't need to reduce memory usage 254 | GGWave::Protocols::tx().disableAll(); 255 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); 256 | //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); 257 | GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); 258 | 259 | // Protocols to use for RX 260 | // Remove the ones that you don't need to reduce memory usage 261 | GGWave::Protocols::rx().disableAll(); 262 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); 263 | #ifdef LONG_RANGE 264 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); 265 | #endif 266 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); 267 | //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); 268 | #ifdef LONG_RANGE 269 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); 270 | #endif 271 | GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); 272 | 273 | // Print the memory required for the "ggwave" instance: 274 | ggwave.prepare(p, false); 275 | 276 | Serial.print(F("Required memory by the ggwave instance: ")); 277 | Serial.print(ggwave.heapSize()); 278 | Serial.println(F(" bytes")); 279 | 280 | // Initialize the "ggwave" instance: 281 | ggwave.prepare(p, true); 282 | Serial.print(F("Instance initialized successfully! Memory used: ")); 283 | } 284 | 285 | // Start capturing audio 286 | { 287 | Serial.println(F("Initializing I2S interface")); 288 | 289 | // Install and start i2s driver 290 | i2s_driver_install(i2s_port, &i2s_config, 0, NULL); 291 | 292 | #if defined(MIC_ANALOG) 293 | Serial.println(F("Using analog input - initializing ADC")); 294 | 295 | // Init ADC pad 296 | i2s_set_adc_mode(adc_unit, adc_channel); 297 | 298 | // Enable the adc 299 | i2s_adc_enable(i2s_port); 300 | 301 | Serial.println(F("I2S ADC started")); 302 | #endif 303 | 304 | #if defined(MIC_I2S) || defined(MIC_I2S_SPH0645) 305 | Serial.println(F("Using I2S input")); 306 | 307 | #if defined(MIC_I2S_SPH0645) 308 | Serial.println(F("Applying fix for SPH0645")); 309 | 310 | // https://github.com/atomic14/esp32_audio/blob/d2ac3490c0836cb46a69c83b0570873de18f695e/i2s_sampling/src/I2SMEMSSampler.cpp#L17-L22 311 | REG_SET_BIT(I2S_TIMING_REG(i2s_port), BIT(9)); 312 | REG_SET_BIT(I2S_CONF_REG(i2s_port), I2S_RX_MSB_SHIFT); 313 | #endif 314 | 315 | i2s_set_pin(i2s_port, &pin_config); 316 | #endif 317 | } 318 | } 319 | 320 | int niter = 0; 321 | int tLastReceive = -10000; 322 | 323 | GGWave::TxRxData result; 324 | 325 | void loop() { 326 | // Read from i2s 327 | { 328 | size_t bytes_read = 0; 329 | i2s_read(i2s_port, sampleBufferRaw, sizeof(TSampleInput)*samplesPerFrame, &bytes_read, portMAX_DELAY); 330 | 331 | int nSamples = bytes_read/sizeof(TSampleInput); 332 | if (nSamples != samplesPerFrame) { 333 | Serial.println("Failed to read samples"); 334 | return; 335 | } 336 | 337 | #if defined(MIC_ANALOG) 338 | // the ADC samples are 12-bit so we need to do some massaging to make them 16-bit 339 | for (int i = 0; i < nSamples; i += 2) { 340 | auto & s0 = sampleBuffer[i]; 341 | auto & s1 = sampleBuffer[i + 1]; 342 | 343 | s0 = s0 & 0x0fff; 344 | s1 = s1 & 0x0fff; 345 | 346 | s0 = s0 ^ s1; 347 | s1 = s0 ^ s1; 348 | s0 = s0 ^ s1; 349 | } 350 | #endif 351 | 352 | #if defined(MIC_I2S) || defined(MIC_I2S_SPH0645) 353 | for (int i = 0; i < nSamples; ++i) { 354 | sampleBuffer[i] = (sampleBufferRaw[i] & 0xFFFFFFF0) >> 11; 355 | } 356 | #endif 357 | } 358 | 359 | // Use this with the serial plotter to observe real-time audio signal 360 | //for (int i = 0; i < nSamples; i++) { 361 | // Serial.println(sampleBuffer[i]); 362 | //} 363 | 364 | // Try to decode any "ggwave" data: 365 | auto tStart = millis(); 366 | 367 | if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) { 368 | Serial.println("Failed to decode"); 369 | } 370 | 371 | auto tEnd = millis(); 372 | 373 | if (++niter % 10 == 0) { 374 | // print the time it took the last decode() call to complete 375 | // should be smaller than samplesPerFrame/sampleRate seconds 376 | // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms 377 | Serial.println(tEnd - tStart); 378 | if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { 379 | Serial.println(F("Warning: decode() took too long to execute!")); 380 | } 381 | } 382 | 383 | // Check if we have successfully decoded any data: 384 | int nr = ggwave.rxTakeData(result); 385 | if (nr > 0) { 386 | Serial.println(tEnd - tStart); 387 | Serial.print(F("Received data with length ")); 388 | Serial.print(nr); // should be equal to p.payloadLength 389 | Serial.println(F(" bytes:")); 390 | 391 | Serial.println((char *) result.data()); 392 | 393 | tLastReceive = tEnd; 394 | } 395 | 396 | #ifdef DISPLAY_OUTPUT 397 | const auto t = millis(); 398 | 399 | static GGWave::Spectrum rxSpectrum; 400 | if (ggwave.rxTakeSpectrum(rxSpectrum) && t > 2000) { 401 | const bool isNew = t - tLastReceive < 2000; 402 | 403 | if (isNew) { 404 | digitalWrite(kPinLED0, HIGH); 405 | } else { 406 | digitalWrite(kPinLED0, LOW); 407 | } 408 | 409 | display.clearDisplay(); 410 | 411 | display.setTextSize(isNew ? 2 : 1); 412 | display.setTextColor(SSD1306_WHITE); 413 | display.setCursor(0, 0); 414 | display.println((char *) result.data()); 415 | 416 | const int nBin0 = 16; 417 | const int nBins = 64; 418 | const int dX = SCREEN_WIDTH/nBins; 419 | 420 | float smax = 0.0f; 421 | for (int x = 0; x < nBins; x++) { 422 | smax = std::max(smax, rxSpectrum[nBin0 + x]); 423 | } 424 | smax = smax == 0.0f ? 1.0f : 1.0f/smax; 425 | 426 | const float h = isNew ? 0.25f: 0.75f; 427 | for (int x = 0; x < nBins; x++) { 428 | const int x0 = x*dX; 429 | const int x1 = x0 + dX; 430 | const int y = (int) (h*SCREEN_HEIGHT*(rxSpectrum[nBin0 + x]*smax)); 431 | display.fillRect(x0, SCREEN_HEIGHT - y, dX, y, SSD1306_WHITE); 432 | } 433 | 434 | display.display(); 435 | } 436 | #endif 437 | } 438 | -------------------------------------------------------------------------------- /src/ggwave/reed-solomon/rs.hpp: -------------------------------------------------------------------------------- 1 | /* Author: Mike Lubinets (aka mersinvald) 2 | * Date: 29.12.15 3 | * 4 | * See LICENSE */ 5 | 6 | #ifndef RS_HPP 7 | #define RS_HPP 8 | 9 | #include "poly.hpp" 10 | #include "gf.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace RS { 18 | 19 | #define MSG_CNT 3 // message-length polynomials count 20 | #define POLY_CNT 14 // (ecc_length*2)-length polynomialc count 21 | 22 | class ReedSolomon { 23 | public: 24 | const uint8_t msg_length; 25 | const uint8_t ecc_length; 26 | 27 | uint8_t * heap_memory = nullptr; 28 | uint8_t * generator_cache = nullptr; 29 | bool owns_heap_memory = false; 30 | bool generator_cached = false; 31 | 32 | // used to pre-allocate a memory buffer for the Reed-Solomon class in order to avoid memory allocations 33 | static size_t getWorkSize_bytes(uint8_t msg_length, uint8_t ecc_length) { 34 | return ecc_length + 1 + MSG_CNT * msg_length + POLY_CNT * ecc_length * 2; 35 | } 36 | 37 | ReedSolomon(uint8_t msg_length_p, uint8_t ecc_length_p, uint8_t * heap_memory_p = nullptr) : 38 | msg_length(msg_length_p), ecc_length(ecc_length_p) { 39 | if (heap_memory_p) { 40 | heap_memory = heap_memory_p; 41 | owns_heap_memory = false; 42 | } else { 43 | heap_memory = (uint8_t *) malloc(getWorkSize_bytes(msg_length, ecc_length)); 44 | owns_heap_memory = true; 45 | } 46 | generator_cache = heap_memory; 47 | 48 | const uint8_t enc_len = msg_length + ecc_length; 49 | const uint8_t poly_len = ecc_length * 2; 50 | uint8_t** memptr = &memory; 51 | uint16_t offset = 0; 52 | 53 | /* Initialize first six polys manually cause their amount depends on template parameters */ 54 | 55 | polynoms[0].Init(ID_MSG_IN, offset, enc_len, memptr); 56 | offset += enc_len; 57 | 58 | polynoms[1].Init(ID_MSG_OUT, offset, enc_len, memptr); 59 | offset += enc_len; 60 | 61 | for(uint8_t i = ID_GENERATOR; i < ID_MSG_E; i++) { 62 | polynoms[i].Init(i, offset, poly_len, memptr); 63 | offset += poly_len; 64 | } 65 | 66 | polynoms[5].Init(ID_MSG_E, offset, enc_len, memptr); 67 | offset += enc_len; 68 | 69 | for(uint8_t i = ID_TPOLY3; i < ID_ERR_EVAL+2; i++) { 70 | polynoms[i].Init(i, offset, poly_len, memptr); 71 | offset += poly_len; 72 | } 73 | } 74 | 75 | ~ReedSolomon() { 76 | if (owns_heap_memory) { 77 | delete[] heap_memory; 78 | } 79 | // Dummy destructor, gcc-generated one crashes programm 80 | memory = NULL; 81 | } 82 | 83 | /* @brief Message block encoding 84 | * @param *src - input message buffer (msg_lenth size) 85 | * @param *dst - output buffer for ecc (ecc_length size at least) */ 86 | void EncodeBlock(const void* src, void* dst) { 87 | assert(msg_length + ecc_length < 256); 88 | 89 | ///* Allocating memory on stack for polynomials storage */ 90 | //uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2]; 91 | //this->memory = stack_memory; 92 | 93 | // gg : allocation is now on the heap 94 | this->memory = heap_memory + ecc_length + 1; 95 | 96 | const uint8_t* src_ptr = (const uint8_t*) src; 97 | uint8_t* dst_ptr = (uint8_t*) dst; 98 | 99 | Poly *msg_in = &polynoms[ID_MSG_IN]; 100 | Poly *msg_out = &polynoms[ID_MSG_OUT]; 101 | Poly *gen = &polynoms[ID_GENERATOR]; 102 | 103 | // Weird shit, but without reseting msg_in it simply doesn't work 104 | msg_in->Reset(); 105 | msg_out->Reset(); 106 | 107 | // Using cached generator or generating new one 108 | if(generator_cached) { 109 | gen->Set(generator_cache, ecc_length + 1); 110 | } else { 111 | GeneratorPoly(); 112 | memcpy(generator_cache, gen->ptr(), gen->length); 113 | generator_cached = true; 114 | } 115 | 116 | // Copying input message to internal polynomial 117 | msg_in->Set(src_ptr, msg_length); 118 | msg_out->Set(src_ptr, msg_length); 119 | msg_out->length = msg_in->length + ecc_length; 120 | 121 | // Here all the magic happens 122 | uint8_t coef = 0; // cache 123 | for(uint8_t i = 0; i < msg_length; i++){ 124 | coef = msg_out->at(i); 125 | if(coef != 0){ 126 | for(uint32_t j = 1; j < gen->length; j++){ 127 | msg_out->at(i+j) ^= gf::mul(gen->at(j), coef); 128 | } 129 | } 130 | } 131 | 132 | // Copying ECC to the output buffer 133 | memcpy(dst_ptr, msg_out->ptr()+msg_length, ecc_length * sizeof(uint8_t)); 134 | } 135 | 136 | /* @brief Message encoding 137 | * @param *src - input message buffer (msg_lenth size) 138 | * @param *dst - output buffer (msg_length + ecc_length size at least) */ 139 | void Encode(const void* src, void* dst) { 140 | uint8_t* dst_ptr = (uint8_t*) dst; 141 | 142 | // Copying message to the output buffer 143 | memcpy(dst_ptr, src, msg_length * sizeof(uint8_t)); 144 | 145 | // Calling EncodeBlock to write ecc to out[ut buffer 146 | EncodeBlock(src, dst_ptr+msg_length); 147 | } 148 | 149 | /* @brief Message block decoding 150 | * @param *src - encoded message buffer (msg_length size) 151 | * @param *ecc - ecc buffer (ecc_length size) 152 | * @param *msg_out - output buffer (msg_length size at least) 153 | * @param *erase_pos - known errors positions 154 | * @param erase_count - count of known errors 155 | * @return RESULT_SUCCESS if successfull, error code otherwise */ 156 | int DecodeBlock(const void* src, const void* ecc, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) { 157 | assert(msg_length + ecc_length < 256); 158 | 159 | const uint8_t *src_ptr = (const uint8_t*) src; 160 | const uint8_t *ecc_ptr = (const uint8_t*) ecc; 161 | uint8_t *dst_ptr = (uint8_t*) dst; 162 | 163 | const uint8_t src_len = msg_length + ecc_length; 164 | const uint8_t dst_len = msg_length; 165 | 166 | bool ok; 167 | 168 | ///* Allocation memory on stack */ 169 | //uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2]; 170 | //this->memory = stack_memory; 171 | 172 | // gg : allocation is now on the heap 173 | this->memory = heap_memory + ecc_length + 1; 174 | 175 | Poly *msg_in = &polynoms[ID_MSG_IN]; 176 | Poly *msg_out = &polynoms[ID_MSG_OUT]; 177 | Poly *epos = &polynoms[ID_ERASURES]; 178 | 179 | // Copying message to polynomials memory 180 | msg_in->Set(src_ptr, msg_length); 181 | msg_in->Set(ecc_ptr, ecc_length, msg_length); 182 | msg_out->Copy(msg_in); 183 | 184 | // Copying known errors to polynomial 185 | if(erase_pos == NULL) { 186 | epos->length = 0; 187 | } else { 188 | epos->Set(erase_pos, erase_count); 189 | for(uint8_t i = 0; i < epos->length; i++){ 190 | msg_in->at(epos->at(i)) = 0; 191 | } 192 | } 193 | 194 | // Too many errors 195 | if(epos->length > ecc_length) return 1; 196 | 197 | Poly *synd = &polynoms[ID_SYNDROMES]; 198 | Poly *eloc = &polynoms[ID_ERRORS_LOC]; 199 | Poly *reloc = &polynoms[ID_TPOLY1]; 200 | Poly *err = &polynoms[ID_ERRORS]; 201 | Poly *forney = &polynoms[ID_FORNEY]; 202 | 203 | // Calculating syndrome 204 | CalcSyndromes(msg_in); 205 | 206 | // Checking for errors 207 | bool has_errors = false; 208 | for(uint8_t i = 0; i < synd->length; i++) { 209 | if(synd->at(i) != 0) { 210 | has_errors = true; 211 | break; 212 | } 213 | } 214 | 215 | // Going to exit if no errors 216 | if(!has_errors) goto return_corrected_msg; 217 | 218 | CalcForneySyndromes(synd, epos, src_len); 219 | FindErrorLocator(forney, NULL, epos->length); 220 | 221 | // Reversing syndrome 222 | // TODO optimize through special Poly flag 223 | reloc->length = eloc->length; 224 | for(int8_t i = eloc->length-1, j = 0; i >= 0; i--, j++){ 225 | reloc->at(j) = eloc->at(i); 226 | } 227 | 228 | // Fing errors 229 | ok = FindErrors(reloc, src_len); 230 | if(!ok) return 1; 231 | 232 | // Error happened while finding errors (so helpfull :D) 233 | if(err->length == 0) return 1; 234 | 235 | /* Adding found errors with known */ 236 | for(uint8_t i = 0; i < err->length; i++) { 237 | epos->Append(err->at(i)); 238 | } 239 | 240 | // Correcting errors 241 | CorrectErrata(synd, epos, msg_in); 242 | 243 | return_corrected_msg: 244 | // Wrighting corrected message to output buffer 245 | msg_out->length = dst_len; 246 | memcpy(dst_ptr, msg_out->ptr(), msg_out->length * sizeof(uint8_t)); 247 | return 0; 248 | } 249 | 250 | /* @brief Message block decoding 251 | * @param *src - encoded message buffer (msg_length + ecc_length size) 252 | * @param *msg_out - output buffer (msg_length size at least) 253 | * @param *erase_pos - known errors positions 254 | * @param erase_count - count of known errors 255 | * @return RESULT_SUCCESS if successfull, error code otherwise */ 256 | int Decode(const void* src, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) { 257 | const uint8_t *src_ptr = (const uint8_t*) src; 258 | const uint8_t *ecc_ptr = src_ptr + msg_length; 259 | 260 | return DecodeBlock(src, ecc_ptr, dst, erase_pos, erase_count); 261 | } 262 | 263 | #ifndef DEBUG 264 | private: 265 | #endif 266 | 267 | enum POLY_ID { 268 | ID_MSG_IN = 0, 269 | ID_MSG_OUT, 270 | ID_GENERATOR, // 3 271 | ID_TPOLY1, // T for Temporary 272 | ID_TPOLY2, 273 | 274 | ID_MSG_E, // 5 275 | 276 | ID_TPOLY3, // 6 277 | ID_TPOLY4, 278 | 279 | ID_SYNDROMES, 280 | ID_FORNEY, 281 | 282 | ID_ERASURES_LOC, 283 | ID_ERRORS_LOC, 284 | 285 | ID_ERASURES, 286 | ID_ERRORS, 287 | 288 | ID_COEF_POS, 289 | ID_ERR_EVAL 290 | }; 291 | 292 | // Pointer for polynomials memory on stack 293 | uint8_t* memory; 294 | Poly polynoms[MSG_CNT + POLY_CNT]; 295 | 296 | void GeneratorPoly() { 297 | Poly *gen = polynoms + ID_GENERATOR; 298 | gen->at(0) = 1; 299 | gen->length = 1; 300 | 301 | Poly *mulp = polynoms + ID_TPOLY1; 302 | Poly *temp = polynoms + ID_TPOLY2; 303 | mulp->length = 2; 304 | 305 | for(int8_t i = 0; i < ecc_length; i++){ 306 | mulp->at(0) = 1; 307 | mulp->at(1) = gf::pow(2, i); 308 | 309 | gf::poly_mul(gen, mulp, temp); 310 | 311 | gen->Copy(temp); 312 | } 313 | } 314 | 315 | void CalcSyndromes(const Poly *msg) { 316 | Poly *synd = &polynoms[ID_SYNDROMES]; 317 | synd->length = ecc_length+1; 318 | synd->at(0) = 0; 319 | for(uint8_t i = 1; i < ecc_length+1; i++){ 320 | synd->at(i) = gf::poly_eval(msg, gf::pow(2, i-1)); 321 | } 322 | } 323 | 324 | void FindErrataLocator(const Poly *epos) { 325 | Poly *errata_loc = &polynoms[ID_ERASURES_LOC]; 326 | Poly *mulp = &polynoms[ID_TPOLY1]; 327 | Poly *addp = &polynoms[ID_TPOLY2]; 328 | Poly *apol = &polynoms[ID_TPOLY3]; 329 | Poly *temp = &polynoms[ID_TPOLY4]; 330 | 331 | errata_loc->length = 1; 332 | errata_loc->at(0) = 1; 333 | 334 | mulp->length = 1; 335 | addp->length = 2; 336 | 337 | for(uint8_t i = 0; i < epos->length; i++){ 338 | mulp->at(0) = 1; 339 | addp->at(0) = gf::pow(2, epos->at(i)); 340 | addp->at(1) = 0; 341 | 342 | gf::poly_add(mulp, addp, apol); 343 | gf::poly_mul(errata_loc, apol, temp); 344 | 345 | errata_loc->Copy(temp); 346 | } 347 | } 348 | 349 | void FindErrorEvaluator(const Poly *synd, const Poly *errata_loc, Poly *dst, uint8_t ecclen) { 350 | Poly *mulp = &polynoms[ID_TPOLY1]; 351 | gf::poly_mul(synd, errata_loc, mulp); 352 | 353 | Poly *divisor = &polynoms[ID_TPOLY2]; 354 | divisor->length = ecclen+2; 355 | 356 | divisor->Reset(); 357 | divisor->at(0) = 1; 358 | 359 | gf::poly_div(mulp, divisor, dst); 360 | } 361 | 362 | void CorrectErrata(const Poly *synd, const Poly *err_pos, const Poly *msg_in) { 363 | Poly *c_pos = &polynoms[ID_COEF_POS]; 364 | Poly *corrected = &polynoms[ID_MSG_OUT]; 365 | c_pos->length = err_pos->length; 366 | 367 | for(uint8_t i = 0; i < err_pos->length; i++) 368 | c_pos->at(i) = msg_in->length - 1 - err_pos->at(i); 369 | 370 | /* uses t_poly 1, 2, 3, 4 */ 371 | FindErrataLocator(c_pos); 372 | Poly *errata_loc = &polynoms[ID_ERASURES_LOC]; 373 | 374 | /* reversing syndromes */ 375 | Poly *rsynd = &polynoms[ID_TPOLY3]; 376 | rsynd->length = synd->length; 377 | 378 | for(int8_t i = synd->length-1, j = 0; i >= 0; i--, j++) { 379 | rsynd->at(j) = synd->at(i); 380 | } 381 | 382 | /* getting reversed error evaluator polynomial */ 383 | Poly *re_eval = &polynoms[ID_TPOLY4]; 384 | 385 | /* uses T_POLY 1, 2 */ 386 | FindErrorEvaluator(rsynd, errata_loc, re_eval, errata_loc->length-1); 387 | 388 | /* reversing it back */ 389 | Poly *e_eval = &polynoms[ID_ERR_EVAL]; 390 | e_eval->length = re_eval->length; 391 | for(int8_t i = re_eval->length-1, j = 0; i >= 0; i--, j++) { 392 | e_eval->at(j) = re_eval->at(i); 393 | } 394 | 395 | Poly *X = &polynoms[ID_TPOLY1]; /* this will store errors positions */ 396 | X->length = 0; 397 | 398 | int16_t l; 399 | for(uint8_t i = 0; i < c_pos->length; i++){ 400 | l = 255 - c_pos->at(i); 401 | X->Append(gf::pow(2, -l)); 402 | } 403 | 404 | /* Magnitude polynomial 405 | Shit just got real */ 406 | Poly *E = &polynoms[ID_MSG_E]; 407 | E->Reset(); 408 | E->length = msg_in->length; 409 | 410 | uint8_t Xi_inv; 411 | 412 | Poly *err_loc_prime_temp = &polynoms[ID_TPOLY2]; 413 | 414 | uint8_t err_loc_prime; 415 | uint8_t y; 416 | 417 | for(uint8_t i = 0; i < X->length; i++){ 418 | Xi_inv = gf::inverse(X->at(i)); 419 | 420 | err_loc_prime_temp->length = 0; 421 | for(uint8_t j = 0; j < X->length; j++){ 422 | if(j != i){ 423 | err_loc_prime_temp->Append(gf::sub(1, gf::mul(Xi_inv, X->at(j)))); 424 | } 425 | } 426 | 427 | err_loc_prime = 1; 428 | for(uint8_t j = 0; j < err_loc_prime_temp->length; j++){ 429 | err_loc_prime = gf::mul(err_loc_prime, err_loc_prime_temp->at(j)); 430 | } 431 | 432 | y = gf::poly_eval(re_eval, Xi_inv); 433 | y = gf::mul(gf::pow(X->at(i), 1), y); 434 | 435 | E->at(err_pos->at(i)) = gf::div(y, err_loc_prime); 436 | } 437 | 438 | gf::poly_add(msg_in, E, corrected); 439 | } 440 | 441 | bool FindErrorLocator(const Poly *synd, Poly *erase_loc = NULL, size_t erase_count = 0) { 442 | Poly *error_loc = &polynoms[ID_ERRORS_LOC]; 443 | Poly *err_loc = &polynoms[ID_TPOLY1]; 444 | Poly *old_loc = &polynoms[ID_TPOLY2]; 445 | Poly *temp = &polynoms[ID_TPOLY3]; 446 | Poly *temp2 = &polynoms[ID_TPOLY4]; 447 | 448 | if(erase_loc != NULL) { 449 | err_loc->Copy(erase_loc); 450 | old_loc->Copy(erase_loc); 451 | } else { 452 | err_loc->length = 1; 453 | old_loc->length = 1; 454 | err_loc->at(0) = 1; 455 | old_loc->at(0) = 1; 456 | } 457 | 458 | uint8_t synd_shift = 0; 459 | if(synd->length > ecc_length) { 460 | synd_shift = synd->length - ecc_length; 461 | } 462 | 463 | uint8_t K = 0; 464 | uint8_t delta = 0; 465 | uint8_t index; 466 | 467 | for(uint8_t i = 0; i < ecc_length - erase_count; i++){ 468 | if(erase_loc != NULL) 469 | K = erase_count + i + synd_shift; 470 | else 471 | K = i + synd_shift; 472 | 473 | delta = synd->at(K); 474 | for(uint8_t j = 1; j < err_loc->length; j++) { 475 | index = err_loc->length - j - 1; 476 | delta ^= gf::mul(err_loc->at(index), synd->at(K-j)); 477 | } 478 | 479 | old_loc->Append(0); 480 | 481 | if(delta != 0) { 482 | if(old_loc->length > err_loc->length) { 483 | gf::poly_scale(old_loc, temp, delta); 484 | gf::poly_scale(err_loc, old_loc, gf::inverse(delta)); 485 | err_loc->Copy(temp); 486 | } 487 | gf::poly_scale(old_loc, temp, delta); 488 | gf::poly_add(err_loc, temp, temp2); 489 | err_loc->Copy(temp2); 490 | } 491 | } 492 | 493 | uint32_t shift = 0; 494 | while(err_loc->length && err_loc->at(shift) == 0) shift++; 495 | 496 | uint32_t errs = err_loc->length - shift - 1; 497 | if(((errs - erase_count) * 2 + erase_count) > ecc_length){ 498 | return false; /* Error count is greater then we can fix! */ 499 | } 500 | 501 | memcpy(error_loc->ptr(), err_loc->ptr() + shift, (err_loc->length - shift) * sizeof(uint8_t)); 502 | error_loc->length = (err_loc->length - shift); 503 | return true; 504 | } 505 | 506 | bool FindErrors(const Poly *error_loc, size_t msg_in_size) { 507 | Poly *err = &polynoms[ID_ERRORS]; 508 | 509 | uint8_t errs = error_loc->length - 1; 510 | err->length = 0; 511 | 512 | for(uint8_t i = 0; i < msg_in_size; i++) { 513 | if(gf::poly_eval(error_loc, gf::pow(2, i)) == 0) { 514 | err->Append(msg_in_size - 1 - i); 515 | } 516 | } 517 | 518 | /* Sanity check: 519 | * the number of err/errata positions found 520 | * should be exactly the same as the length of the errata locator polynomial */ 521 | if(err->length != errs) 522 | /* couldn't find error locations */ 523 | return false; 524 | return true; 525 | } 526 | 527 | void CalcForneySyndromes(const Poly *synd, const Poly *erasures_pos, size_t msg_in_size) { 528 | Poly *erase_pos_reversed = &polynoms[ID_TPOLY1]; 529 | Poly *forney_synd = &polynoms[ID_FORNEY]; 530 | erase_pos_reversed->length = 0; 531 | 532 | for(uint8_t i = 0; i < erasures_pos->length; i++){ 533 | erase_pos_reversed->Append(msg_in_size - 1 - erasures_pos->at(i)); 534 | } 535 | 536 | forney_synd->Reset(); 537 | forney_synd->Set(synd->ptr()+1, synd->length-1); 538 | 539 | uint8_t x; 540 | for(uint8_t i = 0; i < erasures_pos->length; i++) { 541 | x = gf::pow(2, erase_pos_reversed->at(i)); 542 | for(int8_t j = 0; j < forney_synd->length - 1; j++){ 543 | forney_synd->at(j) = gf::mul(forney_synd->at(j), x) ^ forney_synd->at(j+1); 544 | } 545 | } 546 | } 547 | }; 548 | 549 | } 550 | 551 | #endif // RS_HPP 552 | 553 | -------------------------------------------------------------------------------- /src/ggwave/fft.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | 5 | The FFT routines below are taken from: 6 | 7 | https://www.kurims.kyoto-u.ac.jp/~ooura/fft.html 8 | 9 | License 10 | Copyright Takuya OOURA, 1996-2001 11 | 12 | */ 13 | 14 | 15 | /* 16 | Fast Fourier/Cosine/Sine Transform 17 | dimension :one 18 | data length :power of 2 19 | decimation :frequency 20 | radix :4, 2 21 | data :inplace 22 | table :use 23 | functions 24 | rdft: Real Discrete Fourier Transform 25 | function prototypes 26 | void rdft(int, int, float *, int *, float *); 27 | 28 | 29 | -------- Real DFT / Inverse of Real DFT -------- 30 | [definition] 31 | RDFT 32 | R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2 33 | I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0 IRDFT (excluding scale) 35 | a[k] = (R[0] + R[n/2]*cos(pi*k))/2 + 36 | sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) + 37 | sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k 40 | ip[0] = 0; // first time only 41 | rdft(n, 1, a, ip, w); 42 | 43 | ip[0] = 0; // first time only 44 | rdft(n, -1, a, ip, w); 45 | [parameters] 46 | n :data length (int) 47 | n >= 2, n = power of 2 48 | a[0...n-1] :input/output data (float *) 49 | 50 | output data 51 | a[2*k] = R[k], 0<=k 55 | input data 56 | a[2*j] = R[j], 0<=j= 2+sqrt(n/2) 61 | strictly, 62 | length of ip >= 63 | 2+(1<<(int)(log(n/2+0.5)/log(2))/2). 64 | ip[0],ip[1] are pointers of the cos/sin table. 65 | w[0...n/2-1] :cos/sin table (float *) 66 | w[],ip[] are initialized if ip[0] == 0. 67 | [remark] 68 | Inverse of 69 | rdft(n, 1, a, ip, w); 70 | is 71 | rdft(n, -1, a, ip, w); 72 | for (j = 0; j <= n - 1; j++) { 73 | a[j] *= 2.0 / n; 74 | } 75 | . 76 | 77 | 78 | Appendix : 79 | The cos/sin table is recalculated when the larger table required. 80 | w[] and ip[] are compatible with all routines. 81 | */ 82 | 83 | void rdft(int n, int isgn, float *a, int *ip, float *w) 84 | { 85 | void makewt(int nw, int *ip, float *w); 86 | void makect(int nc, int *ip, float *c); 87 | void bitrv2(int n, int *ip, float *a); 88 | void cftfsub(int n, float *a, float *w); 89 | void cftbsub(int n, float *a, float *w); 90 | void rftfsub(int n, float *a, int nc, float *c); 91 | void rftbsub(int n, float *a, int nc, float *c); 92 | int nw, nc; 93 | float xi; 94 | 95 | nw = ip[0]; 96 | if (n > (nw << 2)) { 97 | nw = n >> 2; 98 | makewt(nw, ip, w); 99 | } 100 | nc = ip[1]; 101 | if (n > (nc << 2)) { 102 | nc = n >> 2; 103 | makect(nc, ip, w + nw); 104 | } 105 | if (isgn >= 0) { 106 | if (n > 4) { 107 | bitrv2(n, ip + 2, a); 108 | cftfsub(n, a, w); 109 | rftfsub(n, a, nc, w + nw); 110 | } else if (n == 4) { 111 | cftfsub(n, a, w); 112 | } 113 | xi = a[0] - a[1]; 114 | a[0] += a[1]; 115 | a[1] = xi; 116 | } else { 117 | a[1] = 0.5 * (a[0] - a[1]); 118 | a[0] -= a[1]; 119 | if (n > 4) { 120 | rftbsub(n, a, nc, w + nw); 121 | bitrv2(n, ip + 2, a); 122 | cftbsub(n, a, w); 123 | } else if (n == 4) { 124 | cftfsub(n, a, w); 125 | } 126 | } 127 | } 128 | 129 | /* -------- initializing routines -------- */ 130 | 131 | #include 132 | 133 | void makewt(int nw, int *ip, float *w) 134 | { 135 | void bitrv2(int n, int *ip, float *a); 136 | int j, nwh; 137 | float delta, x, y; 138 | 139 | ip[0] = nw; 140 | ip[1] = 1; 141 | if (nw > 2) { 142 | nwh = nw >> 1; 143 | delta = atan(1.0) / nwh; 144 | w[0] = 1; 145 | w[1] = 0; 146 | w[nwh] = cos(delta * nwh); 147 | w[nwh + 1] = w[nwh]; 148 | if (nwh > 2) { 149 | for (j = 2; j < nwh; j += 2) { 150 | x = cos(delta * j); 151 | y = sin(delta * j); 152 | w[j] = x; 153 | w[j + 1] = y; 154 | w[nw - j] = y; 155 | w[nw - j + 1] = x; 156 | } 157 | bitrv2(nw, ip + 2, w); 158 | } 159 | } 160 | } 161 | 162 | 163 | void makect(int nc, int *ip, float *c) 164 | { 165 | int j, nch; 166 | float delta; 167 | 168 | ip[1] = nc; 169 | if (nc > 1) { 170 | nch = nc >> 1; 171 | delta = atan(1.0) / nch; 172 | c[0] = cos(delta * nch); 173 | c[nch] = 0.5 * c[0]; 174 | for (j = 1; j < nch; j++) { 175 | c[j] = 0.5 * cos(delta * j); 176 | c[nc - j] = 0.5 * sin(delta * j); 177 | } 178 | } 179 | } 180 | 181 | 182 | /* -------- child routines -------- */ 183 | 184 | 185 | void bitrv2(int n, int *ip, float *a) 186 | { 187 | int j, j1, k, k1, l, m, m2; 188 | float xr, xi, yr, yi; 189 | 190 | ip[0] = 0; 191 | l = n; 192 | m = 1; 193 | while ((m << 3) < l) { 194 | l >>= 1; 195 | for (j = 0; j < m; j++) { 196 | ip[m + j] = ip[j] + l; 197 | } 198 | m <<= 1; 199 | } 200 | m2 = 2 * m; 201 | if ((m << 3) == l) { 202 | for (k = 0; k < m; k++) { 203 | for (j = 0; j < k; j++) { 204 | j1 = 2 * j + ip[k]; 205 | k1 = 2 * k + ip[j]; 206 | xr = a[j1]; 207 | xi = a[j1 + 1]; 208 | yr = a[k1]; 209 | yi = a[k1 + 1]; 210 | a[j1] = yr; 211 | a[j1 + 1] = yi; 212 | a[k1] = xr; 213 | a[k1 + 1] = xi; 214 | j1 += m2; 215 | k1 += 2 * m2; 216 | xr = a[j1]; 217 | xi = a[j1 + 1]; 218 | yr = a[k1]; 219 | yi = a[k1 + 1]; 220 | a[j1] = yr; 221 | a[j1 + 1] = yi; 222 | a[k1] = xr; 223 | a[k1 + 1] = xi; 224 | j1 += m2; 225 | k1 -= m2; 226 | xr = a[j1]; 227 | xi = a[j1 + 1]; 228 | yr = a[k1]; 229 | yi = a[k1 + 1]; 230 | a[j1] = yr; 231 | a[j1 + 1] = yi; 232 | a[k1] = xr; 233 | a[k1 + 1] = xi; 234 | j1 += m2; 235 | k1 += 2 * m2; 236 | xr = a[j1]; 237 | xi = a[j1 + 1]; 238 | yr = a[k1]; 239 | yi = a[k1 + 1]; 240 | a[j1] = yr; 241 | a[j1 + 1] = yi; 242 | a[k1] = xr; 243 | a[k1 + 1] = xi; 244 | } 245 | j1 = 2 * k + m2 + ip[k]; 246 | k1 = j1 + m2; 247 | xr = a[j1]; 248 | xi = a[j1 + 1]; 249 | yr = a[k1]; 250 | yi = a[k1 + 1]; 251 | a[j1] = yr; 252 | a[j1 + 1] = yi; 253 | a[k1] = xr; 254 | a[k1 + 1] = xi; 255 | } 256 | } else { 257 | for (k = 1; k < m; k++) { 258 | for (j = 0; j < k; j++) { 259 | j1 = 2 * j + ip[k]; 260 | k1 = 2 * k + ip[j]; 261 | xr = a[j1]; 262 | xi = a[j1 + 1]; 263 | yr = a[k1]; 264 | yi = a[k1 + 1]; 265 | a[j1] = yr; 266 | a[j1 + 1] = yi; 267 | a[k1] = xr; 268 | a[k1 + 1] = xi; 269 | j1 += m2; 270 | k1 += m2; 271 | xr = a[j1]; 272 | xi = a[j1 + 1]; 273 | yr = a[k1]; 274 | yi = a[k1 + 1]; 275 | a[j1] = yr; 276 | a[j1 + 1] = yi; 277 | a[k1] = xr; 278 | a[k1 + 1] = xi; 279 | } 280 | } 281 | } 282 | } 283 | 284 | 285 | void bitrv2conj(int n, int *ip, float *a) 286 | { 287 | int j, j1, k, k1, l, m, m2; 288 | float xr, xi, yr, yi; 289 | 290 | ip[0] = 0; 291 | l = n; 292 | m = 1; 293 | while ((m << 3) < l) { 294 | l >>= 1; 295 | for (j = 0; j < m; j++) { 296 | ip[m + j] = ip[j] + l; 297 | } 298 | m <<= 1; 299 | } 300 | m2 = 2 * m; 301 | if ((m << 3) == l) { 302 | for (k = 0; k < m; k++) { 303 | for (j = 0; j < k; j++) { 304 | j1 = 2 * j + ip[k]; 305 | k1 = 2 * k + ip[j]; 306 | xr = a[j1]; 307 | xi = -a[j1 + 1]; 308 | yr = a[k1]; 309 | yi = -a[k1 + 1]; 310 | a[j1] = yr; 311 | a[j1 + 1] = yi; 312 | a[k1] = xr; 313 | a[k1 + 1] = xi; 314 | j1 += m2; 315 | k1 += 2 * m2; 316 | xr = a[j1]; 317 | xi = -a[j1 + 1]; 318 | yr = a[k1]; 319 | yi = -a[k1 + 1]; 320 | a[j1] = yr; 321 | a[j1 + 1] = yi; 322 | a[k1] = xr; 323 | a[k1 + 1] = xi; 324 | j1 += m2; 325 | k1 -= m2; 326 | xr = a[j1]; 327 | xi = -a[j1 + 1]; 328 | yr = a[k1]; 329 | yi = -a[k1 + 1]; 330 | a[j1] = yr; 331 | a[j1 + 1] = yi; 332 | a[k1] = xr; 333 | a[k1 + 1] = xi; 334 | j1 += m2; 335 | k1 += 2 * m2; 336 | xr = a[j1]; 337 | xi = -a[j1 + 1]; 338 | yr = a[k1]; 339 | yi = -a[k1 + 1]; 340 | a[j1] = yr; 341 | a[j1 + 1] = yi; 342 | a[k1] = xr; 343 | a[k1 + 1] = xi; 344 | } 345 | k1 = 2 * k + ip[k]; 346 | a[k1 + 1] = -a[k1 + 1]; 347 | j1 = k1 + m2; 348 | k1 = j1 + m2; 349 | xr = a[j1]; 350 | xi = -a[j1 + 1]; 351 | yr = a[k1]; 352 | yi = -a[k1 + 1]; 353 | a[j1] = yr; 354 | a[j1 + 1] = yi; 355 | a[k1] = xr; 356 | a[k1 + 1] = xi; 357 | k1 += m2; 358 | a[k1 + 1] = -a[k1 + 1]; 359 | } 360 | } else { 361 | a[1] = -a[1]; 362 | a[m2 + 1] = -a[m2 + 1]; 363 | for (k = 1; k < m; k++) { 364 | for (j = 0; j < k; j++) { 365 | j1 = 2 * j + ip[k]; 366 | k1 = 2 * k + ip[j]; 367 | xr = a[j1]; 368 | xi = -a[j1 + 1]; 369 | yr = a[k1]; 370 | yi = -a[k1 + 1]; 371 | a[j1] = yr; 372 | a[j1 + 1] = yi; 373 | a[k1] = xr; 374 | a[k1 + 1] = xi; 375 | j1 += m2; 376 | k1 += m2; 377 | xr = a[j1]; 378 | xi = -a[j1 + 1]; 379 | yr = a[k1]; 380 | yi = -a[k1 + 1]; 381 | a[j1] = yr; 382 | a[j1 + 1] = yi; 383 | a[k1] = xr; 384 | a[k1 + 1] = xi; 385 | } 386 | k1 = 2 * k + ip[k]; 387 | a[k1 + 1] = -a[k1 + 1]; 388 | a[k1 + m2 + 1] = -a[k1 + m2 + 1]; 389 | } 390 | } 391 | } 392 | 393 | 394 | void cftfsub(int n, float *a, float *w) 395 | { 396 | void cft1st(int n, float *a, float *w); 397 | void cftmdl(int n, int l, float *a, float *w); 398 | int j, j1, j2, j3, l; 399 | float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 400 | 401 | l = 2; 402 | if (n > 8) { 403 | cft1st(n, a, w); 404 | l = 8; 405 | while ((l << 2) < n) { 406 | cftmdl(n, l, a, w); 407 | l <<= 2; 408 | } 409 | } 410 | if ((l << 2) == n) { 411 | for (j = 0; j < l; j += 2) { 412 | j1 = j + l; 413 | j2 = j1 + l; 414 | j3 = j2 + l; 415 | x0r = a[j] + a[j1]; 416 | x0i = a[j + 1] + a[j1 + 1]; 417 | x1r = a[j] - a[j1]; 418 | x1i = a[j + 1] - a[j1 + 1]; 419 | x2r = a[j2] + a[j3]; 420 | x2i = a[j2 + 1] + a[j3 + 1]; 421 | x3r = a[j2] - a[j3]; 422 | x3i = a[j2 + 1] - a[j3 + 1]; 423 | a[j] = x0r + x2r; 424 | a[j + 1] = x0i + x2i; 425 | a[j2] = x0r - x2r; 426 | a[j2 + 1] = x0i - x2i; 427 | a[j1] = x1r - x3i; 428 | a[j1 + 1] = x1i + x3r; 429 | a[j3] = x1r + x3i; 430 | a[j3 + 1] = x1i - x3r; 431 | } 432 | } else { 433 | for (j = 0; j < l; j += 2) { 434 | j1 = j + l; 435 | x0r = a[j] - a[j1]; 436 | x0i = a[j + 1] - a[j1 + 1]; 437 | a[j] += a[j1]; 438 | a[j + 1] += a[j1 + 1]; 439 | a[j1] = x0r; 440 | a[j1 + 1] = x0i; 441 | } 442 | } 443 | } 444 | 445 | 446 | void cftbsub(int n, float *a, float *w) 447 | { 448 | void cft1st(int n, float *a, float *w); 449 | void cftmdl(int n, int l, float *a, float *w); 450 | int j, j1, j2, j3, l; 451 | float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 452 | 453 | l = 2; 454 | if (n > 8) { 455 | cft1st(n, a, w); 456 | l = 8; 457 | while ((l << 2) < n) { 458 | cftmdl(n, l, a, w); 459 | l <<= 2; 460 | } 461 | } 462 | if ((l << 2) == n) { 463 | for (j = 0; j < l; j += 2) { 464 | j1 = j + l; 465 | j2 = j1 + l; 466 | j3 = j2 + l; 467 | x0r = a[j] + a[j1]; 468 | x0i = -a[j + 1] - a[j1 + 1]; 469 | x1r = a[j] - a[j1]; 470 | x1i = -a[j + 1] + a[j1 + 1]; 471 | x2r = a[j2] + a[j3]; 472 | x2i = a[j2 + 1] + a[j3 + 1]; 473 | x3r = a[j2] - a[j3]; 474 | x3i = a[j2 + 1] - a[j3 + 1]; 475 | a[j] = x0r + x2r; 476 | a[j + 1] = x0i - x2i; 477 | a[j2] = x0r - x2r; 478 | a[j2 + 1] = x0i + x2i; 479 | a[j1] = x1r - x3i; 480 | a[j1 + 1] = x1i - x3r; 481 | a[j3] = x1r + x3i; 482 | a[j3 + 1] = x1i + x3r; 483 | } 484 | } else { 485 | for (j = 0; j < l; j += 2) { 486 | j1 = j + l; 487 | x0r = a[j] - a[j1]; 488 | x0i = -a[j + 1] + a[j1 + 1]; 489 | a[j] += a[j1]; 490 | a[j + 1] = -a[j + 1] - a[j1 + 1]; 491 | a[j1] = x0r; 492 | a[j1 + 1] = x0i; 493 | } 494 | } 495 | } 496 | 497 | 498 | void cft1st(int n, float *a, float *w) 499 | { 500 | int j, k1, k2; 501 | float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; 502 | float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 503 | 504 | x0r = a[0] + a[2]; 505 | x0i = a[1] + a[3]; 506 | x1r = a[0] - a[2]; 507 | x1i = a[1] - a[3]; 508 | x2r = a[4] + a[6]; 509 | x2i = a[5] + a[7]; 510 | x3r = a[4] - a[6]; 511 | x3i = a[5] - a[7]; 512 | a[0] = x0r + x2r; 513 | a[1] = x0i + x2i; 514 | a[4] = x0r - x2r; 515 | a[5] = x0i - x2i; 516 | a[2] = x1r - x3i; 517 | a[3] = x1i + x3r; 518 | a[6] = x1r + x3i; 519 | a[7] = x1i - x3r; 520 | wk1r = w[2]; 521 | x0r = a[8] + a[10]; 522 | x0i = a[9] + a[11]; 523 | x1r = a[8] - a[10]; 524 | x1i = a[9] - a[11]; 525 | x2r = a[12] + a[14]; 526 | x2i = a[13] + a[15]; 527 | x3r = a[12] - a[14]; 528 | x3i = a[13] - a[15]; 529 | a[8] = x0r + x2r; 530 | a[9] = x0i + x2i; 531 | a[12] = x2i - x0i; 532 | a[13] = x0r - x2r; 533 | x0r = x1r - x3i; 534 | x0i = x1i + x3r; 535 | a[10] = wk1r * (x0r - x0i); 536 | a[11] = wk1r * (x0r + x0i); 537 | x0r = x3i + x1r; 538 | x0i = x3r - x1i; 539 | a[14] = wk1r * (x0i - x0r); 540 | a[15] = wk1r * (x0i + x0r); 541 | k1 = 0; 542 | for (j = 16; j < n; j += 16) { 543 | k1 += 2; 544 | k2 = 2 * k1; 545 | wk2r = w[k1]; 546 | wk2i = w[k1 + 1]; 547 | wk1r = w[k2]; 548 | wk1i = w[k2 + 1]; 549 | wk3r = wk1r - 2 * wk2i * wk1i; 550 | wk3i = 2 * wk2i * wk1r - wk1i; 551 | x0r = a[j] + a[j + 2]; 552 | x0i = a[j + 1] + a[j + 3]; 553 | x1r = a[j] - a[j + 2]; 554 | x1i = a[j + 1] - a[j + 3]; 555 | x2r = a[j + 4] + a[j + 6]; 556 | x2i = a[j + 5] + a[j + 7]; 557 | x3r = a[j + 4] - a[j + 6]; 558 | x3i = a[j + 5] - a[j + 7]; 559 | a[j] = x0r + x2r; 560 | a[j + 1] = x0i + x2i; 561 | x0r -= x2r; 562 | x0i -= x2i; 563 | a[j + 4] = wk2r * x0r - wk2i * x0i; 564 | a[j + 5] = wk2r * x0i + wk2i * x0r; 565 | x0r = x1r - x3i; 566 | x0i = x1i + x3r; 567 | a[j + 2] = wk1r * x0r - wk1i * x0i; 568 | a[j + 3] = wk1r * x0i + wk1i * x0r; 569 | x0r = x1r + x3i; 570 | x0i = x1i - x3r; 571 | a[j + 6] = wk3r * x0r - wk3i * x0i; 572 | a[j + 7] = wk3r * x0i + wk3i * x0r; 573 | wk1r = w[k2 + 2]; 574 | wk1i = w[k2 + 3]; 575 | wk3r = wk1r - 2 * wk2r * wk1i; 576 | wk3i = 2 * wk2r * wk1r - wk1i; 577 | x0r = a[j + 8] + a[j + 10]; 578 | x0i = a[j + 9] + a[j + 11]; 579 | x1r = a[j + 8] - a[j + 10]; 580 | x1i = a[j + 9] - a[j + 11]; 581 | x2r = a[j + 12] + a[j + 14]; 582 | x2i = a[j + 13] + a[j + 15]; 583 | x3r = a[j + 12] - a[j + 14]; 584 | x3i = a[j + 13] - a[j + 15]; 585 | a[j + 8] = x0r + x2r; 586 | a[j + 9] = x0i + x2i; 587 | x0r -= x2r; 588 | x0i -= x2i; 589 | a[j + 12] = -wk2i * x0r - wk2r * x0i; 590 | a[j + 13] = -wk2i * x0i + wk2r * x0r; 591 | x0r = x1r - x3i; 592 | x0i = x1i + x3r; 593 | a[j + 10] = wk1r * x0r - wk1i * x0i; 594 | a[j + 11] = wk1r * x0i + wk1i * x0r; 595 | x0r = x1r + x3i; 596 | x0i = x1i - x3r; 597 | a[j + 14] = wk3r * x0r - wk3i * x0i; 598 | a[j + 15] = wk3r * x0i + wk3i * x0r; 599 | } 600 | } 601 | 602 | 603 | void cftmdl(int n, int l, float *a, float *w) 604 | { 605 | int j, j1, j2, j3, k, k1, k2, m, m2; 606 | float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; 607 | float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 608 | 609 | m = l << 2; 610 | for (j = 0; j < l; j += 2) { 611 | j1 = j + l; 612 | j2 = j1 + l; 613 | j3 = j2 + l; 614 | x0r = a[j] + a[j1]; 615 | x0i = a[j + 1] + a[j1 + 1]; 616 | x1r = a[j] - a[j1]; 617 | x1i = a[j + 1] - a[j1 + 1]; 618 | x2r = a[j2] + a[j3]; 619 | x2i = a[j2 + 1] + a[j3 + 1]; 620 | x3r = a[j2] - a[j3]; 621 | x3i = a[j2 + 1] - a[j3 + 1]; 622 | a[j] = x0r + x2r; 623 | a[j + 1] = x0i + x2i; 624 | a[j2] = x0r - x2r; 625 | a[j2 + 1] = x0i - x2i; 626 | a[j1] = x1r - x3i; 627 | a[j1 + 1] = x1i + x3r; 628 | a[j3] = x1r + x3i; 629 | a[j3 + 1] = x1i - x3r; 630 | } 631 | wk1r = w[2]; 632 | for (j = m; j < l + m; j += 2) { 633 | j1 = j + l; 634 | j2 = j1 + l; 635 | j3 = j2 + l; 636 | x0r = a[j] + a[j1]; 637 | x0i = a[j + 1] + a[j1 + 1]; 638 | x1r = a[j] - a[j1]; 639 | x1i = a[j + 1] - a[j1 + 1]; 640 | x2r = a[j2] + a[j3]; 641 | x2i = a[j2 + 1] + a[j3 + 1]; 642 | x3r = a[j2] - a[j3]; 643 | x3i = a[j2 + 1] - a[j3 + 1]; 644 | a[j] = x0r + x2r; 645 | a[j + 1] = x0i + x2i; 646 | a[j2] = x2i - x0i; 647 | a[j2 + 1] = x0r - x2r; 648 | x0r = x1r - x3i; 649 | x0i = x1i + x3r; 650 | a[j1] = wk1r * (x0r - x0i); 651 | a[j1 + 1] = wk1r * (x0r + x0i); 652 | x0r = x3i + x1r; 653 | x0i = x3r - x1i; 654 | a[j3] = wk1r * (x0i - x0r); 655 | a[j3 + 1] = wk1r * (x0i + x0r); 656 | } 657 | k1 = 0; 658 | m2 = 2 * m; 659 | for (k = m2; k < n; k += m2) { 660 | k1 += 2; 661 | k2 = 2 * k1; 662 | wk2r = w[k1]; 663 | wk2i = w[k1 + 1]; 664 | wk1r = w[k2]; 665 | wk1i = w[k2 + 1]; 666 | wk3r = wk1r - 2 * wk2i * wk1i; 667 | wk3i = 2 * wk2i * wk1r - wk1i; 668 | for (j = k; j < l + k; j += 2) { 669 | j1 = j + l; 670 | j2 = j1 + l; 671 | j3 = j2 + l; 672 | x0r = a[j] + a[j1]; 673 | x0i = a[j + 1] + a[j1 + 1]; 674 | x1r = a[j] - a[j1]; 675 | x1i = a[j + 1] - a[j1 + 1]; 676 | x2r = a[j2] + a[j3]; 677 | x2i = a[j2 + 1] + a[j3 + 1]; 678 | x3r = a[j2] - a[j3]; 679 | x3i = a[j2 + 1] - a[j3 + 1]; 680 | a[j] = x0r + x2r; 681 | a[j + 1] = x0i + x2i; 682 | x0r -= x2r; 683 | x0i -= x2i; 684 | a[j2] = wk2r * x0r - wk2i * x0i; 685 | a[j2 + 1] = wk2r * x0i + wk2i * x0r; 686 | x0r = x1r - x3i; 687 | x0i = x1i + x3r; 688 | a[j1] = wk1r * x0r - wk1i * x0i; 689 | a[j1 + 1] = wk1r * x0i + wk1i * x0r; 690 | x0r = x1r + x3i; 691 | x0i = x1i - x3r; 692 | a[j3] = wk3r * x0r - wk3i * x0i; 693 | a[j3 + 1] = wk3r * x0i + wk3i * x0r; 694 | } 695 | wk1r = w[k2 + 2]; 696 | wk1i = w[k2 + 3]; 697 | wk3r = wk1r - 2 * wk2r * wk1i; 698 | wk3i = 2 * wk2r * wk1r - wk1i; 699 | for (j = k + m; j < l + (k + m); j += 2) { 700 | j1 = j + l; 701 | j2 = j1 + l; 702 | j3 = j2 + l; 703 | x0r = a[j] + a[j1]; 704 | x0i = a[j + 1] + a[j1 + 1]; 705 | x1r = a[j] - a[j1]; 706 | x1i = a[j + 1] - a[j1 + 1]; 707 | x2r = a[j2] + a[j3]; 708 | x2i = a[j2 + 1] + a[j3 + 1]; 709 | x3r = a[j2] - a[j3]; 710 | x3i = a[j2 + 1] - a[j3 + 1]; 711 | a[j] = x0r + x2r; 712 | a[j + 1] = x0i + x2i; 713 | x0r -= x2r; 714 | x0i -= x2i; 715 | a[j2] = -wk2i * x0r - wk2r * x0i; 716 | a[j2 + 1] = -wk2i * x0i + wk2r * x0r; 717 | x0r = x1r - x3i; 718 | x0i = x1i + x3r; 719 | a[j1] = wk1r * x0r - wk1i * x0i; 720 | a[j1 + 1] = wk1r * x0i + wk1i * x0r; 721 | x0r = x1r + x3i; 722 | x0i = x1i - x3r; 723 | a[j3] = wk3r * x0r - wk3i * x0i; 724 | a[j3 + 1] = wk3r * x0i + wk3i * x0r; 725 | } 726 | } 727 | } 728 | 729 | 730 | void rftfsub(int n, float *a, int nc, float *c) 731 | { 732 | int j, k, kk, ks, m; 733 | float wkr, wki, xr, xi, yr, yi; 734 | 735 | m = n >> 1; 736 | ks = 2 * nc / m; 737 | kk = 0; 738 | for (j = 2; j < m; j += 2) { 739 | k = n - j; 740 | kk += ks; 741 | wkr = 0.5 - c[nc - kk]; 742 | wki = c[kk]; 743 | xr = a[j] - a[k]; 744 | xi = a[j + 1] + a[k + 1]; 745 | yr = wkr * xr - wki * xi; 746 | yi = wkr * xi + wki * xr; 747 | a[j] -= yr; 748 | a[j + 1] -= yi; 749 | a[k] += yr; 750 | a[k + 1] -= yi; 751 | } 752 | } 753 | 754 | 755 | void rftbsub(int n, float *a, int nc, float *c) 756 | { 757 | int j, k, kk, ks, m; 758 | float wkr, wki, xr, xi, yr, yi; 759 | 760 | a[1] = -a[1]; 761 | m = n >> 1; 762 | ks = 2 * nc / m; 763 | kk = 0; 764 | for (j = 2; j < m; j += 2) { 765 | k = n - j; 766 | kk += ks; 767 | wkr = 0.5 - c[nc - kk]; 768 | wki = c[kk]; 769 | xr = a[j] - a[k]; 770 | xi = a[j + 1] + a[k + 1]; 771 | yr = wkr * xr + wki * xi; 772 | yi = wkr * xi - wki * xr; 773 | a[j] -= yr; 774 | a[j + 1] = yi - a[j + 1]; 775 | a[k] += yr; 776 | a[k + 1] = yi - a[k + 1]; 777 | } 778 | a[m + 1] = -a[m + 1]; 779 | } 780 | -------------------------------------------------------------------------------- /src/ggwave/ggwave.h: -------------------------------------------------------------------------------- 1 | #ifndef GGWAVE_H 2 | #define GGWAVE_H 3 | 4 | #ifdef GGWAVE_SHARED 5 | # ifdef _WIN32 6 | # ifdef GGWAVE_BUILD 7 | # define GGWAVE_API __declspec(dllexport) 8 | # else 9 | # define GGWAVE_API __declspec(dllimport) 10 | # endif 11 | # else 12 | # define GGWAVE_API __attribute__ ((visibility ("default"))) 13 | # endif 14 | #else 15 | # define GGWAVE_API 16 | #endif 17 | 18 | #if defined(ARDUINO_UNO) 19 | #define GGWAVE_CONFIG_FEW_PROTOCOLS 20 | #endif 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | // 27 | // C interface 28 | // 29 | 30 | #define GGWAVE_MAX_INSTANCES 4 31 | 32 | // Data format of the audio samples 33 | typedef enum { 34 | GGWAVE_SAMPLE_FORMAT_UNDEFINED, 35 | GGWAVE_SAMPLE_FORMAT_U8, 36 | GGWAVE_SAMPLE_FORMAT_I8, 37 | GGWAVE_SAMPLE_FORMAT_U16, 38 | GGWAVE_SAMPLE_FORMAT_I16, 39 | GGWAVE_SAMPLE_FORMAT_F32, 40 | } ggwave_SampleFormat; 41 | 42 | // Protocol ids 43 | typedef enum { 44 | #ifndef GGWAVE_CONFIG_FEW_PROTOCOLS 45 | GGWAVE_PROTOCOL_AUDIBLE_NORMAL, 46 | GGWAVE_PROTOCOL_AUDIBLE_FAST, 47 | GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 48 | GGWAVE_PROTOCOL_ULTRASOUND_NORMAL, 49 | GGWAVE_PROTOCOL_ULTRASOUND_FAST, 50 | GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, 51 | #endif 52 | GGWAVE_PROTOCOL_DT_NORMAL, 53 | GGWAVE_PROTOCOL_DT_FAST, 54 | GGWAVE_PROTOCOL_DT_FASTEST, 55 | GGWAVE_PROTOCOL_MT_NORMAL, 56 | GGWAVE_PROTOCOL_MT_FAST, 57 | GGWAVE_PROTOCOL_MT_FASTEST, 58 | 59 | #ifndef GGWAVE_CONFIG_FEW_PROTOCOLS 60 | GGWAVE_PROTOCOL_CUSTOM_0, 61 | GGWAVE_PROTOCOL_CUSTOM_1, 62 | GGWAVE_PROTOCOL_CUSTOM_2, 63 | GGWAVE_PROTOCOL_CUSTOM_3, 64 | GGWAVE_PROTOCOL_CUSTOM_4, 65 | GGWAVE_PROTOCOL_CUSTOM_5, 66 | GGWAVE_PROTOCOL_CUSTOM_6, 67 | GGWAVE_PROTOCOL_CUSTOM_7, 68 | GGWAVE_PROTOCOL_CUSTOM_8, 69 | GGWAVE_PROTOCOL_CUSTOM_9, 70 | 71 | #endif 72 | GGWAVE_PROTOCOL_COUNT, 73 | } ggwave_ProtocolId; 74 | 75 | typedef enum { 76 | GGWAVE_FILTER_HANN, 77 | GGWAVE_FILTER_HAMMING, 78 | GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS, 79 | } ggwave_Filter; 80 | 81 | // Operating modes of ggwave 82 | // 83 | // GGWAVE_OPERATING_MODE_RX: 84 | // The instance will be able to receive audio data 85 | // 86 | // GGWAVE_OPERATING_MODE_TX: 87 | // The instance will be able generate audio waveforms for transmission 88 | // 89 | // GGWAVE_OPERATING_MODE_TX_ONLY_TONES: 90 | // The encoding process generates only a list of tones instead of full audio 91 | // waveform. This is useful for low-memory devices and embedded systems. 92 | // 93 | // GGWAVE_OPERATING_MODE_USE_DSS: 94 | // Enable the built-in Direct Sequence Spread (DSS) algorithm 95 | // 96 | enum { 97 | GGWAVE_OPERATING_MODE_RX = 1 << 1, 98 | GGWAVE_OPERATING_MODE_TX = 1 << 2, 99 | GGWAVE_OPERATING_MODE_RX_AND_TX = (GGWAVE_OPERATING_MODE_RX | 100 | GGWAVE_OPERATING_MODE_TX), 101 | GGWAVE_OPERATING_MODE_TX_ONLY_TONES = 1 << 3, 102 | GGWAVE_OPERATING_MODE_USE_DSS = 1 << 4, 103 | }; 104 | 105 | // GGWave instance parameters 106 | // 107 | // If payloadLength <= 0, then GGWave will transmit with variable payload length 108 | // depending on the provided payload. Sound markers are used to identify the 109 | // start and end of the transmission. 110 | // 111 | // If payloadLength > 0, then the transmitted payload will be of the specified 112 | // fixed length. In this case, no sound markers are emitted and a slightly 113 | // different decoding scheme is applied. This is useful in cases where the 114 | // length of the payload is known in advance. 115 | // 116 | // The sample rates are values typically between 1000 and 96000. 117 | // Default value: GGWave::kDefaultSampleRate 118 | // 119 | // The captured audio is resampled to the specified sampleRate if sampleRatInp 120 | // is different from sampleRate. Same applies to the transmitted audio. 121 | // 122 | // The samplesPerFrame is the number of samples on which ggwave performs FFT. 123 | // This affects the number of bins in the Fourier spectrum. 124 | // Default value: GGWave::kDefaultSamplesPerFrame 125 | // 126 | // The operatingMode controls which functions of the ggwave instance are enabled. 127 | // Use this parameter to reduce the memory footprint of the ggwave instance. For 128 | // example, if only Rx is enabled, then the memory buffers needed for the Tx will 129 | // not be allocated. 130 | // 131 | typedef struct { 132 | int payloadLength; // payload length 133 | float sampleRateInp; // capture sample rate 134 | float sampleRateOut; // playback sample rate 135 | float sampleRate; // the operating sample rate 136 | int samplesPerFrame; // number of samples per audio frame 137 | float soundMarkerThreshold; // sound marker detection threshold 138 | ggwave_SampleFormat sampleFormatInp; // format of the captured audio samples 139 | ggwave_SampleFormat sampleFormatOut; // format of the playback audio samples 140 | int operatingMode; // operating mode 141 | } ggwave_Parameters; 142 | 143 | // GGWave instances are identified with an integer and are stored 144 | // in a private map container. Using void * caused some issues with 145 | // the python module and unfortunately had to do it this way 146 | typedef int ggwave_Instance; 147 | 148 | // Change file stream for internal ggwave logging. NULL - disable logging 149 | // 150 | // Intentionally passing it as void * instead of FILE * to avoid including a header 151 | // 152 | // // log to standard error 153 | // ggwave_setLogFile(stderr); 154 | // 155 | // // log to standard output 156 | // ggwave_setLogFile(stdout); 157 | // 158 | // // disable logging 159 | // ggwave_setLogFile(NULL); 160 | // 161 | // Note: not thread-safe. Do not call while any GGWave instances are running 162 | // 163 | GGWAVE_API void ggwave_setLogFile(void * fptr); 164 | 165 | // Helper method to get default instance parameters 166 | GGWAVE_API ggwave_Parameters ggwave_getDefaultParameters(void); 167 | 168 | // Create a new GGWave instance with the specified parameters 169 | // 170 | // The newly created instance is added to the internal map container. 171 | // This function returns an id that can be used to identify this instance. 172 | // Make sure to deallocate the instance at the end by calling ggwave_free() 173 | // 174 | GGWAVE_API ggwave_Instance ggwave_init(ggwave_Parameters parameters); 175 | 176 | // Free a GGWave instance 177 | GGWAVE_API void ggwave_free(ggwave_Instance instance); 178 | 179 | // Encode data into audio waveform 180 | // 181 | // instance - the GGWave instance to use 182 | // payloadBuffer - the data to encode 183 | // payloadSize - number of bytes in the input payloadBuffer 184 | // protocolId - the protocol to use for encoding 185 | // volume - the volume of the generated waveform [0, 100] 186 | // usually 25 is OK and you should not go over 50 187 | // waveformBuffer - the generated audio waveform. must be big enough to fit the generated data 188 | // query - if == 0, encode data in to waveformBuffer, returns number of bytes 189 | // if != 0, do not perform encoding. 190 | // if == 1, return waveform size in bytes 191 | // if != 1, return waveform size in samples 192 | // 193 | // returns the number of generated bytes or samples (see query) 194 | // 195 | // returns -1 if there was an error 196 | // 197 | // This function can be used to encode some binary data (payload) into an audio waveform. 198 | // 199 | // payload -> waveform 200 | // 201 | // When calling it, make sure that the waveformBuffer is big enough to store the 202 | // generated waveform. This means that its size must be at least: 203 | // 204 | // nSamples*sizeOfSample_bytes 205 | // 206 | // Where nSamples is the number of audio samples in the waveform and sizeOfSample_bytes 207 | // is the size of a single sample in bytes based on the sampleFormatOut parameter 208 | // specified during the initialization of the GGWave instance. 209 | // 210 | // If query != 0, then this function does not perform the actual encoding and just 211 | // outputs the expected size of the waveform that would be generated if you call it 212 | // with query == 0. This mechanism can be used to ask ggwave how much memory to 213 | // allocate for the waveformBuffer. For example: 214 | // 215 | // // this is the data to encode 216 | // const char * payload = "test"; 217 | // 218 | // // query the number of bytes in the waveform 219 | // int n = ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FAST, 25, NULL, 1); 220 | // 221 | // // allocate the output buffer 222 | // char waveform[n]; 223 | // 224 | // // generate the waveform 225 | // ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FAST, 25, waveform, 0); 226 | // 227 | // The payloadBuffer can be any binary data that you would like to transmit (i.e. the payload). 228 | // Usually, this is some text, but it can be any sequence of bytes. 229 | // 230 | GGWAVE_API int ggwave_encode( 231 | ggwave_Instance instance, 232 | const void * payloadBuffer, 233 | int payloadSize, 234 | ggwave_ProtocolId protocolId, 235 | int volume, 236 | void * waveformBuffer, 237 | int query); 238 | 239 | // Decode an audio waveform into data 240 | // 241 | // instance - the GGWave instance to use 242 | // waveformBuffer - the audio waveform 243 | // waveformSize - number of bytes in the input waveformBuffer 244 | // payloadBuffer - stores the decoded data on success 245 | // the maximum size of the output is GGWave::kMaxDataSize 246 | // 247 | // returns the number of decoded bytes 248 | // 249 | // Use this function to continuously provide audio samples to a GGWave instance. 250 | // On each call, GGWave will analyze the provided data and if it detects a payload, 251 | // it will return a non-zero result. 252 | // 253 | // waveform -> payload 254 | // 255 | // If the return value is -1 then there was an error during the decoding process. 256 | // Usually can occur if there is a lot of background noise in the audio. 257 | // 258 | // If the return value is greater than 0, then there are that number of bytes decoded. 259 | // 260 | // IMPORTANT: 261 | // Notice that the decoded data written to the payloadBuffer is NOT null terminated. 262 | // 263 | // Example: 264 | // 265 | // char payload[256]; 266 | // 267 | // while (true) { 268 | // ... capture samplesPerFrame audio samples into waveform ... 269 | // 270 | // int ret = ggwave_decode(instance, waveform, samplesPerFrame*sizeOfSample_bytes, payload); 271 | // if (ret > 0) { 272 | // payload[ret] = 0; // null terminate the string 273 | // printf("Received payload: '%s'\n", payload); 274 | // } 275 | // } 276 | // 277 | GGWAVE_API int ggwave_decode( 278 | ggwave_Instance instance, 279 | const void * waveformBuffer, 280 | int waveformSize, 281 | void * payloadBuffer); 282 | 283 | // Memory-safe overload of ggwave_decode 284 | // 285 | // payloadSize - optionally specify the size of the output buffer 286 | // 287 | // If the return value is -2 then the provided payloadBuffer was not big enough to 288 | // store the decoded data. 289 | // 290 | // See ggwave_decode for more information 291 | // 292 | GGWAVE_API int ggwave_ndecode( 293 | ggwave_Instance instance, 294 | const void * waveformBuffer, 295 | int waveformSize, 296 | void * payloadBuffer, 297 | int payloadSize); 298 | 299 | // Toggle Rx protocols on and off 300 | // 301 | // protocolId - Id of the Rx protocol to modify 302 | // state - 0 - disable, 1 - enable 303 | // 304 | // If an Rx protocol is enabled, newly constructued GGWave instances will attempt to decode 305 | // received data using this protocol. By default, all protocols are enabled. 306 | // Use this function to restrict the number of Rx protocols used in the decoding 307 | // process. This helps to reduce the number of false positives and improves the transmission 308 | // accuracy, especially when the Tx/Rx protocol is known in advance. 309 | // 310 | // Note that this function does not affect the decoding process of instances that have 311 | // already been created. 312 | // 313 | GGWAVE_API void ggwave_rxToggleProtocol( 314 | ggwave_ProtocolId protocolId, 315 | int state); 316 | 317 | // Toggle Tx protocols on and off 318 | // 319 | // protocolId - Id of the Tx protocol to modify 320 | // state - 0 - disable, 1 - enable 321 | // 322 | // If an Tx protocol is enabled, newly constructued GGWave instances will be able to transmit 323 | // data using this protocol. By default, all protocols are enabled. 324 | // Use this function to restrict the number of Tx protocols used for transmission. 325 | // This can reduce the required memory by the GGWave instance. 326 | // 327 | // Note that this function does not affect instances that have already been created. 328 | // 329 | GGWAVE_API void ggwave_txToggleProtocol( 330 | ggwave_ProtocolId protocolId, 331 | int state); 332 | 333 | #ifdef __cplusplus 334 | } 335 | 336 | // 337 | // C++ interface 338 | // 339 | 340 | template 341 | struct ggvector { 342 | private: 343 | T * m_data; 344 | int m_size; 345 | 346 | public: 347 | using value_type = T; 348 | 349 | ggvector() : m_data(nullptr), m_size(0) {} 350 | ggvector(T * data, int size) : m_data(data), m_size(size) {} 351 | 352 | ggvector(const ggvector & other) = default; 353 | 354 | // delete operator= 355 | ggvector & operator=(const ggvector &) = delete; 356 | ggvector & operator=(ggvector &&) = delete; 357 | 358 | T & operator[](int i) { return m_data[i]; } 359 | const T & operator[](int i) const { return m_data[i]; } 360 | 361 | int size() const { return m_size; } 362 | T * data() const { return m_data; } 363 | 364 | T * begin() const { return m_data; } 365 | T * end() const { return m_data + m_size; } 366 | 367 | void assign(const ggvector & other); 368 | void copy(const ggvector & other); 369 | 370 | void zero(); 371 | void zero(int n); 372 | }; 373 | 374 | template 375 | struct ggmatrix { 376 | private: 377 | T * m_data; 378 | int m_size0; 379 | int m_size1; 380 | 381 | public: 382 | using value_type = T; 383 | 384 | ggmatrix() : m_data(nullptr), m_size0(0), m_size1(0) {} 385 | ggmatrix(T * data, int size0, int size1) : m_data(data), m_size0(size0), m_size1(size1) {} 386 | 387 | ggvector operator[](int i) { 388 | return ggvector(m_data + i*m_size1, m_size1); 389 | } 390 | 391 | int size() const { return m_size0; } 392 | 393 | void zero(); 394 | }; 395 | 396 | #include 397 | #include 398 | 399 | #ifdef ARDUINO 400 | #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) 401 | #include 402 | #else 403 | #include 404 | #endif 405 | #endif 406 | 407 | class GGWave { 408 | public: 409 | static constexpr auto kSampleRateMin = 1000.0f; 410 | static constexpr auto kSampleRateMax = 96000.0f; 411 | static constexpr auto kDefaultSampleRate = 48000.0f; 412 | static constexpr auto kDefaultSamplesPerFrame = 1024; 413 | static constexpr auto kDefaultVolume = 10; 414 | static constexpr auto kDefaultSoundMarkerThreshold = 3.0f; 415 | static constexpr auto kDefaultMarkerFrames = 16; 416 | static constexpr auto kDefaultEncodedDataOffset = 3; 417 | static constexpr auto kMaxSamplesPerFrame = 1024; 418 | static constexpr auto kMaxDataSize = 256; 419 | static constexpr auto kMaxLengthVariable = 140; 420 | static constexpr auto kMaxLengthFixed = 64; 421 | static constexpr auto kMaxSpectrumHistory = 4; 422 | static constexpr auto kMaxRecordedFrames = 2048; 423 | 424 | using Parameters = ggwave_Parameters; 425 | using SampleFormat = ggwave_SampleFormat; 426 | using ProtocolId = ggwave_ProtocolId; 427 | using TxProtocolId = ggwave_ProtocolId; 428 | using RxProtocolId = ggwave_ProtocolId; 429 | using OperatingMode = int; // ggwave_OperatingMode; 430 | 431 | struct Protocol { 432 | const char * name; // string identifier of the protocol 433 | 434 | int16_t freqStart; // FFT bin index of the lowest frequency 435 | int8_t framesPerTx; // number of frames to transmit a single chunk of data 436 | int8_t bytesPerTx; // number of bytes in a chunk of data 437 | int8_t extra; // 2 if this is a mono-tone protocol, 1 otherwise 438 | 439 | bool enabled; 440 | 441 | int nTones() const { return (2*bytesPerTx)/extra; } 442 | int nDataBitsPerTx() const { return 8*bytesPerTx; } 443 | int txDuration_ms(int samplesPerFrame, float sampleRate) const { 444 | return framesPerTx*((1000.0f*samplesPerFrame)/sampleRate); 445 | } 446 | }; 447 | 448 | using TxProtocol = Protocol; 449 | using RxProtocol = Protocol; 450 | 451 | struct Protocols; 452 | 453 | using TxProtocols = Protocols; 454 | using RxProtocols = Protocols; 455 | 456 | struct Protocols { 457 | Protocol data[GGWAVE_PROTOCOL_COUNT]; 458 | 459 | int size() const { 460 | return GGWAVE_PROTOCOL_COUNT; 461 | } 462 | 463 | bool empty() const { 464 | return size() == 0; 465 | } 466 | 467 | Protocol & operator[](ProtocolId id) { 468 | return data[id]; 469 | } 470 | 471 | Protocol & operator[](int id) { 472 | return data[id]; 473 | } 474 | 475 | const Protocol & operator[](ProtocolId id) const { 476 | return data[id]; 477 | } 478 | 479 | const Protocol & operator[](int id) const { 480 | return data[id]; 481 | } 482 | 483 | void enableAll(); 484 | void disableAll(); 485 | 486 | void toggle(ProtocolId id, bool state); 487 | void only(ProtocolId id); 488 | 489 | static Protocols & kDefault() { 490 | static Protocols protocols; 491 | 492 | static bool initialized = false; 493 | if (initialized == false) { 494 | for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; ++i) { 495 | protocols.data[i].name = nullptr; 496 | protocols.data[i].enabled = false; 497 | } 498 | 499 | #if defined(ARDUINO_AVR_UNO) 500 | // For Arduino Uno, we put the strings in PROGMEM to save as much RAM as possible: 501 | #define GGWAVE_PSTR PSTR 502 | #else 503 | #define GGWAVE_PSTR(str) (str) 504 | #endif 505 | 506 | #ifndef GGWAVE_CONFIG_FEW_PROTOCOLS 507 | protocols.data[GGWAVE_PROTOCOL_AUDIBLE_NORMAL] = { GGWAVE_PSTR("Normal"), 40, 9, 3, 1, true, }; 508 | protocols.data[GGWAVE_PROTOCOL_AUDIBLE_FAST] = { GGWAVE_PSTR("Fast"), 40, 6, 3, 1, true, }; 509 | protocols.data[GGWAVE_PROTOCOL_AUDIBLE_FASTEST] = { GGWAVE_PSTR("Fastest"), 40, 3, 3, 1, true, }; 510 | protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_NORMAL] = { GGWAVE_PSTR("[U] Normal"), 320, 9, 3, 1, true, }; 511 | protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_FAST] = { GGWAVE_PSTR("[U] Fast"), 320, 6, 3, 1, true, }; 512 | protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_FASTEST] = { GGWAVE_PSTR("[U] Fastest"), 320, 3, 3, 1, true, }; 513 | #endif 514 | protocols.data[GGWAVE_PROTOCOL_DT_NORMAL] = { GGWAVE_PSTR("[DT] Normal"), 24, 9, 1, 1, true, }; 515 | protocols.data[GGWAVE_PROTOCOL_DT_FAST] = { GGWAVE_PSTR("[DT] Fast"), 24, 6, 1, 1, true, }; 516 | protocols.data[GGWAVE_PROTOCOL_DT_FASTEST] = { GGWAVE_PSTR("[DT] Fastest"), 24, 3, 1, 1, true, }; 517 | protocols.data[GGWAVE_PROTOCOL_MT_NORMAL] = { GGWAVE_PSTR("[MT] Normal"), 24, 9, 1, 2, true, }; 518 | protocols.data[GGWAVE_PROTOCOL_MT_FAST] = { GGWAVE_PSTR("[MT] Fast"), 24, 6, 1, 2, true, }; 519 | protocols.data[GGWAVE_PROTOCOL_MT_FASTEST] = { GGWAVE_PSTR("[MT] Fastest"), 24, 3, 1, 2, true, }; 520 | 521 | #undef GGWAVE_PSTR 522 | initialized = true; 523 | } 524 | 525 | return protocols; 526 | } 527 | 528 | static TxProtocols & tx(); 529 | static RxProtocols & rx(); 530 | }; 531 | 532 | using Tone = int8_t; 533 | 534 | // Tone data structure 535 | // 536 | // Each Tone element is the bin index of the tone frequency. 537 | // For protocol p: 538 | // - freq_hz = (p.freqStart + Tone) * hzPerSample 539 | // - duration_ms = p.txDuration_ms(samplesPerFrame, sampleRate) 540 | // 541 | // If the protocol is mono-tone, each element of the vector corresponds to a single tone. 542 | // Otherwise, the tones within a single Tx are separated by value of -1 543 | // 544 | using Tones = ggvector; 545 | 546 | using Amplitude = ggvector; 547 | using AmplitudeArr = ggmatrix; 548 | using AmplitudeI16 = ggvector; 549 | using Spectrum = ggvector; 550 | using RecordedData = ggvector; 551 | using TxRxData = ggvector; 552 | 553 | // Default constructor 554 | // 555 | // The GGWave object is not ready to use until you call prepare() 556 | // No memory is allocated with this constructor. 557 | // 558 | GGWave() = default; 559 | 560 | // Constructor with parameters 561 | // 562 | // Construct and prepare the GGWave object using the given parameters. 563 | // This constructor calls prepare() for you. 564 | // 565 | GGWave(const Parameters & parameters); 566 | 567 | ~GGWave(); 568 | 569 | // Prepare the GGWave object 570 | // 571 | // All memory buffers used by the GGWave instance are allocated with this function. 572 | // No memory allocations occur after that. 573 | // 574 | // Call this method if you used the default constructor. 575 | // Do not call this method if you used the constructor with parameters. 576 | // 577 | // The encode() and decode() methods will not work until this method is called. 578 | // 579 | // The sizes of the buffers are determined by the parameters and the contents of: 580 | // 581 | // - GGWave::Protocols::rx() 582 | // - GGWave::Protocols::tx() 583 | // 584 | // For optimal performance and minimum memory usage, make sure to enable only the 585 | // Rx and Tx protocols that you need. 586 | // 587 | // For example, using a single protocol for Tx is achieved like this: 588 | // 589 | // Parameters parameters; 590 | // parameters.operatingMode = GGWAVE_OPERATING_MODE_TX; 591 | // GGWave::Protocols::tx().only(GGWave::ProtocolId::GGWAVE_PROTOCOL_AUDIBLE_NORMAL); 592 | // GGWave instance(parameters); 593 | // instance.init(...); 594 | // instance.encode(); 595 | // 596 | // The created instance will only be able to transmit data using the "Normal" 597 | // protocol. Rx will be disabled. 598 | // 599 | // To create a corresponding Rx-only instance, use the following: 600 | // 601 | // Parameters parameters; 602 | // parameters.operatingMode = GGWAVE_OPERATING_MODE_RX; 603 | // GGWave::Protocols::rx().only(GGWave::ProtocolId::GGWAVE_PROTOCOL_AUDIBLE_NORMAL); 604 | // GGWave instance(parameters); 605 | // instance.decode(...); 606 | // 607 | // If "allocate" is false, the memory buffers are not allocated and only the required size 608 | // is computed. This is useful if you want to just see how much memory is needed for the 609 | // specific set of parameters and protocols. Do not use this function after you have already 610 | // prepared the instance. Instead, use the heapSize() method to see how much memory is used. 611 | // 612 | bool prepare(const Parameters & parameters, bool allocate = true); 613 | 614 | // Set file stream for the internal ggwave logging 615 | // 616 | // By default, ggwave prints internal log messages to stderr. 617 | // To disable logging all together, call this method with nullptr. 618 | // 619 | // Note: not thread-safe. Do not call while any GGWave instances are running 620 | // 621 | static void setLogFile(FILE * fptr); 622 | 623 | static const Parameters & getDefaultParameters(); 624 | 625 | // Set Tx data to encode into sound 626 | // 627 | // This prepares the GGWave instance for transmission. 628 | // To perform the actual encoding, call the encode() method. 629 | // 630 | // Returns false upon invalid parameters or failure to initialize the transmission 631 | // 632 | bool init(const char * text, TxProtocolId protocolId, const int volume = kDefaultVolume); 633 | bool init(int dataSize, const char * dataBuffer, TxProtocolId protocolId, const int volume = kDefaultVolume); 634 | 635 | // Expected waveform size of the encoded Tx data in bytes 636 | // 637 | // When the output sampling rate is not equal to operating sample rate the result of this method is overestimation 638 | // of the actual number of bytes that would be produced 639 | // 640 | uint32_t encodeSize_bytes() const; 641 | 642 | // Expected waveform size of the encoded Tx data in samples 643 | // 644 | // When the output sampling rate is not equal to operating sample rate the result of this method is overestimation 645 | // of the actual number of samples that would be produced 646 | // 647 | uint32_t encodeSize_samples() const; 648 | 649 | // Encode Tx data into an audio waveform 650 | // 651 | // After calling this method, use the Tx methods to get the encoded audio data. 652 | // 653 | // The generated waveform is available through the txWaveform() method 654 | // The tone frequencies are available through the txTones() method 655 | // 656 | // Returns the number of bytes in the generated waveform 657 | // 658 | uint32_t encode(); 659 | 660 | // Decode an audio waveform 661 | // 662 | // data - pointer to the waveform data 663 | // nBytes - number of bytes in the waveform 664 | // 665 | // The samples pointed to by "data" should be in the format given by sampleFormatInp(). 666 | // After calling this method, use the Rx methods to check if any data was decoded successfully. 667 | // 668 | // Returns false if the provided waveform is somehow invalid 669 | // 670 | bool decode(const void * data, uint32_t nBytes); 671 | 672 | // 673 | // Instance state 674 | // 675 | 676 | bool isDSSEnabled() const; 677 | 678 | int samplesPerFrame() const; 679 | int sampleSizeInp() const; 680 | int sampleSizeOut() const; 681 | 682 | float hzPerSample() const; 683 | float sampleRateInp() const; 684 | float sampleRateOut() const; 685 | SampleFormat sampleFormatInp() const; 686 | SampleFormat sampleFormatOut() const; 687 | 688 | int heapSize() const; 689 | 690 | // 691 | // Tx 692 | // 693 | 694 | // Get the generated Wavform samples for the last encode() call 695 | // 696 | // Call this method after calling encode() to get the generated waveform. The format of the samples pointed to by 697 | // the returned pointer is determined by the sampleFormatOut() method. 698 | // 699 | const void * txWaveform() const; 700 | 701 | // Get a list of the tones generated for the last encode() call 702 | // 703 | // Call this method after calling encode() to get a list of the tones participating in the generated waveform 704 | // 705 | const Tones txTones() const; 706 | 707 | // true if there is data pending to be transmitted 708 | bool txHasData() const; 709 | 710 | // Consume the amplitude data from the last generated waveform 711 | bool txTakeAmplitudeI16(AmplitudeI16 & dst); 712 | 713 | // The instance will allow Tx only with these protocols. They are determined upon construction or when calling the 714 | // prepare() method, base on the contents of the global GGWave::Protocols::tx() 715 | const TxProtocols & txProtocols() const; 716 | 717 | // 718 | // Rx 719 | // 720 | 721 | bool rxReceiving() const; 722 | bool rxAnalyzing() const; 723 | 724 | int rxSamplesNeeded() const; 725 | int rxFramesToRecord() const; 726 | int rxFramesLeftToRecord() const; 727 | int rxFramesToAnalyze() const; 728 | int rxFramesLeftToAnalyze() const; 729 | 730 | bool rxStopReceiving(); 731 | 732 | // The instance will attempt to decode only these protocols. 733 | // They are determined upon construction or when calling the prepare() method, base on the contents of the global 734 | // GGWave::Protocols::rx() 735 | // 736 | // Note: do not enable protocols that were not enabled upon preparation of the GGWave instance, or the decoding 737 | // will likely crash 738 | // 739 | RxProtocols & rxProtocols(); 740 | 741 | // Information about last received data 742 | int rxDataLength() const; 743 | const TxRxData & rxData() const; 744 | const RxProtocol & rxProtocol() const; 745 | const RxProtocolId & rxProtocolId() const; 746 | const Spectrum & rxSpectrum() const; 747 | const Amplitude & rxAmplitude() const; 748 | 749 | // Consume the received data 750 | // 751 | // Returns the data length in bytes 752 | // 753 | int rxTakeData(TxRxData & dst); 754 | 755 | // Consume the received spectrum / amplitude data 756 | // 757 | // Returns true if there was new data available 758 | // 759 | bool rxTakeSpectrum(Spectrum & dst); 760 | bool rxTakeAmplitude(Amplitude & dst); 761 | 762 | // 763 | // Utils 764 | // 765 | 766 | // Compute FFT of real values 767 | // 768 | // src - input real-valued data, size is N 769 | // dst - output complex-valued data, size is 2*N 770 | // 771 | // N must be == samplesPerFrame() 772 | // 773 | bool computeFFTR(const float * src, float * dst, int N); 774 | 775 | // Compute FFT of real values (static) 776 | // 777 | // src - input real-valued data, size is N 778 | // dst - output complex-valued data, size is 2*N 779 | // wi - work buffer, with size 2*N 780 | // wf - work buffer, with size 3 + sqrt(N/2) 781 | // 782 | // First time calling this function, make sure that wi[0] == 0 783 | // This will initialize some internal coefficients and store them in wi and wf for 784 | // future usage. 785 | // 786 | // If wi == nullptr - returns the needed size for wi 787 | // If wi != nullptr and wf == nullptr - returns the needed size for wf 788 | // If wi != nullptr and wf != nullptr - returns 1 on success, 0 on failure 789 | // 790 | static int computeFFTR(const float * src, float * dst, int N, int * wi, float * wf); 791 | 792 | // Filter the waveform 793 | // 794 | // filter - filter to use 795 | // waveform - input waveform, size is N 796 | // N - number of samples in the waveform 797 | // p0 - parameter 798 | // p1 - parameter 799 | // w - work buffer 800 | // 801 | // Filter is applied in-place. 802 | // First time calling this function, make sure that w[0] == 0 and w[1] == 0 803 | // This will initialize some internal coefficients and store them in w for 804 | // future usage. 805 | // 806 | // For GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS: 807 | // - p0 = cutoff frequency in Hz 808 | // - p1 = sample rate in Hz 809 | // 810 | // If w == nullptr - returns the needed size for w for the specified filter 811 | // If w != nullptr - returns 1 on success, 0 on failure 812 | // 813 | static int filter(ggwave_Filter filter, float * waveform, int N, float p0, float p1, float * w); 814 | 815 | // Resample audio waveforms from one sample rate to another using sinc interpolation 816 | class Resampler { 817 | public: 818 | // this controls the number of neighboring samples 819 | // which are used to interpolate the new samples. The 820 | // processing time is linearly related to this width 821 | static const int kWidth = 64; 822 | 823 | Resampler(); 824 | 825 | bool alloc(void * p, int & n); 826 | 827 | void reset(); 828 | 829 | int nSamplesTotal() const { return m_state.nSamplesTotal; } 830 | 831 | int resample( 832 | float factor, 833 | int nSamples, 834 | const float * samplesInp, 835 | float * samplesOut); 836 | 837 | private: 838 | float getData(int j) const; 839 | void newData(float data); 840 | void makeSinc(); 841 | double sinc(double x) const; 842 | 843 | static const int kDelaySize = 140; 844 | 845 | // this defines how finely the sinc function is sampled for storage in the table 846 | static const int kSamplesPerZeroCrossing = 32; 847 | 848 | ggvector m_sincTable; 849 | ggvector m_delayBuffer; 850 | ggvector m_edgeSamples; 851 | ggvector m_samplesInp; 852 | 853 | struct State { 854 | int nSamplesTotal = 0; 855 | int timeInt = 0; 856 | int timeLast = 0; 857 | double timeNow = 0.0; 858 | }; 859 | 860 | State m_state; 861 | }; 862 | 863 | private: 864 | bool alloc(void * p, int & n); 865 | 866 | void decode_fixed(); 867 | void decode_variable(); 868 | 869 | int maxFramesPerTx(const Protocols & protocols, bool excludeMT) const; 870 | int minBytesPerTx(const Protocols & protocols) const; 871 | int maxBytesPerTx(const Protocols & protocols) const; 872 | int maxTonesPerTx(const Protocols & protocols) const; 873 | int minFreqStart(const Protocols & protocols) const; 874 | 875 | double bitFreq(const Protocol & p, int bit) const; 876 | 877 | // Initialized via prepare() 878 | float m_sampleRateInp = -1.0f; 879 | float m_sampleRateOut = -1.0f; 880 | float m_sampleRate = -1.0f; 881 | int m_samplesPerFrame = -1; 882 | float m_isamplesPerFrame = -1.0f; 883 | int m_sampleSizeInp = -1; 884 | int m_sampleSizeOut = -1; 885 | SampleFormat m_sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED; 886 | SampleFormat m_sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED; 887 | 888 | float m_hzPerSample = -1.0f; 889 | float m_ihzPerSample = -1.0f; 890 | 891 | int m_freqDelta_bin = -1; 892 | float m_freqDelta_hz = -1.0f; 893 | 894 | int m_nBitsInMarker = -1; 895 | int m_nMarkerFrames = -1; 896 | int m_encodedDataOffset = -1; 897 | 898 | float m_soundMarkerThreshold = -1.0f; 899 | 900 | bool m_isFixedPayloadLength = false; 901 | int m_payloadLength = -1; 902 | 903 | bool m_isRxEnabled = false; 904 | bool m_isTxEnabled = false; 905 | bool m_needResampling = false; 906 | bool m_txOnlyTones = false; 907 | bool m_isDSSEnabled = false; 908 | 909 | // Common 910 | TxRxData m_dataEncoded; 911 | TxRxData m_workRSLength; // Reed-Solomon work buffers 912 | TxRxData m_workRSData; 913 | 914 | // Impl 915 | 916 | struct Rx { 917 | bool receiving = false; 918 | bool analyzing = false; 919 | 920 | int nMarkersSuccess = 0; 921 | int markerFreqStart = 0; 922 | int recvDuration_frames = 0; 923 | int minFreqStart = 0; 924 | 925 | int framesLeftToAnalyze = 0; 926 | int framesLeftToRecord = 0; 927 | int framesToAnalyze = 0; 928 | int framesToRecord = 0; 929 | int samplesNeeded = 0; 930 | 931 | ggvector fftOut; // complex 932 | ggvector fftWorkI; 933 | ggvector fftWorkF; 934 | 935 | bool hasNewRxData = false; 936 | bool hasNewSpectrum = false; 937 | bool hasNewAmplitude = false; 938 | 939 | Spectrum spectrum; 940 | Amplitude amplitude; 941 | Amplitude amplitudeResampled; 942 | TxRxData amplitudeTmp; 943 | 944 | int dataLength = 0; 945 | 946 | TxRxData data; 947 | RxProtocol protocol; 948 | RxProtocolId protocolId; 949 | RxProtocols protocols; 950 | 951 | // variable-length decoding 952 | int historyId = 0; 953 | 954 | Amplitude amplitudeAverage; 955 | AmplitudeArr amplitudeHistory; 956 | RecordedData amplitudeRecorded; 957 | 958 | // fixed-length decoding 959 | int historyIdFixed = 0; 960 | 961 | ggmatrix spectrumHistoryFixed; 962 | ggvector detectedBins; 963 | ggvector detectedTones; 964 | } m_rx; 965 | 966 | struct Tx { 967 | bool hasData = false; 968 | 969 | float sendVolume = 0.1f; 970 | 971 | int dataLength = 0; 972 | int lastAmplitudeSize = 0; 973 | 974 | ggvector dataBits; 975 | ggvector phaseOffsets; 976 | 977 | AmplitudeArr bit1Amplitude; 978 | AmplitudeArr bit0Amplitude; 979 | 980 | TxRxData data; 981 | TxProtocol protocol; 982 | TxProtocols protocols; 983 | 984 | Amplitude output; 985 | Amplitude outputResampled; 986 | TxRxData outputTmp; 987 | AmplitudeI16 outputI16; 988 | 989 | int nTones = 0; 990 | Tones tones; 991 | } m_tx; 992 | 993 | mutable Resampler m_resampler; 994 | 995 | void * m_heap = nullptr; 996 | int m_heapSize = 0; 997 | }; 998 | 999 | #endif 1000 | 1001 | #endif 1002 | --------------------------------------------------------------------------------