├── 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 | 
11 | 
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 | 
27 |
28 | 
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 | 
31 |
32 | 
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 | 
51 |
52 | 
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 |
16 |
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 |
24 |
25 | |
26 |
27 |
28 | |
29 |
30 |
31 | |
32 |
33 |
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 |
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 | 
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 |
--------------------------------------------------------------------------------