mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-06 16:47:59 +08:00
Initial commit
This commit is contained in:
111
.github/workflows/build.yml
vendored
Normal file
111
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
name: CI
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
ubuntu-18_04-gcc:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build: [Debug, Release]
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential xorg-dev libglu1-mesa-dev
|
||||
sudo apt-get install cmake;
|
||||
sudo apt-get install libsdl2-dev;
|
||||
|
||||
- name: Configure
|
||||
run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }}
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
|
||||
ubuntu-18_04-clang:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build: [Debug, Release]
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential xorg-dev libglu1-mesa-dev
|
||||
sudo apt-get install cmake;
|
||||
sudo apt-get install libsdl2-dev;
|
||||
|
||||
- name: Configure
|
||||
run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
|
||||
macOS-10_14-clang:
|
||||
runs-on: macOS-10.14
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build: [Debug, Release]
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install sdl2
|
||||
|
||||
- name: Configure
|
||||
run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }}
|
||||
|
||||
- name: Build
|
||||
run: make
|
||||
|
||||
Emscripten:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
build: [Debug, Release]
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Dependencies
|
||||
run: |
|
||||
wget -q https://github.com/emscripten-core/emsdk/archive/master.tar.gz
|
||||
tar -xvf master.tar.gz
|
||||
emsdk-master/emsdk update
|
||||
emsdk-master/emsdk install latest
|
||||
emsdk-master/emsdk activate latest
|
||||
|
||||
- name: Configure
|
||||
run: echo "tmp"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pushd emsdk-master
|
||||
source ./emsdk_env.sh
|
||||
popd
|
||||
emcmake cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }}
|
||||
make
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
build
|
||||
build-em
|
||||
compile_commands.json
|
||||
.clangd
|
||||
78
CMakeLists.txt
Normal file
78
CMakeLists.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
cmake_minimum_required (VERSION 3.0)
|
||||
project (ggwave)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS "on")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
set(GGWAVE_STANDALONE ON)
|
||||
include(cmake/GitVars.cmake)
|
||||
include(cmake/BuildTypes.cmake)
|
||||
else()
|
||||
set(GGWAVE_STANDALONE OFF)
|
||||
endif()
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
set(GGWAVE_SUPPORT_SDL2_DEFAULT ON)
|
||||
set(GGWAVE_LIBRARY_TYPE STATIC)
|
||||
else()
|
||||
set(GGWAVE_SUPPORT_SDL2_DEFAULT ON)
|
||||
if (BUILD_SHARED_LIBS)
|
||||
set(GGWAVE_LIBRARY_TYPE SHARED)
|
||||
else ()
|
||||
set(GGWAVE_LIBRARY_TYPE STATIC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# options
|
||||
|
||||
option(USE_FINDSDL2 "wave-share: use the FindSDL2.cmake script" OFF)
|
||||
|
||||
option(GGWAVE_ALL_WARNINGS "wave-share: enable all compiler warnings" ON)
|
||||
option(GGWAVE_ALL_WARNINGS_3RD_PARTY "wave-share: enable all compiler warnings in 3rd party libs" ON)
|
||||
|
||||
option(GGWAVE_SANITIZE_THREAD "wave-share: enable thread sanitizer" OFF)
|
||||
option(GGWAVE_SANITIZE_ADDRESS "wave-share: enable address sanitizer" OFF)
|
||||
option(GGWAVE_SANITIZE_UNDEFINED "wave-share: enable undefined sanitizer" OFF)
|
||||
|
||||
option(GGWAVE_SUPPORT_SDL2 "wave-share: support for libSDL2" ${GGWAVE_SUPPORT_SDL2_DEFAULT})
|
||||
|
||||
option(GGWAVE_BUILD_EXAMPLES "wave-share: build examples" ${GGWAVE_STANDALONE})
|
||||
|
||||
# sanitizers
|
||||
|
||||
if (GGWAVE_SANITIZE_THREAD)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||
endif()
|
||||
|
||||
if (GGWAVE_SANITIZE_ADDRESS)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
||||
endif()
|
||||
|
||||
if (GGWAVE_SANITIZE_UNDEFINED)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
|
||||
endif()
|
||||
|
||||
# dependencies
|
||||
|
||||
# main
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (GGWAVE_ALL_WARNINGS)
|
||||
if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
else()
|
||||
# todo : windows
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
if (GGWAVE_STANDALONE AND GGWAVE_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Georgi Gerganov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
78
README.md
Normal file
78
README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# ggwave
|
||||
|
||||
Tiny data-over-sound library.
|
||||
|
||||
|
||||
## Details
|
||||
|
||||
This is a simple C++ library that allows communication of small amounts of data between air-gapped devices using sound. It implements a simple transmission protocol that can be easily integrated in various projects. The bandwidth rate is between 8-16 bytes/sec depending on the protocol parameters. Error correction codes (ECC) are used to improve demodulation robustness.
|
||||
|
||||
This library is used only to generate and analyze the RAW waveforms that are played and captured from your audio devices (speakers, microphones, etc.). You are free to use any audio backend (e.g. PulseAudio, ALSA, etc.) as long as you provide callbacks for queuing and dequeuing samples.
|
||||
|
||||
### Modulation (Tx)
|
||||
|
||||
The current approach uses a multi-frequency [Frequency-Shift Keying (FSK)](https://en.wikipedia.org/wiki/Frequency-shift_keying) modulation scheme. The data to be transmitted is first split into 4-bit chunks. At each moment of time, 3 bytes are transmitted using 6 tones - one tone for each 4-bit chunk. The 6 tones are emitted in a 4.5kHz range divided in 96 equally-spaced frequencies:
|
||||
|
||||
| Freq, [Hz] | Value, [bits] | Freq, [Hz] | Value, [bits] | ... | Freq, [Hz] | Value, [bits] |
|
||||
| ------------ | --------------- | ------------ | --------------- | --- | ------------ | --------------- |
|
||||
| `F0 + 00*dF` | Chunk 0: `0000` | `F0 + 16*dF` | Chunk 1: `0000` | ... | `F0 + 80*dF` | Chunk 5: `0000` |
|
||||
| `F0 + 01*dF` | Chunk 0: `0001` | `F0 + 17*dF` | Chunk 1: `0001` | ... | `F0 + 81*dF` | Chunk 5: `0001` |
|
||||
| `F0 + 02*dF` | Chunk 0: `0010` | `F0 + 18*dF` | Chunk 1: `0010` | ... | `F0 + 82*dF` | Chunk 5: `0010` |
|
||||
| ... | ... | ... | ... | ... | ... | ... |
|
||||
| `F0 + 14*dF` | Chunk 0: `1110` | `F0 + 30*dF` | Chunk 1: `1110` | ... | `F0 + 94*dF` | Chunk 5: `1110` |
|
||||
| `F0 + 15*dF` | Chunk 0: `1111` | `F0 + 31*dF` | Chunk 1: `1111` | ... | `F0 + 95*dF` | Chunk 5: `1111` |
|
||||
|
||||
For all protocols: `dF = 46.875 Hz`. For non-ultrasonic protocols: `F0 = 1875.000 Hz`. For ultrasonic protocols: `F0 = 15000.000 Hz`.
|
||||
|
||||
The original data is encoded using [Reed-Solomon error codes](https://github.com/ggerganov/ggwave/blob/master/src/reed-solomon). The number of ECC bytes is determined based on the length of the original data. The encoded data is the one being transmitted.
|
||||
|
||||
### Demodulation (Rx)
|
||||
|
||||
Beginning and ending of the transmission are marked with special sound markers. The receiver listens for these markers and records the in-between sound data. The recorded data is then Fourier transformed to obtain a frequency spectrum. The detected frequencies are decoded back to binary data in the same way they were encoded.
|
||||
|
||||
Reed-Solomon decoding is finally performed to obtain the original data.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
The [examples](https://github.com/ggerganov/ggwave/blob/master/examples/) folder contains several sample applications of the library:
|
||||
|
||||
- [ggwave-cli](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-cli) - a command line tool for sending/receiving data through sound
|
||||
- [ggwave-wasm](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-wasm) - a WebAssembly module for web applications
|
||||
|
||||
Other projects using **ggwave** or one of its prototypes:
|
||||
|
||||
- [wave-gui](https://github.com/ggerganov/wave-gui) - a GUI for exploring different modulation protocols
|
||||
- [wave-share](https://github.com/ggerganov/wave-share) - WebRTC file sharing with sound signaling
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
### Linux and Mac:
|
||||
|
||||
```bash
|
||||
# build
|
||||
git clone https://github.com/ggerganov/ggwave
|
||||
cd ggwave && mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
|
||||
# running
|
||||
./bin/wave-share-cli
|
||||
```
|
||||
|
||||
### Emscripten:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ggerganov/ggwave
|
||||
cd ggwave
|
||||
mkdir build && cd build
|
||||
emcmake cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
## Todo
|
||||
|
||||
- [ ] Improve library interface
|
||||
- [ ] Support for non-float32 input and non-int16 output
|
||||
- [ ] Mobile app examples
|
||||
54
cmake/BuildTypes.cmake
Normal file
54
cmake/BuildTypes.cmake
Normal file
@@ -0,0 +1,54 @@
|
||||
# Add new build types
|
||||
|
||||
# ReleaseGG - Release with enabled asserts
|
||||
|
||||
SET(CMAKE_CXX_FLAGS_RELEASEGG
|
||||
"-O3"
|
||||
CACHE STRING "Flags used by the c++ compiler during release builds with enabled asserts."
|
||||
FORCE )
|
||||
SET(CMAKE_C_FLAGS_RELEASEGG
|
||||
"-O3"
|
||||
CACHE STRING "Flags used by the compiler during release builds with enabled asserts."
|
||||
FORCE )
|
||||
SET(CMAKE_EXE_LINKER_FLAGS_RELEASEGG
|
||||
""
|
||||
CACHE STRING "Flags used for linking binaries during release builds with enabled asserts."
|
||||
FORCE )
|
||||
SET(CMAKE_SHARED_LINKER_FLAGS_RELEASEGG
|
||||
""
|
||||
CACHE STRING "Flags used by the shared libraries linker during release builds with enabled asserts."
|
||||
FORCE )
|
||||
MARK_AS_ADVANCED(
|
||||
CMAKE_CXX_FLAGS_RELEASEGG
|
||||
CMAKE_C_FLAGS_RELEASEGG
|
||||
CMAKE_EXE_LINKER_FLAGS_RELEASEGG
|
||||
CMAKE_SHARED_LINKER_FLAGS_RELEASEGG )
|
||||
|
||||
# RelWithDebInfoGG - RelWithDebInfo with enabled asserts
|
||||
|
||||
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFOGG
|
||||
"-O2 -g"
|
||||
CACHE STRING "Flags used by the c++ compiler during release builds with debug symbols and enabled asserts."
|
||||
FORCE )
|
||||
SET(CMAKE_C_FLAGS_RELWITHDEBINFOGG
|
||||
"-O2 -g"
|
||||
CACHE STRING "Flags used by the compiler during release builds with debug symbols and enabled asserts."
|
||||
FORCE )
|
||||
SET(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFOGG
|
||||
""
|
||||
CACHE STRING "Flags used for linking binaries during release builds with debug symbols and enabled asserts."
|
||||
FORCE )
|
||||
SET(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFOGG
|
||||
""
|
||||
CACHE STRING "Flags used by the shared libraries linker during release builds with debug symbols and enabled asserts."
|
||||
FORCE )
|
||||
MARK_AS_ADVANCED(
|
||||
CMAKE_CXX_FLAGS_RELWITHDEBINFOGG
|
||||
CMAKE_C_FLAGS_RELWITHDEBINFOGG
|
||||
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFOGG
|
||||
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFOGG )
|
||||
|
||||
if (NOT XCODE AND NOT MSVC AND NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "ReleaseGG" "RelWithDebInfoGG")
|
||||
endif()
|
||||
22
cmake/GitVars.cmake
Normal file
22
cmake/GitVars.cmake
Normal file
@@ -0,0 +1,22 @@
|
||||
find_package(Git)
|
||||
|
||||
# the commit's SHA1
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}" describe --match=NeVeRmAtCh --always --abbrev=8
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE GIT_SHA1
|
||||
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# the date of the commit
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}" log -1 --format=%ad --date=local
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE GIT_DATE
|
||||
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
# the subject of the commit
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}" log -1 --format=%s
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE GIT_COMMIT_SUBJECT
|
||||
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
203
cmake/sdl2/FindSDL2.cmake
Normal file
203
cmake/sdl2/FindSDL2.cmake
Normal file
@@ -0,0 +1,203 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#.rst:
|
||||
# FindSDL2
|
||||
# -------
|
||||
#
|
||||
# Locate SDL2 library
|
||||
#
|
||||
# This module defines
|
||||
#
|
||||
# ::
|
||||
#
|
||||
# SDL2_LIBRARY, the name of the library to link against
|
||||
# SDL2_FOUND, if false, do not try to link to SDL
|
||||
# SDL2_INCLUDE_DIR, where to find SDL.h
|
||||
# SDL2_VERSION_STRING, human-readable string containing the version of SDL
|
||||
#
|
||||
#
|
||||
#
|
||||
# This module responds to the flag:
|
||||
#
|
||||
# ::
|
||||
#
|
||||
# SDL2_BUILDING_LIBRARY
|
||||
# If this is defined, then no SDL2_main will be linked in because
|
||||
# only applications need main().
|
||||
# Otherwise, it is assumed you are building an application and this
|
||||
# module will attempt to locate and set the proper link flags
|
||||
# as part of the returned SDL2_LIBRARY variable.
|
||||
#
|
||||
#
|
||||
#
|
||||
# Don't forget to include SDLmain.h and SDLmain.m your project for the
|
||||
# OS X framework based version. (Other versions link to -lSDLmain which
|
||||
# this module will try to find on your behalf.) Also for OS X, this
|
||||
# module will automatically add the -framework Cocoa on your behalf.
|
||||
#
|
||||
#
|
||||
#
|
||||
# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your
|
||||
# configuration and no SDL2_LIBRARY, it means CMake did not find your SDL
|
||||
# library (SDL.dll, libsdl.so, SDL.framework, etc). Set
|
||||
# SDL2_LIBRARY_TEMP to point to your SDL library, and configure again.
|
||||
# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this
|
||||
# value as appropriate. These values are used to generate the final
|
||||
# SDL2_LIBRARY variable, but when these values are unset, SDL2_LIBRARY
|
||||
# does not get created.
|
||||
#
|
||||
#
|
||||
#
|
||||
# $SDLDIR is an environment variable that would correspond to the
|
||||
# ./configure --prefix=$SDLDIR used in building SDL. l.e.galup 9-20-02
|
||||
#
|
||||
# Modified by Eric Wing. Added code to assist with automated building
|
||||
# by using environmental variables and providing a more
|
||||
# controlled/consistent search behavior. Added new modifications to
|
||||
# recognize OS X frameworks and additional Unix paths (FreeBSD, etc).
|
||||
# Also corrected the header search path to follow "proper" SDL
|
||||
# guidelines. Added a search for SDLmain which is needed by some
|
||||
# platforms. Added a search for threads which is needed by some
|
||||
# platforms. Added needed compile switches for MinGW.
|
||||
#
|
||||
# On OSX, this will prefer the Framework version (if found) over others.
|
||||
# People will have to manually change the cache values of SDL2_LIBRARY to
|
||||
# override this selection or set the CMake environment
|
||||
# CMAKE_INCLUDE_PATH to modify the search paths.
|
||||
#
|
||||
# Note that the header path has changed from SDL/SDL.h to just SDL.h
|
||||
# This needed to change because "proper" SDL convention is #include
|
||||
# "SDL.h", not <SDL/SDL.h>. This is done for portability reasons
|
||||
# because not all systems place things in SDL/ (see FreeBSD).
|
||||
|
||||
if(NOT SDL2_DIR)
|
||||
set(SDL2_DIR "" CACHE PATH "SDL2 directory")
|
||||
endif()
|
||||
|
||||
find_path(SDL2_INCLUDE_DIR SDL_scancode.h
|
||||
HINTS
|
||||
ENV SDLDIR
|
||||
${SDL2_DIR}
|
||||
PATH_SUFFIXES SDL2
|
||||
# path suffixes to search inside ENV{SDLDIR}
|
||||
include/SDL2 include
|
||||
)
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(VC_LIB_PATH_SUFFIX lib/x64)
|
||||
else()
|
||||
set(VC_LIB_PATH_SUFFIX lib/x86)
|
||||
endif()
|
||||
|
||||
# SDL-1.1 is the name used by FreeBSD ports...
|
||||
# don't confuse it for the version number.
|
||||
find_library(SDL2_LIBRARY_TEMP
|
||||
NAMES SDL2
|
||||
HINTS
|
||||
ENV SDLDIR
|
||||
${SDL2_DIR}
|
||||
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
|
||||
)
|
||||
|
||||
# Hide this cache variable from the user, it's an internal implementation
|
||||
# detail. The documented library variable for the user is SDL2_LIBRARY
|
||||
# which is derived from SDL2_LIBRARY_TEMP further below.
|
||||
set_property(CACHE SDL2_LIBRARY_TEMP PROPERTY TYPE INTERNAL)
|
||||
|
||||
if(NOT SDL2_BUILDING_LIBRARY)
|
||||
if(NOT SDL2_INCLUDE_DIR MATCHES ".framework")
|
||||
# Non-OS X framework versions expect you to also dynamically link to
|
||||
# SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms
|
||||
# seem to provide SDLmain for compatibility even though they don't
|
||||
# necessarily need it.
|
||||
find_library(SDL2MAIN_LIBRARY
|
||||
NAMES SDL2main
|
||||
HINTS
|
||||
ENV SDLDIR
|
||||
${SDL2_DIR}
|
||||
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
|
||||
PATHS
|
||||
/sw
|
||||
/opt/local
|
||||
/opt/csw
|
||||
/opt
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# SDL may require threads on your system.
|
||||
# The Apple build may not need an explicit flag because one of the
|
||||
# frameworks may already provide it.
|
||||
# But for non-OSX systems, I will use the CMake Threads package.
|
||||
if(NOT APPLE)
|
||||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
# MinGW needs an additional link flag, -mwindows
|
||||
# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -mwindows
|
||||
if(MINGW)
|
||||
set(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "link flags for MinGW")
|
||||
endif()
|
||||
|
||||
if(SDL2_LIBRARY_TEMP)
|
||||
# For SDLmain
|
||||
if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY)
|
||||
list(FIND SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX)
|
||||
if(_SDL2_MAIN_INDEX EQUAL -1)
|
||||
set(SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARY_TEMP})
|
||||
endif()
|
||||
unset(_SDL2_MAIN_INDEX)
|
||||
endif()
|
||||
|
||||
# For OS X, SDL uses Cocoa as a backend so it must link to Cocoa.
|
||||
# CMake doesn't display the -framework Cocoa string in the UI even
|
||||
# though it actually is there if I modify a pre-used variable.
|
||||
# I think it has something to do with the CACHE STRING.
|
||||
# So I use a temporary variable until the end so I can set the
|
||||
# "real" variable in one-shot.
|
||||
if(APPLE)
|
||||
set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa")
|
||||
endif()
|
||||
|
||||
# For threads, as mentioned Apple doesn't need this.
|
||||
# In fact, there seems to be a problem if I used the Threads package
|
||||
# and try using this line, so I'm just skipping it entirely for OS X.
|
||||
if(NOT APPLE)
|
||||
set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
# For MinGW library
|
||||
if(MINGW)
|
||||
set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})
|
||||
endif()
|
||||
|
||||
# Set the final string here so the GUI reflects the final state.
|
||||
set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found")
|
||||
endif()
|
||||
|
||||
if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL2_version.h")
|
||||
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+[0-9]+$")
|
||||
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+[0-9]+$")
|
||||
file(STRINGS "${SDL2_INCLUDE_DIR}/SDL2_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+[0-9]+$")
|
||||
string(REGEX REPLACE "^#define[ \t]+SDL2_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}")
|
||||
string(REGEX REPLACE "^#define[ \t]+SDL2_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}")
|
||||
string(REGEX REPLACE "^#define[ \t]+SDL2_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}")
|
||||
set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH})
|
||||
unset(SDL2_VERSION_MAJOR_LINE)
|
||||
unset(SDL2_VERSION_MINOR_LINE)
|
||||
unset(SDL2_VERSION_PATCH_LINE)
|
||||
unset(SDL2_VERSION_MAJOR)
|
||||
unset(SDL2_VERSION_MINOR)
|
||||
unset(SDL2_VERSION_PATCH)
|
||||
endif()
|
||||
|
||||
set(SDL2_LIBRARIES ${SDL2_LIBRARY})
|
||||
set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})
|
||||
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL
|
||||
REQUIRED_VARS SDL2_LIBRARIES SDL2_INCLUDE_DIRS
|
||||
VERSION_VAR SDL2_VERSION_STRING)
|
||||
|
||||
mark_as_advanced(SDL2_LIBRARY SDL2_INCLUDE_DIR)
|
||||
|
||||
61
examples/CMakeLists.txt
Normal file
61
examples/CMakeLists.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_library(ggwave-common ${GGWAVE_LIBRARY_TYPE}
|
||||
ggwave-common.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ggwave-common PRIVATE
|
||||
)
|
||||
|
||||
if (GGWAVE_SUPPORT_SDL2)
|
||||
# SDL2
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
set (CMAKE_CXX_FLAGS "-s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=2 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0 -s 'EXTRA_EXPORTED_RUNTIME_METHODS=[\"writeArrayToMemory\"]'")
|
||||
endif()
|
||||
|
||||
add_library(ggwave-common-sdl2 ${GGWAVE_LIBRARY_TYPE}
|
||||
ggwave-common-sdl2.cpp
|
||||
)
|
||||
|
||||
if (NOT EMSCRIPTEN)
|
||||
find_package(SDL2)
|
||||
|
||||
if (NOT USE_FINDSDL2 AND NOT SDL2_FOUND)
|
||||
message(WARNING "Unable to find SDL2 library. It is either not installed or CMake cannot find it."
|
||||
" In the latter case, setting the USE_FINDSDL2 variable might help:\n"
|
||||
" $ cmake -D USE_FINDSDL2 .."
|
||||
)
|
||||
|
||||
message(FATAL_ERROR "Aborting")
|
||||
endif()
|
||||
|
||||
string(STRIP "${SDL2_LIBRARIES}" SDL2_LIBRARIES)
|
||||
endif()
|
||||
|
||||
message(STATUS "SDL2_INCLUDE_DIRS = ${SDL2_INCLUDE_DIRS}")
|
||||
message(STATUS "SDL2_LIBRARIES = ${SDL2_LIBRARIES}")
|
||||
|
||||
# ggwave-common-sdl2
|
||||
|
||||
target_include_directories(ggwave-common-sdl2 PUBLIC
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(ggwave-common-sdl2 PRIVATE
|
||||
ggwave
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (GGWAVE_SUPPORT_SDL2)
|
||||
if (EMSCRIPTEN)
|
||||
# emscripten sdl2 examples
|
||||
|
||||
add_subdirectory(ggwave-wasm)
|
||||
else()
|
||||
# non-emscripten sdl2 examples
|
||||
|
||||
add_subdirectory(ggwave-cli)
|
||||
endif()
|
||||
endif()
|
||||
13
examples/ggwave-cli/CMakeLists.txt
Normal file
13
examples/ggwave-cli/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
add_executable(ggwave-cli main.cpp)
|
||||
|
||||
target_include_directories(ggwave-cli PRIVATE
|
||||
..
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(ggwave-cli PRIVATE
|
||||
ggwave
|
||||
ggwave-common
|
||||
ggwave-common-sdl2
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
197
examples/ggwave-cli/main.cpp
Normal file
197
examples/ggwave-cli/main.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*! \file main.cpp
|
||||
* \brief Send/Receive data through sound in the terminal
|
||||
* \author Georgi Gerganov
|
||||
*/
|
||||
|
||||
#include "ggwave/ggwave.h"
|
||||
|
||||
#include "ggwave-common.h"
|
||||
#include "ggwave-common-sdl2.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_audio.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
static char *g_defaultCaptureDeviceName = nullptr;
|
||||
|
||||
static int g_captureId = -1;
|
||||
static int g_playbackId = -1;
|
||||
|
||||
static bool g_isInitialized = false;
|
||||
|
||||
static SDL_AudioDeviceID g_devIdIn = 0;
|
||||
static SDL_AudioDeviceID g_devIdOut = 0;
|
||||
|
||||
static GGWave *g_ggWave = nullptr;
|
||||
|
||||
// main loop
|
||||
void update() {
|
||||
if (g_isInitialized == false) return;
|
||||
|
||||
SDL_Event e;
|
||||
SDL_bool shouldTerminate = SDL_FALSE;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
if (e.type == SDL_QUIT) {
|
||||
shouldTerminate = SDL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static GGWave::CBQueueAudio cbQueueAudio = [&](const void * data, uint32_t nBytes) {
|
||||
SDL_QueueAudio(g_devIdOut, data, nBytes);
|
||||
};
|
||||
|
||||
static GGWave::CBDequeueAudio CBDequeueAudio = [&](void * data, uint32_t nMaxBytes) {
|
||||
return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes);
|
||||
};
|
||||
|
||||
if (g_ggWave->getHasData() == false) {
|
||||
SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE);
|
||||
|
||||
static auto tLastNoData = std::chrono::high_resolution_clock::now();
|
||||
auto tNow = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) {
|
||||
SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE);
|
||||
if (::getTime_ms(tLastNoData, tNow) > 500.0f) {
|
||||
g_ggWave->receive(CBDequeueAudio);
|
||||
if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) {
|
||||
SDL_ClearQueuedAudio(g_devIdIn);
|
||||
}
|
||||
} else {
|
||||
SDL_ClearQueuedAudio(g_devIdIn);
|
||||
}
|
||||
} else {
|
||||
tLastNoData = tNow;
|
||||
//SDL_ClearQueuedAudio(g_devIdIn);
|
||||
//SDL_Delay(10);
|
||||
}
|
||||
} else {
|
||||
SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE);
|
||||
SDL_PauseAudioDevice(g_devIdIn, SDL_TRUE);
|
||||
|
||||
g_ggWave->send(cbQueueAudio);
|
||||
}
|
||||
|
||||
if (shouldTerminate) {
|
||||
SDL_PauseAudioDevice(g_devIdIn, 1);
|
||||
SDL_CloseAudioDevice(g_devIdIn);
|
||||
SDL_PauseAudioDevice(g_devIdOut, 1);
|
||||
SDL_CloseAudioDevice(g_devIdOut);
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
printf("Usage: %s [-cN] [-pN] [-tN]\n", argv[0]);
|
||||
printf(" -cN - select capture device N\n");
|
||||
printf(" -pN - select playback device N\n");
|
||||
printf(" -tN - transmission protocol:\n");
|
||||
printf(" -t0 : Normal\n");
|
||||
printf(" -t1 : Fast (default)\n");
|
||||
printf(" -t2 : Fastest\n");
|
||||
printf(" -t3 : Ultrasonic\n");
|
||||
printf("\n");
|
||||
|
||||
g_defaultCaptureDeviceName = nullptr;
|
||||
|
||||
auto argm = parseCmdArguments(argc, argv);
|
||||
g_captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]);
|
||||
g_playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]);
|
||||
int txProtocol = argm["t"].empty() ? 1 : std::stoi(argm["t"]);
|
||||
|
||||
initSDL2ForGGWave(
|
||||
g_isInitialized,
|
||||
g_playbackId,
|
||||
g_devIdIn,
|
||||
g_captureId,
|
||||
g_devIdOut,
|
||||
g_ggWave);
|
||||
|
||||
g_ggWave->setTxMode(GGWave::TxMode::VariableLength);
|
||||
|
||||
printf("Selecting Tx protocol %d\n", txProtocol);
|
||||
switch (txProtocol) {
|
||||
case 0:
|
||||
{
|
||||
printf("Using 'Normal' Tx Protocol\n");
|
||||
g_ggWave->setParameters(1, 40, 9, 3, 50);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
printf("Using 'Fast' Tx Protocol\n");
|
||||
g_ggWave->setParameters(1, 40, 6, 3, 50);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{
|
||||
printf("Using 'Fastest' Tx Protocol\n");
|
||||
g_ggWave->setParameters(1, 40, 3, 3, 50);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
printf("Using 'Ultrasonic' Tx Protocol\n");
|
||||
g_ggWave->setParameters(1, 320, 9, 3, 50);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
printf("Using 'Fast' Tx Protocol\n");
|
||||
g_ggWave->setParameters(1, 40, 6, 3, 50);
|
||||
}
|
||||
};
|
||||
printf("\n");
|
||||
|
||||
g_ggWave->init(0, "");
|
||||
|
||||
std::mutex mutex;
|
||||
std::thread inputThread([&mutex]() {
|
||||
std::string inputOld = "";
|
||||
while (true) {
|
||||
std::string input;
|
||||
std::cout << "Enter text: ";
|
||||
getline(std::cin, input);
|
||||
if (input.empty()) {
|
||||
std::cout << "Re-sending ... " << std::endl;
|
||||
input = inputOld;
|
||||
} else {
|
||||
std::cout << "Sending ... " << std::endl;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
g_ggWave->init(input.size(), input.data());
|
||||
}
|
||||
inputOld = input;
|
||||
}
|
||||
});
|
||||
|
||||
while (true) {
|
||||
SDL_Delay(1);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
inputThread.join();
|
||||
|
||||
delete g_ggWave;
|
||||
|
||||
SDL_PauseAudioDevice(g_devIdIn, 1);
|
||||
SDL_CloseAudioDevice(g_devIdIn);
|
||||
SDL_PauseAudioDevice(g_devIdOut, 1);
|
||||
SDL_CloseAudioDevice(g_devIdOut);
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
152
examples/ggwave-common-sdl2.cpp
Normal file
152
examples/ggwave-common-sdl2.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "ggwave-common-sdl2.h"
|
||||
|
||||
#include "ggwave/ggwave.h"
|
||||
|
||||
constexpr double kBaseSampleRate = 48000.0;
|
||||
|
||||
bool initSDL2ForGGWave(
|
||||
bool & isInitialized,
|
||||
const int playbackId,
|
||||
SDL_AudioDeviceID & devIdIn,
|
||||
const int captureId,
|
||||
SDL_AudioDeviceID & devIdOut,
|
||||
GGWave *& ggWave,
|
||||
const char * defaultCaptureDeviceName) {
|
||||
if (isInitialized) return 0;
|
||||
|
||||
printf("Initializing ...\n");
|
||||
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
|
||||
|
||||
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
|
||||
return (1);
|
||||
}
|
||||
|
||||
SDL_SetHintWithPriority(SDL_HINT_AUDIO_RESAMPLING_MODE, "medium", SDL_HINT_OVERRIDE);
|
||||
|
||||
{
|
||||
int nDevices = SDL_GetNumAudioDevices(SDL_FALSE);
|
||||
printf("Found %d playback devices:\n", nDevices);
|
||||
for (int i = 0; i < nDevices; i++) {
|
||||
printf(" - Playback device #%d: '%s'\n", i, SDL_GetAudioDeviceName(i, SDL_FALSE));
|
||||
}
|
||||
}
|
||||
{
|
||||
int nDevices = SDL_GetNumAudioDevices(SDL_TRUE);
|
||||
printf("Found %d capture devices:\n", nDevices);
|
||||
for (int i = 0; i < nDevices; i++) {
|
||||
printf(" - Capture device #%d: '%s'\n", i, SDL_GetAudioDeviceName(i, SDL_TRUE));
|
||||
}
|
||||
}
|
||||
|
||||
SDL_AudioSpec playbackSpec;
|
||||
SDL_zero(playbackSpec);
|
||||
|
||||
playbackSpec.freq = ::kBaseSampleRate;
|
||||
playbackSpec.format = AUDIO_S16SYS;
|
||||
playbackSpec.channels = 1;
|
||||
playbackSpec.samples = 16*1024;
|
||||
playbackSpec.callback = NULL;
|
||||
|
||||
SDL_AudioSpec obtainedSpecIn;
|
||||
SDL_AudioSpec obtainedSpecOut;
|
||||
|
||||
int sampleSizeBytesIn = 4;
|
||||
int sampleSizeBytesOut = 2;
|
||||
|
||||
SDL_zero(obtainedSpecIn);
|
||||
SDL_zero(obtainedSpecOut);
|
||||
|
||||
if (playbackId >= 0) {
|
||||
printf("Attempt to open playback device %d : '%s' ...\n", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE));
|
||||
devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &obtainedSpecOut, 0);
|
||||
} else {
|
||||
printf("Attempt to open default playback device ...\n");
|
||||
devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &obtainedSpecOut, 0);
|
||||
}
|
||||
|
||||
if (!devIdOut) {
|
||||
printf("Couldn't open an audio device for playback: %s!\n", SDL_GetError());
|
||||
devIdOut = 0;
|
||||
} else {
|
||||
printf("Obtained spec for output device (SDL Id = %d):\n", devIdOut);
|
||||
printf(" - Sample rate: %d (required: %d)\n", obtainedSpecOut.freq, playbackSpec.freq);
|
||||
printf(" - Format: %d (required: %d)\n", obtainedSpecOut.format, playbackSpec.format);
|
||||
printf(" - Channels: %d (required: %d)\n", obtainedSpecOut.channels, playbackSpec.channels);
|
||||
printf(" - Samples per frame: %d (required: %d)\n", obtainedSpecOut.samples, playbackSpec.samples);
|
||||
|
||||
if (obtainedSpecOut.format != playbackSpec.format ||
|
||||
obtainedSpecOut.channels != playbackSpec.channels ||
|
||||
obtainedSpecOut.samples != playbackSpec.samples) {
|
||||
SDL_CloseAudio();
|
||||
fprintf(stderr, "Failed to initialize playback SDL_OpenAudio!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (obtainedSpecOut.format) {
|
||||
case AUDIO_U8:
|
||||
case AUDIO_S8:
|
||||
sampleSizeBytesOut = 1;
|
||||
break;
|
||||
case AUDIO_U16SYS:
|
||||
case AUDIO_S16SYS:
|
||||
sampleSizeBytesOut = 2;
|
||||
break;
|
||||
case AUDIO_S32SYS:
|
||||
case AUDIO_F32SYS:
|
||||
sampleSizeBytesOut = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_AudioSpec captureSpec;
|
||||
captureSpec = obtainedSpecOut;
|
||||
captureSpec.freq = ::kBaseSampleRate;
|
||||
captureSpec.format = AUDIO_F32SYS;
|
||||
captureSpec.samples = 4096;
|
||||
|
||||
if (captureId >= 0) {
|
||||
printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_FALSE));
|
||||
devIdIn = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &obtainedSpecIn, 0);
|
||||
} else {
|
||||
printf("Attempt to open default capture device ...\n");
|
||||
devIdIn = SDL_OpenAudioDevice(defaultCaptureDeviceName, SDL_TRUE, &captureSpec, &obtainedSpecIn, 0);
|
||||
}
|
||||
if (!devIdIn) {
|
||||
printf("Couldn't open an audio device for capture: %s!\n", SDL_GetError());
|
||||
devIdIn = 0;
|
||||
} else {
|
||||
printf("Obtained spec for input device (SDL Id = %d):\n", devIdIn);
|
||||
printf(" - Sample rate: %d\n", obtainedSpecIn.freq);
|
||||
printf(" - Format: %d (required: %d)\n", obtainedSpecIn.format, captureSpec.format);
|
||||
printf(" - Channels: %d (required: %d)\n", obtainedSpecIn.channels, captureSpec.channels);
|
||||
printf(" - Samples per frame: %d\n", obtainedSpecIn.samples);
|
||||
}
|
||||
|
||||
switch (obtainedSpecIn.format) {
|
||||
case AUDIO_U8:
|
||||
case AUDIO_S8:
|
||||
sampleSizeBytesIn = 1;
|
||||
break;
|
||||
case AUDIO_U16SYS:
|
||||
case AUDIO_S16SYS:
|
||||
sampleSizeBytesIn = 2;
|
||||
break;
|
||||
case AUDIO_S32SYS:
|
||||
case AUDIO_F32SYS:
|
||||
sampleSizeBytesIn = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
ggWave = new GGWave(
|
||||
obtainedSpecIn.freq,
|
||||
obtainedSpecOut.freq,
|
||||
1024,
|
||||
sampleSizeBytesIn,
|
||||
sampleSizeBytesOut);
|
||||
|
||||
isInitialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
14
examples/ggwave-common-sdl2.h
Normal file
14
examples/ggwave-common-sdl2.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
class GGWave;
|
||||
|
||||
bool initSDL2ForGGWave(
|
||||
bool & isInitialized,
|
||||
const int playbackId,
|
||||
SDL_AudioDeviceID & devIdIn,
|
||||
const int captureId,
|
||||
SDL_AudioDeviceID & devIdOut,
|
||||
GGWave *& ggWave,
|
||||
const char * defaultCaptureDeviceName = nullptr);
|
||||
17
examples/ggwave-common.cpp
Normal file
17
examples/ggwave-common.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "ggwave-common.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
std::map<std::string, std::string> parseCmdArguments(int argc, char ** argv) {
|
||||
int last = argc;
|
||||
std::map<std::string, std::string> res;
|
||||
for (int i = 1; i < last; ++i) {
|
||||
if (argv[i][0] == '-') {
|
||||
if (strlen(argv[i]) > 1) {
|
||||
res[std::string(1, argv[i][1])] = strlen(argv[i]) > 2 ? argv[i] + 2 : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
12
examples/ggwave-common.h
Normal file
12
examples/ggwave-common.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
template <class T>
|
||||
float getTime_ms(const T & tStart, const T & tEnd) {
|
||||
return ((float)(std::chrono::duration_cast<std::chrono::microseconds>(tEnd - tStart).count()))/1000.0;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> parseCmdArguments(int argc, char ** argv);
|
||||
18
examples/ggwave-wasm/CMakeLists.txt
Normal file
18
examples/ggwave-wasm/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
set(TARGET ggwave-wasm)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp.h.tmpl ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY)
|
||||
|
||||
add_executable(${TARGET}
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${TARGET} PRIVATE
|
||||
..
|
||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET} PRIVATE
|
||||
ggwave
|
||||
ggwave-common
|
||||
ggwave-common-sdl2
|
||||
)
|
||||
1
examples/ggwave-wasm/build_timestamp.h.tmpl
Normal file
1
examples/ggwave-wasm/build_timestamp.h.tmpl
Normal file
@@ -0,0 +1 @@
|
||||
static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)";
|
||||
181
examples/ggwave-wasm/main.cpp
Normal file
181
examples/ggwave-wasm/main.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*! \file main.cpp
|
||||
* \brief Send/Receive - WebAssembly port
|
||||
* \author Georgi Gerganov
|
||||
*/
|
||||
|
||||
#include "ggwave/ggwave.h"
|
||||
|
||||
#include "ggwave-common.h"
|
||||
#include "ggwave-common-sdl2.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_audio.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
#include "build_timestamp.h"
|
||||
#include "emscripten/emscripten.h"
|
||||
|
||||
static char *g_defaultCaptureDeviceName = nullptr;
|
||||
|
||||
static int g_captureId = -1;
|
||||
static int g_playbackId = -1;
|
||||
|
||||
static bool g_isInitialized = false;
|
||||
|
||||
static SDL_AudioDeviceID g_devIdIn = 0;
|
||||
static SDL_AudioDeviceID g_devIdOut = 0;
|
||||
|
||||
static GGWave *g_ggWave = nullptr;
|
||||
|
||||
// JS interface
|
||||
extern "C" {
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int setText(int textLength, const char * text) {
|
||||
g_ggWave->init(textLength, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getText(char * text) {
|
||||
std::copy(g_ggWave->getRxData().begin(), g_ggWave->getRxData().end(), text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getSampleRate() { return g_ggWave->getSampleRateIn(); }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
float getAverageRxTime_ms() { return g_ggWave->getAverageRxTime_ms(); }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getFramesToRecord() { return g_ggWave->getFramesToRecord(); }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getFramesLeftToRecord() { return g_ggWave->getFramesLeftToRecord(); }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getFramesToAnalyze() { return g_ggWave->getFramesToAnalyze(); }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int getFramesLeftToAnalyze() { return g_ggWave->getFramesLeftToAnalyze(); }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int hasDeviceOutput() { return g_devIdOut; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int hasDeviceCapture() { return (g_ggWave->getTotalBytesCaptured() > 0) ? g_devIdIn : 0; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int doInit() {
|
||||
return initSDL2ForGGWave(
|
||||
g_isInitialized,
|
||||
g_playbackId,
|
||||
g_devIdIn,
|
||||
g_captureId,
|
||||
g_devIdOut,
|
||||
g_ggWave,
|
||||
g_defaultCaptureDeviceName);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int setTxMode(int txMode) {
|
||||
g_ggWave->setTxMode((GGWave::TxMode)(txMode));
|
||||
g_ggWave->init(0, "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void setParameters(
|
||||
int paramFreqDelta,
|
||||
int paramFreqStart,
|
||||
int paramFramesPerTx,
|
||||
int paramBytesPerTx,
|
||||
int /*paramECCBytesPerTx*/,
|
||||
int paramVolume) {
|
||||
if (g_ggWave == nullptr) return;
|
||||
|
||||
g_ggWave->setParameters(
|
||||
paramFreqDelta,
|
||||
paramFreqStart,
|
||||
paramFramesPerTx,
|
||||
paramBytesPerTx,
|
||||
paramVolume);
|
||||
|
||||
g_ggWave->init(0, "");
|
||||
}
|
||||
}
|
||||
|
||||
// main loop
|
||||
void update() {
|
||||
if (g_isInitialized == false) return;
|
||||
|
||||
SDL_Event e;
|
||||
SDL_bool shouldTerminate = SDL_FALSE;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
if (e.type == SDL_QUIT) {
|
||||
shouldTerminate = SDL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static GGWave::CBQueueAudio cbQueueAudio = [&](const void * data, uint32_t nBytes) {
|
||||
SDL_QueueAudio(g_devIdOut, data, nBytes);
|
||||
};
|
||||
|
||||
static GGWave::CBDequeueAudio CBDequeueAudio = [&](void * data, uint32_t nMaxBytes) {
|
||||
return SDL_DequeueAudio(g_devIdIn, data, nMaxBytes);
|
||||
};
|
||||
|
||||
if (g_ggWave->getHasData() == false) {
|
||||
SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE);
|
||||
|
||||
static auto tLastNoData = std::chrono::high_resolution_clock::now();
|
||||
auto tNow = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesOut()) {
|
||||
SDL_PauseAudioDevice(g_devIdIn, SDL_FALSE);
|
||||
if (::getTime_ms(tLastNoData, tNow) > 500.0f) {
|
||||
g_ggWave->receive(CBDequeueAudio);
|
||||
if ((int) SDL_GetQueuedAudioSize(g_devIdIn) > 32*g_ggWave->getSamplesPerFrame()*g_ggWave->getSampleSizeBytesIn()) {
|
||||
SDL_ClearQueuedAudio(g_devIdIn);
|
||||
}
|
||||
} else {
|
||||
SDL_ClearQueuedAudio(g_devIdIn);
|
||||
}
|
||||
} else {
|
||||
tLastNoData = tNow;
|
||||
//SDL_ClearQueuedAudio(g_devIdIn);
|
||||
//SDL_Delay(10);
|
||||
}
|
||||
} else {
|
||||
SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE);
|
||||
SDL_PauseAudioDevice(g_devIdIn, SDL_TRUE);
|
||||
|
||||
g_ggWave->send(cbQueueAudio);
|
||||
}
|
||||
|
||||
if (shouldTerminate) {
|
||||
SDL_PauseAudioDevice(g_devIdIn, 1);
|
||||
SDL_CloseAudioDevice(g_devIdIn);
|
||||
SDL_PauseAudioDevice(g_devIdOut, 1);
|
||||
SDL_CloseAudioDevice(g_devIdOut);
|
||||
SDL_CloseAudio();
|
||||
SDL_Quit();
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_cancel_main_loop();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
int main(int , char** argv) {
|
||||
printf("Build time: %s\n", BUILD_TIMESTAMP);
|
||||
printf("Press the Init button to start\n");
|
||||
|
||||
g_defaultCaptureDeviceName = argv[1];
|
||||
|
||||
emscripten_set_main_loop(update, 60, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
154
include/ggwave/ggwave.h
Normal file
154
include/ggwave/ggwave.h
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace RS {
|
||||
class ReedSolomon;
|
||||
}
|
||||
|
||||
class GGWave {
|
||||
public:
|
||||
enum TxMode {
|
||||
FixedLength = 0,
|
||||
VariableLength,
|
||||
};
|
||||
|
||||
static constexpr auto kMaxSamplesPerFrame = 1024;
|
||||
static constexpr auto kMaxDataBits = 256;
|
||||
static constexpr auto kMaxDataSize = 256;
|
||||
static constexpr auto kMaxLength = 140;
|
||||
static constexpr auto kMaxSpectrumHistory = 4;
|
||||
static constexpr auto kMaxRecordedFrames = 64*10;
|
||||
static constexpr auto kDefaultFixedLength = 82;
|
||||
|
||||
using AmplitudeData = std::array<float, kMaxSamplesPerFrame>;
|
||||
using AmplitudeData16 = std::array<int16_t, kMaxRecordedFrames*kMaxSamplesPerFrame>;
|
||||
using SpectrumData = std::array<float, kMaxSamplesPerFrame>;
|
||||
using RecordedData = std::array<float, kMaxRecordedFrames*kMaxSamplesPerFrame>;
|
||||
|
||||
using CBQueueAudio = std::function<void(const void * data, uint32_t nBytes)>;
|
||||
using CBDequeueAudio = std::function<uint32_t(void * data, uint32_t nMaxBytes)>;
|
||||
|
||||
GGWave(
|
||||
int aSampleRateIn,
|
||||
int aSampleRateOut,
|
||||
int aSamplesPerFrame,
|
||||
int aSampleSizeBytesIn,
|
||||
int aSampleSizeBytesOut);
|
||||
|
||||
void setTxMode(TxMode aTxMode) { txMode = aTxMode; }
|
||||
|
||||
bool setParameters(
|
||||
int aParamFreqDelta,
|
||||
int aParamFreqStart,
|
||||
int aParamFramesPerTx,
|
||||
int aParamBytesPerTx,
|
||||
int aParamVolume);
|
||||
|
||||
bool init(int textLength, const char * stext);
|
||||
|
||||
void send(const CBQueueAudio & cbQueueAudio);
|
||||
void receive(const CBDequeueAudio & CBDequeueAudio);
|
||||
|
||||
const bool & getHasData() const { return hasData; }
|
||||
|
||||
const int & getFramesToRecord() const { return framesToRecord; }
|
||||
const int & getFramesLeftToRecord() const { return framesLeftToRecord; }
|
||||
const int & getFramesToAnalyze() const { return framesToAnalyze; }
|
||||
const int & getFramesLeftToAnalyze() const { return framesLeftToAnalyze; }
|
||||
const int & getSamplesPerFrame() const { return samplesPerFrame; }
|
||||
const int & getSampleSizeBytesIn() const { return sampleSizeBytesIn; }
|
||||
const int & getSampleSizeBytesOut() const { return sampleSizeBytesOut; }
|
||||
const int & getTotalBytesCaptured() const { return totalBytesCaptured; }
|
||||
|
||||
const float & getSampleRateIn() const { return sampleRateIn; }
|
||||
const float & getAverageRxTime_ms() const { return averageRxTime_ms; }
|
||||
|
||||
const std::array<std::uint8_t, kMaxDataSize> & getRxData() const { return rxData; }
|
||||
|
||||
private:
|
||||
int nIterations;
|
||||
|
||||
int paramFreqDelta = 6;
|
||||
int paramFreqStart = 40;
|
||||
int paramFramesPerTx = 6;
|
||||
int paramBytesPerTx = 2;
|
||||
int paramECCBytesPerTx = 32; // used for fixed-length Tx
|
||||
int paramVolume = 10;
|
||||
|
||||
// Rx
|
||||
bool receivingData;
|
||||
bool analyzingData;
|
||||
|
||||
int nCalls = 0;
|
||||
int recvDuration_frames;
|
||||
int totalBytesCaptured;
|
||||
|
||||
float tSum_ms = 0.0f;
|
||||
float averageRxTime_ms = 0.0;
|
||||
|
||||
std::array<float, kMaxSamplesPerFrame> fftIn;
|
||||
std::array<std::complex<float>, kMaxSamplesPerFrame> fftOut;
|
||||
|
||||
AmplitudeData sampleAmplitude;
|
||||
SpectrumData sampleSpectrum;
|
||||
|
||||
std::array<std::uint8_t, kMaxDataSize> rxData;
|
||||
std::array<std::uint8_t, kMaxDataSize> txData;
|
||||
std::array<std::uint8_t, kMaxDataSize> txDataEncoded;
|
||||
|
||||
int historyId = 0;
|
||||
AmplitudeData sampleAmplitudeAverage;
|
||||
std::array<AmplitudeData, kMaxSpectrumHistory> sampleAmplitudeHistory;
|
||||
|
||||
RecordedData recordedAmplitude;
|
||||
|
||||
// Tx
|
||||
bool hasData;
|
||||
|
||||
float freqDelta_hz;
|
||||
float freqStart_hz;
|
||||
float hzPerFrame;
|
||||
float ihzPerFrame;
|
||||
float isamplesPerFrame;
|
||||
float sampleRateIn;
|
||||
float sampleRateOut;
|
||||
float sendVolume;
|
||||
|
||||
int frameId;
|
||||
int framesLeftToAnalyze;
|
||||
int framesLeftToRecord;
|
||||
int framesPerTx;
|
||||
int framesToAnalyze;
|
||||
int framesToRecord;
|
||||
int freqDelta_bin = 1;
|
||||
int nBitsInMarker;
|
||||
int nDataBitsPerTx;
|
||||
int nECCBytesPerTx;
|
||||
int nMarkerFrames;
|
||||
int nPostMarkerFrames;
|
||||
int sampleSizeBytesIn;
|
||||
int sampleSizeBytesOut;
|
||||
int samplesPerFrame;
|
||||
int sendDataLength;
|
||||
|
||||
std::string textToSend;
|
||||
|
||||
TxMode txMode = TxMode::FixedLength;
|
||||
|
||||
AmplitudeData outputBlock;
|
||||
AmplitudeData16 outputBlock16;
|
||||
|
||||
std::array<bool, kMaxDataBits> dataBits;
|
||||
std::array<double, kMaxDataBits> phaseOffsets;
|
||||
std::array<double, kMaxDataBits> dataFreqs_hz;
|
||||
|
||||
std::array<AmplitudeData, kMaxDataBits> bit1Amplitude;
|
||||
std::array<AmplitudeData, kMaxDataBits> bit0Amplitude;
|
||||
|
||||
RS::ReedSolomon * rsData = nullptr;
|
||||
RS::ReedSolomon * rsLength = nullptr;
|
||||
};
|
||||
17
src/CMakeLists.txt
Normal file
17
src/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# core
|
||||
|
||||
add_library(ggwave ${GGWAVE_LIBRARY_TYPE}
|
||||
ggwave.cpp
|
||||
)
|
||||
|
||||
target_include_directories(ggwave PUBLIC
|
||||
.
|
||||
../include
|
||||
)
|
||||
|
||||
target_link_libraries(ggwave PUBLIC
|
||||
)
|
||||
|
||||
target_link_libraries(ggwave PRIVATE
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
641
src/ggwave.cpp
Normal file
641
src/ggwave.cpp
Normal file
@@ -0,0 +1,641 @@
|
||||
#include "ggwave/ggwave.h"
|
||||
|
||||
#include "reed-solomon/rs.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
// FFT routines taken from https://stackoverflow.com/a/37729648/4039976
|
||||
|
||||
int log2(int N) {
|
||||
int k = N, i = 0;
|
||||
while(k) {
|
||||
k >>= 1;
|
||||
i++;
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
int reverse(int N, int n) {
|
||||
int j, p = 0;
|
||||
for(j = 1; j <= log2(N); j++) {
|
||||
if(n & (1 << (log2(N) - j)))
|
||||
p |= 1 << (j - 1);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void ordina(std::complex<float>* f1, int N) {
|
||||
std::complex<float> f2[GGWave::kMaxSamplesPerFrame];
|
||||
for(int i = 0; i < N; i++)
|
||||
f2[i] = f1[reverse(N, i)];
|
||||
for(int j = 0; j < N; j++)
|
||||
f1[j] = f2[j];
|
||||
}
|
||||
|
||||
void transform(std::complex<float>* f, int N) {
|
||||
ordina(f, N); //first: reverse order
|
||||
std::complex<float> *W;
|
||||
W = (std::complex<float> *)malloc(N / 2 * sizeof(std::complex<float>));
|
||||
W[1] = std::polar(1., -2. * M_PI / N);
|
||||
W[0] = 1;
|
||||
for(int i = 2; i < N / 2; i++)
|
||||
W[i] = pow(W[1], i);
|
||||
int n = 1;
|
||||
int a = N / 2;
|
||||
for(int j = 0; j < log2(N); j++) {
|
||||
for(int i = 0; i < N; i++) {
|
||||
if(!(i & n)) {
|
||||
std::complex<float> temp = f[i];
|
||||
std::complex<float> Temp = W[(i * a) % (n * a)] * f[i + n];
|
||||
f[i] = temp + Temp;
|
||||
f[i + n] = temp - Temp;
|
||||
}
|
||||
}
|
||||
n *= 2;
|
||||
a = a / 2;
|
||||
}
|
||||
free(W);
|
||||
}
|
||||
|
||||
void FFT(std::complex<float>* f, int N, float d) {
|
||||
transform(f, N);
|
||||
for(int i = 0; i < N; i++)
|
||||
f[i] *= d; //multiplying by step
|
||||
}
|
||||
|
||||
void FFT(float * src, std::complex<float>* dst, int N, float d) {
|
||||
for (int i = 0; i < N; ++i) {
|
||||
dst[i].real(src[i]);
|
||||
dst[i].imag(0);
|
||||
}
|
||||
FFT(dst, N, d);
|
||||
}
|
||||
|
||||
inline void addAmplitudeSmooth(
|
||||
const GGWave::AmplitudeData & src,
|
||||
GGWave::AmplitudeData & dst,
|
||||
float scalar, int startId, int finalId, int cycleMod, int nPerCycle) {
|
||||
int nTotal = nPerCycle*finalId;
|
||||
float frac = 0.15f;
|
||||
float ds = frac*nTotal;
|
||||
float ids = 1.0f/ds;
|
||||
int nBegin = frac*nTotal;
|
||||
int nEnd = (1.0f - frac)*nTotal;
|
||||
for (int i = startId; i < finalId; i++) {
|
||||
float k = cycleMod*finalId + i;
|
||||
if (k < nBegin) {
|
||||
dst[i] += scalar*src[i]*(k*ids);
|
||||
} else if (k > nEnd) {
|
||||
dst[i] += scalar*src[i]*(((float)(nTotal) - k)*ids);
|
||||
} else {
|
||||
dst[i] += scalar*src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
float getTime_ms(const T & tStart, const T & tEnd) {
|
||||
return ((float)(std::chrono::duration_cast<std::chrono::microseconds>(tEnd - tStart).count()))/1000.0;
|
||||
}
|
||||
|
||||
int getECCBytesForLength(int len) {
|
||||
return std::max(4, 2*(len/5));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GGWave::GGWave(
|
||||
int aSampleRateIn,
|
||||
int aSampleRateOut,
|
||||
int aSamplesPerFrame,
|
||||
int aSampleSizeBytesIn,
|
||||
int aSampleSizeBytesOut) {
|
||||
|
||||
sampleRateIn = aSampleRateIn;
|
||||
sampleRateOut = aSampleRateOut;
|
||||
samplesPerFrame = aSamplesPerFrame;
|
||||
sampleSizeBytesIn = aSampleSizeBytesIn;
|
||||
sampleSizeBytesOut = aSampleSizeBytesOut;
|
||||
|
||||
init(0, "");
|
||||
}
|
||||
|
||||
bool GGWave::setParameters(
|
||||
int aParamFreqDelta,
|
||||
int aParamFreqStart,
|
||||
int aParamFramesPerTx,
|
||||
int aParamBytesPerTx,
|
||||
int aParamVolume) {
|
||||
|
||||
paramFreqDelta = aParamFreqDelta;
|
||||
paramFreqStart = aParamFreqStart;
|
||||
paramFramesPerTx = aParamFramesPerTx;
|
||||
paramBytesPerTx = aParamBytesPerTx;
|
||||
paramVolume = aParamVolume;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GGWave::init(int textLength, const char * stext) {
|
||||
if (textLength > kMaxLength) {
|
||||
printf("Truncating data from %d to 140 bytes\n", textLength);
|
||||
textLength = kMaxLength;
|
||||
}
|
||||
|
||||
const uint8_t * text = reinterpret_cast<const uint8_t *>(stext);
|
||||
frameId = 0;
|
||||
nIterations = 0;
|
||||
hasData = false;
|
||||
|
||||
isamplesPerFrame = 1.0f/samplesPerFrame;
|
||||
sendVolume = ((double)(paramVolume))/100.0f;
|
||||
hzPerFrame = sampleRateIn/samplesPerFrame;
|
||||
ihzPerFrame = 1.0/hzPerFrame;
|
||||
framesPerTx = paramFramesPerTx;
|
||||
|
||||
nDataBitsPerTx = paramBytesPerTx*8;
|
||||
nECCBytesPerTx = (txMode == TxMode::FixedLength) ? paramECCBytesPerTx : getECCBytesForLength(textLength);
|
||||
|
||||
framesToAnalyze = 0;
|
||||
framesLeftToAnalyze = 0;
|
||||
framesToRecord = 0;
|
||||
framesLeftToRecord = 0;
|
||||
nBitsInMarker = 16;
|
||||
nMarkerFrames = 16;
|
||||
nPostMarkerFrames = 0;
|
||||
sendDataLength = (txMode == TxMode::FixedLength) ? kDefaultFixedLength : textLength + 3;
|
||||
|
||||
freqDelta_bin = paramFreqDelta/2;
|
||||
freqDelta_hz = hzPerFrame*paramFreqDelta;
|
||||
freqStart_hz = hzPerFrame*paramFreqStart;
|
||||
if (paramFreqDelta == 1) {
|
||||
freqDelta_bin = 1;
|
||||
freqDelta_hz *= 2;
|
||||
}
|
||||
|
||||
outputBlock.fill(0);
|
||||
|
||||
txData.fill(0);
|
||||
txDataEncoded.fill(0);
|
||||
|
||||
for (int k = 0; k < (int) phaseOffsets.size(); ++k) {
|
||||
phaseOffsets[k] = (M_PI*k)/(nDataBitsPerTx);
|
||||
}
|
||||
|
||||
// note : what is the purpose of this shuffle ? I forgot .. :(
|
||||
std::random_shuffle(phaseOffsets.begin(), phaseOffsets.end());
|
||||
|
||||
for (int k = 0; k < (int) dataBits.size(); ++k) {
|
||||
double freq = freqStart_hz + freqDelta_hz*k;
|
||||
dataFreqs_hz[k] = freq;
|
||||
|
||||
double phaseOffset = phaseOffsets[k];
|
||||
double curHzPerFrame = sampleRateOut/samplesPerFrame;
|
||||
double curIHzPerFrame = 1.0/curHzPerFrame;
|
||||
for (int i = 0; i < samplesPerFrame; i++) {
|
||||
double curi = i;
|
||||
bit1Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*(freq*curIHzPerFrame) + phaseOffset);
|
||||
}
|
||||
for (int i = 0; i < samplesPerFrame; i++) {
|
||||
double curi = i;
|
||||
bit0Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*((freq + hzPerFrame*freqDelta_bin)*curIHzPerFrame) + phaseOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (rsData) delete rsData;
|
||||
if (rsLength) delete rsLength;
|
||||
|
||||
if (txMode == TxMode::FixedLength) {
|
||||
rsData = new RS::ReedSolomon(kDefaultFixedLength, nECCBytesPerTx);
|
||||
rsLength = nullptr;
|
||||
} else {
|
||||
rsData = new RS::ReedSolomon(textLength, nECCBytesPerTx);
|
||||
rsLength = new RS::ReedSolomon(1, 2);
|
||||
}
|
||||
|
||||
if (textLength > 0) {
|
||||
if (txMode == TxMode::FixedLength) {
|
||||
for (int i = 0; i < textLength; ++i) txData[i] = text[i];
|
||||
rsData->Encode(txData.data(), txDataEncoded.data());
|
||||
} else {
|
||||
txData[0] = textLength;
|
||||
for (int i = 0; i < textLength; ++i) txData[i + 1] = text[i];
|
||||
rsData->Encode(txData.data() + 1, txDataEncoded.data() + 3);
|
||||
rsLength->Encode(txData.data(), txDataEncoded.data());
|
||||
}
|
||||
|
||||
hasData = true;
|
||||
}
|
||||
|
||||
// Rx
|
||||
receivingData = false;
|
||||
analyzingData = false;
|
||||
|
||||
sampleAmplitude.fill(0);
|
||||
|
||||
sampleSpectrum.fill(0);
|
||||
for (auto & s : sampleAmplitudeHistory) {
|
||||
s.fill(0);
|
||||
}
|
||||
|
||||
rxData.fill(0);
|
||||
|
||||
for (int i = 0; i < samplesPerFrame; ++i) {
|
||||
fftOut[i].real(0.0f);
|
||||
fftOut[i].imag(0.0f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GGWave::send(const CBQueueAudio & cbQueueAudio) {
|
||||
int samplesPerFrameOut = (sampleRateOut/sampleRateIn)*samplesPerFrame;
|
||||
if (sampleRateOut != sampleRateIn) {
|
||||
printf("Resampling from %d Hz to %d Hz\n", (int) sampleRateIn, (int) sampleRateOut);
|
||||
}
|
||||
|
||||
while (hasData) {
|
||||
int nBytesPerTx = nDataBitsPerTx/8;
|
||||
std::fill(outputBlock.begin(), outputBlock.end(), 0.0f);
|
||||
std::uint16_t nFreq = 0;
|
||||
|
||||
if (sampleRateOut != sampleRateIn) {
|
||||
for (int k = 0; k < nDataBitsPerTx; ++k) {
|
||||
double freq = freqStart_hz + freqDelta_hz*k;
|
||||
|
||||
double phaseOffset = phaseOffsets[k];
|
||||
double curHzPerFrame = sampleRateOut/samplesPerFrame;
|
||||
double curIHzPerFrame = 1.0/curHzPerFrame;
|
||||
for (int i = 0; i < samplesPerFrameOut; i++) {
|
||||
double curi = (i + frameId*samplesPerFrameOut);
|
||||
bit1Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*(freq*curIHzPerFrame) + phaseOffset);
|
||||
}
|
||||
for (int i = 0; i < samplesPerFrameOut; i++) {
|
||||
double curi = (i + frameId*samplesPerFrameOut);
|
||||
bit0Amplitude[k][i] = std::sin((2.0*M_PI)*(curi*isamplesPerFrame)*((freq + hzPerFrame*freqDelta_bin)*curIHzPerFrame) + phaseOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frameId < nMarkerFrames) {
|
||||
nFreq = nBitsInMarker;
|
||||
|
||||
for (int i = 0; i < nBitsInMarker; ++i) {
|
||||
if (i%2 == 0) {
|
||||
::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId, nMarkerFrames);
|
||||
} else {
|
||||
::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId, nMarkerFrames);
|
||||
}
|
||||
}
|
||||
} else if (frameId < nMarkerFrames + nPostMarkerFrames) {
|
||||
nFreq = nBitsInMarker;
|
||||
|
||||
for (int i = 0; i < nBitsInMarker; ++i) {
|
||||
if (i%2 == 0) {
|
||||
::addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId - nMarkerFrames, nPostMarkerFrames);
|
||||
} else {
|
||||
::addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, frameId - nMarkerFrames, nPostMarkerFrames);
|
||||
}
|
||||
}
|
||||
} else if (frameId <
|
||||
(nMarkerFrames + nPostMarkerFrames) +
|
||||
((sendDataLength + nECCBytesPerTx)/nBytesPerTx + 2)*framesPerTx) {
|
||||
int dataOffset = frameId - nMarkerFrames - nPostMarkerFrames;
|
||||
int cycleModMain = dataOffset%framesPerTx;
|
||||
dataOffset /= framesPerTx;
|
||||
dataOffset *= nBytesPerTx;
|
||||
|
||||
dataBits.fill(0);
|
||||
|
||||
if (paramFreqDelta > 1) {
|
||||
for (int j = 0; j < nBytesPerTx; ++j) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
dataBits[j*8 + i] = txDataEncoded[dataOffset + j] & (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < nDataBitsPerTx; ++k) {
|
||||
++nFreq;
|
||||
if (dataBits[k] == false) {
|
||||
::addAmplitudeSmooth(bit0Amplitude[k], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx);
|
||||
continue;
|
||||
}
|
||||
::addAmplitudeSmooth(bit1Amplitude[k], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx);
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < nBytesPerTx; ++j) {
|
||||
{
|
||||
uint8_t d = txDataEncoded[dataOffset + j] & 15;
|
||||
dataBits[(2*j + 0)*16 + d] = 1;
|
||||
}
|
||||
{
|
||||
uint8_t d = txDataEncoded[dataOffset + j] & 240;
|
||||
dataBits[(2*j + 1)*16 + (d >> 4)] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < 2*nBytesPerTx*16; ++k) {
|
||||
if (dataBits[k] == 0) continue;
|
||||
|
||||
++nFreq;
|
||||
if (k%2) {
|
||||
::addAmplitudeSmooth(bit0Amplitude[k/2], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx);
|
||||
} else {
|
||||
::addAmplitudeSmooth(bit1Amplitude[k/2], outputBlock, sendVolume, 0, samplesPerFrameOut, cycleModMain, framesPerTx);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (txMode == TxMode::VariableLength && frameId <
|
||||
(nMarkerFrames + nPostMarkerFrames) +
|
||||
((sendDataLength + nECCBytesPerTx)/nBytesPerTx + 2)*framesPerTx +
|
||||
(nMarkerFrames)) {
|
||||
nFreq = nBitsInMarker;
|
||||
|
||||
int fId = frameId - ((nMarkerFrames + nPostMarkerFrames) + ((sendDataLength + nECCBytesPerTx)/nBytesPerTx + 2)*framesPerTx);
|
||||
for (int i = 0; i < nBitsInMarker; ++i) {
|
||||
if (i%2 == 0) {
|
||||
addAmplitudeSmooth(bit0Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, fId, nMarkerFrames);
|
||||
} else {
|
||||
addAmplitudeSmooth(bit1Amplitude[i], outputBlock, sendVolume, 0, samplesPerFrameOut, fId, nMarkerFrames);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
textToSend = "";
|
||||
hasData = false;
|
||||
}
|
||||
|
||||
if (nFreq == 0) nFreq = 1;
|
||||
float scale = 1.0f/nFreq;
|
||||
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||
outputBlock[i] *= scale;
|
||||
}
|
||||
|
||||
// todo : support for non-int16 output
|
||||
for (int i = 0; i < samplesPerFrameOut; ++i) {
|
||||
outputBlock16[frameId*samplesPerFrameOut + i] = std::round(32000.0*outputBlock[i]);
|
||||
}
|
||||
|
||||
++frameId;
|
||||
}
|
||||
cbQueueAudio(outputBlock16.data(), frameId*samplesPerFrameOut*sampleSizeBytesOut);
|
||||
}
|
||||
|
||||
void GGWave::receive(const CBDequeueAudio & CBDequeueAudio) {
|
||||
auto tCallStart = std::chrono::high_resolution_clock::now();
|
||||
|
||||
while (hasData == false) {
|
||||
// read capture data
|
||||
//
|
||||
// todo : support for non-float input
|
||||
auto nBytesRecorded = CBDequeueAudio(sampleAmplitude.data(), samplesPerFrame*sampleSizeBytesIn);
|
||||
|
||||
if (nBytesRecorded != 0) {
|
||||
{
|
||||
sampleAmplitudeHistory[historyId] = sampleAmplitude;
|
||||
|
||||
if (++historyId >= kMaxSpectrumHistory) {
|
||||
historyId = 0;
|
||||
}
|
||||
|
||||
if (historyId == 0 && (receivingData == false || (receivingData && txMode == TxMode::VariableLength))) {
|
||||
std::fill(sampleAmplitudeAverage.begin(), sampleAmplitudeAverage.end(), 0.0f);
|
||||
for (auto & s : sampleAmplitudeHistory) {
|
||||
for (int i = 0; i < samplesPerFrame; ++i) {
|
||||
sampleAmplitudeAverage[i] += s[i];
|
||||
}
|
||||
}
|
||||
float norm = 1.0f/kMaxSpectrumHistory;
|
||||
for (int i = 0; i < samplesPerFrame; ++i) {
|
||||
sampleAmplitudeAverage[i] *= norm;
|
||||
}
|
||||
|
||||
// calculate spectrum
|
||||
std::copy(sampleAmplitudeAverage.begin(), sampleAmplitudeAverage.begin() + samplesPerFrame, fftIn.data());
|
||||
|
||||
FFT(fftIn.data(), fftOut.data(), samplesPerFrame, 1.0);
|
||||
|
||||
double fsum = 0.0;
|
||||
for (int i = 0; i < samplesPerFrame; ++i) {
|
||||
sampleSpectrum[i] = (fftOut[i].real()*fftOut[i].real() + fftOut[i].imag()*fftOut[i].imag());
|
||||
fsum += sampleSpectrum[i];
|
||||
}
|
||||
for (int i = 1; i < samplesPerFrame/2; ++i) {
|
||||
sampleSpectrum[i] += sampleSpectrum[samplesPerFrame - i];
|
||||
}
|
||||
|
||||
if (fsum < 1e-10) {
|
||||
totalBytesCaptured = 0;
|
||||
} else {
|
||||
totalBytesCaptured += nBytesRecorded;
|
||||
}
|
||||
}
|
||||
|
||||
if (framesLeftToRecord > 0) {
|
||||
std::copy(sampleAmplitude.begin(),
|
||||
sampleAmplitude.begin() + samplesPerFrame,
|
||||
recordedAmplitude.data() + (framesToRecord - framesLeftToRecord)*samplesPerFrame);
|
||||
|
||||
if (--framesLeftToRecord <= 0) {
|
||||
std::fill(sampleSpectrum.begin(), sampleSpectrum.end(), 0.0f);
|
||||
analyzingData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (analyzingData) {
|
||||
int nBytesPerTx = nDataBitsPerTx/8;
|
||||
int stepsPerFrame = 16;
|
||||
int step = samplesPerFrame/stepsPerFrame;
|
||||
|
||||
int offsetStart = 0;
|
||||
|
||||
framesToAnalyze = nMarkerFrames*stepsPerFrame;
|
||||
framesLeftToAnalyze = framesToAnalyze;
|
||||
|
||||
bool isValid = false;
|
||||
for (int ii = nMarkerFrames*stepsPerFrame - 1; ii >= nMarkerFrames*stepsPerFrame/2; --ii) {
|
||||
offsetStart = ii;
|
||||
bool knownLength = txMode == TxMode::FixedLength;
|
||||
int encodedOffset = (txMode == TxMode::FixedLength) ? 0 : 3;
|
||||
|
||||
for (int itx = 0; itx < 1024; ++itx) {
|
||||
int offsetTx = offsetStart + itx*framesPerTx*stepsPerFrame;
|
||||
if (offsetTx >= recvDuration_frames*stepsPerFrame) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::copy(
|
||||
recordedAmplitude.begin() + offsetTx*step,
|
||||
recordedAmplitude.begin() + offsetTx*step + samplesPerFrame, fftIn.data());
|
||||
|
||||
for (int k = 1; k < framesPerTx-1; ++k) {
|
||||
for (int i = 0; i < samplesPerFrame; ++i) {
|
||||
fftIn[i] += recordedAmplitude[(offsetTx + k*stepsPerFrame)*step + i];
|
||||
}
|
||||
}
|
||||
|
||||
FFT(fftIn.data(), fftOut.data(), samplesPerFrame, 1.0);
|
||||
|
||||
for (int i = 0; i < samplesPerFrame; ++i) {
|
||||
sampleSpectrum[i] = (fftOut[i].real()*fftOut[i].real() + fftOut[i].imag()*fftOut[i].imag());
|
||||
}
|
||||
for (int i = 1; i < samplesPerFrame/2; ++i) {
|
||||
sampleSpectrum[i] += sampleSpectrum[samplesPerFrame - i];
|
||||
}
|
||||
|
||||
uint8_t curByte = 0;
|
||||
if (paramFreqDelta > 1) {
|
||||
for (int i = 0; i < nDataBitsPerTx; ++i) {
|
||||
int k = i%8;
|
||||
int bin = std::round(dataFreqs_hz[i]*ihzPerFrame);
|
||||
if (sampleSpectrum[bin] > 1*sampleSpectrum[bin + freqDelta_bin]) {
|
||||
curByte += 1 << k;
|
||||
} else if (sampleSpectrum[bin + freqDelta_bin] > 1*sampleSpectrum[bin]) {
|
||||
} else {
|
||||
}
|
||||
if (k == 7) {
|
||||
txDataEncoded[itx*nBytesPerTx + i/8] = curByte;
|
||||
curByte = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 2*nBytesPerTx; ++i) {
|
||||
int bin = std::round(dataFreqs_hz[0]*ihzPerFrame) + i*16;
|
||||
|
||||
int kmax = 0;
|
||||
double amax = 0.0;
|
||||
for (int k = 0; k < 16; ++k) {
|
||||
if (sampleSpectrum[bin + k] > amax) {
|
||||
kmax = k;
|
||||
amax = sampleSpectrum[bin + k];
|
||||
}
|
||||
}
|
||||
|
||||
if (i%2) {
|
||||
curByte += (kmax << 4);
|
||||
txDataEncoded[itx*nBytesPerTx + i/2] = curByte;
|
||||
curByte = 0;
|
||||
} else {
|
||||
curByte = kmax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (txMode == TxMode::VariableLength) {
|
||||
if (itx*nBytesPerTx > 3 && knownLength == false) {
|
||||
if ((rsLength->Decode(txDataEncoded.data(), rxData.data()) == 0) && (rxData[0] <= 140)) {
|
||||
knownLength = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (txMode == TxMode::VariableLength && knownLength) {
|
||||
if (rsData) delete rsData;
|
||||
rsData = new RS::ReedSolomon(rxData[0], ::getECCBytesForLength(rxData[0]));
|
||||
}
|
||||
|
||||
if (knownLength) {
|
||||
int decodedLength = rxData[0];
|
||||
if (rsData->Decode(txDataEncoded.data() + encodedOffset, rxData.data()) == 0) {
|
||||
printf("Decoded length = %d\n", decodedLength);
|
||||
if (txMode == TxMode::FixedLength && rxData[0] == 'A') {
|
||||
printf("[ANSWER] Received sound data successfully!\n");
|
||||
} else if (txMode == TxMode::FixedLength && rxData[0] == 'O') {
|
||||
printf("[OFFER] Received sound data successfully!\n");
|
||||
} else {
|
||||
std::string s((char *) rxData.data(), decodedLength);
|
||||
printf("Received sound data successfully: '%s'\n", s.c_str());
|
||||
}
|
||||
framesToRecord = 0;
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
break;
|
||||
}
|
||||
--framesLeftToAnalyze;
|
||||
}
|
||||
|
||||
if (isValid == false) {
|
||||
printf("Failed to capture sound data. Please try again\n");
|
||||
framesToRecord = -1;
|
||||
}
|
||||
|
||||
receivingData = false;
|
||||
analyzingData = false;
|
||||
|
||||
std::fill(sampleSpectrum.begin(), sampleSpectrum.end(), 0.0f);
|
||||
|
||||
framesToAnalyze = 0;
|
||||
framesLeftToAnalyze = 0;
|
||||
}
|
||||
|
||||
// check if receiving data
|
||||
if (receivingData == false) {
|
||||
bool isReceiving = true;
|
||||
|
||||
for (int i = 0; i < nBitsInMarker; ++i) {
|
||||
int bin = std::round(dataFreqs_hz[i]*ihzPerFrame);
|
||||
|
||||
if (i%2 == 0) {
|
||||
if (sampleSpectrum[bin] <= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isReceiving = false;
|
||||
} else {
|
||||
if (sampleSpectrum[bin] >= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isReceiving = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isReceiving) {
|
||||
std::time_t timestamp = std::time(nullptr);
|
||||
printf("%sReceiving sound data ...\n", std::asctime(std::localtime(×tamp)));
|
||||
rxData.fill(0);
|
||||
receivingData = true;
|
||||
if (txMode == TxMode::FixedLength) {
|
||||
recvDuration_frames = nMarkerFrames + nPostMarkerFrames + framesPerTx*((kDefaultFixedLength + paramECCBytesPerTx)/paramBytesPerTx + 1);
|
||||
} else {
|
||||
recvDuration_frames = nMarkerFrames + nPostMarkerFrames + framesPerTx*((kMaxLength + ::getECCBytesForLength(kMaxLength))/paramBytesPerTx + 1);
|
||||
}
|
||||
framesToRecord = recvDuration_frames;
|
||||
framesLeftToRecord = recvDuration_frames;
|
||||
}
|
||||
} else if (txMode == TxMode::VariableLength) {
|
||||
bool isEnded = true;
|
||||
|
||||
for (int i = 0; i < nBitsInMarker; ++i) {
|
||||
int bin = std::round(dataFreqs_hz[i]*ihzPerFrame);
|
||||
|
||||
if (i%2 == 0) {
|
||||
if (sampleSpectrum[bin] >= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isEnded = false;
|
||||
} else {
|
||||
if (sampleSpectrum[bin] <= 3.0f*sampleSpectrum[bin + freqDelta_bin]) isEnded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnded && framesToRecord > 1) {
|
||||
std::time_t timestamp = std::time(nullptr);
|
||||
printf("%sReceived end marker\n", std::asctime(std::localtime(×tamp)));
|
||||
recvDuration_frames -= framesLeftToRecord - 1;
|
||||
framesLeftToRecord = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
++nIterations;
|
||||
}
|
||||
|
||||
auto tCallEnd = std::chrono::high_resolution_clock::now();
|
||||
tSum_ms += getTime_ms(tCallStart, tCallEnd);
|
||||
if (++nCalls == 10) {
|
||||
averageRxTime_ms = tSum_ms/nCalls;
|
||||
tSum_ms = 0.0f;
|
||||
nCalls = 0;
|
||||
}
|
||||
}
|
||||
21
src/reed-solomon/LICENSE
Normal file
21
src/reed-solomon/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
Copyright © 2015 Mike Lubinets, github.com/mersinvald
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the “Software”), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
239
src/reed-solomon/gf.hpp
Normal file
239
src/reed-solomon/gf.hpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/* Author: Mike Lubinets (aka mersinvald)
|
||||
* Date: 29.12.15
|
||||
*
|
||||
* See LICENSE */
|
||||
|
||||
#ifndef GF_H
|
||||
#define GF_H
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "poly.hpp"
|
||||
|
||||
#if !defined DEBUG && !defined __CC_ARM
|
||||
#include <assert.h>
|
||||
#else
|
||||
#define assert(dummy)
|
||||
#endif
|
||||
|
||||
|
||||
namespace RS {
|
||||
|
||||
namespace gf {
|
||||
|
||||
|
||||
/* GF tables pre-calculated for 0x11d primitive polynomial */
|
||||
|
||||
const uint8_t exp[512] = {
|
||||
0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c,
|
||||
0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d,
|
||||
0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46,
|
||||
0x8c, 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f,
|
||||
0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd,
|
||||
0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9,
|
||||
0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81,
|
||||
0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85,
|
||||
0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8,
|
||||
0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6,
|
||||
0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3,
|
||||
0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82,
|
||||
0x19, 0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51,
|
||||
0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12,
|
||||
0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c,
|
||||
0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2,
|
||||
0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98,
|
||||
0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d, 0x27,
|
||||
0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c,
|
||||
0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe,
|
||||
0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7,
|
||||
0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf,
|
||||
0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f,
|
||||
0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17,
|
||||
0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d,
|
||||
0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1,
|
||||
0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb,
|
||||
0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19,
|
||||
0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2,
|
||||
0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12, 0x24,
|
||||
0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c, 0x58,
|
||||
0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2
|
||||
};
|
||||
|
||||
const uint8_t log[256] = {
|
||||
0x0, 0x0, 0x1, 0x19, 0x2, 0x32, 0x1a, 0xc6, 0x3, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x4,
|
||||
0x64, 0xe0, 0xe, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x8, 0x4c, 0x71, 0x5,
|
||||
0x8a, 0x65, 0x2f, 0xe1, 0x24, 0xf, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d,
|
||||
0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x9, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 0x6,
|
||||
0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36,
|
||||
0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e,
|
||||
0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca,
|
||||
0x5e, 0x9b, 0x9f, 0xa, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x7,
|
||||
0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0xd, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3,
|
||||
0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37,
|
||||
0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2,
|
||||
0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f,
|
||||
0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0xc, 0x6f, 0xf6, 0x6c,
|
||||
0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb,
|
||||
0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0xb, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 0x4f,
|
||||
0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* ################################
|
||||
* # OPERATIONS OVER GALUA FIELDS #
|
||||
* ################################ */
|
||||
|
||||
/* @brief Addition in Galua Fields
|
||||
* @param x - left operand
|
||||
* @param y - right operand
|
||||
* @return x + y */
|
||||
inline uint8_t add(uint8_t x, uint8_t y) {
|
||||
return x^y;
|
||||
}
|
||||
|
||||
/* ##### GF substraction ###### */
|
||||
/* @brief Substraction in Galua Fields
|
||||
* @param x - left operand
|
||||
* @param y - right operand
|
||||
* @return x - y */
|
||||
inline uint8_t sub(uint8_t x, uint8_t y) {
|
||||
return x^y;
|
||||
}
|
||||
|
||||
/* @brief Multiplication in Galua Fields
|
||||
* @param x - left operand
|
||||
* @param y - rifht operand
|
||||
* @return x * y */
|
||||
inline uint8_t mul(uint16_t x, uint16_t y){
|
||||
if (x == 0 || y == 0)
|
||||
return 0;
|
||||
return exp[log[x] + log[y]];
|
||||
}
|
||||
|
||||
/* @brief Division in Galua Fields
|
||||
* @param x - dividend
|
||||
* @param y - divisor
|
||||
* @return x / y */
|
||||
inline uint8_t div(uint8_t x, uint8_t y){
|
||||
assert(y != 0);
|
||||
if(x == 0) return 0;
|
||||
return exp[(log[x] + 255 - log[y]) % 255];
|
||||
}
|
||||
|
||||
/* @brief X in power Y w
|
||||
* @param x - operand
|
||||
* @param power - power
|
||||
* @return x^power */
|
||||
inline uint8_t pow(uint8_t x, intmax_t power){
|
||||
intmax_t i = log[x];
|
||||
i *= power;
|
||||
i %= 255;
|
||||
if(i < 0) i = i + 255;
|
||||
return exp[i];
|
||||
}
|
||||
|
||||
/* @brief Inversion in Galua Fields
|
||||
* @param x - number
|
||||
* @return inversion of x */
|
||||
inline uint8_t inverse(uint8_t x){
|
||||
return exp[255 - log[x]]; /* == div(1, x); */
|
||||
}
|
||||
|
||||
/* ##########################
|
||||
* # POLYNOMIALS OPERATIONS #
|
||||
* ########################## */
|
||||
|
||||
/* @brief Multiplication polynomial by scalar
|
||||
* @param &p - source polynomial
|
||||
* @param &newp - destination polynomial
|
||||
* @param x - scalar */
|
||||
inline void
|
||||
poly_scale(const Poly *p, Poly *newp, uint16_t x) {
|
||||
newp->length = p->length;
|
||||
for(uint16_t i = 0; i < p->length; i++){
|
||||
newp->at(i) = mul(p->at(i), x);
|
||||
}
|
||||
}
|
||||
|
||||
/* @brief Addition of two polynomials
|
||||
* @param &p - right operand polynomial
|
||||
* @param &q - left operand polynomial
|
||||
* @param &newp - destination polynomial */
|
||||
inline void
|
||||
poly_add(const Poly *p, const Poly *q, Poly *newp) {
|
||||
newp->length = poly_max(p->length, q->length);
|
||||
memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));
|
||||
|
||||
for(uint8_t i = 0; i < p->length; i++){
|
||||
newp->at(i + newp->length - p->length) = p->at(i);
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < q->length; i++){
|
||||
newp->at(i + newp->length - q->length) ^= q->at(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* @brief Multiplication of two polynomials
|
||||
* @param &p - right operand polynomial
|
||||
* @param &q - left operand polynomial
|
||||
* @param &newp - destination polynomial */
|
||||
inline void
|
||||
poly_mul(const Poly *p, const Poly *q, Poly *newp) {
|
||||
newp->length = p->length + q->length - 1;
|
||||
memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));
|
||||
/* Compute the polynomial multiplication (just like the outer product of two vectors,
|
||||
* we multiply each coefficients of p with all coefficients of q) */
|
||||
for(uint8_t j = 0; j < q->length; j++){
|
||||
for(uint8_t i = 0; i < p->length; i++){
|
||||
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])) */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @brief Division of two polynomials
|
||||
* @param &p - right operand polynomial
|
||||
* @param &q - left operand polynomial
|
||||
* @param &newp - destination polynomial */
|
||||
inline void
|
||||
poly_div(const Poly *p, const Poly *q, Poly *newp) {
|
||||
if(p->ptr() != newp->ptr()) {
|
||||
memcpy(newp->ptr(), p->ptr(), p->length*sizeof(uint8_t));
|
||||
}
|
||||
|
||||
newp->length = p->length;
|
||||
|
||||
uint8_t coef;
|
||||
|
||||
for(int i = 0; i < (p->length-(q->length-1)); i++){
|
||||
coef = newp->at(i);
|
||||
if(coef != 0){
|
||||
for(uint8_t j = 1; j < q->length; j++){
|
||||
if(q->at(j) != 0)
|
||||
newp->at(i+j) ^= mul(q->at(j), coef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t sep = p->length-(q->length-1);
|
||||
memmove(newp->ptr(), newp->ptr()+sep, (newp->length-sep) * sizeof(uint8_t));
|
||||
newp->length = newp->length-sep;
|
||||
}
|
||||
|
||||
/* @brief Evaluation of polynomial in x
|
||||
* @param &p - polynomial to evaluate
|
||||
* @param x - evaluation point */
|
||||
inline int8_t
|
||||
poly_eval(const Poly *p, uint16_t x) {
|
||||
uint8_t y = p->at(0);
|
||||
for(uint8_t i = 1; i < p->length; i++){
|
||||
y = mul(y, x) ^ p->at(i);
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
} /* end of gf namespace */
|
||||
|
||||
}
|
||||
#endif // GF_H
|
||||
|
||||
98
src/reed-solomon/poly.hpp
Normal file
98
src/reed-solomon/poly.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/* Author: Mike Lubinets (aka mersinvald)
|
||||
* Date: 29.12.15
|
||||
*
|
||||
* See LICENSE */
|
||||
|
||||
#ifndef POLY_H
|
||||
#define POLY_H
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#if !defined DEBUG && !defined __CC_ARM
|
||||
#include <assert.h>
|
||||
#else
|
||||
#define assert(dummy)
|
||||
#endif
|
||||
|
||||
namespace RS {
|
||||
|
||||
struct Poly {
|
||||
Poly()
|
||||
: length(0), _memory(NULL) {}
|
||||
|
||||
Poly(uint8_t id, uint16_t offset, uint8_t size) \
|
||||
: length(0), _id(id), _size(size), _offset(offset), _memory(NULL) {}
|
||||
|
||||
/* @brief Append number at the end of polynomial
|
||||
* @param num - number to append
|
||||
* @return false if polynomial can't be stretched */
|
||||
inline bool Append(uint8_t num) {
|
||||
assert(length < _size);
|
||||
ptr()[length++] = num;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* @brief Polynomial initialization */
|
||||
inline void Init(uint8_t id, uint16_t offset, uint8_t size, uint8_t** memory_ptr) {
|
||||
this->_id = id;
|
||||
this->_offset = offset;
|
||||
this->_size = size;
|
||||
this->length = 0;
|
||||
this->_memory = memory_ptr;
|
||||
}
|
||||
|
||||
/* @brief Polynomial memory zeroing */
|
||||
inline void Reset() {
|
||||
memset((void*)ptr(), 0, this->_size);
|
||||
}
|
||||
|
||||
/* @brief Copy polynomial to memory
|
||||
* @param src - source byte-sequence
|
||||
* @param size - size of polynomial
|
||||
* @param offset - write offset */
|
||||
inline void Set(const uint8_t* src, uint8_t len, uint8_t offset = 0) {
|
||||
assert(src && len <= this->_size-offset);
|
||||
memcpy(ptr()+offset, src, len * sizeof(uint8_t));
|
||||
length = len + offset;
|
||||
}
|
||||
|
||||
#define poly_max(a, b) ((a > b) ? (a) : (b))
|
||||
|
||||
inline void Copy(const Poly* src) {
|
||||
length = poly_max(length, src->length);
|
||||
Set(src->ptr(), length);
|
||||
}
|
||||
|
||||
inline uint8_t& at(uint8_t i) const {
|
||||
assert(i < _size);
|
||||
return ptr()[i];
|
||||
}
|
||||
|
||||
inline uint8_t id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
inline uint8_t size() const {
|
||||
return _size;
|
||||
}
|
||||
|
||||
// Returns pointer to memory of this polynomial
|
||||
inline uint8_t* ptr() const {
|
||||
assert(_memory && *_memory);
|
||||
return (*_memory) + _offset;
|
||||
}
|
||||
|
||||
uint8_t length;
|
||||
|
||||
protected:
|
||||
|
||||
uint8_t _id;
|
||||
uint8_t _size; // Size of reserved memory for this polynomial
|
||||
uint16_t _offset; // Offset in memory
|
||||
uint8_t** _memory; // Pointer to pointer to memory
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // POLY_H
|
||||
542
src/reed-solomon/rs.hpp
Normal file
542
src/reed-solomon/rs.hpp
Normal file
@@ -0,0 +1,542 @@
|
||||
/* Author: Mike Lubinets (aka mersinvald)
|
||||
* Date: 29.12.15
|
||||
*
|
||||
* See LICENSE */
|
||||
|
||||
#ifndef RS_HPP
|
||||
#define RS_HPP
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include "poly.hpp"
|
||||
#include "gf.hpp"
|
||||
|
||||
#if !defined DEBUG && !defined __CC_ARM
|
||||
#include <assert.h>
|
||||
#else
|
||||
#define assert(dummy)
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace RS {
|
||||
|
||||
#define MSG_CNT 3 // message-length polynomials count
|
||||
#define POLY_CNT 14 // (ecc_length*2)-length polynomialc count
|
||||
|
||||
class ReedSolomon {
|
||||
public:
|
||||
const uint8_t msg_length;
|
||||
const uint8_t ecc_length;
|
||||
|
||||
uint8_t * generator_cache = nullptr;
|
||||
bool generator_cached = false;
|
||||
|
||||
ReedSolomon(uint8_t msg_length_p, uint8_t ecc_length_p) :
|
||||
msg_length(msg_length_p), ecc_length(ecc_length_p) {
|
||||
generator_cache = new uint8_t[ecc_length + 1];
|
||||
|
||||
const uint8_t enc_len = msg_length + ecc_length;
|
||||
const uint8_t poly_len = ecc_length * 2;
|
||||
uint8_t** memptr = &memory;
|
||||
uint16_t offset = 0;
|
||||
|
||||
/* Initialize first six polys manually cause their amount depends on template parameters */
|
||||
|
||||
polynoms[0].Init(ID_MSG_IN, offset, enc_len, memptr);
|
||||
offset += enc_len;
|
||||
|
||||
polynoms[1].Init(ID_MSG_OUT, offset, enc_len, memptr);
|
||||
offset += enc_len;
|
||||
|
||||
for(uint8_t i = ID_GENERATOR; i < ID_MSG_E; i++) {
|
||||
polynoms[i].Init(i, offset, poly_len, memptr);
|
||||
offset += poly_len;
|
||||
}
|
||||
|
||||
polynoms[5].Init(ID_MSG_E, offset, enc_len, memptr);
|
||||
offset += enc_len;
|
||||
|
||||
for(uint8_t i = ID_TPOLY3; i < ID_ERR_EVAL+2; i++) {
|
||||
polynoms[i].Init(i, offset, poly_len, memptr);
|
||||
offset += poly_len;
|
||||
}
|
||||
}
|
||||
|
||||
~ReedSolomon() {
|
||||
delete [] generator_cache;
|
||||
// Dummy destructor, gcc-generated one crashes programm
|
||||
memory = NULL;
|
||||
}
|
||||
|
||||
/* @brief Message block encoding
|
||||
* @param *src - input message buffer (msg_lenth size)
|
||||
* @param *dst - output buffer for ecc (ecc_length size at least) */
|
||||
void EncodeBlock(const void* src, void* dst) {
|
||||
assert(msg_length + ecc_length < 256);
|
||||
|
||||
///* Allocating memory on stack for polynomials storage */
|
||||
//uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];
|
||||
//this->memory = stack_memory;
|
||||
|
||||
// gg : allocation is now on the heap
|
||||
std::vector<uint8_t> stack_memory(MSG_CNT * msg_length + POLY_CNT * ecc_length * 2);
|
||||
this->memory = stack_memory.data();
|
||||
|
||||
const uint8_t* src_ptr = (const uint8_t*) src;
|
||||
uint8_t* dst_ptr = (uint8_t*) dst;
|
||||
|
||||
Poly *msg_in = &polynoms[ID_MSG_IN];
|
||||
Poly *msg_out = &polynoms[ID_MSG_OUT];
|
||||
Poly *gen = &polynoms[ID_GENERATOR];
|
||||
|
||||
// Weird shit, but without reseting msg_in it simply doesn't work
|
||||
msg_in->Reset();
|
||||
msg_out->Reset();
|
||||
|
||||
// Using cached generator or generating new one
|
||||
if(generator_cached) {
|
||||
gen->Set(generator_cache, ecc_length + 1);
|
||||
} else {
|
||||
GeneratorPoly();
|
||||
memcpy(generator_cache, gen->ptr(), gen->length);
|
||||
generator_cached = true;
|
||||
}
|
||||
|
||||
// Copying input message to internal polynomial
|
||||
msg_in->Set(src_ptr, msg_length);
|
||||
msg_out->Set(src_ptr, msg_length);
|
||||
msg_out->length = msg_in->length + ecc_length;
|
||||
|
||||
// Here all the magic happens
|
||||
uint8_t coef = 0; // cache
|
||||
for(uint8_t i = 0; i < msg_length; i++){
|
||||
coef = msg_out->at(i);
|
||||
if(coef != 0){
|
||||
for(uint32_t j = 1; j < gen->length; j++){
|
||||
msg_out->at(i+j) ^= gf::mul(gen->at(j), coef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copying ECC to the output buffer
|
||||
memcpy(dst_ptr, msg_out->ptr()+msg_length, ecc_length * sizeof(uint8_t));
|
||||
}
|
||||
|
||||
/* @brief Message encoding
|
||||
* @param *src - input message buffer (msg_lenth size)
|
||||
* @param *dst - output buffer (msg_length + ecc_length size at least) */
|
||||
void Encode(const void* src, void* dst) {
|
||||
uint8_t* dst_ptr = (uint8_t*) dst;
|
||||
|
||||
// Copying message to the output buffer
|
||||
memcpy(dst_ptr, src, msg_length * sizeof(uint8_t));
|
||||
|
||||
// Calling EncodeBlock to write ecc to out[ut buffer
|
||||
EncodeBlock(src, dst_ptr+msg_length);
|
||||
}
|
||||
|
||||
/* @brief Message block decoding
|
||||
* @param *src - encoded message buffer (msg_length size)
|
||||
* @param *ecc - ecc buffer (ecc_length size)
|
||||
* @param *msg_out - output buffer (msg_length size at least)
|
||||
* @param *erase_pos - known errors positions
|
||||
* @param erase_count - count of known errors
|
||||
* @return RESULT_SUCCESS if successfull, error code otherwise */
|
||||
int DecodeBlock(const void* src, const void* ecc, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {
|
||||
assert(msg_length + ecc_length < 256);
|
||||
|
||||
const uint8_t *src_ptr = (const uint8_t*) src;
|
||||
const uint8_t *ecc_ptr = (const uint8_t*) ecc;
|
||||
uint8_t *dst_ptr = (uint8_t*) dst;
|
||||
|
||||
const uint8_t src_len = msg_length + ecc_length;
|
||||
const uint8_t dst_len = msg_length;
|
||||
|
||||
bool ok;
|
||||
|
||||
///* Allocation memory on stack */
|
||||
//uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];
|
||||
//this->memory = stack_memory;
|
||||
|
||||
// gg : allocation is now on the heap
|
||||
std::vector<uint8_t> stack_memory(MSG_CNT * msg_length + POLY_CNT * ecc_length * 2);
|
||||
this->memory = stack_memory.data();
|
||||
|
||||
Poly *msg_in = &polynoms[ID_MSG_IN];
|
||||
Poly *msg_out = &polynoms[ID_MSG_OUT];
|
||||
Poly *epos = &polynoms[ID_ERASURES];
|
||||
|
||||
// Copying message to polynomials memory
|
||||
msg_in->Set(src_ptr, msg_length);
|
||||
msg_in->Set(ecc_ptr, ecc_length, msg_length);
|
||||
msg_out->Copy(msg_in);
|
||||
|
||||
// Copying known errors to polynomial
|
||||
if(erase_pos == NULL) {
|
||||
epos->length = 0;
|
||||
} else {
|
||||
epos->Set(erase_pos, erase_count);
|
||||
for(uint8_t i = 0; i < epos->length; i++){
|
||||
msg_in->at(epos->at(i)) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Too many errors
|
||||
if(epos->length > ecc_length) return 1;
|
||||
|
||||
Poly *synd = &polynoms[ID_SYNDROMES];
|
||||
Poly *eloc = &polynoms[ID_ERRORS_LOC];
|
||||
Poly *reloc = &polynoms[ID_TPOLY1];
|
||||
Poly *err = &polynoms[ID_ERRORS];
|
||||
Poly *forney = &polynoms[ID_FORNEY];
|
||||
|
||||
// Calculating syndrome
|
||||
CalcSyndromes(msg_in);
|
||||
|
||||
// Checking for errors
|
||||
bool has_errors = false;
|
||||
for(uint8_t i = 0; i < synd->length; i++) {
|
||||
if(synd->at(i) != 0) {
|
||||
has_errors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Going to exit if no errors
|
||||
if(!has_errors) goto return_corrected_msg;
|
||||
|
||||
CalcForneySyndromes(synd, epos, src_len);
|
||||
FindErrorLocator(forney, NULL, epos->length);
|
||||
|
||||
// Reversing syndrome
|
||||
// TODO optimize through special Poly flag
|
||||
reloc->length = eloc->length;
|
||||
for(int8_t i = eloc->length-1, j = 0; i >= 0; i--, j++){
|
||||
reloc->at(j) = eloc->at(i);
|
||||
}
|
||||
|
||||
// Fing errors
|
||||
ok = FindErrors(reloc, src_len);
|
||||
if(!ok) return 1;
|
||||
|
||||
// Error happened while finding errors (so helpfull :D)
|
||||
if(err->length == 0) return 1;
|
||||
|
||||
/* Adding found errors with known */
|
||||
for(uint8_t i = 0; i < err->length; i++) {
|
||||
epos->Append(err->at(i));
|
||||
}
|
||||
|
||||
// Correcting errors
|
||||
CorrectErrata(synd, epos, msg_in);
|
||||
|
||||
return_corrected_msg:
|
||||
// Wrighting corrected message to output buffer
|
||||
msg_out->length = dst_len;
|
||||
memcpy(dst_ptr, msg_out->ptr(), msg_out->length * sizeof(uint8_t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* @brief Message block decoding
|
||||
* @param *src - encoded message buffer (msg_length + ecc_length size)
|
||||
* @param *msg_out - output buffer (msg_length size at least)
|
||||
* @param *erase_pos - known errors positions
|
||||
* @param erase_count - count of known errors
|
||||
* @return RESULT_SUCCESS if successfull, error code otherwise */
|
||||
int Decode(const void* src, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {
|
||||
const uint8_t *src_ptr = (const uint8_t*) src;
|
||||
const uint8_t *ecc_ptr = src_ptr + msg_length;
|
||||
|
||||
return DecodeBlock(src, ecc_ptr, dst, erase_pos, erase_count);
|
||||
}
|
||||
|
||||
#ifndef DEBUG
|
||||
private:
|
||||
#endif
|
||||
|
||||
enum POLY_ID {
|
||||
ID_MSG_IN = 0,
|
||||
ID_MSG_OUT,
|
||||
ID_GENERATOR, // 3
|
||||
ID_TPOLY1, // T for Temporary
|
||||
ID_TPOLY2,
|
||||
|
||||
ID_MSG_E, // 5
|
||||
|
||||
ID_TPOLY3, // 6
|
||||
ID_TPOLY4,
|
||||
|
||||
ID_SYNDROMES,
|
||||
ID_FORNEY,
|
||||
|
||||
ID_ERASURES_LOC,
|
||||
ID_ERRORS_LOC,
|
||||
|
||||
ID_ERASURES,
|
||||
ID_ERRORS,
|
||||
|
||||
ID_COEF_POS,
|
||||
ID_ERR_EVAL
|
||||
};
|
||||
|
||||
// Pointer for polynomials memory on stack
|
||||
uint8_t* memory;
|
||||
Poly polynoms[MSG_CNT + POLY_CNT];
|
||||
|
||||
void GeneratorPoly() {
|
||||
Poly *gen = polynoms + ID_GENERATOR;
|
||||
gen->at(0) = 1;
|
||||
gen->length = 1;
|
||||
|
||||
Poly *mulp = polynoms + ID_TPOLY1;
|
||||
Poly *temp = polynoms + ID_TPOLY2;
|
||||
mulp->length = 2;
|
||||
|
||||
for(int8_t i = 0; i < ecc_length; i++){
|
||||
mulp->at(0) = 1;
|
||||
mulp->at(1) = gf::pow(2, i);
|
||||
|
||||
gf::poly_mul(gen, mulp, temp);
|
||||
|
||||
gen->Copy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void CalcSyndromes(const Poly *msg) {
|
||||
Poly *synd = &polynoms[ID_SYNDROMES];
|
||||
synd->length = ecc_length+1;
|
||||
synd->at(0) = 0;
|
||||
for(uint8_t i = 1; i < ecc_length+1; i++){
|
||||
synd->at(i) = gf::poly_eval(msg, gf::pow(2, i-1));
|
||||
}
|
||||
}
|
||||
|
||||
void FindErrataLocator(const Poly *epos) {
|
||||
Poly *errata_loc = &polynoms[ID_ERASURES_LOC];
|
||||
Poly *mulp = &polynoms[ID_TPOLY1];
|
||||
Poly *addp = &polynoms[ID_TPOLY2];
|
||||
Poly *apol = &polynoms[ID_TPOLY3];
|
||||
Poly *temp = &polynoms[ID_TPOLY4];
|
||||
|
||||
errata_loc->length = 1;
|
||||
errata_loc->at(0) = 1;
|
||||
|
||||
mulp->length = 1;
|
||||
addp->length = 2;
|
||||
|
||||
for(uint8_t i = 0; i < epos->length; i++){
|
||||
mulp->at(0) = 1;
|
||||
addp->at(0) = gf::pow(2, epos->at(i));
|
||||
addp->at(1) = 0;
|
||||
|
||||
gf::poly_add(mulp, addp, apol);
|
||||
gf::poly_mul(errata_loc, apol, temp);
|
||||
|
||||
errata_loc->Copy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void FindErrorEvaluator(const Poly *synd, const Poly *errata_loc, Poly *dst, uint8_t ecclen) {
|
||||
Poly *mulp = &polynoms[ID_TPOLY1];
|
||||
gf::poly_mul(synd, errata_loc, mulp);
|
||||
|
||||
Poly *divisor = &polynoms[ID_TPOLY2];
|
||||
divisor->length = ecclen+2;
|
||||
|
||||
divisor->Reset();
|
||||
divisor->at(0) = 1;
|
||||
|
||||
gf::poly_div(mulp, divisor, dst);
|
||||
}
|
||||
|
||||
void CorrectErrata(const Poly *synd, const Poly *err_pos, const Poly *msg_in) {
|
||||
Poly *c_pos = &polynoms[ID_COEF_POS];
|
||||
Poly *corrected = &polynoms[ID_MSG_OUT];
|
||||
c_pos->length = err_pos->length;
|
||||
|
||||
for(uint8_t i = 0; i < err_pos->length; i++)
|
||||
c_pos->at(i) = msg_in->length - 1 - err_pos->at(i);
|
||||
|
||||
/* uses t_poly 1, 2, 3, 4 */
|
||||
FindErrataLocator(c_pos);
|
||||
Poly *errata_loc = &polynoms[ID_ERASURES_LOC];
|
||||
|
||||
/* reversing syndromes */
|
||||
Poly *rsynd = &polynoms[ID_TPOLY3];
|
||||
rsynd->length = synd->length;
|
||||
|
||||
for(int8_t i = synd->length-1, j = 0; i >= 0; i--, j++) {
|
||||
rsynd->at(j) = synd->at(i);
|
||||
}
|
||||
|
||||
/* getting reversed error evaluator polynomial */
|
||||
Poly *re_eval = &polynoms[ID_TPOLY4];
|
||||
|
||||
/* uses T_POLY 1, 2 */
|
||||
FindErrorEvaluator(rsynd, errata_loc, re_eval, errata_loc->length-1);
|
||||
|
||||
/* reversing it back */
|
||||
Poly *e_eval = &polynoms[ID_ERR_EVAL];
|
||||
e_eval->length = re_eval->length;
|
||||
for(int8_t i = re_eval->length-1, j = 0; i >= 0; i--, j++) {
|
||||
e_eval->at(j) = re_eval->at(i);
|
||||
}
|
||||
|
||||
Poly *X = &polynoms[ID_TPOLY1]; /* this will store errors positions */
|
||||
X->length = 0;
|
||||
|
||||
int16_t l;
|
||||
for(uint8_t i = 0; i < c_pos->length; i++){
|
||||
l = 255 - c_pos->at(i);
|
||||
X->Append(gf::pow(2, -l));
|
||||
}
|
||||
|
||||
/* Magnitude polynomial
|
||||
Shit just got real */
|
||||
Poly *E = &polynoms[ID_MSG_E];
|
||||
E->Reset();
|
||||
E->length = msg_in->length;
|
||||
|
||||
uint8_t Xi_inv;
|
||||
|
||||
Poly *err_loc_prime_temp = &polynoms[ID_TPOLY2];
|
||||
|
||||
uint8_t err_loc_prime;
|
||||
uint8_t y;
|
||||
|
||||
for(uint8_t i = 0; i < X->length; i++){
|
||||
Xi_inv = gf::inverse(X->at(i));
|
||||
|
||||
err_loc_prime_temp->length = 0;
|
||||
for(uint8_t j = 0; j < X->length; j++){
|
||||
if(j != i){
|
||||
err_loc_prime_temp->Append(gf::sub(1, gf::mul(Xi_inv, X->at(j))));
|
||||
}
|
||||
}
|
||||
|
||||
err_loc_prime = 1;
|
||||
for(uint8_t j = 0; j < err_loc_prime_temp->length; j++){
|
||||
err_loc_prime = gf::mul(err_loc_prime, err_loc_prime_temp->at(j));
|
||||
}
|
||||
|
||||
y = gf::poly_eval(re_eval, Xi_inv);
|
||||
y = gf::mul(gf::pow(X->at(i), 1), y);
|
||||
|
||||
E->at(err_pos->at(i)) = gf::div(y, err_loc_prime);
|
||||
}
|
||||
|
||||
gf::poly_add(msg_in, E, corrected);
|
||||
}
|
||||
|
||||
bool FindErrorLocator(const Poly *synd, Poly *erase_loc = NULL, size_t erase_count = 0) {
|
||||
Poly *error_loc = &polynoms[ID_ERRORS_LOC];
|
||||
Poly *err_loc = &polynoms[ID_TPOLY1];
|
||||
Poly *old_loc = &polynoms[ID_TPOLY2];
|
||||
Poly *temp = &polynoms[ID_TPOLY3];
|
||||
Poly *temp2 = &polynoms[ID_TPOLY4];
|
||||
|
||||
if(erase_loc != NULL) {
|
||||
err_loc->Copy(erase_loc);
|
||||
old_loc->Copy(erase_loc);
|
||||
} else {
|
||||
err_loc->length = 1;
|
||||
old_loc->length = 1;
|
||||
err_loc->at(0) = 1;
|
||||
old_loc->at(0) = 1;
|
||||
}
|
||||
|
||||
uint8_t synd_shift = 0;
|
||||
if(synd->length > ecc_length) {
|
||||
synd_shift = synd->length - ecc_length;
|
||||
}
|
||||
|
||||
uint8_t K = 0;
|
||||
uint8_t delta = 0;
|
||||
uint8_t index;
|
||||
|
||||
for(uint8_t i = 0; i < ecc_length - erase_count; i++){
|
||||
if(erase_loc != NULL)
|
||||
K = erase_count + i + synd_shift;
|
||||
else
|
||||
K = i + synd_shift;
|
||||
|
||||
delta = synd->at(K);
|
||||
for(uint8_t j = 1; j < err_loc->length; j++) {
|
||||
index = err_loc->length - j - 1;
|
||||
delta ^= gf::mul(err_loc->at(index), synd->at(K-j));
|
||||
}
|
||||
|
||||
old_loc->Append(0);
|
||||
|
||||
if(delta != 0) {
|
||||
if(old_loc->length > err_loc->length) {
|
||||
gf::poly_scale(old_loc, temp, delta);
|
||||
gf::poly_scale(err_loc, old_loc, gf::inverse(delta));
|
||||
err_loc->Copy(temp);
|
||||
}
|
||||
gf::poly_scale(old_loc, temp, delta);
|
||||
gf::poly_add(err_loc, temp, temp2);
|
||||
err_loc->Copy(temp2);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t shift = 0;
|
||||
while(err_loc->length && err_loc->at(shift) == 0) shift++;
|
||||
|
||||
uint32_t errs = err_loc->length - shift - 1;
|
||||
if(((errs - erase_count) * 2 + erase_count) > ecc_length){
|
||||
return false; /* Error count is greater then we can fix! */
|
||||
}
|
||||
|
||||
memcpy(error_loc->ptr(), err_loc->ptr() + shift, (err_loc->length - shift) * sizeof(uint8_t));
|
||||
error_loc->length = (err_loc->length - shift);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FindErrors(const Poly *error_loc, size_t msg_in_size) {
|
||||
Poly *err = &polynoms[ID_ERRORS];
|
||||
|
||||
uint8_t errs = error_loc->length - 1;
|
||||
err->length = 0;
|
||||
|
||||
for(uint8_t i = 0; i < msg_in_size; i++) {
|
||||
if(gf::poly_eval(error_loc, gf::pow(2, i)) == 0) {
|
||||
err->Append(msg_in_size - 1 - i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Sanity check:
|
||||
* the number of err/errata positions found
|
||||
* should be exactly the same as the length of the errata locator polynomial */
|
||||
if(err->length != errs)
|
||||
/* couldn't find error locations */
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CalcForneySyndromes(const Poly *synd, const Poly *erasures_pos, size_t msg_in_size) {
|
||||
Poly *erase_pos_reversed = &polynoms[ID_TPOLY1];
|
||||
Poly *forney_synd = &polynoms[ID_FORNEY];
|
||||
erase_pos_reversed->length = 0;
|
||||
|
||||
for(uint8_t i = 0; i < erasures_pos->length; i++){
|
||||
erase_pos_reversed->Append(msg_in_size - 1 - erasures_pos->at(i));
|
||||
}
|
||||
|
||||
forney_synd->Reset();
|
||||
forney_synd->Set(synd->ptr()+1, synd->length-1);
|
||||
|
||||
uint8_t x;
|
||||
for(uint8_t i = 0; i < erasures_pos->length; i++) {
|
||||
x = gf::pow(2, erase_pos_reversed->at(i));
|
||||
for(int8_t j = 0; j < forney_synd->length - 1; j++){
|
||||
forney_synd->at(j) = gf::mul(forney_synd->at(j), x) ^ forney_synd->at(j+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // RS_HPP
|
||||
|
||||
Reference in New Issue
Block a user