Repository: ggerganov/ggwave Branch: master Commit: 8f02c41d88f9 Files: 154 Total size: 1.2 MB Directory structure: gitextract_1zhnvrim/ ├── .clang-format ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README-tmpl.md ├── README.md ├── bindings/ │ ├── CMakeLists.txt │ ├── javascript/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── emscripten.cpp │ │ ├── ggwave.js │ │ ├── package-tmpl.json │ │ └── package.json │ └── python/ │ ├── .gitignore │ ├── MANIFEST.in │ ├── Makefile │ ├── README-tmpl.rst │ ├── README.md │ ├── cggwave.pxd │ ├── debian/ │ │ ├── changelog │ │ ├── control │ │ ├── copyright │ │ ├── python3-ggwave.docs │ │ ├── rules │ │ └── source/ │ │ ├── format │ │ └── options │ ├── ggwave.pyx │ ├── setup-tmpl.py │ ├── setup.py │ └── test.py ├── cmake/ │ ├── BuildTypes.cmake │ ├── GitVars.cmake │ └── sdl2/ │ └── FindSDL2.cmake ├── examples/ │ ├── CMakeLists.txt │ ├── arduino-rx/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── arduino-rx.ino │ │ └── fritzing-sketch.fzz │ ├── arduino-rx-web/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── arduino-rx-web.cpp │ │ └── index-tmpl.html │ ├── arduino-tx/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── arduino-tx.ino │ │ └── fritzing-sketch.fzz │ ├── arduino-tx-obsolete/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── arduino-tx.ino │ │ └── ggwave.h │ ├── buttons/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── index-tmpl.html │ ├── dr_wav.h │ ├── esp32-rx/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── esp32-rx.ino │ │ └── fritzing-sketch.fzz │ ├── ggwave-cli/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── main.cpp │ ├── ggwave-common-sdl2.cpp │ ├── ggwave-common-sdl2.h │ ├── ggwave-common.cpp │ ├── ggwave-common.h │ ├── ggwave-from-file/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── main.cpp │ ├── ggwave-js/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── index-tmpl.html │ ├── ggwave-py/ │ │ ├── README.md │ │ ├── receive.py │ │ └── send.py │ ├── ggwave-rx/ │ │ ├── CMakeLists.txt │ │ └── main.cpp │ ├── ggwave-to-file/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── ggwave-to-file-local.py │ │ ├── ggwave-to-file.php │ │ ├── ggwave-to-file.py │ │ └── main.cpp │ ├── ggwave-wasm/ │ │ ├── CMakeLists.txt │ │ ├── build_timestamp-tmpl.h │ │ ├── index-tmpl.html │ │ ├── main.cpp │ │ ├── main.js │ │ └── style.css │ ├── icons_font_awesome.h │ ├── pfd/ │ │ ├── COPYING │ │ └── pfd.h │ ├── r2t2/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── build_timestamp-tmpl.h │ │ ├── index-tmpl.html │ │ ├── main.cpp │ │ ├── main.js │ │ ├── r2t2-rx.cpp │ │ └── style.css │ ├── rp2040-rx/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── fritzing-sketch.fzz │ │ ├── mic-analog.cpp │ │ ├── mic-analog.h │ │ └── rp2040-rx.ino │ ├── spectrogram/ │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── build_timestamp-tmpl.h │ │ ├── index-tmpl.html │ │ ├── main.cpp │ │ └── style.css │ ├── third-party/ │ │ ├── CMakeLists.txt │ │ └── imgui/ │ │ ├── CMakeLists.txt │ │ └── imgui-extra/ │ │ ├── imgui_impl.cpp │ │ ├── imgui_impl.h │ │ ├── imgui_impl_opengl3.cpp │ │ ├── imgui_impl_opengl3.h │ │ ├── imgui_impl_sdl.cpp │ │ └── imgui_impl_sdl.h │ └── waver/ │ ├── CMakeLists.txt │ ├── README.md │ ├── build_timestamp-tmpl.h │ ├── common.cpp │ ├── common.h │ ├── index-tmpl.html │ ├── interface-emscripten.cpp │ ├── interface-unix.cpp │ ├── interface.cpp │ ├── interface.h │ ├── main.cpp │ └── style.css ├── include/ │ └── ggwave/ │ └── ggwave.h ├── media/ │ └── favicons-waver/ │ ├── browserconfig.xml │ └── manifest.json ├── snap/ │ └── snapcraft.yaml ├── src/ │ ├── CMakeLists.txt │ ├── fft.h │ ├── ggwave.cpp │ └── reed-solomon/ │ ├── LICENSE │ ├── gf.hpp │ ├── poly.hpp │ └── rs.hpp └── tests/ ├── CMakeLists.txt ├── test-ggwave.c ├── test-ggwave.cpp ├── test-ggwave.js └── test-ggwave.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- BasedOnStyle: Google IndentWidth: '4' DerivePointerAlignment: false PointerAlignment: Middle ColumnLimit: 160 AllowShortFunctionsOnASingleLine: false AllowShortBlocksOnASingleLine: 'false' AllowShortCaseLabelsOnASingleLine: 'false' AllowShortIfStatementsOnASingleLine: 'false' AllowShortLoopsOnASingleLine: 'false' SpaceAfterCStyleCast: 'true' ... ================================================ FILE: .github/workflows/build.yml ================================================ name: CI on: [push] jobs: ubuntu-24_04-python3-ggwave-deb: runs-on: ubuntu-24.04 steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies run: | sudo apt update sudo apt install cython3 build-essential debhelper-compat sudo apt install dh-python python3-all-dev python3-build sudo apt install python3-cogapp python3-venv - name: Build Debian package for Python bindings run: | cd bindings/python make deb ubuntu-24_04-libggwave-dev-deb: runs-on: ubuntu-24.04 strategy: matrix: build: [Release] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies run: | sudo apt update sudo apt install build-essential - name: Build Debian package for libggwave run: | cmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=${{ matrix.build }} make cpack - name: Inspect generated Debian package run: | dpkg --contents dist/libggwave-dev_*.deb dpkg -I dist/libggwave-dev_*.deb sha256sum dist/libggwave-dev_*.deb ubuntu-24_04-target-deb: runs-on: ubuntu-24.04 strategy: matrix: build: [Release] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies run: | sudo apt update sudo apt install cython3 build-essential debhelper-compat sudo apt install dh-python python3-all-dev python3-build sudo apt install python3-cogapp python3-venv - name: Build Debian packages run: | cmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=${{ matrix.build }} make deb - name: Inspect generated Debian packages run: | dpkg --contents dist/libggwave*.deb dpkg -I dist/libggwave*.deb dpkg --contents dist/python3*.deb dpkg -I dist/python3*.deb sha256sum dist/*.deb ubuntu-24_04-gcc: runs-on: ubuntu-24.04 strategy: matrix: build: [Debug, Release] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies run: | sudo apt update sudo apt install build-essential xorg-dev libglu1-mesa-dev sudo apt install cmake sudo apt install libsdl2-dev - name: Configure run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} - name: Build run: | make ctest --output-on-failure ./bin/test-ggwave-cpp --full ubuntu-24_04-python: runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: build: [Release] python-version: [3.8.18, 3.9.20, 3.11.11, 3.12.9] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Dependencies run: | sudo apt update sudo apt install build-essential sudo apt install cmake python3 -m venv venv source venv/bin/activate && pip install setuptools cython cogapp - name: Configure run: cmake . -DGGWAVE_SUPPORT_PYTHON=ON -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=${{ matrix.build }} - name: Build run: | source venv/bin/activate make ctest --output-on-failure ubuntu-24_04-gcc-sanitized: runs-on: ubuntu-24.04 strategy: matrix: sanitizer: [ADDRESS, THREAD, UNDEFINED] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies run: | sudo apt update sudo apt install build-essential sudo apt install cmake - name: Configure run: cmake . -DCMAKE_BUILD_TYPE=Debug -DGGWAVE_BUILD_EXAMPLES=OFF -DGGWAVE_SANITIZE_${{ matrix.sanitizer }}=ON - name: Build run: | make ctest --output-on-failure ubuntu-24_04-clang: runs-on: ubuntu-24.04 strategy: matrix: build: [Release] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies run: | sudo apt update sudo apt install build-essential xorg-dev libglu1-mesa-dev sudo apt install cmake sudo apt install libsdl2-dev - name: Configure run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang - name: Build run: | make ctest --output-on-failure macOS-latest: runs-on: macOS-latest strategy: matrix: build: [Release] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies run: | brew update brew install sdl2 - name: Configure run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} - name: Build run: | make ctest --output-on-failure emscripten: runs-on: ubuntu-24.04 strategy: matrix: build: [Release] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - 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 && ctest --output-on-failure windows-msys2: runs-on: windows-latest defaults: run: shell: msys2 {0} strategy: matrix: build: [Release] steps: - name: Clone uses: actions/checkout@v1 with: submodules: recursive - name: Dependencies uses: msys2/setup-msys2@v2 with: msystem: MINGW64 update: true install: make mingw-w64-x86_64-cmake mingw-w64-x86_64-dlfcn mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2 mingw-w64-x86_64-cmake - name: Configure run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} -G "Unix Makefiles" - name: Build run: | ls -alh . make ctest --output-on-failure ================================================ FILE: .gitignore ================================================ build build-* compile_commands.json .exrc .clangd .cache .vimspector.json .*.swp .ycm_extra_conf.py .DS_Store ================================================ FILE: .gitmodules ================================================ [submodule "examples/third-party/imgui/imgui"] path = examples/third-party/imgui/imgui url = https://github.com/ocornut/imgui [submodule "examples/third-party/ggsock"] path = examples/third-party/ggsock url = https://github.com/ggerganov/ggsock [submodule "bindings/ios"] path = bindings/ios url = https://github.com/ggerganov/ggwave-spm ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [Unreleased] ## [v0.4.0] - 2022-07-05 **This release introduces some breaking changes in the C and C++ API!** Make sure to read the `ggwave.h` header for more information - Major refactoring in order to support microcontrollers ([#65](https://github.com/ggerganov/ggwave/pull/65) - Zero memory allocations during runtime - Do not include STL headers anymore - New, low-frequency, mono-tone (MT) protocols suitable for microcontrollers - Remove code-duplication for some of the examples - Better FFT implementation - Less memory usage - Bug fix in fixed-length payload decoding - Add Arduino and ESP32 examples - Support for Direct Sequence Spread (DSS) ## [v0.3.1] - 2021-11-27 - Add interface for changing ggwave's internal logging ([#52](https://github.com/ggerganov/ggwave/pull/52), [#55](https://github.com/ggerganov/ggwave/pull/55)) - Fix out-of-bounds access in `ggwave_decode` ([#53](https://github.com/ggerganov/ggwave/pull/53)) - Add C interface for selecting Rx protocols ([#60](https://github.com/ggerganov/ggwave/pull/60)) ## [v0.3.0] - 2021-07-03 - Resampling fixes - Add `soundMarkerThreshold` parameter ([f4fb02d](https://github.com/ggerganov/ggwave/commit/f4fb02d5d4cfd6c1021d73b55a0e52ac9d3dbdfa)) - Sampling rates are now consistently represented as float instead of int - Add option to query the generated tones ([ba87a65](https://github.com/ggerganov/ggwave/commit/ba87a651e3e27ce3fa9a85d53ca988a0cedd2e46)) - Fix python build on Windows ([d73b184](https://github.com/ggerganov/ggwave/commit/d73b18426bf0df0e610c31c948e0ddf9a0784073)) ## [v0.2.0] - 2021-02-20 - Supported sampling rates: 6kHz - 96kHz - Variable-length payloads - Fixed-length payloads (no sound markers emitted) - Reed-Solomon based ECC - Ultrasound support [unreleased]: https://github.com/ggerganov/ggwave/compare/ggwave-v0.4.0...HEAD [v0.4.0]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.4.0 [v0.3.1]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.3.1 [v0.3.0]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.3.0 [v0.2.0]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.2.0 ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required (VERSION 3.10) project(ggwave VERSION 0.4.2) set(GGWAVE_VERSION_PYTHON 0.4.2) set(CMAKE_EXPORT_COMPILE_COMMANDS "on") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) set(GGWAVE_STANDALONE ON) include(cmake/GitVars.cmake) include(cmake/BuildTypes.cmake) # configure project version configure_file(${CMAKE_SOURCE_DIR}/README-tmpl.md ${CMAKE_SOURCE_DIR}/README.md @ONLY) configure_file(${CMAKE_SOURCE_DIR}/bindings/python/setup-tmpl.py ${CMAKE_SOURCE_DIR}/bindings/python/setup.py @ONLY) configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/package-tmpl.json ${CMAKE_SOURCE_DIR}/bindings/javascript/package.json @ONLY) configure_file(${CMAKE_SOURCE_DIR}/bindings/ios/Makefile-tmpl ${CMAKE_SOURCE_DIR}/bindings/ios/Makefile @ONLY) else() set(GGWAVE_STANDALONE OFF) endif() if (EMSCRIPTEN) set(GGWAVE_SUPPORT_SDL2_DEFAULT ON) set(BUILD_SHARED_LIBS_DEFAULT OFF) option(GGWAVE_WASM_SINGLE_FILE "ggwave: embed WASM inside the generated ggwave.js" ON) else() set(GGWAVE_SUPPORT_SDL2_DEFAULT ON) if (WIN32) set(BUILD_SHARED_LIBS_DEFAULT OFF) else() set(BUILD_SHARED_LIBS_DEFAULT ON) endif() endif() # options option(BUILD_SHARED_LIBS "ggwave: build shared libs" ${BUILD_SHARED_LIBS_DEFAULT}) option(USE_FINDSDL2 "ggwave: use the FindSDL2.cmake script" OFF) option(GGWAVE_ALL_WARNINGS "ggwave: enable all compiler warnings" ON) option(GGWAVE_ALL_WARNINGS_3RD_PARTY "ggwave: enable all compiler warnings in 3rd party libs" ON) option(GGWAVE_SANITIZE_THREAD "ggwave: enable thread sanitizer" OFF) option(GGWAVE_SANITIZE_ADDRESS "ggwave: enable address sanitizer" OFF) option(GGWAVE_SANITIZE_UNDEFINED "ggwave: enable undefined sanitizer" OFF) option(GGWAVE_SUPPORT_SDL2 "ggwave: support for libSDL2" ${GGWAVE_SUPPORT_SDL2_DEFAULT}) option(GGWAVE_SUPPORT_PYTHON "ggwave: support for python" OFF) option(GGWAVE_SUPPORT_SWIFT "ggwave: support for swift" OFF) option(GGWAVE_BUILD_TESTS "ggwave: build examples" ${GGWAVE_STANDALONE}) option(GGWAVE_BUILD_EXAMPLES "ggwave: 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 -D_GLIBCXX_DEBUG") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -D_GLIBCXX_DEBUG") 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_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") else() # todo : windows endif() endif() add_subdirectory(src) add_subdirectory(bindings) if (GGWAVE_BUILD_TESTS) enable_testing() add_subdirectory(tests) endif() if (GGWAVE_BUILD_EXAMPLES) add_subdirectory(examples) endif() set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) set(CPACK_DEB_COMPONENT_INSTALL YES) set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/ggerganov/ggwave") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Georgi Gerganov") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES) set(CPACK_GENERATOR DEB) SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/dist") set(CPACK_PACKAGE_CONTACT "ggerganov@gmail.com") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Tiny data-over-sound library") set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) set(CPACK_PACKAGE_NAME "libggwave-dev") set(CPACK_PACKAGE_VENDOR "ggwave") set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(CPACK_STRIP_FILES YES) set(CPACK_VERBATIM_VARIABLES YES) include(CPack) set(DEB_PYTHON_BUILD_DIR "${CMAKE_SOURCE_DIR}/bindings/python/") set(DEB_PYTHON_OUTPUT_FILE_PREFIX "${DEB_PYTHON_BUILD_DIR}/dist") set(ENV{SOURCE_DATE_EPOCH} "0") execute_process( COMMAND git log -1 --pretty=%ct WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} TIMEOUT 1 OUTPUT_VARIABLE SOURCE_DATE_EPOCH OUTPUT_STRIP_TRAILING_WHITESPACE ) set(ENV{SOURCE_DATE_EPOCH} ${SOURCE_DATE_EPOCH}) add_custom_target(deb_python_binding COMMAND cd ${DEB_PYTHON_BUILD_DIR} && make deb COMMENT "Building Debian package for Python binding" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) add_custom_target(deb_c_library COMMAND SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} cpack COMMENT "Building Debian package for C library" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) add_custom_target(deb_python_move COMMAND mv ${DEB_PYTHON_OUTPUT_FILE_PREFIX}/*.deb ${CPACK_OUTPUT_FILE_PREFIX}/ COMMENT "Moving Debian package for Python binding into ${CPACK_OUTPUT_FILE_PREFIX}/" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS deb_python_binding deb_c_library ) add_custom_target(deb COMMAND sha256sum ${CPACK_OUTPUT_FILE_PREFIX}/* COMMENT "Debian package sha256 hashes" DEPENDS deb_c_library deb_python_binding deb_python_move WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) add_custom_command(TARGET deb POST_BUILD COMMAND ls -alh ${CPACK_OUTPUT_FILE_PREFIX}/* COMMENT "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 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. ================================================ FILE: README-tmpl.md ================================================ # ggwave [![Actions Status](https://github.com/ggerganov/ggwave/workflows/CI/badge.svg)](https://github.com/ggerganov/ggwave/actions) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![ggwave badge][changelog-badge]][changelog] [![pypi](https://img.shields.io/pypi/v/ggwave.svg)](https://pypi.org/project/ggwave/) [![npm](https://img.shields.io/npm/v/ggwave.svg)](https://www.npmjs.com/package/ggwave/) Tiny data-over-sound library. Click on the images below to hear what it sounds like: https://user-images.githubusercontent.com/1991296/166411509-5e1b9bcb-3655-40b1-9dc3-9bec72889dcf.mp4 https://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4 ## Details This library allows you to communicate small amounts of data between air-gapped devices using sound. It implements a simple FSK-based 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 audio samples. Here is a list of possible applications of **ggwave** with a few examples: - **Serverless, one-to-many broadcast** - [wave-share](https://github.com/ggerganov/wave-share) - file sharing through sound - **Internet of Things** - [esp32-rx](https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx), [arduino-rx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx), [rp2040-rx](https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx), [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) - Sand and receive sound data on microcontrollers - [r2t2](https://github.com/ggerganov/ggwave/tree/master/examples/r2t2) - Transmit data with the PC speaker - [buttons](https://github.com/ggerganov/ggwave/tree/master/examples/buttons) - Record and send commands via [Talking buttons](https://github.com/ggerganov/ggwave/discussions/27) - **Audio QR codes** - [[Twitter]](https://twitter.com/ggerganov/status/1509558482567057417) - Broadcast your clipboard to nearby devices - **Device pairing** - **Authorization** ## Try it out You can easily test the library using the free [waver](https://github.com/ggerganov/ggwave/tree/master/examples/waver) application which is available on the following platforms: Download on the App Store Get it on Google Play Get it from the Snap Store ### Browser demos - https://waver.ggerganov.com - https://ggwave.ggerganov.com - https://ggwave-js.ggerganov.com ### [HTTP service](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file/README.md#http-service) ```bash # audible example curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!' --output hello.wav # ultrasound example curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!&p=4' --output hello.wav ``` ## Technical details Below is a short summary of the modulation and demodulation algorithm used in `ggwave` for encoding and decoding data into sound. ### 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 ([#13](https://github.com/ggerganov/ggwave/discussions/13)). 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: | Example | Description | Audio | | ------- | ----------- | ----- | | [ggtag](https://github.com/rgerganov/ggtag) | Sound-programmable e-paper badge | PDM mic | | [ggwave-rx](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-rx) | Very basic receive-only program | SDL | | [ggwave-cli](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-cli) | Command line tool for sending/receiving data through sound | SDL | | [ggwave-wasm](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-wasm) | WebAssembly module for web applications | SDL | | [ggwave-to-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file) | Output a generated waveform to an uncompressed WAV file | - | | [ggwave-from-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-from-file) | Decode a waveform from an uncompressed WAV file | - | | [waver](https://github.com/ggerganov/ggwave/blob/master/examples/waver) | GUI application for sending/receiving data through sound | SDL | | [ggwave-py](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-py) | Python examples | PortAudio | | [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API | | [spectrogram](https://github.com/ggerganov/ggwave/blob/master/examples/spectrogram) | Spectrogram tool | SDL | | [ggweb-spike](https://gitlab.com/commonsguy/ggweb-spike) | Android example using a `WebView` to wrap `ggwave` into a simple app | WebAudio | | [buttons](https://github.com/ggerganov/ggwave/blob/master/examples/buttons) | Record and send commands via Talking buttons | Web Audio API | | [r2t2](https://github.com/ggerganov/ggwave/blob/master/examples/r2t2) | Transmit data through the PC speaker | PC speaker | | [ggwave-objc](https://github.com/ggerganov/ggwave-objc) | Minimal Objective-C iOS app using ggwave | AudioToolbox | | [ggwave-java](https://github.com/ggerganov/ggwave-java) | Minimal Java Android app using ggwave | android.media | | [ggwave-fm](https://github.com/rgerganov/ggwave-fm) | Transmit ggwave messages with HackRF | Radio | | [esp32-rx](https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx) | Transmit and receive messages using ESP32 | - | | [rp2040-rx](https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx) | Transmit and receive messages using Raspberry Pi Pico (RP2040) | - | | [arduino-rx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx) | Transmit and receive messages using Arduino RP2040 | - | | [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) | Transmit messages using Arduino Uno | - | | [arduino-rx-web](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx-web) | Receive messages from Arduino Uno | Web Audio API | 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 ### Dependencies for SDL-based examples [Ubuntu] $ sudo apt install libsdl2-dev [Mac OS with brew] $ brew install sdl2 [MSYS2] $ pacman -S git cmake make mingw-w64-x86_64-dlfcn mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2 ### Linux, Mac, Windows (MSYS2) ```bash # build git clone https://github.com/ggerganov/ggwave --recursive cd ggwave && mkdir build && cd build cmake .. make # running ./bin/ggwave-cli ``` #### Local Debian packages Build reproducible `libggwave-dev` and `python3-ggwave` Debian packages: ```bash # Fetch source git clone https://github.com/ggerganov/ggwave --recursive cd ggwave # Configure cmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release # Build make deb # Install sudo dpkg -i dist/*.deb ``` ### Emscripten ```bash git clone https://github.com/ggerganov/ggwave --recursive cd ggwave mkdir build && cd build emcmake cmake .. make ``` ### Python ```bash pip install ggwave ``` More info: https://pypi.org/project/ggwave/ ### Node.js ```bash npm install ggwave ``` More info: https://www.npmjs.com/package/ggwave ### iOS Available as a Swift Package: https://github.com/ggerganov/ggwave-spm ## Installing the Waver application [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/waver) ### Linux ```bash sudo snap install waver sudo snap connect waver:audio-record :audio-record ``` ### Mac OS ```bash brew install ggerganov/ggerganov/waver ``` [changelog]: ./CHANGELOG.md [changelog-badge]: https://img.shields.io/badge/changelog-ggwave%20v@PROJECT_VERSION@-dummy [license]: ./LICENSE ================================================ FILE: README.md ================================================ # ggwave [![Actions Status](https://github.com/ggerganov/ggwave/workflows/CI/badge.svg)](https://github.com/ggerganov/ggwave/actions) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![ggwave badge][changelog-badge]][changelog] [![pypi](https://img.shields.io/pypi/v/ggwave.svg)](https://pypi.org/project/ggwave/) [![npm](https://img.shields.io/npm/v/ggwave.svg)](https://www.npmjs.com/package/ggwave/) Tiny data-over-sound library. Click on the images below to hear what it sounds like: https://user-images.githubusercontent.com/1991296/166411509-5e1b9bcb-3655-40b1-9dc3-9bec72889dcf.mp4 https://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4 ## Details This library allows you to communicate small amounts of data between air-gapped devices using sound. It implements a simple FSK-based 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 audio samples. Here is a list of possible applications of **ggwave** with a few examples: - **Serverless, one-to-many broadcast** - [wave-share](https://github.com/ggerganov/wave-share) - file sharing through sound - **Internet of Things** - [esp32-rx](https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx), [arduino-rx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx), [rp2040-rx](https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx), [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) - Send and receive sound data on microcontrollers - [r2t2](https://github.com/ggerganov/ggwave/tree/master/examples/r2t2) - Transmit data with the PC speaker - [buttons](https://github.com/ggerganov/ggwave/tree/master/examples/buttons) - Record and send commands via [Talking buttons](https://github.com/ggerganov/ggwave/discussions/27) - **Audio QR codes** - [[Twitter]](https://twitter.com/ggerganov/status/1509558482567057417) - Broadcast your clipboard to nearby devices - **Device pairing / Contact exchange** - [PairSonic](https://github.com/seemoo-lab/pairsonic) - Exchange contact information and public keys with nearby devices - **Authorization** ## Try it out You can easily test the library using the free [waver](https://github.com/ggerganov/ggwave/tree/master/examples/waver) application which is available on the following platforms: Download on the App Store Get it on Google Play Get it from the Snap Store ### Browser demos - https://waver.ggerganov.com - https://ggwave.ggerganov.com - https://ggwave-js.ggerganov.com ### [HTTP service](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file/README.md#http-service) ```bash # audible example curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!' --output hello.wav # ultrasound example curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!&p=4' --output hello.wav ``` ## Technical details Below is a short summary of the modulation and demodulation algorithm used in `ggwave` for encoding and decoding data into sound. ### 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 ([#13](https://github.com/ggerganov/ggwave/discussions/13)). 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: | Example | Description | Audio | | ------- | ----------- | ----- | | [ggtag](https://github.com/rgerganov/ggtag) | Sound-programmable e-paper badge | PDM mic | | [ggwave-rx](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-rx) | Very basic receive-only program | SDL | | [ggwave-cli](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-cli) | Command line tool for sending/receiving data through sound | SDL | | [ggwave-wasm](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-wasm) | WebAssembly module for web applications | SDL | | [ggwave-to-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file) | Output a generated waveform to an uncompressed WAV file | - | | [ggwave-from-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-from-file) | Decode a waveform from an uncompressed WAV file | - | | [waver](https://github.com/ggerganov/ggwave/blob/master/examples/waver) | GUI application for sending/receiving data through sound | SDL | | [ggwave-py](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-py) | Python examples | PortAudio | | [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API | | [spectrogram](https://github.com/ggerganov/ggwave/blob/master/examples/spectrogram) | Spectrogram tool | SDL | | [ggweb-spike](https://gitlab.com/commonsguy/ggweb-spike) | Android example using a `WebView` to wrap `ggwave` into a simple app | WebAudio | | [buttons](https://github.com/ggerganov/ggwave/blob/master/examples/buttons) | Record and send commands via Talking buttons | Web Audio API | | [r2t2](https://github.com/ggerganov/ggwave/blob/master/examples/r2t2) | Transmit data through the PC speaker | PC speaker | | [ggwave-objc](https://github.com/ggerganov/ggwave-objc) | Minimal Objective-C iOS app using ggwave | AudioToolbox | | [ggwave-java](https://github.com/ggerganov/ggwave-java) | Minimal Java Android app using ggwave | android.media | | [ggwave-kmm](https://github.com/wooram-yang/ggwave-kmm) | Kotlin Multiplatform Project using ggwave | android.media, javax.sound.sampled | | [ggwave-fm](https://github.com/rgerganov/ggwave-fm) | Transmit ggwave messages with HackRF | Radio | | [esp32-rx](https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx) | Transmit and receive messages using ESP32 | - | | [rp2040-rx](https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx) | Transmit and receive messages using Raspberry Pi Pico (RP2040) | - | | [arduino-rx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx) | Transmit and receive messages using Arduino RP2040 | - | | [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) | Transmit messages using Arduino Uno | - | | [arduino-rx-web](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx-web) | Receive messages from Arduino Uno | Web Audio API | 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 ### Dependencies for SDL-based examples [Ubuntu] $ sudo apt install libsdl2-dev [Mac OS with brew] $ brew install sdl2 [MSYS2] $ pacman -S git cmake make mingw-w64-x86_64-dlfcn mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2 ### Linux, Mac, Windows (MSYS2) ```bash # build git clone https://github.com/ggerganov/ggwave --recursive cd ggwave && mkdir build && cd build cmake .. make # running ./bin/ggwave-cli ``` #### Local Debian packages Build reproducible `libggwave-dev` and `python3-ggwave` Debian packages: ```bash # Fetch source git clone https://github.com/ggerganov/ggwave --recursive cd ggwave # Configure cmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release # Build make deb # Install sudo dpkg -i dist/*.deb ``` ### Emscripten ```bash git clone https://github.com/ggerganov/ggwave --recursive cd ggwave mkdir build && cd build emcmake cmake .. make ``` ### Python ```bash pip install ggwave ``` More info: https://pypi.org/project/ggwave/ ### Node.js ```bash npm install ggwave ``` More info: https://www.npmjs.com/package/ggwave ### iOS Available as a Swift Package: https://github.com/ggerganov/ggwave-spm ## Installing the Waver application [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/waver) ### Linux ```bash sudo snap install waver sudo snap connect waver:audio-record :audio-record ``` ### Mac OS ```bash brew install ggerganov/ggerganov/waver ``` ## References - [Evaluating Acoustic Data Transmission Schemes for Ad-Hoc Communication Between Nearby Smart Devices](https://dl.acm.org/doi/10.1145/3779439) [changelog]: ./CHANGELOG.md [changelog-badge]: https://img.shields.io/badge/changelog-ggwave%20v0.4.2-dummy [license]: ./LICENSE ================================================ FILE: bindings/CMakeLists.txt ================================================ if (EMSCRIPTEN) add_subdirectory(javascript) add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/javascript/publish.log DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/javascript/ggwave.js DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/javascript/package.json WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/javascript COMMAND npm publish COMMAND touch publish.log COMMENT "Publishing npm module v${PROJECT_VERSION}" VERBATIM ) add_custom_target(publish-npm DEPENDS javascript/publish.log ) endif() if (GGWAVE_SUPPORT_PYTHON) file(GLOB_RECURSE GGWAVE_SOURCES "../include/*" "../src/*") add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave.bycython.cpp OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/Makefile DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave.pyx DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/cggwave.pxd DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/setup.py DEPENDS ${GGWAVE_SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/python COMMAND make clean COMMAND make COMMENT "Compiling Python module" VERBATIM ) add_custom_target(ggwave-py ALL DEPENDS python/ggwave.bycython.cpp DEPENDS python/ggwave ) add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/python/dist DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/Makefile DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave.pyx DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/cggwave.pxd DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/setup.py DEPENDS ${GGWAVE_SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/python COMMAND make publish COMMENT "Publishing Python module v${GGWAVE_VERSION_PYTHON}" VERBATIM ) add_custom_target(publish-pypi DEPENDS python/dist ) endif() if (GGWAVE_SUPPORT_SWIFT) file(GLOB_RECURSE GGWAVE_SOURCES "../include/*" "../src/*") add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ios/.build DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ios/Makefile DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ios/Package.swift DEPENDS ${GGWAVE_SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ios COMMAND make clean COMMAND make build-submodule COMMENT "Compiling Swift package" VERBATIM ) add_custom_target(ggwave-spm ALL DEPENDS ios/.build ) add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ios/publish DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ios/publish-trigger DEPENDS ${GGWAVE_SOURCES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ios COMMAND make publish COMMENT "Publishing Swift package v${PROJECT_VERSION}" VERBATIM ) add_custom_target(publish-spm DEPENDS ios/publish ) endif() ================================================ FILE: bindings/javascript/.gitignore ================================================ publish.log ================================================ FILE: bindings/javascript/CMakeLists.txt ================================================ set(TARGET libggwave) add_executable(${TARGET} emscripten.cpp ) target_link_libraries(${TARGET} PRIVATE ggwave ) unset(EXTRA_FLAGS) if (GGWAVE_WASM_SINGLE_FILE) set(EXTRA_FLAGS "-s SINGLE_FILE=1") message(STATUS "Embedding WASM inside ggwave.js") add_custom_command( TARGET libggwave POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/bin/libggwave.js ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.js ) endif() set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \ --bind \ -s MODULARIZE=1 \ -s ALLOW_MEMORY_GROWTH=1 \ -s EXPORT_NAME=\"'ggwave_factory'\" \ ${EXTRA_FLAGS} \ ") ================================================ FILE: bindings/javascript/README.md ================================================ # ggwave Tiny data-over-sound library. - Audible and ultrasound transmissions available - Bandwidth of 8-16 bytes/s (depending on the transmission protocol) - Robust FSK modulation - Reed-Solomon based error correction ## Example Usage ```js var factory = require('ggwave') factory().then(function(ggwave) { // create ggwave instance with default parameters var parameters = ggwave.getDefaultParameters(); parameters.operatingMode |= ggwave.GGWAVE_OPERATING_MODE_USE_DSS; var instance = ggwave.init(parameters); console.log('instance: ' + instance); var payload = 'hello js'; // generate audio waveform for string "hello js" var waveform = ggwave.encode(instance, payload, ggwave.ProtocolId.GGWAVE_PROTOCOL_AUDIBLE_FAST, 10); // decode the audio waveform back to text var res = ggwave.decode(instance, waveform); if (new TextDecoder("utf-8").decode(res) != payload) { process.exit(1); } }); ``` ================================================ FILE: bindings/javascript/emscripten.cpp ================================================ #include "ggwave/ggwave.h" #include #include EMSCRIPTEN_BINDINGS(ggwave) { emscripten::enum_("SampleFormat") .value("GGWAVE_SAMPLE_FORMAT_UNDEFINED", GGWAVE_SAMPLE_FORMAT_UNDEFINED) .value("GGWAVE_SAMPLE_FORMAT_U8", GGWAVE_SAMPLE_FORMAT_U8) .value("GGWAVE_SAMPLE_FORMAT_I8", GGWAVE_SAMPLE_FORMAT_I8) .value("GGWAVE_SAMPLE_FORMAT_U16", GGWAVE_SAMPLE_FORMAT_U16) .value("GGWAVE_SAMPLE_FORMAT_I16", GGWAVE_SAMPLE_FORMAT_I16) .value("GGWAVE_SAMPLE_FORMAT_F32", GGWAVE_SAMPLE_FORMAT_F32) ; emscripten::enum_("ProtocolId") .value("GGWAVE_PROTOCOL_AUDIBLE_NORMAL", GGWAVE_PROTOCOL_AUDIBLE_NORMAL) .value("GGWAVE_PROTOCOL_AUDIBLE_FAST", GGWAVE_PROTOCOL_AUDIBLE_FAST) .value("GGWAVE_PROTOCOL_AUDIBLE_FASTEST", GGWAVE_PROTOCOL_AUDIBLE_FASTEST) .value("GGWAVE_PROTOCOL_ULTRASOUND_NORMAL", GGWAVE_PROTOCOL_ULTRASOUND_NORMAL) .value("GGWAVE_PROTOCOL_ULTRASOUND_FAST", GGWAVE_PROTOCOL_ULTRASOUND_FAST) .value("GGWAVE_PROTOCOL_ULTRASOUND_FASTEST", GGWAVE_PROTOCOL_ULTRASOUND_FASTEST) .value("GGWAVE_PROTOCOL_DT_NORMAL", GGWAVE_PROTOCOL_DT_NORMAL) .value("GGWAVE_PROTOCOL_DT_FAST", GGWAVE_PROTOCOL_DT_FAST) .value("GGWAVE_PROTOCOL_DT_FASTEST", GGWAVE_PROTOCOL_DT_FASTEST) .value("GGWAVE_PROTOCOL_MT_NORMAL", GGWAVE_PROTOCOL_MT_NORMAL) .value("GGWAVE_PROTOCOL_MT_FAST", GGWAVE_PROTOCOL_MT_FAST) .value("GGWAVE_PROTOCOL_MT_FASTEST", GGWAVE_PROTOCOL_MT_FASTEST) .value("GGWAVE_PROTOCOL_CUSTOM_0", GGWAVE_PROTOCOL_CUSTOM_0) .value("GGWAVE_PROTOCOL_CUSTOM_1", GGWAVE_PROTOCOL_CUSTOM_1) .value("GGWAVE_PROTOCOL_CUSTOM_2", GGWAVE_PROTOCOL_CUSTOM_2) .value("GGWAVE_PROTOCOL_CUSTOM_3", GGWAVE_PROTOCOL_CUSTOM_3) .value("GGWAVE_PROTOCOL_CUSTOM_4", GGWAVE_PROTOCOL_CUSTOM_4) .value("GGWAVE_PROTOCOL_CUSTOM_5", GGWAVE_PROTOCOL_CUSTOM_5) .value("GGWAVE_PROTOCOL_CUSTOM_6", GGWAVE_PROTOCOL_CUSTOM_6) .value("GGWAVE_PROTOCOL_CUSTOM_7", GGWAVE_PROTOCOL_CUSTOM_7) .value("GGWAVE_PROTOCOL_CUSTOM_8", GGWAVE_PROTOCOL_CUSTOM_8) .value("GGWAVE_PROTOCOL_CUSTOM_9", GGWAVE_PROTOCOL_CUSTOM_9) ; emscripten::constant("GGWAVE_OPERATING_MODE_RX", (int) GGWAVE_OPERATING_MODE_RX); emscripten::constant("GGWAVE_OPERATING_MODE_TX", (int) GGWAVE_OPERATING_MODE_TX); emscripten::constant("GGWAVE_OPERATING_MODE_RX_AND_TX", (int) GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX); emscripten::constant("GGWAVE_OPERATING_MODE_TX_ONLY_TONES", (int) GGWAVE_OPERATING_MODE_TX_ONLY_TONES); emscripten::constant("GGWAVE_OPERATING_MODE_USE_DSS", (int) GGWAVE_OPERATING_MODE_USE_DSS); emscripten::value_object("Parameters") .field("payloadLength", & ggwave_Parameters::payloadLength) .field("sampleRateInp", & ggwave_Parameters::sampleRateInp) .field("sampleRateOut", & ggwave_Parameters::sampleRateOut) .field("sampleRate", & ggwave_Parameters::sampleRate) .field("samplesPerFrame", & ggwave_Parameters::samplesPerFrame) .field("soundMarkerThreshold", & ggwave_Parameters::soundMarkerThreshold) .field("sampleFormatInp", & ggwave_Parameters::sampleFormatInp) .field("sampleFormatOut", & ggwave_Parameters::sampleFormatOut) .field("operatingMode", & ggwave_Parameters::operatingMode) ; emscripten::function("getDefaultParameters", & ggwave_getDefaultParameters); emscripten::function("init", & ggwave_init); emscripten::function("free", & ggwave_free); emscripten::function("encode", emscripten::optional_override( [](ggwave_Instance instance, const std::string & data, ggwave_ProtocolId protocolId, int volume) { auto n = ggwave_encode(instance, data.data(), data.size(), protocolId, volume, nullptr, 1); // TODO: how to return the waveform data? // for now using this static vector and returning a pointer to it static std::vector result(n); result.resize(n); int nActual = ggwave_encode(instance, data.data(), data.size(), protocolId, volume, result.data(), 0); // printf("n = %d, nActual = %d\n", n, nActual); return emscripten::val(emscripten::typed_memory_view(nActual, result.data())); })); emscripten::function("decode", emscripten::optional_override( [](ggwave_Instance instance, const std::string & data) { // TODO: how to return the result? // again using a static array and returning a pointer to it static char output[256]; auto n = ggwave_decode(instance, data.data(), data.size(), output); if (n > 0) { return emscripten::val(emscripten::typed_memory_view(n, output)); } return emscripten::val(emscripten::typed_memory_view(0, output)); })); emscripten::function("disableLog", emscripten::optional_override( []() { ggwave_setLogFile(NULL); })); emscripten::function("enableLog", emscripten::optional_override( []() { ggwave_setLogFile(stderr); })); emscripten::function("rxToggleProtocol", emscripten::optional_override( [](ggwave_ProtocolId protocolId, int state) { ggwave_rxToggleProtocol(protocolId, state); })); emscripten::function("txToggleProtocol", emscripten::optional_override( [](ggwave_ProtocolId protocolId, int state) { ggwave_txToggleProtocol(protocolId, state); })); emscripten::function("rxProtocolSetFreqStart", emscripten::optional_override( [](ggwave_ProtocolId protocolId, int freqStart) { ggwave_rxProtocolSetFreqStart(protocolId, freqStart); })); emscripten::function("txProtocolSetFreqStart", emscripten::optional_override( [](ggwave_ProtocolId protocolId, int freqStart) { ggwave_txProtocolSetFreqStart(protocolId, freqStart); })); emscripten::function("rxDurationFrames", emscripten::optional_override( [](ggwave_Instance instance) { return ggwave_rxDurationFrames(instance); })); } ================================================ FILE: bindings/javascript/ggwave.js ================================================ var ggwave_factory = (() => { var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; return ( function(moduleArg = {}) { var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");if(ENVIRONMENT_IS_WORKER){scriptDirectory=nodePath.dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)onerror(err);else onload(binary?data.buffer:data)})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow};Module["inspect"]=()=>"[Emscripten Module object]"}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="data:application/octet-stream;base64,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AABFDQBBASECA0AgAkH/AXEhGUEAIQIgGQRAA0AgACgCQCgCACAALwE8aiACIAtqQf8BcWoiBQJ/QQAgBygCCCgCACAHLwEEaiACai0AACIERQ0AGkEAIAAoApQBKAIAIAAvAZABaiALai0AACIBRQ0AGiABQdApai0AACAEQdApai0AAGpB0CtqLQAACyAFLQAAczoAACACQQFqIgIgBy0AACIBSQ0ACyAALQCMASEEIAEhAgsgC0EBaiILIARJDQALCyAAIAlBAmo6AEQgACgCTCgCACAALwFIakEAIAAtAEYQGxogACgCTCgCACAALwFIakEBOgAAIAAoAkAoAgAgAC8BPGoiAiAAKAJwKAIAIAAvAWxqIgFHBEAgASACIAAtADgQGhoLIAAgAC0AOCICOgBoIAAtADgiBSAALQBEIgFrQQFqIgdBAEoEQEEAIQIDQAJAIAAoAnAoAgAgAC8BbGogAmotAAAiBEUNACABQQJJDQBBASEFA0AgACgCTCgCACAALwFIaiAFai0AACIHBEAgACgCcCgCACAALwFsaiACIAVqQf8BcWoiASABLQAAIARB0ClqLQAAIAdB0ClqLQAAakHQK2otAABzOgAAIAAtAEQhAQsgBUEBaiIFIAFJDQALIAAtADghBQsgAkEBaiICIAVB/wFxIAFrQQFqIgdIDQALIAAtAGghAgsgACgCcCgCACAALwFsaiIBIAEgB2ogAkH/AXEgB2sQMxogACAALQBoIAdrOgBoIAAgAC0AaCIBOgDIASABQQFrwCICQQBOBEBBACEGA0AgACgC0AEoAgAgAC8BzAFqIAZqIAAoAnAoAgAgAC8BbGogAkH/AXFqLQAAOgAAIAJBAWshAiAGQQFqIgYgAUcNAAsLIABBADoAOCAALQC8AQRAQQAhAgNAIAAoAsQBKAIAIAAvAcABaiACai0AACEEIAAoAkAoAgAhByAAIAAtADgiAUEBajoAOCABIAcgAC8BPGpqIARBf3NBACAEGyIBQX9zQQAgAUH/AXEbQf8BcUHQK2otAAA6AAAgAkEBaiICIAAtALwBSQ0ACwsgACgCWCgCACAALwFUakEAIAAtAFIQGxogACAILQAAIgI6AFAgAC0AOCIGBEBBACEMA0AgACgCQCgCACAALwE8aiAMai0AACEaIABBADoARCAaQdApai0AAEH/AXNB0CtqLQAAIQpBASECAkAgBkH/AXFFDQBBACECA0AgAiAMRwRAIAAoAkAoAgAgAC8BPGogAmotAAAiAQR/IAFB0ClqLQAAIApB0ClqLQAAakHQK2otAAAFQQALIQQgACgCTCgCACEHIAAgAC0ARCIBQQFqOgBEIAEgByAALwFIamogBEEBczoAACAALQA4IQYLIAJBAWoiAiAGQf8BcUkNAAtBASECIAAtAEQiBEUNACAAKAJMKAIAIAAvAUhqIQdBACEGA0ACf0EAIAJB/wFxIgJFDQAaQQAgBiAHai0AACIBRQ0AGiABQdApai0AACACQdApai0AAGpB0CtqLQAACyECIAZBAWoiBiAERw0ACwsgACgCcCgCACAALwFsaiILLQAAIQUCQCAALQBoIgdBAkkNAEEBIQYgB0EBayIBQQFxIRwgB0ECRwRAIAFBfnEhBEEAIQ0DQCAFQf8BcSIBBH8gCkHQKWotAAAgAUHQKWotAABqQdArai0AAAVBAAsgBiALai0AAHNB/wFxIgEEfyAKQdApai0AACABQdApai0AAGpB0CtqLQAABUEACyEHIAZBAWohGyAGQQJqIQYgGyALai0AACAHcyEFIA1BAmoiDSAERw0ACwsgHEUNACAFQf8BcSIBBH8gCkHQKWotAAAgAUHQKWotAABqQdArai0AAAVBAAsgBiALai0AAHMhBQsgACgCrAEoAgAgAC8BqAFqIAxqLQAAIAAoAlgoAgAgAC8BVGpqIAVB/wFxIgcEfyAAKAJAKAIAIAAvATxqIAxqLQAAQdApai0AACIBQQAgAUH/AUcbQf8BcUHQK2otAABB0ClqLQAAIAdB0ClqLQAAakHQK2otAABB0ClqLQAAIAJB/wFxQdApai0AAGtB/wFqQf//A3FB/wFwQdArai0AAAVBAAs6AAAgDEEBaiIMIAAtADgiBkkNAAsgAC0AUCECCyAAIAgtAAAiByACQf8BcSIBIAEgB0kbIgE6ACBBACECIAAoAigoAgAgAC8BJGpBACABEBsaIAgtAAAiBgRAA0AgACgCKCgCACAALwEkaiAALQAgIAIgBmtqQf8BcWogCCgCCCgCACAILwEEaiACai0AADoAACACQQFqIgIgCC0AACIGSQ0ACwsgAC0AUCIGBEBBACECA0AgACgCKCgCACAALwEkaiAALQAgIAIgBmtqQf8BcWoiASABLQAAIAAoAlgoAgAgAC8BVGogAmotAABzOgAAIAJBAWoiAiAALQBQIgZJDQALCwsgACAOOgAgIAMgACgCKCgCACAALwEkaiAOEBoaQQAhAgsgAgvJBAEDf0EBIAAsAAciASABQQBKG0EBIAAtAAkbIgEgACwAEyICIAEgAkgbIAEgAC0AFRsiASAALAAfIgIgASACSBsgASAALQAhGyIBIAAsACsiAiABIAJIGyABIAAtAC0bIgEgACwANyICIAEgAkgbIAEgAC0AORsiAcAiAiAALABDIgMgAiADSBsgASAALQBFGyIBwCICIAAsAE8iAyACIANIGyABIAAtAFEbIgHAIgIgACwAWyIDIAIgA0gbIAEgAC0AXRsiAcAiAiAALABnIgMgAiADSBsgASAALQBpGyIBwCICIAAsAHMiAyACIANIGyABIAAtAHUbIgHAIgIgACwAfyIDIAIgA0gbIAEgAC0AgQEbIgHAIgIgACwAiwEiAyACIANIGyABIAAtAI0BGyIBwCICIAAsAJcBIgMgAiADSBsgASAALQCZARsiAcAiAiAALACjASIDIAIgA0gbIAEgAC0ApQEbIgHAIgIgACwArwEiAyACIANIGyABIAAtALEBGyIBwCICIAAsALsBIgMgAiADSBsgASAALQC9ARsiAcAiAiAALADHASIDIAIgA0gbIAEgAC0AyQEbIgHAIgIgACwA0wEiAyACIANIGyABIAAtANUBGyIBwCICIAAsAN8BIgMgAiADSBsgASAALQDhARsiAcAiAiAALADrASIDIAIgA0gbIAEgAC0A7QEbIgHAIgIgACwA9wEiAyACIANIGyABIAAtAPkBGyIBwCICIAAsAIMCIgMgAiADSBsgASAALQCFAhvAC8YGAgl/Bn0CQCACKAIAIghBAnQgAE4NACACQQE2AgQgAiAAQQJ1Igg2AgAgAEEMSA0AIANCgICA/AM3AgBBAiEEIAMgCEEBdiIGQQJ0aiIFIAayRBgtRFT7Iek/IAa3o7YiDZQQKSIOOAIEIAUgDjgCACAAQRhJDQADQCADIARBAnQiBWogDSAEspQiDhApIg84AgAgAyAFQQRyaiAOEC8iDjgCACADIAggBGtBAnRqIgUgDzgCBCAFIA44AgAgBEECaiIEIAZJDQALIAggAkEIaiADEFMLAkAgAigCBCIGQQJ0IABODQAgAiAAQQJ1IgY2AgQgAEEISA0AQQEhBCADIAhBAnRqIgUgBkEBdiIHskQYLURU+yHpPyAHt6O2Ig2UECkiDjgCACAFIAdBAnRqIA5DAAAAP5Q4AgAgAEEQSQ0AQQIgByAHQQJNG0EBayIHQQFxIQwgAEEYTwRAIAdBfnEhC0EAIQcDQCAFIARBAnRqIA0gBLKUIg4QKUMAAAA/lDgCACAFIAYgBGtBAnRqIA4QL0MAAAA/lDgCACAFIARBAWoiCUECdGogDSAJspQiDhApQwAAAD+UOAIAIAUgBiAJa0ECdGogDhAvQwAAAD+UOAIAIARBAmohBCAHQQJqIgcgC0cNAAsLIAxFDQAgBSAEQQJ0aiANIASylCINEClDAAAAP5Q4AgAgBSAGIARrQQJ0aiANEC9DAAAAP5Q4AgALAkAgAEEFTgRAIAAgAkEIaiABEFMgACABIAMQUiAGQQF0IABBAXYiB20hCSAAQQVGDQFBAiEEIAMgCEECdGohBUEAIQIDQCABIARBAnQiCGoiAyADKgIAIg1DAAAAPyAFIAYgAiAJaiICa0ECdGoqAgCTIg4gDSABIAAgBGtBAnRqIgMqAgCTIg2UIAEgCEEEcmoiCCoCACIPIAMqAgSSIhAgBSACQQJ0aioCACIRlJMiEpM4AgAgCCAPIA4gEJQgESANlJIiDZM4AgAgAyADKgIAIBKSOAIAIAMgAyoCBCANkzgCBCAEQQJqIgQgB0kNAAsMAQsgAEEERw0AQQQgASADEFILIAEgASoCACINIAEqAgQiDpM4AgQgASANIA6SOAIAC34AQYDYAEEfNgIAQYTYAEEANgIAEFpBhNgAQeTgACgCADYCAEHk4ABBgNgANgIAQbDaAEGQzAAoAgA2AgBB6OAAQSA2AgBB7OAAQQA2AgAQS0Hs4ABB5OAAKAIANgIAQeTgAEHo4AA2AgBBlOIAQZzhADYCAEHM4QBBKjYCAAuaAQAgAEEBOgA1AkAgACgCBCACRw0AIABBAToANAJAIAAoAhAiAkUEQCAAQQE2AiQgACADNgIYIAAgATYCECADQQFHDQIgACgCMEEBRg0BDAILIAEgAkYEQCAAKAIYIgJBAkYEQCAAIAM2AhggAyECCyAAKAIwQQFHDQIgAkEBRg0BDAILIAAgACgCJEEBajYCJAsgAEEBOgA2CwtdAQF/IAAoAhAiA0UEQCAAQQE2AiQgACACNgIYIAAgATYCEA8LAkAgASADRgRAIAAoAhhBAkcNASAAIAI2AhgPCyAAQQE6ADYgAEECNgIYIAAgACgCJEEBajYCJAsLugIBA38jAEFAaiICJAAgACgCACIDQQRrKAIAIQQgA0EIaygCACEDIAJCADcCICACQgA3AiggAkIANwIwIAJCADcANyACQgA3AhggAkEANgIUIAJBpNAANgIQIAIgADYCDCACIAE2AgggACADaiEAQQAhAwJAIAQgAUEAECEEQCACQQE2AjggBCACQQhqIAAgAEEBQQAgBCgCACgCFBEJACAAQQAgAigCIEEBRhshAwwBCyAEIAJBCGogAEEBQQAgBCgCACgCGBEHAAJAAkAgAigCLA4CAAECCyACKAIcQQAgAigCKEEBRhtBACACKAIkQQFGG0EAIAIoAjBBAUYbIQMMAQsgAigCIEEBRwRAIAIoAjANASACKAIkQQFHDQEgAigCKEEBRw0BCyACKAIYIQMLIAJBQGskACADCwoAIAAgAUEAECELmQIAIABFBEBBAA8LAn8CQCAABH8gAUH/AE0NAQJAQZTiACgCACgCAEUEQCABQYB/cUGAvwNGDQMMAQsgAUH/D00EQCAAIAFBP3FBgAFyOgABIAAgAUEGdkHAAXI6AABBAgwECyABQYBAcUGAwANHIAFBgLADT3FFBEAgACABQT9xQYABcjoAAiAAIAFBDHZB4AFyOgAAIAAgAUEGdkE/cUGAAXI6AAFBAwwECyABQYCABGtB//8/TQRAIAAgAUE/cUGAAXI6AAMgACABQRJ2QfABcjoAACAAIAFBBnZBP3FBgAFyOgACIAAgAUEMdkE/cUGAAXI6AAFBBAwECwtB+OAAQRk2AgBBfwVBAQsMAQsgACABOgAAQQELC7QCAAJAAkACQAJAAkACQAJAAkACQAJAAkAgAUEJaw4SAAgJCggJAQIDBAoJCgoICQUGBwsgAiACKAIAIgFBBGo2AgAgACABKAIANgIADwsgAiACKAIAIgFBBGo2AgAgACABMgEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMwEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMAAANwMADwsgAiACKAIAIgFBBGo2AgAgACABMQAANwMADwsgAiAC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ACwAHyICIAEgAkobIAEgAC0AIRsiASAALAArIgIgASACShsgASAALQAtGyIBIAAsADciAiABIAJKGyABIAAtADkbIgEgACwAQyICIAEgAkobIAEgAC0ARRsiASAALABPIgIgASACShsgASAALQBRGyIBIAAsAFsiAiABIAJKGyABIAAtAF0bIgEgACwAZyICIAEgAkobIAEgAC0AaRsiASAALABzIgIgASACShsgASAALQB1GyIBIAAsAH8iAiABIAJKGyABIAAtAIEBGyIBIAAsAIsBIgIgASACShsgASAALQCNARsiASAALACXASICIAEgAkobIAEgAC0AmQEbIgEgACwAowEiAiABIAJKGyABIAAtAKUBGyIBIAAsAK8BIgIgASACShsgASAALQCxARsiASAALAC7ASICIAEgAkobIAEgAC0AvQEbIgEgACwAxwEiAiABIAJKGyABIAAtAMkBGyIBIAAsANMBIgIgASACShsgASAALQDVARsiASAALADfASICIAEgAkobIAEgAC0A4QEbIgEgACwA6wEiAiABIAJKGyABIAAtAO0BGyIBIAAsAPcBIgIgASACShsgASAALQD5ARsiASAALACDAiICIAEgAkobIAEgAC0AhQIbC9ceAgl/A3wjAEEgayIHJAACf0ECIAAoAkhBjAEgAC0ARBsiBkEESA0AGkEEIAZBD0kNABogBkEFbkEBdAshA0HU3gAtAABFBEBBzNwAECRBiAIQGhpB1N4AQQE6AAALQczcABA1IAMgBmoiCGpBAWshC0HI3AAtAABFBEBBwNoAECRBiAIQGhpByNwAQQE6AAALIAtBwNoAEDVtIQkCQCAIQYECTgRAQbDaACgCACIARQ0BIAdBgAI2AgwgBwJ/QQIgBkEESA0AGkEEIAZBD0kNABogBkEFbkEBdAs2AgggByAGNgIEIAcgCDYCACAAQe4kIAcQHAwBCyAAKAI8IAhqIQMgAQRAIAIoAgAhBCAAIAM2AlggACABIARqNgJUCyACIAMgAigCAGpBB2pBCG1BA3QiBDYCACAALQBMBEAgAiAAKAIMIgNBA3QgAQR/IAAgA0EBdDYCmAEgACABIARqNgKUASACKAIABSAEC2pBB2pBCG1BA3QiAzYCAAJ/IAAoAgxBAm23n0QAAAAAAAAIQKAiDJlEAAAAAAAA4EFjBEAgDKoMAQtBgICAgHgLIQQgAiABBH8gACAENgKgASAAIAEgA2o2ApwBIAIoAgAFIAMLIARBAnRqQQdqQQhtQQN0IgM2AgAgACgCDEECbSEEIAIgAQR/IAAgBDYCqAEgACABIANqNgKkASACKAIABSADCyAEQQJ0akEHakEIbUEDdCIDNgIAIAAoAgwhBCACIAEEfyAAIAQ2ArQBIAAgASADajYCsAEgAigCAAUgAwsgBEECdGpBB2pBCG1BA3QiBDYCACAAKAIMIgNBgAFqIAMgAC0ATiIDGyEFIAIgAQR/IAAgBTYCvAEgACABIARqNgK4ASACKAIABSAECyAFQQJ0akEHakEIbUEDdCIENgIAIAAoAgwiBUEDdCAFIAMbIQUgAiABBH8gACAFNgLEASAAIAEgBGo2AsABIAIoAgAFIAQLIAVBAnRqQQdqQQhtQQN0IgQ2AgAgACgCDCIFQQN0IAAoAhQiCiADGyAKIAUgAxtsIQMgAiABBH8gACADNgLMASAAIAEgBGo2AsgBIAIgAyACKAIAakEHakEIbUEDdCIDNgIAIAAgBkEBajYC2AEgACABIANqNgLUASACKAIABSADIARqQQdqQQhtQQN0CyAGakEIakEIbUEDdCIENgIAAkAgAC0ARARAIAAoAkgiA0HBAE4EQEEAIQVBsNoAKAIAIgBFDQQgB0HAADYCFCAHIAM2AhAgAEGbICAHQRBqEBwMBAtB1N4ALQAARQRAQczcABAkQYgCEBoaQdTeAEEBOgAAIAIoAgAhBAsCf0HV3AAtAAAEf0HS3AAsAABB1NwALAAAbCIDQQAgA0EAShsFQQALIQNB4dwALQAABEAgA0He3AAsAABB4NwALAAAbCIFIAMgBUobIQMLQe3cAC0AAARAIANB6twALAAAQezcACwAAGwiBSADIAVKGyEDC0H53AAtAAAEQCADQfbcACwAAEH43AAsAABsIgUgAyAFShshAwtBhd0ALQAABEAgA0GC3QAsAABBhN0ALAAAbCIFIAMgBUobIQMLQZHdAC0AAARAIANBjt0ALAAAQZDdACwAAGwiBSADIAVKGyEDC0Gd3QAtAAAEQCADQZrdACwAAEGc3QAsAABsIgUgAyAFShshAwtBqd0ALQAABEAgA0Gm3QAsAABBqN0ALAAAbCIFIAMgBUobIQMLQbXdAC0AAARAIANBst0ALAAAQbTdACwAAGwiBSADIAVKGyEDC0HB3QAtAAAEQCADQb7dACwAAEHA3QAsAABsIgUgAyAFShshAwtBzd0ALQAABEAgA0HK3QAsAABBzN0ALAAAbCIFIAMgBUobIQMLQdndAC0AAARAIANB1t0ALAAAQdjdACwAAGwiBSADIAVKGyEDC0Hl3QAtAAAEQCADQeLdACwAAEHk3QAsAABsIgUgAyAFShshAwtB8d0ALQAABEAgA0Hu3QAsAABB8N0ALAAAbCIFIAMgBUobIQMLQf3dAC0AAARAIANB+t0ALAAAQfzdACwAAGwiBSADIAVKGyEDC0GJ3gAtAAAEQCADQYbeACwAAEGI3gAsAABsIgUgAyAFShshAwtBld4ALQAABEAgA0GS3gAsAABBlN4ALAAAbCIFIAMgBUobIQMLQaHeAC0AAARAIANBnt4ALAAAQaDeACwAAGwiBSADIAVKGyEDC0Gt3gAtAAAEQCADQareACwAAEGs3gAsAABsIgUgAyAFShshAwtBud4ALQAABEAgA0G23gAsAABBuN4ALAAAbCIFIAMgBUobIQMLQcXeAC0AAARAIANBwt4ALAAAQcTeACwAAGwiBSADIAVKGyEDCyADQdHeAC0AAEUNABogA0HO3gAsAABB0N4ALAAAbCIFIAMgBUobCyAJbCEDIAAoAgwhBSACIAEEfyAAIAU2AqAEIAAgAzYCnAQgACABIARqNgKYBCACKAIABSAECyADIAVsakEHakEIbUEDdCIDNgIAIAhBAXQhBCACIAEEfyAAIAQ2AqgEIAAgASADajYCpAQgAigCAAUgAwsgBGpBB2pBCG1BA3QiAzYCAEHM3AAQTEEFdCEEIAFFDQEgACAENgKwBCAAIAEgA2o2AqwEIAIoAgAhAwwBCyACIAAoAgwiA0ENdCABBH8gACADQQt0NgKQBCAAIAEgBGo2AowEIAIoAgAFIAQLakEHakEIbUEDdCIDNgIAIAAoAgwhBCACIAEEfyAAIAQ2AvwDIAAgASADajYC+AMgAigCAAUgAwsgBEECdGpBB2pBCG1BA3QiAzYCACAAKAIMIQQgAQRAIAAgBDYCiAQgAEEENgKEBCAAIAEgA2o2AoAEIAIoAgAhAwsgBEEEdCEECyACIARBB3IgA2pBCG1BA3QiBDYCAAsgAC0ATQRAQcjcAC0AAEUEQEHA2gAQJEGIAhAaGkHI3ABBAToAAAtBwNoAEEwiA0EFdCEIIAAtAE9FBEAgAigCACEEIAIgAQR/IAAgCDYC0AQgACABIARqNgLMBCACKAIABSAECyADQQh0akEHakEIbUEDdCIENgIAIAIgA0EHdCIFIAAoAgwiA2wgAQR/IAAgAzYC6AQgACAINgLkBCAAIAEgBGo2AuAEIAIoAgAFIAQLakEHakEIbUEDdCIDNgIAIAAoAgwhBCACIAEEfyAAIAQ2AtwEIAAgCDYC2AQgACABIANqNgLUBCACKAIABSADCyAEIAVsQQdyakEIbUEDdCIDNgIAIAAoAgwhBCACIAEEfyAAIAQ2AowHIAAgASADajYCiAcgAigCAAUgAwsgBEECdGpBB2pBCG1BA3QiAzYCACACIAAoAgwiBEEDdCABBH8gACAEQQF0NgKUByAAIAEgA2o2ApAHIAIoAgAFIAMLakEHakEIbUEDdCIENgIAIAAoAgwgACgCGGxBC3QhAwJAIAFFBEAgAiADQQdyIARqQQhtQQN0IgM2AgAgACgCDCEEDAELIAAgAzYCnAcgACABIARqNgKYByACIAIoAgAgA0EHcmpBCG1BA3QiAzYCACAHIAAoAgwiBEELdDYCHCAHIAEgA2o2AhggACAHKAIYNgKgByAAIAcoAhw2AqQHIAIoAgAhAwsgAiADIARBDHRqQQdqQQhtQQN0NgIACwJAIAAtAEQEQEEAIQNByNwALQAARQRAQcDaABAkQYgCEBoaQcjcAEEBOgAAC0EBIQUDQCADQQxsIgRBydoAai0AAARAIAUgBEHH2gBqLAAAQQF0IARByNoAaiwAAG0iBCAEIAVIGyEFCyADQQFqIgNBFkcNAAsMAQsgACgCNCEFCyACAn8gAUUEQCAGIAIoAgBqQQhqQQhtQQN0DAELIAIoAgAhAyAAIAZBAWo2AvAEIAAgASADajYC7AQgAiAGIAIoAgBqQQhqQQhtQQN0IgM2AgAgACAINgLIBCAAIAEgA2o2AsQEIAIoAgALIAhBB3JqQQhtQQN0IgM2AgAgBSAJbCAJQQAgBUEBShtqIQQgAiABBH8gACAENgKwByAAIAEgA2o2AqwHIAIoAgAFIAMLIARqQQdqQQhtQQN0IgQ2AgALIAAoAkhBjAEgAC0ARCIGGyEDIAZFBEAgACgCPEEBa0H/AXFBHWxBBGohBiACIAEEfyAAIAY2AmAgACABIARqNgJcIAIoAgAFIAQLIAZqQQdqQQhtQQN0IgQ2AgALQQEhBQJ/QQIgA0EESA0AGkEEIANBD0kNABogA0EFbkEBdEH+AXELIgYgA0H/AXFBA2xqIAZBHGxqQQFqIQMgAiABBH8gACADNgJoIAAgASAEajYCZCACKAIABSAECyADakEHakEIbUEDdDYCACAALQBORQ0AIAIoAgAhAwJAIAFFBEAgAiADQYfAAGpBCG1BA3RBhwZqQQhtQQN0QYcCakEIbUEDdEGHgAFqQQhtQQN0NgIADAELIABBgBA2ArwHIAAgASADaiIDNgK4ByACIAIoAgBBh8AAakEIbUEDdCIENgIAIABBwAE2AsQHIAAgASAEajYCwAcgAiACKAIAQYcGakEIbUEDdCIENgIAIABBwAA2AswHIAAgASAEaiIGNgLIByACIAIoAgBBhwJqQQhtQQN0IgQ2AgAgAEGAIDYC1AcgACABIARqNgLQByACIAIoAgBBh4ABakEIbUEDdDYCACADQYCAgPwDNgIAQQEhAQNAIAG3IgxEGC1EVPshCUCiRAAAAAAAAKA/oiINECshDiMAQRBrIgIkAAJ8IAxEGC1EVPshWT+iIgy9QiCIp0H/////B3EiBEH7w6T/A00EQEQAAAAAAADwPyAEQZ7BmvIDSQ0BGiAMRAAAAAAAAAAAEC0MAQsgDCAMoSAEQYCAwP8HTw0AGgJAAkACQAJAIAwgAhBIQQNxDgMAAQIDCyACKwMAIAIrAwgQLQwDCyACKwMAIAIrAwhBARAsmgwCCyACKwMAIAIrAwgQLZoMAQsgAisDACACKwMIQQEQLAshDCACQRBqJAAgAyABQQJ0aiAMRAAAAAAAAOA/okQAAAAAAADgP6AgDiANo7a7orY4AgAgAUEBaiIBQYAQRw0ACyAAQgA3A9gHIABCADcD6AcgAEIANwPgByAGQQAgACgCzAdBAnQQGxogACgCwAdBACAAKALEB0ECdBAbGiAAKALQB0EAIAAoAtQHQQJ0EBsaCwsgB0EgaiQAIAULjwcBB38gACAAKAIEIAAtAAFqQQFqNgIQIAAoAhwoAgAgAC8BGGpBACAALQAWEBsaIAAoAigoAgAgAC8BJGpBACAALQAiEBsaAkAgAC0ADQRAIAAoAjQoAgAgAC8BMGogACgCCCAALQABQQFqIgNB/wFxEBoaIAAgAzoALAwBCyAAKAI0KAIAIAAvATBqQQE6AAAgAEECOgA4IABBAToALCAALQABBEADQEEBIQMgACgCQCgCACAALwE8akEBOgAAIAAoAkAoAgAgAC8BPGogB8AiBEH/AWogBCAEQQBIG0HQK2otAAA6AAEgACAALQAsIAAtADhqQQFrIgQ6AEQgACgCTCgCACAALwFIakEAIARB/wFxEBsaIAAtACwhBAJAIAAtADgiBkUNAEEAIQUgBEUNAANAIANB/wFxIQlBACEDIAkEQANAIAAoAkwoAgAgAC8BSGogAyAFakH/AXFqIgQCf0EAIAAoAjQoAgAgAC8BMGogA2otAAAiBkUNABpBACAAKAJAKAIAIAAvATxqIAVqLQAAIghFDQAaIAhB0ClqLQAAIAZB0ClqLQAAakHQK2otAAALIAQtAABzOgAAIANBAWoiAyAALQAsIgRJDQALIAAtADghBiAEIQMLIAVBAWoiBSAGSQ0ACwsgACAEIAAtAEQiAyADIARJGyIDOgAsIAAoAjQoAgAgAC8BMGogACgCTCgCACAALwFIaiADEBoaIAAgAzoALCAALQABIAdBAWoiB8BKDQALCyAAKAIIIAAoAjQoAgAgAC8BMGogAC0ALBAaGiAAQQE6AA0LIAAoAhwoAgAgAC8BGGogASAALQAAIgMQGhogACADOgAUIAAoAigoAgAgAC8BJGogASAALQAAEBoaIAAgAC0AASIDIAAtABRqOgAgIAIgAC0AACIBBH8gAC0ALCEDQQAhAgNAAkAgACgCKCgCACAALwEkaiACai0AACIERQ0AIANBAkkNAEEBIQEDQCAAKAIoKAIAIAAvASRqIAEgAmpB/wFxaiIDIAAoAjQoAgAgAC8BMGogAWotAAAiBQR/IARB0ClqLQAAIAVB0ClqLQAAakHQK2otAAAFQQALIAMtAABzOgAAIAFBAWoiASAALQAsIgNJDQALIAAtAAAhAQsgAkEBaiICIAFB/wFxIgRJDQALIAAtAAEhAyAEBUEACyAAKAIoKAIAIAAvASRqaiADEBoaCw0AIAEgACgCAGooAgALpQYBA38jAEHQAGsiBSQAAkAgAUEASARAQQAhAkGw2gAoAgAiAEUNASAFIAE2AgAgAEHoISAFEBwMAQsCQCAALQBNBEACQCABIAAoAkhBjAEgAC0ARBsiBkwEQCABIQYMAQtBsNoAKAIAIgdFDQAgBSAGNgJEIAUgATYCQCAHQesaIAVBQGsQHAsgBEHlAE8EQEEAIQJBsNoAKAIAIgBFDQMgBSAENgIQIABBwCIgBUEQahAcDAMLIABBADoAtAQgACgC7ARBACAAKALwBBAbGiAAKAJUQQAgACgCWBAbGiAGQQBMDQEgA0EWTwRAQQAhAkGw2gAoAgAiAEUNAyAFIAM2AjAgAEHUIiAFQTBqEBwMAwsgAEGABWogA0EMbGoiAS0ACUUEQEEAIQJBsNoAKAIAIgBFDQMgBSADNgIgIABBsxwgBUEgahAcDAMLIAAtAEQhAwJAIAEtAAhBAkcNACADDQBBACECQbDaACgCACIARQ0DQYUdQTsgABAmDAMLIAAgASkCADcC9AQgACABKAIINgL8BCAAIAAoAkggBiADGyIBNgK8BCAAIAS3RAAAAAAAAFlAo7Y4ArgEIAAoAuwEIAE6AAAgACgCvARBAEoEQEEAIQEDQEEAIQQgAUEBaiIDIAAoAuwEaiABIAZIBH8gASACai0AAAVBAAs6AAAgAC0AUARAIAAoAuwEIANqIgQgBC0AACABQT9xQZApai0AAHM6AAALIAMiASAAKAK8BEgNAAsLIABBAToAtAQMAQsgAUUNAEGw2gAoAgAiAUUNAEGyG0HAACABECYLIAAtAExFBEBBASECDAELIABBADsBbCAAQgA3A4ABIABCADcDiAEgACgCsAFBACAAKAK0AUECdBAbGiAAKAK4AUEAIAAoArwBQQJ0EBsaQQEhAgJAIAAoAoQEIgFBAEwNACAAKAKIBCIDQQBMDQAgACgCgARBACABIANsQQJ0EBsaCyAAKALUAUEAIAAoAtgBEBsaIAAoApwEIgFBAEwNACAAKAKgBCIDQQBMDQAgACgCmARBACABIANsEBsaCyAFQdAAaiQAIAIL/0EDFH8IfQR8IwBBMGsiEiQAAkAgAEECdEGg2gBqKAIAIgdFBEBBfyEEQbDaACgCACIBRQ0BIBIgADYCACABQf4dIBIQHAwBCyAHIAIgASADIAQQUEUEQEF/IQRBsNoAKAIAIgFFDQEgEiAANgIgIAFBwR0gEkEgahAcDAELAkACQAJAIAYOAgIAAQsgBy0AtAQEfyAHKAIMIQAgBy0ATgR/IAdBuAdqIAcqAgggByoCBJUgACAHKAKIB0EAEC5BAWoFIAALIAcsAPoEIAcsAPwEIAcsAPsEIgEgBygCPAJ/QQIgBygCvAQiAEEESA0AGkEEIABBD0kNABogAEEFbkEBdAsgAGpqakEBayABbWxsIAcoAjhBAXRqbAVBAAsgBygCGGwhBAwCCyAHLQC0BEUEQEEAIQQMAgsgBygCDCEAIActAE4EfyAHQbgHaiAHKgIIIAcqAgSVIAAgBygCiAdBABAuQQFqBSAACyAHLAD6BCAHLAD8BCAHLAD7BCIBIAcoAjwCf0ECIAcoArwEIgBBBEgNABpBBCAAQQ9JDQAaIABBBW5BAXQLIABqampBAWsgAW1sbCAHKAI4QQF0amwhBAwBCyMAQeABayIBJAACQCAHLQBNRQRAQQAhAkGw2gAoAgAiA0UNAUGyG0HAACADECYMAQsgBy0ATgRAIAdCADcD6AcgB0IANwPgByAHQgA3A9gHIAcoAsgHQQAgBygCzAdBAnQQGxogBygCwAdBACAHKALEB0ECdBAbGiAHKALQB0EAIAcoAtQHQQJ0EBsaCwJ/QQIgBygCvAQiAkEESA0AGkEEIAJBD0kNABogAkEFbkEBdAshCSAHLAD7BCIDIAcoAjwiBiACIAlqampBAWsgA20hFSAHLAD8BCEYIAcsAPoEIQ0gBy0AREUEQCAHKAJcIQIgAUEANgLcASABQQA6ANQBIAFBADYC0AEgAUEAOgDIASABQQA2AsQBIAFBADoAvAEgAUEANgK4ASABQQA6ALABIAFBADYCrAEgAUEAOgCkASABQQA2AqABIAFBADoAmAEgAUEANgKUASABQQA6AIwBIAFBADYCiAEgAUEAOgCAASABQQA2AnwgAUEAOgB0IAFBADYCcCABQQA6AGggAUEANgJkIAFBADoAXCABQQA2AlggAUEAOgBQIAFBADYCTCABQQA6AEQgAUFAa0EANgIAIAFBADoAOCABQQA2AjQgAUEAOgAsIAFBADYCKCABQQA6ACAgAUEANgIcIAFBADoAFCABIAZBAWsiAzoAASABQQE6AAAgAUEAOwEMIAJFBEBBASEKIANB/wFxQR1sQQRqECUhAgsgAUEAOwEYIAEgBjoAFiABIAZB/wFxIgs7ASQgASAGOgAiIAEgA0EBdCIDOgAuIAFBAzoAOSABIAM6ADogASALQQF0IhM7ATAgASATIANB/gFxIgRqIhM7ATwgASAKOgAMIAEgAjYCBCABIAI2AgggAUEAOwEUIAFBgAI7ASAgAUGABDsBLCABIAFBEGoiAjYCHCABIAI2AiggASACNgI0IAEgBCATaiIKOwFIIAEgAzoARiABIAY6AFIgASADOgBeIAEgAzoAaiABQQg6AHUgASAEIApqIgY7AVQgASAGIAtqIgY7AWAgASAEIAZqIgY7AWwgASAEIAZqIgY7AXggAUGACDsBRCABIAI2AkAgAUEAOgA4IAEgAjYCTCABQYAKOwFQIAEgAjYCWCABQYAMOwFcIAEgAjYCZCABQYAOOwFoIAEgAjYCcCABIAM6AHYgASAEIAZqIgY7AYQBIAEgAzoAggEgASADOgCOASABIAM6AJoBIAFBDDoApQEgASADOgCmASABIAQgBmoiBjsBkAEgASAEIAZqIgY7AZwBIAEgBCAGaiIGOwGoASABQQA6AHQgASACNgJ8IAFBgBI7AYABIAEgAjYCiAEgAUGAFDsBjAEgASACNgKUASABQYAWOwGYASABIAI2AqABIAEgBCAGaiIGOwG0ASABQQ06ALEBIAEgAjYCrAEgAUEAOgCkASABIAM6ALIBIAEgBCAGaiIGOwHAASABQQ46AL0BIAEgAjYCuAEgAUEAOgCwASABIAM6AL4BIAEgBCAGaiIGOwHMASABQQ86AMkBIAEgAjYCxAEgAUEAOgC8ASABIAM6AMoBIAEgBCAGajsB2AEgAUEQOgDVASABIAI2AtABIAFBADoAyAEgASADOgDWASABIAI2AtwBIAFBADoA1AEgASAHKALsBCICIAcoAlQgAiABLQAAEBogAS0AAGoQTgJAIAEtAAxFDQAgASgCBCICRQ0AIAIQIAsgBy0AvAQhAgsgBygCZCEGIAFBADYC3AEgAUEAOgDUASABQQA2AtABIAFBADoAyAEgAUEANgLEASABQQA6ALwBIAFBADYCuAEgAUEAOgCwASABQQA2AqwBIAFBADoApAEgAUEANgKgASABQQA6AJgBIAFBADYClAEgAUEAOgCMASABQQA2AogBIAFBADoAgAEgAUEANgJ8IAFBADoAdCABQQA2AnAgAUEAOgBoIAFBADYCZCABQQA6AFwgAUEANgJYIAFBADoAUCABQQA2Akwg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AkYNABogAiAEKAIEawshCiADQSBqJAAgCgsJACAAKAI8EBULDwAgASAAKAIAaiACOAIACw0AIAEgACgCAGoqAgALJAEBf0Hk4AAoAgAiAARAA0AgACgCABEEACAAKAIEIgANAAsLCyQBAn8gACgCBCIAEEJBAWoiARAlIgIEfyACIAAgARAaBUEACwttAQN/IwBBEGsiAiQAAkACQCAAQQNLDQAgAEECdEGg2gBqIgMoAgAiAUUNACABKALwByIABEAgABAgCyABECAgA0EANgIADAELQbDaACgCACIBRQ0AIAIgADYCACABQfIeIAIQHAsgAkEQaiQAC6QTAwp/An0BfiMAQTBrIgQkAAJAAkBBoNoAKAIARQRAQaDaACEHDAELQaTaACgCAEUEQEGk2gAhB0EBIQgMAQtBqNoAKAIARQRAQajaACEHQQIhCAwBC0Gs2gAoAgBFBEBBrNoAIQdBAyEIDAELQX8hCEGw2gAoAgAiAEUNASAEQQQ2AgAgAEGjIyAEEBwMAQtB+AcQHSEBIAQgACgCADYCDCAEIAAqAgQ4AhAgBCAAKgIIOAIUIAQgACoCDDgCGCAEIAAoAhA2AhwgBCAAKgIUOAIgIAQgACgCGDYCJCAEIAAoAhw2AiggBCAAKAIgNgIsIAFBfzYCSCABQQA6AEQgAUGAgID8ezYCQCABQn83AzggAUKAgID8ezcDMCABQoCAgPx7NwMoIAFCgICAgICAgMC/fzcDICABQv////8PNwMYIAFCgICA/Hs3AxAgAUKAgID8ezcDCCABQoCAgPyLgIDAv383AwAgAUIANwNwIAFCADcDeCABQgA3A4ABIAFCADcDiAEgAUIANwOQASABQgA3A5gBIAFCADcDoAEgAUIANwCnASABQQA2AtgBIAFCADcD0AEgAUIANwPIASABQgA3A8ABIAFCADcDuAEgAUIANwOwASABQQA2AkwgAUEAOgBQIAFCADcCVCABQgA3AlwgAUIANwJkIAFBADsBbCABQfQDakEAQcEAEBsaIAFCADcCvAQgAUHNmbPuAzYCuAQgAUIANwLEBCABQgA3AswEIAFCADcC1AQgAUIANwLcBCABQgA3AuQEIAFCADcC7AQgAUIANwOIByABQgA3A5AHIAFCADcDmAcgAUIANwKgByABQQA2ArAHIAFCADcDqAcgAUIANwO4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AWoiAjYCeEGw2gAoAgAiBwRAIAMgAjYCBCADIAA2AgAgB0GyHyADEBwLIARBATYChAEgBEEANgJwDAELIARBADYCcAsgA0GgAmokAAsCQCAQIAQoAgwiBmsiC0EATA0AIAQoArgBIQhBACECQQAhACAQIAZBf3NqQQNPBEAgC0F8cSEFQQAhBwNAIAggAEECdGogCCAAIAZqQQJ0aioCADgCACAIIABBAXIiA0ECdGogCCADIAZqQQJ0aioCADgCACAIIABBAnIiA0ECdGogCCADIAZqQQJ0aioCADgCACAIIABBA3IiA0ECdGogCCADIAZqQQJ0aioCADgCACAAQQRqIQAgB0EEaiIHIAVHDQALCyALQQNxIgdFDQADQCAIIABBAnRqIAggACAGakECdGoqAgA4AgAgAEEBaiEAIAJBAWoiAiAHRw0ACwsgGyAYayEbIBggGmohGiAEIAYgC2siADYCkAEMAQsLIAQgBiAQazYCkAELIBxBEGokAAJAIANFBEBBfyEAQbDaACgCACICRQ0BIB4gATYCACACQcYeIB4QHAwBC0EAIQBBvNoALQAARQRAQbzaAEEBOgAAQbTaAEIANwIACyAEKALQASIBRQ0AIARBADYC0AFBfyEAIAFBf0YNACAEKALUASEAQbjaACABNgIAQbTaACAANgIAIAFBAEoEQEGg2AAgACABEBoaCyABIQALIB5BEGokACAAQQBKBEAgF0Gg2AA2AgwgFyAANgIIQdAoIBdBCGoQBwwBCyAXQaDYADYCDCAXQQA2AghB0CggF0EIahAHCzYCACAXQRBqJAALsAEBBH8jAEEQayIDJAAgAigCACIEQfD///8HSQRAAkAgBEEKTQRAIAMgBDoACyADIQUMAQsgBEEPckEBaiIGEB0hBSADIAZBgICAgHhyNgIIIAMgBTYCACADIAQ2AgQLIAUgAkEEaiAEEBogBGpBADoAACADQQxqIAEgAyAAEQEAIAMoAgwQCSADKAIMIgAQCCADLAALQQBIBEAgAygCABAgCyADQRBqJAAgAA8LEFYAC6MFAQt/IwBBEGsiCiQAIAEgAigCACACIAItAAsiB8BBAEgiBRsgAigCBCAHIAUbIAMgBEEAQQEQUSEFAkBBlNgALQAARQRAQYjYAEIANwIAQZDYAEEANgIAIAUEQCAFQQBIDQJBiNgAIAUQHSIHNgIAQZDYACAFIAdqIgY2AgAgB0EAIAUQGxpBjNgAIAY2AgALQZTYAEEBOgAACwJAQYzYACgCACIGQYjYACgCACIHayIIIAVJBEBBACEHAkAgBSAIayIJQZDYACgCACIIIAZrTQRAQYzYACAJBH8gBkEAIAkQGyAJagUgBgs2AgAMAQsgBkGI2AAoAgAiC2siDCAJaiIFQQBOBEBB/////wcgCCALayIIQQF0Ig0gBSAFIA1JGyAIQf////8DTxsiBQRAIAUQHSEHCyAFIAdqIQ0gCSAHIAxqIghBACAJEBsiBWohCQJAIAYgC0YEQCAFIQcMAQsgC0F/cyAGaiEPIAxBA3EiDARAQQAhBQNAIAhBAWsiCCAGQQFrIgYtAAA6AAAgBUEBaiIFIAxHDQALCyAPQQNPBEADQCAIQQFrIAZBAWstAAA6AAAgCEECayAGQQJrLQAAOgAAIAhBA2sgBkEDay0AADoAACAIQQRrIgggBkEEayIGLQAAOgAAIAYgC0cNAAsLQYjYACgCACEGC0GQ2AAgDTYCAEGM2AAgCTYCAEGI2AAgBzYCACAGBEAgBhAgCwwBCxBUAAtBiNgAKAIAIQcMAQsgBSAITw0AQYzYACAFIAdqNgIACyABIAIoAgAgAiACLQALIgHAQQBIIgUbIAIoAgQgASAFGyADIAQgB0EAEFEhASAKQYjYACgCADYCDCAKIAE2AgggAEHQKCAKQQhqEAc2AgAgCkEQaiQADwsQVAALIgEBfiABIAKtIAOtQiCGhCAEIAAREwAiBUIgiKckASAFpwu0AQEEfyMAQRBrIgUkACACKAIAIgZB8P///wdJBEACQCAGQQpNBEAgBSAGOgALIAUhBwwBCyAGQQ9yQQFqIggQHSEHIAUgCEGAgICAeHI2AgggBSAHNgIAIAUgBjYCBAsgByACQQRqIAYQGiAGakEAOgAAIAVBDGogASAFIAMgBCAAEQcAIAUoAgwQCSAFKAIMIgAQCCAFLAALQQBIBEAgBSgCABAgCyAFQRBqJAAgAA8LEFYACwcAIAAoAgQLBQBBwgoLFgAgAEUEQEEADwsgAEG00QAQOkEARwsaACAAIAEoAgggBRAhBEAgASACIAMgBBA4Cws3ACAAIAEoAgggBRAhBEAgASACIAMgBBA4DwsgACgCCCIAIAEgAiADIAQgBSAAKAIAKAIUEQkAC6cBACAAIAEoAgggBBAhBEACQCABKAIEIAJHDQAgASgCHEEBRg0AIAEgAzYCHAsPCwJAIAAgASgCACAEECFFDQACQCACIAEoAhBHBEAgASgCFCACRw0BCyADQQFHDQEgAUEBNgIgDwsgASACNgIUIAEgAzYCICABIAEoAihBAWo2AigCQCABKAIkQQFHDQAgASgCGEECRw0AIAFBAToANgsgAUEENgIsCwsJACABIAARAgALC4VOFwBBgAgLpx8tKyAgIDBYMHgALTBYKzBYIDBYLTB4KzB4IDB4AHNhbXBsZUZvcm1hdE91dABzYW1wbGVSYXRlT3V0AFtVXSBGYXN0ZXN0AFtNVF0gRmFzdGVzdABbRFRdIEZhc3Rlc3QAW1VdIEZhc3QAW01UXSBGYXN0AFtEVF0gRmFzdAB1bnNpZ25lZCBzaG9ydAB0eFByb3RvY29sU2V0RnJlcVN0YXJ0AHJ4UHJvdG9jb2xTZXRGcmVxU3RhcnQAdW5zaWduZWQgaW50AGluaXQAZmxvYXQAU2FtcGxlRm9ybWF0AHVpbnQ2NF90AGdldERlZmF1bHRQYXJhbWV0ZXJzAHJ4RHVyYXRpb25GcmFtZXMAdmVjdG9yAHVuc2lnbmVkIGNoYXIAc2FtcGxlRm9ybWF0SW5wAHNhbXBsZVJhdGVJbnAAc3RkOjpleGNlcHRpb24AbmFuAGJvb2wAdHhUb2dnbGVQcm90b2NvbAByeFRvZ2dsZVByb3RvY29sAGVtc2NyaXB0ZW46OnZhbABbVV0gTm9ybWFsAFtNVF0gTm9ybWFsAFtEVF0gTm9ybWFsAHBheWxvYWRMZW5ndGgAZGlzYWJsZUxvZwBlbmFibGVMb2cAdW5zaWduZWQgbG9uZwBzdGQ6OndzdHJpbmcAYmFzaWNfc3RyaW5nAHN0ZDo6c3RyaW5nAHN0ZDo6dTE2c3RyaW5nAHN0ZDo6dTMyc3RyaW5nAGluZgBzYW1wbGVSYXRlAHNhbXBsZXNQZXJGcmFtZQBkb3VibGUAZnJlZQBlbmNvZGUAZGVjb2RlAG9wZXJhdGluZ01vZGUAc291bmRNYXJrZXJUaHJlc2hvbGQAdm9pZABQcm90b2NvbElkAEdHV0FWRV9PUEVSQVRJTkdfTU9ERV9UWABHR1dBVkVfT1BFUkFUSU5HX01PREVfUlhfQU5EX1RYAEdHV0FWRV9PUEVSQVRJTkdfTU9ERV9SWABHR1dBVkVfUFJPVE9DT0xfTVRfRkFTVEVTVABHR1dBVkVfUFJPVE9DT0xfRFRfRkFTVEVTVABHR1dBVkVfUFJPVE9DT0xfQVVESUJMRV9GQVNURVNUAEdHV0FWRV9QUk9UT0NPTF9VTFRSQVNPVU5EX0ZBU1RFU1QAR0dXQVZFX1BST1RPQ09MX01UX0ZBU1QAR0dXQVZFX1BST1RPQ09MX0RUX0ZBU1QAR0dXQVZFX1BST1RPQ09MX0FVRElCTEVfRkFTVABHR1dBVkVfUFJPVE9DT0xfVUxUUkFTT1VORF9GQVNUAEdHV0FWRV9PUEVSQVRJTkdfTU9ERV9VU0VfRFNTAEdHV0FWRV9PUEVSQVRJTkdfTU9ERV9UWF9PTkxZX1RPTkVTAE5BTgBHR1dBVkVfUFJPVE9DT0xfTVRfTk9STUFMAEdHV0FWRV9QUk9UT0NPTF9EVF9OT1JNQUwAR0dXQVZFX1BST1RPQ09MX0FVRElCTEVfTk9STUFMAEdHV0FWRV9QUk9UT0NPTF9VTFRSQVNPVU5EX05PUk1BTABJTkYAR0dXQVZFX1NBTVBMRV9GT1JNQVRfVU5ERUZJTkVEAGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHNob3J0PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1bnNpZ25lZCBzaG9ydD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8aW50PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1bnNpZ25lZCBpbnQ+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PGZsb2F0PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1aW50OF90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxpbnQ4X3Q+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHVpbnQxNl90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxpbnQxNl90PgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1aW50NjRfdD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8aW50NjRfdD4AZW1zY3JpcHRlbjo6bWVtb3J5X3ZpZXc8dWludDMyX3Q+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PGludDMyX3Q+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PGNoYXI+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHVuc2lnbmVkIGNoYXI+AHN0ZDo6YmFzaWNfc3RyaW5nPHVuc2lnbmVkIGNoYXI+AGVtc2NyaXB0ZW46Om1lbW9yeV92aWV3PHNpZ25lZCBjaGFyPgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxsb25nPgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzx1bnNpZ25lZCBsb25nPgBlbXNjcmlwdGVuOjptZW1vcnlfdmlldzxkb3VibGU+AEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fOQBHR1dBVkVfUFJPVE9DT0xfQ1VTVE9NXzgAR0dXQVZFX1NBTVBMRV9GT1JNQVRfVTgAR0dXQVZFX1NBTVBMRV9GT1JNQVRfSTgAR0dXQVZFX1BST1RPQ09MX0NVU1RPTV83AEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fNgBHR1dBVkVfU0FNUExFX0ZPUk1BVF9VMTYAR0dXQVZFX1NBTVBMRV9GT1JNQVRfSTE2AEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fNQBHR1dBVkVfUFJPVE9DT0xfQ1VTVE9NXzQAR0dXQVZFX1BST1RPQ09MX0NVU1RPTV8zAEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fMgBHR1dBVkVfU0FNUExFX0ZPUk1BVF9GMzIAR0dXQVZFX1BST1RPQ09MX0NVU1RPTV8xAEdHV0FWRV9QUk9UT0NPTF9DVVNUT01fMAAuAChudWxsKQBFcnJvcjogY2FwdHVyZSBzYW1wbGUgcmF0ZSAoJWcgSHopIG11c3QgYmUgPj0gJWcgSHoKAEVycm9yOiBjYXB0dXJlIHNhbXBsZSByYXRlICglZyBIeikgbXVzdCBiZSA8PSAlZyBIegoARXJyb3I6IGZhaWxlZCB0byBjb21wdXRlIHRoZSBzaXplIG9mIHRoZSByZXF1aXJlZCBtZW1vcnkKAFRydW5jYXRpbmcgZGF0YSBmcm9tICVkIHRvICVkIGJ5dGVzCgBDYW5ub3QgZGVjb2RlIHdoaWxlIHRyYW5zbWl0dGluZwoAVHggaXMgZGlzYWJsZWQgLSBjYW5ub3QgdHJhbnNtaXQgZGF0YSB3aXRoIHRoaXMgR0dXYXZlIGluc3RhbmNlCgBSeCBpcyBkaXNhYmxlZCAtIGNhbm5vdCByZWNlaXZlIGRhdGEgd2l0aCB0aGlzIEdHV2F2ZSBpbnN0YW5jZQoAUHJvdG9jb2wgJWQgaXMgbm90IGVuYWJsZWQgLSBtYWtlIHN1cmUgdG8gZW5hYmxlIGl0IGJlZm9yZSBjcmVhdGluZyB0aGUgaW5zdGFuY2UKAE1vbm8tdG9uZSBwcm90b2NvbHMgd2l0aCB2YXJpYWJsZSBsZW5ndGggYXJlIG5vdCBzdXBwb3J0ZWQKAEZhaWxlZCB0byBpbml0aWFsaXplIFR4IHRyYW5zbWlzc2lvbiBmb3IgR0dXYXZlIGluc3RhbmNlICVkCgBJbnZhbGlkIEdHV2F2ZSBpbnN0YW5jZSAlZAoARmFpbGVkIHRvIGVuY29kZSBkYXRhIC0gR0dXYXZlIGluc3RhbmNlICVkCgBGYWlsZWQgdG8gZGVjb2RlIGRhdGEgLSBHR1dhdmUgaW5zdGFuY2UgJWQKAEZhaWxlZCB0byBmcmVlIEdHV2F2ZSBpbnN0YW5jZSAtIGludmFsaWQgR0dXYXZlIGluc3RhbmNlIGlkICVkCgBSZWNlaXZlZCBlbmQgbWFya2VyLiBGcmFtZXMgbGVmdCA9ICVkLCByZWNvcmRlZCA9ICVkCgBFcnJvcjogZmFpbGVkIHRvIGFsbG9jYXRlIHRoZSByZXF1aXJlZCBtZW1vcnk6ICVkCgBJbnZhbGlkIHBheWxvYWQgbGVuZ3RoOiAlZCwgbWF4OiAlZAoASW52YWxpZCBzYW1wbGVzIHBlciBmcmFtZTogJWQsIG1heDogJWQKAEludmFsaWQgb3IgdW5zdXBwb3J0ZWQgcGxheWJhY2sgc2FtcGxlIGZvcm1hdDogJWQKAEludmFsaWQgb3IgdW5zdXBwb3J0ZWQgY2FwdHVyZSBzYW1wbGUgZm9ybWF0OiAlZAoASW52YWxpZCBzYW1wbGUgZm9ybWF0OiAlZAoATmVnYXRpdmUgZGF0YSBzaXplOiAlZAoARXJyb3I6IGZhaWxlZCB0byBhbGxvY2F0ZSBtZW1vcnkgLSBoZWFwU2l6ZTA6ICVkLCBoZWFwU2l6ZTogJWQKAEludmFsaWQgdm9sdW1lOiAlZAoASW52YWxpZCBwcm90b2NvbCBJRDogJWQKAFJlY2VpdmluZyBzb3VuZCBkYXRhIC4uLgoAQW5hbHl6aW5nIGNhcHR1cmVkIGRhdGEgLi4KAEZhaWxlZCB0byBjcmVhdGUgR0dXYXZlIGluc3RhbmNlIC0gcmVhY2hlZCBtYXhpbXVtIG51bWJlciBvZiBpbnN0YW5jZXMgKCVkKQoARmFpbHVyZSBkdXJpbmcgY2FwdHVyZSAtIHByb3ZpZGVkIGJ5dGVzICglZCkgYXJlIG5vdCBtdWx0aXBsZSBvZiBzYW1wbGUgc2l6ZSAoJWQpCgBEZWNvZGVkIGxlbmd0aCA9ICVkLCBwcm90b2NvbCA9ICclcycgKCVkKQoARXJyb3I6IHRvdGFsIGxlbmd0aCAlZCAocGF5bG9hZCAlZCArIEVDQyAlZCBieXRlcykgaXMgdG9vIGxhcmdlICggPiAlZCkKAEZhaWxlZCB0byBjYXB0dXJlIHNvdW5kIGRhdGEuIFBsZWFzZSB0cnkgYWdhaW4gKGxlbmd0aCA9ICVkKQoAUmVjZWl2ZWQgc291bmQgZGF0YSBzdWNjZXNzZnVsbHk6ICclcycKADE5Z2d3YXZlX1NhbXBsZUZvcm1hdAAAzCkAAB0TAAAxN2dnd2F2ZV9Qcm90b2NvbElkAMwpAAA8EwAAMTdnZ3dhdmVfUGFyYW1ldGVycwAYKgAAWBMAAGkAdmkAaWlpAHZpaWkAZmlpAHZpaWYAAGwTAABpaQAAaCkAAGwTAAAUKQAAaCkAAHZpaQBBsCcL0gHYEwAAaCkAACAUAABQEwAAaCkAAE4xMGVtc2NyaXB0ZW4zdmFsRQAAGCoAAMQTAABOU3QzX18yMTJiYXNpY19zdHJpbmdJY05TXzExY2hhcl90cmFpdHNJY0VFTlNfOWFsbG9jYXRvckljRUVFRQAAGCoAAOATAABpaWlpaWkATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJY0VFAAAAGCoAAC8UAADYEwAAaCkAACAUAABpaWlpAAAAABQpAAAUKQAAUBMAAGgpAABoKQAAaCkAQZApC7cilp+0rxuR3sVFdeguDzJKX7RWlct/alRqSPILe837k208d17DM0fA8XEyMyc1aEcfTqwjQl8AN6RQbUgkkXyhTgAAARkCMhrGA98z7htox0sEZOAONI3vgRzBafjICExxBYplL+EkDyE1k47a8BKCRR21wn1qJ/m5yZoJeE3kcqYGv4tiZt0w/eKYJbMQkSKINtCUzo+W273x0hNcgzhGQB5CtqPDSH5uazooVPqFuj3KXpufChV5K07U5axz86dXB3DA94yAYw1nSt7tMcX+GOOlmXcmuLR8EUSS2SMgiS43P9FblbzPzZCHl7Lc/L5h8lbTqxQqXZ6EPDlTR21Boh8tQ9i3e6R2xBdJ7H8Mb/ZsoTtSKZ1VqvtghrG7zD5ay1lfsJypoFEL9RbrenUs10+u1enm563odNb06qhQWK8BAgQIECBAgB06dOjNhxMmTJgtWrR16smPAwYMGDBgwJ0nTpwlSpQ1atS1d+7BnyNGjAUKFChQoF26adK5b96hX75hwpkvXrxlyokPHjx48P3n07tr1rF//uHfo1u2ceLZr0OGESJEiA0aNGjQvWfOgR8+fPjtx5M7duzFlzNmzIUXLly4bdqpT54hQoQVKlSoTZopUqRVqkmSOXLk1bdz5tG/Y8aRP3785deze/bx/+Pbq0uWMWLElTdu3KVXrkGCGTJkyI0HDhw4cODdp1OmUaJZsnny+e/DmytWrEWKCRIkSJA9evT19/P768uLCxYsWLB9+unPgxs2bNitR44BAgQIECBAgB06dOjNhxMmTJgtWrR16smPAwYMGDBgwJ0nTpwlSpQ1atS1d+7BnyNGjAUKFChQoF26adK5b96hX75hwpkvXrxlyokPHjx48P3n07tr1rF//uHfo1u2ceLZr0OGESJEiA0aNGjQvWfOgR8+fPjtx5M7duzFlzNmzIUXLly4bdqpT54hQoQVKlSoTZopUqRVqkmSOXLk1bdz5tG/Y8aRP3785deze/bx/+Pbq0uWMWLElTdu3KVXrkGCGTJkyI0HDhw4cODdp1OmUaJZsnny+e/DmytWrEWKCRIkSJA9evT19/P768uLCxYsWLB9+unPgxs2bNitR44BAgAAAAABAAAAAQAAAAIAAAACAAAABAAAAE5TdDNfXzIxMmJhc2ljX3N0cmluZ0loTlNfMTFjaGFyX3RyYWl0c0loRUVOU185YWxsb2NhdG9ySWhFRUVFAAAYKgAA6BcAAE5TdDNfXzIxMmJhc2ljX3N0cmluZ0l3TlNfMTFjaGFyX3RyYWl0c0l3RUVOU185YWxsb2NhdG9ySXdFRUVFAAAYKgAAMBgAAE5TdDNfXzIxMmJhc2ljX3N0cmluZ0lEc05TXzExY2hhcl90cmFpdHNJRHNFRU5TXzlhbGxvY2F0b3JJRHNFRUVFAAAAGCoAAHgYAABOU3QzX18yMTJiYXNpY19zdHJpbmdJRGlOU18xMWNoYXJfdHJhaXRzSURpRUVOU185YWxsb2NhdG9ySURpRUVFRQAAABgqAADEGAAATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJYUVFAAAYKgAAEBkAAE4xMGVtc2NyaXB0ZW4xMW1lbW9yeV92aWV3SWhFRQAAGCoAADgZAABOMTBlbXNjcmlwdGVuMTFtZW1vcnlfdmlld0lzRUUAABgqAABgGQAATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJdEVFAAAYKgAAiBkAAE4xMGVtc2NyaXB0ZW4xMW1lbW9yeV92aWV3SWlFRQAAGCoAALAZAABOMTBlbXNjcmlwdGVuMTFtZW1vcnlfdmlld0lqRUUAABgqAADYGQAATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJbEVFAAAYKgAAABoAAE4xMGVtc2NyaXB0ZW4xMW1lbW9yeV92aWV3SW1FRQAAGCoAACgaAABOMTBlbXNjcmlwdGVuMTFtZW1vcnlfdmlld0l4RUUAABgqAABQGgAATjEwZW1zY3JpcHRlbjExbWVtb3J5X3ZpZXdJeUVFAAAYKgAAeBoAAE4xMGVtc2NyaXB0ZW4xMW1lbW9yeV92aWV3SWZFRQAAGCoAAKAaAABOMTBlbXNjcmlwdGVuMTFtZW1vcnlfdmlld0lkRUUAABgqAADIGgAAAwAAAAQAAAAEAAAABgAAAIP5ogBETm4A/CkVANFXJwDdNPUAYtvAADyZlQBBkEMAY1H+ALveqwC3YcUAOm4kANJNQgBJBuAACeouAByS0QDrHf4AKbEcAOg+pwD1NYIARLsuAJzphAC0JnAAQX5fANaROQBTgzkAnPQ5AItfhAAo+b0A+B87AN7/lwAPmAUAES/vAApaiwBtH20Az342AAnLJwBGT7cAnmY/AC3qXwC6J3UA5evHAD178QD3OQcAklKKAPtr6gAfsV8ACF2NADADVgB7/EYA8KtrACC8zwA29JoA46kdAF5hkQAI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";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}var binary=tryParseAsDataURI(file);if(binary){return binary}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(instance=>instance).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){return instantiateArrayBuffer(binaryFile,imports,callback)}function createWasm(){var info={"a":wasmImports};function receiveInstance(instance,module){var exports=instance.exports;wasmExports=exports;wasmMemory=wasmExports["A"];updateMemoryViews();wasmTable=wasmExports["C"];addOnInit(wasmExports["B"]);removeRunDependency("wasm-instantiate");return exports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var InternalError=undefined;var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>getterReturnType["fromWireType"](getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{};var embind_init_charCodes=()=>{var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes};var embind_charCodes=undefined;var readLatin1String=ptr=>{var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret};var BindingError=undefined;var throwBindingError=message=>{throw new BindingError(message)};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}return sharedRegisterType(rawType,registeredInstance,options)}var GenericWireTypeSize=8;var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":function(pointer){return this["fromWireType"](HEAPU8[pointer])},destructorFunction:null})};var __embind_register_constant=(name,type,value)=>{name=readLatin1String(name);whenDependentTypesAreResolved([],[type],function(type){type=type[0];Module[name]=type["fromWireType"](value);return[]})};function handleAllocatorInit(){Object.assign(HandleAllocator.prototype,{get(id){return this.allocated[id]},has(id){return this.allocated[id]!==undefined},allocate(handle){var id=this.freelist.pop()||this.allocated.length;this.allocated[id]=handle;return id},free(id){this.allocated[id]=undefined;this.freelist.push(id)}})}function HandleAllocator(){this.allocated=[undefined];this.freelist=[]}var emval_handles=new HandleAllocator;var __emval_decref=handle=>{if(handle>=emval_handles.reserved&&0===--emval_handles.get(handle).refcount){emval_handles.free(handle)}};var count_emval_handles=()=>{var count=0;for(var i=emval_handles.reserved;i{emval_handles.allocated.push({value:undefined},{value:null},{value:true},{value:false});emval_handles.reserved=emval_handles.allocated.length;Module["count_emval_handles"]=count_emval_handles};var Emval={toValue:handle=>{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handles.get(handle).value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{return emval_handles.allocate({refcount:1,value:value})}}}};var __embind_register_emval=(rawType,name)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":(destructors,value)=>Emval.toHandle(value),"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})};var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${arguments.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}};var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this["fromWireType"](HEAP8[pointer>>0])}:function(pointer){return this["fromWireType"](HEAPU8[pointer>>0])};case 2:return signed?function(pointer){return this["fromWireType"](HEAP16[pointer>>1])}:function(pointer){return this["fromWireType"](HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this["fromWireType"](HEAP32[pointer>>2])}:function(pointer){return this["fromWireType"](HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_enum=(rawType,name,size,isSigned)=>{name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":(destructors,c)=>c.value,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function createNamedFunction(name,body){name=makeLegalFunctionName(name);return{[name]:function(){return body.apply(this,arguments)}}[name]}var getTypeName=type=>{var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 8:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":value=>value,"toWireType":(destructors,value)=>value,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":floatReadValueFromPointer(name,size),destructorFunction:null})};function newFunc(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError(`new_ called with constructor type ${typeof constructor} which is not a function`)}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc,isAsync){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns||isAsync?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i{var array=[];for(var i=0;i>2])}return array};var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var dynCallLegacy=(sig,ptr,args)=>{var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var dynCall=(sig,ptr,args)=>{if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn};var getDynCaller=(sig,ptr)=>{var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}};var embind__requireFunction=(signature,rawFunction)=>{signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};var extendError=(baseErrorType,errorName)=>{var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return`${this.name}: ${this.message}`}};return errorClass};var UnboundTypeError=undefined;var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer>>0]:pointer=>HEAPU8[pointer>>0];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})};function readPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;var UTF8ArrayToString=(heapOrArray,idx,maxBytesToRead)=>{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var __embind_register_std_string=(rawType,name)=>{name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":value=>{var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i{if(value instanceof ArrayBuffer){value=new Uint8Array(value)}var length;var valueIsOfTypeString=typeof value=="string";if(!(valueIsOfTypeString||value instanceof Uint8Array||value instanceof Uint8ClampedArray||value instanceof Int8Array)){throwBindingError("Cannot pass non-string to std::string")}if(stdStringIsUTF8&&valueIsOfTypeString){length=lengthBytesUTF8(value)}else{length=value.length}var base=_malloc(4+length+1);var ptr=base+4;HEAPU32[base>>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;i_free(ptr)})};var UTF16Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf-16le"):undefined;var UTF16ToString=(ptr,maxBytesToRead)=>{var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder)return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str};var stringToUTF16=(str,outPtr,maxBytesToWrite)=>{if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead)=>{var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=readLatin1String(name);var decodeString,encodeString,getHeap,lengthBytesUTF,shift;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;getHeap=()=>HEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":value=>{var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":GenericWireTypeSize,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:ptr=>_free(ptr)})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})};var __embind_register_void=(rawType,name)=>{name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":()=>undefined,"toWireType":(destructors,o)=>undefined})};var __emval_incref=handle=>{if(handle>4){emval_handles.get(handle).refcount+=1}};var __emval_take_value=(type,arg)=>{type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)};var _abort=()=>{abort("")};var _emscripten_memcpy_big=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);var getHeapMax=()=>2147483648;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var SYSCALLS={varargs:undefined,get(){var ret=HEAP32[SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret},getp(){return SYSCALLS.get()},getStr(ptr){var ret=UTF8ToString(ptr);return ret}};var _fd_close=fd=>52;var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);return 70}var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};InternalError=Module["InternalError"]=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};embind_init_charCodes();BindingError=Module["BindingError"]=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};handleAllocatorInit();init_emval();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");var wasmImports={o:___cxa_throw,p:__embind_finalize_value_object,s:__embind_register_bigint,y:__embind_register_bool,f:__embind_register_constant,x:__embind_register_emval,n:__embind_register_enum,a:__embind_register_enum_value,m:__embind_register_float,c:__embind_register_function,e:__embind_register_integer,b:__embind_register_memory_view,l:__embind_register_std_string,g:__embind_register_std_wstring,q:__embind_register_value_object,d:__embind_register_value_object_field,z:__embind_register_void,i:__emval_decref,j:__emval_incref,h:__emval_take_value,t:_abort,w:_emscripten_memcpy_big,u:_emscripten_resize_heap,v:_fd_close,r:_fd_seek,k:_fd_write};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["B"])();var _malloc=a0=>(_malloc=wasmExports["D"])(a0);var _free=a0=>(_free=wasmExports["E"])(a0);var ___getTypeName=a0=>(___getTypeName=wasmExports["F"])(a0);var __embind_initialize_bindings=Module["__embind_initialize_bindings"]=()=>(__embind_initialize_bindings=Module["__embind_initialize_bindings"]=wasmExports["G"])();var ___errno_location=()=>(___errno_location=wasmExports["__errno_location"])();var ___cxa_is_pointer_type=a0=>(___cxa_is_pointer_type=wasmExports["H"])(a0);var dynCall_jiji=Module["dynCall_jiji"]=(a0,a1,a2,a3,a4)=>(dynCall_jiji=Module["dynCall_jiji"]=wasmExports["I"])(a0,a1,a2,a3,a4);function intArrayFromBase64(s){if(typeof ENVIRONMENT_IS_NODE!="undefined"&&ENVIRONMENT_IS_NODE){var buf=Buffer.from(s,"base64");return new Uint8Array(buf.buffer,buf.byteOffset,buf.length)}try{var decoded=atob(s);var bytes=new Uint8Array(decoded.length);for(var i=0;i0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); return moduleArg.ready } ); })(); if (typeof exports === 'object' && typeof module === 'object') module.exports = ggwave_factory; else if (typeof define === 'function' && define['amd']) define([], () => ggwave_factory); ================================================ FILE: bindings/javascript/package-tmpl.json ================================================ { "name": "ggwave", "version": "@PROJECT_VERSION@", "description": "Tiny data-over-sound library", "main": "ggwave.js", "scripts": { "test": "echo \"todo: add tests\" && exit 0" }, "repository": { "type": "git", "url": "git+https://github.com/ggerganov/ggwave.git" }, "keywords": [ "data-over-sound", "fsk", "sound-library", "ultrasound", "ecc" ], "author": "Georgi Gerganov", "license": "MIT", "bugs": { "url": "https://github.com/ggerganov/ggwave/issues" }, "homepage": "https://github.com/ggerganov/ggwave#readme" } ================================================ FILE: bindings/javascript/package.json ================================================ { "name": "ggwave", "version": "0.4.2", "description": "Tiny data-over-sound library", "main": "ggwave.js", "scripts": { "test": "echo \"todo: add tests\" && exit 0" }, "repository": { "type": "git", "url": "git+https://github.com/ggerganov/ggwave.git" }, "keywords": [ "data-over-sound", "fsk", "sound-library", "ultrasound", "ecc" ], "author": "Georgi Gerganov", "license": "MIT", "bugs": { "url": "https://github.com/ggerganov/ggwave/issues" }, "homepage": "https://github.com/ggerganov/ggwave#readme" } ================================================ FILE: bindings/python/.gitignore ================================================ ggwave.so README.rst ggwave.bycython.cpp ggwave.cpython-*-x86_64-linux-gnu.so ggwave/ ggwave.egg-info/ dist/ ================================================ FILE: bindings/python/MANIFEST.in ================================================ recursive-include ggwave/include * recursive-include ggwave/src/ * include *.pxd include *.pyx ================================================ FILE: bindings/python/Makefile ================================================ default: build PKG_NAME := ggwave DEST := dist/ VER := $(shell cat setup.py | grep version |cut -d\" -f2) VERSION := $(strip $(VER)) ORIG_TGZ := $(PKG_NAME)_$(VERSION).orig.tar.gz ARCH := $(shell dpkg --print-architecture) DEB_BUILD_OPTIONS := nocheck DEB_BUILD_DIR := $(DEST)$(PKG_NAME)-$(VERSION) DEB_VER := 0 DEB := python3-$(PKG_NAME)_$(VERSION)-$(DEB_VER)_$(ARCH).deb SOURCE_DATE_EPOCH := $(shell git log -1 --pretty=%ct) SOURCE_PKG_CMD := python -m build --sdist DEB_BUILD_CMD := dpkg-buildpackage -rfakeroot -uc -us COGAPP ?= $(shell which cog || which cogapp) .PHONY: ggwave: # create a clean (maybe updated) copy of ggwave src rm -rf ggwave && mkdir ggwave && cp -r ../../include ggwave/ && cp -r ../../src ggwave/ deps: echo python -m pip install cogapp cython twine pyggwave.bycython.cpp: ggwave.pyx cggwave.pxd cython --cleanup=3 --cplus ggwave.pyx -o ggwave.bycython.cpp # To build package, README.rst is needed, because it goes into long description of package, # which is what is visible on PyPI. # However, to generate README.rst from README-tmpl.rst, built package is needed (for `import ggwave` in cog)! # Therefore, we first build package without README.rst, use it to generate README.rst, # and then finally build package again but with README.rst. BUILD_SOURCE_FILES=ggwave pyggwave.bycython.cpp setup.py $(DEST)$(DEB): $(DEST)$(ORIG_TGZ) -rm -r $(DEB_BUILD_DIR) tar -C $(DEST) -xf $(DEST)$(ORIG_TGZ) cp -arp debian $(DEB_BUILD_DIR) cd $(DEB_BUILD_DIR) && \ DEB_BUILD_OPTIONS=$(DEB_BUILD_OPTIONS) $(DEB_BUILD_CMD) deb_sdist: $(BUILD_SOURCE_FILES) README.rst SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) $(SOURCE_PKG_CMD) mv $(DEST)$(PKG_NAME)-$(VERSION).tar.gz $(DEST)$(ORIG_TGZ) _deb: @$(eval COGAPP=cogapp) deb: _deb deb_sdist $(DEST)$(DEB) dpkg --contents $(DEST)$(DEB) ls -alh $(DEST)*$(PKG_NAME)* sha256sum $(DEST)$(DEB) buildWithoutREADME.rst: ${BUILD_SOURCE_FILES} GGWAVE_OMIT_README_RST=1 python setup.py build_ext -i README.rst: buildWithoutREADME.rst README-tmpl.rst $(COGAPP) -d -o README.rst README-tmpl.rst BUILD_FILES=${BUILD_SOURCE_FILES} README.rst build: ${BUILD_FILES} python setup.py build_ext -i sdist: ggwave pyggwave.bycython.cpp setup.py README.rst MANIFEST.in python setup.py sdist publish: clean sdist twine upload --repository pypi dist/* clean: rm -rf ggwave dist ggwave.egg-info build rm -f ggwave.c *.bycython.* ggwave.*.so rm -f README.rst ================================================ FILE: bindings/python/README-tmpl.rst ================================================ .. [[[cog import cog import ggwave def indent(text, indentation = " "): return indentation + text.replace("\n", "\n" + indentation) def comment(text): return "# " + text.replace("\n", "\n# ") def cogOutExpression(expr): cog.outl(indent(expr)) cog.outl(indent(comment(str(eval(expr))))) ]]] [[[end]]] ====== ggwave ====== Tiny data-over-sound library. .. [[[cog cog.outl() cog.outl(".. code:: python") cog.outl() cog.outl(indent(comment('generate audio waveform for string "hello python"'))) cog.outl(indent('waveform = ggwave.encode("hello python")')) cog.outl() cog.outl(indent(comment('decode audio waveform'))) cog.outl(indent('text = ggwave.decode(instance, waveform)')) cog.outl() ]]] .. code:: {{ Basic code examples will be generated here. }} .. [[[end]]] -------- Features -------- * Audible and ultrasound transmissions available * Bandwidth of 8-16 bytes/s (depending on the transmission protocol) * Robust FSK modulation * Reed-Solomon based error correction ------------ Installation ------------ :: pip install ggwave --- API --- encode() -------- .. code:: python encode(payload, [protocolId], [volume], [instance]) Encodes ``payload`` into an audio waveform. .. [[[cog import pydoc help_str = pydoc.plain(pydoc.render_doc(ggwave.encode, "%s")) cog.outl() cog.outl('Output of ``help(ggwave.encode)``:') cog.outl() cog.outl('.. code::\n') cog.outl(indent(help_str)) ]]] .. code:: {{ Content of help(ggwave.encode) will be generated here. }} .. [[[end]]] decode() -------- .. code:: python decode(instance, waveform) Analyzes and decodes ``waveform`` into to try and obtain the original payload. A preallocated ggwave ``instance`` is required. .. [[[cog import pydoc help_str = pydoc.plain(pydoc.render_doc(ggwave.decode, "%s")) cog.outl() cog.outl('Output of ``help(ggwave.decode)``:') cog.outl() cog.outl('.. code::\n') cog.outl(indent(help_str)) ]]] .. code:: {{ Content of help(ggwave.decode) will be generated here. }} .. [[[end]]] ----- Usage ----- * Encode and transmit data with sound: .. code:: python import ggwave import pyaudio p = pyaudio.PyAudio() # generate audio waveform for string "hello python" waveform = ggwave.encode("hello python", protocolId = 1, volume = 20) print("Transmitting text 'hello python' ...") stream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, output=True, frames_per_buffer=4096) stream.write(waveform, len(waveform)//4) stream.stop_stream() stream.close() p.terminate() * Capture and decode audio data: .. code:: python import ggwave import pyaudio p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, input=True, frames_per_buffer=1024) print('Listening ... Press Ctrl+C to stop') instance = ggwave.init() try: while True: data = stream.read(1024, exception_on_overflow=False) res = ggwave.decode(instance, data) if (not res is None): try: print('Received text: ' + res.decode("utf-8")) except: pass except KeyboardInterrupt: pass ggwave.free(instance) stream.stop_stream() stream.close() p.terminate() ---- More ---- Check out ``_ for more information about ggwave! ----------- Development ----------- Check out `ggwave python package on Github `_. ================================================ FILE: bindings/python/README.md ================================================ # ggwave python package This README contains only development information, you can check out full README (README.rst) for the latest version of ggwave python package on [ggwave's PyPI page](https://pypi.org/project/ggwave/). README.rst is not commited to git because it is generated from [README-tmpl.rst](./README-tmpl.rst). ## Building Run `make build` to generate an extension module as .so file. You can test it then by importing it from python interpreter `import ggwave` and running `ggwave.encode('test')` (you have to be positioned in the directory where .so was built). This is useful for testing while developing. Run `make sdist` to create a source distribution, but not publish it - it is a tarball in dist/ that will be uploaded to pip on `publish`. Use this to check that tarball is well structured and contains all needed files, before you publish. Good way to test it is to run `sudo pip install dist/ggwave-*.tar.gz`, which will try to install ggwave from it, same way as pip will do it when it is published. `make clean` removes all generated files. README.rst is auto-generated from [README-tmpl.rst](./README-tmpl.rst), to run regeneration do `make README.rst`. README.rst is also automatically regenerated when building package (e.g. `make build`). This enables us to always have up to date results of code execution and help documentation of ggwave methods in readme. ## Publishing Remember to update version in setup.py before publishing. To trigger automatic publish to PyPI, create a tag and push it to Github -> Travis will create sdist, build wheels, and push them all to PyPI while publishing new version. You can also publish new version manually if needed: run `make publish` to create a source distribution and publish it to the PyPI. ## Acknowledgments These Python bindings are generated by following [edlib](https://github.com/Martinsos/edlib) example ================================================ FILE: bindings/python/cggwave.pxd ================================================ cdef extern from "ggwave.h" nogil: ctypedef enum ggwave_SampleFormat: GGWAVE_SAMPLE_FORMAT_UNDEFINED, GGWAVE_SAMPLE_FORMAT_U8, GGWAVE_SAMPLE_FORMAT_I8, GGWAVE_SAMPLE_FORMAT_U16, GGWAVE_SAMPLE_FORMAT_I16, GGWAVE_SAMPLE_FORMAT_F32 ctypedef enum ggwave_ProtocolId: GGWAVE_PROTOCOL_AUDIBLE_NORMAL, GGWAVE_PROTOCOL_AUDIBLE_FAST, GGWAVE_PROTOCOL_AUDIBLE_FASTEST, GGWAVE_PROTOCOL_ULTRASOUND_NORMAL, GGWAVE_PROTOCOL_ULTRASOUND_FAST, GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, GGWAVE_PROTOCOL_DT_NORMAL, GGWAVE_PROTOCOL_DT_FAST, GGWAVE_PROTOCOL_DT_FASTEST, GGWAVE_PROTOCOL_MT_NORMAL, GGWAVE_PROTOCOL_MT_FAST, GGWAVE_PROTOCOL_MT_FASTEST, GGWAVE_PROTOCOL_CUSTOM_0, GGWAVE_PROTOCOL_CUSTOM_1, GGWAVE_PROTOCOL_CUSTOM_2, GGWAVE_PROTOCOL_CUSTOM_3, GGWAVE_PROTOCOL_CUSTOM_4, GGWAVE_PROTOCOL_CUSTOM_5, GGWAVE_PROTOCOL_CUSTOM_6, GGWAVE_PROTOCOL_CUSTOM_7, GGWAVE_PROTOCOL_CUSTOM_8, GGWAVE_PROTOCOL_CUSTOM_9 enum: GGWAVE_OPERATING_MODE_RX, GGWAVE_OPERATING_MODE_TX, GGWAVE_OPERATING_MODE_RX_AND_TX, GGWAVE_OPERATING_MODE_TX_ONLY_TONES, GGWAVE_OPERATING_MODE_USE_DSS ctypedef struct ggwave_Parameters: int payloadLength float sampleRateInp float sampleRateOut float sampleRate int samplesPerFrame float soundMarkerThreshold ggwave_SampleFormat sampleFormatInp ggwave_SampleFormat sampleFormatOut int operatingMode ctypedef int ggwave_Instance ggwave_Parameters ggwave_getDefaultParameters(); ggwave_Instance ggwave_init(const ggwave_Parameters parameters); void ggwave_free(ggwave_Instance instance); int ggwave_encode( ggwave_Instance instance, const void * payloadBuffer, int payloadSize, ggwave_ProtocolId protocolId, int volume, void * waveformBuffer, int query); int ggwave_decode( ggwave_Instance instance, const void * waveformBuffer, int waveformSize, void * payloadBuffer); void ggwave_setLogFile(void * fptr); void ggwave_rxToggleProtocol( ggwave_ProtocolId protocolId, int state); void ggwave_txToggleProtocol( ggwave_ProtocolId protocolId, int state); ================================================ FILE: bindings/python/debian/changelog ================================================ ggwave (0.4.2-0) unstable; urgency=low * Initial release -- Georgi Gerganov Sat, 15 Mar 2025 11:17:22 +0000 ================================================ FILE: bindings/python/debian/control ================================================ Source: ggwave Section: python Priority: optional Maintainer: Georgi Gerganov Build-Depends: debhelper-compat (= 13), dh-python, cython3, python3-all-dev, python3-setuptools, Standards-Version: 4.6.2.0 Testsuite: autopkgtest-pkg-pybuild Homepage: https://github.com/ggerganov/ggwave Rules-Requires-Root: no Package: python3-ggwave Architecture: any Depends: ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends}, Description: Tiny data-over-sound library Tiny data-over-sound library. . . .. code:: python . # generate audio waveform for string "hello python" waveform = ggwave.encode("hello python") . # decode audio waveform text = ggwave.decode(instance, waveform) . . -------- Features -------- . * Audible and ultrasound transmissions available * Bandwidth of 8-16 bytes/s (depending on the transmission protocol) * Robust FSK modulation * Reed-Solomon based error correction . ------------ ================================================ FILE: bindings/python/debian/copyright ================================================ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ggwave Upstream-Contact: Georgi Gerganov Source: https://github.com/ggerganov/ggwave Files: * Copyright: Georgi Gerganov License: MIT Files: debian/* Copyright: Georgi Gerganov License: MIT License: MIT ================================================ FILE: bindings/python/debian/python3-ggwave.docs ================================================ README.rst ================================================ FILE: bindings/python/debian/rules ================================================ #! /usr/bin/make -f export PYBUILD_NAME=ggwave %: dh $@ --with python3 --buildsystem=pybuild ================================================ FILE: bindings/python/debian/source/format ================================================ 3.0 (quilt) ================================================ FILE: bindings/python/debian/source/options ================================================ extend-diff-ignore="^[^/]+.(egg-info|dist-info)/" ================================================ FILE: bindings/python/ggwave.pyx ================================================ # cython: language_level=3str cimport cython from libc.stdio cimport stderr from cpython.mem cimport PyMem_Malloc, PyMem_Free import re cimport cggwave def getDefaultParameters(): return cggwave.ggwave_getDefaultParameters() def init(parameters = None): if (parameters is None): parameters = getDefaultParameters() return cggwave.ggwave_init(parameters) def free(instance): return cggwave.ggwave_free(instance) def encode(payload, protocolId = 1, volume = 10, instance = None): """ Encode payload into an audio waveform. @param {string} payload, the data to be encoded @return Generated audio waveform bytes representing 16-bit signed integer samples. """ if isinstance(payload, str): payload = payload.encode('utf-8') cdef bytes data_bytes = payload cdef char* cdata = data_bytes own = False if (instance is None): own = True instance = init(getDefaultParameters()) n = cggwave.ggwave_encode(instance, cdata, len(data_bytes), protocolId, volume, NULL, 1) cdef bytes output_bytes = bytes(n) cdef char* coutput = output_bytes n = cggwave.ggwave_encode(instance, cdata, len(data_bytes), protocolId, volume, coutput, 0) if (own): free(instance) return output_bytes def decode(instance, waveform): """ Analyze and decode audio waveform to obtain original payload @param {bytes} waveform, the audio waveform to decode @return The decoded payload if successful. """ cdef bytes data_bytes = waveform cdef char* cdata = data_bytes cdef bytes output_bytes = bytes(256) cdef char* coutput = output_bytes rxDataLength = cggwave.ggwave_decode(instance, cdata, len(data_bytes), coutput) if (rxDataLength > 0): return coutput[0:rxDataLength] return None def disableLog(): cggwave.ggwave_setLogFile(NULL); def enableLog(): cggwave.ggwave_setLogFile(stderr); def rxToggleProtocol(protocolId, state): cggwave.ggwave_rxToggleProtocol(protocolId, state); def txToggleProtocol(protocolId, state): cggwave.ggwave_txToggleProtocol(protocolId, state); ================================================ FILE: bindings/python/setup-tmpl.py ================================================ from setuptools import setup, Extension from codecs import open import os cmdclass = {} long_description = "" # Build directly from cython source file(s) if user wants so (probably for some experiments). # Otherwise, pre-generated c source file(s) are used. # User has to set environment variable GGWAVE_USE_CYTHON. # e.g.: GGWAVE_USE_CYTHON=1 python setup.py install USE_CYTHON = os.getenv('GGWAVE_USE_CYTHON', False) if USE_CYTHON: from Cython.Build import build_ext ggwave_module_src = "ggwave.pyx" cmdclass['build_ext'] = build_ext else: ggwave_module_src = "ggwave.bycython.cpp" # Load README.rst into long description. # User can skip using README.rst as long description: GGWAVE_OMIT_README_RST=1 python setup.py install OMIT_README_RST = os.getenv('GGWAVE_OMIT_README_RST', False) if not OMIT_README_RST: here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() setup( # Information name = "ggwave", description = "Tiny data-over-sound library.", long_description = long_description, version = "@GGWAVE_VERSION_PYTHON@", url = "https://github.com/ggerganov/ggwave", author = "Georgi Gerganov", author_email = "ggerganov@gmail.com", license = "MIT", keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound", # Build instructions ext_modules = [Extension("ggwave", [ggwave_module_src, "ggwave/src/ggwave.cpp"], include_dirs=["ggwave/include", "ggwave/include/ggwave"], depends=["ggwave/include/ggwave/ggwave.h"], language="c++", extra_compile_args=["-O3", "-std=c++11"])], cmdclass = cmdclass ) ================================================ FILE: bindings/python/setup.py ================================================ from setuptools import setup, Extension from codecs import open import os cmdclass = {} long_description = "" # Build directly from cython source file(s) if user wants so (probably for some experiments). # Otherwise, pre-generated c source file(s) are used. # User has to set environment variable GGWAVE_USE_CYTHON. # e.g.: GGWAVE_USE_CYTHON=1 python setup.py install USE_CYTHON = os.getenv('GGWAVE_USE_CYTHON', False) if USE_CYTHON: from Cython.Build import build_ext ggwave_module_src = "ggwave.pyx" cmdclass['build_ext'] = build_ext else: ggwave_module_src = "ggwave.bycython.cpp" # Load README.rst into long description. # User can skip using README.rst as long description: GGWAVE_OMIT_README_RST=1 python setup.py install OMIT_README_RST = os.getenv('GGWAVE_OMIT_README_RST', False) if not OMIT_README_RST: here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() setup( # Information name = "ggwave", description = "Tiny data-over-sound library.", long_description = long_description, version = "0.4.2", url = "https://github.com/ggerganov/ggwave", author = "Georgi Gerganov", author_email = "ggerganov@gmail.com", license = "MIT", keywords = "data-over-sound fsk ecc serverless pairing qrcode ultrasound", # Build instructions ext_modules = [Extension("ggwave", [ggwave_module_src, "ggwave/src/ggwave.cpp"], include_dirs=["ggwave/include", "ggwave/include/ggwave"], depends=["ggwave/include/ggwave/ggwave.h"], language="c++", extra_compile_args=["-O3", "-std=c++11"])], cmdclass = cmdclass ) ================================================ FILE: bindings/python/test.py ================================================ import sys import ggwave testFailed = False ggwave.disableLog() ggwave.enableLog() samples = ggwave.encode("hello python") if not (samples): testFailed = True if testFailed: print("Some of the tests failed!") else: print("All tests passed!") sys.exit(testFailed) ================================================ FILE: cmake/BuildTypes.cmake ================================================ # 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() ================================================ FILE: cmake/GitVars.cmake ================================================ 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) ================================================ FILE: cmake/sdl2/FindSDL2.cmake ================================================ # 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 . 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) ================================================ FILE: examples/CMakeLists.txt ================================================ # dependencies find_package(Threads REQUIRED) if (GGWAVE_SUPPORT_SDL2) # SDL2 if (EMSCRIPTEN) set(CMAKE_CXX_FLAGS " \ -s USE_SDL=2 \ -s DISABLE_EXCEPTION_CATCHING=0 \ ") set(CMAKE_CXX_LINK_FLAGS " \ --bind \ -s TOTAL_MEMORY=67108864 \ -s ASSERTIONS=1 \ -s 'EXPORTED_RUNTIME_METHODS=[\"writeArrayToMemory\"]' \ ") unset(SDL2_INCLUDE_DIRS) unset(SDL2_LIBRARIES) endif() 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) message(STATUS "SDL2_INCLUDE_DIRS = ${SDL2_INCLUDE_DIRS}") message(STATUS "SDL2_LIBRARIES = ${SDL2_LIBRARIES}") endif() endif() # third-party add_subdirectory(third-party) # helper libraries add_library(ggwave-common ggwave-common.cpp ) target_link_libraries(ggwave-common PRIVATE ${CMAKE_DL_LIBS} ) if (MINGW) target_link_libraries(ggwave-common PUBLIC stdc++ ) endif() if (GGWAVE_SUPPORT_SDL2) # ggwave-common-sdl2 add_library(ggwave-common-sdl2 ggwave-common-sdl2.cpp ) target_include_directories(ggwave-common-sdl2 PUBLIC ${SDL2_INCLUDE_DIRS} ) target_link_libraries(ggwave-common-sdl2 PRIVATE ggwave imgui-sdl2 ${SDL2_LIBRARIES} ) endif() # examples if (EMSCRIPTEN) add_subdirectory(ggwave-js) add_subdirectory(buttons) else() add_subdirectory(ggwave-to-file) add_subdirectory(ggwave-from-file) add_subdirectory(arduino-rx) add_subdirectory(arduino-tx) add_subdirectory(arduino-tx-obsolete) add_subdirectory(esp32-rx) add_subdirectory(rp2040-rx) endif() if (GGWAVE_SUPPORT_SDL2) if (UNIX AND NOT APPLE) add_subdirectory(r2t2) endif() add_subdirectory(arduino-rx-web) if (EMSCRIPTEN) # emscripten sdl2 examples add_subdirectory(ggwave-wasm) else() # non-emscripten sdl2 examples add_subdirectory(ggwave-rx) add_subdirectory(ggwave-cli) endif() add_subdirectory(waver) add_subdirectory(spectrogram) endif() install(TARGETS ggwave-common LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static ) if (GGWAVE_SUPPORT_SDL2) install(TARGETS ggwave-common-sdl2 LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static ) endif() ================================================ FILE: examples/arduino-rx/.gitignore ================================================ ggwave ggwave.cpp fft.h resampler.h resampler.cpp reed-solomon ================================================ FILE: examples/arduino-rx/CMakeLists.txt ================================================ # # arduino-rx #configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.h COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/fft.h ${CMAKE_CURRENT_SOURCE_DIR}/fft.h COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE COPYONLY) ================================================ FILE: examples/arduino-rx/README.md ================================================ # arduino-rx This is a sample project for receiving and transmitting audio data using [Arduino RP2040](https://docs.arduino.cc/hardware/nano-rp2040-connect) microcontroller. The development board has a built-in microphone which makes this device very suitable for data-over-sound projects. ## Setup - Arduino RP2040 Connect - OLED SSD1306 - Generic speaker ## Pinout for Arduino Nano RP2040 Connect ### I2C Display (optional) | MCU | Display | | ------------- | --------- | | GND | GND | | 3.3V | VCC / VDD | | D18 / GPIO 12 | SDA | | D19 / GPIO 13 | SCL | ### Peripherals (optional) | MCU | Periph. | | ------------- | ------- | | D5 / GPIO 17 | Button | | D10 / GPIO 5 | Speaker | ![Sketch-Breadboard](fritzing-sketch_bb.png) ![Sketch-Photo](https://user-images.githubusercontent.com/1991296/177850326-e5fefde3-93ee-4cf9-8fa5-861eef9565f7.JPEG) ## Demo https://user-images.githubusercontent.com/1991296/177210657-3c7421ce-5c12-4caf-a86c-251191eefe50.mp4 [Watch high quality on Youtube](https://youtu.be/HiDpGvnxPLs) ================================================ FILE: examples/arduino-rx/arduino-rx.ino ================================================ // arduino-rx // // Sample sketch for receiving data using "ggwave" // // Tested with: // - Arduino Nano RP2040 Connect // // The Arduino Nano RP2040 Connect board has a built-in microphone which is used // in this example to capture audio data. // // The sketch optionally supports displaying the received "ggwave" data on an OLED display. // Use the DISPLAY_OUTPUT macro to enable or disable this functionality. // // If you don't have a display, you can simply observe the decoded data in the serial monitor. // // If you want to perform a quick test, you can use the free "Waver" application: // - Web: https://waver.ggerganov.com // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 // // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of // bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is // listed as Rx in the current sketch. // // Demo: https://youtu.be/HiDpGvnxPLs // // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx // // ## Pinout for Arduino Nano RP2040 Connect // // ### I2C Display (optional) // // | MCU | Display | // | ------------- | --------- | // | GND | GND | // | 3.3V | VCC / VDD | // | D18 / GPIO 12 | SDA | // | D19 / GPIO 13 | SCL | // // ### Peripherals (optional) // // | MCU | Periph. | // | ------------- | ------- | // | D5 / GPIO 17 | Button | // | D10 / GPIO 5 | Speaker | // // Uncoment this line to enable SSD1306 display output //#define DISPLAY_OUTPUT 1 // Uncoment this line to enable long-range transmission // The used protocols are slower and use more memory to decode, but are much more robust //#define LONG_RANGE 1 #include #include // Pin configuration const int kPinLED0 = 2; const int kPinButton0 = 5; const int kPinSpeaker = 10; // Audio capture configuration using TSample = int16_t; const size_t kSampleSize_bytes = sizeof(TSample); const char channels = 1; const int sampleRate = 6000; const int samplesPerFrame = 128; // Audio capture ring-buffer const int qpow = 9; const int qmax = 1 << qpow; volatile int qhead = 0; volatile int qtail = 0; volatile int qsize = 0; TSample sampleBuffer[qmax]; // Error handling volatile int err = 0; // Global GGwave instance GGWave ggwave; #ifdef DISPLAY_OUTPUT #include #include #include #include #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 32 // OLED display height, in pixels // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) // The pins for I2C are defined by the Wire-library. // On an arduino UNO: A4(SDA), A5(SCL) // On an arduino MEGA 2560: 20(SDA), 21(SCL) // On an arduino LEONARDO: 2(SDA), 3(SCL), ... #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #endif // Helper function to output the generated GGWave waveform via a buzzer void send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) { Serial.print(F("Sending text: ")); Serial.println(text); ggwave.init(text, protocolId); ggwave.encode(); const auto & protocol = GGWave::Protocols::tx()[protocolId]; const auto tones = ggwave.txTones(); const auto duration_ms = protocol.txDuration_ms(ggwave.samplesPerFrame(), ggwave.sampleRateOut()); for (auto & curTone : tones) { const auto freq_hz = (protocol.freqStart + curTone)*ggwave.hzPerSample(); tone(pin, freq_hz); delay(duration_ms); } noTone(pin); digitalWrite(pin, LOW); } void setup() { Serial.begin(57600); while (!Serial); pinMode(kPinLED0, OUTPUT); pinMode(kPinSpeaker, OUTPUT); pinMode(kPinButton0, INPUT_PULLUP); digitalWrite(kPinLED0, LOW); #ifdef DISPLAY_OUTPUT { Serial.println(F("Initializing display...")); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } // Show initial display buffer contents on the screen -- // the library initializes this with an Adafruit splash screen. //display.display(); //delay(2000); // Pause for 2 seconds // Clear the buffer display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); // Draw white text display.setCursor(0, 0); // Start at top-left corner display.println(F("GGWave!")); display.setTextSize(1); display.println(F("")); display.println(F("Listening...")); display.display(); } #endif // Initialize "ggwave" { Serial.println(F("Trying to initialize the ggwave instance")); ggwave.setLogFile(nullptr); auto p = GGWave::getDefaultParameters(); // Adjust the "ggwave" parameters to your needs. // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. #ifdef LONG_RANGE // The "FAST" protocols require 2x more memory, so we reduce the payload length to compensate: p.payloadLength = 8; #else p.payloadLength = 16; #endif Serial.print(F("Using payload length: ")); Serial.println(p.payloadLength); p.sampleRateInp = sampleRate; p.sampleRateOut = sampleRate; p.sampleRate = sampleRate; p.samplesPerFrame = samplesPerFrame; p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; // Protocols to use for TX // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::tx().disableAll(); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); //GGWave::Protocols::tx()[GGWAVE_PROTOCOL_MT_FASTEST].freqStart += 48; // Protocols to use for RX // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::rx().disableAll(); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); #ifdef LONG_RANGE GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); #endif GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); #ifdef LONG_RANGE GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); #endif GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); // Print the memory required for the "ggwave" instance: ggwave.prepare(p, false); Serial.print(F("Required memory by the ggwave instance: ")); Serial.print(ggwave.heapSize()); Serial.println(F(" bytes")); // Initialize the "ggwave" instance: ggwave.prepare(p, true); Serial.print(F("Instance initialized successfully! Memory used: ")); } // Start capturing audio { // Configure the data receive callback PDM.onReceive(onPDMdata); // Optionally set the gain // Defaults to 20 on the BLE Sense and -10 on the Portenta Vision Shields //PDM.setGain(30); // Initialize PDM: if (!PDM.begin(channels, sampleRate)) { Serial.println(F("Failed to start PDM!")); while (1); } } } void loop() { int nr = 0; int niter = 0; int but0Prev = HIGH; GGWave::TxRxData result; char resultLast[17]; int tLastReceive = -10000; // Main loop .. while (true) { while (qsize >= samplesPerFrame) { // Use this with the serial plotter to observe real-time audio signal //for (int i = 0; i < samplesPerFrame; i++) { // Serial.println(sampleBuffer[qhead + i]); //} // We have enough captured samples - try to decode any "ggwave" data: auto tStart = millis(); ggwave.decode(sampleBuffer + qhead, samplesPerFrame*kSampleSize_bytes); qsize -= samplesPerFrame; qhead += samplesPerFrame; if (qhead >= qmax) { qhead = 0; } auto tEnd = millis(); if (++niter % 10 == 0) { // Print the time it took the last decode() call to complete. // The time should be smaller than samplesPerFrame/sampleRate seconds // For example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms Serial.println(tEnd - tStart); if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { Serial.println(F("Warning: decode() took too long to execute!")); } } // Check if we have successfully decoded any data: nr = ggwave.rxTakeData(result); if (nr > 0) { Serial.println(tEnd - tStart); Serial.print(F("Received data with length ")); Serial.print(nr); // should be equal to p.payloadLength Serial.println(F(" bytes:")); Serial.println((char *) result.data()); strcpy(resultLast, (char *) result.data()); tLastReceive = tEnd; } #ifdef DISPLAY_OUTPUT const auto t = millis(); static GGWave::Spectrum rxSpectrum; if (ggwave.rxTakeSpectrum(rxSpectrum) && t > 2000) { const bool isNew = t - tLastReceive < 2000; if (isNew) { digitalWrite(kPinLED0, HIGH); } else { digitalWrite(kPinLED0, LOW); } display.clearDisplay(); display.setTextSize(isNew ? 2 : 1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println(resultLast); const int nBin0 = 16; const int nBins = 64; const int dX = SCREEN_WIDTH/nBins; float smax = 0.0f; for (int x = 0; x < nBins; x++) { smax = std::max(smax, rxSpectrum[nBin0 + x]); } smax = smax == 0.0f ? 1.0f : 1.0f/smax; const float h = isNew ? 0.25f: 0.75f; for (int x = 0; x < nBins; x++) { const int x0 = x*dX; const int x1 = x0 + dX; const int y = (int) (h*SCREEN_HEIGHT*(rxSpectrum[nBin0 + x]*smax)); display.fillRect(x0, SCREEN_HEIGHT - y, dX, y, SSD1306_WHITE); } display.display(); } #endif } // This should never happen. // If it does - there is something wrong with the audio capturing callback. // For example, the microcontroller is not able to process the captured data in real-time. if (err > 0) { Serial.println(F("ERRROR")); Serial.println(err); err = 0; } // If the button has been presse - transmit the last received data: int but0 = digitalRead(kPinButton0); if (but0 == LOW && but0Prev == HIGH) { Serial.println(F("Button 0 pressed - transmitting ..")); { // pause microphone capture while transmitting PDM.end(); delay(500); send_text(ggwave, kPinSpeaker, resultLast, GGWAVE_PROTOCOL_MT_FASTEST); // resume microphone capture if (!PDM.begin(channels, sampleRate)) { Serial.println(F("Failed to start PDM!")); while (1); } } Serial.println(F("Done")); but0Prev = LOW; } else if (but0 == HIGH && but0Prev == LOW) { but0Prev = HIGH; } } } /** Callback function to process the data from the PDM microphone. NOTE: This callback is executed as part of an ISR. Therefore using `Serial` to print messages inside this function isn't supported. * */ void onPDMdata() { const int bytesAvailable = PDM.available(); const int nSamples = bytesAvailable/kSampleSize_bytes; if (qsize + nSamples > qmax) { // If you hit this error, try to increase qmax err += 10; qhead = 0; qtail = 0; qsize = 0; } PDM.read(sampleBuffer + qtail, bytesAvailable); qtail += nSamples; qsize += nSamples; if (qtail > qmax) { // If you hit this error, qmax is probably not a multiple of the recorded samples err += 1; } if (qtail >= qmax) { qtail -= qmax; } } ================================================ FILE: examples/arduino-rx-web/CMakeLists.txt ================================================ # # arduino-rx-web set(TARGET arduino-rx-web) if (NOT EMSCRIPTEN) add_executable(${TARGET} arduino-rx-web.cpp ) target_include_directories(${TARGET} PRIVATE .. ${SDL2_INCLUDE_DIRS} ) target_link_libraries(${TARGET} PRIVATE ggwave-common ggwave ${SDL2_LIBRARIES} ) else() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/ggwave.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ggwave.js COPYONLY) endif() ================================================ FILE: examples/arduino-rx-web/README.md ================================================ # arduino-rx-web A simple web page to receive the data from the [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) example https://arduino-rx.ggerganov.com ## Demo https://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4 [Watch high quality on Youtube](https://youtu.be/qbzKo3zbQcI) ================================================ FILE: examples/arduino-rx-web/arduino-rx-web.cpp ================================================ #include "ggwave-common.h" #include "ggwave/ggwave.h" #ifdef __EMSCRIPTEN__ #include "build_timestamp.h" #include #else #define EMSCRIPTEN_KEEPALIVE #endif #include #include #include #include #include #include #include namespace { std::string g_defaultCaptureDeviceName = ""; SDL_AudioDeviceID g_devIdInp = 0; SDL_AudioDeviceID g_devIdOut = 0; SDL_AudioSpec g_obtainedSpecInp; SDL_AudioSpec g_obtainedSpecOut; GGWave *g_ggWave = nullptr; } static std::function g_doInit; static std::function g_setWindowSize; static std::function g_mainUpdate; void mainUpdate(void *) { g_mainUpdate(); } // JS interface extern "C" { EMSCRIPTEN_KEEPALIVE int sendData(int textLength, const char * text, int protocolId, int volume) { g_ggWave->init(textLength, text, GGWave::TxProtocolId(protocolId), volume); return 0; } EMSCRIPTEN_KEEPALIVE int getText(char * text) { std::copy(g_ggWave->rxData().begin(), g_ggWave->rxData().end(), text); return 0; } EMSCRIPTEN_KEEPALIVE float sampleRate() { return g_ggWave->sampleRateInp(); } EMSCRIPTEN_KEEPALIVE int framesToRecord() { return g_ggWave->rxFramesToRecord(); } EMSCRIPTEN_KEEPALIVE int framesLeftToRecord() { return g_ggWave->rxFramesLeftToRecord(); } EMSCRIPTEN_KEEPALIVE int framesToAnalyze() { return g_ggWave->rxFramesToAnalyze(); } EMSCRIPTEN_KEEPALIVE int framesLeftToAnalyze() { return g_ggWave->rxFramesLeftToAnalyze(); } EMSCRIPTEN_KEEPALIVE int hasDeviceOutput() { return g_devIdOut; } EMSCRIPTEN_KEEPALIVE int hasDeviceCapture() { return g_devIdInp; } EMSCRIPTEN_KEEPALIVE int doInit() { return g_doInit(); } } void GGWave_setDefaultCaptureDeviceName(std::string name) { g_defaultCaptureDeviceName = std::move(name); } bool GGWave_init( const int playbackId, const int captureId, const int payloadLength, const float sampleRateOffset, const bool useDSS) { if (g_devIdInp && g_devIdOut) { return false; } if (g_devIdInp == 0 && g_devIdOut == 0) { 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)); } } } bool reinit = false; if (g_devIdOut == 0) { printf("Initializing playback ...\n"); SDL_AudioSpec playbackSpec; SDL_zero(playbackSpec); playbackSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset; playbackSpec.format = AUDIO_S16SYS; playbackSpec.channels = 1; playbackSpec.samples = 16*1024; playbackSpec.callback = NULL; SDL_zero(g_obtainedSpecOut); if (playbackId >= 0) { printf("Attempt to open playback device %d : '%s' ...\n", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE)); g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } else { printf("Attempt to open default playback device ...\n"); g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } if (!g_devIdOut) { printf("Couldn't open an audio device for playback: %s!\n", SDL_GetError()); g_devIdOut = 0; } else { printf("Obtained spec for output device (SDL Id = %d):\n", g_devIdOut); printf(" - Sample rate: %d (required: %d)\n", g_obtainedSpecOut.freq, playbackSpec.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecOut.format, playbackSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecOut.channels, playbackSpec.channels); printf(" - Samples per frame: %d (required: %d)\n", g_obtainedSpecOut.samples, playbackSpec.samples); if (g_obtainedSpecOut.format != playbackSpec.format || g_obtainedSpecOut.channels != playbackSpec.channels || g_obtainedSpecOut.samples != playbackSpec.samples) { g_devIdOut = 0; SDL_CloseAudio(); fprintf(stderr, "Failed to initialize playback SDL_OpenAudio!"); return false; } reinit = true; } } if (g_devIdInp == 0) { SDL_AudioSpec captureSpec; captureSpec = g_obtainedSpecOut; captureSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset; captureSpec.format = AUDIO_F32SYS; captureSpec.samples = 1024; SDL_zero(g_obtainedSpecInp); if (captureId >= 0) { printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE)); g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } else { printf("Attempt to open default capture device ...\n"); g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } if (!g_devIdInp) { printf("Couldn't open an audio device for capture: %s!\n", SDL_GetError()); g_devIdInp = 0; } else { printf("Obtained spec for input device (SDL Id = %d):\n", g_devIdInp); printf(" - Sample rate: %d\n", g_obtainedSpecInp.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecInp.format, captureSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecInp.channels, captureSpec.channels); printf(" - Samples per frame: %d\n", g_obtainedSpecInp.samples); reinit = true; } } GGWave::SampleFormat sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED; GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED; switch (g_obtainedSpecInp.format) { case AUDIO_U8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U8; break; case AUDIO_S8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I8; break; case AUDIO_U16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U16; break; case AUDIO_S16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break; case AUDIO_S32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break; case AUDIO_F32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break; } switch (g_obtainedSpecOut.format) { case AUDIO_U8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; break; case AUDIO_S8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8; break; case AUDIO_U16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break; case AUDIO_S16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break; case AUDIO_S32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break; case AUDIO_F32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break; break; } if (reinit) { if (g_ggWave) delete g_ggWave; GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX; if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS; g_ggWave = new GGWave({ payloadLength, (float) g_obtainedSpecInp.freq, (float) g_obtainedSpecOut.freq, GGWave::kDefaultSampleRate, 512, GGWave::kDefaultSoundMarkerThreshold, sampleFormatInp, sampleFormatOut, mode, }); } return true; } GGWave *& GGWave_instance() { return g_ggWave; } bool GGWave_mainLoop() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } if (g_ggWave->txHasData() == 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->samplesPerFrame()*g_ggWave->sampleSizeOut()) { SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE); const int nHave = (int) SDL_GetQueuedAudioSize(g_devIdInp); const int nNeed = g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeInp(); if (::getTime_ms(tLastNoData, tNow) > 500.0f && nHave >= nNeed) { static std::vector dataInp(nNeed); SDL_DequeueAudio(g_devIdInp, dataInp.data(), nNeed); if (g_ggWave->decode(dataInp.data(), dataInp.size()) == false) { fprintf(stderr, "Warning: failed to decode input data!\n"); } else { GGWave::TxRxData rxData; int n = g_ggWave->rxTakeData(rxData); if (n > 0) { std::time_t timestamp = std::time(nullptr); std::string tstr = std::asctime(std::localtime(×tamp)); tstr.back() = 0; printf("[%s] Received: '%s'\n", tstr.c_str(), rxData.data()); } } if (nHave > 32*nNeed) { fprintf(stderr, "Warning: slow processing, clearing queued audio buffer of %d bytes ...", SDL_GetQueuedAudioSize(g_devIdInp)); SDL_ClearQueuedAudio(g_devIdInp); } } else { SDL_ClearQueuedAudio(g_devIdInp); } } else { tLastNoData = tNow; } } else { SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE); SDL_PauseAudioDevice(g_devIdInp, SDL_TRUE); const auto nBytes = g_ggWave->encode(); SDL_QueueAudio(g_devIdOut, g_ggWave->txWaveform(), nBytes); } return true; } bool GGWave_deinit() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } delete g_ggWave; g_ggWave = nullptr; SDL_PauseAudioDevice(g_devIdInp, 1); SDL_CloseAudioDevice(g_devIdInp); SDL_PauseAudioDevice(g_devIdOut, 1); SDL_CloseAudioDevice(g_devIdOut); g_devIdInp = 0; g_devIdOut = 0; return true; } int main(int argc, char** argv) { #ifdef __EMSCRIPTEN__ printf("Build time: %s\n", BUILD_TIMESTAMP); printf("Press the Init button to start\n"); if (argv[1]) { GGWave_setDefaultCaptureDeviceName(argv[1]); } #else printf("Usage: %s [-cN] [-lN]\n", argv[0]); printf(" -cN - select capture device N\n"); printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); printf("\n"); #endif GGWave::Protocols::rx() = { { { "[R2T2] Normal", 64, 9, 1, 2, true, }, { "[R2T2] Fast", 64, 6, 1, 2, true, }, { "[R2T2] Fastest", 64, 3, 1, 2, true, }, { "[R2T2] Low Normal", 16, 9, 1, 2, true, }, { "[R2T2] Low Fast", 16, 6, 1, 2, true, }, { "[R2T2] Low Fastest", 16, 3, 1, 2, true, }, } }; const auto argm = parseCmdArguments(argc, argv); const int captureId = argm.count("c") == 0 ? 0 : std::stoi(argm.at("c")); const int payloadLength = argm.count("l") == 0 ? 16 : std::stoi(argm.at("l")); bool isInitialized = false; g_doInit = [&]() { if (GGWave_init(0, captureId, payloadLength, 0, true) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); return false; } isInitialized = true; printf("Listening for payload with length = %d bytes ..\n", payloadLength); return true; }; g_mainUpdate = [&]() { if (isInitialized == false) { return true; } GGWave_mainLoop(); return true; }; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true); #else if (g_doInit() == false) { printf("Error: failed to initialize audio\n"); return -2; } while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (g_mainUpdate() == false) break; } GGWave_deinit(); // Cleanup SDL_CloseAudio(); SDL_Quit(); #endif return 0; } ================================================ FILE: examples/arduino-rx-web/index-tmpl.html ================================================ ggwave : arduino-rx
================================================ FILE: examples/arduino-tx/.gitignore ================================================ ggwave ggwave.cpp fft.h resampler.h resampler.cpp reed-solomon ================================================ FILE: examples/arduino-tx/CMakeLists.txt ================================================ # # arduino-tx #configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.h COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/fft.h ${CMAKE_CURRENT_SOURCE_DIR}/fft.h COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE COPYONLY) ================================================ FILE: examples/arduino-tx/README.md ================================================ # arduino-tx This is a sample project for transmitting data via sound using [Arduino Uno](https://store.arduino.cc/products/arduino-uno-rev3) microcontroller. ## Setup - Arduino Uno R3 - Generic buzzer ![Sketch-Breadboard](fritzing-sketch_bb.png) ![IMG_0257](https://user-images.githubusercontent.com/1991296/173232151-c01d01e9-8b36-4705-83a9-fb52b58382c7.jpg) ## Demo https://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4 [Watch high quality on Youtube](https://youtu.be/qbzKo3zbQcI) ================================================ FILE: examples/arduino-tx/arduino-tx.ino ================================================ // arduino-tx // // Sample sketch for transmitting data using "ggwave" // // Tested with: // - Arduino Uno R3 // - Arduino Nano RP2040 Connect // - NodeMCU-ESP32-S // - NodeMCU-ESP8266EX // // If you want to perform a quick test, you can use the free "Waver" application: // - Web: https://waver.ggerganov.com // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 // // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of // bytes to be equal to "payloadLength" used in the sketch. // // Demo: https://youtu.be/qbzKo3zbQcI // // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx // #include // Pin configuration const int kPinLed0 = 13; const int kPinSpeaker = 10; const int kPinButton0 = 2; const int kPinButton1 = 4; const int samplesPerFrame = 128; const int sampleRate = 6000; // Global GGwave instance GGWave ggwave; char txt[64]; #define P(str) (strcpy_P(txt, PSTR(str)), txt) // Helper function to output the generated GGWave waveform via a buzzer void send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) { Serial.print(F("Sending text: ")); Serial.println(text); ggwave.init(text, protocolId); ggwave.encode(); const auto & protocol = GGWave::Protocols::tx()[protocolId]; const auto tones = ggwave.txTones(); const auto duration_ms = protocol.txDuration_ms(ggwave.samplesPerFrame(), ggwave.sampleRateOut()); for (auto & curTone : tones) { const auto freq_hz = (protocol.freqStart + curTone)*ggwave.hzPerSample(); tone(pin, freq_hz); delay(duration_ms); } noTone(pin); digitalWrite(pin, LOW); } void setup() { Serial.begin(57600); while (!Serial); pinMode(kPinLed0, OUTPUT); pinMode(kPinSpeaker, OUTPUT); pinMode(kPinButton0, INPUT); pinMode(kPinButton1, INPUT); // Initialize "ggwave" { Serial.println(F("Trying to initialize the ggwave instance")); ggwave.setLogFile(nullptr); auto p = GGWave::getDefaultParameters(); // Adjust the "ggwave" parameters to your needs. // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. p.payloadLength = 16; p.sampleRateInp = sampleRate; p.sampleRateOut = sampleRate; p.sampleRate = sampleRate; p.samplesPerFrame = samplesPerFrame; p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; p.operatingMode = GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES | GGWAVE_OPERATING_MODE_USE_DSS; // Protocols to use for TX GGWave::Protocols::tx().only(GGWAVE_PROTOCOL_MT_FASTEST); // Sometimes, the speaker might not be able to produce frequencies in the Mono-tone range of 1-2 kHz. // In such cases, you can shift the base frequency up by changing the "freqStart" parameter of the protocol. // Make sure that in the receiver (for example, the "Waver" app) the base frequency is shifted by the same amount. // Here we shift the frequency by +48 bins. Each bin is equal to 48000/1024 = 46.875 Hz. // So the base frequency is shifted by +2250 Hz. //GGWave::Protocols::tx()[GGWAVE_PROTOCOL_MT_FASTEST].freqStart += 48; // Initialize the ggwave instance and print the memory usage ggwave.prepare(p); Serial.println(ggwave.heapSize()); Serial.println(F("Instance initialized successfully!")); } } // Button state int pressed = 0; bool isDown = false; void loop() { delay(1000); digitalWrite(kPinLed0, HIGH); send_text(ggwave, kPinSpeaker, P("Hello!"), GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(2000); digitalWrite(kPinLed0, HIGH); send_text(ggwave, kPinSpeaker, P("This is a"), GGWAVE_PROTOCOL_MT_FASTEST); send_text(ggwave, kPinSpeaker, P("ggwave demo"), GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(2000); digitalWrite(kPinLed0, HIGH); send_text(ggwave, kPinSpeaker, P("The arduino"), GGWAVE_PROTOCOL_MT_FASTEST); delay(200); send_text(ggwave, kPinSpeaker, P("transmits data"), GGWAVE_PROTOCOL_MT_FASTEST); delay(200); send_text(ggwave, kPinSpeaker, P("using sound"), GGWAVE_PROTOCOL_MT_FASTEST); delay(200); send_text(ggwave, kPinSpeaker, P("through a buzzer"), GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(1000); digitalWrite(kPinLed0, HIGH); send_text(ggwave, kPinSpeaker, P("The sound is"), GGWAVE_PROTOCOL_MT_FASTEST); delay(200); send_text(ggwave, kPinSpeaker, P("decoded in a"), GGWAVE_PROTOCOL_MT_FASTEST); delay(200); send_text(ggwave, kPinSpeaker, P("web page."), GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(1000); digitalWrite(kPinLed0, HIGH); send_text(ggwave, kPinSpeaker, P("Press the button!"), GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); Serial.println(F("Starting main loop")); while (true) { int but0 = digitalRead(kPinButton0); int but1 = digitalRead(kPinButton1); if (but1 == LOW && isDown == false) { delay(200); ++pressed; isDown = true; } else if (but1 == HIGH) { isDown = false; } if (but0 == LOW) { snprintf(txt, 16, "Pressed: %d", pressed); digitalWrite(kPinLed0, HIGH); send_text(ggwave, kPinSpeaker, txt, GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); pressed = 0; } } } ================================================ FILE: examples/arduino-tx-obsolete/.gitignore ================================================ ggwave ggwave.cpp fft.h resampler.h resampler.cpp reed-solomon ================================================ FILE: examples/arduino-tx-obsolete/CMakeLists.txt ================================================ ================================================ FILE: examples/arduino-tx-obsolete/arduino-tx.ino ================================================ // This example uses a custom ggwave imlpementation specifically for Arduino UNO. // Since the Arduino UNO has only 2KB of RAM, the ggwave library is not able to // to fit into the Arduino's memory (eventhough it is very close). // If your microcontroller has more than 2KB of RAM, you should check the other Tx // examples to see if you can use the original ggwave library. #include "ggwave.h" const int kPinLed0 = 13; const int kPinSpeaker = 10; const int kPinButton0 = 2; const int kPinButton1 = 4; void setup() { Serial.begin(57600); pinMode(kPinLed0, OUTPUT); pinMode(kPinSpeaker, OUTPUT); pinMode(kPinButton0, INPUT); pinMode(kPinButton1, INPUT); delay(3000); digitalWrite(kPinLed0, HIGH); GGWave::send_text(kPinSpeaker, "Hello!", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(2000); digitalWrite(kPinLed0, HIGH); GGWave::send_text(kPinSpeaker, "This is a", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); GGWave::send_text(kPinSpeaker, "ggwave demo", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(2000); digitalWrite(kPinLed0, HIGH); GGWave::send_text(kPinSpeaker, "The arduino", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); delay(200); GGWave::send_text(kPinSpeaker, "transmits data", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); delay(200); GGWave::send_text(kPinSpeaker, "using sound", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); delay(200); GGWave::send_text(kPinSpeaker, "through a buzzer", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(1000); digitalWrite(kPinLed0, HIGH); GGWave::send_text(kPinSpeaker, "The sound is", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); delay(200); GGWave::send_text(kPinSpeaker, "decoded in a", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); delay(200); GGWave::send_text(kPinSpeaker, "web page.", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); delay(1000); digitalWrite(kPinLed0, HIGH); GGWave::send_text(kPinSpeaker, "Press the button!", GGWave::GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); } char txt[16]; int pressed = 0; bool isDown = false; void loop() { int but0 = digitalRead(kPinButton0); int but1 = digitalRead(kPinButton1); if (but1 == LOW && isDown == false) { delay(200); ++pressed; isDown = true; } else if (but1 == HIGH) { isDown = false; } if (but0 == LOW) { snprintf(txt, 16, "Pressed: %d", pressed); digitalWrite(kPinLed0, HIGH); GGWave::send_text(kPinSpeaker, txt, GGWave::GGWAVE_PROTOCOL_MT_FASTEST); digitalWrite(kPinLed0, LOW); pressed = 0; } } ================================================ FILE: examples/arduino-tx-obsolete/ggwave.h ================================================ #pragma once // // The code in the RS namespace provides Reed-Solomon based error correction. // The code is taken from https://github.com/mersinvald/Reed-Solomon. // The LICENSE of the code is copied below: // // 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. // // The GGWave implementation is in the GGWave namespace at the bottom of this file. // #include 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 }; namespace gf { /* GF tables pre-calculated for 0x11d primitive polynomial */ const uint8_t exp[512] PROGMEM = { 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] PROGMEM = { 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 pgm_read_byte(exp + pgm_read_byte(log + x) + pgm_read_byte(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 pgm_read_byte(exp + (pgm_read_byte(log + x) + 255 - pgm_read_byte(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 = pgm_read_byte(log + x); i *= power; i %= 255; if(i < 0) i = i + 255; return pgm_read_byte(exp + i); } /* @brief Inversion in Galua Fields * @param x - number * @return inversion of x */ inline uint8_t inverse(uint8_t x){ return pgm_read_byte(exp + 255 - pgm_read_byte(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 */ #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 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 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); } } } }; } namespace GGWave { // Direct-sequence spread magic numbers // Used to xor the actual payload const uint8_t kDSSMagic[] = { 0x96, 0x9f, 0xb4, 0xaf, 0x1b, 0x91, 0xde, 0xc5, 0x45, 0x75, 0xe8, 0x2e, 0x0f, 0x32, 0x4a, 0x5f, 0xb4, 0x56, 0x95, 0xcb, 0x7f, 0x6a, 0x54, 0x6a, 0x48, 0xf2, 0x0b, 0x7b, 0xcd, 0xfb, 0x93, 0x6d, 0x3c, 0x77, 0x5e, 0xc3, 0x33, 0x47, 0xc0, 0xf1, 0x71, 0x32, 0x33, 0x27, 0x35, 0x68, 0x47, 0x1f, 0x4e, 0xac, 0x23, 0x42, 0x5f, 0x00, 0x37, 0xa4, 0x50, 0x6d, 0x48, 0x24, 0x91, 0x7c, 0xa1, 0x4e, }; uint8_t mymax(uint8_t a, uint8_t b) { return (a > b) ? a : b; } uint8_t getECCBytesForLength(uint8_t len) { return len < 4 ? 2 : mymax(4, 2*(len/5)); } const uint8_t kDataLength_bytes = 16; const uint8_t kECCLength_bytes = getECCBytesForLength(kDataLength_bytes); typedef enum { GGWAVE_PROTOCOL_MT_NORMAL, GGWAVE_PROTOCOL_MT_FAST, GGWAVE_PROTOCOL_MT_FASTEST, } TxProtocolId; struct Parameters { int sampleRate; uint8_t freqStart; uint8_t timePerFrame_ms; }; void send(uint8_t pin, const uint8_t * data, const Parameters & parameters) { const float dF = 48000.0/float(parameters.sampleRate); char buf[kDataLength_bytes]; char enc[kDataLength_bytes + kECCLength_bytes]; for (uint8_t i = 0; i < kDataLength_bytes; i++) { buf[i] = data[i] ^ kDSSMagic[i%sizeof(kDSSMagic)]; } RS::ReedSolomon rsLength(kDataLength_bytes, kECCLength_bytes); rsLength.Encode(buf, enc); float fcur = 0.0f; for (int i = 0; i < 2*(kDataLength_bytes + kECCLength_bytes); i++) { uint8_t cur = enc[i/2]; if (i%2 == 0) { cur = cur & 0x0F; } else { cur = cur >> 4; } fcur = float(parameters.freqStart)*dF + float(cur)*dF; tone(pin, fcur); delay(parameters.timePerFrame_ms); } noTone(pin); digitalWrite(pin, LOW); } void send(uint8_t pin, const uint8_t * data, TxProtocolId protocolId = GGWAVE_PROTOCOL_MT_FASTEST) { Parameters parameters = { 1024, 24, 64 }; switch (protocolId) { case GGWAVE_PROTOCOL_MT_NORMAL: parameters = { 1024, 24, 192 }; break; case GGWAVE_PROTOCOL_MT_FAST: parameters = { 1024, 24, 128 }; break; case GGWAVE_PROTOCOL_MT_FASTEST: parameters = { 1024, 24, 64 }; break; }; send(pin, data, parameters); } void send_text(uint8_t pin, const char * text, TxProtocolId protocolId = GGWAVE_PROTOCOL_MT_FASTEST) { char tx[kDataLength_bytes]; memset(tx, 0, sizeof(tx)); strncpy(tx, text, sizeof(tx)); send(pin, (uint8_t *) tx, protocolId); } } ================================================ FILE: examples/buttons/CMakeLists.txt ================================================ set(TARGET buttons) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/ggwave.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ggwave.js COPYONLY) ================================================ FILE: examples/buttons/README.md ================================================ # buttons Record and send commands via [Talking buttons](https://github.com/ggerganov/ggwave/discussions/27) Live demo: https://buttons.ggerganov.com ================================================ FILE: examples/buttons/index-tmpl.html ================================================ ggwave : buttons
Talking buttons tester:



If you have Fluent Pet talking buttons, enable this option during the recording:

Fluent Pet



| Build time: @GIT_DATE@ | Commit hash: @GIT_SHA1@ | Commit subject: @GIT_COMMIT_SUBJECT@ | Source Code |
================================================ FILE: examples/dr_wav.h ================================================ /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. dr_wav - v0.12.16 - 2020-12-02 David Reid - mackron@gmail.com GitHub: https://github.com/mackron/dr_libs */ /* RELEASE NOTES - VERSION 0.12 ============================ Version 0.12 includes breaking changes to custom chunk handling. Changes to Chunk Callback ------------------------- dr_wav supports the ability to fire a callback when a chunk is encounted (except for WAVE and FMT chunks). The callback has been updated to include both the container (RIFF or Wave64) and the FMT chunk which contains information about the format of the data in the wave file. Previously, there was no direct way to determine the container, and therefore no way to discriminate against the different IDs in the chunk header (RIFF and Wave64 containers encode chunk ID's differently). The `container` parameter can be used to know which ID to use. Sometimes it can be useful to know the data format at the time the chunk callback is fired. A pointer to a `drwav_fmt` object is now passed into the chunk callback which will give you information about the data format. To determine the sample format, use `drwav_fmt_get_format()`. This will return one of the `DR_WAVE_FORMAT_*` tokens. */ /* Introduction ============ This is a single file library. To use it, do something like the following in one .c file. ```c #define DR_WAV_IMPLEMENTATION #include "dr_wav.h" ``` You can then #include this file in other parts of the program as you would with any other header file. Do something like the following to read audio data: ```c drwav wav; if (!drwav_init_file(&wav, "my_song.wav", NULL)) { // Error opening WAV file. } drwav_int32* pDecodedInterleavedPCMFrames = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32)); size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); ... drwav_uninit(&wav); ``` If you just want to quickly open and read the audio data in a single operation you can do something like this: ```c unsigned int channels; unsigned int sampleRate; drwav_uint64 totalPCMFrameCount; float* pSampleData = drwav_open_file_and_read_pcm_frames_f32("my_song.wav", &channels, &sampleRate, &totalPCMFrameCount, NULL); if (pSampleData == NULL) { // Error opening and reading WAV file. } ... drwav_free(pSampleData); ``` The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in this case), but you can still output the audio data in its internal format (see notes below for supported formats): ```c size_t framesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames); ``` You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for a particular data format: ```c size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); ``` dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at `drwav_init_write()`, `drwav_init_file_write()`, etc. Use `drwav_write_pcm_frames()` to write samples, or `drwav_write_raw()` to write raw data in the "data" chunk. ```c drwav_data_format format; format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. format.channels = 2; format.sampleRate = 44100; format.bitsPerSample = 16; drwav_init_file_write(&wav, "data/recording.wav", &format, NULL); ... drwav_uint64 framesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples); ``` dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work without any manual intervention. Build Options ============= #define these options before including this file. #define DR_WAV_NO_CONVERSION_API Disables conversion APIs such as `drwav_read_pcm_frames_f32()` and `drwav_s16_to_f32()`. #define DR_WAV_NO_STDIO Disables APIs that initialize a decoder from a file such as `drwav_init_file()`, `drwav_init_file_write()`, etc. Notes ===== - Samples are always interleaved. - The default read function does not do any data conversion. Use `drwav_read_pcm_frames_f32()`, `drwav_read_pcm_frames_s32()` and `drwav_read_pcm_frames_s16()` to read and convert audio data to 32-bit floating point, signed 32-bit integer and signed 16-bit integer samples respectively. Tested and supported internal formats include the following: - Unsigned 8-bit PCM - Signed 12-bit PCM - Signed 16-bit PCM - Signed 24-bit PCM - Signed 32-bit PCM - IEEE 32-bit floating point - IEEE 64-bit floating point - A-law and u-law - Microsoft ADPCM - IMA ADPCM (DVI, format code 0x11) - dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. */ #ifndef dr_wav_h #define dr_wav_h #ifdef __cplusplus extern "C" { #endif #define DRWAV_STRINGIFY(x) #x #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 #define DRWAV_VERSION_REVISION 16 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include /* For size_t. */ /* Sized types. */ typedef signed char drwav_int8; typedef unsigned char drwav_uint8; typedef signed short drwav_int16; typedef unsigned short drwav_uint16; typedef signed int drwav_int32; typedef unsigned int drwav_uint32; #if defined(_MSC_VER) typedef signed __int64 drwav_int64; typedef unsigned __int64 drwav_uint64; #else #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) #pragma GCC diagnostic ignored "-Wc++11-long-long" #endif #endif typedef signed long long drwav_int64; typedef unsigned long long drwav_uint64; #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) typedef drwav_uint64 drwav_uintptr; #else typedef drwav_uint32 drwav_uintptr; #endif typedef drwav_uint8 drwav_bool8; typedef drwav_uint32 drwav_bool32; #define DRWAV_TRUE 1 #define DRWAV_FALSE 0 #if !defined(DRWAV_API) #if defined(DRWAV_DLL) #if defined(_WIN32) #define DRWAV_DLL_IMPORT __declspec(dllimport) #define DRWAV_DLL_EXPORT __declspec(dllexport) #define DRWAV_DLL_PRIVATE static #else #if defined(__GNUC__) && __GNUC__ >= 4 #define DRWAV_DLL_IMPORT __attribute__((visibility("default"))) #define DRWAV_DLL_EXPORT __attribute__((visibility("default"))) #define DRWAV_DLL_PRIVATE __attribute__((visibility("hidden"))) #else #define DRWAV_DLL_IMPORT #define DRWAV_DLL_EXPORT #define DRWAV_DLL_PRIVATE static #endif #endif #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) #define DRWAV_API DRWAV_DLL_EXPORT #else #define DRWAV_API DRWAV_DLL_IMPORT #endif #define DRWAV_PRIVATE DRWAV_DLL_PRIVATE #else #define DRWAV_API extern #define DRWAV_PRIVATE static #endif #endif typedef drwav_int32 drwav_result; #define DRWAV_SUCCESS 0 #define DRWAV_ERROR -1 /* A generic error. */ #define DRWAV_INVALID_ARGS -2 #define DRWAV_INVALID_OPERATION -3 #define DRWAV_OUT_OF_MEMORY -4 #define DRWAV_OUT_OF_RANGE -5 #define DRWAV_ACCESS_DENIED -6 #define DRWAV_DOES_NOT_EXIST -7 #define DRWAV_ALREADY_EXISTS -8 #define DRWAV_TOO_MANY_OPEN_FILES -9 #define DRWAV_INVALID_FILE -10 #define DRWAV_TOO_BIG -11 #define DRWAV_PATH_TOO_LONG -12 #define DRWAV_NAME_TOO_LONG -13 #define DRWAV_NOT_DIRECTORY -14 #define DRWAV_IS_DIRECTORY -15 #define DRWAV_DIRECTORY_NOT_EMPTY -16 #define DRWAV_END_OF_FILE -17 #define DRWAV_NO_SPACE -18 #define DRWAV_BUSY -19 #define DRWAV_IO_ERROR -20 #define DRWAV_INTERRUPT -21 #define DRWAV_UNAVAILABLE -22 #define DRWAV_ALREADY_IN_USE -23 #define DRWAV_BAD_ADDRESS -24 #define DRWAV_BAD_SEEK -25 #define DRWAV_BAD_PIPE -26 #define DRWAV_DEADLOCK -27 #define DRWAV_TOO_MANY_LINKS -28 #define DRWAV_NOT_IMPLEMENTED -29 #define DRWAV_NO_MESSAGE -30 #define DRWAV_BAD_MESSAGE -31 #define DRWAV_NO_DATA_AVAILABLE -32 #define DRWAV_INVALID_DATA -33 #define DRWAV_TIMEOUT -34 #define DRWAV_NO_NETWORK -35 #define DRWAV_NOT_UNIQUE -36 #define DRWAV_NOT_SOCKET -37 #define DRWAV_NO_ADDRESS -38 #define DRWAV_BAD_PROTOCOL -39 #define DRWAV_PROTOCOL_UNAVAILABLE -40 #define DRWAV_PROTOCOL_NOT_SUPPORTED -41 #define DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED -42 #define DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED -43 #define DRWAV_SOCKET_NOT_SUPPORTED -44 #define DRWAV_CONNECTION_RESET -45 #define DRWAV_ALREADY_CONNECTED -46 #define DRWAV_NOT_CONNECTED -47 #define DRWAV_CONNECTION_REFUSED -48 #define DRWAV_NO_HOST -49 #define DRWAV_IN_PROGRESS -50 #define DRWAV_CANCELLED -51 #define DRWAV_MEMORY_ALREADY_MAPPED -52 #define DRWAV_AT_END -53 /* Common data formats. */ #define DR_WAVE_FORMAT_PCM 0x1 #define DR_WAVE_FORMAT_ADPCM 0x2 #define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 #define DR_WAVE_FORMAT_ALAW 0x6 #define DR_WAVE_FORMAT_MULAW 0x7 #define DR_WAVE_FORMAT_DVI_ADPCM 0x11 #define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE /* Constants. */ #ifndef DRWAV_MAX_SMPL_LOOPS #define DRWAV_MAX_SMPL_LOOPS 1 #endif /* Flags to pass into drwav_init_ex(), etc. */ #define DRWAV_SEQUENTIAL 0x00000001 DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision); DRWAV_API const char* drwav_version_string(void); typedef enum { drwav_seek_origin_start, drwav_seek_origin_current } drwav_seek_origin; typedef enum { drwav_container_riff, drwav_container_w64, drwav_container_rf64 } drwav_container; typedef struct { union { drwav_uint8 fourcc[4]; drwav_uint8 guid[16]; } id; /* The size in bytes of the chunk. */ drwav_uint64 sizeInBytes; /* RIFF = 2 byte alignment. W64 = 8 byte alignment. */ unsigned int paddingSize; } drwav_chunk_header; typedef struct { /* The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications that require support for data formats not natively supported by dr_wav. */ drwav_uint16 formatTag; /* The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. */ drwav_uint16 channels; /* The sample rate. Usually set to something like 44100. */ drwav_uint32 sampleRate; /* Average bytes per second. You probably don't need this, but it's left here for informational purposes. */ drwav_uint32 avgBytesPerSec; /* Block align. This is equal to the number of channels * bytes per sample. */ drwav_uint16 blockAlign; /* Bits per sample. */ drwav_uint16 bitsPerSample; /* The size of the extended data. Only used internally for validation, but left here for informational purposes. */ drwav_uint16 extendedSize; /* The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, is always rounded up to the nearest multiple of 8. This variable contains information about exactly how many bits are valid per sample. Mainly used for informational purposes. */ drwav_uint16 validBitsPerSample; /* The channel mask. Not used at the moment. */ drwav_uint32 channelMask; /* The sub-format, exactly as specified by the wave file. */ drwav_uint8 subFormat[16]; } drwav_fmt; DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT); /* Callback for when data is read. Return value is the number of bytes actually read. pUserData [in] The user data that was passed to drwav_init() and family. pBufferOut [out] The output buffer. bytesToRead [in] The number of bytes to read. Returns the number of bytes actually read. A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until either the entire bytesToRead is filled or you have reached the end of the stream. */ typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); /* Callback for when data is written. Returns value is the number of bytes actually written. pUserData [in] The user data that was passed to drwav_init_write() and family. pData [out] A pointer to the data to write. bytesToWrite [in] The number of bytes to write. Returns the number of bytes actually written. If the return value differs from bytesToWrite, it indicates an error. */ typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); /* Callback for when data needs to be seeked. pUserData [in] The user data that was passed to drwav_init() and family. offset [in] The number of bytes to move, relative to the origin. Will never be negative. origin [in] The origin of the seek - the current position or the start of the stream. Returns whether or not the seek was successful. Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be either drwav_seek_origin_start or drwav_seek_origin_current. */ typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); /* Callback for when drwav_init_ex() finds a chunk. pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex() and family. onRead [in] A pointer to the function to call when reading. onSeek [in] A pointer to the function to call when seeking. pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex() and family. pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. container [in] Whether or not the WAV file is a RIFF or Wave64 container. If you're unsure of the difference, assume RIFF. pFMT [in] A pointer to the object containing the contents of the "fmt" chunk. Returns the number of bytes read + seeked. To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must be the total number of bytes you have read _plus_ seeked. Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should use `id.fourcc`, otherwise you should use `id.guid`. The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the `DR_WAVE_FORMAT_*` identifiers. The read pointer will be sitting on the first byte after the chunk's header. You must not attempt to read beyond the boundary of the chunk. */ typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader, drwav_container container, const drwav_fmt* pFMT); typedef struct { void* pUserData; void* (* onMalloc)(size_t sz, void* pUserData); void* (* onRealloc)(void* p, size_t sz, void* pUserData); void (* onFree)(void* p, void* pUserData); } drwav_allocation_callbacks; /* Structure for internal use. Only used for loaders opened with drwav_init_memory(). */ typedef struct { const drwav_uint8* data; size_t dataSize; size_t currentReadPos; } drwav__memory_stream; /* Structure for internal use. Only used for writers opened with drwav_init_memory_write(). */ typedef struct { void** ppData; size_t* pDataSize; size_t dataSize; size_t dataCapacity; size_t currentWritePos; } drwav__memory_stream_write; typedef struct { drwav_container container; /* RIFF, W64. */ drwav_uint32 format; /* DR_WAVE_FORMAT_* */ drwav_uint32 channels; drwav_uint32 sampleRate; drwav_uint32 bitsPerSample; } drwav_data_format; /* See the following for details on the 'smpl' chunk: https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl */ typedef struct { drwav_uint32 cuePointId; drwav_uint32 type; drwav_uint32 start; drwav_uint32 end; drwav_uint32 fraction; drwav_uint32 playCount; } drwav_smpl_loop; typedef struct { drwav_uint32 manufacturer; drwav_uint32 product; drwav_uint32 samplePeriod; drwav_uint32 midiUnityNotes; drwav_uint32 midiPitchFraction; drwav_uint32 smpteFormat; drwav_uint32 smpteOffset; drwav_uint32 numSampleLoops; drwav_uint32 samplerData; drwav_smpl_loop loops[DRWAV_MAX_SMPL_LOOPS]; } drwav_smpl; typedef struct { /* A pointer to the function to call when more data is needed. */ drwav_read_proc onRead; /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */ drwav_write_proc onWrite; /* A pointer to the function to call when the wav file needs to be seeked. */ drwav_seek_proc onSeek; /* The user data to pass to callbacks. */ void* pUserData; /* Allocation callbacks. */ drwav_allocation_callbacks allocationCallbacks; /* Whether or not the WAV file is formatted as a standard RIFF file or W64. */ drwav_container container; /* Structure containing format information exactly as specified by the wav file. */ drwav_fmt fmt; /* The sample rate. Will be set to something like 44100. */ drwav_uint32 sampleRate; /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. */ drwav_uint16 channels; /* The bits per sample. Will be set to something like 16, 24, etc. */ drwav_uint16 bitsPerSample; /* Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). */ drwav_uint16 translatedFormatTag; /* The total number of PCM frames making up the audio data. */ drwav_uint64 totalPCMFrameCount; /* The size in bytes of the data chunk. */ drwav_uint64 dataChunkDataSize; /* The position in the stream of the first byte of the data chunk. This is used for seeking. */ drwav_uint64 dataChunkDataPos; /* The number of bytes remaining in the data chunk. */ drwav_uint64 bytesRemaining; /* Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. */ drwav_uint64 dataChunkDataSizeTargetWrite; /* Keeps track of whether or not the wav writer was initialized in sequential mode. */ drwav_bool32 isSequentialWrite; /* smpl chunk. */ drwav_smpl smpl; /* A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_init_memory(). */ drwav__memory_stream memoryStream; drwav__memory_stream_write memoryStreamWrite; /* Generic data for compressed formats. This data is shared across all block-compressed formats. */ struct { drwav_uint64 iCurrentPCMFrame; /* The index of the next PCM frame that will be read by drwav_read_*(). This is used with "totalPCMFrameCount" to ensure we don't read excess samples at the end of the last block. */ } compressed; /* Microsoft ADPCM specific data. */ struct { drwav_uint32 bytesRemainingInBlock; drwav_uint16 predictor[2]; drwav_int32 delta[2]; drwav_int32 cachedFrames[4]; /* Samples are stored in this cache during decoding. */ drwav_uint32 cachedFrameCount; drwav_int32 prevFrames[2][2]; /* The previous 2 samples for each channel (2 channels at most). */ } msadpcm; /* IMA ADPCM specific data. */ struct { drwav_uint32 bytesRemainingInBlock; drwav_int32 predictor[2]; drwav_int32 stepIndex[2]; drwav_int32 cachedFrames[16]; /* Samples are stored in this cache during decoding. */ drwav_uint32 cachedFrameCount; } ima; } drwav; /* Initializes a pre-allocated drwav object for reading. pWav [out] A pointer to the drwav object being initialized. onRead [in] The function to call when data needs to be read from the client. onSeek [in] The function to call when the read position of the client data needs to move. onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. flags [in, optional] A set of flags for controlling how things are loaded. Returns true if successful; false otherwise. Close the loader with drwav_uninit(). This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() to open the stream from a file or from a block of memory respectively. Possible values for flags: DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". The onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt after the function returns. See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() */ DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); /* Initializes a pre-allocated drwav object for writing. onWrite [in] The function to call when data needs to be written. onSeek [in] The function to call when the write position needs to move. pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. Returns true if successful; false otherwise. Close the writer with drwav_uninit(). This is the lowest level function for initializing a WAV file. You can also use drwav_init_file_write() and drwav_init_memory_write() to open the stream from a file or from a block of memory respectively. If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() */ DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks); /* Utility function to determine the target size of the entire data to be written (including all headers and chunks). Returns the target size in bytes. Useful if the application needs to know the size to allocate. Only writing to the RIFF chunk and one data chunk is currently supported. See also: drwav_init_write(), drwav_init_file_write(), drwav_init_memory_write() */ DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); /* Uninitializes the given drwav object. Use this only for objects initialized with drwav_init*() functions (drwav_init(), drwav_init_ex(), drwav_init_write(), drwav_init_write_sequential()). */ DRWAV_API drwav_result drwav_uninit(drwav* pWav); /* Reads raw audio data. This is the lowest level function for reading audio data. It simply reads the given number of bytes of the raw internal sample data. Consider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for reading sample data in a consistent format. pBufferOut can be NULL in which case a seek will be performed. Returns the number of bytes actually read. */ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); /* Reads up to the specified number of PCM frames from the WAV file. The output data will be in the file's internal format, converted to native-endian byte order. Use drwav_read_pcm_frames_s16/f32/s32() to read data in a specific format. If the return value is less than it means the end of the file has been reached or you have requested more PCM frames than can possibly fit in the output buffer. This function will only work when sample data is of a fixed size and uncompressed. If you are using a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32(). pBufferOut can be NULL in which case a seek will be performed. */ DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); /* Seeks to the given PCM frame. Returns true if successful; false otherwise. */ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); /* Writes raw audio data. Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. */ DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); /* Writes PCM frames. Returns the number of PCM frames written. Input samples need to be in native-endian byte order. On big-endian architectures the input data will be converted to little-endian. Use drwav_write_raw() to write raw audio data without performing any conversion. */ DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); /* Conversion Utilities */ #ifndef DR_WAV_NO_CONVERSION_API /* Reads a chunk of audio data and converts it to signed 16-bit PCM samples. pBufferOut can be NULL in which case a seek will be performed. Returns the number of PCM frames actually read. If the return value is less than it means the end of the file has been reached. */ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); /* Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. */ DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. */ DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. */ DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); /* Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. */ DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); /* Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. */ DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); /* Low-level function for converting A-law samples to signed 16-bit PCM samples. */ DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting u-law samples to signed 16-bit PCM samples. */ DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. pBufferOut can be NULL in which case a seek will be performed. Returns the number of PCM frames actually read. If the return value is less than it means the end of the file has been reached. */ DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); /* Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. */ DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. */ DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); /* Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. */ DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. */ DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); /* Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. */ DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); /* Low-level function for converting A-law samples to IEEE 32-bit floating point samples. */ DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting u-law samples to IEEE 32-bit floating point samples. */ DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Reads a chunk of audio data and converts it to signed 32-bit PCM samples. pBufferOut can be NULL in which case a seek will be performed. Returns the number of PCM frames actually read. If the return value is less than it means the end of the file has been reached. */ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); /* Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. */ DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. */ DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); /* Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. */ DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. */ DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); /* Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. */ DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); /* Low-level function for converting A-law samples to signed 32-bit PCM samples. */ DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); /* Low-level function for converting u-law samples to signed 32-bit PCM samples. */ DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); #endif /* DR_WAV_NO_CONVERSION_API */ /* High-Level Convenience Helpers */ #ifndef DR_WAV_NO_STDIO /* Helper for initializing a wave file for reading using stdio. This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav objects because the operating system may restrict the number of file handles an application can have open at any given time. */ DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); /* Helper for initializing a wave file for writing using stdio. This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav objects because the operating system may restrict the number of file handles an application can have open at any given time. */ DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); #endif /* DR_WAV_NO_STDIO */ /* Helper for initializing a loader from a pre-allocated memory buffer. This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for the lifetime of the drwav object. The buffer should contain the contents of the entire wave file, not just the sample data. */ DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks); /* Helper for initializing a writer which outputs data to a memory buffer. dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). The buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid until after drwav_uninit() has been called. */ DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks); #ifndef DR_WAV_NO_CONVERSION_API /* Opens and reads an entire wav file in a single operation. The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. */ DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); #ifndef DR_WAV_NO_STDIO /* Opens and decodes an entire wav file in a single operation. The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. */ DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); #endif /* Opens and decodes an entire wav file from a block of memory in a single operation. The return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer. */ DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks); #endif /* Frees data that was allocated internally by dr_wav. */ DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks); /* Converts bytes from a wav stream to a sized type of native endian. */ DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data); DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data); DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data); DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data); DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data); DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data); /* Compares a GUID for the purpose of checking the type of a Wave64 chunk. */ DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]); /* Compares a four-character-code for the purpose of checking the type of a RIFF chunk. */ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); #ifdef __cplusplus } #endif #endif /* dr_wav_h */ /************************************************************************************************************************************************************ ************************************************************************************************************************************************************ IMPLEMENTATION ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) #ifndef dr_wav_c #define dr_wav_c #include #include /* For memcpy(), memset() */ #include /* For INT_MAX */ #ifndef DR_WAV_NO_STDIO #include #include #endif /* Standard library stuff. */ #ifndef DRWAV_ASSERT #include #define DRWAV_ASSERT(expression) assert(expression) #endif #ifndef DRWAV_MALLOC #define DRWAV_MALLOC(sz) malloc((sz)) #endif #ifndef DRWAV_REALLOC #define DRWAV_REALLOC(p, sz) realloc((p), (sz)) #endif #ifndef DRWAV_FREE #define DRWAV_FREE(p) free((p)) #endif #ifndef DRWAV_COPY_MEMORY #define DRWAV_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) #endif #ifndef DRWAV_ZERO_MEMORY #define DRWAV_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) #endif #ifndef DRWAV_ZERO_OBJECT #define DRWAV_ZERO_OBJECT(p) DRWAV_ZERO_MEMORY((p), sizeof(*p)) #endif #define drwav_countof(x) (sizeof(x) / sizeof(x[0])) #define drwav_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) #define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) #define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) #define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) #define DRWAV_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ /* CPU architecture. */ #if defined(__x86_64__) || defined(_M_X64) #define DRWAV_X64 #elif defined(__i386) || defined(_M_IX86) #define DRWAV_X86 #elif defined(__arm__) || defined(_M_ARM) #define DRWAV_ARM #endif #ifdef _MSC_VER #define DRWAV_INLINE __forceinline #elif defined(__GNUC__) /* I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue I am using "__inline__" only when we're compiling in strict ANSI mode. */ #if defined(__STRICT_ANSI__) #define DRWAV_INLINE __inline__ __attribute__((always_inline)) #else #define DRWAV_INLINE inline __attribute__((always_inline)) #endif #elif defined(__WATCOMC__) #define DRWAV_INLINE __inline #else #define DRWAV_INLINE #endif #if defined(SIZE_MAX) #define DRWAV_SIZE_MAX SIZE_MAX #else #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) #else #define DRWAV_SIZE_MAX 0xFFFFFFFF #endif #endif #if defined(_MSC_VER) && _MSC_VER >= 1400 #define DRWAV_HAS_BYTESWAP16_INTRINSIC #define DRWAV_HAS_BYTESWAP32_INTRINSIC #define DRWAV_HAS_BYTESWAP64_INTRINSIC #elif defined(__clang__) #if defined(__has_builtin) #if __has_builtin(__builtin_bswap16) #define DRWAV_HAS_BYTESWAP16_INTRINSIC #endif #if __has_builtin(__builtin_bswap32) #define DRWAV_HAS_BYTESWAP32_INTRINSIC #endif #if __has_builtin(__builtin_bswap64) #define DRWAV_HAS_BYTESWAP64_INTRINSIC #endif #endif #elif defined(__GNUC__) #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) #define DRWAV_HAS_BYTESWAP32_INTRINSIC #define DRWAV_HAS_BYTESWAP64_INTRINSIC #endif #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) #define DRWAV_HAS_BYTESWAP16_INTRINSIC #endif #endif DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision) { if (pMajor) { *pMajor = DRWAV_VERSION_MAJOR; } if (pMinor) { *pMinor = DRWAV_VERSION_MINOR; } if (pRevision) { *pRevision = DRWAV_VERSION_REVISION; } } DRWAV_API const char* drwav_version_string(void) { return DRWAV_VERSION_STRING; } /* These limits are used for basic validation when initializing the decoder. If you exceed these limits, first of all: what on Earth are you doing?! (Let me know, I'd be curious!) Second, you can adjust these by #define-ing them before the dr_wav implementation. */ #ifndef DRWAV_MAX_SAMPLE_RATE #define DRWAV_MAX_SAMPLE_RATE 384000 #endif #ifndef DRWAV_MAX_CHANNELS #define DRWAV_MAX_CHANNELS 256 #endif #ifndef DRWAV_MAX_BITS_PER_SAMPLE #define DRWAV_MAX_BITS_PER_SAMPLE 64 #endif static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; /* 66666972-912E-11CF-A5D6-28DB04C10000 */ static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 65766177-ACF3-11D3-8CD1-00C04F8EDB8A */ /*static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 74636166-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 61746164-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A */ static DRWAV_INLINE drwav_bool32 drwav__guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) { int i; for (i = 0; i < 16; i += 1) { if (a[i] != b[i]) { return DRWAV_FALSE; } } return DRWAV_TRUE; } static DRWAV_INLINE drwav_bool32 drwav__fourcc_equal(const drwav_uint8* a, const char* b) { return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; } static DRWAV_INLINE int drwav__is_little_endian(void) { #if defined(DRWAV_X86) || defined(DRWAV_X64) return DRWAV_TRUE; #elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN return DRWAV_TRUE; #else int n = 1; return (*(char*)&n) == 1; #endif } static DRWAV_INLINE drwav_uint16 drwav__bytes_to_u16(const drwav_uint8* data) { return (data[0] << 0) | (data[1] << 8); } static DRWAV_INLINE drwav_int16 drwav__bytes_to_s16(const drwav_uint8* data) { return (short)drwav__bytes_to_u16(data); } static DRWAV_INLINE drwav_uint32 drwav__bytes_to_u32(const drwav_uint8* data) { return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } static DRWAV_INLINE drwav_int32 drwav__bytes_to_s32(const drwav_uint8* data) { return (drwav_int32)drwav__bytes_to_u32(data); } static DRWAV_INLINE drwav_uint64 drwav__bytes_to_u64(const drwav_uint8* data) { return ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); } static DRWAV_INLINE drwav_int64 drwav__bytes_to_s64(const drwav_uint8* data) { return (drwav_int64)drwav__bytes_to_u64(data); } static DRWAV_INLINE void drwav__bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid) { int i; for (i = 0; i < 16; ++i) { guid[i] = data[i]; } } static DRWAV_INLINE drwav_uint16 drwav__bswap16(drwav_uint16 n) { #ifdef DRWAV_HAS_BYTESWAP16_INTRINSIC #if defined(_MSC_VER) return _byteswap_ushort(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap16(n); #else #error "This compiler does not support the byte swap intrinsic." #endif #else return ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); #endif } static DRWAV_INLINE drwav_uint32 drwav__bswap32(drwav_uint32 n) { #ifdef DRWAV_HAS_BYTESWAP32_INTRINSIC #if defined(_MSC_VER) return _byteswap_ulong(n); #elif defined(__GNUC__) || defined(__clang__) #if defined(DRWAV_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(DRWAV_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ drwav_uint32 r; __asm__ __volatile__ ( #if defined(DRWAV_64BIT) "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ #else "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) #endif ); return r; #else return __builtin_bswap32(n); #endif #else #error "This compiler does not support the byte swap intrinsic." #endif #else return ((n & 0xFF000000) >> 24) | ((n & 0x00FF0000) >> 8) | ((n & 0x0000FF00) << 8) | ((n & 0x000000FF) << 24); #endif } static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) { #ifdef DRWAV_HAS_BYTESWAP64_INTRINSIC #if defined(_MSC_VER) return _byteswap_uint64(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap64(n); #else #error "This compiler does not support the byte swap intrinsic." #endif #else /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | ((n & ((drwav_uint64)0xFF000000 )) << 8) | ((n & ((drwav_uint64)0x00FF0000 )) << 24) | ((n & ((drwav_uint64)0x0000FF00 )) << 40) | ((n & ((drwav_uint64)0x000000FF )) << 56); #endif } static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) { return (drwav_int16)drwav__bswap16((drwav_uint16)n); } static DRWAV_INLINE void drwav__bswap_samples_s16(drwav_int16* pSamples, drwav_uint64 sampleCount) { drwav_uint64 iSample; for (iSample = 0; iSample < sampleCount; iSample += 1) { pSamples[iSample] = drwav__bswap_s16(pSamples[iSample]); } } static DRWAV_INLINE void drwav__bswap_s24(drwav_uint8* p) { drwav_uint8 t; t = p[0]; p[0] = p[2]; p[2] = t; } static DRWAV_INLINE void drwav__bswap_samples_s24(drwav_uint8* pSamples, drwav_uint64 sampleCount) { drwav_uint64 iSample; for (iSample = 0; iSample < sampleCount; iSample += 1) { drwav_uint8* pSample = pSamples + (iSample*3); drwav__bswap_s24(pSample); } } static DRWAV_INLINE drwav_int32 drwav__bswap_s32(drwav_int32 n) { return (drwav_int32)drwav__bswap32((drwav_uint32)n); } static DRWAV_INLINE void drwav__bswap_samples_s32(drwav_int32* pSamples, drwav_uint64 sampleCount) { drwav_uint64 iSample; for (iSample = 0; iSample < sampleCount; iSample += 1) { pSamples[iSample] = drwav__bswap_s32(pSamples[iSample]); } } static DRWAV_INLINE float drwav__bswap_f32(float n) { union { drwav_uint32 i; float f; } x; x.f = n; x.i = drwav__bswap32(x.i); return x.f; } static DRWAV_INLINE void drwav__bswap_samples_f32(float* pSamples, drwav_uint64 sampleCount) { drwav_uint64 iSample; for (iSample = 0; iSample < sampleCount; iSample += 1) { pSamples[iSample] = drwav__bswap_f32(pSamples[iSample]); } } static DRWAV_INLINE double drwav__bswap_f64(double n) { union { drwav_uint64 i; double f; } x; x.f = n; x.i = drwav__bswap64(x.i); return x.f; } static DRWAV_INLINE void drwav__bswap_samples_f64(double* pSamples, drwav_uint64 sampleCount) { drwav_uint64 iSample; for (iSample = 0; iSample < sampleCount; iSample += 1) { pSamples[iSample] = drwav__bswap_f64(pSamples[iSample]); } } static DRWAV_INLINE void drwav__bswap_samples_pcm(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) { /* Assumes integer PCM. Floating point PCM is done in drwav__bswap_samples_ieee(). */ switch (bytesPerSample) { case 2: /* s16, s12 (loosely packed) */ { drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); } break; case 3: /* s24 */ { drwav__bswap_samples_s24((drwav_uint8*)pSamples, sampleCount); } break; case 4: /* s32 */ { drwav__bswap_samples_s32((drwav_int32*)pSamples, sampleCount); } break; default: { /* Unsupported format. */ DRWAV_ASSERT(DRWAV_FALSE); } break; } } static DRWAV_INLINE void drwav__bswap_samples_ieee(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample) { switch (bytesPerSample) { #if 0 /* Contributions welcome for f16 support. */ case 2: /* f16 */ { drwav__bswap_samples_f16((drwav_float16*)pSamples, sampleCount); } break; #endif case 4: /* f32 */ { drwav__bswap_samples_f32((float*)pSamples, sampleCount); } break; case 8: /* f64 */ { drwav__bswap_samples_f64((double*)pSamples, sampleCount); } break; default: { /* Unsupported format. */ DRWAV_ASSERT(DRWAV_FALSE); } break; } } static DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample, drwav_uint16 format) { switch (format) { case DR_WAVE_FORMAT_PCM: { drwav__bswap_samples_pcm(pSamples, sampleCount, bytesPerSample); } break; case DR_WAVE_FORMAT_IEEE_FLOAT: { drwav__bswap_samples_ieee(pSamples, sampleCount, bytesPerSample); } break; case DR_WAVE_FORMAT_ALAW: case DR_WAVE_FORMAT_MULAW: { drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); } break; case DR_WAVE_FORMAT_ADPCM: case DR_WAVE_FORMAT_DVI_ADPCM: default: { /* Unsupported format. */ DRWAV_ASSERT(DRWAV_FALSE); } break; } } static void* drwav__malloc_default(size_t sz, void* pUserData) { (void)pUserData; return DRWAV_MALLOC(sz); } static void* drwav__realloc_default(void* p, size_t sz, void* pUserData) { (void)pUserData; return DRWAV_REALLOC(p, sz); } static void drwav__free_default(void* p, void* pUserData) { (void)pUserData; DRWAV_FREE(p); } static void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; } if (pAllocationCallbacks->onMalloc != NULL) { return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); } /* Try using realloc(). */ if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); } return NULL; } static void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; } if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); } /* Try emulating realloc() in terms of malloc()/free(). */ if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { void* p2; p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); if (p2 == NULL) { return NULL; } if (p != NULL) { DRWAV_COPY_MEMORY(p2, p, szOld); pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } return p2; } return NULL; } static void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) { if (p == NULL || pAllocationCallbacks == NULL) { return; } if (pAllocationCallbacks->onFree != NULL) { pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } } static drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { /* Copy. */ return *pAllocationCallbacks; } else { /* Defaults. */ drwav_allocation_callbacks allocationCallbacks; allocationCallbacks.pUserData = NULL; allocationCallbacks.onMalloc = drwav__malloc_default; allocationCallbacks.onRealloc = drwav__realloc_default; allocationCallbacks.onFree = drwav__free_default; return allocationCallbacks; } } static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag) { return formatTag == DR_WAVE_FORMAT_ADPCM || formatTag == DR_WAVE_FORMAT_DVI_ADPCM; } static unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) { return (unsigned int)(chunkSize % 2); } static unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) { return (unsigned int)(chunkSize % 8); } static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) { if (container == drwav_container_riff || container == drwav_container_rf64) { drwav_uint8 sizeInBytes[4]; if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { return DRWAV_AT_END; } if (onRead(pUserData, sizeInBytes, 4) != 4) { return DRWAV_INVALID_FILE; } pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes); pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 8; } else { drwav_uint8 sizeInBytes[8]; if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { return DRWAV_AT_END; } if (onRead(pUserData, sizeInBytes, 8) != 8) { return DRWAV_INVALID_FILE; } pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 24; } return DRWAV_SUCCESS; } static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) { drwav_uint64 bytesRemainingToSeek = offset; while (bytesRemainingToSeek > 0) { if (bytesRemainingToSeek > 0x7FFFFFFF) { if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { return DRWAV_FALSE; } bytesRemainingToSeek -= 0x7FFFFFFF; } else { if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { return DRWAV_FALSE; } bytesRemainingToSeek = 0; } } return DRWAV_TRUE; } static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) { if (offset <= 0x7FFFFFFF) { return onSeek(pUserData, (int)offset, drwav_seek_origin_start); } /* Larger than 32-bit seek. */ if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { return DRWAV_FALSE; } offset -= 0x7FFFFFFF; for (;;) { if (offset <= 0x7FFFFFFF) { return onSeek(pUserData, (int)offset, drwav_seek_origin_current); } if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { return DRWAV_FALSE; } offset -= 0x7FFFFFFF; } /* Should never get here. */ /*return DRWAV_TRUE; */ } static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) { drwav_chunk_header header; drwav_uint8 fmt[16]; if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { return DRWAV_FALSE; } /* Skip non-fmt chunks. */ while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { return DRWAV_FALSE; } *pRunningBytesReadOut += header.sizeInBytes + header.paddingSize; /* Try the next header. */ if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { return DRWAV_FALSE; } } /* Validation. */ if (container == drwav_container_riff || container == drwav_container_rf64) { if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { return DRWAV_FALSE; } } else { if (!drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT)) { return DRWAV_FALSE; } } if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) { return DRWAV_FALSE; } *pRunningBytesReadOut += sizeof(fmt); fmtOut->formatTag = drwav__bytes_to_u16(fmt + 0); fmtOut->channels = drwav__bytes_to_u16(fmt + 2); fmtOut->sampleRate = drwav__bytes_to_u32(fmt + 4); fmtOut->avgBytesPerSec = drwav__bytes_to_u32(fmt + 8); fmtOut->blockAlign = drwav__bytes_to_u16(fmt + 12); fmtOut->bitsPerSample = drwav__bytes_to_u16(fmt + 14); fmtOut->extendedSize = 0; fmtOut->validBitsPerSample = 0; fmtOut->channelMask = 0; memset(fmtOut->subFormat, 0, sizeof(fmtOut->subFormat)); if (header.sizeInBytes > 16) { drwav_uint8 fmt_cbSize[2]; int bytesReadSoFar = 0; if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { return DRWAV_FALSE; /* Expecting more data. */ } *pRunningBytesReadOut += sizeof(fmt_cbSize); bytesReadSoFar = 18; fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize); if (fmtOut->extendedSize > 0) { /* Simple validation. */ if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { if (fmtOut->extendedSize != 22) { return DRWAV_FALSE; } } if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { drwav_uint8 fmtext[22]; if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) { return DRWAV_FALSE; /* Expecting more data. */ } fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0); fmtOut->channelMask = drwav__bytes_to_u32(fmtext + 2); drwav__bytes_to_guid(fmtext + 6, fmtOut->subFormat); } else { if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) { return DRWAV_FALSE; } } *pRunningBytesReadOut += fmtOut->extendedSize; bytesReadSoFar += fmtOut->extendedSize; } /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */ if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) { return DRWAV_FALSE; } *pRunningBytesReadOut += (header.sizeInBytes - bytesReadSoFar); } if (header.paddingSize > 0) { if (!onSeek(pUserData, header.paddingSize, drwav_seek_origin_current)) { return DRWAV_FALSE; } *pRunningBytesReadOut += header.paddingSize; } return DRWAV_TRUE; } static size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) { size_t bytesRead; DRWAV_ASSERT(onRead != NULL); DRWAV_ASSERT(pCursor != NULL); bytesRead = onRead(pUserData, pBufferOut, bytesToRead); *pCursor += bytesRead; return bytesRead; } #if 0 static drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) { DRWAV_ASSERT(onSeek != NULL); DRWAV_ASSERT(pCursor != NULL); if (!onSeek(pUserData, offset, origin)) { return DRWAV_FALSE; } if (origin == drwav_seek_origin_start) { *pCursor = offset; } else { *pCursor += offset; } return DRWAV_TRUE; } #endif static drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) { /* The bytes per frame is a bit ambiguous. It can be either be based on the bits per sample, or the block align. The way I'm doing it here is that if the bits per sample is a multiple of 8, use floor(bitsPerSample*channels/8), otherwise fall back to the block align. */ if ((pWav->bitsPerSample & 0x7) == 0) { /* Bits per sample is a multiple of 8. */ return (pWav->bitsPerSample * pWav->fmt.channels) >> 3; } else { return pWav->fmt.blockAlign; } } DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) { if (pFMT == NULL) { return 0; } if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) { return pFMT->formatTag; } else { return drwav__bytes_to_u16(pFMT->subFormat); /* Only the first two bytes are required. */ } } static drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pWav == NULL || onRead == NULL || onSeek == NULL) { return DRWAV_FALSE; } DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); pWav->onRead = onRead; pWav->onSeek = onSeek; pWav->pUserData = pReadSeekUserData; pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { return DRWAV_FALSE; /* Invalid allocation callbacks. */ } return DRWAV_TRUE; } static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) { /* This function assumes drwav_preinit() has been called beforehand. */ drwav_uint64 cursor; /* <-- Keeps track of the byte position so we can seek to specific locations. */ drwav_bool32 sequential; drwav_uint8 riff[4]; drwav_fmt fmt; unsigned short translatedFormatTag; drwav_bool32 foundDataChunk; drwav_uint64 dataChunkSize = 0; /* <-- Important! Don't explicitly set this to 0 anywhere else. Calculation of the size of the data chunk is performed in different paths depending on the container. */ drwav_uint64 sampleCountFromFactChunk = 0; /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */ drwav_uint64 chunkSize; cursor = 0; sequential = (flags & DRWAV_SEQUENTIAL) != 0; /* The first 4 bytes should be the RIFF identifier. */ if (drwav__on_read(pWav->onRead, pWav->pUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { return DRWAV_FALSE; } /* The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for w64 it will start with "riff". */ if (drwav__fourcc_equal(riff, "RIFF")) { pWav->container = drwav_container_riff; } else if (drwav__fourcc_equal(riff, "riff")) { int i; drwav_uint8 riff2[12]; pWav->container = drwav_container_w64; /* Check the rest of the GUID for validity. */ if (drwav__on_read(pWav->onRead, pWav->pUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { return DRWAV_FALSE; } for (i = 0; i < 12; ++i) { if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { return DRWAV_FALSE; } } } else if (drwav__fourcc_equal(riff, "RF64")) { pWav->container = drwav_container_rf64; } else { return DRWAV_FALSE; /* Unknown or unsupported container. */ } if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { drwav_uint8 chunkSizeBytes[4]; drwav_uint8 wave[4]; /* RIFF/WAVE */ if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { return DRWAV_FALSE; } if (pWav->container == drwav_container_riff) { if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ } } else { if (drwav__bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { return DRWAV_FALSE; /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */ } } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; } if (!drwav__fourcc_equal(wave, "WAVE")) { return DRWAV_FALSE; /* Expecting "WAVE". */ } } else { drwav_uint8 chunkSizeBytes[8]; drwav_uint8 wave[16]; /* W64 */ if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { return DRWAV_FALSE; } if (drwav__bytes_to_u64(chunkSizeBytes) < 80) { return DRWAV_FALSE; } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; } if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) { return DRWAV_FALSE; } } /* For RF64, the "ds64" chunk must come next, before the "fmt " chunk. */ if (pWav->container == drwav_container_rf64) { drwav_uint8 sizeBytes[8]; drwav_uint64 bytesRemainingInChunk; drwav_chunk_header header; drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); if (result != DRWAV_SUCCESS) { return DRWAV_FALSE; } if (!drwav__fourcc_equal(header.id.fourcc, "ds64")) { return DRWAV_FALSE; /* Expecting "ds64". */ } bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; /* We don't care about the size of the RIFF chunk - skip it. */ if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { return DRWAV_FALSE; } bytesRemainingInChunk -= 8; cursor += 8; /* Next 8 bytes is the size of the "data" chunk. */ if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { return DRWAV_FALSE; } bytesRemainingInChunk -= 8; dataChunkSize = drwav__bytes_to_u64(sizeBytes); /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */ if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { return DRWAV_FALSE; } bytesRemainingInChunk -= 8; sampleCountFromFactChunk = drwav__bytes_to_u64(sizeBytes); /* Skip over everything else. */ if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { return DRWAV_FALSE; } cursor += bytesRemainingInChunk; } /* The next bytes should be the "fmt " chunk. */ if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { return DRWAV_FALSE; /* Failed to read the "fmt " chunk. */ } /* Basic validation. */ if ((fmt.sampleRate == 0 || fmt.sampleRate > DRWAV_MAX_SAMPLE_RATE) || (fmt.channels == 0 || fmt.channels > DRWAV_MAX_CHANNELS) || (fmt.bitsPerSample == 0 || fmt.bitsPerSample > DRWAV_MAX_BITS_PER_SAMPLE) || fmt.blockAlign == 0) { return DRWAV_FALSE; /* Probably an invalid WAV file. */ } /* Translate the internal format. */ translatedFormatTag = fmt.formatTag; if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); } /* We need to enumerate over each chunk for two reasons: 1) The "data" chunk may not be the next one 2) We may want to report each chunk back to the client In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. */ foundDataChunk = DRWAV_FALSE; /* The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. */ for (;;) { drwav_chunk_header header; drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); if (result != DRWAV_SUCCESS) { if (!foundDataChunk) { return DRWAV_FALSE; } else { break; /* Probably at the end of the file. Get out of the loop. */ } } /* Tell the client about this chunk. */ if (!sequential && onChunk != NULL) { drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, pWav->onRead, pWav->onSeek, pWav->pUserData, &header, pWav->container, &fmt); /* dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before we called the callback. */ if (callbackBytesRead > 0) { if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) { return DRWAV_FALSE; } } } if (!foundDataChunk) { pWav->dataChunkDataPos = cursor; } chunkSize = header.sizeInBytes; if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "data")) { foundDataChunk = DRWAV_TRUE; if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ dataChunkSize = chunkSize; } } } else { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { foundDataChunk = DRWAV_TRUE; dataChunkSize = chunkSize; } } /* If at this point we have found the data chunk and we're running in sequential mode, we need to break out of this loop. The reason for this is that we would otherwise require a backwards seek which sequential mode forbids. */ if (foundDataChunk && sequential) { break; } /* Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. */ if (pWav->container == drwav_container_riff) { if (drwav__fourcc_equal(header.id.fourcc, "fact")) { drwav_uint32 sampleCount; if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { return DRWAV_FALSE; } chunkSize -= 4; if (!foundDataChunk) { pWav->dataChunkDataPos = cursor; } /* The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this for Microsoft ADPCM formats. */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { sampleCountFromFactChunk = sampleCount; } else { sampleCountFromFactChunk = 0; } } } else if (pWav->container == drwav_container_w64) { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return DRWAV_FALSE; } chunkSize -= 8; if (!foundDataChunk) { pWav->dataChunkDataPos = cursor; } } } else if (pWav->container == drwav_container_rf64) { /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */ } /* "smpl" chunk. */ if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { drwav_uint8 smplHeaderData[36]; /* 36 = size of the smpl header section, not including the loop data. */ if (chunkSize >= sizeof(smplHeaderData)) { drwav_uint64 bytesJustRead = drwav__on_read(pWav->onRead, pWav->pUserData, smplHeaderData, sizeof(smplHeaderData), &cursor); chunkSize -= bytesJustRead; if (bytesJustRead == sizeof(smplHeaderData)) { drwav_uint32 iLoop; pWav->smpl.manufacturer = drwav__bytes_to_u32(smplHeaderData+0); pWav->smpl.product = drwav__bytes_to_u32(smplHeaderData+4); pWav->smpl.samplePeriod = drwav__bytes_to_u32(smplHeaderData+8); pWav->smpl.midiUnityNotes = drwav__bytes_to_u32(smplHeaderData+12); pWav->smpl.midiPitchFraction = drwav__bytes_to_u32(smplHeaderData+16); pWav->smpl.smpteFormat = drwav__bytes_to_u32(smplHeaderData+20); pWav->smpl.smpteOffset = drwav__bytes_to_u32(smplHeaderData+24); pWav->smpl.numSampleLoops = drwav__bytes_to_u32(smplHeaderData+28); pWav->smpl.samplerData = drwav__bytes_to_u32(smplHeaderData+32); for (iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) { drwav_uint8 smplLoopData[24]; /* 24 = size of a loop section in the smpl chunk. */ bytesJustRead = drwav__on_read(pWav->onRead, pWav->pUserData, smplLoopData, sizeof(smplLoopData), &cursor); chunkSize -= bytesJustRead; if (bytesJustRead == sizeof(smplLoopData)) { pWav->smpl.loops[iLoop].cuePointId = drwav__bytes_to_u32(smplLoopData+0); pWav->smpl.loops[iLoop].type = drwav__bytes_to_u32(smplLoopData+4); pWav->smpl.loops[iLoop].start = drwav__bytes_to_u32(smplLoopData+8); pWav->smpl.loops[iLoop].end = drwav__bytes_to_u32(smplLoopData+12); pWav->smpl.loops[iLoop].fraction = drwav__bytes_to_u32(smplLoopData+16); pWav->smpl.loops[iLoop].playCount = drwav__bytes_to_u32(smplLoopData+20); } else { break; /* Break from the smpl loop for loop. */ } } } } else { /* Looks like invalid data. Ignore the chunk. */ } } } else { if (drwav__guid_equal(header.id.guid, drwavGUID_W64_SMPL)) { /* This path will be hit when a W64 WAV file contains a smpl chunk. I don't have a sample file to test this path, so a contribution is welcome to add support for this. */ } } /* Make sure we seek past the padding. */ chunkSize += header.paddingSize; if (!drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData)) { break; } cursor += chunkSize; if (!foundDataChunk) { pWav->dataChunkDataPos = cursor; } } /* If we haven't found a data chunk, return an error. */ if (!foundDataChunk) { return DRWAV_FALSE; } /* We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. */ if (!sequential) { if (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) { return DRWAV_FALSE; } cursor = pWav->dataChunkDataPos; } /* At this point we should be sitting on the first byte of the raw audio data. */ pWav->fmt = fmt; pWav->sampleRate = fmt.sampleRate; pWav->channels = fmt.channels; pWav->bitsPerSample = fmt.bitsPerSample; pWav->bytesRemaining = dataChunkSize; pWav->translatedFormatTag = translatedFormatTag; pWav->dataChunkDataSize = dataChunkSize; if (sampleCountFromFactChunk != 0) { pWav->totalPCMFrameCount = sampleCountFromFactChunk; } else { pWav->totalPCMFrameCount = dataChunkSize / drwav_get_bytes_per_pcm_frame(pWav); if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { drwav_uint64 totalBlockHeaderSizeInBytes; drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; /* Make sure any trailing partial block is accounted for. */ if ((blockCount * fmt.blockAlign) < dataChunkSize) { blockCount += 1; } /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ totalBlockHeaderSizeInBytes = blockCount * (6*fmt.channels); pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { drwav_uint64 totalBlockHeaderSizeInBytes; drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; /* Make sure any trailing partial block is accounted for. */ if ((blockCount * fmt.blockAlign) < dataChunkSize) { blockCount += 1; } /* We decode two samples per byte. There will be blockCount headers in the data chunk. This is enough to know how to calculate the total PCM frame count. */ totalBlockHeaderSizeInBytes = blockCount * (4*fmt.channels); pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; /* The header includes a decoded sample for each channel which acts as the initial predictor sample. */ pWav->totalPCMFrameCount += blockCount; } } /* Some formats only support a certain number of channels. */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { if (pWav->channels > 2) { return DRWAV_FALSE; } } #ifdef DR_WAV_LIBSNDFILE_COMPAT /* I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my correctness tests against libsndfile, and is disabled by default. */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; /* x2 because two samples per byte. */ } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; } #endif return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { if (!drwav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) { return DRWAV_FALSE; } return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); } static drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) { drwav_uint64 chunkSize = 4 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 24 = "fmt " chunk. */ if (chunkSize > 0xFFFFFFFFUL) { chunkSize = 0xFFFFFFFFUL; } return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */ } static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) { if (dataChunkSize <= 0xFFFFFFFFUL) { return (drwav_uint32)dataChunkSize; } else { return 0xFFFFFFFFUL; } } static drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) { drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize); return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize; /* +24 because W64 includes the size of the GUID and size fields. */ } static drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) { return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ } static drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize) { drwav_uint64 chunkSize = 4 + 36 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 36 = "ds64" chunk. 24 = "fmt " chunk. */ if (chunkSize > 0xFFFFFFFFUL) { chunkSize = 0xFFFFFFFFUL; } return chunkSize; } static drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) { return dataChunkSize; } static size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); /* Generic write. Assumes no byte reordering required. */ return pWav->onWrite(pWav->pUserData, pData, dataSize); } static size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); if (!drwav__is_little_endian()) { value = drwav__bswap16(value); } return drwav__write(pWav, &value, 2); } static size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); if (!drwav__is_little_endian()) { value = drwav__bswap32(value); } return drwav__write(pWav, &value, 4); } static size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); if (!drwav__is_little_endian()) { value = drwav__bswap64(value); } return drwav__write(pWav, &value, 8); } static drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pWav == NULL || onWrite == NULL) { return DRWAV_FALSE; } if (!isSequential && onSeek == NULL) { return DRWAV_FALSE; /* <-- onSeek is required when in non-sequential mode. */ } /* Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. */ if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { return DRWAV_FALSE; } if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) { return DRWAV_FALSE; } DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav)); pWav->onWrite = onWrite; pWav->onSeek = onSeek; pWav->pUserData = pUserData; pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) { return DRWAV_FALSE; /* Invalid allocation callbacks. */ } pWav->fmt.formatTag = (drwav_uint16)pFormat->format; pWav->fmt.channels = (drwav_uint16)pFormat->channels; pWav->fmt.sampleRate = pFormat->sampleRate; pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8); pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8); pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; pWav->fmt.extendedSize = 0; pWav->isSequentialWrite = isSequential; return DRWAV_TRUE; } static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) { /* The function assumes drwav_preinit_write() was called beforehand. */ size_t runningPos = 0; drwav_uint64 initialDataChunkSize = 0; drwav_uint64 chunkSizeFMT; /* The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. */ if (pWav->isSequentialWrite) { initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; /* The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 so for the sake of simplicity I'm not doing any validation for that. */ if (pFormat->container == drwav_container_riff) { if (initialDataChunkSize > (0xFFFFFFFFUL - 36)) { return DRWAV_FALSE; /* Not enough room to store every sample. */ } } } pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; /* "RIFF" chunk. */ if (pFormat->container == drwav_container_riff) { drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ runningPos += drwav__write(pWav, "RIFF", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, "WAVE", 4); } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); } else if (pFormat->container == drwav_container_rf64) { runningPos += drwav__write(pWav, "RF64", 4); runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always 0xFFFFFFFF for RF64. Set to a proper value in the "ds64" chunk. */ runningPos += drwav__write(pWav, "WAVE", 4); } /* "ds64" chunk (RF64 only). */ if (pFormat->container == drwav_container_rf64) { drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; /* +8 for the ds64 header. */ runningPos += drwav__write(pWav, "ds64", 4); runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); /* Size of ds64. */ runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); /* Size of RIFF. Set to true value at the end. */ runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); /* Size of DATA. Set to true value at the end. */ runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); /* Sample count. */ runningPos += drwav__write_u32ne_to_le(pWav, 0); /* Table length. Always set to zero in our case since we're not doing any other chunks than "DATA". */ } /* "fmt " chunk. */ if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { chunkSizeFMT = 16; runningPos += drwav__write(pWav, "fmt ", 4); runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); } else if (pFormat->container == drwav_container_w64) { chunkSizeFMT = 40; runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); } runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.formatTag); runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.channels); runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.sampleRate); runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec); runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign); runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample); pWav->dataChunkDataPos = runningPos; /* "data" chunk. */ if (pFormat->container == drwav_container_riff) { drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "data", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); } else if (pFormat->container == drwav_container_rf64) { runningPos += drwav__write(pWav, "data", 4); runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */ } /* The runningPos variable is incremented in the section above but is left unused which is causing some static analysis tools to detect it as a dead store. I'm leaving this as-is for safety just in case I want to expand this function later to include other tags and want to keep track of the running position for whatever reason. The line below should silence the static analysis tools. */ (void)runningPos; /* Set some properties for the client's convenience. */ pWav->container = pFormat->container; pWav->channels = (drwav_uint16)pFormat->channels; pWav->sampleRate = pFormat->sampleRate; pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; pWav->translatedFormatTag = (drwav_uint16)pFormat->format; return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) { return DRWAV_FALSE; } return drwav_init_write__internal(pWav, pFormat, 0); /* DRWAV_FALSE = Not Sequential */ } DRWAV_API drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (!drwav_preinit_write(pWav, pFormat, DRWAV_TRUE, onWrite, NULL, pUserData, pAllocationCallbacks)) { return DRWAV_FALSE; } return drwav_init_write__internal(pWav, pFormat, totalSampleCount); /* DRWAV_TRUE = Sequential */ } DRWAV_API drwav_bool32 drwav_init_write_sequential_pcm_frames(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, drwav_write_proc onWrite, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pFormat == NULL) { return DRWAV_FALSE; } return drwav_init_write_sequential(pWav, pFormat, totalPCMFrameCount*pFormat->channels, onWrite, pUserData, pAllocationCallbacks); } DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) { /* Casting totalSampleCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */ drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalSampleCount * pFormat->channels * pFormat->bitsPerSample/8.0); drwav_uint64 riffChunkSizeBytes; drwav_uint64 fileSizeBytes = 0; if (pFormat->container == drwav_container_riff) { riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes); fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ } else if (pFormat->container == drwav_container_w64) { riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); fileSizeBytes = riffChunkSizeBytes; } else if (pFormat->container == drwav_container_rf64) { riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes); fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ } return fileSizeBytes; } #ifndef DR_WAV_NO_STDIO /* drwav_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ #include static drwav_result drwav_result_from_errno(int e) { switch (e) { case 0: return DRWAV_SUCCESS; #ifdef EPERM case EPERM: return DRWAV_INVALID_OPERATION; #endif #ifdef ENOENT case ENOENT: return DRWAV_DOES_NOT_EXIST; #endif #ifdef ESRCH case ESRCH: return DRWAV_DOES_NOT_EXIST; #endif #ifdef EINTR case EINTR: return DRWAV_INTERRUPT; #endif #ifdef EIO case EIO: return DRWAV_IO_ERROR; #endif #ifdef ENXIO case ENXIO: return DRWAV_DOES_NOT_EXIST; #endif #ifdef E2BIG case E2BIG: return DRWAV_INVALID_ARGS; #endif #ifdef ENOEXEC case ENOEXEC: return DRWAV_INVALID_FILE; #endif #ifdef EBADF case EBADF: return DRWAV_INVALID_FILE; #endif #ifdef ECHILD case ECHILD: return DRWAV_ERROR; #endif #ifdef EAGAIN case EAGAIN: return DRWAV_UNAVAILABLE; #endif #ifdef ENOMEM case ENOMEM: return DRWAV_OUT_OF_MEMORY; #endif #ifdef EACCES case EACCES: return DRWAV_ACCESS_DENIED; #endif #ifdef EFAULT case EFAULT: return DRWAV_BAD_ADDRESS; #endif #ifdef ENOTBLK case ENOTBLK: return DRWAV_ERROR; #endif #ifdef EBUSY case EBUSY: return DRWAV_BUSY; #endif #ifdef EEXIST case EEXIST: return DRWAV_ALREADY_EXISTS; #endif #ifdef EXDEV case EXDEV: return DRWAV_ERROR; #endif #ifdef ENODEV case ENODEV: return DRWAV_DOES_NOT_EXIST; #endif #ifdef ENOTDIR case ENOTDIR: return DRWAV_NOT_DIRECTORY; #endif #ifdef EISDIR case EISDIR: return DRWAV_IS_DIRECTORY; #endif #ifdef EINVAL case EINVAL: return DRWAV_INVALID_ARGS; #endif #ifdef ENFILE case ENFILE: return DRWAV_TOO_MANY_OPEN_FILES; #endif #ifdef EMFILE case EMFILE: return DRWAV_TOO_MANY_OPEN_FILES; #endif #ifdef ENOTTY case ENOTTY: return DRWAV_INVALID_OPERATION; #endif #ifdef ETXTBSY case ETXTBSY: return DRWAV_BUSY; #endif #ifdef EFBIG case EFBIG: return DRWAV_TOO_BIG; #endif #ifdef ENOSPC case ENOSPC: return DRWAV_NO_SPACE; #endif #ifdef ESPIPE case ESPIPE: return DRWAV_BAD_SEEK; #endif #ifdef EROFS case EROFS: return DRWAV_ACCESS_DENIED; #endif #ifdef EMLINK case EMLINK: return DRWAV_TOO_MANY_LINKS; #endif #ifdef EPIPE case EPIPE: return DRWAV_BAD_PIPE; #endif #ifdef EDOM case EDOM: return DRWAV_OUT_OF_RANGE; #endif #ifdef ERANGE case ERANGE: return DRWAV_OUT_OF_RANGE; #endif #ifdef EDEADLK case EDEADLK: return DRWAV_DEADLOCK; #endif #ifdef ENAMETOOLONG case ENAMETOOLONG: return DRWAV_PATH_TOO_LONG; #endif #ifdef ENOLCK case ENOLCK: return DRWAV_ERROR; #endif #ifdef ENOSYS case ENOSYS: return DRWAV_NOT_IMPLEMENTED; #endif #ifdef ENOTEMPTY case ENOTEMPTY: return DRWAV_DIRECTORY_NOT_EMPTY; #endif #ifdef ELOOP case ELOOP: return DRWAV_TOO_MANY_LINKS; #endif #ifdef ENOMSG case ENOMSG: return DRWAV_NO_MESSAGE; #endif #ifdef EIDRM case EIDRM: return DRWAV_ERROR; #endif #ifdef ECHRNG case ECHRNG: return DRWAV_ERROR; #endif #ifdef EL2NSYNC case EL2NSYNC: return DRWAV_ERROR; #endif #ifdef EL3HLT case EL3HLT: return DRWAV_ERROR; #endif #ifdef EL3RST case EL3RST: return DRWAV_ERROR; #endif #ifdef ELNRNG case ELNRNG: return DRWAV_OUT_OF_RANGE; #endif #ifdef EUNATCH case EUNATCH: return DRWAV_ERROR; #endif #ifdef ENOCSI case ENOCSI: return DRWAV_ERROR; #endif #ifdef EL2HLT case EL2HLT: return DRWAV_ERROR; #endif #ifdef EBADE case EBADE: return DRWAV_ERROR; #endif #ifdef EBADR case EBADR: return DRWAV_ERROR; #endif #ifdef EXFULL case EXFULL: return DRWAV_ERROR; #endif #ifdef ENOANO case ENOANO: return DRWAV_ERROR; #endif #ifdef EBADRQC case EBADRQC: return DRWAV_ERROR; #endif #ifdef EBADSLT case EBADSLT: return DRWAV_ERROR; #endif #ifdef EBFONT case EBFONT: return DRWAV_INVALID_FILE; #endif #ifdef ENOSTR case ENOSTR: return DRWAV_ERROR; #endif #ifdef ENODATA case ENODATA: return DRWAV_NO_DATA_AVAILABLE; #endif #ifdef ETIME case ETIME: return DRWAV_TIMEOUT; #endif #ifdef ENOSR case ENOSR: return DRWAV_NO_DATA_AVAILABLE; #endif #ifdef ENONET case ENONET: return DRWAV_NO_NETWORK; #endif #ifdef ENOPKG case ENOPKG: return DRWAV_ERROR; #endif #ifdef EREMOTE case EREMOTE: return DRWAV_ERROR; #endif #ifdef ENOLINK case ENOLINK: return DRWAV_ERROR; #endif #ifdef EADV case EADV: return DRWAV_ERROR; #endif #ifdef ESRMNT case ESRMNT: return DRWAV_ERROR; #endif #ifdef ECOMM case ECOMM: return DRWAV_ERROR; #endif #ifdef EPROTO case EPROTO: return DRWAV_ERROR; #endif #ifdef EMULTIHOP case EMULTIHOP: return DRWAV_ERROR; #endif #ifdef EDOTDOT case EDOTDOT: return DRWAV_ERROR; #endif #ifdef EBADMSG case EBADMSG: return DRWAV_BAD_MESSAGE; #endif #ifdef EOVERFLOW case EOVERFLOW: return DRWAV_TOO_BIG; #endif #ifdef ENOTUNIQ case ENOTUNIQ: return DRWAV_NOT_UNIQUE; #endif #ifdef EBADFD case EBADFD: return DRWAV_ERROR; #endif #ifdef EREMCHG case EREMCHG: return DRWAV_ERROR; #endif #ifdef ELIBACC case ELIBACC: return DRWAV_ACCESS_DENIED; #endif #ifdef ELIBBAD case ELIBBAD: return DRWAV_INVALID_FILE; #endif #ifdef ELIBSCN case ELIBSCN: return DRWAV_INVALID_FILE; #endif #ifdef ELIBMAX case ELIBMAX: return DRWAV_ERROR; #endif #ifdef ELIBEXEC case ELIBEXEC: return DRWAV_ERROR; #endif #ifdef EILSEQ case EILSEQ: return DRWAV_INVALID_DATA; #endif #ifdef ERESTART case ERESTART: return DRWAV_ERROR; #endif #ifdef ESTRPIPE case ESTRPIPE: return DRWAV_ERROR; #endif #ifdef EUSERS case EUSERS: return DRWAV_ERROR; #endif #ifdef ENOTSOCK case ENOTSOCK: return DRWAV_NOT_SOCKET; #endif #ifdef EDESTADDRREQ case EDESTADDRREQ: return DRWAV_NO_ADDRESS; #endif #ifdef EMSGSIZE case EMSGSIZE: return DRWAV_TOO_BIG; #endif #ifdef EPROTOTYPE case EPROTOTYPE: return DRWAV_BAD_PROTOCOL; #endif #ifdef ENOPROTOOPT case ENOPROTOOPT: return DRWAV_PROTOCOL_UNAVAILABLE; #endif #ifdef EPROTONOSUPPORT case EPROTONOSUPPORT: return DRWAV_PROTOCOL_NOT_SUPPORTED; #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: return DRWAV_SOCKET_NOT_SUPPORTED; #endif #ifdef EOPNOTSUPP case EOPNOTSUPP: return DRWAV_INVALID_OPERATION; #endif #ifdef EPFNOSUPPORT case EPFNOSUPPORT: return DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED; #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: return DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED; #endif #ifdef EADDRINUSE case EADDRINUSE: return DRWAV_ALREADY_IN_USE; #endif #ifdef EADDRNOTAVAIL case EADDRNOTAVAIL: return DRWAV_ERROR; #endif #ifdef ENETDOWN case ENETDOWN: return DRWAV_NO_NETWORK; #endif #ifdef ENETUNREACH case ENETUNREACH: return DRWAV_NO_NETWORK; #endif #ifdef ENETRESET case ENETRESET: return DRWAV_NO_NETWORK; #endif #ifdef ECONNABORTED case ECONNABORTED: return DRWAV_NO_NETWORK; #endif #ifdef ECONNRESET case ECONNRESET: return DRWAV_CONNECTION_RESET; #endif #ifdef ENOBUFS case ENOBUFS: return DRWAV_NO_SPACE; #endif #ifdef EISCONN case EISCONN: return DRWAV_ALREADY_CONNECTED; #endif #ifdef ENOTCONN case ENOTCONN: return DRWAV_NOT_CONNECTED; #endif #ifdef ESHUTDOWN case ESHUTDOWN: return DRWAV_ERROR; #endif #ifdef ETOOMANYREFS case ETOOMANYREFS: return DRWAV_ERROR; #endif #ifdef ETIMEDOUT case ETIMEDOUT: return DRWAV_TIMEOUT; #endif #ifdef ECONNREFUSED case ECONNREFUSED: return DRWAV_CONNECTION_REFUSED; #endif #ifdef EHOSTDOWN case EHOSTDOWN: return DRWAV_NO_HOST; #endif #ifdef EHOSTUNREACH case EHOSTUNREACH: return DRWAV_NO_HOST; #endif #ifdef EALREADY case EALREADY: return DRWAV_IN_PROGRESS; #endif #ifdef EINPROGRESS case EINPROGRESS: return DRWAV_IN_PROGRESS; #endif #ifdef ESTALE case ESTALE: return DRWAV_INVALID_FILE; #endif #ifdef EUCLEAN case EUCLEAN: return DRWAV_ERROR; #endif #ifdef ENOTNAM case ENOTNAM: return DRWAV_ERROR; #endif #ifdef ENAVAIL case ENAVAIL: return DRWAV_ERROR; #endif #ifdef EISNAM case EISNAM: return DRWAV_ERROR; #endif #ifdef EREMOTEIO case EREMOTEIO: return DRWAV_IO_ERROR; #endif #ifdef EDQUOT case EDQUOT: return DRWAV_NO_SPACE; #endif #ifdef ENOMEDIUM case ENOMEDIUM: return DRWAV_DOES_NOT_EXIST; #endif #ifdef EMEDIUMTYPE case EMEDIUMTYPE: return DRWAV_ERROR; #endif #ifdef ECANCELED case ECANCELED: return DRWAV_CANCELLED; #endif #ifdef ENOKEY case ENOKEY: return DRWAV_ERROR; #endif #ifdef EKEYEXPIRED case EKEYEXPIRED: return DRWAV_ERROR; #endif #ifdef EKEYREVOKED case EKEYREVOKED: return DRWAV_ERROR; #endif #ifdef EKEYREJECTED case EKEYREJECTED: return DRWAV_ERROR; #endif #ifdef EOWNERDEAD case EOWNERDEAD: return DRWAV_ERROR; #endif #ifdef ENOTRECOVERABLE case ENOTRECOVERABLE: return DRWAV_ERROR; #endif #ifdef ERFKILL case ERFKILL: return DRWAV_ERROR; #endif #ifdef EHWPOISON case EHWPOISON: return DRWAV_ERROR; #endif default: return DRWAV_ERROR; } } static drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { #if _MSC_VER && _MSC_VER >= 1400 errno_t err; #endif if (ppFile != NULL) { *ppFile = NULL; /* Safety. */ } if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRWAV_INVALID_ARGS; } #if _MSC_VER && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drwav_result_from_errno(err); } #else #if defined(_WIN32) || defined(__APPLE__) *ppFile = fopen(pFilePath, pOpenMode); #else #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) *ppFile = fopen64(pFilePath, pOpenMode); #else *ppFile = fopen(pFilePath, pOpenMode); #endif #endif if (*ppFile == NULL) { drwav_result result = drwav_result_from_errno(errno); if (result == DRWAV_SUCCESS) { result = DRWAV_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ } return result; } #endif return DRWAV_SUCCESS; } /* _wfopen() isn't always available in all compilation environments. * Windows only. * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). * MinGW-64 (both 32- and 64-bit) seems to support it. * MinGW wraps it in !defined(__STRICT_ANSI__). * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define DRWAV_HAS_WFOPEN #endif #endif static drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) { if (ppFile != NULL) { *ppFile = NULL; /* Safety. */ } if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRWAV_INVALID_ARGS; } #if defined(DRWAV_HAS_WFOPEN) { /* Use _wfopen() on Windows. */ #if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drwav_result_from_errno(err); } #else *ppFile = _wfopen(pFilePath, pOpenMode); if (*ppFile == NULL) { return drwav_result_from_errno(errno); } #endif (void)pAllocationCallbacks; } #else /* Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. */ { mbstate_t mbs; size_t lenMB; const wchar_t* pFilePathTemp = pFilePath; char* pFilePathMB = NULL; char pOpenModeMB[32] = {0}; /* Get the length first. */ DRWAV_ZERO_OBJECT(&mbs); lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); if (lenMB == (size_t)-1) { return drwav_result_from_errno(errno); } pFilePathMB = (char*)drwav__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); if (pFilePathMB == NULL) { return DRWAV_OUT_OF_MEMORY; } pFilePathTemp = pFilePath; DRWAV_ZERO_OBJECT(&mbs); wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ { size_t i = 0; for (;;) { if (pOpenMode[i] == 0) { pOpenModeMB[i] = '\0'; break; } pOpenModeMB[i] = (char)pOpenMode[i]; i += 1; } } *ppFile = fopen(pFilePathMB, pOpenModeMB); drwav__free_from_callbacks(pFilePathMB, pAllocationCallbacks); } if (*ppFile == NULL) { return DRWAV_ERROR; } #endif return DRWAV_SUCCESS; } static size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) { return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); } static size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) { return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); } static drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) { return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_file_ex(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); } static drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav_bool32 result; result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); if (result != DRWAV_TRUE) { fclose(pFile); return result; } result = drwav_init__internal(pWav, onChunk, pChunkUserData, flags); if (result != DRWAV_TRUE) { fclose(pFile); return result; } return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_fopen(&pFile, filename, "rb") != DRWAV_SUCCESS) { return DRWAV_FALSE; } /* This takes ownership of the FILE* object. */ return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_file_ex_w(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_wfopen(&pFile, filename, L"rb", pAllocationCallbacks) != DRWAV_SUCCESS) { return DRWAV_FALSE; } /* This takes ownership of the FILE* object. */ return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks); } static drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav_bool32 result; result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); if (result != DRWAV_TRUE) { fclose(pFile); return result; } result = drwav_init_write__internal(pWav, pFormat, totalSampleCount); if (result != DRWAV_TRUE) { fclose(pFile); return result; } return DRWAV_TRUE; } static drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS) { return DRWAV_FALSE; } /* This takes ownership of the FILE* object. */ return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); } static drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_wfopen(&pFile, filename, L"wb", pAllocationCallbacks) != DRWAV_SUCCESS) { return DRWAV_FALSE; } /* This takes ownership of the FILE* object. */ return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pFormat == NULL) { return DRWAV_FALSE; } return drwav_init_file_write_sequential(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_file_write_w__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_write_sequential_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_file_write_w__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pFormat == NULL) { return DRWAV_FALSE; } return drwav_init_file_write_sequential_w(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); } #endif /* DR_WAV_NO_STDIO */ static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) { drwav* pWav = (drwav*)pUserData; size_t bytesRemaining; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->memoryStream.dataSize >= pWav->memoryStream.currentReadPos); bytesRemaining = pWav->memoryStream.dataSize - pWav->memoryStream.currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } if (bytesToRead > 0) { DRWAV_COPY_MEMORY(pBufferOut, pWav->memoryStream.data + pWav->memoryStream.currentReadPos, bytesToRead); pWav->memoryStream.currentReadPos += bytesToRead; } return bytesToRead; } static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; DRWAV_ASSERT(pWav != NULL); if (origin == drwav_seek_origin_current) { if (offset > 0) { if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) { return DRWAV_FALSE; /* Trying to seek too far forward. */ } } else { if (pWav->memoryStream.currentReadPos < (size_t)-offset) { return DRWAV_FALSE; /* Trying to seek too far backwards. */ } } /* This will never underflow thanks to the clamps above. */ pWav->memoryStream.currentReadPos += offset; } else { if ((drwav_uint32)offset <= pWav->memoryStream.dataSize) { pWav->memoryStream.currentReadPos = offset; } else { return DRWAV_FALSE; /* Trying to seek too far forward. */ } } return DRWAV_TRUE; } static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) { drwav* pWav = (drwav*)pUserData; size_t bytesRemaining; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->memoryStreamWrite.dataCapacity >= pWav->memoryStreamWrite.currentWritePos); bytesRemaining = pWav->memoryStreamWrite.dataCapacity - pWav->memoryStreamWrite.currentWritePos; if (bytesRemaining < bytesToWrite) { /* Need to reallocate. */ void* pNewData; size_t newDataCapacity = (pWav->memoryStreamWrite.dataCapacity == 0) ? 256 : pWav->memoryStreamWrite.dataCapacity * 2; /* If doubling wasn't enough, just make it the minimum required size to write the data. */ if ((newDataCapacity - pWav->memoryStreamWrite.currentWritePos) < bytesToWrite) { newDataCapacity = pWav->memoryStreamWrite.currentWritePos + bytesToWrite; } pNewData = drwav__realloc_from_callbacks(*pWav->memoryStreamWrite.ppData, newDataCapacity, pWav->memoryStreamWrite.dataCapacity, &pWav->allocationCallbacks); if (pNewData == NULL) { return 0; } *pWav->memoryStreamWrite.ppData = pNewData; pWav->memoryStreamWrite.dataCapacity = newDataCapacity; } DRWAV_COPY_MEMORY(((drwav_uint8*)(*pWav->memoryStreamWrite.ppData)) + pWav->memoryStreamWrite.currentWritePos, pDataIn, bytesToWrite); pWav->memoryStreamWrite.currentWritePos += bytesToWrite; if (pWav->memoryStreamWrite.dataSize < pWav->memoryStreamWrite.currentWritePos) { pWav->memoryStreamWrite.dataSize = pWav->memoryStreamWrite.currentWritePos; } *pWav->memoryStreamWrite.pDataSize = pWav->memoryStreamWrite.dataSize; return bytesToWrite; } static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; DRWAV_ASSERT(pWav != NULL); if (origin == drwav_seek_origin_current) { if (offset > 0) { if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) { offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos); /* Trying to seek too far forward. */ } } else { if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) { offset = -(int)pWav->memoryStreamWrite.currentWritePos; /* Trying to seek too far backwards. */ } } /* This will never underflow thanks to the clamps above. */ pWav->memoryStreamWrite.currentWritePos += offset; } else { if ((drwav_uint32)offset <= pWav->memoryStreamWrite.dataSize) { pWav->memoryStreamWrite.currentWritePos = offset; } else { pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */ } } return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { if (data == NULL || dataSize == 0) { return DRWAV_FALSE; } if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) { return DRWAV_FALSE; } pWav->memoryStream.data = (const drwav_uint8*)data; pWav->memoryStream.dataSize = dataSize; pWav->memoryStream.currentReadPos = 0; return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); } static drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { if (ppData == NULL || pDataSize == NULL) { return DRWAV_FALSE; } *ppData = NULL; /* Important because we're using realloc()! */ *pDataSize = 0; if (!drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, pWav, pAllocationCallbacks)) { return DRWAV_FALSE; } pWav->memoryStreamWrite.ppData = ppData; pWav->memoryStreamWrite.pDataSize = pDataSize; pWav->memoryStreamWrite.dataSize = 0; pWav->memoryStreamWrite.dataCapacity = 0; pWav->memoryStreamWrite.currentWritePos = 0; return drwav_init_write__internal(pWav, pFormat, totalSampleCount); } DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks) { return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks); } DRWAV_API drwav_bool32 drwav_init_memory_write_sequential_pcm_frames(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalPCMFrameCount, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pFormat == NULL) { return DRWAV_FALSE; } return drwav_init_memory_write_sequential(pWav, ppData, pDataSize, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); } DRWAV_API drwav_result drwav_uninit(drwav* pWav) { drwav_result result = DRWAV_SUCCESS; if (pWav == NULL) { return DRWAV_INVALID_ARGS; } /* If the drwav object was opened in write mode we'll need to finalize a few things: - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. - Set the size of the "data" chunk. */ if (pWav->onWrite != NULL) { drwav_uint32 paddingSize = 0; /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); } else { paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); } if (paddingSize > 0) { drwav_uint64 paddingData = 0; drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */ } /* Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need to do this when using non-sequential mode. */ if (pWav->onSeek && !pWav->isSequentialWrite) { if (pWav->container == drwav_container_riff) { /* The "RIFF" chunk size. */ if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, riffChunkSize); } /* the "data" chunk size. */ if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) { drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, dataChunkSize); } } else if (pWav->container == drwav_container_w64) { /* The "RIFF" chunk size. */ if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, riffChunkSize); } /* The "data" chunk size. */ if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) { drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } } else if (pWav->container == drwav_container_rf64) { /* We only need to update the ds64 chunk. The "RIFF" and "data" chunks always have their sizes set to 0xFFFFFFFF for RF64. */ int ds64BodyPos = 12 + 8; /* The "RIFF" chunk size. */ if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, riffChunkSize); } /* The "data" chunk size. */ if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } } } /* Validation for sequential mode. */ if (pWav->isSequentialWrite) { if (pWav->dataChunkDataSize != pWav->dataChunkDataSizeTargetWrite) { result = DRWAV_INVALID_FILE; } } } #ifndef DR_WAV_NO_STDIO /* If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() was used by looking at the onRead and onSeek callbacks. */ if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { fclose((FILE*)pWav->pUserData); } #endif return result; } DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) { size_t bytesRead; if (pWav == NULL || bytesToRead == 0) { return 0; } if (bytesToRead > pWav->bytesRemaining) { bytesToRead = (size_t)pWav->bytesRemaining; } if (pBufferOut != NULL) { bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); } else { /* We need to seek. If we fail, we need to read-and-discard to make sure we get a good byte count. */ bytesRead = 0; while (bytesRead < bytesToRead) { size_t bytesToSeek = (bytesToRead - bytesRead); if (bytesToSeek > 0x7FFFFFFF) { bytesToSeek = 0x7FFFFFFF; } if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) { break; } bytesRead += bytesToSeek; } /* When we get here we may need to read-and-discard some data. */ while (bytesRead < bytesToRead) { drwav_uint8 buffer[4096]; size_t bytesSeeked; size_t bytesToSeek = (bytesToRead - bytesRead); if (bytesToSeek > sizeof(buffer)) { bytesToSeek = sizeof(buffer); } bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); bytesRead += bytesSeeked; if (bytesSeeked < bytesToSeek) { break; /* Reached the end. */ } } } pWav->bytesRemaining -= bytesRead; return bytesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) { drwav_uint32 bytesPerFrame; drwav_uint64 bytesToRead; /* Intentionally uint64 instead of size_t so we can do a check that we're not reading too much on 32-bit builds. */ if (pWav == NULL || framesToRead == 0) { return 0; } /* Cannot use this function for compressed formats. */ if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { return 0; } bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } /* Don't try to read more samples than can potentially fit in the output buffer. */ bytesToRead = framesToRead * bytesPerFrame; if (bytesToRead > DRWAV_SIZE_MAX) { bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; /* Round the number of bytes to read to a clean frame boundary. */ } /* Doing an explicit check here just to make it clear that we don't want to be attempt to read anything if there's no bytes to read. There *could* be a time where it evaluates to 0 due to overflowing. */ if (bytesToRead == 0) { return 0; } return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL) { drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, drwav_get_bytes_per_pcm_frame(pWav)/pWav->channels, pWav->translatedFormatTag); } return framesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) { if (drwav__is_little_endian()) { return drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); } else { return drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); } } DRWAV_API drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) { if (pWav->onWrite != NULL) { return DRWAV_FALSE; /* No seeking in write mode. */ } if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { return DRWAV_FALSE; } if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { pWav->compressed.iCurrentPCMFrame = 0; /* Cached data needs to be cleared for compressed formats. */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { DRWAV_ZERO_OBJECT(&pWav->msadpcm); } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { DRWAV_ZERO_OBJECT(&pWav->ima); } else { DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ } } pWav->bytesRemaining = pWav->dataChunkDataSize; return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) { /* Seeking should be compatible with wave files > 2GB. */ if (pWav == NULL || pWav->onSeek == NULL) { return DRWAV_FALSE; } /* No seeking in write mode. */ if (pWav->onWrite != NULL) { return DRWAV_FALSE; } /* If there are no samples, just return DRWAV_TRUE without doing anything. */ if (pWav->totalPCMFrameCount == 0) { return DRWAV_TRUE; } /* Make sure the sample is clamped. */ if (targetFrameIndex >= pWav->totalPCMFrameCount) { targetFrameIndex = pWav->totalPCMFrameCount - 1; } /* For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need to seek back to the start. */ if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { /* TODO: This can be optimized. */ /* If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, we first need to seek back to the start and then just do the same thing as a forward seek. */ if (targetFrameIndex < pWav->compressed.iCurrentPCMFrame) { if (!drwav_seek_to_first_pcm_frame(pWav)) { return DRWAV_FALSE; } } if (targetFrameIndex > pWav->compressed.iCurrentPCMFrame) { drwav_uint64 offsetInFrames = targetFrameIndex - pWav->compressed.iCurrentPCMFrame; drwav_int16 devnull[2048]; while (offsetInFrames > 0) { drwav_uint64 framesRead = 0; drwav_uint64 framesToRead = offsetInFrames; if (framesToRead > drwav_countof(devnull)/pWav->channels) { framesToRead = drwav_countof(devnull)/pWav->channels; } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { framesRead = drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, devnull); } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { framesRead = drwav_read_pcm_frames_s16__ima(pWav, framesToRead, devnull); } else { DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ } if (framesRead != framesToRead) { return DRWAV_FALSE; } offsetInFrames -= framesRead; } } } else { drwav_uint64 totalSizeInBytes; drwav_uint64 currentBytePos; drwav_uint64 targetBytePos; drwav_uint64 offset; totalSizeInBytes = pWav->totalPCMFrameCount * drwav_get_bytes_per_pcm_frame(pWav); DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining); currentBytePos = totalSizeInBytes - pWav->bytesRemaining; targetBytePos = targetFrameIndex * drwav_get_bytes_per_pcm_frame(pWav); if (currentBytePos < targetBytePos) { /* Offset forwards. */ offset = (targetBytePos - currentBytePos); } else { /* Offset backwards. */ if (!drwav_seek_to_first_pcm_frame(pWav)) { return DRWAV_FALSE; } offset = targetBytePos; } while (offset > 0) { int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { return DRWAV_FALSE; } pWav->bytesRemaining -= offset32; offset -= offset32; } } return DRWAV_TRUE; } DRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) { size_t bytesWritten; if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { return 0; } bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); pWav->dataChunkDataSize += bytesWritten; return bytesWritten; } DRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) { drwav_uint64 bytesToWrite; drwav_uint64 bytesWritten; const drwav_uint8* pRunningData; if (pWav == NULL || framesToWrite == 0 || pData == NULL) { return 0; } bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); if (bytesToWrite > DRWAV_SIZE_MAX) { return 0; } bytesWritten = 0; pRunningData = (const drwav_uint8*)pData; while (bytesToWrite > 0) { size_t bytesJustWritten; drwav_uint64 bytesToWriteThisIteration; bytesToWriteThisIteration = bytesToWrite; DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); if (bytesJustWritten == 0) { break; } bytesToWrite -= bytesJustWritten; bytesWritten += bytesJustWritten; pRunningData += bytesJustWritten; } return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; } DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) { drwav_uint64 bytesToWrite; drwav_uint64 bytesWritten; drwav_uint32 bytesPerSample; const drwav_uint8* pRunningData; if (pWav == NULL || framesToWrite == 0 || pData == NULL) { return 0; } bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8); if (bytesToWrite > DRWAV_SIZE_MAX) { return 0; } bytesWritten = 0; pRunningData = (const drwav_uint8*)pData; bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; while (bytesToWrite > 0) { drwav_uint8 temp[4096]; drwav_uint32 sampleCount; size_t bytesJustWritten; drwav_uint64 bytesToWriteThisIteration; bytesToWriteThisIteration = bytesToWrite; DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX); /* <-- This is checked above. */ /* WAV files are always little-endian. We need to byte swap on big-endian architectures. Since our input buffer is read-only we need to use an intermediary buffer for the conversion. */ sampleCount = sizeof(temp)/bytesPerSample; if (bytesToWriteThisIteration > ((drwav_uint64)sampleCount)*bytesPerSample) { bytesToWriteThisIteration = ((drwav_uint64)sampleCount)*bytesPerSample; } DRWAV_COPY_MEMORY(temp, pRunningData, (size_t)bytesToWriteThisIteration); drwav__bswap_samples(temp, sampleCount, bytesPerSample, pWav->translatedFormatTag); bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, temp); if (bytesJustWritten == 0) { break; } bytesToWrite -= bytesJustWritten; bytesWritten += bytesJustWritten; pRunningData += bytesJustWritten; } return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels; } DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) { if (drwav__is_little_endian()) { return drwav_write_pcm_frames_le(pWav, framesToWrite, pData); } else { return drwav_write_pcm_frames_be(pWav, framesToWrite, pData); } } static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead = 0; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(framesToRead > 0); /* TODO: Lots of room for optimization here. */ while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { /* If there are no cached frames we need to load a new block. */ if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { /* Mono. */ drwav_uint8 header[7]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalFramesRead; } pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); pWav->msadpcm.predictor[0] = header[0]; pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 1); pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 3); pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 5); pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrameCount = 2; } else { /* Stereo. */ drwav_uint8 header[14]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalFramesRead; } pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); pWav->msadpcm.predictor[0] = header[0]; pWav->msadpcm.predictor[1] = header[1]; pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 2); pWav->msadpcm.delta[1] = drwav__bytes_to_s16(header + 4); pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 6); pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav__bytes_to_s16(header + 8); pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 10); pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav__bytes_to_s16(header + 12); pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0]; pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1]; pWav->msadpcm.cachedFrameCount = 2; } } /* Output anything that's cached. */ while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { if (pBufferOut != NULL) { drwav_uint32 iSample = 0; for (iSample = 0; iSample < pWav->channels; iSample += 1) { pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample]; } pBufferOut += pWav->channels; } framesToRead -= 1; totalFramesRead += 1; pWav->compressed.iCurrentPCMFrame += 1; pWav->msadpcm.cachedFrameCount -= 1; } if (framesToRead == 0) { return totalFramesRead; } /* If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next loop iteration which will trigger the loading of a new block. */ if (pWav->msadpcm.cachedFrameCount == 0) { if (pWav->msadpcm.bytesRemainingInBlock == 0) { continue; } else { static drwav_int32 adaptationTable[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; drwav_uint8 nibbles; drwav_int32 nibble0; drwav_int32 nibble1; if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { return totalFramesRead; } pWav->msadpcm.bytesRemainingInBlock -= 1; /* TODO: Optimize away these if statements. */ nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } if (pWav->channels == 1) { /* Mono. */ drwav_int32 newSample0; drwav_int32 newSample1; newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = drwav_clamp(newSample0, -32768, 32767); pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; if (pWav->msadpcm.delta[0] < 16) { pWav->msadpcm.delta[0] = 16; } pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.prevFrames[0][1] = newSample0; newSample1 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample1 += nibble1 * pWav->msadpcm.delta[0]; newSample1 = drwav_clamp(newSample1, -32768, 32767); pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; if (pWav->msadpcm.delta[0] < 16) { pWav->msadpcm.delta[0] = 16; } pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.prevFrames[0][1] = newSample1; pWav->msadpcm.cachedFrames[2] = newSample0; pWav->msadpcm.cachedFrames[3] = newSample1; pWav->msadpcm.cachedFrameCount = 2; } else { /* Stereo. */ drwav_int32 newSample0; drwav_int32 newSample1; /* Left. */ newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = drwav_clamp(newSample0, -32768, 32767); pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; if (pWav->msadpcm.delta[0] < 16) { pWav->msadpcm.delta[0] = 16; } pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.prevFrames[0][1] = newSample0; /* Right. */ newSample1 = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; newSample1 += nibble1 * pWav->msadpcm.delta[1]; newSample1 = drwav_clamp(newSample1, -32768, 32767); pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; if (pWav->msadpcm.delta[1] < 16) { pWav->msadpcm.delta[1] = 16; } pWav->msadpcm.prevFrames[1][0] = pWav->msadpcm.prevFrames[1][1]; pWav->msadpcm.prevFrames[1][1] = newSample1; pWav->msadpcm.cachedFrames[2] = newSample0; pWav->msadpcm.cachedFrames[3] = newSample1; pWav->msadpcm.cachedFrameCount = 1; } } } } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_uint32 iChannel; static drwav_int32 indexTable[16] = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 }; static drwav_int32 stepTable[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(framesToRead > 0); /* TODO: Lots of room for optimization here. */ while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { /* If there are no cached samples we need to load a new block. */ if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { /* Mono. */ drwav_uint8 header[4]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalFramesRead; } pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); if (header[2] >= drwav_countof(stepTable)) { pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; /* Invalid data. */ } pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); pWav->ima.stepIndex[0] = header[2]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; pWav->ima.cachedFrameCount = 1; } else { /* Stereo. */ drwav_uint8 header[8]; if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { return totalFramesRead; } pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) { pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; /* Invalid data. */ } pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); pWav->ima.stepIndex[0] = header[2]; pWav->ima.predictor[1] = drwav__bytes_to_s16(header + 4); pWav->ima.stepIndex[1] = header[6]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; pWav->ima.cachedFrameCount = 1; } } /* Output anything that's cached. */ while (framesToRead > 0 && pWav->ima.cachedFrameCount > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { if (pBufferOut != NULL) { drwav_uint32 iSample; for (iSample = 0; iSample < pWav->channels; iSample += 1) { pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample]; } pBufferOut += pWav->channels; } framesToRead -= 1; totalFramesRead += 1; pWav->compressed.iCurrentPCMFrame += 1; pWav->ima.cachedFrameCount -= 1; } if (framesToRead == 0) { return totalFramesRead; } /* If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next loop iteration which will trigger the loading of a new block. */ if (pWav->ima.cachedFrameCount == 0) { if (pWav->ima.bytesRemainingInBlock == 0) { continue; } else { /* From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the left channel, 4 bytes for the right channel. */ pWav->ima.cachedFrameCount = 8; for (iChannel = 0; iChannel < pWav->channels; ++iChannel) { drwav_uint32 iByte; drwav_uint8 nibbles[4]; if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { pWav->ima.cachedFrameCount = 0; return totalFramesRead; } pWav->ima.bytesRemainingInBlock -= 4; for (iByte = 0; iByte < 4; ++iByte) { drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); drwav_int32 step = stepTable[pWav->ima.stepIndex[iChannel]]; drwav_int32 predictor = pWav->ima.predictor[iChannel]; drwav_int32 diff = step >> 3; if (nibble0 & 1) diff += step >> 2; if (nibble0 & 2) diff += step >> 1; if (nibble0 & 4) diff += step; if (nibble0 & 8) diff = -diff; predictor = drwav_clamp(predictor + diff, -32768, 32767); pWav->ima.predictor[iChannel] = predictor; pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+0)*pWav->channels + iChannel] = predictor; step = stepTable[pWav->ima.stepIndex[iChannel]]; predictor = pWav->ima.predictor[iChannel]; diff = step >> 3; if (nibble1 & 1) diff += step >> 2; if (nibble1 & 2) diff += step >> 1; if (nibble1 & 4) diff += step; if (nibble1 & 8) diff = -diff; predictor = drwav_clamp(predictor + diff, -32768, 32767); pWav->ima.predictor[iChannel] = predictor; pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+1)*pWav->channels + iChannel] = predictor; } } } } } return totalFramesRead; } #ifndef DR_WAV_NO_CONVERSION_API static unsigned short g_drwavAlawTable[256] = { 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 }; static unsigned short g_drwavMulawTable[256] = { 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 }; static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) { return (short)g_drwavAlawTable[sampleIn]; } static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) { return (short)g_drwavMulawTable[sampleIn]; } static void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { unsigned int i; /* Special case for 8-bit sample data because it's treated as unsigned. */ if (bytesPerSample == 1) { drwav_u8_to_s16(pOut, pIn, totalSampleCount); return; } /* Slightly more optimal implementation for common formats. */ if (bytesPerSample == 2) { for (i = 0; i < totalSampleCount; ++i) { *pOut++ = ((const drwav_int16*)pIn)[i]; } return; } if (bytesPerSample == 3) { drwav_s24_to_s16(pOut, pIn, totalSampleCount); return; } if (bytesPerSample == 4) { drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); return; } /* Anything more than 64 bits per sample is not supported. */ if (bytesPerSample > 8) { DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); return; } /* Generic, slow converter. */ for (i = 0; i < totalSampleCount; ++i) { drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; unsigned int j; for (j = 0; j < bytesPerSample; j += 1) { DRWAV_ASSERT(j < 8); sample |= (drwav_uint64)(pIn[j]) << shift; shift += 8; } pIn += j; *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); } } static void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); return; } else if (bytesPerSample == 8) { drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); return; } else { /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); return; } } static drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint32 bytesPerFrame; drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; /* Fast path. */ if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { if (pWav == NULL || framesToRead == 0) { return 0; } if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } /* Don't try to read more samples than can potentially fit in the output buffer. */ if (framesToRead * pWav->channels * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16) / pWav->channels; } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_s16__pcm(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { return drwav_read_pcm_frames_s16__ieee(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { return drwav_read_pcm_frames_s16__alaw(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { return drwav_read_pcm_frames_s16__mulaw(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { return drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { return drwav_read_pcm_frames_s16__ima(pWav, framesToRead, pBufferOut); } return 0; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); } return framesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); } return framesRead; } DRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { int r; size_t i; for (i = 0; i < sampleCount; ++i) { int x = pIn[i]; r = x << 8; r = r - 32768; pOut[i] = (short)r; } } DRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { int r; size_t i; for (i = 0; i < sampleCount; ++i) { int x = ((int)(((unsigned int)(((const drwav_uint8*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const drwav_uint8*)pIn)[i*3+2])) << 24)) >> 8; r = x >> 8; pOut[i] = (short)r; } } DRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) { int r; size_t i; for (i = 0; i < sampleCount; ++i) { int x = pIn[i]; r = x >> 16; pOut[i] = (short)r; } } DRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) { int r; size_t i; for (i = 0; i < sampleCount; ++i) { float x = pIn[i]; float c; c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); c = c + 1; r = (int)(c * 32767.5f); r = r - 32768; pOut[i] = (short)r; } } DRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) { int r; size_t i; for (i = 0; i < sampleCount; ++i) { double x = pIn[i]; double c; c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); c = c + 1; r = (int)(c * 32767.5); r = r - 32768; pOut[i] = (short)r; } } DRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; for (i = 0; i < sampleCount; ++i) { pOut[i] = drwav__alaw_to_s16(pIn[i]); } } DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; for (i = 0; i < sampleCount; ++i) { pOut[i] = drwav__mulaw_to_s16(pIn[i]); } } static void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) { unsigned int i; /* Special case for 8-bit sample data because it's treated as unsigned. */ if (bytesPerSample == 1) { drwav_u8_to_f32(pOut, pIn, sampleCount); return; } /* Slightly more optimal implementation for common formats. */ if (bytesPerSample == 2) { drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); return; } if (bytesPerSample == 3) { drwav_s24_to_f32(pOut, pIn, sampleCount); return; } if (bytesPerSample == 4) { drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount); return; } /* Anything more than 64 bits per sample is not supported. */ if (bytesPerSample > 8) { DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); return; } /* Generic, slow converter. */ for (i = 0; i < sampleCount; ++i) { drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; unsigned int j; for (j = 0; j < bytesPerSample; j += 1) { DRWAV_ASSERT(j < 8); sample |= (drwav_uint64)(pIn[j]) << shift; shift += 8; } pIn += j; *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); } } static void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { unsigned int i; for (i = 0; i < sampleCount; ++i) { *pOut++ = ((const float*)pIn)[i]; } return; } else if (bytesPerSample == 8) { drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); return; } else { /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut)); return; } } static drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)framesRead*pWav->channels, bytesPerFrame/pWav->channels); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't want to duplicate that code. */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { break; } drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't want to duplicate that code. */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { break; } drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame; /* Fast path. */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { if (pWav == NULL || framesToRead == 0) { return 0; } if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } /* Don't try to read more samples than can potentially fit in the output buffer. */ if (framesToRead * pWav->channels * sizeof(float) > DRWAV_SIZE_MAX) { framesToRead = DRWAV_SIZE_MAX / sizeof(float) / pWav->channels; } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { return drwav_read_pcm_frames_f32__msadpcm(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { return drwav_read_pcm_frames_f32__alaw(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { return drwav_read_pcm_frames_f32__ima(pWav, framesToRead, pBufferOut); } return 0; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); } return framesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); } return framesRead; } DRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } #ifdef DR_WAV_LIBSNDFILE_COMPAT /* It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated correctness testing. This is disabled by default. */ for (i = 0; i < sampleCount; ++i) { *pOut++ = (pIn[i] / 256.0f) * 2 - 1; } #else for (i = 0; i < sampleCount; ++i) { float x = pIn[i]; x = x * 0.00784313725490196078f; /* 0..255 to 0..2 */ x = x - 1; /* 0..2 to -1..1 */ *pOut++ = x; } #endif } DRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = pIn[i] * 0.000030517578125f; } } DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { double x; drwav_uint32 a = ((drwav_uint32)(pIn[i*3+0]) << 8); drwav_uint32 b = ((drwav_uint32)(pIn[i*3+1]) << 16); drwav_uint32 c = ((drwav_uint32)(pIn[i*3+2]) << 24); x = (double)((drwav_int32)(a | b | c) >> 8); *pOut++ = (float)(x * 0.00000011920928955078125); } } DRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = (float)(pIn[i] / 2147483648.0); } } DRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = (float)pIn[i]; } } DRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; } } DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; } } static void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { unsigned int i; /* Special case for 8-bit sample data because it's treated as unsigned. */ if (bytesPerSample == 1) { drwav_u8_to_s32(pOut, pIn, totalSampleCount); return; } /* Slightly more optimal implementation for common formats. */ if (bytesPerSample == 2) { drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); return; } if (bytesPerSample == 3) { drwav_s24_to_s32(pOut, pIn, totalSampleCount); return; } if (bytesPerSample == 4) { for (i = 0; i < totalSampleCount; ++i) { *pOut++ = ((const drwav_int32*)pIn)[i]; } return; } /* Anything more than 64 bits per sample is not supported. */ if (bytesPerSample > 8) { DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); return; } /* Generic, slow converter. */ for (i = 0; i < totalSampleCount; ++i) { drwav_uint64 sample = 0; unsigned int shift = (8 - bytesPerSample) * 8; unsigned int j; for (j = 0; j < bytesPerSample; j += 1) { DRWAV_ASSERT(j < 8); sample |= (drwav_uint64)(pIn[j]) << shift; shift += 8; } pIn += j; *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); } } static void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); return; } else if (bytesPerSample == 8) { drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); return; } else { /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */ DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut)); return; } } static drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame; /* Fast path. */ if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't want to duplicate that code. */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { break; } drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't want to duplicate that code. */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { break; } drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); /* <-- Safe cast because we're clamping to 2048. */ pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } static drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { break; } drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { if (pWav == NULL || framesToRead == 0) { return 0; } if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } /* Don't try to read more samples than can potentially fit in the output buffer. */ if (framesToRead * pWav->channels * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32) / pWav->channels; } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { return drwav_read_pcm_frames_s32__msadpcm(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { return drwav_read_pcm_frames_s32__alaw(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { return drwav_read_pcm_frames_s32__ima(pWav, framesToRead, pBufferOut); } return 0; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); } return framesRead; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); } return framesRead; } DRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = ((int)pIn[i] - 128) << 24; } } DRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = pIn[i] << 16; } } DRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { unsigned int s0 = pIn[i*3 + 0]; unsigned int s1 = pIn[i*3 + 1]; unsigned int s2 = pIn[i*3 + 2]; drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); *pOut++ = sample32; } } DRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); } } DRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); } } DRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i = 0; i < sampleCount; ++i) { *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; } } DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) { size_t i; if (pOut == NULL || pIn == NULL) { return; } for (i= 0; i < sampleCount; ++i) { *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; } } static drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; drwav_int16* pSampleData; drwav_uint64 framesRead; DRWAV_ASSERT(pWav != NULL); sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int16); if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); return NULL; /* File's too big. */ } pSampleData = (drwav_int16*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ if (pSampleData == NULL) { drwav_uninit(pWav); return NULL; /* Failed to allocate memory. */ } framesRead = drwav_read_pcm_frames_s16(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); if (framesRead != pWav->totalPCMFrameCount) { drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); drwav_uninit(pWav); return NULL; /* There was an error reading the samples. */ } drwav_uninit(pWav); if (sampleRate) { *sampleRate = pWav->sampleRate; } if (channels) { *channels = pWav->channels; } if (totalFrameCount) { *totalFrameCount = pWav->totalPCMFrameCount; } return pSampleData; } static float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; float* pSampleData; drwav_uint64 framesRead; DRWAV_ASSERT(pWav != NULL); sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float); if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); return NULL; /* File's too big. */ } pSampleData = (float*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ if (pSampleData == NULL) { drwav_uninit(pWav); return NULL; /* Failed to allocate memory. */ } framesRead = drwav_read_pcm_frames_f32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); if (framesRead != pWav->totalPCMFrameCount) { drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); drwav_uninit(pWav); return NULL; /* There was an error reading the samples. */ } drwav_uninit(pWav); if (sampleRate) { *sampleRate = pWav->sampleRate; } if (channels) { *channels = pWav->channels; } if (totalFrameCount) { *totalFrameCount = pWav->totalPCMFrameCount; } return pSampleData; } static drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; drwav_int32* pSampleData; drwav_uint64 framesRead; DRWAV_ASSERT(pWav != NULL); sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int32); if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); return NULL; /* File's too big. */ } pSampleData = (drwav_int32*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */ if (pSampleData == NULL) { drwav_uninit(pWav); return NULL; /* Failed to allocate memory. */ } framesRead = drwav_read_pcm_frames_s32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData); if (framesRead != pWav->totalPCMFrameCount) { drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks); drwav_uninit(pWav); return NULL; /* There was an error reading the samples. */ } drwav_uninit(pWav); if (sampleRate) { *sampleRate = pWav->sampleRate; } if (channels) { *channels = pWav->channels; } if (totalFrameCount) { *totalFrameCount = pWav->totalPCMFrameCount; } return pSampleData; } DRWAV_API drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } #ifndef DR_WAV_NO_STDIO DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API drwav_int16* drwav_open_file_and_read_pcm_frames_s16_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (sampleRateOut) { *sampleRateOut = 0; } if (channelsOut) { *channelsOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API float* drwav_open_file_and_read_pcm_frames_f32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (sampleRateOut) { *sampleRateOut = 0; } if (channelsOut) { *channelsOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API drwav_int32* drwav_open_file_and_read_pcm_frames_s32_w(const wchar_t* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (sampleRateOut) { *sampleRateOut = 0; } if (channelsOut) { *channelsOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } #endif DRWAV_API drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } DRWAV_API drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav wav; if (channelsOut) { *channelsOut = 0; } if (sampleRateOut) { *sampleRateOut = 0; } if (totalFrameCountOut) { *totalFrameCountOut = 0; } if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) { return NULL; } return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut); } #endif /* DR_WAV_NO_CONVERSION_API */ DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { drwav__free_from_callbacks(p, pAllocationCallbacks); } else { drwav__free_default(p, NULL); } } DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data) { return drwav__bytes_to_u16(data); } DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data) { return drwav__bytes_to_s16(data); } DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data) { return drwav__bytes_to_u32(data); } DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data) { return drwav__bytes_to_s32(data); } DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data) { return drwav__bytes_to_u64(data); } DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data) { return drwav__bytes_to_s64(data); } DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) { return drwav__guid_equal(a, b); } DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) { return drwav__fourcc_equal(a, b); } #endif /* dr_wav_c */ #endif /* DR_WAV_IMPLEMENTATION */ /* RELEASE NOTES - v0.11.0 ======================= Version 0.11.0 has breaking API changes. Improved Client-Defined Memory Allocation ----------------------------------------- The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The existing system of DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE are still in place and will be used by default when no custom allocation callbacks are specified. To use the new system, you pass in a pointer to a drwav_allocation_callbacks object to drwav_init() and family, like this: void* my_malloc(size_t sz, void* pUserData) { return malloc(sz); } void* my_realloc(void* p, size_t sz, void* pUserData) { return realloc(p, sz); } void my_free(void* p, void* pUserData) { free(p); } ... drwav_allocation_callbacks allocationCallbacks; allocationCallbacks.pUserData = &myData; allocationCallbacks.onMalloc = my_malloc; allocationCallbacks.onRealloc = my_realloc; allocationCallbacks.onFree = my_free; drwav_init_file(&wav, "my_file.wav", &allocationCallbacks); The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. Passing in null for the allocation callbacks object will cause dr_wav to use defaults which is the same as DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE and the equivalent of how it worked in previous versions. Every API that opens a drwav object now takes this extra parameter. These include the following: drwav_init() drwav_init_ex() drwav_init_file() drwav_init_file_ex() drwav_init_file_w() drwav_init_file_w_ex() drwav_init_memory() drwav_init_memory_ex() drwav_init_write() drwav_init_write_sequential() drwav_init_write_sequential_pcm_frames() drwav_init_file_write() drwav_init_file_write_sequential() drwav_init_file_write_sequential_pcm_frames() drwav_init_file_write_w() drwav_init_file_write_sequential_w() drwav_init_file_write_sequential_pcm_frames_w() drwav_init_memory_write() drwav_init_memory_write_sequential() drwav_init_memory_write_sequential_pcm_frames() drwav_open_and_read_pcm_frames_s16() drwav_open_and_read_pcm_frames_f32() drwav_open_and_read_pcm_frames_s32() drwav_open_file_and_read_pcm_frames_s16() drwav_open_file_and_read_pcm_frames_f32() drwav_open_file_and_read_pcm_frames_s32() drwav_open_file_and_read_pcm_frames_s16_w() drwav_open_file_and_read_pcm_frames_f32_w() drwav_open_file_and_read_pcm_frames_s32_w() drwav_open_memory_and_read_pcm_frames_s16() drwav_open_memory_and_read_pcm_frames_f32() drwav_open_memory_and_read_pcm_frames_s32() Endian Improvements ------------------- Previously, the following APIs returned little-endian audio data. These now return native-endian data. This improves compatibility on big-endian architectures. drwav_read_pcm_frames() drwav_read_pcm_frames_s16() drwav_read_pcm_frames_s32() drwav_read_pcm_frames_f32() drwav_open_and_read_pcm_frames_s16() drwav_open_and_read_pcm_frames_s32() drwav_open_and_read_pcm_frames_f32() drwav_open_file_and_read_pcm_frames_s16() drwav_open_file_and_read_pcm_frames_s32() drwav_open_file_and_read_pcm_frames_f32() drwav_open_file_and_read_pcm_frames_s16_w() drwav_open_file_and_read_pcm_frames_s32_w() drwav_open_file_and_read_pcm_frames_f32_w() drwav_open_memory_and_read_pcm_frames_s16() drwav_open_memory_and_read_pcm_frames_s32() drwav_open_memory_and_read_pcm_frames_f32() APIs have been added to give you explicit control over whether or not audio data is read or written in big- or little-endian byte order: drwav_read_pcm_frames_le() drwav_read_pcm_frames_be() drwav_read_pcm_frames_s16le() drwav_read_pcm_frames_s16be() drwav_read_pcm_frames_f32le() drwav_read_pcm_frames_f32be() drwav_read_pcm_frames_s32le() drwav_read_pcm_frames_s32be() drwav_write_pcm_frames_le() drwav_write_pcm_frames_be() Removed APIs ------------ The following APIs were deprecated in version 0.10.0 and have now been removed: drwav_open() drwav_open_ex() drwav_open_write() drwav_open_write_sequential() drwav_open_file() drwav_open_file_ex() drwav_open_file_write() drwav_open_file_write_sequential() drwav_open_memory() drwav_open_memory_ex() drwav_open_memory_write() drwav_open_memory_write_sequential() drwav_close() RELEASE NOTES - v0.10.0 ======================= Version 0.10.0 has breaking API changes. There are no significant bug fixes in this release, so if you are affected you do not need to upgrade. Removed APIs ------------ The following APIs were deprecated in version 0.9.0 and have been completely removed in version 0.10.0: drwav_read() drwav_read_s16() drwav_read_f32() drwav_read_s32() drwav_seek_to_sample() drwav_write() drwav_open_and_read_s16() drwav_open_and_read_f32() drwav_open_and_read_s32() drwav_open_file_and_read_s16() drwav_open_file_and_read_f32() drwav_open_file_and_read_s32() drwav_open_memory_and_read_s16() drwav_open_memory_and_read_f32() drwav_open_memory_and_read_s32() drwav::totalSampleCount See release notes for version 0.9.0 at the bottom of this file for replacement APIs. Deprecated APIs --------------- The following APIs have been deprecated. There is a confusing and completely arbitrary difference between drwav_init*() and drwav_open*(), where drwav_init*() initializes a pre-allocated drwav object, whereas drwav_open*() will first allocated a drwav object on the heap and then initialize it. drwav_open*() has been deprecated which means you must now use a pre- allocated drwav object with drwav_init*(). If you need the previous functionality, you can just do a malloc() followed by a called to one of the drwav_init*() APIs. drwav_open() drwav_open_ex() drwav_open_write() drwav_open_write_sequential() drwav_open_file() drwav_open_file_ex() drwav_open_file_write() drwav_open_file_write_sequential() drwav_open_memory() drwav_open_memory_ex() drwav_open_memory_write() drwav_open_memory_write_sequential() drwav_close() These APIs will be removed completely in a future version. The rationale for this change is to remove confusion between the two different ways to initialize a drwav object. */ /* REVISION HISTORY ================ v0.12.16 - 2020-12-02 - Fix a bug when trying to read more bytes than can fit in a size_t. v0.12.15 - 2020-11-21 - Fix compilation with OpenWatcom. v0.12.14 - 2020-11-13 - Minor code clean up. v0.12.13 - 2020-11-01 - Improve compiler support for older versions of GCC. v0.12.12 - 2020-09-28 - Add support for RF64. - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. v0.12.11 - 2020-09-08 - Fix a compilation error on older compilers. v0.12.10 - 2020-08-24 - Fix a bug when seeking with ADPCM formats. v0.12.9 - 2020-08-02 - Simplify sized types. v0.12.8 - 2020-07-25 - Fix a compilation warning. v0.12.7 - 2020-07-15 - Fix some bugs on big-endian architectures. - Fix an error in s24 to f32 conversion. v0.12.6 - 2020-06-23 - Change drwav_read_*() to allow NULL to be passed in as the output buffer which is equivalent to a forward seek. - Fix a buffer overflow when trying to decode invalid IMA-ADPCM files. - Add include guard for the implementation section. v0.12.5 - 2020-05-27 - Minor documentation fix. v0.12.4 - 2020-05-16 - Replace assert() with DRWAV_ASSERT(). - Add compile-time and run-time version querying. - DRWAV_VERSION_MINOR - DRWAV_VERSION_MAJOR - DRWAV_VERSION_REVISION - DRWAV_VERSION_STRING - drwav_version() - drwav_version_string() v0.12.3 - 2020-04-30 - Fix compilation errors with VC6. v0.12.2 - 2020-04-21 - Fix a bug where drwav_init_file() does not close the file handle after attempting to load an erroneous file. v0.12.1 - 2020-04-13 - Fix some pedantic warnings. v0.12.0 - 2020-04-04 - API CHANGE: Add container and format parameters to the chunk callback. - Minor documentation updates. v0.11.5 - 2020-03-07 - Fix compilation error with Visual Studio .NET 2003. v0.11.4 - 2020-01-29 - Fix some static analysis warnings. - Fix a bug when reading f32 samples from an A-law encoded stream. v0.11.3 - 2020-01-12 - Minor changes to some f32 format conversion routines. - Minor bug fix for ADPCM conversion when end of file is reached. v0.11.2 - 2019-12-02 - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. - Fix an integer overflow bug. - Fix a null pointer dereference bug. - Add limits to sample rate, channels and bits per sample to tighten up some validation. v0.11.1 - 2019-10-07 - Internal code clean up. v0.11.0 - 2019-10-06 - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: - drwav_init() - drwav_init_ex() - drwav_init_file() - drwav_init_file_ex() - drwav_init_file_w() - drwav_init_file_w_ex() - drwav_init_memory() - drwav_init_memory_ex() - drwav_init_write() - drwav_init_write_sequential() - drwav_init_write_sequential_pcm_frames() - drwav_init_file_write() - drwav_init_file_write_sequential() - drwav_init_file_write_sequential_pcm_frames() - drwav_init_file_write_w() - drwav_init_file_write_sequential_w() - drwav_init_file_write_sequential_pcm_frames_w() - drwav_init_memory_write() - drwav_init_memory_write_sequential() - drwav_init_memory_write_sequential_pcm_frames() - drwav_open_and_read_pcm_frames_s16() - drwav_open_and_read_pcm_frames_f32() - drwav_open_and_read_pcm_frames_s32() - drwav_open_file_and_read_pcm_frames_s16() - drwav_open_file_and_read_pcm_frames_f32() - drwav_open_file_and_read_pcm_frames_s32() - drwav_open_file_and_read_pcm_frames_s16_w() - drwav_open_file_and_read_pcm_frames_f32_w() - drwav_open_file_and_read_pcm_frames_s32_w() - drwav_open_memory_and_read_pcm_frames_s16() - drwav_open_memory_and_read_pcm_frames_f32() - drwav_open_memory_and_read_pcm_frames_s32() Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE. - Add support for reading and writing PCM frames in an explicit endianness. New APIs: - drwav_read_pcm_frames_le() - drwav_read_pcm_frames_be() - drwav_read_pcm_frames_s16le() - drwav_read_pcm_frames_s16be() - drwav_read_pcm_frames_f32le() - drwav_read_pcm_frames_f32be() - drwav_read_pcm_frames_s32le() - drwav_read_pcm_frames_s32be() - drwav_write_pcm_frames_le() - drwav_write_pcm_frames_be() - Remove deprecated APIs. - API CHANGE: The following APIs now return native-endian data. Previously they returned little-endian data. - drwav_read_pcm_frames() - drwav_read_pcm_frames_s16() - drwav_read_pcm_frames_s32() - drwav_read_pcm_frames_f32() - drwav_open_and_read_pcm_frames_s16() - drwav_open_and_read_pcm_frames_s32() - drwav_open_and_read_pcm_frames_f32() - drwav_open_file_and_read_pcm_frames_s16() - drwav_open_file_and_read_pcm_frames_s32() - drwav_open_file_and_read_pcm_frames_f32() - drwav_open_file_and_read_pcm_frames_s16_w() - drwav_open_file_and_read_pcm_frames_s32_w() - drwav_open_file_and_read_pcm_frames_f32_w() - drwav_open_memory_and_read_pcm_frames_s16() - drwav_open_memory_and_read_pcm_frames_s32() - drwav_open_memory_and_read_pcm_frames_f32() v0.10.1 - 2019-08-31 - Correctly handle partial trailing ADPCM blocks. v0.10.0 - 2019-08-04 - Remove deprecated APIs. - Add wchar_t variants for file loading APIs: drwav_init_file_w() drwav_init_file_ex_w() drwav_init_file_write_w() drwav_init_file_write_sequential_w() - Add drwav_target_write_size_bytes() which calculates the total size in bytes of a WAV file given a format and sample count. - Add APIs for specifying the PCM frame count instead of the sample count when opening in sequential write mode: drwav_init_write_sequential_pcm_frames() drwav_init_file_write_sequential_pcm_frames() drwav_init_file_write_sequential_pcm_frames_w() drwav_init_memory_write_sequential_pcm_frames() - Deprecate drwav_open*() and drwav_close(): drwav_open() drwav_open_ex() drwav_open_write() drwav_open_write_sequential() drwav_open_file() drwav_open_file_ex() drwav_open_file_write() drwav_open_file_write_sequential() drwav_open_memory() drwav_open_memory_ex() drwav_open_memory_write() drwav_open_memory_write_sequential() drwav_close() - Minor documentation updates. v0.9.2 - 2019-05-21 - Fix warnings. v0.9.1 - 2019-05-05 - Add support for C89. - Change license to choice of public domain or MIT-0. v0.9.0 - 2018-12-16 - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and will be removed in v0.10.0. Deprecated APIs and their replacements: drwav_read() -> drwav_read_pcm_frames() drwav_read_s16() -> drwav_read_pcm_frames_s16() drwav_read_f32() -> drwav_read_pcm_frames_f32() drwav_read_s32() -> drwav_read_pcm_frames_s32() drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() drwav_write() -> drwav_write_pcm_frames() drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() drwav::totalSampleCount -> drwav::totalPCMFrameCount - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). - Add built-in support for smpl chunks. - Add support for firing a callback for each chunk in the file at initialization time. - This is enabled through the drwav_init_ex(), etc. family of APIs. - Handle invalid FMT chunks more robustly. v0.8.5 - 2018-09-11 - Const correctness. - Fix a potential stack overflow. v0.8.4 - 2018-08-07 - Improve 64-bit detection. v0.8.3 - 2018-08-05 - Fix C++ build on older versions of GCC. v0.8.2 - 2018-08-02 - Fix some big-endian bugs. v0.8.1 - 2018-06-29 - Add support for sequential writing APIs. - Disable seeking in write mode. - Fix bugs with Wave64. - Fix typos. v0.8 - 2018-04-27 - Bug fix. - Start using major.minor.revision versioning. v0.7f - 2018-02-05 - Restrict ADPCM formats to a maximum of 2 channels. v0.7e - 2018-02-02 - Fix a crash. v0.7d - 2018-02-01 - Fix a crash. v0.7c - 2018-02-01 - Set drwav.bytesPerSample to 0 for all compressed formats. - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). - Fix some divide-by-zero errors. v0.7b - 2018-01-22 - Fix errors with seeking of compressed formats. - Fix compilation error when DR_WAV_NO_CONVERSION_API v0.7a - 2017-11-17 - Fix some GCC warnings. v0.7 - 2017-11-04 - Add writing APIs. v0.6 - 2017-08-16 - API CHANGE: Rename dr_* types to drwav_*. - Add support for custom implementations of malloc(), realloc(), etc. - Add support for Microsoft ADPCM. - Add support for IMA ADPCM (DVI, format code 0x11). - Optimizations to drwav_read_s16(). - Bug fixes. v0.5g - 2017-07-16 - Change underlying type for booleans to unsigned. v0.5f - 2017-04-04 - Fix a minor bug with drwav_open_and_read_s16() and family. v0.5e - 2016-12-29 - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. - Minor fixes to documentation. v0.5d - 2016-12-28 - Use drwav_int* and drwav_uint* sized types to improve compiler support. v0.5c - 2016-11-11 - Properly handle JUNK chunks that come before the FMT chunk. v0.5b - 2016-10-23 - A minor change to drwav_bool8 and drwav_bool32 types. v0.5a - 2016-10-11 - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. - Improve A-law and mu-law efficiency. v0.5 - 2016-09-29 - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to keep it consistent with dr_audio and dr_flac. v0.4b - 2016-09-18 - Fixed a typo in documentation. v0.4a - 2016-09-18 - Fixed a typo. - Change date format to ISO 8601 (YYYY-MM-DD) v0.4 - 2016-07-13 - API CHANGE. Make onSeek consistent with dr_flac. - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. - Added support for Sony Wave64. v0.3a - 2016-05-28 - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. - Fixed a memory leak. v0.3 - 2016-05-22 - Lots of API changes for consistency. v0.2a - 2016-05-16 - Fixed Linux/GCC build. v0.2 - 2016-05-11 - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. v0.1a - 2016-05-07 - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. v0.1 - 2016-05-04 - Initial versioned release. */ /* This software is available as a choice of the following licenses. Choose whichever you prefer. =============================================================================== ALTERNATIVE 1 - Public Domain (www.unlicense.org) =============================================================================== This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. 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 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. For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== Copyright 2020 David Reid 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. 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. */ ================================================ FILE: examples/esp32-rx/.gitignore ================================================ ggwave ggwave.cpp fft.h resampler.h resampler.cpp reed-solomon ================================================ FILE: examples/esp32-rx/CMakeLists.txt ================================================ # # esp32-rx #configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.h COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/fft.h ${CMAKE_CURRENT_SOURCE_DIR}/fft.h COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY) #configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE COPYONLY) ================================================ FILE: examples/esp32-rx/README.md ================================================ # esp32-rx This is a sample project for receiving audio data using [ESP32](https://www.espressif.com/en/products/socs/esp32) microcontroller. The chip has a built-in 12-bit ADC which is used to process the analog audio from the external microphone module in real-time. The program also support input from I2S MEMS microphones that does not require the usage of the ADC. The received messages are optionally displayed on the attached OLED display. ## Setup - NodeMCU-ESP32 - OLED SSD1306 - Microphone, tested with the following, but others could be also supported: - Analog: - MAX9814 - KY-037 - KY-038 - WS Sound sensor - I2S MEMS: - SPH0645 ## Pinout ### Analog Microphone | MCU | Mic | | ------- | --------- | | GND | GND | | 3.3V | VCC / VDD | | GPIO 35 | Out | ### Digital (I2S) Microphone | MCU | Mic | | ------- | ----------- | | GND | GND | | 3.3V | VCC / VDD | | GPIO 26 | BCLK | | GPIO 33 | Data / DOUT | | GPIO 25 | LRCL | ### I2C Display (optional) | MCU | Display | | ------- | ----------- | | GND | GND | | 3.3V | VCC / VDD | | GPIO 21 | SDA | | GPIO 22 | SCL | ![Sketch-Breadboard](fritzing-sketch_bb.png) ![Sketch-photo](https://user-images.githubusercontent.com/1991296/177842221-411c77a4-09cd-43b7-988f-44eebbad8f8c.JPEG) ## Demo https://user-images.githubusercontent.com/1991296/177211906-2102e9fa-8203-4b80-82e6-4839bf66f01f.mp4 [Watch high quality on Youtube](https://youtu.be/38JoMwdpH6I) ================================================ FILE: examples/esp32-rx/esp32-rx.ino ================================================ // esp32-rx // // Sample sketch for receiving sound data using "ggwave" // // Tested MCU boards: // - NodeMCU-ESP32-S // // Tested analog microphones: // - MAX9814 // - KY-037 // - KY-038 // - WS Sound sensor // // Tested I2S microphones: // - Adafruit I2S SPH0645 // // The ESP32 microcontroller has a built-in 12-bit ADC which is used to digitalize the analog signal // from the external analog microphone. When I2S microphone is used, the ADC is not used. // // The sketch optionally supports displaying the received "ggwave" data on an OLED display. // Use the DISPLAY_OUTPUT macro to enable or disable this functionality. // // If you don't have a display, you can simply observe the decoded data in the serial monitor. // // If you want to perform a quick test, you can use the free "Waver" application: // - Web: https://waver.ggerganov.com // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 // // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of // bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is // listed as Rx in the current sketch. // // Demo: https://youtu.be/38JoMwdpH6I // // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx // // ## Pinout // // ### Analog Microphone // // | MCU | Mic | // | ------- | --------- | // | GND | GND | // | 3.3V | VCC / VDD | // | GPIO 35 | Out | // // ### Digital (I2S) Microphone // // | MCU | Mic | // | ------- | ----------- | // | GND | GND | // | 3.3V | VCC / VDD | // | GPIO 26 | BCLK | // | GPIO 33 | Data / DOUT | // | GPIO 25 | LRCL | // // ### I2C Display (optional) // // | MCU | Display | // | ------- | --------- | // | GND | GND | // | 3.3V | VCC / VDD | // | GPIO 21 | SDA | // | GPIO 22 | SCL | // // Uncomment the line coresponding to your microhpone #define MIC_ANALOG //#define MIC_I2S //#define MIC_I2S_SPH0645 // Uncoment this line to enable SSD1306 display output //#define DISPLAY_OUTPUT 1 // Uncoment this line to enable long-range transmission // These protocols are slower and use more memory to decode, but are much more robust //#define LONG_RANGE 1 #include #include #include // Pin configuration const int kPinLED0 = 2; // Global GGwave instance GGWave ggwave; // Audio capture configuration using TSample = int16_t; #if defined(MIC_ANALOG) using TSampleInput = int16_t; #elif defined(MIC_I2S) || defined(MIC_I2S_SPH0645) using TSampleInput = int32_t; #endif const size_t kSampleSize_bytes = sizeof(TSample); // High sample rate - better quality, but more CPU/Memory usage const int sampleRate = 24000; const int samplesPerFrame = 512; // Low sample rate // Only MT protocols will work in this mode //const int sampleRate = 12000; //const int samplesPerFrame = 256; TSample sampleBuffer[samplesPerFrame]; // helper buffer for data input in different formats: #if defined(MIC_ANALOG) TSampleInput * sampleBufferRaw = sampleBuffer; #elif defined(MIC_I2S) || defined(MIC_I2S_SPH0645) TSampleInput sampleBufferRaw[samplesPerFrame]; #endif const i2s_port_t i2s_port = I2S_NUM_0; #if defined(MIC_ANALOG) // ADC configuration const adc_unit_t adc_unit = ADC_UNIT_1; const adc1_channel_t adc_channel = ADC1_GPIO35_CHANNEL; // i2s config for using the internal ADC const i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), .sample_rate = sampleRate, .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, .communication_format = I2S_COMM_FORMAT_I2S_LSB, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = samplesPerFrame, .use_apll = false, .tx_desc_auto_clear = false, .fixed_mclk = 0 }; #endif #if defined(MIC_I2S) || defined(MIC_I2S_SPH0645) // i2s config for using I2S mic input from RIGHT channel const i2s_config_t i2s_config = { .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = sampleRate, .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = samplesPerFrame, .use_apll = false, .tx_desc_auto_clear = false, .fixed_mclk = 0 }; // The pin config as per the setup const i2s_pin_config_t pin_config = { .bck_io_num = 26, // Serial Clock (SCK) .ws_io_num = 25, // Word Select (WS) .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers) .data_in_num = 33 // Serial Data (SD) }; #endif #ifdef DISPLAY_OUTPUT #include #include #include #include #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 32 // OLED display height, in pixels // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) // The pins for I2C are defined by the Wire-library. // On an arduino UNO: A4(SDA), A5(SCL) // On an arduino MEGA 2560: 20(SDA), 21(SCL) // On an arduino LEONARDO: 2(SDA), 3(SCL), ... #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #endif void setup() { Serial.begin(115200); while (!Serial); pinMode(kPinLED0, OUTPUT); digitalWrite(kPinLED0, LOW); #ifdef DISPLAY_OUTPUT { Serial.println(F("Initializing display...")); // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } // Show initial display buffer contents on the screen -- // the library initializes this with an Adafruit splash screen. //display.display(); //delay(2000); // Pause for 2 seconds // Clear the buffer display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); // Draw white text display.setCursor(0, 0); // Start at top-left corner display.println(F("GGWave!")); display.setTextSize(1); display.println(F("")); display.println(F("Listening...")); display.display(); } #endif // Initialize "ggwave" { Serial.println(F("Trying to initialize the ggwave instance")); ggwave.setLogFile(nullptr); auto p = GGWave::getDefaultParameters(); // Adjust the "ggwave" parameters to your needs. // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. #ifdef LONG_RANGE // The "FAST" protocols require 2x more memory, so we reduce the payload length to compensate: p.payloadLength = 8; #else p.payloadLength = 16; #endif Serial.print(F("Using payload length: ")); Serial.println(p.payloadLength); p.sampleRateInp = sampleRate; p.sampleRateOut = sampleRate; p.sampleRate = sampleRate; p.samplesPerFrame = samplesPerFrame; p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; // Protocols to use for TX // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::tx().disableAll(); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); // Protocols to use for RX // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::rx().disableAll(); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); #ifdef LONG_RANGE GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); #endif GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); #ifdef LONG_RANGE GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); #endif GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); // Print the memory required for the "ggwave" instance: ggwave.prepare(p, false); Serial.print(F("Required memory by the ggwave instance: ")); Serial.print(ggwave.heapSize()); Serial.println(F(" bytes")); // Initialize the "ggwave" instance: ggwave.prepare(p, true); Serial.print(F("Instance initialized successfully! Memory used: ")); } // Start capturing audio { Serial.println(F("Initializing I2S interface")); // Install and start i2s driver i2s_driver_install(i2s_port, &i2s_config, 0, NULL); #if defined(MIC_ANALOG) Serial.println(F("Using analog input - initializing ADC")); // Init ADC pad i2s_set_adc_mode(adc_unit, adc_channel); // Enable the adc i2s_adc_enable(i2s_port); Serial.println(F("I2S ADC started")); #endif #if defined(MIC_I2S) || defined(MIC_I2S_SPH0645) Serial.println(F("Using I2S input")); #if defined(MIC_I2S_SPH0645) Serial.println(F("Applying fix for SPH0645")); // https://github.com/atomic14/esp32_audio/blob/d2ac3490c0836cb46a69c83b0570873de18f695e/i2s_sampling/src/I2SMEMSSampler.cpp#L17-L22 REG_SET_BIT(I2S_TIMING_REG(i2s_port), BIT(9)); REG_SET_BIT(I2S_CONF_REG(i2s_port), I2S_RX_MSB_SHIFT); #endif i2s_set_pin(i2s_port, &pin_config); #endif } } int niter = 0; int tLastReceive = -10000; GGWave::TxRxData result; void loop() { // Read from i2s { size_t bytes_read = 0; i2s_read(i2s_port, sampleBufferRaw, sizeof(TSampleInput)*samplesPerFrame, &bytes_read, portMAX_DELAY); int nSamples = bytes_read/sizeof(TSampleInput); if (nSamples != samplesPerFrame) { Serial.println("Failed to read samples"); return; } #if defined(MIC_ANALOG) // the ADC samples are 12-bit so we need to do some massaging to make them 16-bit for (int i = 0; i < nSamples; i += 2) { auto & s0 = sampleBuffer[i]; auto & s1 = sampleBuffer[i + 1]; s0 = s0 & 0x0fff; s1 = s1 & 0x0fff; s0 = s0 ^ s1; s1 = s0 ^ s1; s0 = s0 ^ s1; } #endif #if defined(MIC_I2S) || defined(MIC_I2S_SPH0645) for (int i = 0; i < nSamples; ++i) { sampleBuffer[i] = (sampleBufferRaw[i] & 0xFFFFFFF0) >> 11; } #endif } // Use this with the serial plotter to observe real-time audio signal //for (int i = 0; i < nSamples; i++) { // Serial.println(sampleBuffer[i]); //} // Try to decode any "ggwave" data: auto tStart = millis(); if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) { Serial.println("Failed to decode"); } auto tEnd = millis(); if (++niter % 10 == 0) { // print the time it took the last decode() call to complete // should be smaller than samplesPerFrame/sampleRate seconds // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms Serial.println(tEnd - tStart); if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { Serial.println(F("Warning: decode() took too long to execute!")); } } // Check if we have successfully decoded any data: int nr = ggwave.rxTakeData(result); if (nr > 0) { Serial.println(tEnd - tStart); Serial.print(F("Received data with length ")); Serial.print(nr); // should be equal to p.payloadLength Serial.println(F(" bytes:")); Serial.println((char *) result.data()); tLastReceive = tEnd; } #ifdef DISPLAY_OUTPUT const auto t = millis(); static GGWave::Spectrum rxSpectrum; if (ggwave.rxTakeSpectrum(rxSpectrum) && t > 2000) { const bool isNew = t - tLastReceive < 2000; if (isNew) { digitalWrite(kPinLED0, HIGH); } else { digitalWrite(kPinLED0, LOW); } display.clearDisplay(); display.setTextSize(isNew ? 2 : 1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println((char *) result.data()); const int nBin0 = 16; const int nBins = 64; const int dX = SCREEN_WIDTH/nBins; float smax = 0.0f; for (int x = 0; x < nBins; x++) { smax = std::max(smax, rxSpectrum[nBin0 + x]); } smax = smax == 0.0f ? 1.0f : 1.0f/smax; const float h = isNew ? 0.25f: 0.75f; for (int x = 0; x < nBins; x++) { const int x0 = x*dX; const int x1 = x0 + dX; const int y = (int) (h*SCREEN_HEIGHT*(rxSpectrum[nBin0 + x]*smax)); display.fillRect(x0, SCREEN_HEIGHT - y, dX, y, SSD1306_WHITE); } display.display(); } #endif } ================================================ FILE: examples/ggwave-cli/CMakeLists.txt ================================================ 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} ) ================================================ FILE: examples/ggwave-cli/README.md ================================================ # ggwave-cli A basic command line tool for sending and receiving `ggwave` data. ![ggwave-cli](https://i.imgur.com/fhNggnq.png) ================================================ FILE: examples/ggwave-cli/main.cpp ================================================ #include "ggwave/ggwave.h" #include "ggwave-common.h" #include "ggwave-common-sdl2.h" #include #include #include #include #include #include int main(int argc, char** argv) { printf("Usage: %s [-cN] [-pN] [-tN] [-lN]\n", argv[0]); printf(" -cN - select capture device N\n"); printf(" -pN - select playback device N\n"); printf(" -tN - transmission protocol\n"); printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); printf(" -d - use Direct Sequence Spread (DSS)\n"); printf(" -v - print generated tones on resend\n"); printf("\n"); const auto argm = parseCmdArguments(argc, argv); const int captureId = argm.count("c") == 0 ? 0 : std::stoi(argm.at("c")); const int playbackId = argm.count("p") == 0 ? 0 : std::stoi(argm.at("p")); const int txProtocolId = argm.count("t") == 0 ? 1 : std::stoi(argm.at("t")); const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); const bool useDSS = argm.count("d") > 0; const bool printTones = argm.count("v") > 0; if (GGWave_init(playbackId, captureId, payloadLength, 0.0f, useDSS) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); return -1; } auto ggWave = GGWave_instance(); printf("Available Tx protocols:\n"); const auto & protocols = GGWave::Protocols::kDefault(); for (int i = 0; i < (int) protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled == false) { continue; } printf(" %d - %s\n", i, protocol.name); } if (txProtocolId < 0) { fprintf(stderr, "Unknown Tx protocol %d\n", txProtocolId); return -3; } printf("Selecting Tx protocol %d\n", txProtocolId); std::mutex mutex; std::thread inputThread([&]() { std::string inputOld = ""; while (true) { std::string input; printf("Enter text: "); fflush(stdout); getline(std::cin, input); if (input.empty()) { printf("Re-sending ...\n"); input = inputOld; if (printTones) { printf("Printing generated waveform tones (Hz):\n"); const auto & protocol = protocols[txProtocolId]; const auto tones = ggWave->txTones(); for (int i = 0; i < (int) tones.size(); ++i) { if (tones[i] < 0) { printf(" - end tx\n"); continue; } const auto freq_hz = (protocol.freqStart + tones[i])*ggWave->hzPerSample(); printf(" - tone %3d: %f\n", i, freq_hz); } } } else { printf("Sending ...\n"); } { std::lock_guard lock(mutex); ggWave->init(input.size(), input.data(), GGWave::TxProtocolId(txProtocolId), 10); } inputOld = input; } }); while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); { std::lock_guard lock(mutex); GGWave_mainLoop(); } } inputThread.join(); GGWave_deinit(); SDL_CloseAudio(); SDL_Quit(); return 0; } ================================================ FILE: examples/ggwave-common-sdl2.cpp ================================================ #include "ggwave-common-sdl2.h" #include "ggwave-common.h" #include "ggwave/ggwave.h" #include #include #include #ifdef __EMSCRIPTEN__ #include #else #define EMSCRIPTEN_KEEPALIVE #endif namespace { std::string g_defaultCaptureDeviceName = ""; SDL_AudioDeviceID g_devIdInp = 0; SDL_AudioDeviceID g_devIdOut = 0; SDL_AudioSpec g_obtainedSpecInp; SDL_AudioSpec g_obtainedSpecOut; std::shared_ptr g_ggWave = nullptr; } // JS interface extern "C" { EMSCRIPTEN_KEEPALIVE int sendData(int textLength, const char * text, int protocolId, int volume) { g_ggWave->init(textLength, text, GGWave::TxProtocolId(protocolId), volume); return 0; } EMSCRIPTEN_KEEPALIVE int getText(char * text) { std::copy(g_ggWave->rxData().begin(), g_ggWave->rxData().end(), text); return 0; } EMSCRIPTEN_KEEPALIVE float sampleRate() { return g_ggWave->sampleRateInp(); } EMSCRIPTEN_KEEPALIVE int framesToRecord() { return g_ggWave->rxFramesToRecord(); } EMSCRIPTEN_KEEPALIVE int framesLeftToRecord() { return g_ggWave->rxFramesLeftToRecord(); } EMSCRIPTEN_KEEPALIVE int framesToAnalyze() { return g_ggWave->rxFramesToAnalyze(); } EMSCRIPTEN_KEEPALIVE int framesLeftToAnalyze() { return g_ggWave->rxFramesLeftToAnalyze(); } EMSCRIPTEN_KEEPALIVE int hasDeviceOutput() { return g_devIdOut; } EMSCRIPTEN_KEEPALIVE int hasDeviceCapture() { return g_devIdInp; } EMSCRIPTEN_KEEPALIVE int doInit() { return GGWave_init(-1, -1); } } void GGWave_setDefaultCaptureDeviceName(std::string name) { g_defaultCaptureDeviceName = std::move(name); } bool GGWave_init( const int playbackId, const int captureId, const int payloadLength, const float sampleRateOffset, const bool useDSS) { if (g_devIdInp && g_devIdOut) { return false; } if (g_devIdInp == 0 && g_devIdOut == 0) { 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)); } } } bool reinit = false; if (g_devIdOut == 0) { printf("Initializing playback ...\n"); SDL_AudioSpec playbackSpec; SDL_zero(playbackSpec); playbackSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset; playbackSpec.format = AUDIO_S16SYS; playbackSpec.channels = 1; playbackSpec.samples = 16*1024; playbackSpec.callback = NULL; SDL_zero(g_obtainedSpecOut); if (playbackId >= 0) { printf("Attempt to open playback device %d : '%s' ...\n", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE)); g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } else { printf("Attempt to open default playback device ...\n"); g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } if (!g_devIdOut) { printf("Couldn't open an audio device for playback: %s!\n", SDL_GetError()); g_devIdOut = 0; } else { printf("Obtained spec for output device (SDL Id = %d):\n", g_devIdOut); printf(" - Sample rate: %d (required: %d)\n", g_obtainedSpecOut.freq, playbackSpec.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecOut.format, playbackSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecOut.channels, playbackSpec.channels); printf(" - Samples per frame: %d (required: %d)\n", g_obtainedSpecOut.samples, playbackSpec.samples); if (g_obtainedSpecOut.format != playbackSpec.format || g_obtainedSpecOut.channels != playbackSpec.channels || g_obtainedSpecOut.samples != playbackSpec.samples) { g_devIdOut = 0; SDL_CloseAudio(); fprintf(stderr, "Failed to initialize playback SDL_OpenAudio!"); return false; } reinit = true; } } if (g_devIdInp == 0) { SDL_AudioSpec captureSpec; captureSpec = g_obtainedSpecOut; captureSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset; captureSpec.format = AUDIO_F32SYS; captureSpec.samples = 1024; SDL_zero(g_obtainedSpecInp); if (captureId >= 0) { printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE)); g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } else { printf("Attempt to open default capture device ...\n"); g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } if (!g_devIdInp) { printf("Couldn't open an audio device for capture: %s!\n", SDL_GetError()); g_devIdInp = 0; } else { printf("Obtained spec for input device (SDL Id = %d):\n", g_devIdInp); printf(" - Sample rate: %d\n", g_obtainedSpecInp.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecInp.format, captureSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecInp.channels, captureSpec.channels); printf(" - Samples per frame: %d\n", g_obtainedSpecInp.samples); reinit = true; } } GGWave::SampleFormat sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED; GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED; switch (g_obtainedSpecInp.format) { case AUDIO_U8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U8; break; case AUDIO_S8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I8; break; case AUDIO_U16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U16; break; case AUDIO_S16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break; case AUDIO_S32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break; case AUDIO_F32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break; } switch (g_obtainedSpecOut.format) { case AUDIO_U8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; break; case AUDIO_S8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8; break; case AUDIO_U16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break; case AUDIO_S16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break; case AUDIO_S32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break; case AUDIO_F32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break; break; } if (reinit) { GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX; if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS; g_ggWave = std::make_shared(GGWave::Parameters { payloadLength, (float) g_obtainedSpecInp.freq, (float) g_obtainedSpecOut.freq, GGWave::kDefaultSampleRate, GGWave::kDefaultSamplesPerFrame, GGWave::kDefaultSoundMarkerThreshold, sampleFormatInp, sampleFormatOut, mode, }); } return true; } std::shared_ptr GGWave_instance() { return g_ggWave; } void GGWave_reset(void * parameters) { g_ggWave = std::make_shared(*(GGWave::Parameters *)(parameters)); } bool GGWave_mainLoop() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } if (g_ggWave->txHasData() == 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->samplesPerFrame()*g_ggWave->sampleSizeOut()) { SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE); const int nHave = (int) SDL_GetQueuedAudioSize(g_devIdInp); const int nNeed = g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeInp(); if (::getTime_ms(tLastNoData, tNow) > 500.0f && nHave >= nNeed) { static std::vector dataInp(nNeed); SDL_DequeueAudio(g_devIdInp, dataInp.data(), nNeed); if (g_ggWave->decode(dataInp.data(), dataInp.size()) == false) { fprintf(stderr, "Warning: failed to decode input data!\n"); } if (nHave > 32*nNeed) { fprintf(stderr, "Warning: slow processing, clearing queued audio buffer of %d bytes ...\n", SDL_GetQueuedAudioSize(g_devIdInp)); SDL_ClearQueuedAudio(g_devIdInp); } } else { SDL_ClearQueuedAudio(g_devIdInp); } } else { tLastNoData = tNow; } } else { SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE); SDL_PauseAudioDevice(g_devIdInp, SDL_TRUE); const auto nBytes = g_ggWave->encode(); SDL_QueueAudio(g_devIdOut, g_ggWave->txWaveform(), nBytes); } return true; } bool GGWave_deinit() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } g_ggWave.reset(); SDL_PauseAudioDevice(g_devIdInp, 1); SDL_CloseAudioDevice(g_devIdInp); SDL_PauseAudioDevice(g_devIdOut, 1); SDL_CloseAudioDevice(g_devIdOut); g_devIdInp = 0; g_devIdOut = 0; return true; } ================================================ FILE: examples/ggwave-common-sdl2.h ================================================ #pragma once #include #include class GGWave; // GGWave helpers void GGWave_setDefaultCaptureDeviceName(std::string name); bool GGWave_init(const int playbackId, const int captureId, const int payloadLength = -1, const float sampleRateOffset = 0, const bool useDSS = false); std::shared_ptr GGWave_instance(); void GGWave_reset(void * parameters); bool GGWave_mainLoop(); bool GGWave_deinit(); ================================================ FILE: examples/ggwave-common.cpp ================================================ #include "ggwave-common.h" #if !defined(_WIN32) #include #include #endif #include #include #include namespace { void dummy() {} } std::map parseCmdArguments(int argc, char ** argv) { int last = argc; std::map 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; } std::vector readFile(const char* filename) { std::ifstream file(filename, std::ios::binary); if (!file.is_open() || !file.good()) return {}; file.unsetf(std::ios::skipws); std::streampos fileSize; file.seekg(0, std::ios::end); fileSize = file.tellg(); file.seekg(0, std::ios::beg); std::vector vec; vec.reserve(fileSize); vec.insert(vec.begin(), std::istream_iterator(file), std::istream_iterator()); return vec; } std::string getBinaryPath() { #if defined(__EMSCRIPTEN__) || defined(_WIN32) return ""; #else std::string result; void* p = reinterpret_cast(dummy); Dl_info info; dladdr(p, &info); if (*info.dli_fname == '/') { result = info.dli_fname; } else { char buff[2048]; auto len = readlink("/proc/self/exe", buff, sizeof(buff) - 1); if (len > 0) { buff[len] = 0; result = buff; } } auto slash = result.rfind('/'); if (slash != std::string::npos) { result.erase(slash + 1); } return result; #endif } ================================================ FILE: examples/ggwave-common.h ================================================ #pragma once #include #include #include #include // some basic helper methods for the examples template float getTime_ms(const T & tStart, const T & tEnd) { return ((float)(std::chrono::duration_cast(tEnd - tStart).count()))/1000.0; } std::vector readFile(const char* filename); std::string getBinaryPath(); std::map parseCmdArguments(int argc, char ** argv); ================================================ FILE: examples/ggwave-from-file/CMakeLists.txt ================================================ set(TARGET ggwave-from-file) add_executable(${TARGET} main.cpp) target_include_directories(${TARGET} PRIVATE .. ) target_link_libraries(${TARGET} PRIVATE ggwave ggwave-common ${CMAKE_THREAD_LIBS_INIT} ) install(TARGETS ${TARGET} RUNTIME DESTINATION bin) ================================================ FILE: examples/ggwave-from-file/README.md ================================================ ## ggwave-from-file Decode GGWave messages from an input WAV file ``` Usage: ./bin/ggwave-from-file [-lN] [-d] -lN - fixed payload length of size N, N in [1, 64] -d - use Direct Sequence Spread (DSS) ``` ### Examples - Basic usage with auto-detection of frequency and speed: ```bash echo "Hello world" | ./bin/ggwave-to-file > example.wav ./bin/ggwave-from-file example.wav Usage: ./bin/ggwave-from-file audio.wav [-lN] [-d] -lN - fixed payload length of size N, N in [1, 64] -d - use Direct Sequence Spread (DSS) [+] Number of channels: 1 [+] Sample rate: 48000 [+] Bits per sample: 16 [+] Total samples: 69632 [+] Decoding .. [+] Decoded message with length 11: 'Hello world' [+] Done ``` - Decoding fixed-length payload with DSS enabled: ```bash echo "Hello world" | ./bin/ggwave-to-file -l16 -d > example.wav ./bin/ggwave-from-file example.wav -l16 -d ``` ================================================ FILE: examples/ggwave-from-file/main.cpp ================================================ #include "ggwave/ggwave.h" #define DR_WAV_IMPLEMENTATION #include "dr_wav.h" #include "ggwave-common.h" #include #include #include int main(int argc, char** argv) { fprintf(stderr, "Usage: %s audio.wav [-lN] [-d]\n", argv[0]); fprintf(stderr, " -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); fprintf(stderr, " -d - use Direct Sequence Spread (DSS)\n"); fprintf(stderr, "\n"); if (argc < 2) { return -1; } const auto argm = parseCmdArguments(argc, argv); if (argm.count("h") > 0) { return 0; } const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); const bool useDSS = argm.count("d") > 0; drwav wav; if (!drwav_init_file(&wav, argv[1], nullptr)) { fprintf(stderr, "Failed to open WAV file\n"); return -4; } if (wav.channels != 1) { fprintf(stderr, "Only mono WAV files are supported\n"); return -5; } // Read WAV samples into a buffer // Add 3 seconds of silence at the end const size_t samplesSilence = 3*wav.sampleRate; const size_t samplesCount = wav.totalPCMFrameCount; const size_t samplesSize = wav.bitsPerSample/8; size_t samplesTotal = samplesCount + samplesSilence; std::vector samples(samplesTotal*samplesSize*wav.channels, 0); printf("[+] Number of channels: %d\n", wav.channels); printf("[+] Sample rate: %d\n", wav.sampleRate); printf("[+] Bits per sample: %d\n", wav.bitsPerSample); printf("[+] Total samples: %zu\n", samplesCount); printf("[+] Decoding .. \n\n"); GGWave::Parameters parameters = GGWave::getDefaultParameters(); parameters.payloadLength = payloadLength; parameters.sampleRateInp = wav.sampleRate; parameters.operatingMode = GGWAVE_OPERATING_MODE_RX; if (useDSS) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS; switch (wav.bitsPerSample) { case 16: drwav_read_pcm_frames_s16(&wav, samplesCount, reinterpret_cast(samples.data())); if (wav.channels > 1) { for (size_t i = 0; i < samplesCount; ++i) { int16_t sample = 0; for (size_t j = 0; j < wav.channels; ++j) { sample += reinterpret_cast(samples.data())[i*wav.channels + j]; } reinterpret_cast(samples.data())[i] = sample / wav.channels; } } parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break; case 32: drwav_read_pcm_frames_f32(&wav, samplesCount, reinterpret_cast(samples.data())); if (wav.channels > 1) { for (size_t i = 0; i < samplesCount; ++i) { float sample = 0.0f; for (size_t j = 0; j < wav.channels; ++j) { sample += reinterpret_cast(samples.data())[i*wav.channels + j]; } reinterpret_cast(samples.data())[i] = sample / wav.channels; } } parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break; default: fprintf(stderr, "Unsupported WAV format\n"); return -6; } GGWave ggWave(parameters); ggWave.setLogFile(nullptr); GGWave::TxRxData data; auto ptr = samples.data(); while ((int) samplesTotal >= parameters.samplesPerFrame) { if (ggWave.decode(ptr, parameters.samplesPerFrame*samplesSize*wav.channels) == false) { fprintf(stderr, "Failed to decode the waveform in the WAV file\n"); return -7; } ptr += parameters.samplesPerFrame*samplesSize*wav.channels; samplesTotal -= parameters.samplesPerFrame; const int n = ggWave.rxTakeData(data); if (n > 0) { printf("[+] Decoded message with length %d: '", n); for (auto i = 0; i < n; ++i) { printf("%c", data[i]); } printf("'\n"); } } printf("\n[+] Done\n"); return 0; } ================================================ FILE: examples/ggwave-js/CMakeLists.txt ================================================ set(TARGET ggwave-js) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/ggwave.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ggwave.js COPYONLY) ================================================ FILE: examples/ggwave-js/README.md ================================================ # ggwave-js Live demo: https://ggwave-js.ggerganov.com ================================================ FILE: examples/ggwave-js/index-tmpl.html ================================================ ggwave : javascript example
Minimal ggwave example using Javascript bindings

Tx Data:



Rx data:



| Build time: @GIT_DATE@ | Commit hash: @GIT_SHA1@ | Commit subject: @GIT_COMMIT_SUBJECT@ | Source Code |
================================================ FILE: examples/ggwave-py/README.md ================================================ ## ggwave-py Python examples using the `ggwave` python package ### Install ```bash pip install ggwave ``` Some of the packages depend on `pyaudio`. To install it: ```bash sudo apt-get install portaudio19-dev python-pyaudio python3-pyaudio pip install pyaudio ``` ================================================ FILE: examples/ggwave-py/receive.py ================================================ import ggwave import pyaudio p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, input=True, frames_per_buffer=1024) print('Listening ... Press Ctrl+C to stop') instance = ggwave.init() try: while True: data = stream.read(1024, exception_on_overflow=False) res = ggwave.decode(instance, data) if (not res is None): try: print('Received text: ' + res.decode("utf-8")) except: pass except KeyboardInterrupt: pass ggwave.free(instance) stream.stop_stream() stream.close() p.terminate() ================================================ FILE: examples/ggwave-py/send.py ================================================ import ggwave import pyaudio p = pyaudio.PyAudio() # generate audio waveform for string "hello python" waveform = ggwave.encode("hello python", protocolId = 1, volume = 20) print("Transmitting text 'hello python' ...") stream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, output=True, frames_per_buffer=4096) stream.write(waveform, len(waveform)//4) stream.stop_stream() stream.close() p.terminate() ================================================ FILE: examples/ggwave-rx/CMakeLists.txt ================================================ set(TARGET ggwave-rx) add_executable(${TARGET} main.cpp) target_include_directories(${TARGET} PRIVATE .. ${SDL2_INCLUDE_DIRS} ) target_link_libraries(${TARGET} PRIVATE ggwave ggwave-common ggwave-common-sdl2 ) ================================================ FILE: examples/ggwave-rx/main.cpp ================================================ #include "ggwave/ggwave.h" #include "ggwave-common.h" #include "ggwave-common-sdl2.h" #include #include #include int main(int argc, char** argv) { printf("Usage: %s [-cN]\n", argv[0]); printf(" -cN - select capture device N\n"); printf("\n"); auto argm = parseCmdArguments(argc, argv); int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]); if (GGWave_init(0, captureId) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); return -1; } while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); GGWave_mainLoop(); } GGWave_deinit(); SDL_CloseAudio(); SDL_Quit(); return 0; } ================================================ FILE: examples/ggwave-to-file/CMakeLists.txt ================================================ set(TARGET ggwave-to-file) add_executable(${TARGET} main.cpp) target_include_directories(${TARGET} PRIVATE .. ) target_link_libraries(${TARGET} PRIVATE ggwave ggwave-common ${CMAKE_THREAD_LIBS_INIT} ) ================================================ FILE: examples/ggwave-to-file/README.md ================================================ ## ggwave-to-file Output a generated waveform to an uncompressed WAV file. ``` Usage: ./bin/ggwave-to-file [-vN] [-sN] [-pN] [-lN] [-d] -vN - output volume, N in (0, 100], (default: 50) -sN - output sample rate, N in [6000, 96000], (default: 48000) -pN - select the transmission protocol id (default: 1) -lN - fixed payload length of size N, N in [1, 16] -d - use Direct Sequence Spread (DSS) Available protocols: 0 - Normal 1 - Fast 2 - Fastest 3 - [U] Normal 4 - [U] Fast 5 - [U] Fastest 6 - [DT] Normal 7 - [DT] Fast 8 - [DT] Fastest 9 - [MT] Normal 10 - [MT] Fast 11 - [MT] Fastest ``` ### Examples - Generate waveform with default parameters ```bash echo "Hello world!" | ./bin/ggwave-to-file > example.wav ``` - Generate waveform at 24 kHz sample rate ```bash echo "Hello world!" | ./bin/ggwave-to-file -s24000 > example.wav ``` - Generate ultrasound waveform using the `[U] Fast` protocol ```bash echo "Hello world!" | ./bin/ggwave-to-file -p4 > example.wav ``` - Use fixed-length encoding (i.e. no sound markers) ```bash echo "Hello world!" | ./bin/ggwave-to-file -l12 > example.wav ``` - Use DSS when encoding the text ```bash echo "aaaaaaaa" | ./bin/ggwave-to-file -l8 -d > example.wav ``` - Play the generated waveform directly through the speakers ```bash echo "Hello world!" | ./bin/ggwave-to-file | play --ignore-length -t wav - ``` ## HTTP service Based on this tool, there is an HTTP service available on the following link: https://ggwave-to-file.ggerganov.com/ You can use it to query audio waveforms by specifying the text message as a GET parameter to the HTTP request. Here are a few examples: ### terminal ```bash # audible example curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!' --output hello.wav # ultrasound example curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!&p=4' --output hello.wav ``` ### browser - Audible example https://ggwave-to-file.ggerganov.com/?m=Hello%20world%21 - Ultrasound example https://ggwave-to-file.ggerganov.com/?m=Hello%20world%21&p=4 ### python ```python from typing import Dict, Union import requests import wave def ggwave(message: str, file: str, protocolId: int = 1, sampleRate: float = 48000, volume: int = 50, payloadLength: int = -1, useDSS: int = 0) -> None: url = 'https://ggwave-to-file.ggerganov.com/' params: Dict[str, Union[str, int, float] = { 'm': message, # message to encode 'p': protocolId, # transmission protocol to use 's': sampleRate, # output sample rate 'v': volume, # output volume 'l': payloadLength, # if positive - use fixed-length encoding 'd': useDSS, # if positive - use DSS } response = requests.get(url, params=params) if response == '' or b'Usage: ggwave-to-file' in response.content: raise SyntaxError('Request failed') with wave.open(file, 'wb') as f: f.setnchannels(1) f.setframerate(sampleRate) f.setsampwidth(2) f.writeframes(response.context) ``` ... ```python # query waveform from server and write to file ggwave("Hello world!", "hello_world.wav") ``` ================================================ FILE: examples/ggwave-to-file/ggwave-to-file-local.py ================================================ import ggwave import wave import numpy as np # Parameters volume_ = 20 sample_rate_ = 48000 filename = "output.wav" # Generate audio waveform for string "hello python" waveform = ggwave.encode("hello python", protocolId=5, volume=volume_) # Convert byte data into float32 waveform_float32 = np.frombuffer(waveform, dtype=np.float32) # Normalize the float32 data to the range of int16 waveform_int16 = np.int16(waveform_float32 * 32767) # Save the waveform to a .wav file with wave.open(filename, "wb") as wf: wf.setnchannels(1) # mono audio wf.setsampwidth(2) # 2 bytes per sample (16-bit PCM) wf.setframerate(sample_rate_) # sample rate wf.writeframes(waveform_int16.tobytes()) # write the waveform as bytes ================================================ FILE: examples/ggwave-to-file/ggwave-to-file.php ================================================ 0) $cmd .= " -d"; } $descriptorspec = array( 0 => array("pipe", "r"), //1 => array("pipe", "w"), 2 => array("pipe", "w"), ); $path_wav = tempnam("/tmp", "ggwave"); $cmd .= " > $path_wav"; $process = proc_open($cmd, $descriptorspec, $pipes); if (is_resource($process)) { $message = $_GET['m']; fwrite($pipes[0], $message); fclose($pipes[0]); //$result = stream_get_contents($pipes[1]); //fclose($pipes[1]); $log = stream_get_contents($pipes[2]); fclose($pipes[2]); $return_value = proc_close($process); //exec("ffmpeg -i ".$path_wav." ".$path_wav); $result = file_get_contents($path_wav); $size = filesize($path_wav); if ($size == 0) { header('Content-type: text/plain'); echo $log; } else { //header("Content-Type: audio/wav"); header("Content-Type: ". mime_content_type($path_wav)); header("Content-Length: $size"); header("Accept-Ranges: bytes"); header('Content-Disposition: attachment; filename="output.wav"'); header("Content-Transfer-Encoding: binary"); header("Content-Range: bytes 0-".$size."/".$size); echo $result; } } unlink($path_wav); ?> ================================================ FILE: examples/ggwave-to-file/ggwave-to-file.py ================================================ from typing import Dict, Union import requests import wave def ggwave(message: str, file: str, protocolId: int = 1, sampleRate: float = 48000, volume: int = 50, payloadLength: int = -1, useDSS: int = 0) -> None: url = 'https://ggwave-to-file.ggerganov.com/' params: Dict[str, Union[str, int, float]] = { 'm': message, # message to encode 'p': protocolId, # transmission protocol to use 's': sampleRate, # output sample rate 'v': volume, # output volume 'l': payloadLength, # if positive - use fixed-length encoding 'd': useDSS, # if positive - use DSS } response = requests.get(url, params=params) if response == '' or b'Usage: ggwave-to-file' in response.content: raise SyntaxError('Request failed') with wave.open(file, 'wb') as f: f.setnchannels(1) f.setframerate(sampleRate) f.setsampwidth(2) f.writeframes(response.content) if __name__ == "__main__": ggwave("Hello world!", "hello_world.wav") ================================================ FILE: examples/ggwave-to-file/main.cpp ================================================ #include "ggwave/ggwave.h" #define DR_WAV_IMPLEMENTATION #include "dr_wav.h" #include "ggwave-common.h" #include #include #include int main(int argc, char** argv) { #if defined(_WIN32) const std::string & defaultFile = "audio.wav"; #else const std::string & defaultFile = "/dev/stdout"; #endif fprintf(stderr, "Usage: %s [-vN] [-sN] [-pN] [-lN] [-d]\n", argv[0]); fprintf(stderr, " -fF - output filename, (default: %s)\n", defaultFile.c_str()); fprintf(stderr, " -vN - output volume, N in (0, 100], (default: 50)\n"); fprintf(stderr, " -sN - output sample rate, N in [%d, %d], (default: %d)\n", (int) GGWave::kSampleRateMin, (int) GGWave::kSampleRateMax, (int) GGWave::kDefaultSampleRate); fprintf(stderr, " -pN - select the transmission protocol id (default: 1)\n"); fprintf(stderr, " -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); fprintf(stderr, " -d - use Direct Sequence Spread (DSS)\n"); fprintf(stderr, "\n"); fprintf(stderr, " Available protocols:\n"); const auto & protocols = GGWave::Protocols::kDefault(); for (int i = 0; i < (int) protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled == false) { continue; } fprintf(stderr, " %d - %s\n", i, protocol.name); } fprintf(stderr, "\n"); if (argc < 1) { return -1; } const auto argm = parseCmdArguments(argc, argv); if (argm.count("h") > 0) { return 0; } const int volume = argm.count("v") == 0 ? 50 : std::stoi(argm.at("v")); const std::string & file = argm.count("f") == 0 ? defaultFile : argm.at("f"); const float sampleRateOut = argm.count("s") == 0 ? GGWave::kDefaultSampleRate : std::stof(argm.at("s")); const int protocolId = argm.count("p") == 0 ? 1 : std::stoi(argm.at("p")); const int payloadLength = argm.count("l") == 0 ? -1 : std::stoi(argm.at("l")); const bool useDSS = argm.count("d") > 0; if (volume <= 0 || volume > 100) { fprintf(stderr, "Invalid volume\n"); return -1; } if (sampleRateOut < GGWave::kSampleRateMin || sampleRateOut > GGWave::kSampleRateMax) { fprintf(stderr, "Invalid sample rate: %g\n", sampleRateOut); return -1; } if (protocolId < 0 || protocolId >= (int) protocols.size()) { fprintf(stderr, "Invalid transmission protocol id\n"); return -1; } if (protocols[protocolId].enabled == false) { fprintf(stderr, "Protocol %d is not enabled\n", protocolId); return -1; } fprintf(stderr, "Enter a text message:\n"); std::string message; std::getline(std::cin, message); if (message.size() == 0) { fprintf(stderr, "Invalid message: size = 0\n"); return -2; } if (message.size() > 140) { fprintf(stderr, "Invalid message: size > 140\n"); return -3; } fprintf(stderr, "Generating waveform for message '%s' ...\n", message.c_str()); GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX; if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS; GGWave ggWave({ payloadLength, GGWave::kDefaultSampleRate, sampleRateOut, GGWave::kDefaultSampleRate, GGWave::kDefaultSamplesPerFrame, GGWave::kDefaultSoundMarkerThreshold, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_I16, mode, }); ggWave.init(message.size(), message.data(), GGWave::TxProtocolId(protocolId), volume); const auto nBytes = ggWave.encode(); if (nBytes == 0) { fprintf(stderr, "Failed to generate waveform!\n"); return -4; } std::vector bufferPCM(nBytes); std::memcpy(bufferPCM.data(), ggWave.txWaveform(), nBytes); fprintf(stderr, "Output file = %s\n", file.c_str()); fprintf(stderr, "Output size = %d bytes\n", (int) bufferPCM.size()); drwav_data_format format; format.container = drwav_container_riff; format.format = DR_WAVE_FORMAT_PCM; format.channels = 1; format.sampleRate = sampleRateOut; format.bitsPerSample = 16; fprintf(stderr, "Writing WAV data ...\n"); drwav wav; drwav_init_file_write(&wav, file.c_str(), &format, NULL); drwav_uint64 framesWritten = drwav_write_pcm_frames(&wav, bufferPCM.size()/2, bufferPCM.data()); fprintf(stderr, "WAV frames written = %d\n", (int) framesWritten); drwav_uninit(&wav); return 0; } ================================================ FILE: examples/ggwave-wasm/CMakeLists.txt ================================================ set(TARGET ggwave-wasm) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${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 ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/main.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/main.js COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/plucky.mp3 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/plucky.mp3 COPYONLY) ================================================ FILE: examples/ggwave-wasm/build_timestamp-tmpl.h ================================================ static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)"; ================================================ FILE: examples/ggwave-wasm/index-tmpl.html ================================================ ggwave : emscripten example

ggwave

Open this page on multiple devices (computers, phones, tablets, etc.).
Press the init button and broadcast some text. Make sure your speakers and microphones are enabled.

Output:

Capture:

Browser:



Standard output:

Downloading...
| Build time: @GIT_DATE@ | Commit hash: @GIT_SHA1@ | Commit subject: @GIT_COMMIT_SUBJECT@ |
================================================ FILE: examples/ggwave-wasm/main.cpp ================================================ #include "build_timestamp.h" #include "ggwave-common-sdl2.h" #include "emscripten/emscripten.h" void update() { GGWave_mainLoop(); } int main(int , char** argv) { printf("Build time: %s\n", BUILD_TIMESTAMP); printf("Press the Init button to start\n"); if (argv[1]) { GGWave_setDefaultCaptureDeviceName(argv[1]); } emscripten_set_main_loop(update, 60, 1); return 0; } ================================================ FILE: examples/ggwave-wasm/main.js ================================================ function transmitText(sText) { var r = new Uint8Array(256); for (var i = 0; i < sText.length; ++i) { r[i] = sText.charCodeAt(i); } var buffer = Module._malloc(256); Module.writeArrayToMemory(r, buffer, 256); Module._sendData(sText.length, buffer, protocolId, volume); Module._free(buffer); } var firstTimeFail = false; var peerInfo = document.querySelector('a#peer-info'); function updatePeerInfo() { if (typeof Module === 'undefined') return; var framesLeftToRecord = Module._getFramesLeftToRecord(); var framesToRecord = Module._getFramesToRecord(); var framesLeftToAnalyze = Module._getFramesLeftToAnalyze(); var framesToAnalyze = Module._getFramesToAnalyze(); if (framesToAnalyze > 0) { peerInfo.innerHTML= "Analyzing Rx data: "; peerReceive.innerHTML= ""; } else if (framesLeftToRecord > Math.max(0, 0.05*framesToRecord)) { firstTimeFail = true; peerInfo.innerHTML= "Transmission in progress: "; } else if (framesToRecord > 0) { peerInfo.innerHTML= "Analyzing Rx data ..."; } else if (framesToRecord == 0) { peerInfo.innerHTML= "

Listening for waves ...

"; } else if (framesToRecord == -1) { if (firstTimeFail) { playSound("/media/case-closed"); firstTimeFail = false; } peerInfo.innerHTML= "

Failed to decode Rx data

"; } } function updateRx() { if (typeof Module === 'undefined') return; Module._getText(bufferRx); var result = ""; for (var i = 0; i < 140; ++i){ result += (String.fromCharCode((Module.HEAPU8)[bufferRx + i])); brx[i] = (Module.HEAPU8)[bufferRx + i]; } document.getElementById('rxData').innerHTML = result; } ================================================ FILE: examples/ggwave-wasm/style.css ================================================ body { margin: 0; background-color: white; -webkit-font-smoothing: subpixel-antialiased; font-smoothing: subpixel-antialiased; } #screen { margin: 0; padding: 0; font-size: 13px; height: 100%; font: sans-serif; } .no-sel { -moz-user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; -ms-user-select:none; user-select:none; -o-user-select:none; } .cell { pointer-events: none; } .cell-version { padding-left: 4px; padding-top: 0.5em; text-align: left; display: inline-block; float: left; color: rgba(0, 0, 0, 0.75); } .cell-about { padding-right: 24px; padding-top: 0.5em; text-align: right; display: inline-block; float: right; } .nav-link { text-decoration: none; color: rgba(0, 0, 0, 1.0); } #main-container { font-size:12px; font-family: monospace; } textarea { font-size:12px; font-family: monospace; } .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten { text-align: center; } div.emscripten_border { border: 1px solid black; } canvas.emscripten { border: 0px none; background-color: black; } .spinner { height: 30px; width: 30px; margin: 0; margin-top: 20px; margin-left: 20px; display: inline-block; vertical-align: top; -webkit-animation: rotation .8s linear infinite; -moz-animation: rotation .8s linear infinite; -o-animation: rotation .8s linear infinite; animation: rotation 0.8s linear infinite; border-left: 5px solid rgb(235, 235, 235); border-right: 5px solid rgb(235, 235, 235); border-bottom: 5px solid rgb(235, 235, 235); border-top: 5px solid rgb(120, 120, 120); border-radius: 100%; background-color: rgb(189, 215, 46); } @-webkit-keyframes rotation { from {-webkit-transform: rotate(0deg);} to {-webkit-transform: rotate(360deg);} } @-moz-keyframes rotation { from {-moz-transform: rotate(0deg);} to {-moz-transform: rotate(360deg);} } @-o-keyframes rotation { from {-o-transform: rotate(0deg);} to {-o-transform: rotate(360deg);} } @keyframes rotation { from {transform: rotate(0deg);} to {transform: rotate(360deg);} } #status { display: inline-block; vertical-align: top; margin-top: 30px; margin-left: 20px; font-weight: bold; color: rgb(120, 120, 120); } #progress { height: 20px; width: 30px; } #output { width: 800px; height: 200px; margin: 0 auto; margin-top: 10px; border-left: 0px; border-right: 0px; padding-left: 0px; padding-right: 0px; background-color: black; color: white; font-size:10px; font-family: 'Lucida Console', Monaco, monospace; outline: none; } .led-box { height: 30px; width: 25%; margin: 10px 0; float: left; } .led-box p { font-size: 12px; text-align: center; margin: 1em; } .led-red { margin: 0 auto; width: 12px; height: 12px; background-color: #F00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 12px; -webkit-animation: blinkRed 0.5s infinite; -moz-animation: blinkRed 0.5s infinite; -ms-animation: blinkRed 0.5s infinite; -o-animation: blinkRed 0.5s infinite; animation: blinkRed 0.5s infinite; } @-webkit-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-moz-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-ms-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-o-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } .led-yellow { margin: 0 auto; width: 12px; height: 12px; background-color: #FF0; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px; -webkit-animation: blinkYellow 1s infinite; -moz-animation: blinkYellow 1s infinite; -ms-animation: blinkYellow 1s infinite; -o-animation: blinkYellow 1s infinite; animation: blinkYellow 1s infinite; } @-webkit-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-moz-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-ms-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-o-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } .led-green { margin: 0 auto; width: 12px; height: 12px; background-color: #ABFF00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px; } .led-blue { margin: 0 auto; width: 18px; height: 18px; background-color: #24E0FF; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } td[Attributes Style] { text-align: -webkit-center; } td { display: table-cell; vertical-align: inherit; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { border-collapse: separate; border-spacing: 2px; } ================================================ FILE: examples/icons_font_awesome.h ================================================ // Generated by https://github.com/juliettef/IconFontCppHeaders script // GenerateIconFontCppHeaders.py for language C89 from // https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/metadata/icons.yml // for use with // https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-solid-900.ttf, // https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-regular-400.ttf, #pragma once #define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf" #define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf" #define ICON_MIN_FA 0xf000 #define ICON_MAX_FA 0xf897 #define ICON_FA_CLOUD_SHOWERS_HEAVY "\xEF\x9D\x80" #define ICON_FA_CHEVRON_CIRCLE_RIGHT "\xEF\x84\xB8" #define ICON_FA_DHARMACHAKRA "\xEF\x99\x95" #define ICON_FA_BROADCAST_TOWER "\xEF\x94\x99" #define ICON_FA_EXTERNAL_LINK_SQUARE_ALT "\xEF\x8D\xA0" #define ICON_FA_SMOKING "\xEF\x92\x8D" #define ICON_FA_PENCIL_ALT "\xEF\x8C\x83" #define ICON_FA_CHESS_BISHOP "\xEF\x90\xBA" #define ICON_FA_ICONS "\xEF\xA1\xAD" #define ICON_FA_TV "\xEF\x89\xAC" #define ICON_FA_CROP_ALT "\xEF\x95\xA5" #define ICON_FA_LIST "\xEF\x80\xBA" #define ICON_FA_BATTERY_QUARTER "\xEF\x89\x83" #define ICON_FA_TH "\xEF\x80\x8A" #define ICON_FA_RECYCLE "\xEF\x86\xB8" #define ICON_FA_SMILE "\xEF\x84\x98" #define ICON_FA_FAX "\xEF\x86\xAC" #define ICON_FA_DRAFTING_COMPASS "\xEF\x95\xA8" #define ICON_FA_USER_INJURED "\xEF\x9C\xA8" #define ICON_FA_SCREWDRIVER "\xEF\x95\x8A" #define ICON_FA_CROSSHAIRS "\xEF\x81\x9B" #define ICON_FA_HAND_PEACE "\xEF\x89\x9B" #define ICON_FA_FAN "\xEF\xA1\xA3" #define ICON_FA_GOPURAM "\xEF\x99\xA4" #define ICON_FA_CARET_UP "\xEF\x83\x98" #define ICON_FA_SCHOOL "\xEF\x95\x89" #define ICON_FA_FILE_PDF "\xEF\x87\x81" #define ICON_FA_USERS_COG "\xEF\x94\x89" #define ICON_FA_BALANCE_SCALE "\xEF\x89\x8E" #define ICON_FA_UPLOAD "\xEF\x82\x93" #define ICON_FA_LAPTOP_MEDICAL "\xEF\xA0\x92" #define ICON_FA_VENUS "\xEF\x88\xA1" #define ICON_FA_HEADING "\xEF\x87\x9C" #define ICON_FA_ARROW_DOWN "\xEF\x81\xA3" #define ICON_FA_BICYCLE "\xEF\x88\x86" #define ICON_FA_TIRED "\xEF\x97\x88" #define ICON_FA_COMMENT_MEDICAL "\xEF\x9F\xB5" #define ICON_FA_BACON "\xEF\x9F\xA5" #define ICON_FA_SYNC "\xEF\x80\xA1" #define ICON_FA_PAPER_PLANE "\xEF\x87\x98" #define ICON_FA_VOLLEYBALL_BALL "\xEF\x91\x9F" #define ICON_FA_RIBBON "\xEF\x93\x96" #define ICON_FA_SQUARE_ROOT_ALT "\xEF\x9A\x98" #define ICON_FA_SUN "\xEF\x86\x85" #define ICON_FA_FILE_POWERPOINT "\xEF\x87\x84" #define ICON_FA_MICROCHIP "\xEF\x8B\x9B" #define ICON_FA_TRASH_RESTORE_ALT "\xEF\xA0\xAA" #define ICON_FA_GRADUATION_CAP "\xEF\x86\x9D" #define ICON_FA_INFO_CIRCLE "\xEF\x81\x9A" #define ICON_FA_TAGS "\xEF\x80\xAC" #define ICON_FA_HAND_PAPER "\xEF\x89\x96" #define ICON_FA_EQUALS "\xEF\x94\xAC" #define ICON_FA_DIRECTIONS "\xEF\x97\xAB" #define ICON_FA_FILE_INVOICE "\xEF\x95\xB0" #define ICON_FA_SEARCH "\xEF\x80\x82" #define ICON_FA_BIBLE "\xEF\x99\x87" #define ICON_FA_WEIGHT_HANGING "\xEF\x97\x8D" #define ICON_FA_CALENDAR_TIMES "\xEF\x89\xB3" #define ICON_FA_GREATER_THAN_EQUAL "\xEF\x94\xB2" #define ICON_FA_SLIDERS_H "\xEF\x87\x9E" #define ICON_FA_EYE_SLASH "\xEF\x81\xB0" #define ICON_FA_BIRTHDAY_CAKE "\xEF\x87\xBD" #define ICON_FA_FEATHER_ALT "\xEF\x95\xAB" #define ICON_FA_DNA "\xEF\x91\xB1" #define ICON_FA_BASEBALL_BALL "\xEF\x90\xB3" #define ICON_FA_HOSPITAL "\xEF\x83\xB8" #define ICON_FA_COINS "\xEF\x94\x9E" #define ICON_FA_TEMPERATURE_HIGH "\xEF\x9D\xA9" #define ICON_FA_FONT_AWESOME_LOGO_FULL "\xEF\x93\xA6" #define ICON_FA_PASSPORT "\xEF\x96\xAB" #define ICON_FA_SHOPPING_CART "\xEF\x81\xBA" #define ICON_FA_AWARD "\xEF\x95\x99" #define ICON_FA_WINDOW_RESTORE "\xEF\x8B\x92" #define ICON_FA_PHONE "\xEF\x82\x95" #define ICON_FA_FLAG "\xEF\x80\xA4" #define ICON_FA_FILE_INVOICE_DOLLAR "\xEF\x95\xB1" #define ICON_FA_DICE_D6 "\xEF\x9B\x91" #define ICON_FA_OUTDENT "\xEF\x80\xBB" #define ICON_FA_LONG_ARROW_ALT_RIGHT "\xEF\x8C\x8B" #define ICON_FA_PIZZA_SLICE "\xEF\xA0\x98" #define ICON_FA_ADDRESS_CARD "\xEF\x8A\xBB" #define ICON_FA_PARAGRAPH "\xEF\x87\x9D" #define ICON_FA_MALE "\xEF\x86\x83" #define ICON_FA_HISTORY "\xEF\x87\x9A" #define ICON_FA_USER_TIE "\xEF\x94\x88" #define ICON_FA_SEARCH_PLUS "\xEF\x80\x8E" #define ICON_FA_LIFE_RING "\xEF\x87\x8D" #define ICON_FA_STEP_FORWARD "\xEF\x81\x91" #define ICON_FA_MOUSE_POINTER "\xEF\x89\x85" #define ICON_FA_ALIGN_JUSTIFY "\xEF\x80\xB9" #define ICON_FA_TOILET_PAPER "\xEF\x9C\x9E" #define ICON_FA_BATTERY_THREE_QUARTERS "\xEF\x89\x81" #define ICON_FA_OBJECT_UNGROUP "\xEF\x89\x88" #define ICON_FA_BRIEFCASE "\xEF\x82\xB1" #define ICON_FA_OIL_CAN "\xEF\x98\x93" #define ICON_FA_THERMOMETER_FULL "\xEF\x8B\x87" #define ICON_FA_SNOWBOARDING "\xEF\x9F\x8E" #define ICON_FA_UNLINK "\xEF\x84\xA7" #define ICON_FA_WINDOW_MAXIMIZE "\xEF\x8B\x90" #define ICON_FA_YEN_SIGN "\xEF\x85\x97" #define ICON_FA_SHARE_ALT_SQUARE "\xEF\x87\xA1" #define ICON_FA_STEP_BACKWARD "\xEF\x81\x88" #define ICON_FA_DRAGON "\xEF\x9B\x95" #define ICON_FA_MICROPHONE_SLASH "\xEF\x84\xB1" #define ICON_FA_USER_PLUS "\xEF\x88\xB4" #define ICON_FA_WRENCH "\xEF\x82\xAD" #define ICON_FA_AMBULANCE "\xEF\x83\xB9" #define ICON_FA_ETHERNET "\xEF\x9E\x96" #define ICON_FA_EGG "\xEF\x9F\xBB" #define ICON_FA_WIND "\xEF\x9C\xAE" #define ICON_FA_UNIVERSAL_ACCESS "\xEF\x8A\x9A" #define ICON_FA_BURN "\xEF\x91\xAA" #define ICON_FA_RADIATION "\xEF\x9E\xB9" #define ICON_FA_DICE_ONE "\xEF\x94\xA5" #define ICON_FA_KEYBOARD "\xEF\x84\x9C" #define ICON_FA_CHECK_DOUBLE "\xEF\x95\xA0" #define ICON_FA_HEADPHONES_ALT "\xEF\x96\x8F" #define ICON_FA_BATTERY_HALF "\xEF\x89\x82" #define ICON_FA_PROJECT_DIAGRAM "\xEF\x95\x82" #define ICON_FA_PRAY "\xEF\x9A\x83" #define ICON_FA_PHONE_ALT "\xEF\xA1\xB9" #define ICON_FA_BABY_CARRIAGE "\xEF\x9D\xBD" #define ICON_FA_TH_LIST "\xEF\x80\x8B" #define ICON_FA_GRIN_TEARS "\xEF\x96\x88" #define ICON_FA_SORT_AMOUNT_UP "\xEF\x85\xA1" #define ICON_FA_COFFEE "\xEF\x83\xB4" #define ICON_FA_TABLET_ALT "\xEF\x8F\xBA" #define ICON_FA_GRIN_BEAM_SWEAT "\xEF\x96\x83" #define ICON_FA_HAND_POINT_RIGHT "\xEF\x82\xA4" #define ICON_FA_GRIN_STARS "\xEF\x96\x87" #define ICON_FA_CHARGING_STATION "\xEF\x97\xA7" #define ICON_FA_VOTE_YEA "\xEF\x9D\xB2" #define ICON_FA_VOLUME_OFF "\xEF\x80\xA6" #define ICON_FA_SAD_TEAR "\xEF\x96\xB4" #define ICON_FA_CARET_RIGHT "\xEF\x83\x9A" #define ICON_FA_BONG "\xEF\x95\x9C" #define ICON_FA_BONE "\xEF\x97\x97" #define ICON_FA_WEIGHT "\xEF\x92\x96" #define ICON_FA_CARET_SQUARE_RIGHT "\xEF\x85\x92" #define ICON_FA_FISH "\xEF\x95\xB8" #define ICON_FA_SPIDER "\xEF\x9C\x97" #define ICON_FA_QRCODE "\xEF\x80\xA9" #define ICON_FA_SPINNER "\xEF\x84\x90" #define ICON_FA_ELLIPSIS_H "\xEF\x85\x81" #define ICON_FA_RUPEE_SIGN "\xEF\x85\x96" #define ICON_FA_ASSISTIVE_LISTENING_SYSTEMS "\xEF\x8A\xA2" #define ICON_FA_SMS "\xEF\x9F\x8D" #define ICON_FA_POUND_SIGN "\xEF\x85\x94" #define ICON_FA_HAND_POINT_DOWN "\xEF\x82\xA7" #define ICON_FA_ADJUST "\xEF\x81\x82" #define ICON_FA_PRINT "\xEF\x80\xAF" #define ICON_FA_SURPRISE "\xEF\x97\x82" #define ICON_FA_SORT_NUMERIC_UP "\xEF\x85\xA3" #define ICON_FA_VIDEO_SLASH "\xEF\x93\xA2" #define ICON_FA_SUBWAY "\xEF\x88\xB9" #define ICON_FA_SORT_AMOUNT_DOWN "\xEF\x85\xA0" #define ICON_FA_WINE_BOTTLE "\xEF\x9C\xAF" #define ICON_FA_BOOK_READER "\xEF\x97\x9A" #define ICON_FA_COOKIE "\xEF\x95\xA3" #define ICON_FA_MONEY_BILL "\xEF\x83\x96" #define ICON_FA_CHEVRON_DOWN "\xEF\x81\xB8" #define ICON_FA_CAR_SIDE "\xEF\x97\xA4" #define ICON_FA_FILTER "\xEF\x82\xB0" #define ICON_FA_FOLDER_OPEN "\xEF\x81\xBC" #define ICON_FA_SIGNATURE "\xEF\x96\xB7" #define ICON_FA_HEARTBEAT "\xEF\x88\x9E" #define ICON_FA_THUMBTACK "\xEF\x82\x8D" #define ICON_FA_USER "\xEF\x80\x87" #define ICON_FA_LAUGH_WINK "\xEF\x96\x9C" #define ICON_FA_BREAD_SLICE "\xEF\x9F\xAC" #define ICON_FA_TEXT_HEIGHT "\xEF\x80\xB4" #define ICON_FA_VOLUME_MUTE "\xEF\x9A\xA9" #define ICON_FA_GRIN_TONGUE "\xEF\x96\x89" #define ICON_FA_CAMPGROUND "\xEF\x9A\xBB" #define ICON_FA_MERCURY "\xEF\x88\xA3" #define ICON_FA_USER_ASTRONAUT "\xEF\x93\xBB" #define ICON_FA_HORSE "\xEF\x9B\xB0" #define ICON_FA_SORT_DOWN "\xEF\x83\x9D" #define ICON_FA_PERCENTAGE "\xEF\x95\x81" #define ICON_FA_AIR_FRESHENER "\xEF\x97\x90" #define ICON_FA_STORE "\xEF\x95\x8E" #define ICON_FA_COMMENT_DOTS "\xEF\x92\xAD" #define ICON_FA_SMILE_WINK "\xEF\x93\x9A" #define ICON_FA_HOTEL "\xEF\x96\x94" #define ICON_FA_PEPPER_HOT "\xEF\xA0\x96" #define ICON_FA_CUBES "\xEF\x86\xB3" #define ICON_FA_DUMPSTER_FIRE "\xEF\x9E\x94" #define ICON_FA_CLOUD_SUN_RAIN "\xEF\x9D\x83" #define ICON_FA_GLOBE_ASIA "\xEF\x95\xBE" #define ICON_FA_VIAL "\xEF\x92\x92" #define ICON_FA_STROOPWAFEL "\xEF\x95\x91" #define ICON_FA_CALENDAR_MINUS "\xEF\x89\xB2" #define ICON_FA_TREE "\xEF\x86\xBB" #define ICON_FA_SHOWER "\xEF\x8B\x8C" #define ICON_FA_DRUM_STEELPAN "\xEF\x95\xAA" #define ICON_FA_FILE_UPLOAD "\xEF\x95\xB4" #define ICON_FA_MEDKIT "\xEF\x83\xBA" #define ICON_FA_MINUS "\xEF\x81\xA8" #define ICON_FA_SHEKEL_SIGN "\xEF\x88\x8B" #define ICON_FA_USER_NINJA "\xEF\x94\x84" #define ICON_FA_KAABA "\xEF\x99\xAB" #define ICON_FA_BELL_SLASH "\xEF\x87\xB6" #define ICON_FA_SPELL_CHECK "\xEF\xA2\x91" #define ICON_FA_MAIL_BULK "\xEF\x99\xB4" #define ICON_FA_MOUNTAIN "\xEF\x9B\xBC" #define ICON_FA_COUCH "\xEF\x92\xB8" #define ICON_FA_CHESS "\xEF\x90\xB9" #define ICON_FA_FILE_EXPORT "\xEF\x95\xAE" #define ICON_FA_SIGN_LANGUAGE "\xEF\x8A\xA7" #define ICON_FA_SNOWFLAKE "\xEF\x8B\x9C" #define ICON_FA_PLAY "\xEF\x81\x8B" #define ICON_FA_HEADSET "\xEF\x96\x90" #define ICON_FA_CHART_BAR "\xEF\x82\x80" #define ICON_FA_WAVE_SQUARE "\xEF\xA0\xBE" #define ICON_FA_CHART_AREA "\xEF\x87\xBE" #define ICON_FA_EURO_SIGN "\xEF\x85\x93" #define ICON_FA_CHESS_KING "\xEF\x90\xBF" #define ICON_FA_MOBILE "\xEF\x84\x8B" #define ICON_FA_CLOCK "\xEF\x80\x97" #define ICON_FA_BOX_OPEN "\xEF\x92\x9E" #define ICON_FA_DOG "\xEF\x9B\x93" #define ICON_FA_FUTBOL "\xEF\x87\xA3" #define ICON_FA_LIRA_SIGN "\xEF\x86\x95" #define ICON_FA_LIGHTBULB "\xEF\x83\xAB" #define ICON_FA_BOMB "\xEF\x87\xA2" #define ICON_FA_MITTEN "\xEF\x9E\xB5" #define ICON_FA_TRUCK_MONSTER "\xEF\x98\xBB" #define ICON_FA_RANDOM "\xEF\x81\xB4" #define ICON_FA_CHESS_ROOK "\xEF\x91\x87" #define ICON_FA_FIRE_EXTINGUISHER "\xEF\x84\xB4" #define ICON_FA_ARROWS_ALT_V "\xEF\x8C\xB8" #define ICON_FA_ICICLES "\xEF\x9E\xAD" #define ICON_FA_FONT "\xEF\x80\xB1" #define ICON_FA_CAMERA_RETRO "\xEF\x82\x83" #define ICON_FA_BLENDER "\xEF\x94\x97" #define ICON_FA_THUMBS_DOWN "\xEF\x85\xA5" #define ICON_FA_ROCKET "\xEF\x84\xB5" #define ICON_FA_COPYRIGHT "\xEF\x87\xB9" #define ICON_FA_TRAM "\xEF\x9F\x9A" #define ICON_FA_JEDI "\xEF\x99\xA9" #define ICON_FA_HOCKEY_PUCK "\xEF\x91\x93" #define ICON_FA_STOP_CIRCLE "\xEF\x8A\x8D" #define ICON_FA_BEZIER_CURVE "\xEF\x95\x9B" #define ICON_FA_FOLDER "\xEF\x81\xBB" #define ICON_FA_CALENDAR_CHECK "\xEF\x89\xB4" #define ICON_FA_YIN_YANG "\xEF\x9A\xAD" #define ICON_FA_COLUMNS "\xEF\x83\x9B" #define ICON_FA_GLASS_CHEERS "\xEF\x9E\x9F" #define ICON_FA_GRIN_WINK "\xEF\x96\x8C" #define ICON_FA_STOP "\xEF\x81\x8D" #define ICON_FA_MONEY_CHECK_ALT "\xEF\x94\xBD" #define ICON_FA_COMPASS "\xEF\x85\x8E" #define ICON_FA_TOOLBOX "\xEF\x95\x92" #define ICON_FA_LIST_OL "\xEF\x83\x8B" #define ICON_FA_WINE_GLASS "\xEF\x93\xA3" #define ICON_FA_HORSE_HEAD "\xEF\x9E\xAB" #define ICON_FA_USER_ALT_SLASH "\xEF\x93\xBA" #define ICON_FA_USER_TAG "\xEF\x94\x87" #define ICON_FA_MICROSCOPE "\xEF\x98\x90" #define ICON_FA_BRUSH "\xEF\x95\x9D" #define ICON_FA_BAN "\xEF\x81\x9E" #define ICON_FA_BARS "\xEF\x83\x89" #define ICON_FA_CAR_CRASH "\xEF\x97\xA1" #define ICON_FA_ARROW_ALT_CIRCLE_DOWN "\xEF\x8D\x98" #define ICON_FA_MONEY_BILL_ALT "\xEF\x8F\x91" #define ICON_FA_JOURNAL_WHILLS "\xEF\x99\xAA" #define ICON_FA_CHALKBOARD_TEACHER "\xEF\x94\x9C" #define ICON_FA_PORTRAIT "\xEF\x8F\xA0" #define ICON_FA_BALANCE_SCALE_LEFT "\xEF\x94\x95" #define ICON_FA_HAMMER "\xEF\x9B\xA3" #define ICON_FA_RETWEET "\xEF\x81\xB9" #define ICON_FA_HOURGLASS "\xEF\x89\x94" #define ICON_FA_BORDER_NONE "\xEF\xA1\x90" #define ICON_FA_FILE_ALT "\xEF\x85\x9C" #define ICON_FA_SUBSCRIPT "\xEF\x84\xAC" #define ICON_FA_DONATE "\xEF\x92\xB9" #define ICON_FA_GLASS_MARTINI_ALT "\xEF\x95\xBB" #define ICON_FA_CODE_BRANCH "\xEF\x84\xA6" #define ICON_FA_MEH "\xEF\x84\x9A" #define ICON_FA_LIST_ALT "\xEF\x80\xA2" #define ICON_FA_USER_COG "\xEF\x93\xBE" #define ICON_FA_PRESCRIPTION "\xEF\x96\xB1" #define ICON_FA_TABLET "\xEF\x84\x8A" #define ICON_FA_LAUGH_SQUINT "\xEF\x96\x9B" #define ICON_FA_CREDIT_CARD "\xEF\x82\x9D" #define ICON_FA_ARCHWAY "\xEF\x95\x97" #define ICON_FA_HARD_HAT "\xEF\xA0\x87" #define ICON_FA_TRAFFIC_LIGHT "\xEF\x98\xB7" #define ICON_FA_COG "\xEF\x80\x93" #define ICON_FA_HANUKIAH "\xEF\x9B\xA6" #define ICON_FA_SHUTTLE_VAN "\xEF\x96\xB6" #define ICON_FA_MONEY_CHECK "\xEF\x94\xBC" #define ICON_FA_BELL "\xEF\x83\xB3" #define ICON_FA_CALENDAR_DAY "\xEF\x9E\x83" #define ICON_FA_TINT_SLASH "\xEF\x97\x87" #define ICON_FA_PLANE_DEPARTURE "\xEF\x96\xB0" #define ICON_FA_USER_CHECK "\xEF\x93\xBC" #define ICON_FA_CHURCH "\xEF\x94\x9D" #define ICON_FA_SEARCH_MINUS "\xEF\x80\x90" #define ICON_FA_SHIPPING_FAST "\xEF\x92\x8B" #define ICON_FA_TINT "\xEF\x81\x83" #define ICON_FA_ALIGN_RIGHT "\xEF\x80\xB8" #define ICON_FA_QUOTE_RIGHT "\xEF\x84\x8E" #define ICON_FA_BEER "\xEF\x83\xBC" #define ICON_FA_GRIN_ALT "\xEF\x96\x81" #define ICON_FA_SORT_NUMERIC_DOWN "\xEF\x85\xA2" #define ICON_FA_FIRE "\xEF\x81\xAD" #define ICON_FA_FAST_FORWARD "\xEF\x81\x90" #define ICON_FA_MAP_MARKED_ALT "\xEF\x96\xA0" #define ICON_FA_CHILD "\xEF\x86\xAE" #define ICON_FA_KISS_BEAM "\xEF\x96\x97" #define ICON_FA_TRUCK_LOADING "\xEF\x93\x9E" #define ICON_FA_EXPAND_ARROWS_ALT "\xEF\x8C\x9E" #define ICON_FA_CARET_SQUARE_DOWN "\xEF\x85\x90" #define ICON_FA_CRUTCH "\xEF\x9F\xB7" #define ICON_FA_OBJECT_GROUP "\xEF\x89\x87" #define ICON_FA_BIKING "\xEF\xA1\x8A" #define ICON_FA_ANCHOR "\xEF\x84\xBD" #define ICON_FA_HAND_POINT_LEFT "\xEF\x82\xA5" #define ICON_FA_USER_TIMES "\xEF\x88\xB5" #define ICON_FA_CALCULATOR "\xEF\x87\xAC" #define ICON_FA_DIZZY "\xEF\x95\xA7" #define ICON_FA_KISS_WINK_HEART "\xEF\x96\x98" #define ICON_FA_FILE_MEDICAL "\xEF\x91\xB7" #define ICON_FA_SWIMMING_POOL "\xEF\x97\x85" #define ICON_FA_VR_CARDBOARD "\xEF\x9C\xA9" #define ICON_FA_USER_FRIENDS "\xEF\x94\x80" #define ICON_FA_FAST_BACKWARD "\xEF\x81\x89" #define ICON_FA_SATELLITE "\xEF\x9E\xBF" #define ICON_FA_MINUS_CIRCLE "\xEF\x81\x96" #define ICON_FA_CHESS_PAWN "\xEF\x91\x83" #define ICON_FA_DATABASE "\xEF\x87\x80" #define ICON_FA_LANDMARK "\xEF\x99\xAF" #define ICON_FA_SWATCHBOOK "\xEF\x97\x83" #define ICON_FA_HOTDOG "\xEF\xA0\x8F" #define ICON_FA_SNOWMAN "\xEF\x9F\x90" #define ICON_FA_LAPTOP "\xEF\x84\x89" #define ICON_FA_TORAH "\xEF\x9A\xA0" #define ICON_FA_FROWN_OPEN "\xEF\x95\xBA" #define ICON_FA_REDO_ALT "\xEF\x8B\xB9" #define ICON_FA_AD "\xEF\x99\x81" #define ICON_FA_USER_CIRCLE "\xEF\x8A\xBD" #define ICON_FA_DIVIDE "\xEF\x94\xA9" #define ICON_FA_HANDSHAKE "\xEF\x8A\xB5" #define ICON_FA_CUT "\xEF\x83\x84" #define ICON_FA_GAMEPAD "\xEF\x84\x9B" #define ICON_FA_STREET_VIEW "\xEF\x88\x9D" #define ICON_FA_GREATER_THAN "\xEF\x94\xB1" #define ICON_FA_PASTAFARIANISM "\xEF\x99\xBB" #define ICON_FA_MINUS_SQUARE "\xEF\x85\x86" #define ICON_FA_SAVE "\xEF\x83\x87" #define ICON_FA_COMMENT_DOLLAR "\xEF\x99\x91" #define ICON_FA_TRASH_ALT "\xEF\x8B\xAD" #define ICON_FA_PUZZLE_PIECE "\xEF\x84\xAE" #define ICON_FA_SORT_ALPHA_UP_ALT "\xEF\xA2\x82" #define ICON_FA_MENORAH "\xEF\x99\xB6" #define ICON_FA_CLOUD_SUN "\xEF\x9B\x84" #define ICON_FA_USER_EDIT "\xEF\x93\xBF" #define ICON_FA_THEATER_MASKS "\xEF\x98\xB0" #define ICON_FA_FILE_MEDICAL_ALT "\xEF\x91\xB8" #define ICON_FA_BOXES "\xEF\x91\xA8" #define ICON_FA_THERMOMETER_EMPTY "\xEF\x8B\x8B" #define ICON_FA_EXCLAMATION_TRIANGLE "\xEF\x81\xB1" #define ICON_FA_GIFT "\xEF\x81\xAB" #define ICON_FA_COGS "\xEF\x82\x85" #define ICON_FA_SIGNAL "\xEF\x80\x92" #define ICON_FA_SHAPES "\xEF\x98\x9F" #define ICON_FA_CLOUD_RAIN "\xEF\x9C\xBD" #define ICON_FA_LESS_THAN_EQUAL "\xEF\x94\xB7" #define ICON_FA_CHEVRON_CIRCLE_LEFT "\xEF\x84\xB7" #define ICON_FA_MORTAR_PESTLE "\xEF\x96\xA7" #define ICON_FA_DUMBBELL "\xEF\x91\x8B" #define ICON_FA_SITEMAP "\xEF\x83\xA8" #define ICON_FA_BUS_ALT "\xEF\x95\x9E" #define ICON_FA_FILE_CODE "\xEF\x87\x89" #define ICON_FA_BATTERY_FULL "\xEF\x89\x80" #define ICON_FA_CROWN "\xEF\x94\xA1" #define ICON_FA_EXCHANGE_ALT "\xEF\x8D\xA2" #define ICON_FA_TRANSGENDER_ALT "\xEF\x88\xA5" #define ICON_FA_STAR_OF_DAVID "\xEF\x9A\x9A" #define ICON_FA_CASH_REGISTER "\xEF\x9E\x88" #define ICON_FA_TOOLS "\xEF\x9F\x99" #define ICON_FA_EXCLAMATION_CIRCLE "\xEF\x81\xAA" #define ICON_FA_COMMENTS "\xEF\x82\x86" #define ICON_FA_BRIEFCASE_MEDICAL "\xEF\x91\xA9" #define ICON_FA_COMMENTS_DOLLAR "\xEF\x99\x93" #define ICON_FA_BACKSPACE "\xEF\x95\x9A" #define ICON_FA_SLASH "\xEF\x9C\x95" #define ICON_FA_HOT_TUB "\xEF\x96\x93" #define ICON_FA_SUITCASE_ROLLING "\xEF\x97\x81" #define ICON_FA_BOLD "\xEF\x80\xB2" #define ICON_FA_HANDS_HELPING "\xEF\x93\x84" #define ICON_FA_SLEIGH "\xEF\x9F\x8C" #define ICON_FA_BOLT "\xEF\x83\xA7" #define ICON_FA_THERMOMETER_QUARTER "\xEF\x8B\x8A" #define ICON_FA_TROPHY "\xEF\x82\x91" #define ICON_FA_USER_ALT "\xEF\x90\x86" #define ICON_FA_BRAILLE "\xEF\x8A\xA1" #define ICON_FA_PLUS "\xEF\x81\xA7" #define ICON_FA_LIST_UL "\xEF\x83\x8A" #define ICON_FA_SMOKING_BAN "\xEF\x95\x8D" #define ICON_FA_BOOK "\xEF\x80\xAD" #define ICON_FA_VOLUME_DOWN "\xEF\x80\xA7" #define ICON_FA_QUESTION_CIRCLE "\xEF\x81\x99" #define ICON_FA_CARROT "\xEF\x9E\x87" #define ICON_FA_BATH "\xEF\x8B\x8D" #define ICON_FA_GAVEL "\xEF\x83\xA3" #define ICON_FA_CANDY_CANE "\xEF\x9E\x86" #define ICON_FA_NETWORK_WIRED "\xEF\x9B\xBF" #define ICON_FA_CARET_SQUARE_LEFT "\xEF\x86\x91" #define ICON_FA_PLANE_ARRIVAL "\xEF\x96\xAF" #define ICON_FA_SHARE_SQUARE "\xEF\x85\x8D" #define ICON_FA_MEDAL "\xEF\x96\xA2" #define ICON_FA_THERMOMETER_HALF "\xEF\x8B\x89" #define ICON_FA_QUESTION "\xEF\x84\xA8" #define ICON_FA_CAR_BATTERY "\xEF\x97\x9F" #define ICON_FA_DOOR_CLOSED "\xEF\x94\xAA" #define ICON_FA_USER_MINUS "\xEF\x94\x83" #define ICON_FA_MUSIC "\xEF\x80\x81" #define ICON_FA_HOUSE_DAMAGE "\xEF\x9B\xB1" #define ICON_FA_CHEVRON_RIGHT "\xEF\x81\x94" #define ICON_FA_GRIP_HORIZONTAL "\xEF\x96\x8D" #define ICON_FA_DICE_FOUR "\xEF\x94\xA4" #define ICON_FA_DEAF "\xEF\x8A\xA4" #define ICON_FA_MEH_BLANK "\xEF\x96\xA4" #define ICON_FA_WINDOW_CLOSE "\xEF\x90\x90" #define ICON_FA_LINK "\xEF\x83\x81" #define ICON_FA_ATOM "\xEF\x97\x92" #define ICON_FA_LESS_THAN "\xEF\x94\xB6" #define ICON_FA_OTTER "\xEF\x9C\x80" #define ICON_FA_DICE_TWO "\xEF\x94\xA8" #define ICON_FA_SORT_ALPHA_DOWN_ALT "\xEF\xA2\x81" #define ICON_FA_EJECT "\xEF\x81\x92" #define ICON_FA_SKULL "\xEF\x95\x8C" #define ICON_FA_GRIP_LINES "\xEF\x9E\xA4" #define ICON_FA_SORT_AMOUNT_DOWN_ALT "\xEF\xA2\x84" #define ICON_FA_HOSPITAL_SYMBOL "\xEF\x91\xBE" #define ICON_FA_X_RAY "\xEF\x92\x97" #define ICON_FA_ARROW_UP "\xEF\x81\xA2" #define ICON_FA_MONEY_BILL_WAVE "\xEF\x94\xBA" #define ICON_FA_DOT_CIRCLE "\xEF\x86\x92" #define ICON_FA_IMAGES "\xEF\x8C\x82" #define ICON_FA_STAR_HALF "\xEF\x82\x89" #define ICON_FA_SPLOTCH "\xEF\x96\xBC" #define ICON_FA_STAR_HALF_ALT "\xEF\x97\x80" #define ICON_FA_SHIP "\xEF\x88\x9A" #define ICON_FA_BOOK_DEAD "\xEF\x9A\xB7" #define ICON_FA_CHECK "\xEF\x80\x8C" #define ICON_FA_RAINBOW "\xEF\x9D\x9B" #define ICON_FA_POWER_OFF "\xEF\x80\x91" #define ICON_FA_LEMON "\xEF\x82\x94" #define ICON_FA_GLOBE_AMERICAS "\xEF\x95\xBD" #define ICON_FA_PEACE "\xEF\x99\xBC" #define ICON_FA_THERMOMETER_THREE_QUARTERS "\xEF\x8B\x88" #define ICON_FA_WAREHOUSE "\xEF\x92\x94" #define ICON_FA_TRANSGENDER "\xEF\x88\xA4" #define ICON_FA_PLUS_SQUARE "\xEF\x83\xBE" #define ICON_FA_BULLSEYE "\xEF\x85\x80" #define ICON_FA_COOKIE_BITE "\xEF\x95\xA4" #define ICON_FA_USERS "\xEF\x83\x80" #define ICON_FA_DRUMSTICK_BITE "\xEF\x9B\x97" #define ICON_FA_ASTERISK "\xEF\x81\xA9" #define ICON_FA_PLUS_CIRCLE "\xEF\x81\x95" #define ICON_FA_CART_ARROW_DOWN "\xEF\x88\x98" #define ICON_FA_LEAF "\xEF\x81\xAC" #define ICON_FA_FLUSHED "\xEF\x95\xB9" #define ICON_FA_STORE_ALT "\xEF\x95\x8F" #define ICON_FA_PEOPLE_CARRY "\xEF\x93\x8E" #define ICON_FA_CHESS_BOARD "\xEF\x90\xBC" #define ICON_FA_LONG_ARROW_ALT_DOWN "\xEF\x8C\x89" #define ICON_FA_SAD_CRY "\xEF\x96\xB3" #define ICON_FA_DIGITAL_TACHOGRAPH "\xEF\x95\xA6" #define ICON_FA_ANGLE_DOUBLE_DOWN "\xEF\x84\x83" #define ICON_FA_FILE_EXCEL "\xEF\x87\x83" #define ICON_FA_TEETH "\xEF\x98\xAE" #define ICON_FA_HAND_SCISSORS "\xEF\x89\x97" #define ICON_FA_STETHOSCOPE "\xEF\x83\xB1" #define ICON_FA_BACKWARD "\xEF\x81\x8A" #define ICON_FA_SCROLL "\xEF\x9C\x8E" #define ICON_FA_IGLOO "\xEF\x9E\xAE" #define ICON_FA_NOTES_MEDICAL "\xEF\x92\x81" #define ICON_FA_CODE "\xEF\x84\xA1" #define ICON_FA_SORT_NUMERIC_UP_ALT "\xEF\xA2\x87" #define ICON_FA_NOT_EQUAL "\xEF\x94\xBE" #define ICON_FA_SKIING "\xEF\x9F\x89" #define ICON_FA_CHAIR "\xEF\x9B\x80" #define ICON_FA_HAND_LIZARD "\xEF\x89\x98" #define ICON_FA_QUIDDITCH "\xEF\x91\x98" #define ICON_FA_ANGLE_DOUBLE_LEFT "\xEF\x84\x80" #define ICON_FA_MOSQUE "\xEF\x99\xB8" #define ICON_FA_PEN "\xEF\x8C\x84" #define ICON_FA_HRYVNIA "\xEF\x9B\xB2" #define ICON_FA_ANGLE_LEFT "\xEF\x84\x84" #define ICON_FA_ATLAS "\xEF\x95\x98" #define ICON_FA_PIGGY_BANK "\xEF\x93\x93" #define ICON_FA_DOLLY_FLATBED "\xEF\x91\xB4" #define ICON_FA_ARROWS_ALT_H "\xEF\x8C\xB7" #define ICON_FA_PEN_ALT "\xEF\x8C\x85" #define ICON_FA_PRAYING_HANDS "\xEF\x9A\x84" #define ICON_FA_VOLUME_UP "\xEF\x80\xA8" #define ICON_FA_CLIPBOARD_LIST "\xEF\x91\xAD" #define ICON_FA_BORDER_ALL "\xEF\xA1\x8C" #define ICON_FA_MAGIC "\xEF\x83\x90" #define ICON_FA_FOLDER_MINUS "\xEF\x99\x9D" #define ICON_FA_DEMOCRAT "\xEF\x9D\x87" #define ICON_FA_MAGNET "\xEF\x81\xB6" #define ICON_FA_VIHARA "\xEF\x9A\xA7" #define ICON_FA_GRIMACE "\xEF\x95\xBF" #define ICON_FA_CHECK_CIRCLE "\xEF\x81\x98" #define ICON_FA_SEARCH_DOLLAR "\xEF\x9A\x88" #define ICON_FA_LONG_ARROW_ALT_LEFT "\xEF\x8C\x8A" #define ICON_FA_FILE_PRESCRIPTION "\xEF\x95\xB2" #define ICON_FA_CROW "\xEF\x94\xA0" #define ICON_FA_EYE_DROPPER "\xEF\x87\xBB" #define ICON_FA_CROP "\xEF\x84\xA5" #define ICON_FA_SIGN "\xEF\x93\x99" #define ICON_FA_ARROW_CIRCLE_DOWN "\xEF\x82\xAB" #define ICON_FA_VIDEO "\xEF\x80\xBD" #define ICON_FA_DOWNLOAD "\xEF\x80\x99" #define ICON_FA_CARET_DOWN "\xEF\x83\x97" #define ICON_FA_CHEVRON_LEFT "\xEF\x81\x93" #define ICON_FA_GLOBE_AFRICA "\xEF\x95\xBC" #define ICON_FA_HAMSA "\xEF\x99\xA5" #define ICON_FA_CART_PLUS "\xEF\x88\x97" #define ICON_FA_CLIPBOARD "\xEF\x8C\xA8" #define ICON_FA_SHOE_PRINTS "\xEF\x95\x8B" #define ICON_FA_PHONE_SLASH "\xEF\x8F\x9D" #define ICON_FA_REPLY "\xEF\x8F\xA5" #define ICON_FA_HOURGLASS_HALF "\xEF\x89\x92" #define ICON_FA_LONG_ARROW_ALT_UP "\xEF\x8C\x8C" #define ICON_FA_CHESS_KNIGHT "\xEF\x91\x81" #define ICON_FA_BARCODE "\xEF\x80\xAA" #define ICON_FA_DRAW_POLYGON "\xEF\x97\xAE" #define ICON_FA_WATER "\xEF\x9D\xB3" #define ICON_FA_WINE_GLASS_ALT "\xEF\x97\x8E" #define ICON_FA_PHONE_VOLUME "\xEF\x8A\xA0" #define ICON_FA_GLASS_WHISKEY "\xEF\x9E\xA0" #define ICON_FA_BOX "\xEF\x91\xA6" #define ICON_FA_DIAGNOSES "\xEF\x91\xB0" #define ICON_FA_FILE_IMAGE "\xEF\x87\x85" #define ICON_FA_VENUS_MARS "\xEF\x88\xA8" #define ICON_FA_TASKS "\xEF\x82\xAE" #define ICON_FA_HIKING "\xEF\x9B\xAC" #define ICON_FA_VECTOR_SQUARE "\xEF\x97\x8B" #define ICON_FA_QUOTE_LEFT "\xEF\x84\x8D" #define ICON_FA_MOBILE_ALT "\xEF\x8F\x8D" #define ICON_FA_USER_SHIELD "\xEF\x94\x85" #define ICON_FA_BLOG "\xEF\x9E\x81" #define ICON_FA_MARKER "\xEF\x96\xA1" #define ICON_FA_HAMBURGER "\xEF\xA0\x85" #define ICON_FA_REDO "\xEF\x80\x9E" #define ICON_FA_CLOUD "\xEF\x83\x82" #define ICON_FA_HAND_HOLDING_USD "\xEF\x93\x80" #define ICON_FA_CERTIFICATE "\xEF\x82\xA3" #define ICON_FA_ANGRY "\xEF\x95\x96" #define ICON_FA_FROG "\xEF\x94\xAE" #define ICON_FA_CAMERA "\xEF\x80\xB0" #define ICON_FA_DICE_THREE "\xEF\x94\xA7" #define ICON_FA_MEMORY "\xEF\x94\xB8" #define ICON_FA_PEN_SQUARE "\xEF\x85\x8B" #define ICON_FA_SORT "\xEF\x83\x9C" #define ICON_FA_PLUG "\xEF\x87\xA6" #define ICON_FA_SHARE "\xEF\x81\xA4" #define ICON_FA_ENVELOPE "\xEF\x83\xA0" #define ICON_FA_LAYER_GROUP "\xEF\x97\xBD" #define ICON_FA_TRAIN "\xEF\x88\xB8" #define ICON_FA_BULLHORN "\xEF\x82\xA1" #define ICON_FA_BABY "\xEF\x9D\xBC" #define ICON_FA_CONCIERGE_BELL "\xEF\x95\xA2" #define ICON_FA_CIRCLE "\xEF\x84\x91" #define ICON_FA_I_CURSOR "\xEF\x89\x86" #define ICON_FA_CAR "\xEF\x86\xB9" #define ICON_FA_CAT "\xEF\x9A\xBE" #define ICON_FA_WALLET "\xEF\x95\x95" #define ICON_FA_BOOK_MEDICAL "\xEF\x9F\xA6" #define ICON_FA_H_SQUARE "\xEF\x83\xBD" #define ICON_FA_HEART "\xEF\x80\x84" #define ICON_FA_LOCK_OPEN "\xEF\x8F\x81" #define ICON_FA_STREAM "\xEF\x95\x90" #define ICON_FA_LOCK "\xEF\x80\xA3" #define ICON_FA_PARACHUTE_BOX "\xEF\x93\x8D" #define ICON_FA_TAG "\xEF\x80\xAB" #define ICON_FA_SMILE_BEAM "\xEF\x96\xB8" #define ICON_FA_USER_NURSE "\xEF\xA0\xAF" #define ICON_FA_MICROPHONE_ALT "\xEF\x8F\x89" #define ICON_FA_SPA "\xEF\x96\xBB" #define ICON_FA_CHEVRON_CIRCLE_DOWN "\xEF\x84\xBA" #define ICON_FA_FOLDER_PLUS "\xEF\x99\x9E" #define ICON_FA_TICKET_ALT "\xEF\x8F\xBF" #define ICON_FA_BOOK_OPEN "\xEF\x94\x98" #define ICON_FA_MAP "\xEF\x89\xB9" #define ICON_FA_COCKTAIL "\xEF\x95\xA1" #define ICON_FA_CLONE "\xEF\x89\x8D" #define ICON_FA_ID_CARD_ALT "\xEF\x91\xBF" #define ICON_FA_CHECK_SQUARE "\xEF\x85\x8A" #define ICON_FA_CHART_LINE "\xEF\x88\x81" #define ICON_FA_POO_STORM "\xEF\x9D\x9A" #define ICON_FA_DOVE "\xEF\x92\xBA" #define ICON_FA_MARS_STROKE "\xEF\x88\xA9" #define ICON_FA_ENVELOPE_OPEN "\xEF\x8A\xB6" #define ICON_FA_WHEELCHAIR "\xEF\x86\x93" #define ICON_FA_ROBOT "\xEF\x95\x84" #define ICON_FA_UNDO_ALT "\xEF\x8B\xAA" #define ICON_FA_CLOUD_MEATBALL "\xEF\x9C\xBB" #define ICON_FA_TRUCK "\xEF\x83\x91" #define ICON_FA_FLASK "\xEF\x83\x83" #define ICON_FA_WON_SIGN "\xEF\x85\x99" #define ICON_FA_SUPERSCRIPT "\xEF\x84\xAB" #define ICON_FA_TTY "\xEF\x87\xA4" #define ICON_FA_USER_MD "\xEF\x83\xB0" #define ICON_FA_BRAIN "\xEF\x97\x9C" #define ICON_FA_TABLETS "\xEF\x92\x90" #define ICON_FA_MOTORCYCLE "\xEF\x88\x9C" #define ICON_FA_PHONE_SQUARE_ALT "\xEF\xA1\xBB" #define ICON_FA_ANGLE_UP "\xEF\x84\x86" #define ICON_FA_BROOM "\xEF\x94\x9A" #define ICON_FA_DICE_D20 "\xEF\x9B\x8F" #define ICON_FA_LEVEL_DOWN_ALT "\xEF\x8E\xBE" #define ICON_FA_PAPERCLIP "\xEF\x83\x86" #define ICON_FA_USER_CLOCK "\xEF\x93\xBD" #define ICON_FA_MUG_HOT "\xEF\x9E\xB6" #define ICON_FA_SORT_ALPHA_UP "\xEF\x85\x9E" #define ICON_FA_AUDIO_DESCRIPTION "\xEF\x8A\x9E" #define ICON_FA_FILE_CSV "\xEF\x9B\x9D" #define ICON_FA_FILE_DOWNLOAD "\xEF\x95\xAD" #define ICON_FA_SYNC_ALT "\xEF\x8B\xB1" #define ICON_FA_ANGLE_DOUBLE_UP "\xEF\x84\x82" #define ICON_FA_HANDS "\xEF\x93\x82" #define ICON_FA_REPUBLICAN "\xEF\x9D\x9E" #define ICON_FA_UNIVERSITY "\xEF\x86\x9C" #define ICON_FA_KHANDA "\xEF\x99\xAD" #define ICON_FA_GLASSES "\xEF\x94\xB0" #define ICON_FA_SQUARE "\xEF\x83\x88" #define ICON_FA_GRIN_SQUINT "\xEF\x96\x85" #define ICON_FA_CLOSED_CAPTIONING "\xEF\x88\x8A" #define ICON_FA_RECEIPT "\xEF\x95\x83" #define ICON_FA_STRIKETHROUGH "\xEF\x83\x8C" #define ICON_FA_UNLOCK "\xEF\x82\x9C" #define ICON_FA_ARROW_LEFT "\xEF\x81\xA0" #define ICON_FA_DICE_SIX "\xEF\x94\xA6" #define ICON_FA_GRIP_VERTICAL "\xEF\x96\x8E" #define ICON_FA_PILLS "\xEF\x92\x84" #define ICON_FA_EXCLAMATION "\xEF\x84\xAA" #define ICON_FA_PERSON_BOOTH "\xEF\x9D\x96" #define ICON_FA_CALENDAR_PLUS "\xEF\x89\xB1" #define ICON_FA_SMOG "\xEF\x9D\x9F" #define ICON_FA_LOCATION_ARROW "\xEF\x84\xA4" #define ICON_FA_UMBRELLA "\xEF\x83\xA9" #define ICON_FA_QURAN "\xEF\x9A\x87" #define ICON_FA_UNDO "\xEF\x83\xA2" #define ICON_FA_DUMPSTER "\xEF\x9E\x93" #define ICON_FA_FUNNEL_DOLLAR "\xEF\x99\xA2" #define ICON_FA_INDENT "\xEF\x80\xBC" #define ICON_FA_LANGUAGE "\xEF\x86\xAB" #define ICON_FA_ARROW_ALT_CIRCLE_UP "\xEF\x8D\x9B" #define ICON_FA_ROUTE "\xEF\x93\x97" #define ICON_FA_HEADPHONES "\xEF\x80\xA5" #define ICON_FA_TIMES "\xEF\x80\x8D" #define ICON_FA_CLINIC_MEDICAL "\xEF\x9F\xB2" #define ICON_FA_PLANE "\xEF\x81\xB2" #define ICON_FA_TORII_GATE "\xEF\x9A\xA1" #define ICON_FA_LEVEL_UP_ALT "\xEF\x8E\xBF" #define ICON_FA_BLIND "\xEF\x8A\x9D" #define ICON_FA_CHEESE "\xEF\x9F\xAF" #define ICON_FA_PHONE_SQUARE "\xEF\x82\x98" #define ICON_FA_SHOPPING_BASKET "\xEF\x8A\x91" #define ICON_FA_ICE_CREAM "\xEF\xA0\x90" #define ICON_FA_RING "\xEF\x9C\x8B" #define ICON_FA_CITY "\xEF\x99\x8F" #define ICON_FA_TEXT_WIDTH "\xEF\x80\xB5" #define ICON_FA_RSS_SQUARE "\xEF\x85\x83" #define ICON_FA_PAINT_BRUSH "\xEF\x87\xBC" #define ICON_FA_BOOKMARK "\xEF\x80\xAE" #define ICON_FA_PHOTO_VIDEO "\xEF\xA1\xBC" #define ICON_FA_SIM_CARD "\xEF\x9F\x84" #define ICON_FA_CLOUD_UPLOAD_ALT "\xEF\x8E\x82" #define ICON_FA_COMPACT_DISC "\xEF\x94\x9F" #define ICON_FA_SORT_UP "\xEF\x83\x9E" #define ICON_FA_SIGN_OUT_ALT "\xEF\x8B\xB5" #define ICON_FA_SIGN_IN_ALT "\xEF\x8B\xB6" #define ICON_FA_FORWARD "\xEF\x81\x8E" #define ICON_FA_SHARE_ALT "\xEF\x87\xA0" #define ICON_FA_COPY "\xEF\x83\x85" #define ICON_FA_RSS "\xEF\x82\x9E" #define ICON_FA_PEN_FANCY "\xEF\x96\xAC" #define ICON_FA_BIOHAZARD "\xEF\x9E\x80" #define ICON_FA_BED "\xEF\x88\xB6" #define ICON_FA_INFO "\xEF\x84\xA9" #define ICON_FA_TOGGLE_OFF "\xEF\x88\x84" #define ICON_FA_MAP_MARKER_ALT "\xEF\x8F\x85" #define ICON_FA_TRACTOR "\xEF\x9C\xA2" #define ICON_FA_CLOUD_DOWNLOAD_ALT "\xEF\x8E\x81" #define ICON_FA_ID_BADGE "\xEF\x8B\x81" #define ICON_FA_SORT_NUMERIC_DOWN_ALT "\xEF\xA2\x86" #define ICON_FA_RULER_HORIZONTAL "\xEF\x95\x87" #define ICON_FA_PAINT_ROLLER "\xEF\x96\xAA" #define ICON_FA_HAT_WIZARD "\xEF\x9B\xA8" #define ICON_FA_MAP_SIGNS "\xEF\x89\xB7" #define ICON_FA_MICROPHONE "\xEF\x84\xB0" #define ICON_FA_FOOTBALL_BALL "\xEF\x91\x8E" #define ICON_FA_ALLERGIES "\xEF\x91\xA1" #define ICON_FA_ID_CARD "\xEF\x8B\x82" #define ICON_FA_USER_LOCK "\xEF\x94\x82" #define ICON_FA_PLAY_CIRCLE "\xEF\x85\x84" #define ICON_FA_REMOVE_FORMAT "\xEF\xA1\xBD" #define ICON_FA_THERMOMETER "\xEF\x92\x91" #define ICON_FA_REGISTERED "\xEF\x89\x9D" #define ICON_FA_DOLLAR_SIGN "\xEF\x85\x95" #define ICON_FA_DUNGEON "\xEF\x9B\x99" #define ICON_FA_COMPRESS "\xEF\x81\xA6" #define ICON_FA_SEARCH_LOCATION "\xEF\x9A\x89" #define ICON_FA_UTENSILS "\xEF\x8B\xA7" #define ICON_FA_BLENDER_PHONE "\xEF\x9A\xB6" #define ICON_FA_ANGLE_RIGHT "\xEF\x84\x85" #define ICON_FA_CHESS_QUEEN "\xEF\x91\x85" #define ICON_FA_PAGER "\xEF\xA0\x95" #define ICON_FA_SORT_AMOUNT_UP_ALT "\xEF\xA2\x85" #define ICON_FA_CLIPBOARD_CHECK "\xEF\x91\xAC" #define ICON_FA_HOURGLASS_END "\xEF\x89\x93" #define ICON_FA_TOOTH "\xEF\x97\x89" #define ICON_FA_BUSINESS_TIME "\xEF\x99\x8A" #define ICON_FA_PLACE_OF_WORSHIP "\xEF\x99\xBF" #define ICON_FA_GRIN_TONGUE_SQUINT "\xEF\x96\x8A" #define ICON_FA_MEH_ROLLING_EYES "\xEF\x96\xA5" #define ICON_FA_WALKING "\xEF\x95\x94" #define ICON_FA_EDIT "\xEF\x81\x84" #define ICON_FA_CARET_LEFT "\xEF\x83\x99" #define ICON_FA_PAUSE "\xEF\x81\x8C" #define ICON_FA_DICE "\xEF\x94\xA2" #define ICON_FA_RUBLE_SIGN "\xEF\x85\x98" #define ICON_FA_TERMINAL "\xEF\x84\xA0" #define ICON_FA_RULER_VERTICAL "\xEF\x95\x88" #define ICON_FA_HAND_POINTER "\xEF\x89\x9A" #define ICON_FA_TAPE "\xEF\x93\x9B" #define ICON_FA_SHOPPING_BAG "\xEF\x8A\x90" #define ICON_FA_SKIING_NORDIC "\xEF\x9F\x8A" #define ICON_FA_FIST_RAISED "\xEF\x9B\x9E" #define ICON_FA_CUBE "\xEF\x86\xB2" #define ICON_FA_CAPSULES "\xEF\x91\xAB" #define ICON_FA_KIWI_BIRD "\xEF\x94\xB5" #define ICON_FA_CHEVRON_CIRCLE_UP "\xEF\x84\xB9" #define ICON_FA_MARS_STROKE_V "\xEF\x88\xAA" #define ICON_FA_FILE_ARCHIVE "\xEF\x87\x86" #define ICON_FA_JOINT "\xEF\x96\x95" #define ICON_FA_MARS_STROKE_H "\xEF\x88\xAB" #define ICON_FA_ADDRESS_BOOK "\xEF\x8A\xB9" #define ICON_FA_PROCEDURES "\xEF\x92\x87" #define ICON_FA_GEM "\xEF\x8E\xA5" #define ICON_FA_RULER_COMBINED "\xEF\x95\x86" #define ICON_FA_ALIGN_LEFT "\xEF\x80\xB6" #define ICON_FA_STAR_AND_CRESCENT "\xEF\x9A\x99" #define ICON_FA_FIGHTER_JET "\xEF\x83\xBB" #define ICON_FA_SPACE_SHUTTLE "\xEF\x86\x97" #define ICON_FA_MAP_PIN "\xEF\x89\xB6" #define ICON_FA_GLOBE "\xEF\x82\xAC" #define ICON_FA_ALIGN_CENTER "\xEF\x80\xB7" #define ICON_FA_SORT_ALPHA_DOWN "\xEF\x85\x9D" #define ICON_FA_PARKING "\xEF\x95\x80" #define ICON_FA_CALENDAR "\xEF\x84\xB3" #define ICON_FA_PALETTE "\xEF\x94\xBF" #define ICON_FA_GLASS_MARTINI "\xEF\x80\x80" #define ICON_FA_TIMES_CIRCLE "\xEF\x81\x97" #define ICON_FA_EYE "\xEF\x81\xAE" #define ICON_FA_MONUMENT "\xEF\x96\xA6" #define ICON_FA_TRASH_RESTORE "\xEF\xA0\xA9" #define ICON_FA_GUITAR "\xEF\x9E\xA6" #define ICON_FA_GRIN_BEAM "\xEF\x96\x82" #define ICON_FA_KEY "\xEF\x82\x84" #define ICON_FA_FIRST_AID "\xEF\x91\xB9" #define ICON_FA_UMBRELLA_BEACH "\xEF\x97\x8A" #define ICON_FA_DRUM "\xEF\x95\xA9" #define ICON_FA_FILE_CONTRACT "\xEF\x95\xAC" #define ICON_FA_VOICEMAIL "\xEF\xA2\x97" #define ICON_FA_RESTROOM "\xEF\x9E\xBD" #define ICON_FA_UNLOCK_ALT "\xEF\x84\xBE" #define ICON_FA_MICROPHONE_ALT_SLASH "\xEF\x94\xB9" #define ICON_FA_USER_SECRET "\xEF\x88\x9B" #define ICON_FA_ARROW_RIGHT "\xEF\x81\xA1" #define ICON_FA_FILE_VIDEO "\xEF\x87\x88" #define ICON_FA_ARROW_ALT_CIRCLE_RIGHT "\xEF\x8D\x9A" #define ICON_FA_CALENDAR_WEEK "\xEF\x9E\x84" #define ICON_FA_USER_GRADUATE "\xEF\x94\x81" #define ICON_FA_HAND_MIDDLE_FINGER "\xEF\xA0\x86" #define ICON_FA_POO "\xEF\x8B\xBE" #define ICON_FA_LAUGH "\xEF\x96\x99" #define ICON_FA_TABLE "\xEF\x83\x8E" #define ICON_FA_POLL "\xEF\x9A\x81" #define ICON_FA_CAR_ALT "\xEF\x97\x9E" #define ICON_FA_THUMBS_UP "\xEF\x85\xA4" #define ICON_FA_SWIMMER "\xEF\x97\x84" #define ICON_FA_TRADEMARK "\xEF\x89\x9C" #define ICON_FA_CLOUD_MOON_RAIN "\xEF\x9C\xBC" #define ICON_FA_VIALS "\xEF\x92\x93" #define ICON_FA_ERASER "\xEF\x84\xAD" #define ICON_FA_MARS "\xEF\x88\xA2" #define ICON_FA_HELICOPTER "\xEF\x94\xB3" #define ICON_FA_FEATHER "\xEF\x94\xAD" #define ICON_FA_SQUARE_FULL "\xEF\x91\x9C" #define ICON_FA_DOLLY "\xEF\x91\xB2" #define ICON_FA_HAND_HOLDING "\xEF\x92\xBD" #define ICON_FA_HOURGLASS_START "\xEF\x89\x91" #define ICON_FA_GRIN_HEARTS "\xEF\x96\x84" #define ICON_FA_VENUS_DOUBLE "\xEF\x88\xA6" #define ICON_FA_HASHTAG "\xEF\x8A\x92" #define ICON_FA_FINGERPRINT "\xEF\x95\xB7" #define ICON_FA_SEEDLING "\xEF\x93\x98" #define ICON_FA_HAYKAL "\xEF\x99\xA6" #define ICON_FA_TSHIRT "\xEF\x95\x93" #define ICON_FA_PENCIL_RULER "\xEF\x96\xAE" #define ICON_FA_HDD "\xEF\x82\xA0" #define ICON_FA_NEWSPAPER "\xEF\x87\xAA" #define ICON_FA_HOSPITAL_ALT "\xEF\x91\xBD" #define ICON_FA_USER_SLASH "\xEF\x94\x86" #define ICON_FA_FILE_WORD "\xEF\x87\x82" #define ICON_FA_ENVELOPE_SQUARE "\xEF\x86\x99" #define ICON_FA_GENDERLESS "\xEF\x88\xAD" #define ICON_FA_DICE_FIVE "\xEF\x94\xA3" #define ICON_FA_SYNAGOGUE "\xEF\x9A\x9B" #define ICON_FA_PAW "\xEF\x86\xB0" #define ICON_FA_HAND_HOLDING_HEART "\xEF\x92\xBE" #define ICON_FA_CROSS "\xEF\x99\x94" #define ICON_FA_ARCHIVE "\xEF\x86\x87" #define ICON_FA_SOLAR_PANEL "\xEF\x96\xBA" #define ICON_FA_INFINITY "\xEF\x94\xB4" #define ICON_FA_ANKH "\xEF\x99\x84" #define ICON_FA_MAP_MARKER "\xEF\x81\x81" #define ICON_FA_CALENDAR_ALT "\xEF\x81\xB3" #define ICON_FA_AMERICAN_SIGN_LANGUAGE_INTERPRETING "\xEF\x8A\xA3" #define ICON_FA_BINOCULARS "\xEF\x87\xA5" #define ICON_FA_STICKY_NOTE "\xEF\x89\x89" #define ICON_FA_RUNNING "\xEF\x9C\x8C" #define ICON_FA_PEN_NIB "\xEF\x96\xAD" #define ICON_FA_MAP_MARKED "\xEF\x96\x9F" #define ICON_FA_EXPAND "\xEF\x81\xA5" #define ICON_FA_TRUCK_PICKUP "\xEF\x98\xBC" #define ICON_FA_HOLLY_BERRY "\xEF\x9E\xAA" #define ICON_FA_PRESCRIPTION_BOTTLE "\xEF\x92\x85" #define ICON_FA_LAPTOP_CODE "\xEF\x97\xBC" #define ICON_FA_GOLF_BALL "\xEF\x91\x90" #define ICON_FA_SKULL_CROSSBONES "\xEF\x9C\x94" #define ICON_FA_TAXI "\xEF\x86\xBA" #define ICON_FA_COMMENT "\xEF\x81\xB5" #define ICON_FA_KISS "\xEF\x96\x96" #define ICON_FA_HIPPO "\xEF\x9B\xAD" #define ICON_FA_ARROWS_ALT "\xEF\x82\xB2" #define ICON_FA_UNDERLINE "\xEF\x83\x8D" #define ICON_FA_ARROW_CIRCLE_UP "\xEF\x82\xAA" #define ICON_FA_BASKETBALL_BALL "\xEF\x90\xB4" #define ICON_FA_DESKTOP "\xEF\x84\x88" #define ICON_FA_PALLET "\xEF\x92\x82" #define ICON_FA_TOGGLE_ON "\xEF\x88\x85" #define ICON_FA_STOPWATCH "\xEF\x8B\xB2" #define ICON_FA_ARROW_ALT_CIRCLE_LEFT "\xEF\x8D\x99" #define ICON_FA_GAS_PUMP "\xEF\x94\xAF" #define ICON_FA_EXTERNAL_LINK_ALT "\xEF\x8D\x9D" #define ICON_FA_FROWN "\xEF\x84\x99" #define ICON_FA_RULER "\xEF\x95\x85" #define ICON_FA_FLAG_USA "\xEF\x9D\x8D" #define ICON_FA_GRIN "\xEF\x96\x80" #define ICON_FA_ARROW_CIRCLE_LEFT "\xEF\x82\xA8" #define ICON_FA_HIGHLIGHTER "\xEF\x96\x91" #define ICON_FA_POLL_H "\xEF\x9A\x82" #define ICON_FA_SERVER "\xEF\x88\xB3" #define ICON_FA_BATTERY_EMPTY "\xEF\x89\x84" #define ICON_FA_SPRAY_CAN "\xEF\x96\xBD" #define ICON_FA_BOWLING_BALL "\xEF\x90\xB6" #define ICON_FA_GRIP_LINES_VERTICAL "\xEF\x9E\xA5" #define ICON_FA_GLOBE_EUROPE "\xEF\x9E\xA2" #define ICON_FA_WINDOW_MINIMIZE "\xEF\x8B\x91" #define ICON_FA_MARS_DOUBLE "\xEF\x88\xA7" #define ICON_FA_PAUSE_CIRCLE "\xEF\x8A\x8B" #define ICON_FA_HOME "\xEF\x80\x95" #define ICON_FA_COMMENT_ALT "\xEF\x89\xBA" #define ICON_FA_UTENSIL_SPOON "\xEF\x8B\xA5" #define ICON_FA_APPLE_ALT "\xEF\x97\x91" #define ICON_FA_MOON "\xEF\x86\x86" #define ICON_FA_CANNABIS "\xEF\x95\x9F" #define ICON_FA_LAUGH_BEAM "\xEF\x96\x9A" #define ICON_FA_TEETH_OPEN "\xEF\x98\xAF" #define ICON_FA_CHART_PIE "\xEF\x88\x80" #define ICON_FA_SOCKS "\xEF\x9A\x96" #define ICON_FA_SD_CARD "\xEF\x9F\x82" #define ICON_FA_ARROW_CIRCLE_RIGHT "\xEF\x82\xA9" #define ICON_FA_PASTE "\xEF\x83\xAA" #define ICON_FA_OM "\xEF\x99\xB9" #define ICON_FA_LUGGAGE_CART "\xEF\x96\x9D" #define ICON_FA_INDUSTRY "\xEF\x89\xB5" #define ICON_FA_STAMP "\xEF\x96\xBF" #define ICON_FA_RADIATION_ALT "\xEF\x9E\xBA" #define ICON_FA_COMPRESS_ARROWS_ALT "\xEF\x9E\x8C" #define ICON_FA_ROAD "\xEF\x80\x98" #define ICON_FA_IMAGE "\xEF\x80\xBE" #define ICON_FA_BALANCE_SCALE_RIGHT "\xEF\x94\x96" #define ICON_FA_ANGLE_DOUBLE_RIGHT "\xEF\x84\x81" #define ICON_FA_CLOUD_MOON "\xEF\x9B\x83" #define ICON_FA_DOOR_OPEN "\xEF\x94\xAB" #define ICON_FA_GRIN_TONGUE_WINK "\xEF\x96\x8B" #define ICON_FA_REPLY_ALL "\xEF\x84\xA2" #define ICON_FA_TEMPERATURE_LOW "\xEF\x9D\xAB" #define ICON_FA_INBOX "\xEF\x80\x9C" #define ICON_FA_FEMALE "\xEF\x86\x82" #define ICON_FA_SYRINGE "\xEF\x92\x8E" #define ICON_FA_CIRCLE_NOTCH "\xEF\x87\x8E" #define ICON_FA_ELLIPSIS_V "\xEF\x85\x82" #define ICON_FA_SNOWPLOW "\xEF\x9F\x92" #define ICON_FA_TABLE_TENNIS "\xEF\x91\x9D" #define ICON_FA_LOW_VISION "\xEF\x8A\xA8" #define ICON_FA_FILE_IMPORT "\xEF\x95\xAF" #define ICON_FA_ITALIC "\xEF\x80\xB3" #define ICON_FA_FILE_SIGNATURE "\xEF\x95\xB3" #define ICON_FA_CHALKBOARD "\xEF\x94\x9B" #define ICON_FA_GHOST "\xEF\x9B\xA2" #define ICON_FA_TACHOMETER_ALT "\xEF\x8F\xBD" #define ICON_FA_BUS "\xEF\x88\x87" #define ICON_FA_ANGLE_DOWN "\xEF\x84\x87" #define ICON_FA_HAND_ROCK "\xEF\x89\x95" #define ICON_FA_BORDER_STYLE "\xEF\xA1\x93" #define ICON_FA_STAR_OF_LIFE "\xEF\x98\xA1" #define ICON_FA_PODCAST "\xEF\x8B\x8E" #define ICON_FA_TRUCK_MOVING "\xEF\x93\x9F" #define ICON_FA_BUG "\xEF\x86\x88" #define ICON_FA_SHIELD_ALT "\xEF\x8F\xAD" #define ICON_FA_FILL_DRIP "\xEF\x95\xB6" #define ICON_FA_COMMENT_SLASH "\xEF\x92\xB3" #define ICON_FA_SUITCASE "\xEF\x83\xB2" #define ICON_FA_SKATING "\xEF\x9F\x85" #define ICON_FA_TOILET "\xEF\x9F\x98" #define ICON_FA_ENVELOPE_OPEN_TEXT "\xEF\x99\x98" #define ICON_FA_HEART_BROKEN "\xEF\x9E\xA9" #define ICON_FA_CARET_SQUARE_UP "\xEF\x85\x91" #define ICON_FA_TH_LARGE "\xEF\x80\x89" #define ICON_FA_AT "\xEF\x87\xBA" #define ICON_FA_FILE "\xEF\x85\x9B" #define ICON_FA_TENGE "\xEF\x9F\x97" #define ICON_FA_FLAG_CHECKERED "\xEF\x84\x9E" #define ICON_FA_FILM "\xEF\x80\x88" #define ICON_FA_FILL "\xEF\x95\xB5" #define ICON_FA_GRIN_SQUINT_TEARS "\xEF\x96\x86" #define ICON_FA_PERCENT "\xEF\x8A\x95" #define ICON_FA_METEOR "\xEF\x9D\x93" #define ICON_FA_TRASH "\xEF\x87\xB8" #define ICON_FA_FILE_AUDIO "\xEF\x87\x87" #define ICON_FA_SATELLITE_DISH "\xEF\x9F\x80" #define ICON_FA_POOP "\xEF\x98\x99" #define ICON_FA_STAR "\xEF\x80\x85" #define ICON_FA_GIFTS "\xEF\x9E\x9C" #define ICON_FA_FIRE_ALT "\xEF\x9F\xA4" #define ICON_FA_BUILDING "\xEF\x86\xAD" #define ICON_FA_PRESCRIPTION_BOTTLE_ALT "\xEF\x92\x86" #define ICON_FA_MONEY_BILL_WAVE_ALT "\xEF\x94\xBB" #define ICON_FA_NEUTER "\xEF\x88\xAC" #define ICON_FA_BAND_AID "\xEF\x91\xA2" #define ICON_FA_WIFI "\xEF\x87\xAB" #define ICON_FA_MASK "\xEF\x9B\xBA" #define ICON_FA_CHEVRON_UP "\xEF\x81\xB7" #define ICON_FA_HAND_SPOCK "\xEF\x89\x99" #define ICON_FA_HAND_POINT_UP "\xEF\x82\xA6" ================================================ FILE: examples/pfd/COPYING ================================================ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. ================================================ FILE: examples/pfd/pfd.h ================================================ // // Portable File Dialogs // // Copyright © 2018—2020 Sam Hocevar // // This library is free software. It comes without any warranty, to // the extent permitted by applicable law. You can redistribute it // and/or modify it under the terms of the Do What the Fuck You Want // to Public License, Version 2, as published by the WTFPL Task Force. // See http://www.wtfpl.net/ for more details. // #pragma once #if _WIN32 #ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN 1 #endif #include #include #include #include // IFileDialog #include #include #include // std::async #elif __EMSCRIPTEN__ #include #else #ifndef _POSIX_C_SOURCE # define _POSIX_C_SOURCE 2 // for popen() #endif #include // popen() #include // std::getenv() #include // fcntl() #include // read(), pipe(), dup2() #include // ::kill, std::signal #include // waitpid() #endif #include // std::string #include // std::shared_ptr #include // std::ostream #include // std::map #include // std::set #include // std::regex #include // std::mutex, std::this_thread #include // std::chrono namespace pfd { enum class button { cancel = -1, ok, yes, no, abort, retry, ignore, }; enum class choice { ok = 0, ok_cancel, yes_no, yes_no_cancel, retry_cancel, abort_retry_ignore, }; enum class icon { info = 0, warning, error, question, }; // Additional option flags for various dialog constructors enum class opt : uint8_t { none = 0, // For file open, allow multiselect. multiselect = 0x1, // For file save, force overwrite and disable the confirmation dialog. force_overwrite = 0x2, // For folder select, force path to be the provided argument instead // of the last opened directory, which is the Microsoft-recommended, // user-friendly behaviour. force_path = 0x4, }; inline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); } inline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); } // The settings class, only exposing to the user a way to set verbose mode // and to force a rescan of installed desktop helpers (zenity, kdialog…). class settings { public: static bool available(); static void verbose(bool value); static void rescan(); protected: explicit settings(bool resync = false); bool check_program(std::string const &program); inline bool is_osascript() const; inline bool is_zenity() const; inline bool is_kdialog() const; enum class flag { is_scanned = 0, is_verbose, has_zenity, has_matedialog, has_qarma, has_kdialog, is_vista, max_flag, }; // Static array of flags for internal state bool const &flags(flag in_flag) const; // Non-const getter for the static array of flags bool &flags(flag in_flag); }; // Internal classes, not to be used by client applications namespace internal { // Process wait timeout, in milliseconds static int const default_wait_timeout = 20; class executor { friend class dialog; public: // High level function to get the result of a command std::string result(int *exit_code = nullptr); // High level function to abort bool kill(); #if _WIN32 void start_func(std::function const &fun); static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); #elif __EMSCRIPTEN__ void start(int exit_code); #else void start_process(std::vector const &command); #endif ~executor(); protected: bool ready(int timeout = default_wait_timeout); void stop(); private: bool m_running = false; std::string m_stdout; int m_exit_code = -1; #if _WIN32 std::future m_future; std::set m_windows; std::condition_variable m_cond; std::mutex m_mutex; DWORD m_tid; #elif __EMSCRIPTEN__ || __NX__ // FIXME: do something #else pid_t m_pid = 0; int m_fd = -1; #endif }; class platform { protected: #if _WIN32 // Helper class around LoadLibraryA() and GetProcAddress() with some safety class dll { public: dll(std::string const &name); ~dll(); template class proc { public: proc(dll const &lib, std::string const &sym) : m_proc(reinterpret_cast(::GetProcAddress(lib.handle, sym.c_str()))) {} operator bool() const { return m_proc != nullptr; } operator T *() const { return m_proc; } private: T *m_proc; }; private: HMODULE handle; }; // Helper class around CoInitialize() and CoUnInitialize() class ole32_dll : public dll { public: ole32_dll(); ~ole32_dll(); bool is_initialized(); private: HRESULT m_state; }; // Helper class around CreateActCtx() and ActivateActCtx() class new_style_context { public: new_style_context(); ~new_style_context(); private: HANDLE create(); ULONG_PTR m_cookie = 0; }; #endif }; class dialog : protected settings, protected platform { public: bool ready(int timeout = default_wait_timeout) const; bool kill() const; protected: explicit dialog(); std::vector desktop_helper() const; static std::string buttons_to_name(choice _choice); static std::string get_icon_name(icon _icon); std::string powershell_quote(std::string const &str) const; std::string osascript_quote(std::string const &str) const; std::string shell_quote(std::string const &str) const; // Keep handle to executing command std::shared_ptr m_async; }; class file_dialog : public dialog { protected: enum type { open, save, folder, }; file_dialog(type in_type, std::string const &title, std::string const &default_path = "", std::vector const &filters = {}, opt options = opt::none); protected: std::string string_result(); std::vector vector_result(); #if _WIN32 static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); std::string select_folder_vista(IFileDialog *ifd, bool force_path); std::wstring m_wtitle; std::wstring m_wdefault_path; std::vector m_vector_result; #endif }; } // namespace internal // // The notify widget // class notify : public internal::dialog { public: notify(std::string const &title, std::string const &message, icon _icon = icon::info); }; // // The message widget // class message : public internal::dialog { public: message(std::string const &title, std::string const &text, choice _choice = choice::ok_cancel, icon _icon = icon::info); button result(); private: // Some extra logic to map the exit code to button number std::map m_mappings; }; // // The open_file, save_file, and open_folder widgets // class open_file : public internal::file_dialog { public: open_file(std::string const &title, std::string const &default_path = "", std::vector const &filters = { "All Files", "*" }, opt options = opt::none); #if defined(__has_cpp_attribute) #if __has_cpp_attribute(deprecated) // Backwards compatibility //[[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] #endif #endif open_file(std::string const &title, std::string const &default_path, std::vector const &filters, bool allow_multiselect); std::vector result(); }; class save_file : public internal::file_dialog { public: save_file(std::string const &title, std::string const &default_path = "", std::vector const &filters = { "All Files", "*" }, opt options = opt::none); #if defined(__has_cpp_attribute) #if __has_cpp_attribute(deprecated) // Backwards compatibility //[[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] #endif #endif save_file(std::string const &title, std::string const &default_path, std::vector const &filters, bool confirm_overwrite); std::string result(); }; class select_folder : public internal::file_dialog { public: select_folder(std::string const &title, std::string const &default_path = "", opt options = opt::none); std::string result(); }; // // Below this are all the method implementations. You may choose to define the // macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except // in one place. This may reduce compilation times. // #if !defined PFD_SKIP_IMPLEMENTATION // internal free functions implementations namespace internal { #if _WIN32 static inline std::wstring str2wstr(std::string const &str) { int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); std::wstring ret(len, '\0'); MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size()); return ret; } static inline std::string wstr2str(std::wstring const &str) { int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr); std::string ret(len, '\0'); WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr); return ret; } static inline bool is_vista() { OSVERSIONINFOEXW osvi; memset(&osvi, 0, sizeof(osvi)); DWORDLONG const mask = VerSetConditionMask( VerSetConditionMask( VerSetConditionMask( 0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, VER_GREATER_EQUAL), VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); osvi.dwOSVersionInfoSize = sizeof(osvi); osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); osvi.wServicePackMajor = 0; return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE; } #endif // This is necessary until C++20 which will have std::string::ends_with() etc. static inline bool ends_with(std::string const &str, std::string const &suffix) { return suffix.size() <= str.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; } static inline bool starts_with(std::string const &str, std::string const &prefix) { return prefix.size() <= str.size() && str.compare(0, prefix.size(), prefix) == 0; } } // namespace internal // settings implementation inline settings::settings(bool resync) { flags(flag::is_scanned) &= !resync; if (flags(flag::is_scanned)) return; #if _WIN32 flags(flag::is_vista) = internal::is_vista(); #elif !__APPLE__ flags(flag::has_zenity) = check_program("zenity"); flags(flag::has_matedialog) = check_program("matedialog"); flags(flag::has_qarma) = check_program("qarma"); flags(flag::has_kdialog) = check_program("kdialog"); // If multiple helpers are available, try to default to the best one if (flags(flag::has_zenity) && flags(flag::has_kdialog)) { auto desktop_name = std::getenv("XDG_SESSION_DESKTOP"); if (desktop_name && desktop_name == std::string("gnome")) flags(flag::has_kdialog) = false; else if (desktop_name && desktop_name == std::string("KDE")) flags(flag::has_zenity) = false; } #endif flags(flag::is_scanned) = true; } inline bool settings::available() { #if _WIN32 return true; #elif __APPLE__ return true; #else settings tmp; return tmp.flags(flag::has_zenity) || tmp.flags(flag::has_matedialog) || tmp.flags(flag::has_qarma) || tmp.flags(flag::has_kdialog); #endif } inline void settings::verbose(bool value) { settings().flags(flag::is_verbose) = value; } inline void settings::rescan() { settings(/* resync = */ true); } // Check whether a program is present using “which”. inline bool settings::check_program(std::string const &program) { #if _WIN32 (void)program; return false; #elif __EMSCRIPTEN__ (void)program; return false; #else int exit_code = -1; internal::executor async; async.start_process({"/bin/sh", "-c", "which " + program}); async.result(&exit_code); return exit_code == 0; #endif } inline bool settings::is_osascript() const { #if __APPLE__ return true; #else return false; #endif } inline bool settings::is_zenity() const { return flags(flag::has_zenity) || flags(flag::has_matedialog) || flags(flag::has_qarma); } inline bool settings::is_kdialog() const { return flags(flag::has_kdialog); } inline bool const &settings::flags(flag in_flag) const { static bool flags[size_t(flag::max_flag)]; return flags[size_t(in_flag)]; } inline bool &settings::flags(flag in_flag) { return const_cast(static_cast(this)->flags(in_flag)); } // executor implementation inline std::string internal::executor::result(int *exit_code /* = nullptr */) { stop(); if (exit_code) *exit_code = m_exit_code; return m_stdout; } inline bool internal::executor::kill() { #if _WIN32 if (m_future.valid()) { // Close all windows that weren’t open when we started the future auto previous_windows = m_windows; EnumWindows(&enum_windows_callback, (LPARAM)this); for (auto hwnd : m_windows) if (previous_windows.find(hwnd) == previous_windows.end()) SendMessage(hwnd, WM_CLOSE, 0, 0); } #elif __EMSCRIPTEN__ || __NX__ // FIXME: do something (void)timeout; return false; // cannot kill #else ::kill(m_pid, SIGKILL); #endif stop(); return true; } #if _WIN32 inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) { auto that = (executor *)lParam; DWORD pid; auto tid = GetWindowThreadProcessId(hwnd, &pid); if (tid == that->m_tid) that->m_windows.insert(hwnd); return TRUE; } #endif #if _WIN32 inline void internal::executor::start_func(std::function const &fun) { stop(); auto trampoline = [fun, this]() { // Save our thread id so that the caller can cancel us m_tid = GetCurrentThreadId(); EnumWindows(&enum_windows_callback, (LPARAM)this); m_cond.notify_all(); return fun(&m_exit_code); }; std::unique_lock lock(m_mutex); m_future = std::async(std::launch::async, trampoline); m_cond.wait(lock); m_running = true; } #elif __EMSCRIPTEN__ inline void internal::executor::start(int exit_code) { m_exit_code = exit_code; } #else inline void internal::executor::start_process(std::vector const &command) { stop(); m_stdout.clear(); m_exit_code = -1; int in[2], out[2]; if (pipe(in) != 0 || pipe(out) != 0) return; m_pid = fork(); if (m_pid < 0) return; close(in[m_pid ? 0 : 1]); close(out[m_pid ? 1 : 0]); if (m_pid == 0) { dup2(in[0], STDIN_FILENO); dup2(out[1], STDOUT_FILENO); // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) int fd = open("/dev/null", O_WRONLY); dup2(fd, STDERR_FILENO); close(fd); std::vector args; std::transform(command.cbegin(), command.cend(), std::back_inserter(args), [](std::string const &s) { return const_cast(s.c_str()); }); args.push_back(nullptr); // null-terminate argv[] execvp(args[0], args.data()); exit(1); } close(in[1]); m_fd = out[0]; auto flags = fcntl(m_fd, F_GETFL); fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); m_running = true; } #endif inline internal::executor::~executor() { stop(); } inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) { if (!m_running) return true; #if _WIN32 if (m_future.valid()) { auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); if (status != std::future_status::ready) { // On Windows, we need to run the message pump. If the async // thread uses a Windows API dialog, it may be attached to the // main thread and waiting for messages that only we can dispatch. MSG msg; while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } return false; } m_stdout = m_future.get(); } #elif __EMSCRIPTEN__ || __NX__ // FIXME: do something (void)timeout; #else char buf[BUFSIZ]; ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore if (received > 0) { m_stdout += std::string(buf, received); return false; } // Reap child process if it is dead. It is possible that the system has already reaped it // (this happens when the calling application handles or ignores SIG_CHLD) and results in // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for // a little while. int status; pid_t child = waitpid(m_pid, &status, WNOHANG); if (child != m_pid && (child >= 0 || errno != ECHILD)) { // FIXME: this happens almost always at first iteration std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); return false; } close(m_fd); m_exit_code = WEXITSTATUS(status); #endif m_running = false; return true; } inline void internal::executor::stop() { // Loop until the user closes the dialog while (!ready()) ; } // dll implementation #if _WIN32 inline internal::platform::dll::dll(std::string const &name) : handle(::LoadLibraryA(name.c_str())) {} inline internal::platform::dll::~dll() { if (handle) ::FreeLibrary(handle); } #endif // _WIN32 // ole32_dll implementation #if _WIN32 inline internal::platform::ole32_dll::ole32_dll() : dll("ole32.dll") { // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. // See https://github.com/samhocevar/portable-file-dialogs/issues/51 auto coinit = proc(*this, "CoInitializeEx"); m_state = coinit(nullptr, COINIT_MULTITHREADED); } inline internal::platform::ole32_dll::~ole32_dll() { if (is_initialized()) proc(*this, "CoUninitialize")(); } inline bool internal::platform::ole32_dll::is_initialized() { return m_state == S_OK || m_state == S_FALSE; } #endif // new_style_context implementation #if _WIN32 inline internal::platform::new_style_context::new_style_context() { // Only create one activation context for the whole app lifetime. static HANDLE hctx = create(); if (hctx != INVALID_HANDLE_VALUE) ActivateActCtx(hctx, &m_cookie); } inline internal::platform::new_style_context::~new_style_context() { DeactivateActCtx(0, m_cookie); } inline HANDLE internal::platform::new_style_context::create() { // This “hack” seems to be necessary for this code to work on windows XP. // Without it, dialogs do not show and close immediately. GetError() // returns 0 so I don’t know what causes this. I was not able to reproduce // this behavior on Windows 7 and 10 but just in case, let it be here for // those versions too. // This hack is not required if other dialogs are used (they load comdlg32 // automatically), only if message boxes are used. dll comdlg32("comdlg32.dll"); // Using approach as shown here: https://stackoverflow.com/a/10444161 UINT len = ::GetSystemDirectoryA(nullptr, 0); std::string sys_dir(len, '\0'); ::GetSystemDirectoryA(&sys_dir[0], len); ACTCTXA act_ctx = { // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a // crash with error “default context is already set”. sizeof(act_ctx), ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, "shell32.dll", 0, 0, sys_dir.c_str(), (LPCSTR)124, }; return ::CreateActCtxA(&act_ctx); } #endif // _WIN32 // dialog implementation inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const { return m_async->ready(timeout); } inline bool internal::dialog::kill() const { return m_async->kill(); } inline internal::dialog::dialog() : m_async(std::make_shared()) { } inline std::vector internal::dialog::desktop_helper() const { #if __APPLE__ return { "osascript" }; #else return { flags(flag::has_zenity) ? "zenity" : flags(flag::has_matedialog) ? "matedialog" : flags(flag::has_qarma) ? "qarma" : flags(flag::has_kdialog) ? "kdialog" : "echo" }; #endif } inline std::string internal::dialog::buttons_to_name(choice _choice) { switch (_choice) { case choice::ok_cancel: return "okcancel"; case choice::yes_no: return "yesno"; case choice::yes_no_cancel: return "yesnocancel"; case choice::retry_cancel: return "retrycancel"; case choice::abort_retry_ignore: return "abortretryignore"; /* case choice::ok: */ default: return "ok"; } } inline std::string internal::dialog::get_icon_name(icon _icon) { switch (_icon) { case icon::warning: return "warning"; case icon::error: return "error"; case icon::question: return "question"; // Zenity wants "information" but WinForms wants "info" /* case icon::info: */ default: #if _WIN32 return "info"; #else return "information"; #endif } } // THis is only used for debugging purposes inline std::ostream& operator <<(std::ostream &s, std::vector const &v) { int not_first = 0; for (auto &e : v) s << (not_first++ ? " " : "") << e; return s; } // Properly quote a string for Powershell: replace ' or " with '' or "" // FIXME: we should probably get rid of newlines! // FIXME: the \" sequence seems unsafe, too! // XXX: this is no longer used but I would like to keep it around just in case inline std::string internal::dialog::powershell_quote(std::string const &str) const { return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; } // Properly quote a string for osascript: replace \ or " with \\ or \" // XXX: this also used to replace ' with \' when popen was used, but it would be // smarter to do shell_quote(osascript_quote(...)) if this is needed again. inline std::string internal::dialog::osascript_quote(std::string const &str) const { return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; } // Properly quote a string for the shell: just replace ' with '\'' // XXX: this is no longer used but I would like to keep it around just in case inline std::string internal::dialog::shell_quote(std::string const &str) const { return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; } // file_dialog implementation inline internal::file_dialog::file_dialog(type in_type, std::string const &title, std::string const &default_path /* = "" */, std::vector const &filters /* = {} */, opt options /* = opt::none */) { #if _WIN32 std::string filter_list; std::regex whitespace(" *"); for (size_t i = 0; i + 1 < filters.size(); i += 2) { filter_list += filters[i] + '\0'; filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; } filter_list += '\0'; m_async->start_func([this, in_type, title, default_path, filter_list, options](int *exit_code) -> std::string { (void)exit_code; m_wtitle = internal::str2wstr(title); m_wdefault_path = internal::str2wstr(default_path); auto wfilter_list = internal::str2wstr(filter_list); // Initialise COM. This is required for the new folder selection window, // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) // and to avoid random crashes with GetOpenFileNameW() (see // https://github.com/samhocevar/portable-file-dialogs/issues/51) ole32_dll ole32; // Folder selection uses a different method if (in_type == type::folder) { if (flags(flag::is_vista)) { // On Vista and higher we should be able to use IFileDialog for folder selection IFileDialog *ifd; HRESULT hr = dll::proc(ole32, "CoCreateInstance") (CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd)); // In case CoCreateInstance fails (which it should not), try legacy approach if (SUCCEEDED(hr)) return select_folder_vista(ifd, options & opt::force_path); } BROWSEINFOW bi; memset(&bi, 0, sizeof(bi)); bi.lpfn = &bffcallback; bi.lParam = (LPARAM)this; if (flags(flag::is_vista)) { if (ole32.is_initialized()) bi.ulFlags |= BIF_NEWDIALOGSTYLE; bi.ulFlags |= BIF_EDITBOX; bi.ulFlags |= BIF_STATUSTEXT; } auto *list = SHBrowseForFolderW(&bi); std::string ret; if (list) { auto buffer = new wchar_t[MAX_PATH]; SHGetPathFromIDListW(list, buffer); dll::proc(ole32, "CoTaskMemFree")(list); ret = internal::wstr2str(buffer); delete[] buffer; } return ret; } OPENFILENAMEW ofn; memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(OPENFILENAMEW); ofn.hwndOwner = GetActiveWindow(); ofn.lpstrFilter = wfilter_list.c_str(); auto woutput = std::wstring(MAX_PATH * 256, L'\0'); ofn.lpstrFile = (LPWSTR)woutput.data(); ofn.nMaxFile = (DWORD)woutput.size(); if (!m_wdefault_path.empty()) { // If a directory was provided, use it as the initial directory. If // a valid path was provided, use it as the initial file. Otherwise, // let the Windows API decide. auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) ofn.lpstrInitialDir = m_wdefault_path.c_str(); else if (m_wdefault_path.size() <= woutput.size()) //second argument is size of buffer, not length of string StringCchCopyW(ofn.lpstrFile, MAX_PATH*256+1, m_wdefault_path.c_str()); else { ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data(); ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size(); } } ofn.lpstrTitle = m_wtitle.c_str(); ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; dll comdlg32("comdlg32.dll"); // Apply new visual style (required for windows XP) new_style_context ctx; if (in_type == type::save) { if (!(options & opt::force_overwrite)) ofn.Flags |= OFN_OVERWRITEPROMPT; dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); if (get_save_file_name(&ofn) == 0) return ""; return internal::wstr2str(woutput.c_str()); } else { if (options & opt::multiselect) ofn.Flags |= OFN_ALLOWMULTISELECT; ofn.Flags |= OFN_PATHMUSTEXIST; dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); if (get_open_file_name(&ofn) == 0) return ""; } std::string prefix; for (wchar_t const *p = woutput.c_str(); *p; ) { auto filename = internal::wstr2str(p); p += wcslen(p); // In multiselect mode, we advance p one wchar further and // check for another filename. If there is one and the // prefix is empty, it means we just read the prefix. if ((options & opt::multiselect) && *++p && prefix.empty()) { prefix = filename + "/"; continue; } m_vector_result.push_back(prefix + filename); } return ""; }); #else auto command = desktop_helper(); if (is_osascript()) { std::string script = "set ret to choose"; switch (in_type) { case type::save: script += " file name"; break; case type::open: default: script += " file"; if (options & opt::multiselect) script += " with multiple selections allowed"; break; case type::folder: script += " folder"; break; } if (default_path.size()) script += " default location " + osascript_quote(default_path); script += " with prompt " + osascript_quote(title); if (in_type == type::open) { // Concatenate all user-provided filter patterns std::string patterns; for (size_t i = 0; i < filters.size() / 2; ++i) patterns += " " + filters[2 * i + 1]; // Split the pattern list to check whether "*" is in there; if it // is, we have to disable filters because there is no mechanism in // OS X for the user to override the filter. std::regex sep("\\s+"); std::string filter_list; bool has_filter = true; std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); std::sregex_token_iterator end; for ( ; iter != end; ++iter) { auto pat = iter->str(); if (pat == "*" || pat == "*.*") has_filter = false; else if (internal::starts_with(pat, "*.")) filter_list += (filter_list.size() == 0 ? "" : ",") + osascript_quote(pat.substr(2, pat.size() - 2)); } if (has_filter && filter_list.size() > 0) script += " of type {" + filter_list + "}"; } if (in_type == type::open && (options & opt::multiselect)) { script += "\nset s to \"\""; script += "\nrepeat with i in ret"; script += "\n set s to s & (POSIX path of i) & \"\\n\""; script += "\nend repeat"; script += "\ncopy s to stdout"; } else { script += "\nPOSIX path of ret"; } command.push_back("-e"); command.push_back(script); } else if (is_zenity()) { command.push_back("--file-selection"); command.push_back("--filename=" + default_path); command.push_back("--title"); command.push_back(title); command.push_back("--separator=\n"); for (size_t i = 0; i < filters.size() / 2; ++i) { command.push_back("--file-filter"); command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); } if (in_type == type::save) command.push_back("--save"); if (in_type == type::folder) command.push_back("--directory"); if (!(options & opt::force_overwrite)) command.push_back("--confirm-overwrite"); if (options & opt::multiselect) command.push_back("--multiple"); } else if (is_kdialog()) { switch (in_type) { case type::save: command.push_back("--getsavefilename"); break; case type::open: command.push_back("--getopenfilename"); break; case type::folder: command.push_back("--getexistingdirectory"); break; } if (options & opt::multiselect) command.push_back(" --multiple"); command.push_back(default_path); std::string filter; for (size_t i = 0; i < filters.size() / 2; ++i) filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; command.push_back(filter); command.push_back("--title"); command.push_back(title); } if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; m_async->start_process(command); #endif } inline std::string internal::file_dialog::string_result() { #if _WIN32 return m_async->result(); #else auto ret = m_async->result(); // Strip potential trailing newline (zenity). Also strip trailing slash // added by osascript for consistency with other backends. while (ret.back() == '\n' || ret.back() == '/') ret = ret.substr(0, ret.size() - 1); return ret; #endif } inline std::vector internal::file_dialog::vector_result() { #if _WIN32 m_async->result(); return m_vector_result; #else std::vector ret; auto result = m_async->result(); for (;;) { // Split result along newline characters auto i = result.find('\n'); if (i == 0 || i == std::string::npos) break; ret.push_back(result.substr(0, i)); result = result.substr(i + 1, result.size()); } return ret; #endif } #if _WIN32 // Use a static function to pass as BFFCALLBACK for legacy folder select inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData) { auto inst = (file_dialog *)pData; switch (uMsg) { case BFFM_INITIALIZED: SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str()); break; } return 0; } inline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, bool force_path) { std::string result; IShellItem *folder; // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) dll shell32("shell32.dll"); dll::proc create_item(shell32, "SHCreateItemFromParsingName"); if (!create_item) return ""; auto hr = create_item(m_wdefault_path.c_str(), nullptr, IID_PPV_ARGS(&folder)); // Set default folder if found. This only sets the default folder. If // Windows has any info about the most recently selected folder, it // will display it instead. Generally, calling SetFolder() to set the // current directory “is not a good or expected user experience and // should therefore be avoided”: // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder if (SUCCEEDED(hr)) { if (force_path) ifd->SetFolder(folder); else ifd->SetDefaultFolder(folder); folder->Release(); } // Set the dialog title and option to select folders ifd->SetOptions(FOS_PICKFOLDERS); ifd->SetTitle(m_wtitle.c_str()); hr = ifd->Show(GetActiveWindow()); if (SUCCEEDED(hr)) { IShellItem* item; hr = ifd->GetResult(&item); if (SUCCEEDED(hr)) { wchar_t* wselected = nullptr; item->GetDisplayName(SIGDN_FILESYSPATH, &wselected); item->Release(); if (wselected) { result = internal::wstr2str(std::wstring(wselected)); dll::proc(ole32_dll(), "CoTaskMemFree")(wselected); } } } ifd->Release(); return result; } #endif // notify implementation inline notify::notify(std::string const &title, std::string const &message, icon _icon /* = icon::info */) { if (_icon == icon::question) // Not supported by notifications _icon = icon::info; #if _WIN32 // Use a static shared pointer for notify_icon so that we can delete // it whenever we need to display a new one, and we can also wait // until the program has finished running. struct notify_icon_data : public NOTIFYICONDATAW { ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } }; static std::shared_ptr nid; // Release the previous notification icon, if any, and allocate a new // one. Note that std::make_shared() does value initialization, so there // is no need to memset the structure. nid = nullptr; nid = std::make_shared(); // For XP support nid->cbSize = NOTIFYICONDATAW_V2_SIZE; nid->hWnd = nullptr; nid->uID = 0; // Flag Description: // - NIF_ICON The hIcon member is valid. // - NIF_MESSAGE The uCallbackMessage member is valid. // - NIF_TIP The szTip member is valid. // - NIF_STATE The dwState and dwStateMask members are valid. // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and dwInfoFlags members are valid. // - NIF_GUID Reserved. nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; // Flag Description // - NIIF_ERROR An error icon. // - NIIF_INFO An information icon. // - NIIF_NONE No icon. // - NIIF_WARNING A warning icon. // - NIIF_ICON_MASK Version 6.0. Reserved. // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips switch (_icon) { case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break; case icon::error: nid->dwInfoFlags = NIIF_ERROR; break; /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; } ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL { ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); return false; }; nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get()); nid->uTimeout = 5000; StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); // Display the new icon Shell_NotifyIconW(NIM_ADD, nid.get()); #else auto command = desktop_helper(); if (is_osascript()) { command.push_back("-e"); command.push_back("display notification " + osascript_quote(message) + " with title " + osascript_quote(title)); } else if (is_zenity()) { command.push_back("--notification"); command.push_back("--window-icon"); command.push_back(get_icon_name(_icon)); command.push_back("--text"); command.push_back(title + "\n" + message); } else if (is_kdialog()) { command.push_back("--icon"); command.push_back(get_icon_name(_icon)); command.push_back("--title"); command.push_back(title); command.push_back("--passivepopup"); command.push_back(message); command.push_back("5"); } if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; m_async->start_process(command); #endif } // message implementation inline message::message(std::string const &title, std::string const &text, choice _choice /* = choice::ok_cancel */, icon _icon /* = icon::info */) { #if _WIN32 UINT style = MB_TOPMOST; switch (_icon) { case icon::warning: style |= MB_ICONWARNING; break; case icon::error: style |= MB_ICONERROR; break; case icon::question: style |= MB_ICONQUESTION; break; /* case icon::info: */ default: style |= MB_ICONINFORMATION; break; } switch (_choice) { case choice::ok_cancel: style |= MB_OKCANCEL; break; case choice::yes_no: style |= MB_YESNO; break; case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break; case choice::retry_cancel: style |= MB_RETRYCANCEL; break; case choice::abort_retry_ignore: style |= MB_ABORTRETRYIGNORE; break; /* case choice::ok: */ default: style |= MB_OK; break; } m_mappings[IDCANCEL] = button::cancel; m_mappings[IDOK] = button::ok; m_mappings[IDYES] = button::yes; m_mappings[IDNO] = button::no; m_mappings[IDABORT] = button::abort; m_mappings[IDRETRY] = button::retry; m_mappings[IDIGNORE] = button::ignore; m_async->start_func([this, text, title, style](int* exit_code) -> std::string { auto wtext = internal::str2wstr(text); auto wtitle = internal::str2wstr(title); // Apply new visual style (required for all Windows versions) new_style_context ctx; *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); return ""; }); #elif __EMSCRIPTEN__ std::string full_message; switch (_icon) { case icon::warning: full_message = "⚠️"; break; case icon::error: full_message = "⛔"; break; case icon::question: full_message = "❓"; break; /* case icon::info: */ default: full_message = "ℹ"; break; } full_message += ' ' + title + "\n\n" + text; // This does not really start an async task; it just passes the // EM_ASM_INT return value to a fake start() function. m_async->start(EM_ASM_INT( { if ($1) return window.confirm(UTF8ToString($0)) ? 0 : -1; alert(UTF8ToString($0)); return 0; }, full_message.c_str(), _choice == choice::ok_cancel)); #else auto command = desktop_helper(); if (is_osascript()) { std::string script = "display dialog " + osascript_quote(text) + " with title " + osascript_quote(title); switch (_choice) { case choice::ok_cancel: script += "buttons {\"OK\", \"Cancel\"}" " default button \"OK\"" " cancel button \"Cancel\""; m_mappings[256] = button::cancel; break; case choice::yes_no: script += "buttons {\"Yes\", \"No\"}" " default button \"Yes\"" " cancel button \"No\""; m_mappings[256] = button::no; break; case choice::yes_no_cancel: script += "buttons {\"Yes\", \"No\", \"Cancel\"}" " default button \"Yes\"" " cancel button \"Cancel\""; m_mappings[256] = button::cancel; break; case choice::retry_cancel: script += "buttons {\"Retry\", \"Cancel\"}" " default button \"Retry\"" " cancel button \"Cancel\""; m_mappings[256] = button::cancel; break; case choice::abort_retry_ignore: script += "buttons {\"Abort\", \"Retry\", \"Ignore\"}" " default button \"Retry\"" " cancel button \"Retry\""; m_mappings[256] = button::cancel; break; case choice::ok: default: script += "buttons {\"OK\"}" " default button \"OK\"" " cancel button \"OK\""; m_mappings[256] = button::ok; break; } script += " with icon "; switch (_icon) { #define PFD_OSX_ICON(n) "alias ((path to library folder from system domain) as text " \ "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" case icon::info: default: script += PFD_OSX_ICON("ToolBarInfo"); break; case icon::warning: script += "caution"; break; case icon::error: script += "stop"; break; case icon::question: script += PFD_OSX_ICON("GenericQuestionMarkIcon"); break; #undef PFD_OSX_ICON } command.push_back("-e"); command.push_back(script); } else if (is_zenity()) { switch (_choice) { case choice::ok_cancel: command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" }); break; case choice::yes_no: // Do not use standard --question because it causes “No” to return -1, // which is inconsistent with the “Yes/No/Cancel” mode below. command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" }); break; case choice::yes_no_cancel: command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No", "--extra-button=Yes" }); break; case choice::retry_cancel: command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" }); break; case choice::abort_retry_ignore: command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore", "--extra-button=Abort", "--extra-button=Retry" }); break; case choice::ok: default: switch (_icon) { case icon::error: command.push_back("--error"); break; case icon::warning: command.push_back("--warning"); break; default: command.push_back("--info"); break; } } command.insert(command.end(), { "--title", title, "--width=300", "--height=0", // sensible defaults "--text", text, "--icon-name=dialog-" + get_icon_name(_icon) }); } else if (is_kdialog()) { if (_choice == choice::ok) { switch (_icon) { case icon::error: command.push_back("--error"); break; case icon::warning: command.push_back("--sorry"); break; default: command.push_back("--msgbox"); break; } } else { std::string flag = "--"; if (_icon == icon::warning || _icon == icon::error) flag += "warning"; flag += "yesno"; if (_choice == choice::yes_no_cancel) flag += "cancel"; command.push_back(flag); if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) { m_mappings[0] = button::yes; m_mappings[256] = button::no; } } command.push_back(text); command.push_back("--title"); command.push_back(title); // Must be after the above part if (_choice == choice::ok_cancel) command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" }); } if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; m_async->start_process(command); #endif } inline button message::result() { int exit_code; auto ret = m_async->result(&exit_code); // osascript will say "button returned:Cancel\n" // and others will just say "Cancel\n" if (exit_code < 0 || // this means cancel internal::ends_with(ret, "Cancel\n")) return button::cancel; if (internal::ends_with(ret, "OK\n")) return button::ok; if (internal::ends_with(ret, "Yes\n")) return button::yes; if (internal::ends_with(ret, "No\n")) return button::no; if (internal::ends_with(ret, "Abort\n")) return button::abort; if (internal::ends_with(ret, "Retry\n")) return button::retry; if (internal::ends_with(ret, "Ignore\n")) return button::ignore; if (m_mappings.count(exit_code) != 0) return m_mappings[exit_code]; return exit_code == 0 ? button::ok : button::cancel; } // open_file implementation inline open_file::open_file(std::string const &title, std::string const &default_path /* = "" */, std::vector const &filters /* = { "All Files", "*" } */, opt options /* = opt::none */) : file_dialog(type::open, title, default_path, filters, options) { } inline open_file::open_file(std::string const &title, std::string const &default_path, std::vector const &filters, bool allow_multiselect) : open_file(title, default_path, filters, (allow_multiselect ? opt::multiselect : opt::none)) { } inline std::vector open_file::result() { return vector_result(); } // save_file implementation inline save_file::save_file(std::string const &title, std::string const &default_path /* = "" */, std::vector const &filters /* = { "All Files", "*" } */, opt options /* = opt::none */) : file_dialog(type::save, title, default_path, filters, options) { } inline save_file::save_file(std::string const &title, std::string const &default_path, std::vector const &filters, bool confirm_overwrite) : save_file(title, default_path, filters, (confirm_overwrite ? opt::none : opt::force_overwrite)) { } inline std::string save_file::result() { return string_result(); } // select_folder implementation inline select_folder::select_folder(std::string const &title, std::string const &default_path /* = "" */, opt options /* = opt::none */) : file_dialog(type::folder, title, default_path, {}, options) { } inline std::string select_folder::result() { return string_result(); } #endif // PFD_SKIP_IMPLEMENTATION } // namespace pfd ================================================ FILE: examples/r2t2/CMakeLists.txt ================================================ # # r2t2 set(TARGET r2t2) if (NOT EMSCRIPTEN) add_executable(${TARGET} main.cpp ) target_include_directories(${TARGET} PRIVATE .. ) target_link_libraries(${TARGET} PRIVATE ggwave-common ggwave ) endif() # # r2t2-rx set(TARGET r2t2-rx) if (NOT EMSCRIPTEN) add_executable(${TARGET} r2t2-rx.cpp ) target_include_directories(${TARGET} PRIVATE .. ${SDL2_INCLUDE_DIRS} ) target_link_libraries(${TARGET} PRIVATE ggwave-common ggwave ${SDL2_LIBRARIES} ) else() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY) add_executable(${TARGET} r2t2-rx.cpp ) target_include_directories(${TARGET} PRIVATE .. ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ ) target_link_libraries(${TARGET} PRIVATE ggwave-common ggwave ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/main.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/main.js COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/plucky.mp3 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/plucky.mp3 COPYONLY) endif() ================================================ FILE: examples/r2t2/README.md ================================================ # r2t2 Transmit data with the PC speaker

Vid. r2t2 demonstration

This is a command-line program that encodes short messages/data into audio and plays it via the motherboard's PC speaker. To use this tool, you need to attach a [piezo speaker/buzzer](https://en.wikipedia.org/wiki/Piezoelectric_speaker) to your motherboard. Some computers have such speaker already attached. You can then run the following commands: ```bash # transmit the message "test" with default protocol "[R2T2] Normal" echo test | sudo r2t2 # transmit the message "hello" with protocol "[R2T2] Fast" echo hello | sudo r2t2 -t10 # transmit the message "foo bar" with protocol "[R2T2] Fastest" echo "foo bar" | sudo r2t2 -t11 ``` To receive the transmitted message, open the following page on your phone and place it near the speaker: https://r2t2.ggerganov.com ## Applications This tool can be useful when you need to transmit data from air-gapped machines. The hardware requirements are very cheap - you only need a PC speaker. For example, you can make an automated script to periodically emit some sensor data, which can be received by someone nearby running the `r2t2` receiver application. ## Requirements - [PC speaker / buzzer](https://www.amazon.com/SoundOriginal-Motherboard-Internal-Speaker-Buzzer/dp/B01DM56TFY/ref=sr_1_1_sspa?dchild=1&keywords=Motherboard+Speaker&qid=1614504288&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEzTkpFVlk4SzRXS1lWJmVuY3J5cHRlZElkPUEwOTU3NzI3MkpCQUZJRFIxSzZGNSZlbmNyeXB0ZWRBZElkPUEwODk0ODQ4MlVBQzFSR1RHMTYyMiZ3aWRnZXROYW1lPXNwX2F0ZiZhY3Rpb249Y2xpY2tSZWRpcmVjdCZkb05vdExvZ0NsaWNrPXRydWU=) attached to the motherboard. Here are the ones that I use:

Talking buttons Talking buttons

Img. Left: PC speaker plugged into a motherboard. Right: two PC speakers with a coin for size comparison

- Unix operating system - Add the `pcspkr` kernel module: `sudo modprobe pcspkr` - The program requires to run as `sudo` in order to access the PC speaker ## Build ```bash git clone https://github.com/ggerganov/ggwave --recursive cd ggwave mkdir build && cd build make ./bin/r2t2 ``` ## Acknowledgements Thanks to [Radoslav Gerganov](https://github.com/rgerganov) for this cool idea! ================================================ FILE: examples/r2t2/build_timestamp-tmpl.h ================================================ static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)"; ================================================ FILE: examples/r2t2/index-tmpl.html ================================================ r2t2

r2t2

Press the Init button and place the microphone near the PC speaker to receive messages



Standard output:

Downloading...
| Build time: @GIT_DATE@ | Commit hash: @GIT_SHA1@ | Commit subject: @GIT_COMMIT_SUBJECT@ |
================================================ FILE: examples/r2t2/main.cpp ================================================ #include "ggwave/ggwave.h" #include "ggwave-common.h" #include #include #include #include #include #include #define CONSOLE "/dev/tty0" #include #include #include #include void processTone(int fd, double freq_hz, long duration_ms, bool useBeep, bool printTones, bool printArduino) { if (printTones) { printf("TONE %8.2f Hz %5ld ms\n", freq_hz, duration_ms); return; } if (printArduino) { printf("tone(kPinSpeaker, %8.2f); delay(%4ld);\n", freq_hz, duration_ms); return; } if (useBeep) { static char cmd[128]; snprintf(cmd, 128, "beep -f %g -l %ld", freq_hz, duration_ms); int ret = system(cmd); if (ret != 0) { printf("system(\"%s\") failed with %d\n", cmd, ret); } return; } long pitch = std::round(1193180.0/freq_hz); long ms = std::round(duration_ms); ioctl(fd, KDMKTONE, (ms<<16)|pitch); usleep(ms*1000); } int main(int argc, char** argv) { printf("Usage: %s [-p] [-b] [-tN] [-lN]\n", argv[0]); printf(" -p - print tones, no playback\n"); printf(" -A - print Arduino code\n"); printf(" -b - use 'beep' command\n"); printf(" -s - use Direct Sequence Spread (DSS)\n"); printf(" -tN - transmission protocol\n"); printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); printf("\n"); auto & protocols = GGWave::Protocols::tx(); protocols = { { { "[R2T2] Normal", 64, 9, 1, 2, true, }, { "[R2T2] Fast", 64, 6, 1, 2, true, }, { "[R2T2] Fastest", 64, 3, 1, 2, true, }, { "[R2T2] Low Normal", 16, 9, 1, 2, true, }, { "[R2T2] Low Fast", 16, 6, 1, 2, true, }, { "[R2T2] Low Fastest", 16, 3, 1, 2, true, }, } }; const auto argm = parseCmdArguments(argc, argv); const bool printTones = argm.count("p") > 0; const bool printArduino = argm.count("A") > 0; const bool useBeep = argm.count("b") > 0; const bool useDSS = argm.count("s") > 0; const int txProtocolId = argm.count("t") == 0 ? 0 : std::stoi(argm.at("t")); const int payloadLength = argm.count("l") == 0 ? 16 : std::stoi(argm.at("l")); GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS; GGWave ggWave({ payloadLength, GGWave::kDefaultSampleRate, GGWave::kDefaultSampleRate, GGWave::kDefaultSampleRate, GGWave::kDefaultSamplesPerFrame, GGWave::kDefaultSoundMarkerThreshold, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_F32, mode, }); printf("Available Tx protocols:\n"); for (int i = 0; i < (int) protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled && protocol.name) { printf(" -t%-2d : %-16s\n", i, protocol.name); } } printf("\n"); if (txProtocolId < 0 || txProtocolId >= (int) protocols.size()) { fprintf(stderr, "Unknown Tx protocol %d\n", txProtocolId); return -3; } printf("Selecting Tx protocol %d\n", txProtocolId); int fd = 1; if (useBeep == false && printTones == false && printArduino == false) { if (ioctl(fd, KDMKTONE, 0)) { fd = open(CONSOLE, O_RDONLY); } if (fd < 0) { perror(CONSOLE); fprintf(stderr, "This program must be run as root\n"); return 1; } } fprintf(stderr, "Enter a text message:\n"); std::string message; std::getline(std::cin, message); printf("\n"); if (message.size() == 0) { fprintf(stderr, "Invalid message: size = 0\n"); return -2; } if ((int) message.size() > payloadLength) { fprintf(stderr, "Invalid message: size > %d\n", payloadLength); return -3; } ggWave.init(message.size(), message.data(), GGWave::TxProtocolId(txProtocolId), 10); ggWave.encode(); const auto & protocol = protocols[txProtocolId]; const auto tones = ggWave.txTones(); const auto duration_ms = protocol.txDuration_ms(ggWave.samplesPerFrame(), ggWave.sampleRateOut()); for (auto & tone : tones) { const auto freq_hz = (protocol.freqStart + tone)*ggWave.hzPerSample(); processTone(fd, freq_hz, duration_ms, useBeep, printTones, printArduino); } return 0; } ================================================ FILE: examples/r2t2/main.js ================================================ function transmitText(sText) { var r = new Uint8Array(256); for (var i = 0; i < sText.length; ++i) { r[i] = sText.charCodeAt(i); } var buffer = Module._malloc(256); Module.writeArrayToMemory(r, buffer, 256); Module._sendData(sText.length, buffer, protocolId, volume); Module._free(buffer); } var firstTimeFail = false; var peerInfo = document.querySelector('a#peer-info'); function updatePeerInfo() { if (typeof Module === 'undefined') return; var framesLeftToRecord = Module._getFramesLeftToRecord(); var framesToRecord = Module._getFramesToRecord(); var framesLeftToAnalyze = Module._getFramesLeftToAnalyze(); var framesToAnalyze = Module._getFramesToAnalyze(); if (framesToAnalyze > 0) { peerInfo.innerHTML= "Analyzing Rx data: "; peerReceive.innerHTML= ""; } else if (framesLeftToRecord > Math.max(0, 0.05*framesToRecord)) { firstTimeFail = true; peerInfo.innerHTML= "Transmission in progress: "; } else if (framesToRecord > 0) { peerInfo.innerHTML= "Analyzing Rx data ..."; } else if (framesToRecord == 0) { peerInfo.innerHTML= "

Listening for waves ...

"; } else if (framesToRecord == -1) { if (firstTimeFail) { playSound("/media/case-closed"); firstTimeFail = false; } peerInfo.innerHTML= "

Failed to decode Rx data

"; } } function updateRx() { if (typeof Module === 'undefined') return; Module._getText(bufferRx); var result = ""; for (var i = 0; i < 140; ++i){ result += (String.fromCharCode((Module.HEAPU8)[bufferRx + i])); brx[i] = (Module.HEAPU8)[bufferRx + i]; } document.getElementById('rxData').innerHTML = result; } ================================================ FILE: examples/r2t2/r2t2-rx.cpp ================================================ #include "ggwave-common.h" #include "ggwave/ggwave.h" #ifdef __EMSCRIPTEN__ #include "build_timestamp.h" #include #else #define EMSCRIPTEN_KEEPALIVE #endif #include #include #include #include #include #include namespace { std::string g_defaultCaptureDeviceName = ""; SDL_AudioDeviceID g_devIdInp = 0; SDL_AudioDeviceID g_devIdOut = 0; SDL_AudioSpec g_obtainedSpecInp; SDL_AudioSpec g_obtainedSpecOut; GGWave *g_ggWave = nullptr; } static std::function g_doInit; static std::function g_setWindowSize; static std::function g_mainUpdate; void mainUpdate(void *) { g_mainUpdate(); } // JS interface extern "C" { EMSCRIPTEN_KEEPALIVE int sendData(int textLength, const char * text, int protocolId, int volume) { g_ggWave->init(textLength, text, GGWave::TxProtocolId(protocolId), volume); return 0; } EMSCRIPTEN_KEEPALIVE int getText(char * text) { std::copy(g_ggWave->rxData().begin(), g_ggWave->rxData().end(), text); return 0; } EMSCRIPTEN_KEEPALIVE float sampleRate() { return g_ggWave->sampleRateInp(); } EMSCRIPTEN_KEEPALIVE int framesToRecord() { return g_ggWave->rxFramesToRecord(); } EMSCRIPTEN_KEEPALIVE int framesLeftToRecord() { return g_ggWave->rxFramesLeftToRecord(); } EMSCRIPTEN_KEEPALIVE int framesToAnalyze() { return g_ggWave->rxFramesToAnalyze(); } EMSCRIPTEN_KEEPALIVE int framesLeftToAnalyze() { return g_ggWave->rxFramesLeftToAnalyze(); } EMSCRIPTEN_KEEPALIVE int hasDeviceOutput() { return g_devIdOut; } EMSCRIPTEN_KEEPALIVE int hasDeviceCapture() { return g_devIdInp; } EMSCRIPTEN_KEEPALIVE int doInit() { return g_doInit(); } } void GGWave_setDefaultCaptureDeviceName(std::string name) { g_defaultCaptureDeviceName = std::move(name); } bool GGWave_init( const int playbackId, const int captureId, const int payloadLength, const float sampleRateOffset, const bool useDSS) { if (g_devIdInp && g_devIdOut) { return false; } if (g_devIdInp == 0 && g_devIdOut == 0) { 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)); } } } bool reinit = false; if (g_devIdOut == 0) { printf("Initializing playback ...\n"); SDL_AudioSpec playbackSpec; SDL_zero(playbackSpec); playbackSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset; playbackSpec.format = AUDIO_S16SYS; playbackSpec.channels = 1; playbackSpec.samples = 16*1024; playbackSpec.callback = NULL; SDL_zero(g_obtainedSpecOut); if (playbackId >= 0) { printf("Attempt to open playback device %d : '%s' ...\n", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE)); g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } else { printf("Attempt to open default playback device ...\n"); g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } if (!g_devIdOut) { printf("Couldn't open an audio device for playback: %s!\n", SDL_GetError()); g_devIdOut = 0; } else { printf("Obtained spec for output device (SDL Id = %d):\n", g_devIdOut); printf(" - Sample rate: %d (required: %d)\n", g_obtainedSpecOut.freq, playbackSpec.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecOut.format, playbackSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecOut.channels, playbackSpec.channels); printf(" - Samples per frame: %d (required: %d)\n", g_obtainedSpecOut.samples, playbackSpec.samples); if (g_obtainedSpecOut.format != playbackSpec.format || g_obtainedSpecOut.channels != playbackSpec.channels || g_obtainedSpecOut.samples != playbackSpec.samples) { g_devIdOut = 0; SDL_CloseAudio(); fprintf(stderr, "Failed to initialize playback SDL_OpenAudio!"); return false; } reinit = true; } } if (g_devIdInp == 0) { SDL_AudioSpec captureSpec; captureSpec = g_obtainedSpecOut; captureSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset; captureSpec.format = AUDIO_F32SYS; captureSpec.samples = 1024; SDL_zero(g_obtainedSpecInp); if (captureId >= 0) { printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE)); g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } else { printf("Attempt to open default capture device ...\n"); g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } if (!g_devIdInp) { printf("Couldn't open an audio device for capture: %s!\n", SDL_GetError()); g_devIdInp = 0; } else { printf("Obtained spec for input device (SDL Id = %d):\n", g_devIdInp); printf(" - Sample rate: %d\n", g_obtainedSpecInp.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecInp.format, captureSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecInp.channels, captureSpec.channels); printf(" - Samples per frame: %d\n", g_obtainedSpecInp.samples); reinit = true; } } GGWave::SampleFormat sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED; GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED; switch (g_obtainedSpecInp.format) { case AUDIO_U8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U8; break; case AUDIO_S8: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I8; break; case AUDIO_U16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U16; break; case AUDIO_S16SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break; case AUDIO_S32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break; case AUDIO_F32SYS: sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break; } switch (g_obtainedSpecOut.format) { case AUDIO_U8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; break; case AUDIO_S8: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8; break; case AUDIO_U16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break; case AUDIO_S16SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break; case AUDIO_S32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break; case AUDIO_F32SYS: sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break; break; } if (reinit) { if (g_ggWave) delete g_ggWave; GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX; if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS; g_ggWave = new GGWave({ payloadLength, (float) g_obtainedSpecInp.freq, (float) g_obtainedSpecOut.freq, GGWave::kDefaultSampleRate, GGWave::kDefaultSamplesPerFrame, GGWave::kDefaultSoundMarkerThreshold, sampleFormatInp, sampleFormatOut, mode, }); } return true; } GGWave *& GGWave_instance() { return g_ggWave; } bool GGWave_mainLoop() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } if (g_ggWave->txHasData() == 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->samplesPerFrame()*g_ggWave->sampleSizeOut()) { SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE); const int nHave = (int) SDL_GetQueuedAudioSize(g_devIdInp); const int nNeed = g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeInp(); if (::getTime_ms(tLastNoData, tNow) > 500.0f && nHave >= nNeed) { static std::vector dataInp(nNeed); SDL_DequeueAudio(g_devIdInp, dataInp.data(), nNeed); if (g_ggWave->decode(dataInp.data(), dataInp.size()) == false) { fprintf(stderr, "Warning: failed to decode input data!\n"); } if (nHave > 32*nNeed) { fprintf(stderr, "Warning: slow processing, clearing queued audio buffer of %d bytes ...\n", SDL_GetQueuedAudioSize(g_devIdInp)); SDL_ClearQueuedAudio(g_devIdInp); } } else { SDL_ClearQueuedAudio(g_devIdInp); } } else { tLastNoData = tNow; } } else { SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE); SDL_PauseAudioDevice(g_devIdInp, SDL_TRUE); const auto nBytes = g_ggWave->encode(); SDL_QueueAudio(g_devIdOut, g_ggWave->txWaveform(), nBytes); } return true; } bool GGWave_deinit() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } delete g_ggWave; g_ggWave = nullptr; SDL_PauseAudioDevice(g_devIdInp, 1); SDL_CloseAudioDevice(g_devIdInp); SDL_PauseAudioDevice(g_devIdOut, 1); SDL_CloseAudioDevice(g_devIdOut); g_devIdInp = 0; g_devIdOut = 0; return true; } int main(int argc, char** argv) { #ifdef __EMSCRIPTEN__ printf("Build time: %s\n", BUILD_TIMESTAMP); printf("Press the Init button to start\n"); if (argv[1]) { GGWave_setDefaultCaptureDeviceName(argv[1]); } #else printf("Usage: %s [-cN] [-lN]\n", argv[0]); printf(" -cN - select capture device N\n"); printf(" -lN - fixed payload length of size N, N in [1, %d]\n", GGWave::kMaxLengthFixed); printf(" -s - use Direct Sequence Spread (DSS)\n"); printf("\n"); #endif GGWave::Protocols::rx() = { { { "[R2T2] Normal", 64, 9, 1, 2, true, }, { "[R2T2] Fast", 64, 6, 1, 2, true, }, { "[R2T2] Fastest", 64, 3, 1, 2, true, }, { "[R2T2] Low Normal", 16, 9, 1, 2, true, }, { "[R2T2] Low Fast", 16, 6, 1, 2, true, }, { "[R2T2] Low Fastest", 16, 3, 1, 2, true, }, } }; const auto argm = parseCmdArguments(argc, argv); const int captureId = argm.count("c") == 0 ? 0 : std::stoi(argm.at("c")); const int payloadLength = argm.count("l") == 0 ? 16 : std::stoi(argm.at("l")); const bool useDSS = argm.count("s") > 0; bool isInitialized = false; g_doInit = [&]() { if (GGWave_init(0, captureId, payloadLength, 0, useDSS) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); return false; } isInitialized = true; printf("Listening for payload with length = %d bytes ..\n", payloadLength); return true; }; g_mainUpdate = [&]() { if (isInitialized == false) { return true; } GGWave_mainLoop(); return true; }; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true); #else if (g_doInit() == false) { printf("Error: failed to initialize audio\n"); return -2; } while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (g_mainUpdate() == false) break; } GGWave_deinit(); // Cleanup SDL_CloseAudio(); SDL_Quit(); #endif return 0; } ================================================ FILE: examples/r2t2/style.css ================================================ body { margin: 0; background-color: white; -webkit-font-smoothing: subpixel-antialiased; font-smoothing: subpixel-antialiased; } #screen { margin: 0; padding: 0; font-size: 13px; height: 100%; font: sans-serif; } .no-sel { -moz-user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; -ms-user-select:none; user-select:none; -o-user-select:none; } .cell { pointer-events: none; } .cell-version { padding-left: 4px; padding-top: 0.5em; text-align: left; display: inline-block; float: left; color: rgba(0, 0, 0, 0.75); } .cell-about { padding-right: 24px; padding-top: 0.5em; text-align: right; display: inline-block; float: right; } .nav-link { text-decoration: none; color: rgba(0, 0, 0, 1.0); } #main-container { font-size:12px; font-family: monospace; } textarea { font-size:12px; font-family: monospace; } .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten { text-align: center; } div.emscripten_border { border: 1px solid black; } canvas.emscripten { border: 0px none; background-color: black; } .spinner { height: 30px; width: 30px; margin: 0; margin-top: 20px; margin-left: 20px; display: inline-block; vertical-align: top; -webkit-animation: rotation .8s linear infinite; -moz-animation: rotation .8s linear infinite; -o-animation: rotation .8s linear infinite; animation: rotation 0.8s linear infinite; border-left: 5px solid rgb(235, 235, 235); border-right: 5px solid rgb(235, 235, 235); border-bottom: 5px solid rgb(235, 235, 235); border-top: 5px solid rgb(120, 120, 120); border-radius: 100%; background-color: rgb(189, 215, 46); } @-webkit-keyframes rotation { from {-webkit-transform: rotate(0deg);} to {-webkit-transform: rotate(360deg);} } @-moz-keyframes rotation { from {-moz-transform: rotate(0deg);} to {-moz-transform: rotate(360deg);} } @-o-keyframes rotation { from {-o-transform: rotate(0deg);} to {-o-transform: rotate(360deg);} } @keyframes rotation { from {transform: rotate(0deg);} to {transform: rotate(360deg);} } #status { display: inline-block; vertical-align: top; margin-top: 30px; margin-left: 20px; font-weight: bold; color: rgb(120, 120, 120); } #progress { height: 20px; width: 30px; } #output { width: 100%; height: 200px; margin: 0 auto; margin-top: 10px; border-left: 0px; border-right: 0px; padding-left: 0px; padding-right: 0px; background-color: black; color: white; font-size:10px; font-family: 'Lucida Console', Monaco, monospace; outline: none; } .led-box { height: 30px; width: 25%; margin: 10px 0; float: left; } .led-box p { font-size: 12px; text-align: center; margin: 1em; } .led-red { margin: 0 auto; width: 12px; height: 12px; background-color: #F00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 12px; -webkit-animation: blinkRed 0.5s infinite; -moz-animation: blinkRed 0.5s infinite; -ms-animation: blinkRed 0.5s infinite; -o-animation: blinkRed 0.5s infinite; animation: blinkRed 0.5s infinite; } @-webkit-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-moz-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-ms-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-o-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } .led-yellow { margin: 0 auto; width: 12px; height: 12px; background-color: #FF0; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px; -webkit-animation: blinkYellow 1s infinite; -moz-animation: blinkYellow 1s infinite; -ms-animation: blinkYellow 1s infinite; -o-animation: blinkYellow 1s infinite; animation: blinkYellow 1s infinite; } @-webkit-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-moz-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-ms-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-o-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } .led-green { margin: 0 auto; width: 12px; height: 12px; background-color: #ABFF00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px; } .led-blue { margin: 0 auto; width: 18px; height: 18px; background-color: #24E0FF; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } td[Attributes Style] { text-align: -webkit-center; } td { display: table-cell; vertical-align: inherit; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { border-collapse: separate; border-spacing: 2px; } ================================================ FILE: examples/rp2040-rx/CMakeLists.txt ================================================ # # rp2040-rx ================================================ FILE: examples/rp2040-rx/README.md ================================================ # rp2040-rx This is a sample project for receiving audio data using [RP2040](https://www.espressif.com/en/products/socs/esp32) microcontroller. The chip has a built-in 12-bit ADC which is used to process the analog audio from the external microphone module in real-time. ## Setup - Raspberry Pi Pico (or other RP2040 board) - Microphone, tested with the following, but others could be also supported: - Analog: - MAX9814 - KY-037 - KY-038 - WS Sound sensor ## Pinout ### Analog Microphone | MCU | Mic | | ------- | --------- | | GND | GND | | 3.3V | VCC / VDD | | GPIO 26 | Out | ![Sketch-Breadboard](fritzing-sketch_bb.png) ![1658510571716150](https://user-images.githubusercontent.com/1991296/180506853-01954beb-ccd4-4b71-ac20-232899d99abf.jpg) ================================================ FILE: examples/rp2040-rx/mic-analog.cpp ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ #include "mic-analog.h" #include "hardware/adc.h" #include "hardware/clocks.h" #include "hardware/dma.h" #include "hardware/irq.h" #include #include #define ANALOG_RAW_BUFFER_COUNT 2 static struct { int dma_channel; uint16_t* raw_buffer[ANALOG_RAW_BUFFER_COUNT]; uint32_t buffer_size; int16_t bias; uint32_t dma_irq; volatile int raw_buffer_write_index; volatile int raw_buffer_read_index; analog_microphone_config config; analog_samples_ready_handler_t samples_ready_handler; } analog_mic; static void analog_dma_handler(); int analog_microphone_init(const struct analog_microphone_config* config) { memset(&analog_mic, 0x00, sizeof(analog_mic)); memcpy(&analog_mic.config, config, sizeof(analog_mic.config)); if (config->gpio < 26 || config->gpio > 29) { return -1; } size_t raw_buffer_size = config->sample_buffer_size * sizeof(analog_mic.raw_buffer[0][0]); analog_mic.buffer_size = config->sample_buffer_size; analog_mic.bias = ((int16_t)((config->bias_voltage * 4095) / 3.3)); for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) { analog_mic.raw_buffer[i] = (uint16_t* )malloc(raw_buffer_size); if (analog_mic.raw_buffer[i] == NULL) { analog_microphone_deinit(); return -1; } } analog_mic.dma_channel = dma_claim_unused_channel(true); if (analog_mic.dma_channel < 0) { analog_microphone_deinit(); return -1; } float clk_div = (clock_get_hz(clk_adc) / (1.0 * config->sample_rate)) - 1; dma_channel_config dma_channel_cfg = dma_channel_get_default_config(analog_mic.dma_channel); channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_16); channel_config_set_read_increment(&dma_channel_cfg, false); channel_config_set_write_increment(&dma_channel_cfg, true); channel_config_set_dreq(&dma_channel_cfg, DREQ_ADC); analog_mic.dma_irq = DMA_IRQ_0; dma_channel_configure( analog_mic.dma_channel, &dma_channel_cfg, analog_mic.raw_buffer[0], &adc_hw->fifo, analog_mic.buffer_size, false ); adc_gpio_init(config->gpio); adc_init(); adc_select_input(config->gpio - 26); adc_fifo_setup( true, // Write each completed conversion to the sample FIFO true, // Enable DMA data request (DREQ) 1, // DREQ (and IRQ) asserted when at least 1 sample present false, // We won't see the ERR bit because of 8 bit reads; disable. false // Don't shift each sample to 8 bits when pushing to FIFO ); adc_set_clkdiv(clk_div); return 0; } void analog_microphone_deinit() { for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) { if (analog_mic.raw_buffer[i]) { free(analog_mic.raw_buffer[i]); analog_mic.raw_buffer[i] = NULL; } } if (analog_mic.dma_channel > -1) { dma_channel_unclaim(analog_mic.dma_channel); analog_mic.dma_channel = -1; } } int analog_microphone_start() { irq_set_enabled(analog_mic.dma_irq, true); irq_set_exclusive_handler(analog_mic.dma_irq, analog_dma_handler); if (analog_mic.dma_irq == DMA_IRQ_0) { dma_channel_set_irq0_enabled(analog_mic.dma_channel, true); } else if (analog_mic.dma_irq == DMA_IRQ_1) { dma_channel_set_irq1_enabled(analog_mic.dma_channel, true); } else { return -1; } analog_mic.raw_buffer_write_index = 0; analog_mic.raw_buffer_read_index = 0; dma_channel_transfer_to_buffer_now( analog_mic.dma_channel, analog_mic.raw_buffer[0], analog_mic.buffer_size ); adc_run(true); // start running the adc // return 0; } void analog_microphone_stop() { adc_run(false); // stop running the adc dma_channel_abort(analog_mic.dma_channel); if (analog_mic.dma_irq == DMA_IRQ_0) { dma_channel_set_irq0_enabled(analog_mic.dma_channel, false); } else if (analog_mic.dma_irq == DMA_IRQ_1) { dma_channel_set_irq1_enabled(analog_mic.dma_channel, false); } irq_set_enabled(analog_mic.dma_irq, false); } static void analog_dma_handler() { // clear IRQ if (analog_mic.dma_irq == DMA_IRQ_0) { dma_hw->ints0 = (1u << analog_mic.dma_channel); } else if (analog_mic.dma_irq == DMA_IRQ_1) { dma_hw->ints1 = (1u << analog_mic.dma_channel); } // get the current buffer index analog_mic.raw_buffer_read_index = analog_mic.raw_buffer_write_index; // get the next capture index to send the dma to start analog_mic.raw_buffer_write_index = (analog_mic.raw_buffer_write_index + 1) % ANALOG_RAW_BUFFER_COUNT; // give the channel a new buffer to write to and re-trigger it dma_channel_transfer_to_buffer_now( analog_mic.dma_channel, analog_mic.raw_buffer[analog_mic.raw_buffer_write_index], analog_mic.buffer_size ); if (analog_mic.samples_ready_handler) { analog_mic.samples_ready_handler(); } } void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler) { analog_mic.samples_ready_handler = handler; } int analog_microphone_read(int16_t* buffer, size_t samples) { if (samples > analog_mic.config.sample_buffer_size) { samples = analog_mic.config.sample_buffer_size; } if (analog_mic.raw_buffer_write_index == analog_mic.raw_buffer_read_index) { return 0; } uint16_t* in = analog_mic.raw_buffer[analog_mic.raw_buffer_read_index]; int16_t* out = buffer; int16_t bias = analog_mic.bias; analog_mic.raw_buffer_read_index++; for (int i = 0; i < samples; i++) { *out++ = *in++ - bias; } return samples; } ================================================ FILE: examples/rp2040-rx/mic-analog.h ================================================ /* * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * */ #ifndef _PICO_ANALOG_MICROPHONE_H_ #define _PICO_ANALOG_MICROPHONE_H_ #include #include typedef void (*analog_samples_ready_handler_t)(void); struct analog_microphone_config { uint32_t gpio; float bias_voltage; uint32_t sample_rate; uint32_t sample_buffer_size; }; int analog_microphone_init(const struct analog_microphone_config* config); void analog_microphone_deinit(); int analog_microphone_start(); void analog_microphone_stop(); void analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler); int analog_microphone_read(int16_t* buffer, size_t samples); #endif ================================================ FILE: examples/rp2040-rx/rp2040-rx.ino ================================================ // rp2040-rx // // Sample sketch for receiving sound data using "ggwave" // // Tested MCU boards: // - Raspberry Pi Pico // - Arduino Nano RP2040 Connect // // Tested analog microphones: // - MAX9814 // - KY-037 // - KY-038 // - WS Sound sensor // // The RP2040 microcontroller has a built-in 12-bit ADC which is used to digitalize the analog signal // from the external analog microphone. The MCU supports sampling rates up to 500kHz which makes it // capable of even recording audio in the ultrasound range, given that your microphone's sensitivity // supports it. // // If you want to perform a quick test, you can use the free "Waver" application: // - Web: https://waver.ggerganov.com // - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver // - iOS: https://apps.apple.com/us/app/waver-data-over-sound/id1543607865 // // Make sure to enable the "Fixed-length" option in "Waver"'s settings and set the number of // bytes to be equal to "payloadLength" used in the sketch. Also, select a protocol that is // listed as Rx in the current sketch. // // Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx // // ## Pinout // // ### Analog Microphone // // | MCU | Mic | // | ------- | --------- | // | GND | GND | // | 3.3V | VCC / VDD | // | GPIO 26 | Out | // // Uncomment the line coresponding to your microhpone #define MIC_ANALOG // Uncoment this line to enable long-range transmission // The used protocols are slower and use more memory to decode, but are much more robust //#define LONG_RANGE 1 #include // Audio capture configuration using TSample = int16_t; const size_t kSampleSize_bytes = sizeof(TSample); // High sample rate - better quality, but more CPU/Memory usage const int sampleRate = 48000; const int samplesPerFrame = 1024; // Low sample rate //const int sampleRate = 24000; //const int samplesPerFrame = 512; TSample sampleBuffer[samplesPerFrame]; #if defined(MIC_ANALOG) #include "mic-analog.h" volatile int samplesRead = 0; const struct analog_microphone_config config = { // GPIO to use for input, must be ADC compatible (GPIO 26 - 28) .gpio = 26, // bias voltage of microphone in volts .bias_voltage = 1.25, // sample rate in Hz .sample_rate = sampleRate, // number of samples to buffer .sample_buffer_size = samplesPerFrame, }; void on_analog_samples_ready() { // callback from library when all the samples in the library // internal sample buffer are ready for reading samplesRead = analog_microphone_read(sampleBuffer, samplesPerFrame); } #endif // Global GGwave instance GGWave ggwave; void setup() { Serial.begin(115200); while (!Serial); // Initialize "ggwave" { Serial.println(F("Trying to initialize the ggwave instance")); ggwave.setLogFile(nullptr); auto p = GGWave::getDefaultParameters(); // Adjust the "ggwave" parameters to your needs. // Make sure that the "payloadLength" parameter matches the one used on the transmitting side. #ifdef LONG_RANGE // The "FAST" protocols require 2x more memory, so we reduce the payload length to compensate: p.payloadLength = 8; #else p.payloadLength = 16; #endif Serial.print(F("Using payload length: ")); Serial.println(p.payloadLength); p.sampleRateInp = sampleRate; p.sampleRateOut = sampleRate; p.sampleRate = sampleRate; p.samplesPerFrame = samplesPerFrame; p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8; p.operatingMode = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES; // Protocols to use for TX // Remove the ones that you don't need to reduce memory usage GGWave::Protocols::tx().disableAll(); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); // Protocols to use for RX // Remove the ones that you don't need to reduce memory and CPU usage GGWave::Protocols::rx().disableAll(); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_NORMAL, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FAST, true); //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, true); #ifdef LONG_RANGE GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FAST, true); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST, true); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST, true); #endif GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, true); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true); GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true); // Print the memory required for the "ggwave" instance: ggwave.prepare(p, false); Serial.print(F("Required memory by the ggwave instance: ")); Serial.print(ggwave.heapSize()); Serial.println(F(" bytes")); // Initialize the "ggwave" instance: ggwave.prepare(p, true); Serial.print(F("Instance initialized successfully! Memory used: ")); } // initialize the analog microphone if (analog_microphone_init(&config) < 0) { Serial.println(F("analog microphone initialization failed!")); while (1) { tight_loop_contents(); } } // set callback that is called when all the samples in the library // internal sample buffer are ready for reading analog_microphone_set_samples_ready_handler(on_analog_samples_ready); // start capturing data from the analog microphone if (analog_microphone_start() < 0) { Serial.println(F("Analog microphone start failed!")); while (1) { tight_loop_contents(); } } Serial.println(F("setup() done")); } int niter = 0; int tLastReceive = -10000; GGWave::TxRxData result; void loop() { // wait for new samples while (samplesRead == 0) { tight_loop_contents(); } // store and clear the samples read from the callback int nSamples = samplesRead; samplesRead = 0; // Use this with the serial plotter to observe real-time audio signal //for (int i = 0; i < nSamples; i++) { // Serial.printf("%d\n", sampleBuffer[i]); //} // Try to decode any "ggwave" data: auto tStart = millis(); if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) { Serial.println("Failed to decode"); } auto tEnd = millis(); if (++niter % 10 == 0) { // print the time it took the last decode() call to complete // should be smaller than samplesPerFrame/sampleRate seconds // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms Serial.println(tEnd - tStart); if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) { Serial.println(F("Warning: decode() took too long to execute!")); } } // Check if we have successfully decoded any data: int nr = ggwave.rxTakeData(result); if (nr > 0) { Serial.println(tEnd - tStart); Serial.print(F("Received data with length ")); Serial.print(nr); // should be equal to p.payloadLength Serial.println(F(" bytes:")); Serial.println((char *) result.data()); tLastReceive = tEnd; } } ================================================ FILE: examples/spectrogram/CMakeLists.txt ================================================ set(TARGET spectrogram) if (EMSCRIPTEN) add_executable(${TARGET} main.cpp) target_include_directories(${TARGET} PRIVATE .. ${SDL2_INCLUDE_DIRS} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ ) target_link_libraries(${TARGET} PRIVATE ggwave ggwave-common ggwave-common-sdl2 ggsock imgui-sdl2 ${CMAKE_THREAD_LIBS_INIT} ) set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \ -s FORCE_FILESYSTEM=1 \ --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../assets/fonts@/ \ ") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY) else() add_executable(${TARGET} main.cpp) target_include_directories(${TARGET} PRIVATE .. ${SDL2_INCLUDE_DIRS} ) target_link_libraries(${TARGET} PRIVATE ggwave ggwave-common ggwave-common-sdl2 imgui-sdl2 ${CMAKE_THREAD_LIBS_INIT} ) endif() ================================================ FILE: examples/spectrogram/README.md ================================================ # spectrogram Real-time audio spectrum visualizer WASM port: https://spectrogram.ggerganov.com ![image](https://user-images.githubusercontent.com/1991296/109423222-50817600-79e7-11eb-9373-0e50aaf6376b.png) ================================================ FILE: examples/spectrogram/build_timestamp-tmpl.h ================================================ static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)"; ================================================ FILE: examples/spectrogram/index-tmpl.html ================================================ Spectrogram

Spectrogram

Loading WebAssembly module - please wait ...

================================================ FILE: examples/spectrogram/main.cpp ================================================ #include "ggwave/ggwave.h" #include "ggwave-common.h" #ifdef __EMSCRIPTEN__ #include "build_timestamp.h" #include "emscripten/emscripten.h" #else #define EMSCRIPTEN_KEEPALIVE #endif #include #include #include #include #include #include #include namespace { std::string g_defaultCaptureDeviceName = ""; SDL_AudioDeviceID g_devIdInp = 0; SDL_AudioDeviceID g_devIdOut = 0; SDL_AudioSpec g_obtainedSpecInp; SDL_AudioSpec g_obtainedSpecOut; struct FreqData { float freq; std::vector mag; }; bool g_isCapturing = true; constexpr int g_nSamplesPerFrame = 1024; constexpr int g_nBins = g_nSamplesPerFrame/2; int g_binMin = 0; int g_binMax = g_nBins; float g_scale = 30.0; bool g_filter0 = false; bool g_filter1 = false; bool g_filter2 = false; bool g_showControls = true; int g_freqDataHead = 0; int g_freqDataSize = 0; std::vector g_freqData; float g_sampleRateOffset = 0; } void GGWave_setDefaultCaptureDeviceName(std::string name) { g_defaultCaptureDeviceName = std::move(name); } bool GGWave_init( const int playbackId, const int captureId) { if (g_devIdInp && g_devIdOut) { return false; } if (g_devIdInp == 0 && g_devIdOut == 0) { 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)); } } } if (g_devIdOut == 0) { printf("Initializing playback ...\n"); SDL_AudioSpec playbackSpec; SDL_zero(playbackSpec); playbackSpec.freq = GGWave::kDefaultSampleRate + g_sampleRateOffset; playbackSpec.format = AUDIO_S16SYS; playbackSpec.channels = 1; playbackSpec.samples = 16*1024; playbackSpec.callback = NULL; SDL_zero(g_obtainedSpecOut); if (playbackId >= 0) { printf("Attempt to open playback device %d : '%s' ...\n", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE)); g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } else { printf("Attempt to open default playback device ...\n"); g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0); } if (!g_devIdOut) { printf("Couldn't open an audio device for playback: %s!\n", SDL_GetError()); g_devIdOut = 0; } else { printf("Obtained spec for output device (SDL Id = %d):\n", g_devIdOut); printf(" - Sample rate: %d (required: %d)\n", g_obtainedSpecOut.freq, playbackSpec.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecOut.format, playbackSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecOut.channels, playbackSpec.channels); printf(" - Samples per frame: %d (required: %d)\n", g_obtainedSpecOut.samples, playbackSpec.samples); if (g_obtainedSpecOut.format != playbackSpec.format || g_obtainedSpecOut.channels != playbackSpec.channels || g_obtainedSpecOut.samples != playbackSpec.samples) { g_devIdOut = 0; SDL_CloseAudio(); fprintf(stderr, "Failed to initialize playback SDL_OpenAudio!"); return false; } } } if (g_devIdInp == 0) { SDL_AudioSpec captureSpec; captureSpec = g_obtainedSpecOut; captureSpec.freq = GGWave::kDefaultSampleRate + g_sampleRateOffset; captureSpec.format = AUDIO_F32SYS; captureSpec.samples = g_nSamplesPerFrame; SDL_zero(g_obtainedSpecInp); if (captureId >= 0) { printf("Attempt to open capture device %d : '%s' ...\n", captureId, SDL_GetAudioDeviceName(captureId, SDL_FALSE)); g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } else { printf("Attempt to open default capture device ...\n"); g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0); } if (!g_devIdInp) { printf("Couldn't open an audio device for capture: %s!\n", SDL_GetError()); g_devIdInp = 0; } else { printf("Obtained spec for input device (SDL Id = %d):\n", g_devIdInp); printf(" - Sample rate: %d\n", g_obtainedSpecInp.freq); printf(" - Format: %d (required: %d)\n", g_obtainedSpecInp.format, captureSpec.format); printf(" - Channels: %d (required: %d)\n", g_obtainedSpecInp.channels, captureSpec.channels); printf(" - Samples per frame: %d\n", g_obtainedSpecInp.samples); } } return true; } bool GGWave_mainLoop() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE); if (!g_isCapturing) { SDL_ClearQueuedAudio(g_devIdInp); } static bool isInitialzed = false; static float data[g_nSamplesPerFrame]; static float out [2*g_nSamplesPerFrame]; static int workI[2*g_nSamplesPerFrame]; static float workF[g_nSamplesPerFrame/2]; static float workF0[g_nSamplesPerFrame]; static float workF1[g_nSamplesPerFrame]; static float workF2[11]; if (!isInitialzed) { memset(data, 0, sizeof(data)); memset(out, 0, sizeof(out)); memset(workI, 0, sizeof(workI)); memset(workF, 0, sizeof(workF)); memset(workF0, 0, sizeof(workF0)); memset(workF1, 0, sizeof(workF1)); memset(workF2, 0, sizeof(workF2)); isInitialzed = true; } int n = 0; do { n = SDL_DequeueAudio(g_devIdInp, data, sizeof(float)*g_nSamplesPerFrame); if (n <= 0) break; if (g_filter2) { GGWave::filter(GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS, data, g_nSamplesPerFrame, 250.0f, GGWave::kDefaultSampleRate, workF2); } if (g_filter0) { GGWave::filter(GGWAVE_FILTER_HANN, data, g_nSamplesPerFrame, 250.0f, GGWave::kDefaultSampleRate, workF0); } if (g_filter1) { GGWave::filter(GGWAVE_FILTER_HAMMING, data, g_nSamplesPerFrame, 250.0f, GGWave::kDefaultSampleRate, workF1); } if (GGWave::computeFFTR(data, out, g_nSamplesPerFrame, workI, workF) == false) { fprintf(stderr, "Failed to compute FFT!\n"); return false; } for (int i = 0; i < g_nSamplesPerFrame; ++i) { out[i] = std::sqrt(out[2*i + 0]*out[2*i + 0] + out[2*i + 1]*out[2*i + 1]); } for (int i = 1; i < g_nSamplesPerFrame/2; ++i) { out[i] += out[g_nSamplesPerFrame - i]; } for (int i = 0; i < (int) g_freqData.size(); ++i) { g_freqData[i].mag[g_freqDataHead] = out[i]; } if (++g_freqDataHead == g_freqDataSize) { g_freqDataHead = 0; } } while (n > 0); return true; } bool GGWave_deinit() { if (g_devIdInp == 0 && g_devIdOut == 0) { return false; } SDL_PauseAudioDevice(g_devIdInp, 1); SDL_CloseAudioDevice(g_devIdInp); SDL_PauseAudioDevice(g_devIdOut, 1); SDL_CloseAudioDevice(g_devIdOut); g_devIdInp = 0; g_devIdOut = 0; return true; } bool ImGui_BeginFrame(SDL_Window * window) { SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ProcessEvent(&event); if (event.type == SDL_QUIT) return false; if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) return false; } ImGui_NewFrame(window); return true; } bool ImGui_EndFrame(SDL_Window * window) { // Rendering int display_w, display_h; SDL_GetWindowSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); glClearColor(0.0f, 0.0f, 0.0f, 0.4f); glClear(GL_COLOR_BUFFER_BIT); ImGui::Render(); ImGui_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(window); return true; } bool ImGui_SetStyle() { ImGuiStyle & style = ImGui::GetStyle(); style.AntiAliasedFill = true; style.AntiAliasedLines = true; style.WindowRounding = 0.0f; style.WindowPadding = ImVec2(8, 8); style.WindowRounding = 0.0f; style.FramePadding = ImVec2(4, 3); style.FrameRounding = 0.0f; style.ItemSpacing = ImVec2(8, 4); style.ItemInnerSpacing = ImVec2(4, 4); style.IndentSpacing = 21.0f; style.ScrollbarSize = 16.0f; style.ScrollbarRounding = 9.0f; style.GrabMinSize = 10.0f; style.GrabRounding = 3.0f; style.Colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.24f, 0.41f, 0.41f, 1.00f); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); //style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f); style.Colors[ImGuiCol_PopupBg] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f); style.Colors[ImGuiCol_Border] = ImVec4(0.31f, 0.31f, 0.31f, 0.71f); style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); style.Colors[ImGuiCol_FrameBg] = ImVec4(0.00f, 0.39f, 0.39f, 0.39f); style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); style.Colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.50f, 0.50f, 0.70f); style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.50f, 0.50f, 1.00f); style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.00f, 0.70f, 0.70f, 1.00f); style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.00f, 0.70f, 0.70f, 1.00f); style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.27f, 0.27f, 1.00f); style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.80f, 0.80f, 0.83f, 0.31f); style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); //style.Colors[ImGuiCol_ComboBg] = ImVec4(0.00f, 0.39f, 0.39f, 1.00f); style.Colors[ImGuiCol_CheckMark] = ImVec4(0.80f, 0.80f, 0.83f, 0.39f); style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.80f, 0.80f, 0.83f, 0.39f); style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); style.Colors[ImGuiCol_Button] = ImVec4(0.13f, 0.55f, 0.55f, 1.00f); style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.61f, 1.00f, 0.00f, 0.51f); style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); style.Colors[ImGuiCol_Header] = ImVec4(0.79f, 0.51f, 0.00f, 0.51f); style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); //style.Colors[ImGuiCol_Column] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); //style.Colors[ImGuiCol_ColumnHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); //style.Colors[ImGuiCol_ColumnActive] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); //style.Colors[ImGuiCol_CloseButton] = ImVec4(0.40f, 0.39f, 0.38f, 0.16f); //style.Colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); //style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); style.Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.65f, 0.38f, 0.67f); style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); style.Colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.65f, 0.38f, 0.67f); style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.25f, 1.00f, 0.00f, 0.43f); style.Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(1.00f, 0.98f, 0.95f, 0.78f); return true; } static std::function g_doInit; static std::function g_setWindowSize; static std::function g_mainUpdate; void mainUpdate(void *) { g_mainUpdate(); } // JS interface extern "C" { EMSCRIPTEN_KEEPALIVE int do_init() { return g_doInit(); } EMSCRIPTEN_KEEPALIVE void set_window_size(int sizeX, int sizeY) { g_setWindowSize(sizeX, sizeY); } } int main(int argc, char** argv) { #ifdef __EMSCRIPTEN__ printf("Build time: %s\n", BUILD_TIMESTAMP); printf("Press the Init button to start\n"); if (argv[1]) { GGWave_setDefaultCaptureDeviceName(argv[1]); } #endif auto argm = parseCmdArguments(argc, argv); int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]); int playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]); if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "Error: %s\n", SDL_GetError()); return -1; } ImGui_PreInit(); int windowX = 1600; int windowY = 1200; const char * windowTitle = "spectrogram"; #ifdef __EMSCRIPTEN__ SDL_Renderer * renderer; SDL_Window * window; SDL_CreateWindowAndRenderer(windowX, windowY, SDL_WINDOW_OPENGL, &window, &renderer); #else SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_Window * window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowX, windowY, window_flags); #endif void * gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); SDL_GL_SetSwapInterval(1); // Enable vsync ImGui_Init(window, gl_context); ImGui::GetIO().IniFilename = nullptr; ImGui_SetStyle(); ImGui_NewFrame(window); ImGui::Render(); bool isInitialized = false; g_doInit = [&]() { if (GGWave_init(playbackId, captureId) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); return false; } g_freqDataSize = (3*GGWave::kDefaultSampleRate)/g_nSamplesPerFrame; float df = float(GGWave::kDefaultSampleRate)/g_nSamplesPerFrame; g_freqData.resize(g_nSamplesPerFrame/2); for (int i = 0; i < g_nSamplesPerFrame/2; ++i) { g_freqData[i].freq = df*i; g_freqData[i].mag.resize(g_freqDataSize); } isInitialized = true; return true; }; g_setWindowSize = [&](int sizeX, int sizeY) { SDL_SetWindowSize(window, sizeX, sizeY); }; g_mainUpdate = [&]() { if (isInitialized == false) { return true; } if (ImGui_BeginFrame(window) == false) { return false; } const auto& displaySize = ImGui::GetIO().DisplaySize; ImGui::SetNextWindowPos({ 0, 0, }); ImGui::SetNextWindowSize(displaySize); ImGui::Begin("Main", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings); auto & style = ImGui::GetStyle(); auto itemSpacingSave = style.ItemSpacing; style.ItemSpacing.x = 0.0f; style.ItemSpacing.y = 0.0f; auto windowPaddingSave = style.WindowPadding; style.WindowPadding.x = 0.0f; style.WindowPadding.y = 0.0f; auto childBorderSizeSave = style.ChildBorderSize; style.ChildBorderSize = 0.0f; { float sum = 0.0; for (int i = g_binMin; i < g_binMax; ++i) { for (int j = 0; j < g_freqDataSize; ++j) { sum += g_freqData[i].mag[j]; } } int nf = g_binMax - g_binMin; sum /= (nf*g_freqDataSize); const auto wSize = ImGui::GetContentRegionAvail(); const float dx = wSize.x/nf; const float dy = wSize.y/g_freqDataSize; auto p0 = ImGui::GetCursorScreenPos(); int nChildWindows = 0; int nFreqPerChild = 32; ImGui::PushID(nChildWindows++); ImGui::BeginChild("Spectrogram", { wSize.x, (nFreqPerChild + 1)*dy }, true); auto drawList = ImGui::GetWindowDrawList(); for (int j = 0; j < g_freqDataSize; ++j) { if (j > 0 && j % nFreqPerChild == 0) { ImGui::EndChild(); ImGui::PopID(); ImGui::PushID(nChildWindows++); ImGui::SetCursorScreenPos({ p0.x, p0.y + nFreqPerChild*int(j/nFreqPerChild)*dy }); ImGui::BeginChild("Spectrogram", { wSize.x, (nFreqPerChild + 1)*dy }, true); drawList = ImGui::GetWindowDrawList(); } for (int i = 0; i < nf; ++i) { int k = g_freqDataHead + j; if (k >= g_freqDataSize) k -= g_freqDataSize; auto v = g_freqData[g_binMin + i].mag[k]; ImVec4 c = { 0.0f, 1.0f, 0.0, 0.0f }; c.w = v/(g_scale*sum); const ImVec2 rp0 = { p0.x + i*dx , p0.y + j*dy }; const ImVec2 rp1 = { p0.x + i*dx + dx, p0.y + j*dy + dy }; drawList->AddRectFilled(rp0, rp1, ImGui::ColorConvertFloat4ToU32(c)); // if hovering -> tooltip if (ImGui::IsMouseHoveringRect(rp0, rp1)) { ImGui::BeginTooltip(); ImGui::Text("%.2f Hz", g_freqData[g_binMin + i].freq); ImGui::Text("%.2f", v); ImGui::EndTooltip(); } } } ImGui::EndChild(); ImGui::PopID(); } style.ItemSpacing = itemSpacingSave; style.WindowPadding = windowPaddingSave; style.ChildBorderSize = childBorderSizeSave; ImGui::End(); bool togglePause = false; if (g_showControls) { ImGui::SetNextWindowFocus(); ImGui::SetNextWindowPos({ std::max(20.0f, displaySize.x - 400.0f - 20.0f), 20.0f }); ImGui::SetNextWindowSize({ std::min(displaySize.x - 40.0f, 400.0f), 210.0f }); ImGui::Begin("Controls", &g_showControls); ImGui::Text("Press 'c' to hide/show this window"); { static char buf[64]; snprintf(buf, 64, "Bin: %3d, Freq: %5.2f Hz", g_binMin, 0.5*g_binMin*g_obtainedSpecInp.freq/g_nBins); ImGui::DragInt("Min", &g_binMin, 1, 0, g_binMax - 2, buf); snprintf(buf, 64, "Bin: %3d, Freq: %5.2f Hz", g_binMax, 0.5*g_binMax*g_obtainedSpecInp.freq/g_nBins); ImGui::DragInt("Max", &g_binMax, 1, g_binMin + 1, g_nBins, buf); } ImGui::DragFloat("Scale", &g_scale, 1.0f, 1.0f, 1000.0f); if (ImGui::Checkbox("High-pass", &g_filter2)) { } ImGui::SameLine(); if (ImGui::Checkbox("Hann", &g_filter0)) { } ImGui::SameLine(); if (ImGui::Checkbox("Hamming", &g_filter1)) { } ImGui::Text("%s", ""); #ifndef __EMSCRIPTEN__ if (ImGui::SliderFloat("Offset", &g_sampleRateOffset, -2048, 2048)) { GGWave_deinit(); GGWave_init(0, 0); } #endif if (ImGui::Button("Pause [Enter]")) { togglePause = true; } if (ImGui::IsKeyPressed(40)) { togglePause = true; } ImGui::End(); } if (togglePause) { g_isCapturing = !g_isCapturing; } if (ImGui::IsKeyPressed(6)) { g_showControls = !g_showControls; } GGWave_mainLoop(); ImGui_EndFrame(window); return true; }; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true); #else if (g_doInit() == false) { printf("Error: failed to initialize audio\n"); return -2; } while (true) { if (g_mainUpdate() == false) break; } GGWave_deinit(); // Cleanup ImGui_Shutdown(); ImGui::DestroyContext(); SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_CloseAudio(); SDL_Quit(); #endif return 0; } ================================================ FILE: examples/spectrogram/style.css ================================================ body { margin: 0; background-color: black; -webkit-font-smoothing: subpixel-antialiased; font-smoothing: subpixel-antialiased; } #screen { margin: 0; padding: 0; font-size: 13px; height: 100%; font: sans-serif; } .no-sel { -moz-user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; -ms-user-select:none; user-select:none; -o-user-select:none; } .cell { pointer-events: none; } .cell-version { padding-left: 4px; padding-top: 0.5em; text-align: left; display: inline-block; float: left; color: rgba(0, 0, 0, 0.75); } .cell-about { padding-right: 24px; padding-top: 0.5em; text-align: right; display: inline-block; float: right; } .nav-link { text-decoration: none; color: rgba(0, 0, 0, 1.0); } #main-container { font-size:12px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } textarea { font-size:12px; font-family: monospace; } .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten_border { border: 1px solid black; } canvas.emscripten { border: 0px none; background-color: black; text-shadow: 4px 4px #1f1f1fb4; } .spinner { height: 30px; width: 30px; margin: 0; margin-top: 20px; margin-left: 20px; display: inline-block; vertical-align: top; -webkit-animation: rotation .8s linear infinite; -moz-animation: rotation .8s linear infinite; -o-animation: rotation .8s linear infinite; animation: rotation 0.8s linear infinite; border-left: 5px solid rgb(235, 235, 235); border-right: 5px solid rgb(235, 235, 235); border-bottom: 5px solid rgb(235, 235, 235); border-top: 5px solid rgb(120, 120, 120); border-radius: 100%; background-color: rgb(189, 215, 46); } @-webkit-keyframes rotation { from {-webkit-transform: rotate(0deg);} to {-webkit-transform: rotate(360deg);} } @-moz-keyframes rotation { from {-moz-transform: rotate(0deg);} to {-moz-transform: rotate(360deg);} } @-o-keyframes rotation { from {-o-transform: rotate(0deg);} to {-o-transform: rotate(360deg);} } @keyframes rotation { from {transform: rotate(0deg);} to {transform: rotate(360deg);} } #status { display: inline-block; vertical-align: top; margin-top: 30px; margin-left: 20px; font-weight: bold; color: rgb(120, 120, 120); } #progress { height: 20px; width: 30px; } #output { width: 800px; height: 200px; margin: 0 auto; margin-top: 10px; border-left: 0px; border-right: 0px; padding-left: 0px; padding-right: 0px; background-color: black; color: white; font-size:10px; font-family: 'Lucida Console', Monaco, monospace; outline: none; } .led-box { height: 30px; width: 25%; margin: 10px 0; float: left; } .led-box p { font-size: 12px; text-align: center; margin: 1em; } .led-red { margin: 0 auto; width: 12px; height: 12px; background-color: #F00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 12px; -webkit-animation: blinkRed 0.5s infinite; -moz-animation: blinkRed 0.5s infinite; -ms-animation: blinkRed 0.5s infinite; -o-animation: blinkRed 0.5s infinite; animation: blinkRed 0.5s infinite; } @-webkit-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-moz-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-ms-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-o-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } .led-yellow { margin: 0 auto; width: 12px; height: 12px; background-color: #FF0; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px; -webkit-animation: blinkYellow 1s infinite; -moz-animation: blinkYellow 1s infinite; -ms-animation: blinkYellow 1s infinite; -o-animation: blinkYellow 1s infinite; animation: blinkYellow 1s infinite; } @-webkit-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-moz-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-ms-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-o-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } .led-green { margin: 0 auto; width: 12px; height: 12px; background-color: #ABFF00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px; } .led-blue { margin: 0 auto; width: 18px; height: 18px; background-color: #24E0FF; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } td[Attributes Style] { text-align: -webkit-center; } td { display: table-cell; vertical-align: inherit; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { border-collapse: separate; border-spacing: 2px; } #description { margin: 10px; padding: 10px; color: rgba(255, 255, 255, 1.00); text-shadow: 2px 2px #1f1f1fb4; } .text-body { color: rgba(255, 255, 255, 1.00); text-shadow: 2px 2px #1f1f1fb4; } .cell-version { padding-left: 4px; padding-top: 0.5em; text-align: left; display: inline-block; float: left; color: rgba(255, 255, 255, 0.75); text-shadow: 2px 2px #1f1f1fb4; } .cell-about { padding-right: 24px; padding-top: 0.5em; text-align: right; display: inline-block; float: right; } .nav-link { text-decoration: none; color: rgba(255, 255, 255, 1.0); text-shadow: 2px 2px #1f1f1fb4; } .nav-link2 { text-decoration: none; color: rgba(0, 255, 0, 1.0); text-shadow: 2px 2px #1f1f1fb4; } svg { -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */ filter: invert(100%); } ================================================ FILE: examples/third-party/CMakeLists.txt ================================================ if (NOT EMSCRIPTEN) endif() add_subdirectory(imgui) add_subdirectory(ggsock) ================================================ FILE: examples/third-party/imgui/CMakeLists.txt ================================================ if (GGWAVE_ALL_WARNINGS_3RD_PARTY) 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() if (APPLE) set(ADDITIONAL_LIBRARIES "-framework Cocoa") else (APPLE) unset(ADDITIONAL_LIBRARIES) endif (APPLE) add_library(imgui imgui/imgui.cpp imgui/imgui_draw.cpp imgui/imgui_demo.cpp imgui/imgui_widgets.cpp ) target_include_directories(imgui INTERFACE . ) target_include_directories(imgui PRIVATE imgui ) target_link_libraries(imgui PRIVATE ${ADDITIONAL_LIBRARIES} ) if (MINGW) target_link_libraries(imgui PUBLIC stdc++ ) endif() if (GGWAVE_SUPPORT_SDL2) if (MINGW) find_package(PkgConfig REQUIRED) pkg_search_module(SDL2 REQUIRED sdl2) add_library(imgui-sdl2 imgui/examples/libs/gl3w/GL/gl3w.c imgui-extra/imgui_impl.cpp imgui-extra/imgui_impl_sdl.cpp imgui-extra/imgui_impl_opengl3.cpp ) target_include_directories(imgui-sdl2 PUBLIC imgui/examples/libs/gl3w ${SDL2_INCLUDE_DIRS} ) target_include_directories(imgui-sdl2 PRIVATE imgui imgui-extra ) target_link_libraries(imgui-sdl2 PUBLIC imgui opengl32 ${SDL2_LIBRARIES} ) target_link_libraries(imgui-sdl2 PRIVATE ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${ADDITIONAL_LIBRARIES} ) elseif (EMSCRIPTEN) add_library(imgui-sdl2 imgui-extra/imgui_impl.cpp imgui-extra/imgui_impl_sdl.cpp imgui-extra/imgui_impl_opengl3.cpp ) target_include_directories(imgui-sdl2 PRIVATE imgui imgui-extra ) target_link_libraries(imgui-sdl2 PUBLIC imgui ) else() set(OpenGL_GL_PREFERENCE GLVND) find_package(OpenGL REQUIRED) add_library(imgui-sdl2 imgui/examples/libs/gl3w/GL/gl3w.c imgui-extra/imgui_impl.cpp imgui-extra/imgui_impl_sdl.cpp imgui-extra/imgui_impl_opengl3.cpp ) # force GL3W loader target_compile_definitions(imgui-sdl2 PUBLIC IMGUI_IMPL_OPENGL_LOADER_GL3W=1 ) target_include_directories(imgui-sdl2 PUBLIC imgui/examples/libs/gl3w ${SDL2_INCLUDE_DIRS} ) target_include_directories(imgui-sdl2 PRIVATE imgui imgui-extra ) target_link_libraries(imgui-sdl2 PUBLIC imgui ${OPENGL_LIBRARIES} ${SDL2_LIBRARIES} ) target_link_libraries(imgui-sdl2 PRIVATE ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${ADDITIONAL_LIBRARIES} ) endif() endif() install(TARGETS imgui LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static ) if (GGWAVE_SUPPORT_SDL2) install(TARGETS imgui-sdl2 LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static ) endif() ================================================ FILE: examples/third-party/imgui/imgui-extra/imgui_impl.cpp ================================================ #include "imgui-extra/imgui_impl.h" #include "imgui-extra/imgui_impl_sdl.h" #include "imgui-extra/imgui_impl_opengl3.h" #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) #include // Initialize with gl3wInit() #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) #include // Initialize with glewInit() #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) #include // Initialize with gladLoadGL() #else #include IMGUI_IMPL_OPENGL_LOADER_CUSTOM #endif #include #include bool ImGui_PreInit() { // Decide GL+GLSL versions #if __APPLE__ // GL 3.2 Core + GLSL 150 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); #elif __EMSCRIPTEN__ const char* glsl_version = "#version 100"; //const char* glsl_version = "#version 300 es"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #else // GL 3.0 + GLSL 130 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #endif // Create window with graphics context SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); return true; } ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) { // Decide GL+GLSL versions #if __APPLE__ // GL 3.2 Core + GLSL 150 const char* glsl_version = "#version 150"; #elif __EMSCRIPTEN__ const char* glsl_version = "#version 100"; #else // GL 3.0 + GLSL 130 const char* glsl_version = "#version 130"; #endif static bool isInitialized = false; if (!isInitialized) { // Initialize OpenGL loader #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) bool err = gl3wInit() != 0; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) bool err = glewInit() != GLEW_OK; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) bool err = gladLoadGL() == 0; #else bool err = false; // If you use IMGUI_IMPL_OPENGL_LOADER_CUSTOM, your loader is likely to requires some form of initialization. #endif if (err) { fprintf(stderr, "Failed to initialize OpenGL loader!\n"); return nullptr; } isInitialized = true; } // Setup Dear ImGui context IMGUI_CHECKVERSION(); auto ctx = ImGui::CreateContext(); ImGui::SetCurrentContext(ctx); ImGuiIO& io = ImGui::GetIO(); (void)io; //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // Setup Platform/Renderer bindings bool res = true; res &= ImGui_ImplSDL2_InitForOpenGL(window, gl_context); res &= ImGui_ImplOpenGL3_Init(glsl_version); return res ? ctx : nullptr; } void ImGui_Shutdown() { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); } void ImGui_NewFrame(SDL_Window* window) { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(window); ImGui::NewFrame(); } bool ImGui_ProcessEvent(const SDL_Event* event) { return ImGui_ImplSDL2_ProcessEvent(event); } void ImGui_RenderDrawData(ImDrawData* draw_data) { ImGui_ImplOpenGL3_RenderDrawData(draw_data); } bool ImGui_CreateFontsTexture() { return ImGui_ImplOpenGL3_CreateFontsTexture(); } void ImGui_DestroyFontsTexture() { ImGui_ImplOpenGL3_DestroyFontsTexture(); } bool ImGui_CreateDeviceObjects() { return ImGui_ImplOpenGL3_CreateDeviceObjects(); } void ImGui_DestroyDeviceObjects() { ImGui_ImplOpenGL3_DestroyDeviceObjects(); } void ImGui_SaveState(int id) { ImGui_ImplSDL2_SaveState(id); ImGui_ImplOpenGL3_SaveState(id); } void ImGui_LoadState(int id) { ImGui_ImplSDL2_LoadState(id); ImGui_ImplOpenGL3_LoadState(id); } ================================================ FILE: examples/third-party/imgui/imgui-extra/imgui_impl.h ================================================ /*! \file imgui_impl.h * \brief Enter description here. */ #pragma once #if (defined(__EMSCRIPTEN__)) #define IMGUI_IMPL_OPENGL_LOADER_GLEW #endif #include "imgui/imgui.h" struct SDL_Window; typedef void * SDL_GLContext; typedef union SDL_Event SDL_Event; IMGUI_API bool ImGui_PreInit(); IMGUI_API ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context); void IMGUI_API ImGui_Shutdown(); void IMGUI_API ImGui_NewFrame(SDL_Window* window); bool IMGUI_API ImGui_ProcessEvent(const SDL_Event* event); void IMGUI_API ImGui_RenderDrawData(ImDrawData* draw_data); bool IMGUI_API ImGui_CreateFontsTexture(); void IMGUI_API ImGui_DestroyFontsTexture(); bool IMGUI_API ImGui_CreateDeviceObjects(); void IMGUI_API ImGui_DestroyDeviceObjects(); void IMGUI_API ImGui_SaveState(int id); void IMGUI_API ImGui_LoadState(int id); ================================================ FILE: examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.cpp ================================================ // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline // - Desktop GL: 2.x 3.x 4.x // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2020-01-07: OpenGL: Added support for glbindings OpenGL loader. // 2019-10-25: OpenGL: Using a combination of GL define and runtime GL version to decide whether to use glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. // 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. // 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. // 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. // 2019-03-15: OpenGL: Added a dummy GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. // 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). // 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. // 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. // 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. // 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. // 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. // 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". // 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. // 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. // 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. // 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. // 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. // 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer. // 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". // 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. // 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. // 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. // 2017-05-01: OpenGL: Fixed save and restore of current blend func state. // 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. // 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. // 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) //---------------------------------------- // OpenGL GLSL GLSL // version version string //---------------------------------------- // 2.0 110 "#version 110" // 2.1 120 "#version 120" // 3.0 130 "#version 130" // 3.1 140 "#version 140" // 3.2 150 "#version 150" // 3.3 330 "#version 330 core" // 4.0 400 "#version 400 core" // 4.1 410 "#version 410 core" // 4.2 420 "#version 410 core" // 4.3 430 "#version 430 core" // ES 2.0 100 "#version 100" = WebGL 1.0 // ES 3.0 300 "#version 300 es" = WebGL 2.0 //---------------------------------------- #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include "imgui.h" #include "imgui_impl_opengl3.h" #include #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier #include // intptr_t #else #include // intptr_t #endif #if defined(__APPLE__) #include "TargetConditionals.h" #endif // Auto-enable GLES on matching platforms #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) #define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" #undef IMGUI_IMPL_OPENGL_LOADER_GL3W #undef IMGUI_IMPL_OPENGL_LOADER_GLEW #undef IMGUI_IMPL_OPENGL_LOADER_GLAD #undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING #undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM #elif defined(__EMSCRIPTEN__) #define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" #undef IMGUI_IMPL_OPENGL_LOADER_GL3W #undef IMGUI_IMPL_OPENGL_LOADER_GLEW #undef IMGUI_IMPL_OPENGL_LOADER_GLAD #undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING #undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM #endif #endif // GL includes #if defined(IMGUI_IMPL_OPENGL_ES2) #include #elif defined(IMGUI_IMPL_OPENGL_ES3) #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) #include // Use GL ES 3 #else #include // Use GL ES 3 #endif #else // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) #include // Needs to be initialized with gl3wInit() in user's code #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) #include // Needs to be initialized with glewInit() in user's code #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) #include // Needs to be initialized with gladLoadGL() in user's code #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING) #include // Initialize with glbinding::initialize() #include using namespace gl; #else #include IMGUI_IMPL_OPENGL_LOADER_CUSTOM #endif #endif // Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. #if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) || !defined(GL_VERSION_3_2) #define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 0 #else #define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 1 #endif // OpenGL Data static GLuint g_GlVersion = 0; // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries. static char g_GlslVersionString[32] = ""; // Specified by user or detected based on compile time GL settings. static GLuint g_FontTexture = 0; static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location static int g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; #include struct State { GLuint FontTexture = 0; GLuint ShaderHandle = 0, VertHandle = 0, FragHandle = 0; int AttribLocationTex = 0, AttribLocationProjMtx = 0; int AttribLocationVtxPos = 0, AttribLocationVtxUV = 0, AttribLocationVtxColor = 0; unsigned int VboHandle = 0, ElementsHandle = 0; }; static std::map g_state; void ImGui_ImplOpenGL3_SaveState(int id) { auto & dst = g_state[id]; dst.FontTexture = g_FontTexture; dst.FontTexture = g_FontTexture; dst.ShaderHandle = g_ShaderHandle; dst.VertHandle = g_VertHandle; dst.FragHandle = g_FragHandle; dst.AttribLocationTex = g_AttribLocationTex; dst.AttribLocationProjMtx = g_AttribLocationProjMtx; dst.AttribLocationVtxPos = g_AttribLocationVtxPos; dst.AttribLocationVtxUV = g_AttribLocationVtxUV; dst.AttribLocationVtxColor = g_AttribLocationVtxColor; dst.VboHandle = g_VboHandle; dst.ElementsHandle = g_ElementsHandle; } void ImGui_ImplOpenGL3_LoadState(int id) { const auto & src = g_state[id]; g_FontTexture = src.FontTexture; g_ShaderHandle = src.ShaderHandle; g_VertHandle = src.VertHandle; g_FragHandle = src.FragHandle; g_AttribLocationTex = src.AttribLocationTex; g_AttribLocationProjMtx = src.AttribLocationProjMtx; g_AttribLocationVtxPos = src.AttribLocationVtxPos; g_AttribLocationVtxUV = src.AttribLocationVtxUV; g_AttribLocationVtxColor = src.AttribLocationVtxColor; g_VboHandle = src.VboHandle; g_ElementsHandle = src.ElementsHandle; } // Functions bool ImGui_ImplOpenGL3_Init(const char* glsl_version) { // Query for GL version #if !defined(IMGUI_IMPL_OPENGL_ES2) GLint major, minor; glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); g_GlVersion = major * 1000 + minor; #else g_GlVersion = 2000; // GLES 2 #endif // Setup back-end capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_opengl3"; #if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (g_GlVersion >= 3200) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. #endif // Store GLSL version string so we can refer to it later in case we recreate shaders. // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. #if defined(IMGUI_IMPL_OPENGL_ES2) if (glsl_version == NULL) glsl_version = "#version 100"; #elif defined(IMGUI_IMPL_OPENGL_ES3) if (glsl_version == NULL) glsl_version = "#version 300 es"; #else if (glsl_version == NULL) glsl_version = "#version 130"; #endif IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); strcpy(g_GlslVersionString, glsl_version); strcat(g_GlslVersionString, "\n"); // Dummy construct to make it easily visible in the IDE and debugger which GL loader has been selected. // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! // If auto-detection fails or doesn't select the same GL loader file as used by your application, // you are likely to get a crash below. // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. const char* gl_loader = "Unknown"; IM_UNUSED(gl_loader); #if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) gl_loader = "GL3W"; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) gl_loader = "GLEW"; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) gl_loader = "GLAD"; #elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING) gl_loader = "glbinding"; #else // IMGUI_IMPL_OPENGL_LOADER_CUSTOM gl_loader = "Custom"; #endif // Make a dummy GL call (we don't actually need the result) // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code. // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above. GLint current_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); return true; } void ImGui_ImplOpenGL3_Shutdown() { ImGui_ImplOpenGL3_DestroyDeviceObjects(); } void ImGui_ImplOpenGL3_NewFrame() { if (!g_ShaderHandle) ImGui_ImplOpenGL3_CreateDeviceObjects(); } static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) { // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); #ifdef GL_POLYGON_MODE glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); float L = draw_data->DisplayPos.x; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; float T = draw_data->DisplayPos.y; float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; const float ortho_projection[4][4] = { { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, { 0.0f, 0.0f, -1.0f, 0.0f }, { (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f }, }; glUseProgram(g_ShaderHandle); glUniform1i(g_AttribLocationTex, 0); glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); #ifdef GL_SAMPLER_BINDING glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. #endif (void)vertex_array_object; #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(vertex_array_object); #endif // Bind vertex/index buffers and setup attributes for ImDrawVert glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); glEnableVertexAttribArray(g_AttribLocationVtxPos); glEnableVertexAttribArray(g_AttribLocationVtxUV); glEnableVertexAttribArray(g_AttribLocationVtxColor); glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); } // OpenGL3 Render function. // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); if (fb_width <= 0 || fb_height <= 0) return; // Backup GL state GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); glActiveTexture(GL_TEXTURE0); GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); #ifdef GL_SAMPLER_BINDING GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); #endif GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 GLint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array_object); #endif #ifdef GL_POLYGON_MODE GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); #endif GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); GLboolean last_enable_blend = glIsEnabled(GL_BLEND); GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); bool clip_origin_lower_left = true; #if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) if (last_clip_origin == GL_UPPER_LEFT) clip_origin_lower_left = false; #endif // Setup desired GL state // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. GLuint vertex_array_object = 0; #ifndef IMGUI_IMPL_OPENGL_ES2 glGenVertexArrays(1, &vertex_array_object); #endif ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Render command lists for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; // Upload vertex/index buffers glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback != NULL) { // User callback, registered via ImDrawList::AddCallback() // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); else pcmd->UserCallback(cmd_list, pcmd); } else { // Project scissor/clipping rectangles into framebuffer space ImVec4 clip_rect; clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) { // Apply scissor/clipping rectangle if (clip_origin_lower_left) glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); else glScissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) // Bind texture, Draw glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); #if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (g_GlVersion >= 3200) glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); else #endif glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); } } } } // Destroy the temporary VAO #ifndef IMGUI_IMPL_OPENGL_ES2 glDeleteVertexArrays(1, &vertex_array_object); #endif // Restore modified GL state glUseProgram(last_program); glBindTexture(GL_TEXTURE_2D, last_texture); #ifdef GL_SAMPLER_BINDING glBindSampler(0, last_sampler); #endif glActiveTexture(last_active_texture); #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(last_vertex_array_object); #endif glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); #ifdef GL_POLYGON_MODE glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); #endif glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); } bool ImGui_ImplOpenGL3_CreateFontsTexture() { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. // Upload texture to graphics system GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGenTextures(1, &g_FontTexture); glBindTexture(GL_TEXTURE_2D, g_FontTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); #ifdef GL_UNPACK_ROW_LENGTH glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); #endif glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture; // Restore state glBindTexture(GL_TEXTURE_2D, last_texture); return true; } void ImGui_ImplOpenGL3_DestroyFontsTexture() { if (g_FontTexture) { ImGuiIO& io = ImGui::GetIO(); glDeleteTextures(1, &g_FontTexture); io.Fonts->TexID = 0; g_FontTexture = 0; } } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. static bool CheckShader(GLuint handle, const char* desc) { GLint status = 0, log_length = 0; glGetShaderiv(handle, GL_COMPILE_STATUS, &status); glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); if (log_length > 1) { ImVector buf; buf.resize((int)(log_length + 1)); glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); fprintf(stderr, "%s\n", buf.begin()); } return (GLboolean)status == GL_TRUE; } // If you get an error please report on GitHub. You may try different GL context version or GLSL version. static bool CheckProgram(GLuint handle, const char* desc) { GLint status = 0, log_length = 0; glGetProgramiv(handle, GL_LINK_STATUS, &status); glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); if ((GLboolean)status == GL_FALSE) fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); if (log_length > 1) { ImVector buf; buf.resize((int)(log_length + 1)); glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); fprintf(stderr, "%s\n", buf.begin()); } return (GLboolean)status == GL_TRUE; } bool ImGui_ImplOpenGL3_CreateDeviceObjects() { // Backup GL state GLint last_texture, last_array_buffer; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); #endif // Parse GLSL version string int glsl_version = 130; sscanf(g_GlslVersionString, "#version %d", &glsl_version); const GLchar* vertex_shader_glsl_120 = "uniform mat4 ProjMtx;\n" "attribute vec2 Position;\n" "attribute vec2 UV;\n" "attribute vec4 Color;\n" "varying vec2 Frag_UV;\n" "varying vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_130 = "uniform mat4 ProjMtx;\n" "in vec2 Position;\n" "in vec2 UV;\n" "in vec4 Color;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_300_es = "precision mediump float;\n" "layout (location = 0) in vec2 Position;\n" "layout (location = 1) in vec2 UV;\n" "layout (location = 2) in vec4 Color;\n" "uniform mat4 ProjMtx;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* vertex_shader_glsl_410_core = "layout (location = 0) in vec2 Position;\n" "layout (location = 1) in vec2 UV;\n" "layout (location = 2) in vec4 Color;\n" "uniform mat4 ProjMtx;\n" "out vec2 Frag_UV;\n" "out vec4 Frag_Color;\n" "void main()\n" "{\n" " Frag_UV = UV;\n" " Frag_Color = Color;\n" " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" "}\n"; const GLchar* fragment_shader_glsl_120 = "#ifdef GL_ES\n" " precision mediump float;\n" "#endif\n" "uniform sampler2D Texture;\n" "varying vec2 Frag_UV;\n" "varying vec4 Frag_Color;\n" "void main()\n" "{\n" " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_130 = "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_300_es = "precision mediump float;\n" "uniform sampler2D Texture;\n" "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "layout (location = 0) out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; const GLchar* fragment_shader_glsl_410_core = "in vec2 Frag_UV;\n" "in vec4 Frag_Color;\n" "uniform sampler2D Texture;\n" "layout (location = 0) out vec4 Out_Color;\n" "void main()\n" "{\n" " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" "}\n"; // Select shaders matching our GLSL versions const GLchar* vertex_shader = NULL; const GLchar* fragment_shader = NULL; if (glsl_version < 130) { vertex_shader = vertex_shader_glsl_120; fragment_shader = fragment_shader_glsl_120; } else if (glsl_version >= 410) { vertex_shader = vertex_shader_glsl_410_core; fragment_shader = fragment_shader_glsl_410_core; } else if (glsl_version == 300) { vertex_shader = vertex_shader_glsl_300_es; fragment_shader = fragment_shader_glsl_300_es; } else { vertex_shader = vertex_shader_glsl_130; fragment_shader = fragment_shader_glsl_130; } // Create shaders const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader }; g_VertHandle = glCreateShader(GL_VERTEX_SHADER); glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); glCompileShader(g_VertHandle); CheckShader(g_VertHandle, "vertex shader"); const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader }; g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); glCompileShader(g_FragHandle); CheckShader(g_FragHandle, "fragment shader"); g_ShaderHandle = glCreateProgram(); glAttachShader(g_ShaderHandle, g_VertHandle); glAttachShader(g_ShaderHandle, g_FragHandle); glLinkProgram(g_ShaderHandle); CheckProgram(g_ShaderHandle, "shader program"); g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); g_AttribLocationVtxPos = glGetAttribLocation(g_ShaderHandle, "Position"); g_AttribLocationVtxUV = glGetAttribLocation(g_ShaderHandle, "UV"); g_AttribLocationVtxColor = glGetAttribLocation(g_ShaderHandle, "Color"); // Create buffers glGenBuffers(1, &g_VboHandle); glGenBuffers(1, &g_ElementsHandle); ImGui_ImplOpenGL3_CreateFontsTexture(); // Restore modified GL state glBindTexture(GL_TEXTURE_2D, last_texture); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(last_vertex_array); #endif return true; } void ImGui_ImplOpenGL3_DestroyDeviceObjects() { if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; } if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; } if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); } if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); } if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; } if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; } if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; } ImGui_ImplOpenGL3_DestroyFontsTexture(); } ================================================ FILE: examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.h ================================================ // dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline // - Desktop GL: 2.x 3.x 4.x // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // About Desktop OpenGL function loaders: // Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. // Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. // About GLSL version: // The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. #pragma once // Backend API IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); // (Optional) Called by Init/NewFrame/Shutdown IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_SaveState(int id); IMGUI_IMPL_API void ImGui_ImplOpenGL3_LoadState(int id); // Specific OpenGL versions //#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten //#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android // Desktop OpenGL: attempt to detect default GL loader based on available header files. // If auto-detection fails or doesn't select the same GL loader file as used by your application, // you are likely to get a crash in ImGui_ImplOpenGL3_Init(). // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. #if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING) \ && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) #if defined(__has_include) #if __has_include() #define IMGUI_IMPL_OPENGL_LOADER_GLEW #elif __has_include() #define IMGUI_IMPL_OPENGL_LOADER_GLAD #elif __has_include() #define IMGUI_IMPL_OPENGL_LOADER_GL3W #elif __has_include() #define IMGUI_IMPL_OPENGL_LOADER_GLBINDING #else #error "Cannot detect OpenGL loader!" #endif #else #define IMGUI_IMPL_OPENGL_LOADER_GL3W // Default to GL3W #endif #endif ================================================ FILE: examples/third-party/imgui/imgui-extra/imgui_impl_sdl.cpp ================================================ // dear imgui: Platform Binding for SDL2 // This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) // (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) // (Requires: SDL 2.0. Prefer SDL 2.0.4+ for full feature support.) // Implemented features: // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Clipboard support. // [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE). // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // Missing features: // [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2). // 2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state). // 2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor. // 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. // 2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application). // 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized. // 2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls. // 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window. // 2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'. // 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls. // 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor. // 2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples. // 2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter. // 2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText). // 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag. // 2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value. // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. // 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. // 2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS). // 2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes. // 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. // 2018-01-19: Inputs: When available (SDL 2.0.4+) using SDL_CaptureMouse() to retrieve coordinates outside of client area when dragging. Otherwise (SDL 2.0.3 and before) testing for SDL_WINDOW_INPUT_FOCUS instead of SDL_WINDOW_MOUSE_FOCUS. // 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. // 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). // 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. #include "imgui.h" #include "imgui_impl_sdl.h" // SDL #include #include #if defined(__APPLE__) #include "TargetConditionals.h" #endif #define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE SDL_VERSION_ATLEAST(2,0,4) #define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6) // Data static SDL_Window* g_Window = NULL; static Uint64 g_Time = 0; static bool g_MousePressed[3] = { false, false, false }; static SDL_Cursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {}; static char* g_ClipboardTextData = NULL; static bool g_MouseCanUseGlobalState = true; #include struct State { SDL_Window* Window = NULL; Uint64 Time = 0; bool MousePressed[3] = { false, false, false }; SDL_Cursor* MouseCursors[ImGuiMouseCursor_COUNT] = {}; char* ClipboardTextData = NULL; bool MouseCanUseGlobalState = true; }; static std::map g_state; void ImGui_ImplSDL2_SaveState(int id) { auto & dst = g_state[id]; dst.Window = g_Window; dst.Time = g_Time; dst.MousePressed[0] = g_MousePressed[0]; dst.MousePressed[1] = g_MousePressed[1]; dst.MousePressed[2] = g_MousePressed[2]; for (int i = 0; i < ImGuiMouseCursor_COUNT; ++i) { dst.MouseCursors[i] = g_MouseCursors[i]; } dst.ClipboardTextData = g_ClipboardTextData; dst.MouseCanUseGlobalState = g_MouseCanUseGlobalState; } void ImGui_ImplSDL2_LoadState(int id) { const auto & src = g_state[id]; g_Window = src.Window; g_Time = src.Time; g_MousePressed[0] = src.MousePressed[0]; g_MousePressed[1] = src.MousePressed[1]; g_MousePressed[2] = src.MousePressed[2]; for (int i = 0; i < ImGuiMouseCursor_COUNT; ++i) { g_MouseCursors[i] = src.MouseCursors[i]; } g_ClipboardTextData = src.ClipboardTextData; g_MouseCanUseGlobalState = src.MouseCanUseGlobalState; } static const char* ImGui_ImplSDL2_GetClipboardText(void*) { if (g_ClipboardTextData) SDL_free(g_ClipboardTextData); g_ClipboardTextData = SDL_GetClipboardText(); return g_ClipboardTextData; } static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text) { SDL_SetClipboardText(text); } // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. // If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field. bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event) { ImGuiIO& io = ImGui::GetIO(); switch (event->type) { case SDL_MOUSEWHEEL: { if (event->wheel.x > 0) io.MouseWheelH += 1; if (event->wheel.x < 0) io.MouseWheelH -= 1; if (event->wheel.y > 0) io.MouseWheel += 1; if (event->wheel.y < 0) io.MouseWheel -= 1; return true; } case SDL_MOUSEBUTTONDOWN: { if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true; if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true; if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true; return true; } case SDL_TEXTINPUT: { io.AddInputCharactersUTF8(event->text.text); return true; } case SDL_KEYDOWN: case SDL_KEYUP: { int key = event->key.keysym.scancode; IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown)); io.KeysDown[key] = (event->type == SDL_KEYDOWN); io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); #ifdef _WIN32 io.KeySuper = false; #else io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); #endif return true; } } return false; } static bool ImGui_ImplSDL2_Init(SDL_Window* window) { g_Window = window; // Setup back-end capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendPlatformName = "imgui_impl_sdl"; // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB; io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT; io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP; io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN; io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP; io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN; io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME; io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END; io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT; io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE; io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE; io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE; io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN; io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE; io.KeyMap[ImGuiKey_KeyPadEnter] = SDL_SCANCODE_KP_ENTER; io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A; io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C; io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V; io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X; io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y; io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z; io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText; io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText; io.ClipboardUserData = NULL; // Load mouse cursors g_MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); g_MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); g_MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); g_MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); g_MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); g_MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); g_MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); // Check and store if we are on Wayland g_MouseCanUseGlobalState = strncmp(SDL_GetCurrentVideoDriver(), "wayland", 7) != 0; #ifdef _WIN32 SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(window, &wmInfo); io.ImeWindowHandle = wmInfo.info.win.window; #else (void)window; #endif return true; } bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context) { (void)sdl_gl_context; // Viewport branch will need this. return ImGui_ImplSDL2_Init(window); } bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window) { #if !SDL_HAS_VULKAN IM_ASSERT(0 && "Unsupported"); #endif return ImGui_ImplSDL2_Init(window); } bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window) { #if !defined(_WIN32) IM_ASSERT(0 && "Unsupported"); #endif return ImGui_ImplSDL2_Init(window); } bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window) { return ImGui_ImplSDL2_Init(window); } void ImGui_ImplSDL2_Shutdown() { g_Window = NULL; // Destroy last known clipboard data if (g_ClipboardTextData) SDL_free(g_ClipboardTextData); g_ClipboardTextData = NULL; // Destroy SDL mouse cursors for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) SDL_FreeCursor(g_MouseCursors[cursor_n]); memset(g_MouseCursors, 0, sizeof(g_MouseCursors)); } static void ImGui_ImplSDL2_UpdateMousePosAndButtons() { ImGuiIO& io = ImGui::GetIO(); // Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) if (io.WantSetMousePos) SDL_WarpMouseInWindow(g_Window, (int)io.MousePos.x, (int)io.MousePos.y); else io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); int mx, my; Uint32 mouse_buttons = SDL_GetMouseState(&mx, &my); io.MouseDown[0] = g_MousePressed[0] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. io.MouseDown[1] = g_MousePressed[1] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; io.MouseDown[2] = g_MousePressed[2] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false; #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) SDL_Window* focused_window = SDL_GetKeyboardFocus(); if (g_Window == focused_window) { if (g_MouseCanUseGlobalState) { // SDL_GetMouseState() gives mouse position seemingly based on the last window entered/focused(?) // The creation of a new windows at runtime and SDL_CaptureMouse both seems to severely mess up with that, so we retrieve that position globally. // Won't use this workaround when on Wayland, as there is no global mouse position. int wx, wy; SDL_GetWindowPosition(focused_window, &wx, &wy); SDL_GetGlobalMouseState(&mx, &my); mx -= wx; my -= wy; } io.MousePos = ImVec2((float)mx, (float)my); } // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger the OS window resize cursor. // The function is only supported from SDL 2.0.4 (released Jan 2016) bool any_mouse_button_down = ImGui::IsAnyMouseDown(); SDL_CaptureMouse(any_mouse_button_down ? SDL_TRUE : SDL_FALSE); #else if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS) io.MousePos = ImVec2((float)mx, (float)my); #endif } static void ImGui_ImplSDL2_UpdateMouseCursor() { ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return; ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor SDL_ShowCursor(SDL_FALSE); } else { // Show OS mouse cursor SDL_SetCursor(g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); SDL_ShowCursor(SDL_TRUE); } } static void ImGui_ImplSDL2_UpdateGamepads() { ImGuiIO& io = ImGui::GetIO(); memset(io.NavInputs, 0, sizeof(io.NavInputs)); if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) return; // Get gamepad SDL_GameController* game_controller = SDL_GameControllerOpen(0); if (!game_controller) { io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; return; } // Update gamepad inputs #define MAP_BUTTON(NAV_NO, BUTTON_NO) { io.NavInputs[NAV_NO] = (SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0) ? 1.0f : 0.0f; } #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float vn = (float)(SDL_GameControllerGetAxis(game_controller, AXIS_NO) - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; } const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value. MAP_BUTTON(ImGuiNavInput_Activate, SDL_CONTROLLER_BUTTON_A); // Cross / A MAP_BUTTON(ImGuiNavInput_Cancel, SDL_CONTROLLER_BUTTON_B); // Circle / B MAP_BUTTON(ImGuiNavInput_Menu, SDL_CONTROLLER_BUTTON_X); // Square / X MAP_BUTTON(ImGuiNavInput_Input, SDL_CONTROLLER_BUTTON_Y); // Triangle / Y MAP_BUTTON(ImGuiNavInput_DpadLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT); // D-Pad Left MAP_BUTTON(ImGuiNavInput_DpadRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); // D-Pad Right MAP_BUTTON(ImGuiNavInput_DpadUp, SDL_CONTROLLER_BUTTON_DPAD_UP); // D-Pad Up MAP_BUTTON(ImGuiNavInput_DpadDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN); // D-Pad Down MAP_BUTTON(ImGuiNavInput_FocusPrev, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB MAP_BUTTON(ImGuiNavInput_FocusNext, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB MAP_BUTTON(ImGuiNavInput_TweakSlow, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); // L1 / LB MAP_BUTTON(ImGuiNavInput_TweakFast, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); // R1 / RB MAP_ANALOG(ImGuiNavInput_LStickLeft, SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768); MAP_ANALOG(ImGuiNavInput_LStickRight, SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767); MAP_ANALOG(ImGuiNavInput_LStickUp, SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32767); MAP_ANALOG(ImGuiNavInput_LStickDown, SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767); io.BackendFlags |= ImGuiBackendFlags_HasGamepad; #undef MAP_BUTTON #undef MAP_ANALOG } void ImGui_ImplSDL2_NewFrame(SDL_Window* window) { ImGuiIO& io = ImGui::GetIO(); IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); // Setup display size (every frame to accommodate for window resizing) int w, h; int display_w, display_h; SDL_GetWindowSize(window, &w, &h); SDL_GL_GetDrawableSize(window, &display_w, &display_h); io.DisplaySize = ImVec2((float)w, (float)h); if (w > 0 && h > 0) io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h); // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) static Uint64 frequency = SDL_GetPerformanceFrequency(); Uint64 current_time = SDL_GetPerformanceCounter(); io.DeltaTime = g_Time > 0 ? (float)((double)(current_time - g_Time) / frequency) : (float)(1.0f / 60.0f); g_Time = current_time; ImGui_ImplSDL2_UpdateMousePosAndButtons(); ImGui_ImplSDL2_UpdateMouseCursor(); // Update game controllers (if enabled and available) ImGui_ImplSDL2_UpdateGamepads(); } ================================================ FILE: examples/third-party/imgui/imgui-extra/imgui_impl_sdl.h ================================================ // dear imgui: Platform Binding for SDL2 // This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..) // (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.) // Implemented features: // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Clipboard support. // [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE). // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // Missing features: // [ ] Platform: SDL2 handling of IME under Windows appears to be broken and it explicitly disable the regular Windows IME. You can restore Windows IME by compiling SDL with SDL_DISABLE_WINDOWS_IME. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui #pragma once struct SDL_Window; typedef union SDL_Event SDL_Event; IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context); IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window); IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window); IMGUI_IMPL_API bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window); IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown(); IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(SDL_Window* window); IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); IMGUI_IMPL_API void ImGui_ImplSDL2_SaveState(int id); IMGUI_IMPL_API void ImGui_ImplSDL2_LoadState(int id); ================================================ FILE: examples/waver/CMakeLists.txt ================================================ set(TARGET waver) if (EMSCRIPTEN) add_executable(${TARGET} main.cpp common.cpp interface.cpp interface-emscripten.cpp) target_include_directories(${TARGET} PRIVATE .. ${SDL2_INCLUDE_DIRS} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ ) target_link_libraries(${TARGET} PRIVATE ggwave ggwave-common ggwave-common-sdl2 ggsock imgui-sdl2 ${CMAKE_THREAD_LIBS_INIT} ) set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \ -s FORCE_FILESYSTEM=1 \ --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../assets/fonts@/ \ ") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/background-0.png ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/background-0.png COPYONLY) else() add_executable(${TARGET} main.cpp common.cpp interface.cpp interface-unix.cpp) target_include_directories(${TARGET} PRIVATE .. ${SDL2_INCLUDE_DIRS} ) target_link_libraries(${TARGET} PRIVATE ggwave ggwave-common ggwave-common-sdl2 ggsock imgui-sdl2 ${CMAKE_THREAD_LIBS_INIT} ) install(FILES ${PROJECT_SOURCE_DIR}/examples/assets/fonts/DroidSans.ttf DESTINATION bin) install(FILES ${PROJECT_SOURCE_DIR}/examples/assets/fonts/fontawesome-webfont.ttf DESTINATION bin) endif() install(TARGETS ${TARGET} RUNTIME DESTINATION bin) ================================================ FILE: examples/waver/README.md ================================================ # Waver Waver allows you to send and receive text messages from nearby devices through sound waves. This application can be used to communicate with multiple nearby devices at once. Both audible and ultrasound communication protocols are available. The app does not connect to the internet and all information is transmitted only through sound. In order to receive incoming messages you only need to allow access to your device's microphone so that it can record nearby sounds. The main purpose of this app is to showcase the capabilities of the [ggwave](https://github.com/ggerganov/ggwave/) library. It is a convenient way to quickly test the transmission performance on virtually any device with speakers and a microphone. ### Install Download on the App Store Get it on Google Play Get it from the Snap Store #### Linux ```bash sudo snap install waver sudo snap connect waver:audio-record :audio-record ``` #### Mac OS ```bash brew install ggerganov/ggerganov/waver ``` #### Run directly in the browser https://waver.ggerganov.com ## How to use Click on the gif to watch a ~2 min Youtube video: - Before starting - make sure the speaker of your device is enabled and disconnect/unplug any headphones. The app uses your device's speaker to emit sounds when sending a text message - To send a message - tap on "Messages", enter some text at the bottom of the screen and click "Send" - Any nearby device that is also running this application can capture the emitted sound and display the received message - In the settings menu, you can adjust the volume and the transmission protocol that will be used when sending messages. Make sure to adjust the volume level high enough, so the sounds can be picked up by other devices - Tap on "Spectrum" to see a real-time frequency spectrum of the currently captured audio by your device's microphone ## File sharing in a local network As of v1.3.0 Waver supports file sharing. It works like this: - Add files that you would like to transmit by sharing them with Waver - In the "Files" menu, click on "Broadcast". This plays an audio message that contains a file broadcast offer - Nearby devices in the same local network can receive this offer and initiate a TCP/IP connection to your device - The files are transmitted over TCP/IP. The sound message is used only to initiate the network connections between the devices - Waver allows sharing multiple files to multiple devices at once ## Known issues - The browser version does not support on-screen keyboard on mobile devices, so it is not possible to input messages. Use the mobile app instead - In some cases utlrasound transmission is not supported (see [#5](https://github.com/ggerganov/ggwave/issues/5)) ================================================ FILE: examples/waver/build_timestamp-tmpl.h ================================================ static const char * BUILD_TIMESTAMP="@GIT_DATE@ (@GIT_SHA1@)"; ================================================ FILE: examples/waver/common.cpp ================================================ #include "common.h" #include "ggwave-common.h" #include "ggwave/ggwave.h" #include "ggsock/communicator.h" #include "ggsock/file-server.h" #include "ggsock/serialization.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(IOS) || defined(ANDROID) #include "imgui-wrapper/icons_font_awesome.h" #endif #ifndef ICON_FA_COGS #include "icons_font_awesome.h" #endif namespace { std::mutex g_mutex; char * toTimeString(const std::chrono::system_clock::time_point & tp) { std::lock_guard lock(g_mutex); time_t t = std::chrono::system_clock::to_time_t(tp); std::tm * ptm = std::localtime(&t); static char buffer[32]; std::strftime(buffer, 32, "%H:%M:%S", ptm); return buffer; } bool ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) { ImGuiContext& g = *ImGui::GetCurrentContext(); ImGuiWindow* window = g.CurrentWindow; bool hovered = false; bool held = false; bool dragging = false; ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle; if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!) ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags); if (held && delta.x != 0.0f) { ImGui::SetScrollX(window, window->Scroll.x + delta.x); } if (held && delta.y != 0.0f) { ImGui::SetScrollY(window, window->Scroll.y + delta.y); dragging = true; } return dragging; } } namespace ImGui { bool ButtonDisabled(const char* label, const ImVec2& size = ImVec2(0, 0)) { { auto col = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled); col.x *= 0.8; col.y *= 0.8; col.z *= 0.8; PushStyleColor(ImGuiCol_Button, col); PushStyleColor(ImGuiCol_ButtonHovered, col); PushStyleColor(ImGuiCol_ButtonActive, col); } { auto col = ImGui::GetStyleColorVec4(ImGuiCol_Text); col.x *= 0.75; col.y *= 0.75; col.z *= 0.75; PushStyleColor(ImGuiCol_Text, col); } bool result = Button(label, size); PopStyleColor(4); return result; } bool ButtonDisablable(const char* label, const ImVec2& size = ImVec2(0, 0), bool isDisabled = false) { if (isDisabled) { ButtonDisabled(label, size); return false; } return Button(label, size); } bool ButtonSelected(const char* label, const ImVec2& size = ImVec2(0, 0)) { auto col = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); PushStyleColor(ImGuiCol_Button, col); PushStyleColor(ImGuiCol_ButtonHovered, col); bool result = Button(label, size); PopStyleColor(2); return result; } bool ButtonSelectable(const char* label, const ImVec2& size = ImVec2(0, 0), bool isSelected = false) { if (isSelected) return ButtonSelected(label, size); return Button(label, size); } } static const char * kFileBroadcastPrefix = "\xba\xbc\xbb"; static const int kMaxSimultaneousChunkRequests = 4; static const float kBroadcastTime_sec = 60.0f; struct Message { enum Type { Error, Text, FileBroadcast, }; bool received; std::chrono::system_clock::time_point timestamp; std::string data; int protocolId; bool dss; float volume; Type type; }; struct GGWaveStats { bool receiving; bool analyzing; int framesToRecord; int framesLeftToRecord; int framesToAnalyze; int framesLeftToAnalyze; int samplesPerFrame; float sampleRateInp; float sampleRateOut; float sampleRate; int sampleSizeInp; int sampleSizeOut; }; struct State { bool update = false; struct Flags { bool newMessage = false; bool newSpectrum = false; bool newTxAmplitude = false; bool newStats = false; bool newTxProtocols = false; void clear() { memset(this, 0, sizeof(Flags)); } } flags; void apply(State & dst) { if (update == false) return; if (this->flags.newMessage) { dst.update = true; dst.flags.newMessage = true; dst.message = std::move(this->message); } if (this->flags.newSpectrum) { dst.update = true; dst.flags.newSpectrum = true; dst.rxSpectrum = std::move(this->rxSpectrum); } if (this->flags.newTxAmplitude) { dst.update = true; dst.flags.newTxAmplitude = true; dst.txAmplitude.assign(this->txAmplitude); } if (this->flags.newStats) { dst.update = true; dst.flags.newStats = true; dst.stats = std::move(this->stats); } if (this->flags.newTxProtocols) { dst.update = true; dst.flags.newTxProtocols = true; dst.txProtocols = std::move(this->txProtocols); } flags.clear(); update = false; } Message message; std::vector rxSpectrum; GGWave::Amplitude rxAmplitude; GGWave::AmplitudeI16 txAmplitude; GGWaveStats stats; GGWave::TxProtocols txProtocols; }; struct Input { bool update = true; struct Flags { bool newMessage = false; bool needReinit = false; bool changeNeedSpectrum = false; bool stopReceiving = false; bool changeRxProtocols = false; void clear() { memset(this, 0, sizeof(Flags)); } } flags; void apply(Input & dst) { if (update == false) return; if (this->flags.newMessage) { dst.update = true; dst.flags.newMessage = true; dst.message = std::move(this->message); } if (this->flags.needReinit) { dst.update = true; dst.flags.needReinit = true; dst.sampleRateOffset = std::move(this->sampleRateOffset); dst.freqStartShift = std::move(this->freqStartShift); dst.payloadLength = std::move(this->payloadLength); dst.directSequenceSpread = std::move(this->directSequenceSpread); } if (this->flags.changeNeedSpectrum) { dst.update = true; dst.flags.changeNeedSpectrum = true; dst.needSpectrum = std::move(this->needSpectrum); } if (this->flags.stopReceiving) { dst.update = true; dst.flags.stopReceiving = true; } if (this->flags.changeRxProtocols) { dst.update = true; dst.flags.changeRxProtocols = true; dst.rxProtocols = std::move(this->rxProtocols); } flags.clear(); update = false; } // message Message message; // reinit float sampleRateOffset = 0; int freqStartShift = 0; int payloadLength = -1; // spectrum bool needSpectrum = false; // other bool directSequenceSpread = false; // rx protocols GGWave::RxProtocols rxProtocols; }; struct Buffer { std::mutex mutex; State stateCore; Input inputCore; State stateUI; Input inputUI; }; std::atomic g_isRunning; Buffer g_buffer; // file send data struct BroadcastInfo { std::string ip; int port; int key; }; bool g_focusFileSend = false; float g_tLastBroadcast = -100.0f; GGSock::FileServer g_fileServer; // file received data struct FileInfoExtended { bool receiving = false; bool readyToShare = false; bool requestToShare = false; int nReceivedChunks = 0; int nRequestedChunks = 0; std::vector isChunkRequested; std::vector isChunkReceived; }; bool g_hasRemoteInfo = false; int g_remotePort = 23045; std::string g_remoteIP = "127.0.0.1"; bool g_hasReceivedFileInfos = false; bool g_hasRequestedFileInfos = false; bool g_hasReceivedFiles = false; GGSock::FileServer::TFileInfos g_receivedFileInfos; std::map g_receivedFiles; std::map g_receivedFileInfosExtended; GGSock::Communicator g_fileClient(false); // external api int g_shareId = 0; ShareInfo g_shareInfo; int g_openId = 0; OpenInfo g_openInfo; int g_deleteId = 0; DeleteInfo g_deleteInfo; int g_receivedId = 0; int getShareId() { return g_shareId; } ShareInfo getShareInfo() { return g_shareInfo; } int getOpenId() { return g_openId; } OpenInfo getOpenInfo() { return g_openInfo; } int getDeleteId() { return g_deleteId; } DeleteInfo getDeleteInfo() { return g_deleteInfo; } int getReceivedId() { return g_receivedId; } std::vector getReceiveInfos() { std::vector result; for (const auto & file : g_receivedFiles) { if (g_receivedFileInfosExtended[file.second.info.uri].requestToShare == false || g_receivedFileInfosExtended[file.second.info.uri].readyToShare == true) { continue; } result.push_back({ file.second.info.uri.c_str(), file.second.info.filename.c_str(), file.second.data.data(), file.second.data.size(), }); } return result; } bool confirmReceive(const char * uri) { if (g_receivedFiles.find(uri) == g_receivedFiles.end()) { return false; } g_receivedFileInfosExtended[uri].readyToShare = true; g_receivedFiles.erase(uri); return true; } void clearAllFiles() { g_fileServer.clearAllFiles(); } void clearFile(const char * uri) { g_fileServer.clearFile(uri); } void addFile( const char * uri, const char * filename, const char * dataBuffer, size_t dataSize, bool focus) { GGSock::FileServer::FileData file; file.info.uri = uri; file.info.filename = filename; file.data.resize(dataSize); std::memcpy(file.data.data(), dataBuffer, dataSize); g_fileServer.addFile(std::move(file)); if (focus) { g_focusFileSend = true; } } void addFile( const char * uri, const char * filename, std::vector && data, bool focus) { GGSock::FileServer::FileData file; file.info.uri = uri; file.info.filename = filename; file.data = std::move(data); g_fileServer.addFile(std::move(file)); if (focus) { g_focusFileSend = true; } } std::string generateFileBroadcastMessage() { // todo : to binary std::string result; int plen = (int) strlen(kFileBroadcastPrefix); result.resize(plen + 4 + 2 + 2); char *p = &result[0]; for (int i = 0; i < (int) plen; ++i) { *p++ = kFileBroadcastPrefix[i]; } { auto ip = GGSock::Communicator::getLocalAddress(); std::replace(ip.begin(), ip.end(), '.', ' '); std::stringstream ss(ip); { int b; ss >> b; *p++ = b; } { int b; ss >> b; *p++ = b; } { int b; ss >> b; *p++ = b; } { int b; ss >> b; *p++ = b; } } { uint16_t port = g_fileServer.getParameters().listenPort; { int b = port/256; *p++ = b; } { int b = port%256; *p++ = b; } } { uint16_t key = rand()%65536; { int b = key/256; *p++ = b; } { int b = key%256; *p++ = b; } } return result; } BroadcastInfo parseBroadcastInfo(const std::string & message) { BroadcastInfo result; const uint8_t *p = (uint8_t *) message.data(); p += strlen(kFileBroadcastPrefix); result.ip += std::to_string((uint8_t)(*p++)); result.ip += '.'; result.ip += std::to_string((uint8_t)(*p++)); result.ip += '.'; result.ip += std::to_string((uint8_t)(*p++)); result.ip += '.'; result.ip += std::to_string((uint8_t)(*p++)); result.port = 256*((int)(*p++)); result.port += ((int)(*p++)); result.key = 256*((int)(*p++)); result.key += ((int)(*p++)); return result; } bool isFileBroadcastMessage(const std::string & message) { if (message.size() != strlen(kFileBroadcastPrefix) + 4 + 2 + 2) { return false; } bool result = true; auto pSrc = kFileBroadcastPrefix; auto pDst = message.data(); while (*pSrc != 0) { if (*pDst == 0 || *pSrc++ != *pDst++) { result = false; break; } } return result; } std::thread initMainAndRunCore() { initMain(); return std::thread([&]() { while (g_isRunning) { updateCore(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); } void initMain() { g_isRunning = true; GGSock::FileServer::Parameters p; #ifdef __EMSCRIPTEN__ p.nWorkerThreads = 0; #else p.nWorkerThreads = 2; #endif g_fileServer.init(p); g_fileClient.setErrorCallback([](GGSock::Communicator::TErrorCode code) { printf("Disconnected with code = %d\n", code); g_hasReceivedFileInfos = false; g_hasRequestedFileInfos = false; g_hasReceivedFiles = false; }); g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileInfosResponse, [&](const char * dataBuffer, size_t dataSize) { printf("Received message %d, size = %d\n", GGSock::FileServer::MsgFileInfosResponse, (int) dataSize); size_t offset = 0; GGSock::Unserialize()(g_receivedFileInfos, dataBuffer, dataSize, offset); for (const auto & info : g_receivedFileInfos) { printf(" - %s : %s (size = %d, chunks = %d)\n", info.second.uri.c_str(), info.second.filename.c_str(), (int) info.second.filesize, (int) info.second.nChunks); g_receivedFiles[info.second.uri].info = info.second; g_receivedFiles[info.second.uri].data.resize(info.second.filesize); g_receivedFileInfosExtended[info.second.uri] = {}; } g_hasReceivedFileInfos = true; return 0; }); g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileChunkResponse, [&](const char * dataBuffer, size_t dataSize) { GGSock::FileServer::FileChunkResponseData data; size_t offset = 0; GGSock::Unserialize()(data, dataBuffer, dataSize, offset); //printf("Received chunk %d for file '%s', size = %d\n", data.chunkId, data.uri.c_str(), (int) data.data.size()); std::memcpy(g_receivedFiles[data.uri].data.data() + data.pStart, data.data.data(), data.pLen); g_receivedFileInfosExtended[data.uri].nReceivedChunks++; g_receivedFileInfosExtended[data.uri].nRequestedChunks--; g_receivedFileInfosExtended[data.uri].isChunkReceived[data.chunkId] = true; return 0; }); } void updateCore() { static Input inputCurrent; static bool isFirstCall = true; static bool needSpectrum = false; static int rxDataLengthLast = 0; static float rxTimestampLast = 0.0f; static GGWave::TxRxData rxDataLast; auto ggWave = GGWave_instance(); if (ggWave == nullptr) { return; } { std::lock_guard lock(g_buffer.mutex); g_buffer.inputCore.apply(inputCurrent); } if (inputCurrent.update) { if (inputCurrent.flags.newMessage) { int n = (int) inputCurrent.message.data.size(); if (ggWave->init( n, inputCurrent.message.data.data(), GGWave::TxProtocolId(inputCurrent.message.protocolId), 100*inputCurrent.message.volume) == false) { g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newMessage = true; g_buffer.stateCore.message = { false, std::chrono::system_clock::now(), (GGWave::Protocols::tx()[inputCurrent.message.protocolId].extra == 2 && inputCurrent.payloadLength <= 0) ? "MT protocols require fixed-length mode" : "Failed to transmit", ggWave->rxProtocolId(), ggWave->isDSSEnabled(), 0, Message::Error, }; } } if (inputCurrent.flags.needReinit) { static auto sampleRateInpOld = ggWave->sampleRateInp(); static auto sampleRateOutOld = ggWave->sampleRateOut(); static auto freqStartShiftOld = 0; auto sampleFormatInpOld = ggWave->sampleFormatInp(); auto sampleFormatOutOld = ggWave->sampleFormatOut(); auto rxProtocolsOld = ggWave->rxProtocols(); for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; ++i) { GGWave::Protocols::tx()[i].freqStart = std::max(1, GGWave::Protocols::tx()[i].freqStart + inputCurrent.freqStartShift - freqStartShiftOld); rxProtocolsOld[i].freqStart = std::max(1, rxProtocolsOld[i].freqStart + inputCurrent.freqStartShift - freqStartShiftOld); } freqStartShiftOld = inputCurrent.freqStartShift; GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX; if (inputCurrent.directSequenceSpread) mode |= GGWAVE_OPERATING_MODE_USE_DSS; GGWave::Parameters parameters { inputCurrent.payloadLength, sampleRateInpOld, sampleRateOutOld + inputCurrent.sampleRateOffset, GGWave::kDefaultSampleRate, GGWave::kDefaultSamplesPerFrame, GGWave::kDefaultSoundMarkerThreshold, sampleFormatInpOld, sampleFormatOutOld, mode, }; GGWave_reset(¶meters); ggWave = GGWave_instance(); ggWave->rxProtocols() = rxProtocolsOld; } if (inputCurrent.flags.changeNeedSpectrum) { needSpectrum = inputCurrent.needSpectrum; } if (inputCurrent.flags.stopReceiving) { ggWave->rxStopReceiving(); } if (inputCurrent.flags.changeRxProtocols) { ggWave->rxProtocols() = inputCurrent.rxProtocols; } inputCurrent.flags.clear(); inputCurrent.update = false; } GGWave_mainLoop(); rxDataLengthLast = ggWave->rxTakeData(rxDataLast); if (rxDataLengthLast == -1) { g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newMessage = true; g_buffer.stateCore.message = { true, std::chrono::system_clock::now(), "Failed to decode", ggWave->rxProtocolId(), ggWave->isDSSEnabled(), 0, Message::Error, }; } else if (rxDataLengthLast > 0 && ImGui::GetTime() - rxTimestampLast > 0.5f) { auto message = std::string((char *) rxDataLast.data(), rxDataLengthLast); const Message::Type type = isFileBroadcastMessage(message) ? Message::FileBroadcast : Message::Text; g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newMessage = true; g_buffer.stateCore.message = { true, std::chrono::system_clock::now(), std::move(message), ggWave->rxProtocolId(), ggWave->isDSSEnabled(), 0, type, }; rxTimestampLast = ImGui::GetTime(); } if (needSpectrum) { if (ggWave->rxTakeAmplitude(g_buffer.stateCore.rxAmplitude)) { static const int NMax = GGWave::kMaxSamplesPerFrame; static float tmp[2*NMax]; const int N = ggWave->samplesPerFrame(); ggWave->computeFFTR(g_buffer.stateCore.rxAmplitude.data(), tmp, N); g_buffer.stateCore.rxSpectrum.resize(N); for (int i = 0; i < N; ++i) { g_buffer.stateCore.rxSpectrum[i] = (tmp[2*i + 0]*tmp[2*i + 0] + tmp[2*i + 1]*tmp[2*i + 1]); } for (int i = 1; i < N/2; ++i) { g_buffer.stateCore.rxSpectrum[i] += g_buffer.stateCore.rxSpectrum[N - i]; } g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newSpectrum = true; } } if (ggWave->txTakeAmplitudeI16(g_buffer.stateCore.txAmplitude)) { g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newTxAmplitude = true; } if (true) { g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newStats = true; g_buffer.stateCore.stats.receiving = ggWave->rxReceiving(); g_buffer.stateCore.stats.analyzing = ggWave->rxAnalyzing(); g_buffer.stateCore.stats.framesToRecord = ggWave->rxFramesToRecord(); g_buffer.stateCore.stats.framesLeftToRecord = ggWave->rxFramesLeftToRecord(); g_buffer.stateCore.stats.framesToAnalyze = ggWave->rxFramesToAnalyze(); g_buffer.stateCore.stats.framesLeftToAnalyze = ggWave->rxFramesLeftToAnalyze(); g_buffer.stateCore.stats.samplesPerFrame = ggWave->samplesPerFrame(); g_buffer.stateCore.stats.sampleRateInp = ggWave->sampleRateInp(); g_buffer.stateCore.stats.sampleRateOut = ggWave->sampleRateOut(); g_buffer.stateCore.stats.sampleRate = GGWave::kDefaultSampleRate; g_buffer.stateCore.stats.sampleSizeInp = ggWave->sampleSizeInp(); g_buffer.stateCore.stats.sampleSizeOut = ggWave->sampleSizeOut(); } if (isFirstCall) { g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newTxProtocols = true; g_buffer.stateCore.txProtocols = GGWave::Protocols::tx(); isFirstCall = false; } { std::lock_guard lock(g_buffer.mutex); g_buffer.stateCore.apply(g_buffer.stateUI); } } void renderMain() { g_fileServer.update(); if (ImGui::GetTime() - g_tLastBroadcast > kBroadcastTime_sec && g_fileServer.isListening()) { g_fileServer.stopListening(); } if (g_fileClient.isConnected()) { if (!g_hasRequestedFileInfos) { g_receivedFileInfos.clear(); g_receivedFiles.clear(); g_receivedFileInfosExtended.clear(); g_fileClient.send(GGSock::FileServer::MsgFileInfosRequest); g_hasRequestedFileInfos = true; } else { for (const auto & fileInfo : g_receivedFileInfos) { const auto & uri = fileInfo.second.uri; auto & fileInfoExtended = g_receivedFileInfosExtended[uri]; if (fileInfoExtended.receiving == false) { continue; } if (fileInfoExtended.nReceivedChunks == fileInfo.second.nChunks) { continue; } int nextChunkId = 0; while (fileInfoExtended.nRequestedChunks < kMaxSimultaneousChunkRequests) { if (fileInfoExtended.nReceivedChunks + fileInfoExtended.nRequestedChunks == fileInfo.second.nChunks) { break; } while (fileInfoExtended.isChunkRequested[nextChunkId] == true) { ++nextChunkId; } fileInfoExtended.isChunkRequested[nextChunkId] = true; GGSock::FileServer::FileChunkRequestData data; data.uri = fileInfo.second.uri; data.chunkId = nextChunkId; data.nChunksHave = 0; data.nChunksExpected = fileInfo.second.nChunks; GGSock::SerializationBuffer buffer; GGSock::Serialize()(data, buffer); g_fileClient.send(GGSock::FileServer::MsgFileChunkRequest, buffer.data(), (int32_t) buffer.size()); ++fileInfoExtended.nRequestedChunks; } } } } g_fileClient.update(); static State stateCurrent; { std::lock_guard lock(g_buffer.mutex); g_buffer.stateUI.apply(stateCurrent); } enum class WindowId { Settings, Messages, Files, Spectrum, }; enum class SubWindowIdFiles { Send, Receive, }; enum class SubWindowIdSpectrum { Spectrum, Spectrogram, }; struct Settings { int protocolId = GGWAVE_PROTOCOL_AUDIBLE_FAST; bool isSampleRateOffset = false; float sampleRateOffset = -512.0f; bool isFreqStartShift = false; int freqStartShift = 48; bool isFixedLength = false; bool directSequenceSpread = false; int payloadLength = 16; float volume = 0.10f; GGWave::TxProtocols txProtocols; GGWave::RxProtocols rxProtocols; }; static WindowId windowId = WindowId::Messages; static WindowId windowIdLast = windowId; static SubWindowIdFiles subWindowIdFiles = SubWindowIdFiles::Send; static SubWindowIdSpectrum subWindowIdSpectrum = SubWindowIdSpectrum::Spectrum; static Settings settings; const double tHoldContextPopup = 0.2f; const int kMaxInputSize = 140; static char inputBuf[kMaxInputSize]; static bool doInputFocus = false; static bool doSendMessage = false; static bool mouseButtonLeftLast = 0; static bool isTextInput = false; static bool scrollMessagesToBottom = true; static bool hasAudioCaptureData = false; static bool hasNewMessages = false; static bool hasNewSpectrum = false; static bool showSpectrumSettings = true; #ifdef __EMSCRIPTEN__ static bool hasFileSharingSupport = false; #else static bool hasFileSharingSupport = true; #endif #if defined(IOS) || defined(ANDROID) static double tStartInput = 0.0f; static double tEndInput = -100.0f; #endif static double tStartTx = 0.0f; static double tLengthTx = 0.0f; static GGWaveStats statsCurrent; static std::vector spectrumCurrent; static std::vector txAmplitudeCurrent; static std::vector messageHistory; static std::string inputLast = ""; // keyboard shortcuts: if (ImGui::IsKeyPressed(62)) { printf("F5 pressed : clear message history\n"); messageHistory.clear(); } if (ImGui::IsKeyPressed(63)) { if (messageHistory.size() > 0) { printf("F6 pressed : delete last message\n"); messageHistory.erase(messageHistory.end() - 1); } } if (stateCurrent.update) { if (stateCurrent.flags.newMessage) { scrollMessagesToBottom = true; messageHistory.push_back(std::move(stateCurrent.message)); hasNewMessages = true; } if (stateCurrent.flags.newSpectrum) { spectrumCurrent = std::move(stateCurrent.rxSpectrum); hasNewSpectrum = true; hasAudioCaptureData = !spectrumCurrent.empty(); } if (stateCurrent.flags.newTxAmplitude) { txAmplitudeCurrent.resize(stateCurrent.txAmplitude.size()); std::copy(stateCurrent.txAmplitude.begin(), stateCurrent.txAmplitude.end(), txAmplitudeCurrent.begin()); tStartTx = ImGui::GetTime() + (16.0f*1024.0f)/statsCurrent.sampleRateOut; tLengthTx = txAmplitudeCurrent.size()/statsCurrent.sampleRateOut; { auto & ampl = txAmplitudeCurrent; int nBins = 512; int nspb = (int) ampl.size()/nBins; for (int i = 0; i < nBins; ++i) { double sum = 0.0; for (int j = 0; j < nspb; ++j) { sum += std::abs(ampl[i*nspb + j]); } ampl[i] = sum/nspb; } ampl.resize(nBins); } } if (stateCurrent.flags.newStats) { statsCurrent = std::move(stateCurrent.stats); } if (stateCurrent.flags.newTxProtocols) { settings.txProtocols = std::move(stateCurrent.txProtocols); settings.rxProtocols = settings.txProtocols; } stateCurrent.flags.clear(); stateCurrent.update = false; } if (settings.txProtocols.empty()) { printf("No Tx Protocols available\n"); return; } if (g_focusFileSend) { windowId = WindowId::Files; subWindowIdFiles = SubWindowIdFiles::Send; g_focusFileSend = false; } if (mouseButtonLeftLast == 0 && ImGui::GetIO().MouseDown[0] == 1) { ImGui::GetIO().MouseDelta = { 0.0, 0.0 }; } mouseButtonLeftLast = ImGui::GetIO().MouseDown[0]; const auto& displaySize = ImGui::GetIO().DisplaySize; auto& style = ImGui::GetStyle(); const auto sendButtonText = ICON_FA_PLAY_CIRCLE " Send"; #if defined(IOS) || defined(ANDROID) const double tShowKeyboard = 0.2f; #endif #if defined(IOS) const float statusBarHeight = displaySize.x < displaySize.y ? 2.0f*style.ItemSpacing.y : 0.1f; #else const float statusBarHeight = 0.1f; #endif const float menuButtonHeight = 24.0f + 2.0f*style.ItemSpacing.y; const auto & mouse_delta = ImGui::GetIO().MouseDelta; ImGui::SetNextWindowPos({ 0, 0, }); ImGui::SetNextWindowSize(displaySize); ImGui::Begin("Main", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings); ImGui::InvisibleButton("StatusBar", { ImGui::GetContentRegionAvailWidth(), statusBarHeight }); if (ImGui::ButtonSelectable(ICON_FA_COGS, { menuButtonHeight, menuButtonHeight }, windowId == WindowId::Settings )) { windowId = WindowId::Settings; } ImGui::SameLine(); { auto posSave = ImGui::GetCursorScreenPos(); if (ImGui::ButtonSelectable(ICON_FA_COMMENT_ALT " Messages", { 0.35f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Messages)) { windowId = WindowId::Messages; } auto radius = 0.3f*ImGui::GetTextLineHeight(); posSave.x += 2.0f*radius; posSave.y += 2.0f*radius; if (hasNewMessages) { ImGui::GetWindowDrawList()->AddCircleFilled(posSave, radius, ImGui::ColorConvertFloat4ToU32({ 1.0f, 0.0f, 0.0f, 1.0f }), 16); } } ImGui::SameLine(); if (!hasFileSharingSupport) { ImGui::ButtonDisabled(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Text("File sharing is not supported on this platform!"); ImGui::EndTooltip(); } } else if (ImGui::ButtonSelectable(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Files)) { windowId = WindowId::Files; } ImGui::SameLine(); { auto posSave = ImGui::GetCursorScreenPos(); if (ImGui::ButtonSelectable(ICON_FA_SIGNAL " Spectrum", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Spectrum)) { if (windowId == WindowId::Spectrum) { showSpectrumSettings = !showSpectrumSettings; } windowId = WindowId::Spectrum; } auto radius = 0.3f*ImGui::GetTextLineHeight(); posSave.x += 2.0f*radius; posSave.y += 2.0f*radius; ImGui::GetWindowDrawList()->AddCircleFilled(posSave, radius, hasAudioCaptureData ? ImGui::ColorConvertFloat4ToU32({ 0.0f, 1.0f, 0.0f, 1.0f }) : ImGui::ColorConvertFloat4ToU32({ 1.0f, 0.0f, 0.0f, 1.0f }), 16); } if ((windowIdLast != windowId) || (hasAudioCaptureData == false)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.changeNeedSpectrum = true; g_buffer.inputUI.needSpectrum = (windowId == WindowId::Spectrum) || (hasAudioCaptureData == false); windowIdLast = windowId; } if (windowId == WindowId::Settings) { ImGui::BeginChild("Settings:main", ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImGui::Text("Waver v1.5.3"); ImGui::Separator(); ImGui::Text("%s", ""); ImGui::Text("Sample rate (capture): %g, %d B/sample", statsCurrent.sampleRateInp, statsCurrent.sampleSizeInp); ImGui::Text("Sample rate (playback): %g, %d B/sample", statsCurrent.sampleRateOut, statsCurrent.sampleSizeOut); const float kLabelWidth = ImGui::CalcTextSize("Inp. SR Offset: ").x; // volume ImGui::Text("%s", ""); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); if (settings.volume < 0.2f) { ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 0.5f }, "Normal volume"); } else if (settings.volume < 0.5f) { ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 0.5f }, "Intermediate volume"); } else { ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 0.5f }, "Warning: high volume!"); } } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Volume: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } { auto p0 = ImGui::GetCursorScreenPos(); { auto & cols = ImGui::GetStyle().Colors; ImGui::PushStyleColor(ImGuiCol_FrameBg, cols[ImGuiCol_WindowBg]); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, cols[ImGuiCol_WindowBg]); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, cols[ImGuiCol_WindowBg]); ImGui::SliderFloat("##volume", &settings.volume, 0.0f, 1.0f); ImGui::PopStyleColor(3); } auto posSave = ImGui::GetCursorScreenPos(); ImGui::SameLine(); auto p1 = ImGui::GetCursorScreenPos(); p1.x -= ImGui::CalcTextSize(" ").x; p1.y += ImGui::GetTextLineHeightWithSpacing() + 0.5f*style.ItemInnerSpacing.y; ImGui::GetWindowDrawList()->AddRectFilledMultiColor( p0, { 0.35f*(p0.x + p1.x), p1.y }, ImGui::ColorConvertFloat4ToU32({0.0f, 1.0f, 0.0f, 0.5f}), ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f}), ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f}), ImGui::ColorConvertFloat4ToU32({0.0f, 1.0f, 0.0f, 0.5f}) ); ImGui::GetWindowDrawList()->AddRectFilledMultiColor( { 0.35f*(p0.x + p1.x), p0.y }, p1, ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f}), ImGui::ColorConvertFloat4ToU32({1.0f, 0.0f, 0.0f, 0.5f}), ImGui::ColorConvertFloat4ToU32({1.0f, 0.0f, 0.0f, 0.5f}), ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f}) ); ImGui::SetCursorScreenPos(posSave); } // tx protocol ImGui::Text("%s", ""); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::TextDisabled("[U] = ultrasound"); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::TextDisabled("[DT] = dual-tone"); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::TextDisabled("[MT] = mono-tone"); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Tx Protocol: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } if (ImGui::BeginCombo("##txProtocol", settings.txProtocols[settings.protocolId].name)) { for (int i = 0; i < (int) settings.txProtocols.size(); ++i) { const auto & txProtocol = settings.txProtocols[i]; if (txProtocol.name == nullptr) continue; const bool isSelected = (settings.protocolId == i); if (ImGui::Selectable(txProtocol.name, isSelected)) { settings.protocolId = i; } if (isSelected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Bandwidth: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } { const auto & protocol = settings.txProtocols[settings.protocolId]; const float bandwidth = ((float(0.715f*protocol.bytesPerTx)/(protocol.framesPerTx*statsCurrent.samplesPerFrame))*statsCurrent.sampleRate)/protocol.extra; ImGui::Text("%4.2f B/s", bandwidth); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Frequencies: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } { const float df = statsCurrent.sampleRate/statsCurrent.samplesPerFrame; const auto & protocol = settings.txProtocols[settings.protocolId]; const auto freqStart = std::max(1, protocol.freqStart + (settings.isFreqStartShift ? settings.freqStartShift : 0)); const float f0 = df*freqStart; const float f1 = df*(freqStart + float(2*16*protocol.bytesPerTx)/protocol.extra); ImGui::Text("%6.2f Hz - %6.2f Hz", f0, f1); } // fixed-length ImGui::Text("%s", ""); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::PushTextWrapPos(); ImGui::TextDisabled("Fixed-length Tx/Rx does not use sound-markers"); ImGui::PopTextWrapPos(); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Fixed-length: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } if (ImGui::Checkbox("##fixed-length", &settings.isFixedLength)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.needReinit = true; g_buffer.inputUI.payloadLength = settings.isFixedLength ? settings.payloadLength : -1; } if (settings.isFixedLength) { ImGui::SameLine(); ImGui::PushItemWidth(0.5*ImGui::GetContentRegionAvailWidth()); if (ImGui::DragInt("Bytes", &settings.payloadLength, 1, 1, GGWave::kMaxLengthFixed)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.needReinit = true; g_buffer.inputUI.payloadLength = settings.isFixedLength ? settings.payloadLength : -1; } ImGui::PopItemWidth(); } // Direct-sequence spread //ImGui::Text("%s", ""); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::PushTextWrapPos(); ImGui::TextDisabled("Direct-sequence spread"); ImGui::PopTextWrapPos(); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Use DSS: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } if (ImGui::Checkbox("##direct-sequence-spread", &settings.directSequenceSpread)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.needReinit = true; g_buffer.inputUI.directSequenceSpread = settings.directSequenceSpread; } // FreqStart offset //ImGui::Text("%s", ""); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::PushTextWrapPos(); ImGui::TextDisabled("Apply tx/rx frequency shift"); ImGui::PopTextWrapPos(); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Freq shift: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } if (ImGui::Checkbox("##freq-start-offset", &settings.isFreqStartShift)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.needReinit = true; g_buffer.inputUI.freqStartShift = settings.isFreqStartShift ? settings.freqStartShift : 0; } if (settings.isFreqStartShift) { ImGui::SameLine(); ImGui::PushItemWidth(0.5*ImGui::GetContentRegionAvailWidth()); if (ImGui::DragInt("bins", &settings.freqStartShift, 1, -64, 64, "%d")) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.needReinit = true; g_buffer.inputUI.freqStartShift = settings.isFreqStartShift ? settings.freqStartShift : 0; } ImGui::PopItemWidth(); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text(""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } { const float df = statsCurrent.sampleRate/statsCurrent.samplesPerFrame; ImGui::Text("%6.2f Hz", df*settings.freqStartShift); } } // Output sample-rate offset //ImGui::Text("%s", ""); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::PushTextWrapPos(); ImGui::TextDisabled("Modify the output Sampling Rate"); ImGui::PopTextWrapPos(); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Pitch shift: "); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); } if (ImGui::Checkbox("##output-sample-rate-offset", &settings.isSampleRateOffset)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.needReinit = true; g_buffer.inputUI.sampleRateOffset = settings.isSampleRateOffset ? settings.sampleRateOffset : 0; } if (settings.isSampleRateOffset) { ImGui::SameLine(); ImGui::PushItemWidth(0.5*ImGui::GetContentRegionAvailWidth()); if (ImGui::SliderFloat("Samples", &settings.sampleRateOffset, -1000, 1000, "%.0f")) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.needReinit = true; g_buffer.inputUI.sampleRateOffset = settings.isSampleRateOffset ? settings.sampleRateOffset : 0; } ImGui::PopItemWidth(); } // rx protocols bool updateRxProtocols = false; ImGui::Text("%s", ""); { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); ImGui::PushTextWrapPos(); ImGui::TextDisabled("Waver will receive only the selected protocols:"); ImGui::PopTextWrapPos(); } { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("Rx Protocols: "); ImGui::SetCursorScreenPos(posSave); } { ImGui::PushID("RxProtocols"); for (int i = 0; i < settings.rxProtocols.size(); ++i) { auto & rxProtocol = settings.rxProtocols[i]; if (rxProtocol.name == nullptr) continue; auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("%s", ""); ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y }); if (ImGui::Checkbox(rxProtocol.name, &rxProtocol.enabled)) { updateRxProtocols = true; } } ImGui::PopID(); } if (updateRxProtocols) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.changeRxProtocols = true; g_buffer.inputUI.rxProtocols = settings.rxProtocols; } ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); ImGui::EndChild(); } if (windowId == WindowId::Messages) { const float messagesInputHeight = 2*ImGui::GetTextLineHeightWithSpacing(); const float messagesHistoryHeigthMax = ImGui::GetContentRegionAvail().y - messagesInputHeight - 2.0f*style.ItemSpacing.x; float messagesHistoryHeigth = messagesHistoryHeigthMax; hasNewMessages = false; // no automatic screen resize support for iOS #if defined(IOS) || defined(ANDROID) if (displaySize.x < displaySize.y) { if (isTextInput) { messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; } else { messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax - 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; } } else { if (isTextInput) { messagesHistoryHeigth -= 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; } else { messagesHistoryHeigth -= 0.5f*displaySize.y - 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; } } #endif bool showScrollToBottom = false; const auto wPos0 = ImGui::GetCursorScreenPos(); const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), messagesHistoryHeigth }; ImGui::BeginChild("Messages:history", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); static bool isDragging = false; static bool isHoldingMessage = false; static bool isHoldingInput = false; static int messageIdHolding = 0; const float tMessageFlyIn = 0.3f; // we need this because we push messages in the next loop if (messageHistory.capacity() == messageHistory.size()) { messageHistory.reserve(messageHistory.size() + 16); } for (int i = 0; i < (int) messageHistory.size(); ++i) { ImGui::PushID(i); const auto & message = messageHistory[i]; const float tRecv = 0.001f*std::chrono::duration_cast(std::chrono::system_clock::now() - message.timestamp).count(); const float interp = std::min(tRecv, tMessageFlyIn)/tMessageFlyIn; const float xoffset = std::max(0.0f, (1.0f - interp)*ImGui::GetContentRegionAvailWidth()); if (xoffset > 0.0f) { ImGui::Indent(xoffset); } else { ImGui::PushTextWrapPos(); } const auto msgStatus = message.received ? "Recv" : "Send"; const auto msgColor = message.received ? ImVec4 { 0.0f, 1.0f, 0.0f, interp } : ImVec4 { 1.0f, 1.0f, 0.0f, interp }; ImGui::TextDisabled("%s |", ::toTimeString(message.timestamp)); ImGui::SameLine(); ImGui::TextColored(msgColor, "%s", msgStatus); ImGui::SameLine(); ImGui::TextDisabled("|"); ImGui::SameLine(); ImGui::TextColored({ 0.0f, 0.6f, 0.4f, interp }, "%s", settings.txProtocols[message.protocolId].name); ImGui::SameLine(); if (message.dss) { ImGui::TextColored({ 0.4f, 0.6f, 0.4f, interp }, "DSS"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Direct Sequence Spread"); } ImGui::SameLine(); } ImGui::TextDisabled("|"); { auto p0 = ImGui::GetCursorScreenPos(); auto p00 = p0; p00.y -= ImGui::GetTextLineHeightWithSpacing(); p0.x -= style.ItemSpacing.x; p0.y -= 0.5f*style.ItemSpacing.y; switch (message.type) { case Message::Error: { auto col = ImVec4 { 1.0f, 0.0f, 0.0f, 1.0f }; col.w = interp; ImGui::TextColored(col, "Error: %s", message.data.c_str()); } break; case Message::Text: { auto col = style.Colors[ImGuiCol_Text]; col.w = interp; ImGui::TextColored(col, "%s", message.data.c_str()); } break; case Message::FileBroadcast: { auto col = ImVec4 { 0.0f, 1.0f, 1.0f, 1.0f }; col.w = interp; auto broadcastInfo = parseBroadcastInfo(message.data); ImGui::TextColored(col, "-=[ File Broadcast from %s:%d ]=-", broadcastInfo.ip.c_str(), broadcastInfo.port); } break; } auto p1 = ImGui::GetCursorScreenPos(); p1.x = p00.x + ImGui::GetContentRegionAvailWidth(); if (xoffset == 0.0f) { if (ImGui::IsMouseHoveringRect(p00, p1, true)) { auto col = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled); col.w *= ImGui::GetIO().MouseDownDuration[0] > tHoldContextPopup ? 0.25f : 0.10f; ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, ImGui::ColorConvertFloat4ToU32(col), 12.0f); if (ImGui::GetIO().MouseDownDuration[0] > tHoldContextPopup) { isHoldingMessage = true; messageIdHolding = i; } } } } if (xoffset == 0.0f) { ImGui::PopTextWrapPos(); } ImGui::Text("%s", ""); ImGui::PopID(); } if (ImGui::IsMouseReleased(0) && isHoldingMessage && isDragging == false) { auto pos = ImGui::GetMousePos(); pos.x -= 1.0f*ImGui::CalcTextSize("Resend | Copy").x; pos.y -= 1.0f*ImGui::GetTextLineHeightWithSpacing(); ImGui::SetNextWindowPos(pos); ImGui::OpenPopup("Message options"); isHoldingMessage = false; } if (ImGui::BeginPopup("Message options")) { const auto & messageSelected = messageHistory[messageIdHolding]; if (ImGui::ButtonDisablable("Resend", {}, messageSelected.type != Message::Text)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.newMessage = true; g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, messageSelected.dss, settings.volume, Message::Text }; messageHistory.push_back(g_buffer.inputUI.message); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); ImGui::TextDisabled("|"); ImGui::SameLine(); if (ImGui::ButtonDisablable("Copy", {}, messageSelected.type != Message::Text)) { SDL_SetClipboardText(messageSelected.data.c_str()); ImGui::CloseCurrentPopup(); } if (messageSelected.type == Message::FileBroadcast) { ImGui::SameLine(); ImGui::TextDisabled("|"); ImGui::SameLine(); if (ImGui::ButtonDisablable("Receive", {}, !messageSelected.received || messageSelected.type != Message::FileBroadcast || !hasFileSharingSupport)) { auto broadcastInfo = parseBroadcastInfo(messageSelected.data); g_remoteIP = broadcastInfo.ip; g_remotePort = broadcastInfo.port; g_hasRemoteInfo = true; g_fileClient.disconnect(); g_hasReceivedFileInfos = false; g_hasRequestedFileInfos = false; g_hasReceivedFiles = false; windowId = WindowId::Files; subWindowIdFiles = SubWindowIdFiles::Receive; ImGui::CloseCurrentPopup(); } } ImGui::EndPopup(); } if (scrollMessagesToBottom) { ImGui::SetScrollHereY(); scrollMessagesToBottom = false; } if (ImGui::GetScrollY() < ImGui::GetScrollMaxY() - 10) { showScrollToBottom = true; } if (showScrollToBottom) { auto posSave = ImGui::GetCursorScreenPos(); auto butSize = ImGui::CalcTextSize(ICON_FA_ARROW_CIRCLE_DOWN); ImGui::SetCursorScreenPos({ wPos0.x + wSize.x - 2.0f*butSize.x - 2*style.ItemSpacing.x, wPos0.y + wSize.y - 2.0f*butSize.y - 2*style.ItemSpacing.y }); if (ImGui::Button(ICON_FA_ARROW_CIRCLE_DOWN)) { scrollMessagesToBottom = true; } ImGui::SetCursorScreenPos(posSave); } { bool isDraggingCur = ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); if (isDraggingCur && ImGui::IsMouseDown(0)) { isDragging = true; } else if (isDraggingCur == false && ImGui::IsMouseDown(0) == false) { isDragging = false; } } ImGui::EndChild(); if (statsCurrent.receiving) { if (statsCurrent.analyzing) { ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Analyzing ..."); ImGui::SameLine(); ImGui::ProgressBar(1.0f - float(statsCurrent.framesLeftToAnalyze)/statsCurrent.framesToAnalyze, { ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x, ImGui::GetTextLineHeight() }); } else { ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Receiving ..."); ImGui::SameLine(); if (ImGui::SmallButton("Stop")) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.stopReceiving = true; } ImGui::SameLine(); ImGui::ProgressBar(1.0f - float(statsCurrent.framesLeftToRecord)/statsCurrent.framesToRecord, { ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x, ImGui::GetTextLineHeight() }); } } else { static float amax = 0.0f; static float frac = 0.0f; amax = 0.0f; frac = (ImGui::GetTime() - tStartTx)/tLengthTx; if (txAmplitudeCurrent.size() && frac <= 1.0f) { struct Funcs { static float Sample(void * data, int i) { auto res = std::fabs(((int16_t *)(data))[i]) ; if (res > amax) amax = res; return res; } static float SampleFrac(void * data, int i) { if (i > frac*txAmplitudeCurrent.size()) { return 0.0f; } return std::fabs(((int16_t *)(data))[i]); } }; auto posSave = ImGui::GetCursorScreenPos(); auto wSize = ImGui::GetContentRegionAvail(); wSize.x = ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x; wSize.y = ImGui::GetTextLineHeight(); ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.3f, 0.3f, 0.3f, 0.3f }); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); ImGui::PlotHistogram("##plotSpectrumCurrent", Funcs::Sample, txAmplitudeCurrent.data(), (int) txAmplitudeCurrent.size(), 0, (std::string("")).c_str(), 0.0f, FLT_MAX, wSize); ImGui::PopStyleColor(2); ImGui::SetCursorScreenPos(posSave); ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.0f, 0.0f, 0.0f, 0.0f }); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 0.0f, 1.0f, 0.0f, 1.0f }); ImGui::PlotHistogram("##plotSpectrumCurrent", Funcs::SampleFrac, txAmplitudeCurrent.data(), (int) txAmplitudeCurrent.size(), 0, (std::string("")).c_str(), 0.0f, amax, wSize); ImGui::PopStyleColor(2); } else { if (settings.isFixedLength) { ImGui::TextDisabled("Listening for waves (fixed-length %d bytes)", settings.payloadLength); } else { ImGui::TextDisabled("Listening for waves (variable-length)"); } } } if (doInputFocus) { ImGui::SetKeyboardFocusHere(); doInputFocus = false; } doSendMessage = false; { auto pos0 = ImGui::GetCursorScreenPos(); ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x); if (ImGui::InputText("##Messages:Input", inputBuf, kMaxInputSize, ImGuiInputTextFlags_EnterReturnsTrue)) { doSendMessage = true; } ImGui::PopItemWidth(); if (isTextInput == false && inputBuf[0] == 0) { auto drawList = ImGui::GetWindowDrawList(); pos0.x += style.ItemInnerSpacing.x; pos0.y += 0.5*style.ItemInnerSpacing.y; static char tmp[128]; snprintf(tmp, 128, "Send message using '%s'", settings.txProtocols[settings.protocolId].name); drawList->AddText(pos0, ImGui::ColorConvertFloat4ToU32({0.0f, 0.6f, 0.4f, 1.0f}), tmp); } } if (ImGui::IsItemActive() && isTextInput == false) { SDL_StartTextInput(); isTextInput = true; #if defined(IOS) || defined(ANDROID) tStartInput = ImGui::GetTime(); #endif } bool requestStopTextInput = false; if (ImGui::IsItemDeactivated()) { requestStopTextInput = true; } if (isTextInput) { if (ImGui::IsItemHovered() && ImGui::GetIO().MouseDownDuration[0] > tHoldContextPopup) { isHoldingInput = true; } } if (ImGui::IsMouseReleased(0) && isHoldingInput) { auto pos = ImGui::GetMousePos(); pos.x -= 2.0f*ImGui::CalcTextSize("Paste").x; pos.y -= 1.0f*ImGui::GetTextLineHeightWithSpacing(); ImGui::SetNextWindowPos(pos); ImGui::OpenPopup("Input options"); isHoldingInput = false; } if (ImGui::BeginPopup("Input options")) { if (ImGui::Button("Paste")) { for (int i = 0; i < kMaxInputSize; ++i) inputBuf[i] = 0; strncpy(inputBuf, SDL_GetClipboardText(), kMaxInputSize - 1); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::SameLine(); { auto posCur = ImGui::GetCursorScreenPos(); posCur.y -= ImGui::GetTextLineHeightWithSpacing(); ImGui::SetCursorScreenPos(posCur); } if ((ImGui::Button(sendButtonText, { 0, 2*ImGui::GetTextLineHeightWithSpacing() }) || doSendMessage)) { if (inputBuf[0] == 0) { strncpy(inputBuf, inputLast.data(), kMaxInputSize - 1); } if (inputBuf[0] != 0) { inputLast = std::string(inputBuf); g_buffer.inputUI.update = true; g_buffer.inputUI.flags.newMessage = true; g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.directSequenceSpread, settings.volume, Message::Text }; messageHistory.push_back(g_buffer.inputUI.message); inputBuf[0] = 0; doInputFocus = true; scrollMessagesToBottom = true; } } if (!ImGui::IsItemHovered() && requestStopTextInput) { SDL_StopTextInput(); isTextInput = false; #if defined(IOS) || defined(ANDROID) tEndInput = ImGui::GetTime(); #endif } } if (windowId == WindowId::Files) { const float subWindowButtonHeight = menuButtonHeight; if (ImGui::ButtonSelectable("Send", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, subWindowIdFiles == SubWindowIdFiles::Send)) { subWindowIdFiles = SubWindowIdFiles::Send; } ImGui::SameLine(); if (ImGui::ButtonSelectable("Receive", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, subWindowIdFiles == SubWindowIdFiles::Receive)) { subWindowIdFiles = SubWindowIdFiles::Receive; } switch (subWindowIdFiles) { case SubWindowIdFiles::Send: { const float statusWindowHeight = 2*style.ItemInnerSpacing.y + 4*ImGui::GetTextLineHeightWithSpacing(); bool hasAtLeastOneFile = false; { const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - statusWindowHeight - 2*style.ItemInnerSpacing.y }; ImGui::BeginChild("Files:Send:fileInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); auto fileInfos = g_fileServer.getFileInfos(); for (const auto & fileInfo : fileInfos) { hasAtLeastOneFile = true; ImGui::PushID(fileInfo.first); ImGui::Text("File: '%s' (%4.2f MB)\n", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f); if (ImGui::Button(ICON_FA_SHARE_ALT " Share")) { g_shareInfo.uri = fileInfo.second.uri; g_shareInfo.filename = fileInfo.second.filename; const auto & fileData = g_fileServer.getFileData(g_shareInfo.uri); g_shareInfo.dataBuffer = fileData.data.data(); g_shareInfo.dataSize = fileData.data.size(); g_shareId++; } ImGui::SameLine(); #ifdef ANDROID if (ImGui::Button(ICON_FA_EYE " VIEW")) { g_openInfo.uri = fileInfo.second.uri; g_openInfo.filename = fileInfo.second.filename; const auto & fileData = g_fileServer.getFileData(g_openInfo.uri); g_openInfo.dataBuffer = fileData.data.data(); g_openInfo.dataSize = fileData.data.size(); g_openId++; } ImGui::SameLine(); #endif if (ImGui::Button(ICON_FA_TRASH " Clear")) { g_deleteInfo.uri = fileInfo.second.uri.data(); g_deleteInfo.filename = fileInfo.second.filename.data(); g_deleteId++; } ImGui::PopID(); } ImGui::PushTextWrapPos(); if (hasAtLeastOneFile == false) { ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "There are currently no files availble to share."); #if defined(IOS) || defined(ANDROID) ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Share some files with this app to be able to broadcast them to nearby devices through sound."); #else ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Drag and drop some files on this window to be able to broadcast them to nearby devices through sound."); #endif ImGui::TextColored({ 1.0f, 0.6f, 0.0f, 1.0f }, "File sharing works only between peers in the same local network!"); } ImGui::PopTextWrapPos(); ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left); ImGui::EndChild(); } { const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - style.ItemInnerSpacing.y }; ImGui::BeginChild("Files:Send:clientInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); if (g_fileServer.isListening() == false) { ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Not accepting new connections."); } else { ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Accepting new connections at: %s:%d (%4.1f sec)", GGSock::Communicator::getLocalAddress().c_str(), g_fileServer.getParameters().listenPort, kBroadcastTime_sec - ImGui::GetTime() + g_tLastBroadcast); } auto clientInfos = g_fileServer.getClientInfos(); if (clientInfos.empty()) { ImGui::Text("No peers currently connected .."); } else { for (const auto & clientInfo : clientInfos) { ImGui::Text("Peer: %s\n", clientInfo.second.address.c_str()); } } ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left); ImGui::EndChild(); } { if (ImGui::Button("Broadcast", { 0.40f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.newMessage = true; g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), ::generateFileBroadcastMessage(), settings.protocolId, settings.directSequenceSpread, settings.volume, Message::FileBroadcast }; messageHistory.push_back(g_buffer.inputUI.message); g_tLastBroadcast = ImGui::GetTime(); g_fileServer.startListening(); } ImGui::SameLine(); if (ImGui::ButtonDisablable("Stop", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !g_fileServer.isListening())) { g_fileServer.stopListening(); } ImGui::SameLine(); if (ImGui::ButtonDisablable("Clear", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !hasAtLeastOneFile)) { g_deleteInfo.uri = "###ALL-FILES###"; g_deleteInfo.filename = ""; g_deleteId++; } } } break; case SubWindowIdFiles::Receive: { const float statusWindowHeight = 2*style.ItemInnerSpacing.y + 4*ImGui::GetTextLineHeightWithSpacing(); { const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - statusWindowHeight - 2*style.ItemInnerSpacing.y }; ImGui::BeginChild("Files:Receive:fileInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); int toErase = -1; auto fileInfos = g_receivedFileInfos; for (const auto & fileInfo : fileInfos) { ImGui::PushID(fileInfo.first); ImGui::Text("File: '%s' (%4.2f MB)\n", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f); const auto & uri = fileInfo.second.uri; auto & fileInfoExtended = g_receivedFileInfosExtended[uri]; const bool isReceived = fileInfo.second.nChunks == fileInfoExtended.nReceivedChunks; if (isReceived) { if (fileInfoExtended.requestToShare == false) { if (ImGui::Button(ICON_FA_FOLDER " To Send")) { fileInfoExtended.requestToShare = true; g_receivedId++; } } if (fileInfoExtended.readyToShare) { ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Ready to share!"); } } else if (g_fileClient.isConnected() && (fileInfoExtended.receiving || fileInfoExtended.nReceivedChunks > 0)) { if (fileInfoExtended.receiving) { if (ImGui::Button(ICON_FA_PAUSE " Pause")) { fileInfoExtended.receiving = false; } } else { if (ImGui::Button(ICON_FA_PLAY " Resume")) { fileInfoExtended.receiving = true; } } ImGui::SameLine(); ImGui::ProgressBar(float(fileInfoExtended.nReceivedChunks)/fileInfo.second.nChunks); } else if (g_fileClient.isConnected()) { if (ImGui::Button(ICON_FA_DOWNLOAD " Receive")) { fileInfoExtended.receiving = true; fileInfoExtended.isChunkReceived.resize(fileInfo.second.nChunks); fileInfoExtended.isChunkRequested.resize(fileInfo.second.nChunks); } } else { ImGui::Text("%s", ""); } if ((fileInfoExtended.receiving == false || isReceived) && fileInfoExtended.requestToShare == false) { ImGui::SameLine(); if (ImGui::Button(ICON_FA_TRASH " Clear")) { toErase = fileInfo.first; } } ImGui::PopID(); } if (toErase != -1) { g_receivedFiles.erase(g_receivedFileInfos[toErase].uri); g_receivedFileInfosExtended.erase(g_receivedFileInfos[toErase].uri); g_receivedFileInfos.erase(toErase); } ImGui::EndChild(); } { const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - style.ItemInnerSpacing.y }; ImGui::BeginChild("Files:Receive:status", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImGui::PushTextWrapPos(); if (g_hasRemoteInfo == false) { ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, "There is no broadcast offer selected yet."); ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, "Wait for a broadcast message to be received first."); } else { if (g_fileClient.isConnected()) { ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Successfully connected to peer:"); } else { ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Broadcast offer from the following peer:"); } ImGui::Text("Remote IP: %s", g_remoteIP.c_str()); ImGui::Text("Remote Port: %d", g_remotePort); if (g_fileClient.isConnecting()) { ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Attempting to connect ..."); } } ImGui::PopTextWrapPos(); ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left); ImGui::EndChild(); } { if (g_fileClient.isConnecting() == false && g_fileClient.isConnected() == false) { if (ImGui::ButtonDisablable("Connect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !g_hasRemoteInfo)) { g_fileClient.connect(g_remoteIP, g_remotePort, 0); } } if (g_fileClient.isConnecting() || g_fileClient.isConnected()) { if (ImGui::Button("Disconnect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { g_fileClient.disconnect(); g_hasReceivedFileInfos = false; g_hasRequestedFileInfos = false; g_hasReceivedFiles = false; } } } } break; }; } if (windowId == WindowId::Spectrum) { if (hasAudioCaptureData == false) { ImGui::Text("%s", ""); ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, "No capture data available!"); ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, "Please make sure you have allowed microphone access for this app."); } else { const int nBins = statsCurrent.samplesPerFrame/2; static int binMin = 20; static int binMax = 100; static float scale = 30.0f; static bool showFPS = false; static bool showRx = true; static bool showSpectrogram = false; static const float kSpectrogramTime_s = 3.0f; // 3 seconds data static int freqDataSize = (kSpectrogramTime_s*statsCurrent.sampleRateInp)/statsCurrent.samplesPerFrame; static int freqDataHead = 0; struct FreqData { float freq; std::vector mag; }; static std::vector freqData; if (freqData.empty()) { float df = statsCurrent.sampleRateInp/statsCurrent.samplesPerFrame; freqData.resize(nBins); for (int i = 0; i < nBins; ++i) { freqData[i].freq = df*i; freqData[i].mag.resize(freqDataSize); } } if (hasNewSpectrum) { for (int i = 0; i < (int) freqData.size(); ++i) { freqData[i].mag[freqDataHead] = spectrumCurrent[i]; } if (++freqDataHead == freqDataSize) { freqDataHead = 0; } hasNewSpectrum = false; } if (showSpectrumSettings) { auto width = ImGui::GetContentRegionAvailWidth(); ImGui::PushItemWidth(0.5*width); static char buf[64]; snprintf(buf, 64, "Bin: %3d, Freq: %5.2f Hz", binMin, 0.5*binMin*statsCurrent.sampleRateInp/nBins); ImGui::DragInt("##binMin", &binMin, 1, 0, binMax - 2, buf); ImGui::SameLine(); ImGui::Checkbox("FPS", &showFPS); ImGui::SameLine(); ImGui::Checkbox("Spectrogram", &showSpectrogram); snprintf(buf, 64, "Bin: %3d, Freq: %5.2f Hz", binMax, 0.5*binMax*statsCurrent.sampleRateInp/nBins); ImGui::DragInt("##binMax", &binMax, 1, binMin + 1, nBins, buf); ImGui::SameLine(); ImGui::Checkbox("Rx", &showRx); ImGui::SameLine(); ImGui::DragFloat("##scale", &scale, 1.0f, 1.0f, 1000.0f); ImGui::PopItemWidth(); } if (showSpectrogram) { subWindowIdSpectrum = SubWindowIdSpectrum::Spectrogram; } else { subWindowIdSpectrum = SubWindowIdSpectrum::Spectrum; } auto p0 = ImGui::GetCursorScreenPos(); auto p1 = ImGui::GetContentRegionAvail(); p1.x += p0.x; p1.y += p0.y; if (ImGui::IsMouseHoveringRect(p0, p1) && ImGui::IsMouseClicked(0)) { g_buffer.inputUI.update = true; g_buffer.inputUI.flags.changeNeedSpectrum = true; g_buffer.inputUI.needSpectrum = !g_buffer.inputUI.needSpectrum; } auto itemSpacingSave = style.ItemSpacing; style.ItemSpacing.x = 0.0f; style.ItemSpacing.y = 0.0f; auto windowPaddingSave = style.WindowPadding; style.WindowPadding.x = 0.0f; style.WindowPadding.y = 0.0f; auto childBorderSizeSave = style.ChildBorderSize; style.ChildBorderSize = 0.0f; ImGui::BeginChild("Spectrum:main", ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); switch (subWindowIdSpectrum) { case SubWindowIdSpectrum::Spectrum: { ImGui::PushTextWrapPos(); if (showFPS) { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("FPS: %4.2f\n", ImGui::GetIO().Framerate); ImGui::SetCursorScreenPos(posSave); } auto wSize = ImGui::GetContentRegionAvail(); ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.3f, 0.3f, 0.3f, 0.3f }); if (statsCurrent.receiving) { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 1.0f, 0.0f, 0.0f, 1.0f }); } else { ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 0.0f, 1.0f, 0.0f, 1.0f }); } ImGui::PlotHistogram("##plotSpectrumCurrent", spectrumCurrent.data() + binMin, binMax - binMin, 0, (std::string("Current Spectrum")).c_str(), 0.0f, FLT_MAX, wSize); ImGui::PopStyleColor(2); ImGui::PopTextWrapPos(); } break; case SubWindowIdSpectrum::Spectrogram: { if (showFPS) { auto posSave = ImGui::GetCursorScreenPos(); ImGui::Text("FPS: %4.2f\n", ImGui::GetIO().Framerate); ImGui::SetCursorScreenPos(posSave); } float sum = 0.0; for (int i = binMin; i < binMax; ++i) { for (int j = 0; j < freqDataSize; ++j) { sum += freqData[i].mag[j]; } } int nf = binMax - binMin; sum /= (nf*freqDataSize); const auto wSize = ImGui::GetContentRegionAvail(); const float dx = wSize.x/nf; const float dy = wSize.y/freqDataSize; auto p0 = ImGui::GetCursorScreenPos(); int nChildWindows = 0; int nFreqPerChild = 32; ImGui::PushID(nChildWindows++); ImGui::BeginChild("Spectrogram", { wSize.x, (nFreqPerChild + 1)*dy }, true); auto drawList = ImGui::GetWindowDrawList(); for (int j = 0; j < freqDataSize; ++j) { if (j > 0 && j % nFreqPerChild == 0) { ImGui::EndChild(); ImGui::PopID(); ImGui::PushID(nChildWindows++); ImGui::SetCursorScreenPos({ p0.x, p0.y + nFreqPerChild*int(j/nFreqPerChild)*dy }); ImGui::BeginChild("Spectrogram", { wSize.x, (nFreqPerChild + 1)*dy }, true); drawList = ImGui::GetWindowDrawList(); } for (int i = 0; i < nf; ++i) { int k = freqDataHead + j; if (k >= freqDataSize) k -= freqDataSize; auto v = freqData[binMin + i].mag[k]; ImVec4 c = { 0.0f, 1.0f, 0.0, 0.0f }; c.w = v/(scale*sum); drawList->AddRectFilled({ p0.x + i*dx, p0.y + j*dy }, { p0.x + i*dx + dx, p0.y + j*dy + dy }, ImGui::ColorConvertFloat4ToU32(c)); } } ImGui::EndChild(); ImGui::PopID(); while (showRx && messageHistory.size() > 0) { const auto& msg = messageHistory.back(); static float tRecv = 0.0; if (g_buffer.inputUI.needSpectrum) { tRecv = 0.001f*std::chrono::duration_cast(std::chrono::system_clock::now() - msg.timestamp).count(); } if (tRecv > 2.0f*kSpectrogramTime_s || msg.received == false) { break; } const auto & protocol = settings.txProtocols[msg.protocolId]; const int msgLength_bytes = settings.isFixedLength ? 1.4f*settings.payloadLength : 1.4f*msg.data.size() + GGWave::kDefaultEncodedDataOffset; const int msgLength_frames = settings.isFixedLength ? ((msgLength_bytes + protocol.bytesPerTx - 1)/protocol.bytesPerTx)*protocol.framesPerTx : ((msgLength_bytes + protocol.bytesPerTx - 1)/protocol.bytesPerTx)*protocol.framesPerTx + 2*GGWave::kDefaultMarkerFrames; const float frameLength_s = (float(statsCurrent.samplesPerFrame)/statsCurrent.sampleRateInp); const float x0 = protocol.freqStart - binMin; const float x1 = x0 + 32*protocol.bytesPerTx; const float y1 = freqDataSize - tRecv/frameLength_s + (settings.isFixedLength ? 0.0f : 0.5*GGWave::kDefaultMarkerFrames); const float y0 = y1 - msgLength_frames; ImVec4 c = { 1.0f, 0.0f, 0.0, 1.0f }; drawList->AddRect({ p0.x + x0*dx, p0.y + y0*dy }, { p0.x + x1*dx + dx, p0.y + y1*dy }, ImGui::ColorConvertFloat4ToU32(c)); break; } } break; }; ImGui::EndChild(); style.ItemSpacing = itemSpacingSave; style.WindowPadding = windowPaddingSave; style.ChildBorderSize = childBorderSizeSave; } } ImGui::End(); ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Backspace]] = false; ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Enter]] = false; { std::lock_guard lock(g_buffer.mutex); g_buffer.inputUI.apply(g_buffer.inputCore); } } void deinitMain() { g_isRunning = false; } ================================================ FILE: examples/waver/common.h ================================================ #pragma once #include "ggwave-common-sdl2.h" #include #include std::thread initMainAndRunCore(); void initMain(); void updateCore(); void renderMain(); void deinitMain(); // share info struct ShareInfo { std::string uri; std::string filename; const char * dataBuffer; size_t dataSize; }; int getShareId(); ShareInfo getShareInfo(); // open info struct OpenInfo { std::string uri; std::string filename; const char * dataBuffer; size_t dataSize; }; int getOpenId(); OpenInfo getOpenInfo(); // delete file struct DeleteInfo { std::string uri; std::string filename; }; int getDeleteId(); DeleteInfo getDeleteInfo(); // receive struct ReceiveInfo { const char * uri; const char * filename; const char * dataBuffer; size_t dataSize; }; int getReceivedId(); std::vector getReceiveInfos(); bool confirmReceive(const char * uri); // input void clearAllFiles(); void clearFile(const char * uri); void addFile( const char * uri, const char * filename, const char * dataBuffer, size_t dataSize, bool focus); void addFile( const char * uri, const char * filename, std::vector && data, bool focus); ================================================ FILE: examples/waver/index-tmpl.html ================================================ Waver

Waver: Data Over Sound

Loading WebAssembly module - please wait ...


================================================ FILE: examples/waver/interface-emscripten.cpp ================================================ #include "interface.h" #include void interface_addFile( const char * , const char * , const char * , size_t ) { } void interface_loadAllFiles() { } void interface_shareFile( const char * , const char * , const char * , size_t ) { } void interface_openFile( const char * , const char * , const char * , size_t ) { } void interface_deleteFile( const char * , const char * ) { } void interface_receiveFile( const char * , const char * , const char * , size_t ) { } bool interface_needReloadFiles() { return false; } ================================================ FILE: examples/waver/interface-unix.cpp ================================================ #include "interface.h" #include "pfd/pfd.h" #include void interface_addFile( const char * , const char * , const char * , size_t ) { } void interface_loadAllFiles() { } void interface_shareFile( const char * , const char * filename, const char * dataBuffer, size_t dataSize) { auto f = pfd::save_file("Save file", filename, { "All Files", "*" }, pfd::opt::none); if (f.result().empty() == false) { printf("Saving to: %s\n", f.result().c_str()); std::ofstream fout(f.result(), std::ios::binary); if (fout.is_open() && fout.good()) { fout.write(dataBuffer, dataSize); fout.close(); } } } void interface_openFile( const char * , const char * , const char * , size_t ) { } void interface_deleteFile( const char * , const char * ) { } void interface_receiveFile( const char * uri, const char * filename, const char * dataBuffer, size_t dataSize) { addFile(uri, filename, dataBuffer, dataSize, false); } bool interface_needReloadFiles() { return false; } ================================================ FILE: examples/waver/interface.cpp ================================================ #include "interface.h" int g_lastShareId = 0; int g_lastOpenId = 0; int g_lastDeleteId = 0; int g_lastReceivedId = 0; int g_frameCount = 0; void updateMain() { auto curShareId = getShareId(); if (curShareId != g_lastShareId) { auto shareInfo = getShareInfo(); interface_shareFile( shareInfo.uri.data(), shareInfo.filename.data(), shareInfo.dataBuffer, shareInfo.dataSize); g_lastShareId = curShareId; } auto curOpenId = getOpenId(); if (curOpenId != g_lastOpenId) { auto openInfo = getOpenInfo(); interface_openFile( openInfo.uri.data(), openInfo.filename.data(), openInfo.dataBuffer, openInfo.dataSize); g_lastOpenId = curOpenId; } auto curDeleteId = getDeleteId(); if (curDeleteId != g_lastDeleteId) { auto deleteInfo = getDeleteInfo(); interface_deleteFile(deleteInfo.uri.c_str(), deleteInfo.filename.c_str()); bool isRemoveAll = std::string(deleteInfo.uri) == "###ALL-FILES###"; if (interface_needReloadFiles() || isRemoveAll) { clearAllFiles(); interface_loadAllFiles(); } else { clearFile(deleteInfo.uri.c_str()); } g_lastDeleteId = curDeleteId; } auto curReceivedId = getReceivedId(); if (curReceivedId != g_lastReceivedId) { auto receiveInfos = getReceiveInfos(); int n = (int) receiveInfos.size(); for (int i = 0; i < n; ++i) { interface_receiveFile( receiveInfos[i].uri, receiveInfos[i].filename, receiveInfos[i].dataBuffer, receiveInfos[i].dataSize); confirmReceive(receiveInfos[i].uri); } if (interface_needReloadFiles()) { clearAllFiles(); interface_loadAllFiles(); } g_lastReceivedId = curReceivedId; } } ================================================ FILE: examples/waver/interface.h ================================================ #ifndef interface_h #define interface_h #ifdef __cplusplus #include "common.h" extern "C" { #endif void interface_addFile( const char * uri, const char * filename, const char * dataBuffer, size_t dataSize); void interface_loadAllFiles(); void interface_shareFile( const char * uri, const char * filename, const char * dataBuffer, size_t dataSize); void interface_openFile( const char * uri, const char * filename, const char * dataBuffer, size_t dataSize); void interface_deleteFile( const char * uri, const char * filename); void interface_receiveFile( const char * uri, const char * filename, const char * dataBuffer, size_t dataSize); bool interface_needReloadFiles(); void updateMain(); #ifdef __cplusplus } #endif #endif /* interface_h */ ================================================ FILE: examples/waver/main.cpp ================================================ #include "interface.h" #include "ggwave-common.h" #ifdef __EMSCRIPTEN__ #include "build_timestamp.h" #include "emscripten/emscripten.h" #else #define EMSCRIPTEN_KEEPALIVE #endif #include #include #include #include #include #include const float kGlobalImGuiScale = 1.25f; // ImGui helpers bool ImGui_tryLoadFont(const std::string & filename, float size = 14.0f, bool merge = false) { printf("Trying to load font from '%s' ..\n", filename.c_str()); std::ifstream f(filename); if (f.good() == false) { printf(" - failed\n"); return false; } printf(" - success\n"); if (merge) { // todo : ugly static !!! static ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; static ImFontConfig config; config.MergeMode = true; config.GlyphOffset = { 0.0f, 0.0f }; ImGui::GetIO().Fonts->AddFontFromFileTTF(filename.c_str(), size, &config, ranges); } else { ImGui::GetIO().Fonts->AddFontFromFileTTF(filename.c_str(), size); } return true; } bool ImGui_BeginFrame(SDL_Window * window) { SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ProcessEvent(&event); if (event.type == SDL_QUIT) return false; if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) return false; if (event.type == SDL_DROPFILE) { printf("Dropped file: '%s'\n", event.drop.file); auto data = readFile(event.drop.file); if (data.empty()) { fprintf(stderr, "Unable to access file. Probably missing permissions\n"); continue; } std::string uri = event.drop.file; std::string filename = event.drop.file; if (uri.find("/") || uri.find("\\")) { filename = uri.substr(uri.find_last_of("/\\") + 1); } addFile(uri.c_str(), filename.c_str(), std::move(data), true); } } ImGui_NewFrame(window); return true; } bool ImGui_EndFrame(SDL_Window * window) { // Rendering int display_w, display_h; SDL_GetWindowSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); glClearColor(0.0f, 0.0f, 0.0f, 0.4f); glClear(GL_COLOR_BUFFER_BIT); ImGui::Render(); ImGui_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(window); return true; } bool ImGui_SetStyle() { ImGuiStyle & style = ImGui::GetStyle(); style.AntiAliasedFill = true; style.AntiAliasedLines = true; style.WindowRounding = 0.0f; style.WindowPadding = ImVec2(8, 8); style.WindowRounding = 0.0f; style.FramePadding = ImVec2(4, 3); style.FrameRounding = 0.0f; style.ItemSpacing = ImVec2(8, 4); style.ItemInnerSpacing = ImVec2(4, 4); style.IndentSpacing = 21.0f; style.ScrollbarSize = 16.0f; style.ScrollbarRounding = 9.0f; style.GrabMinSize = 10.0f; style.GrabRounding = 3.0f; style.Colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.24f, 0.41f, 0.41f, 1.00f); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.20f, 0.60f); //style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f); style.Colors[ImGuiCol_PopupBg] = ImVec4(0.07f, 0.07f, 0.09f, 1.00f); style.Colors[ImGuiCol_Border] = ImVec4(0.31f, 0.31f, 0.31f, 0.71f); style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); style.Colors[ImGuiCol_FrameBg] = ImVec4(0.00f, 0.39f, 0.39f, 0.39f); style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); style.Colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.50f, 0.50f, 0.70f); style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.50f, 0.50f, 1.00f); style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.00f, 0.70f, 0.70f, 1.00f); style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.00f, 0.70f, 0.70f, 1.00f); style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.10f, 0.27f, 0.27f, 1.00f); style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.80f, 0.80f, 0.83f, 0.31f); style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); //style.Colors[ImGuiCol_ComboBg] = ImVec4(0.00f, 0.39f, 0.39f, 1.00f); style.Colors[ImGuiCol_CheckMark] = ImVec4(0.80f, 0.80f, 0.83f, 0.39f); style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.80f, 0.80f, 0.83f, 0.39f); style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); style.Colors[ImGuiCol_Button] = ImVec4(0.13f, 0.55f, 0.55f, 1.00f); style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.61f, 1.00f, 0.00f, 0.51f); style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); style.Colors[ImGuiCol_Header] = ImVec4(0.79f, 0.51f, 0.00f, 0.51f); style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); //style.Colors[ImGuiCol_Column] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); //style.Colors[ImGuiCol_ColumnHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); //style.Colors[ImGuiCol_ColumnActive] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.00f, 0.78f, 0.00f, 1.00f); //style.Colors[ImGuiCol_CloseButton] = ImVec4(0.40f, 0.39f, 0.38f, 0.16f); //style.Colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.26f, 1.00f, 1.00f, 0.39f); //style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.79f, 0.51f, 0.00f, 0.67f); style.Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.65f, 0.38f, 0.67f); style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); style.Colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.65f, 0.38f, 0.67f); style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.25f, 1.00f, 0.00f, 1.00f); style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.25f, 1.00f, 0.00f, 0.43f); style.Colors[ImGuiCol_ModalWindowDarkening] = ImVec4(1.00f, 0.98f, 0.95f, 0.78f); style.ScaleAllSizes(kGlobalImGuiScale); return true; } static std::function g_doInit; static std::function g_setWindowSize; static std::function g_mainUpdate; void mainUpdate(void *) { g_mainUpdate(); } // JS interface extern "C" { EMSCRIPTEN_KEEPALIVE int do_init() { return g_doInit(); } EMSCRIPTEN_KEEPALIVE void set_window_size(int sizeX, int sizeY) { g_setWindowSize(sizeX, sizeY); } } int main(int argc, char** argv) { #ifdef __EMSCRIPTEN__ printf("Build time: %s\n", BUILD_TIMESTAMP); printf("Press the Init button to start\n"); if (argv[1]) { GGWave_setDefaultCaptureDeviceName(argv[1]); } #endif auto argm = parseCmdArguments(argc, argv); int captureId = argm["c"].empty() ? 0 : std::stoi(argm["c"]); int playbackId = argm["p"].empty() ? 0 : std::stoi(argm["p"]); if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "Error: %s\n", SDL_GetError()); return -1; } ImGui_PreInit(); int windowX = 400; int windowY = 600; // Waver video settings //float scale = 0.65; //int windowX = scale*570; //int windowY = scale*917; const char * windowTitle = "Waver"; #ifdef __EMSCRIPTEN__ SDL_Renderer * renderer; SDL_Window * window; SDL_CreateWindowAndRenderer(windowX, windowY, SDL_WINDOW_OPENGL, &window, &renderer); #else SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_Window * window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowX, windowY, window_flags); #endif void * gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); SDL_GL_SetSwapInterval(1); // Enable vsync ImGui_Init(window, gl_context); ImGui::GetIO().IniFilename = nullptr; { bool isNotLoaded = true; isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "DroidSans.ttf", kGlobalImGuiScale*14.0f, false); isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "../bin/DroidSans.ttf", kGlobalImGuiScale*14.0f, false); isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "../examples/assets/fonts/DroidSans.ttf", kGlobalImGuiScale*14.0f, false); isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "../../examples/assets/fonts/DroidSans.ttf", kGlobalImGuiScale*14.0f, false); } { bool isNotLoaded = true; isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "fontawesome-webfont.ttf", kGlobalImGuiScale*14.0f, true); isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "../bin/fontawesome-webfont.ttf", kGlobalImGuiScale*14.0f, true); isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "../examples/assets/fonts/fontawesome-webfont.ttf", kGlobalImGuiScale*14.0f, true); isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + "../../examples/assets/fonts/fontawesome-webfont.ttf", kGlobalImGuiScale*14.0f, true); } ImGui_SetStyle(); SDL_EventState(SDL_DROPFILE, SDL_ENABLE); ImGui_NewFrame(window); ImGui::Render(); // tmp //addFile("test0.raw", "test0.raw", std::vector(1024)); //addFile("test1.jpg", "test0.jpg", std::vector(1024*1024 + 624)); //addFile("test2.mpv", "test0.mov", std::vector(1024*1024*234 + 53827)); bool isInitialized = false; std::thread worker; g_doInit = [&]() { if (GGWave_init(playbackId, captureId) == false) { fprintf(stderr, "Failed to initialize GGWave\n"); return false; } #ifdef __EMSCRIPTEN__ initMain(); #else worker = initMainAndRunCore(); #endif isInitialized = true; return true; }; g_setWindowSize = [&](int sizeX, int sizeY) { SDL_SetWindowSize(window, sizeX, sizeY); }; g_mainUpdate = [&]() { if (isInitialized == false) { return true; } if (ImGui_BeginFrame(window) == false) { return false; } renderMain(); updateMain(); ImGui_EndFrame(window); #ifdef __EMSCRIPTEN__ updateCore(); #endif return true; }; #ifdef __EMSCRIPTEN__ emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true); #else if (g_doInit() == false) { printf("Error: failed to initialize audio\n"); return -2; } while (true) { if (g_mainUpdate() == false) break; } deinitMain(); worker.join(); GGWave_deinit(); // Cleanup ImGui_Shutdown(); ImGui::DestroyContext(); SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_CloseAudio(); SDL_Quit(); #endif return 0; } ================================================ FILE: examples/waver/style.css ================================================ body { background-image: url('background-0.png'); margin: 0; background-color: white; -webkit-font-smoothing: subpixel-antialiased; font-smoothing: subpixel-antialiased; } #screen { margin: 0; padding: 0; font-size: 13px; height: 100%; font: sans-serif; } .no-sel { -moz-user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; -ms-user-select:none; user-select:none; -o-user-select:none; } .cell { pointer-events: none; } .cell-version { padding-left: 4px; padding-top: 0.5em; text-align: left; display: inline-block; float: left; color: rgba(0, 0, 0, 0.75); } .cell-about { padding-right: 24px; padding-top: 0.5em; text-align: right; display: inline-block; float: right; } .nav-link { text-decoration: none; color: rgba(0, 0, 0, 1.0); } #main-container { font-size:12px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } textarea { font-size:12px; font-family: monospace; } .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } div.emscripten_border { border: 1px solid black; } canvas.emscripten { border: 0px none; background-color: black; text-shadow: 4px 4px #1f1f1fb4; } .spinner { height: 30px; width: 30px; margin: 0; margin-top: 20px; margin-left: 20px; display: inline-block; vertical-align: top; -webkit-animation: rotation .8s linear infinite; -moz-animation: rotation .8s linear infinite; -o-animation: rotation .8s linear infinite; animation: rotation 0.8s linear infinite; border-left: 5px solid rgb(235, 235, 235); border-right: 5px solid rgb(235, 235, 235); border-bottom: 5px solid rgb(235, 235, 235); border-top: 5px solid rgb(120, 120, 120); border-radius: 100%; background-color: rgb(189, 215, 46); } @-webkit-keyframes rotation { from {-webkit-transform: rotate(0deg);} to {-webkit-transform: rotate(360deg);} } @-moz-keyframes rotation { from {-moz-transform: rotate(0deg);} to {-moz-transform: rotate(360deg);} } @-o-keyframes rotation { from {-o-transform: rotate(0deg);} to {-o-transform: rotate(360deg);} } @keyframes rotation { from {transform: rotate(0deg);} to {transform: rotate(360deg);} } #status { display: inline-block; vertical-align: top; margin-top: 30px; margin-left: 20px; font-weight: bold; color: rgb(120, 120, 120); } #progress { height: 20px; width: 30px; } #output { width: 800px; height: 200px; margin: 0 auto; margin-top: 10px; border-left: 0px; border-right: 0px; padding-left: 0px; padding-right: 0px; background-color: black; color: white; font-size:10px; font-family: 'Lucida Console', Monaco, monospace; outline: none; } .led-box { height: 30px; width: 25%; margin: 10px 0; float: left; } .led-box p { font-size: 12px; text-align: center; margin: 1em; } .led-red { margin: 0 auto; width: 12px; height: 12px; background-color: #F00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 12px; -webkit-animation: blinkRed 0.5s infinite; -moz-animation: blinkRed 0.5s infinite; -ms-animation: blinkRed 0.5s infinite; -o-animation: blinkRed 0.5s infinite; animation: blinkRed 0.5s infinite; } @-webkit-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-moz-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-ms-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @-o-keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } @keyframes blinkRed { from { background-color: #F00; } 50% { background-color: #A00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 0;} to { background-color: #F00; } } .led-yellow { margin: 0 auto; width: 12px; height: 12px; background-color: #FF0; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px; -webkit-animation: blinkYellow 1s infinite; -moz-animation: blinkYellow 1s infinite; -ms-animation: blinkYellow 1s infinite; -o-animation: blinkYellow 1s infinite; animation: blinkYellow 1s infinite; } @-webkit-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-moz-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-ms-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @-o-keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } @keyframes blinkYellow { from { background-color: #FF0; } 50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; } to { background-color: #FF0; } } .led-green { margin: 0 auto; width: 12px; height: 12px; background-color: #ABFF00; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px; } .led-blue { margin: 0 auto; width: 18px; height: 18px; background-color: #24E0FF; border-radius: 50%; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } table td { border: 1px solid #e8e8e8; } table th, table td { padding: 10px 10px; } td[Attributes Style] { text-align: -webkit-center; } td { display: table-cell; vertical-align: inherit; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { margin-bottom: 30px; width: 800px; text-align: left; color: #3f3f3f; border-collapse: collapse; border: 1px solid #e8e8e8; } table { border-collapse: separate; border-spacing: 2px; } #description { margin: 10px; padding: 10px; color: rgba(255, 255, 255, 1.00); text-shadow: 2px 2px #1f1f1fb4; } .text-body { color: rgba(255, 255, 255, 1.00); text-shadow: 2px 2px #1f1f1fb4; } .cell-version { padding-left: 4px; padding-top: 0.5em; text-align: left; display: inline-block; float: left; color: rgba(255, 255, 255, 0.75); text-shadow: 2px 2px #1f1f1fb4; } .cell-about { padding-right: 24px; padding-top: 0.5em; text-align: right; display: inline-block; float: right; } .nav-link { text-decoration: none; color: rgba(255, 255, 255, 1.0); text-shadow: 2px 2px #1f1f1fb4; } .nav-link2 { text-decoration: none; color: rgba(0, 255, 0, 1.0); text-shadow: 2px 2px #1f1f1fb4; } svg { -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */ filter: invert(100%); } ================================================ FILE: include/ggwave/ggwave.h ================================================ #ifndef GGWAVE_H #define GGWAVE_H #ifdef GGWAVE_SHARED # ifdef _WIN32 # ifdef GGWAVE_BUILD # define GGWAVE_API __declspec(dllexport) # else # define GGWAVE_API __declspec(dllimport) # endif # else # define GGWAVE_API __attribute__ ((visibility ("default"))) # endif #else # define GGWAVE_API #endif #if defined(ARDUINO_UNO) #define GGWAVE_CONFIG_FEW_PROTOCOLS #endif #ifdef __cplusplus extern "C" { #endif // // C interface // #define GGWAVE_MAX_INSTANCES 4 // Data format of the audio samples typedef enum { GGWAVE_SAMPLE_FORMAT_UNDEFINED, GGWAVE_SAMPLE_FORMAT_U8, GGWAVE_SAMPLE_FORMAT_I8, GGWAVE_SAMPLE_FORMAT_U16, GGWAVE_SAMPLE_FORMAT_I16, GGWAVE_SAMPLE_FORMAT_F32, } ggwave_SampleFormat; // Protocol ids typedef enum { #ifndef GGWAVE_CONFIG_FEW_PROTOCOLS GGWAVE_PROTOCOL_AUDIBLE_NORMAL, GGWAVE_PROTOCOL_AUDIBLE_FAST, GGWAVE_PROTOCOL_AUDIBLE_FASTEST, GGWAVE_PROTOCOL_ULTRASOUND_NORMAL, GGWAVE_PROTOCOL_ULTRASOUND_FAST, GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, #endif GGWAVE_PROTOCOL_DT_NORMAL, GGWAVE_PROTOCOL_DT_FAST, GGWAVE_PROTOCOL_DT_FASTEST, GGWAVE_PROTOCOL_MT_NORMAL, GGWAVE_PROTOCOL_MT_FAST, GGWAVE_PROTOCOL_MT_FASTEST, #ifndef GGWAVE_CONFIG_FEW_PROTOCOLS GGWAVE_PROTOCOL_CUSTOM_0, GGWAVE_PROTOCOL_CUSTOM_1, GGWAVE_PROTOCOL_CUSTOM_2, GGWAVE_PROTOCOL_CUSTOM_3, GGWAVE_PROTOCOL_CUSTOM_4, GGWAVE_PROTOCOL_CUSTOM_5, GGWAVE_PROTOCOL_CUSTOM_6, GGWAVE_PROTOCOL_CUSTOM_7, GGWAVE_PROTOCOL_CUSTOM_8, GGWAVE_PROTOCOL_CUSTOM_9, #endif GGWAVE_PROTOCOL_COUNT, } ggwave_ProtocolId; typedef enum { GGWAVE_FILTER_HANN, GGWAVE_FILTER_HAMMING, GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS, } ggwave_Filter; // Operating modes of ggwave // // GGWAVE_OPERATING_MODE_RX: // The instance will be able to receive audio data // // GGWAVE_OPERATING_MODE_TX: // The instance will be able generate audio waveforms for transmission // // GGWAVE_OPERATING_MODE_TX_ONLY_TONES: // The encoding process generates only a list of tones instead of full audio // waveform. This is useful for low-memory devices and embedded systems. // // GGWAVE_OPERATING_MODE_USE_DSS: // Enable the built-in Direct Sequence Spread (DSS) algorithm // enum { GGWAVE_OPERATING_MODE_RX = 1 << 1, GGWAVE_OPERATING_MODE_TX = 1 << 2, GGWAVE_OPERATING_MODE_RX_AND_TX = (GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX), GGWAVE_OPERATING_MODE_TX_ONLY_TONES = 1 << 3, GGWAVE_OPERATING_MODE_USE_DSS = 1 << 4, }; // GGWave instance parameters // // If payloadLength <= 0, then GGWave will transmit with variable payload length // depending on the provided payload. Sound markers are used to identify the // start and end of the transmission. // // If payloadLength > 0, then the transmitted payload will be of the specified // fixed length. In this case, no sound markers are emitted and a slightly // different decoding scheme is applied. This is useful in cases where the // length of the payload is known in advance. // // The sample rates are values typically between 1000 and 96000. // Default value: GGWave::kDefaultSampleRate // // The captured audio is resampled to the specified sampleRate if sampleRatInp // is different from sampleRate. Same applies to the transmitted audio. // // The samplesPerFrame is the number of samples on which ggwave performs FFT. // This affects the number of bins in the Fourier spectrum. // Default value: GGWave::kDefaultSamplesPerFrame // // The operatingMode controls which functions of the ggwave instance are enabled. // Use this parameter to reduce the memory footprint of the ggwave instance. For // example, if only Rx is enabled, then the memory buffers needed for the Tx will // not be allocated. // typedef struct { int payloadLength; // payload length float sampleRateInp; // capture sample rate float sampleRateOut; // playback sample rate float sampleRate; // the operating sample rate int samplesPerFrame; // number of samples per audio frame float soundMarkerThreshold; // sound marker detection threshold ggwave_SampleFormat sampleFormatInp; // format of the captured audio samples ggwave_SampleFormat sampleFormatOut; // format of the playback audio samples int operatingMode; // operating mode } ggwave_Parameters; // GGWave instances are identified with an integer and are stored // in a private map container. Using void * caused some issues with // the python module and unfortunately had to do it this way typedef int ggwave_Instance; // Change file stream for internal ggwave logging. NULL - disable logging // // Intentionally passing it as void * instead of FILE * to avoid including a header // // // log to standard error // ggwave_setLogFile(stderr); // // // log to standard output // ggwave_setLogFile(stdout); // // // disable logging // ggwave_setLogFile(NULL); // // Note: not thread-safe. Do not call while any GGWave instances are running // GGWAVE_API void ggwave_setLogFile(void * fptr); // Helper method to get default instance parameters GGWAVE_API ggwave_Parameters ggwave_getDefaultParameters(void); // Create a new GGWave instance with the specified parameters // // The newly created instance is added to the internal map container. // This function returns an id that can be used to identify this instance. // Make sure to deallocate the instance at the end by calling ggwave_free() // GGWAVE_API ggwave_Instance ggwave_init(ggwave_Parameters parameters); // Free a GGWave instance GGWAVE_API void ggwave_free(ggwave_Instance instance); // Encode data into audio waveform // // instance - the GGWave instance to use // payloadBuffer - the data to encode // payloadSize - number of bytes in the input payloadBuffer // protocolId - the protocol to use for encoding // volume - the volume of the generated waveform [0, 100] // usually 25 is OK and you should not go over 50 // waveformBuffer - the generated audio waveform. must be big enough to fit the generated data // query - if == 0, encode data in to waveformBuffer, returns number of bytes // if != 0, do not perform encoding. // if == 1, return waveform size in bytes // if != 1, return waveform size in samples // // returns the number of generated bytes or samples (see query) // // returns -1 if there was an error // // This function can be used to encode some binary data (payload) into an audio waveform. // // payload -> waveform // // When calling it, make sure that the waveformBuffer is big enough to store the // generated waveform. This means that its size must be at least: // // nSamples*sizeOfSample_bytes // // Where nSamples is the number of audio samples in the waveform and sizeOfSample_bytes // is the size of a single sample in bytes based on the sampleFormatOut parameter // specified during the initialization of the GGWave instance. // // If query != 0, then this function does not perform the actual encoding and just // outputs the expected size of the waveform that would be generated if you call it // with query == 0. This mechanism can be used to ask ggwave how much memory to // allocate for the waveformBuffer. For example: // // // this is the data to encode // const char * payload = "test"; // // // query the number of bytes in the waveform // int n = ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FAST, 25, NULL, 1); // // // allocate the output buffer // char waveform[n]; // // // generate the waveform // ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FAST, 25, waveform, 0); // // The payloadBuffer can be any binary data that you would like to transmit (i.e. the payload). // Usually, this is some text, but it can be any sequence of bytes. // GGWAVE_API int ggwave_encode( ggwave_Instance instance, const void * payloadBuffer, int payloadSize, ggwave_ProtocolId protocolId, int volume, void * waveformBuffer, int query); // Decode an audio waveform into data // // instance - the GGWave instance to use // waveformBuffer - the audio waveform // waveformSize - number of bytes in the input waveformBuffer // payloadBuffer - stores the decoded data on success // the maximum size of the output is GGWave::kMaxDataSize // // returns the number of decoded bytes // // Use this function to continuously provide audio samples to a GGWave instance. // On each call, GGWave will analyze the provided data and if it detects a payload, // it will return a non-zero result. // // waveform -> payload // // If the return value is -1 then there was an error during the decoding process. // Usually can occur if there is a lot of background noise in the audio. // // If the return value is greater than 0, then there are that number of bytes decoded. // // IMPORTANT: // Notice that the decoded data written to the payloadBuffer is NOT null terminated. // // Example: // // char payload[256]; // // while (true) { // ... capture samplesPerFrame audio samples into waveform ... // // int ret = ggwave_decode(instance, waveform, samplesPerFrame*sizeOfSample_bytes, payload); // if (ret > 0) { // payload[ret] = 0; // null terminate the string // printf("Received payload: '%s'\n", payload); // } // } // GGWAVE_API int ggwave_decode( ggwave_Instance instance, const void * waveformBuffer, int waveformSize, void * payloadBuffer); // Memory-safe overload of ggwave_decode // // payloadSize - optionally specify the size of the output buffer // // If the return value is -2 then the provided payloadBuffer was not big enough to // store the decoded data. // // See ggwave_decode for more information // GGWAVE_API int ggwave_ndecode( ggwave_Instance instance, const void * waveformBuffer, int waveformSize, void * payloadBuffer, int payloadSize); // Toggle Rx protocols on and off // // protocolId - Id of the Rx protocol to modify // state - 0 - disable, 1 - enable // // If an Rx protocol is enabled, newly constructued GGWave instances will attempt to decode // received data using this protocol. By default, all protocols are enabled. // Use this function to restrict the number of Rx protocols used in the decoding // process. This helps to reduce the number of false positives and improves the transmission // accuracy, especially when the Tx/Rx protocol is known in advance. // // Note that this function does not affect the decoding process of instances that have // already been created. // GGWAVE_API void ggwave_rxToggleProtocol( ggwave_ProtocolId protocolId, int state); // Toggle Tx protocols on and off // // protocolId - Id of the Tx protocol to modify // state - 0 - disable, 1 - enable // // If an Tx protocol is enabled, newly constructued GGWave instances will be able to transmit // data using this protocol. By default, all protocols are enabled. // Use this function to restrict the number of Tx protocols used for transmission. // This can reduce the required memory by the GGWave instance. // // Note that this function does not affect instances that have already been created. // GGWAVE_API void ggwave_txToggleProtocol( ggwave_ProtocolId protocolId, int state); // Set freqStart for an Rx protocol GGWAVE_API void ggwave_rxProtocolSetFreqStart( ggwave_ProtocolId protocolId, int freqStart); // Set freqStart for a Tx protocol GGWAVE_API void ggwave_txProtocolSetFreqStart( ggwave_ProtocolId protocolId, int freqStart); // Return recvDuration_frames value for a rx protocol GGWAVE_API int ggwave_rxDurationFrames( ggwave_Instance instance); #ifdef __cplusplus } // // C++ interface // template struct ggvector { private: T * m_data; int m_size; public: using value_type = T; ggvector() : m_data(nullptr), m_size(0) {} ggvector(T * data, int size) : m_data(data), m_size(size) {} ggvector(const ggvector & other) = default; // delete operator= ggvector & operator=(const ggvector &) = delete; ggvector & operator=(ggvector &&) = delete; T & operator[](int i) { return m_data[i]; } const T & operator[](int i) const { return m_data[i]; } int size() const { return m_size; } T * data() const { return m_data; } T * begin() const { return m_data; } T * end() const { return m_data + m_size; } void assign(const ggvector & other); void copy(const ggvector & other); void zero(); void zero(int n); }; template struct ggmatrix { private: T * m_data; int m_size0; int m_size1; public: using value_type = T; ggmatrix() : m_data(nullptr), m_size0(0), m_size1(0) {} ggmatrix(T * data, int size0, int size1) : m_data(data), m_size0(size0), m_size1(size1) {} ggvector operator[](int i) { return ggvector(m_data + i*m_size1, m_size1); } int size() const { return m_size0; } void zero(); }; #include #include #ifdef ARDUINO #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040) #include #else #include #endif #endif class GGWave { public: static constexpr auto kSampleRateMin = 1000.0f; static constexpr auto kSampleRateMax = 96000.0f; static constexpr auto kDefaultSampleRate = 48000.0f; static constexpr auto kDefaultSamplesPerFrame = 1024; static constexpr auto kDefaultVolume = 10; static constexpr auto kDefaultSoundMarkerThreshold = 3.0f; static constexpr auto kDefaultMarkerFrames = 16; static constexpr auto kDefaultEncodedDataOffset = 3; static constexpr auto kMaxSamplesPerFrame = 1024; static constexpr auto kMaxDataSize = 256; static constexpr auto kMaxLengthVariable = 140; static constexpr auto kMaxLengthFixed = 64; static constexpr auto kMaxSpectrumHistory = 4; static constexpr auto kMaxRecordedFrames = 2048; using Parameters = ggwave_Parameters; using SampleFormat = ggwave_SampleFormat; using ProtocolId = ggwave_ProtocolId; using TxProtocolId = ggwave_ProtocolId; using RxProtocolId = ggwave_ProtocolId; using OperatingMode = int; // ggwave_OperatingMode; struct Protocol { const char * name; // string identifier of the protocol int16_t freqStart; // FFT bin index of the lowest frequency int8_t framesPerTx; // number of frames to transmit a single chunk of data int8_t bytesPerTx; // number of bytes in a chunk of data int8_t extra; // 2 if this is a mono-tone protocol, 1 otherwise bool enabled; int nTones() const { return (2*bytesPerTx)/extra; } int nDataBitsPerTx() const { return 8*bytesPerTx; } int txDuration_ms(int samplesPerFrame, float sampleRate) const { return framesPerTx*((1000.0f*samplesPerFrame)/sampleRate); } }; using TxProtocol = Protocol; using RxProtocol = Protocol; struct Protocols; using TxProtocols = Protocols; using RxProtocols = Protocols; struct Protocols { Protocol data[GGWAVE_PROTOCOL_COUNT]; int size() const { return GGWAVE_PROTOCOL_COUNT; } bool empty() const { return size() == 0; } Protocol & operator[](ProtocolId id) { return data[id]; } Protocol & operator[](int id) { return data[id]; } const Protocol & operator[](ProtocolId id) const { return data[id]; } const Protocol & operator[](int id) const { return data[id]; } void enableAll(); void disableAll(); void toggle(ProtocolId id, bool state); void only(ProtocolId id); static Protocols & kDefault() { static Protocols protocols; static bool initialized = false; if (initialized == false) { for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; ++i) { protocols.data[i].name = nullptr; protocols.data[i].enabled = false; } #if defined(ARDUINO_AVR_UNO) // For Arduino Uno, we put the strings in PROGMEM to save as much RAM as possible: #define GGWAVE_PSTR PSTR #else #define GGWAVE_PSTR(str) (str) #endif #ifndef GGWAVE_CONFIG_FEW_PROTOCOLS protocols.data[GGWAVE_PROTOCOL_AUDIBLE_NORMAL] = { GGWAVE_PSTR("Normal"), 40, 9, 3, 1, true, }; protocols.data[GGWAVE_PROTOCOL_AUDIBLE_FAST] = { GGWAVE_PSTR("Fast"), 40, 6, 3, 1, true, }; protocols.data[GGWAVE_PROTOCOL_AUDIBLE_FASTEST] = { GGWAVE_PSTR("Fastest"), 40, 3, 3, 1, true, }; protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_NORMAL] = { GGWAVE_PSTR("[U] Normal"), 320, 9, 3, 1, true, }; protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_FAST] = { GGWAVE_PSTR("[U] Fast"), 320, 6, 3, 1, true, }; protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_FASTEST] = { GGWAVE_PSTR("[U] Fastest"), 320, 3, 3, 1, true, }; #endif protocols.data[GGWAVE_PROTOCOL_DT_NORMAL] = { GGWAVE_PSTR("[DT] Normal"), 24, 9, 1, 1, true, }; protocols.data[GGWAVE_PROTOCOL_DT_FAST] = { GGWAVE_PSTR("[DT] Fast"), 24, 6, 1, 1, true, }; protocols.data[GGWAVE_PROTOCOL_DT_FASTEST] = { GGWAVE_PSTR("[DT] Fastest"), 24, 3, 1, 1, true, }; protocols.data[GGWAVE_PROTOCOL_MT_NORMAL] = { GGWAVE_PSTR("[MT] Normal"), 24, 9, 1, 2, true, }; protocols.data[GGWAVE_PROTOCOL_MT_FAST] = { GGWAVE_PSTR("[MT] Fast"), 24, 6, 1, 2, true, }; protocols.data[GGWAVE_PROTOCOL_MT_FASTEST] = { GGWAVE_PSTR("[MT] Fastest"), 24, 3, 1, 2, true, }; #undef GGWAVE_PSTR initialized = true; } return protocols; } static TxProtocols & tx(); static RxProtocols & rx(); }; using Tone = int8_t; // Tone data structure // // Each Tone element is the bin index of the tone frequency. // For protocol p: // - freq_hz = (p.freqStart + Tone) * hzPerSample // - duration_ms = p.txDuration_ms(samplesPerFrame, sampleRate) // // If the protocol is mono-tone, each element of the vector corresponds to a single tone. // Otherwise, the tones within a single Tx are separated by value of -1 // using Tones = ggvector; using Amplitude = ggvector; using AmplitudeArr = ggmatrix; using AmplitudeI16 = ggvector; using Spectrum = ggvector; using RecordedData = ggvector; using TxRxData = ggvector; // Default constructor // // The GGWave object is not ready to use until you call prepare() // No memory is allocated with this constructor. // GGWave() = default; // Constructor with parameters // // Construct and prepare the GGWave object using the given parameters. // This constructor calls prepare() for you. // GGWave(const Parameters & parameters); ~GGWave(); // Prepare the GGWave object // // All memory buffers used by the GGWave instance are allocated with this function. // No memory allocations occur after that. // // Call this method if you used the default constructor. // Do not call this method if you used the constructor with parameters. // // The encode() and decode() methods will not work until this method is called. // // The sizes of the buffers are determined by the parameters and the contents of: // // - GGWave::Protocols::rx() // - GGWave::Protocols::tx() // // For optimal performance and minimum memory usage, make sure to enable only the // Rx and Tx protocols that you need. // // For example, using a single protocol for Tx is achieved like this: // // Parameters parameters; // parameters.operatingMode = GGWAVE_OPERATING_MODE_TX; // GGWave::Protocols::tx().only(GGWave::ProtocolId::GGWAVE_PROTOCOL_AUDIBLE_NORMAL); // GGWave instance(parameters); // instance.init(...); // instance.encode(); // // The created instance will only be able to transmit data using the "Normal" // protocol. Rx will be disabled. // // To create a corresponding Rx-only instance, use the following: // // Parameters parameters; // parameters.operatingMode = GGWAVE_OPERATING_MODE_RX; // GGWave::Protocols::rx().only(GGWave::ProtocolId::GGWAVE_PROTOCOL_AUDIBLE_NORMAL); // GGWave instance(parameters); // instance.decode(...); // // If "allocate" is false, the memory buffers are not allocated and only the required size // is computed. This is useful if you want to just see how much memory is needed for the // specific set of parameters and protocols. Do not use this function after you have already // prepared the instance. Instead, use the heapSize() method to see how much memory is used. // bool prepare(const Parameters & parameters, bool allocate = true); // Set file stream for the internal ggwave logging // // By default, ggwave prints internal log messages to stderr. // To disable logging all together, call this method with nullptr. // // Note: not thread-safe. Do not call while any GGWave instances are running // static void setLogFile(FILE * fptr); static const Parameters & getDefaultParameters(); // Set Tx data to encode into sound // // This prepares the GGWave instance for transmission. // To perform the actual encoding, call the encode() method. // // Returns false upon invalid parameters or failure to initialize the transmission // bool init(const char * text, TxProtocolId protocolId, const int volume = kDefaultVolume); bool init(int dataSize, const char * dataBuffer, TxProtocolId protocolId, const int volume = kDefaultVolume); // Expected waveform size of the encoded Tx data in bytes // // When the output sampling rate is not equal to operating sample rate the result of this method is overestimation // of the actual number of bytes that would be produced // uint32_t encodeSize_bytes() const; // Expected waveform size of the encoded Tx data in samples // // When the output sampling rate is not equal to operating sample rate the result of this method is overestimation // of the actual number of samples that would be produced // uint32_t encodeSize_samples() const; // Encode Tx data into an audio waveform // // After calling this method, use the Tx methods to get the encoded audio data. // // The generated waveform is available through the txWaveform() method // The tone frequencies are available through the txTones() method // // Returns the number of bytes in the generated waveform // uint32_t encode(); // Decode an audio waveform // // data - pointer to the waveform data // nBytes - number of bytes in the waveform // // The samples pointed to by "data" should be in the format given by sampleFormatInp(). // After calling this method, use the Rx methods to check if any data was decoded successfully. // // Returns false if the provided waveform is somehow invalid // bool decode(const void * data, uint32_t nBytes); // // Instance state // bool isDSSEnabled() const; int samplesPerFrame() const; int sampleSizeInp() const; int sampleSizeOut() const; float hzPerSample() const; float sampleRateInp() const; float sampleRateOut() const; SampleFormat sampleFormatInp() const; SampleFormat sampleFormatOut() const; int heapSize() const; // // Tx // // Get the generated Wavform samples for the last encode() call // // Call this method after calling encode() to get the generated waveform. The format of the samples pointed to by // the returned pointer is determined by the sampleFormatOut() method. // const void * txWaveform() const; // Get a list of the tones generated for the last encode() call // // Call this method after calling encode() to get a list of the tones participating in the generated waveform // const Tones txTones() const; // true if there is data pending to be transmitted bool txHasData() const; // Consume the amplitude data from the last generated waveform bool txTakeAmplitudeI16(AmplitudeI16 & dst); // The instance will allow Tx only with these protocols. They are determined upon construction or when calling the // prepare() method, base on the contents of the global GGWave::Protocols::tx() const TxProtocols & txProtocols() const; // // Rx // bool rxReceiving() const; bool rxAnalyzing() const; int rxSamplesNeeded() const; int rxFramesToRecord() const; int rxFramesLeftToRecord() const; int rxFramesToAnalyze() const; int rxFramesLeftToAnalyze() const; int rxDurationFrames() const; bool rxStopReceiving(); // The instance will attempt to decode only these protocols. // They are determined upon construction or when calling the prepare() method, base on the contents of the global // GGWave::Protocols::rx() // // Note: do not enable protocols that were not enabled upon preparation of the GGWave instance, or the decoding // will likely crash // RxProtocols & rxProtocols(); // Information about last received data int rxDataLength() const; const TxRxData & rxData() const; const RxProtocol & rxProtocol() const; const RxProtocolId & rxProtocolId() const; const Spectrum & rxSpectrum() const; const Amplitude & rxAmplitude() const; // Consume the received data // // Returns the data length in bytes // int rxTakeData(TxRxData & dst); // Consume the received spectrum / amplitude data // // Returns true if there was new data available // bool rxTakeSpectrum(Spectrum & dst); bool rxTakeAmplitude(Amplitude & dst); // // Utils // // Compute FFT of real values // // src - input real-valued data, size is N // dst - output complex-valued data, size is 2*N // // N must be == samplesPerFrame() // bool computeFFTR(const float * src, float * dst, int N); // Compute FFT of real values (static) // // src - input real-valued data, size is N // dst - output complex-valued data, size is 2*N // wi - work buffer, with size 2*N // wf - work buffer, with size 3 + sqrt(N/2) // // First time calling this function, make sure that wi[0] == 0 // This will initialize some internal coefficients and store them in wi and wf for // future usage. // // If wi == nullptr - returns the needed size for wi // If wi != nullptr and wf == nullptr - returns the needed size for wf // If wi != nullptr and wf != nullptr - returns 1 on success, 0 on failure // static int computeFFTR(const float * src, float * dst, int N, int * wi, float * wf); // Filter the waveform // // filter - filter to use // waveform - input waveform, size is N // N - number of samples in the waveform // p0 - parameter // p1 - parameter // w - work buffer // // Filter is applied in-place. // First time calling this function, make sure that w[0] == 0 and w[1] == 0 // This will initialize some internal coefficients and store them in w for // future usage. // // For GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS: // - p0 = cutoff frequency in Hz // - p1 = sample rate in Hz // // If w == nullptr - returns the needed size for w for the specified filter // If w != nullptr - returns 1 on success, 0 on failure // static int filter(ggwave_Filter filter, float * waveform, int N, float p0, float p1, float * w); // Resample audio waveforms from one sample rate to another using sinc interpolation class Resampler { public: // this controls the number of neighboring samples // which are used to interpolate the new samples. The // processing time is linearly related to this width static const int kWidth = 64; Resampler(); bool alloc(void * p, int & n); void reset(); int nSamplesTotal() const { return m_state.nSamplesTotal; } int resample( float factor, int nSamples, const float * samplesInp, float * samplesOut); private: float getData(int j) const; void newData(float data); void makeSinc(); double sinc(double x) const; static const int kDelaySize = 140; // this defines how finely the sinc function is sampled for storage in the table static const int kSamplesPerZeroCrossing = 32; ggvector m_sincTable; ggvector m_delayBuffer; ggvector m_edgeSamples; ggvector m_samplesInp; struct State { int nSamplesTotal = 0; int timeInt = 0; int timeLast = 0; double timeNow = 0.0; }; State m_state; }; private: bool alloc(void * p, int & n); void decode_fixed(); void decode_variable(); int maxFramesPerTx(const Protocols & protocols, bool excludeMT) const; int minBytesPerTx(const Protocols & protocols) const; int maxBytesPerTx(const Protocols & protocols) const; int maxTonesPerTx(const Protocols & protocols) const; int minFreqStart(const Protocols & protocols) const; double bitFreq(const Protocol & p, int bit) const; // Initialized via prepare() float m_sampleRateInp = -1.0f; float m_sampleRateOut = -1.0f; float m_sampleRate = -1.0f; int m_samplesPerFrame = -1; float m_isamplesPerFrame = -1.0f; int m_sampleSizeInp = -1; int m_sampleSizeOut = -1; SampleFormat m_sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED; SampleFormat m_sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED; float m_hzPerSample = -1.0f; float m_ihzPerSample = -1.0f; int m_freqDelta_bin = -1; float m_freqDelta_hz = -1.0f; int m_nBitsInMarker = -1; int m_nMarkerFrames = -1; int m_encodedDataOffset = -1; float m_soundMarkerThreshold = -1.0f; bool m_isFixedPayloadLength = false; int m_payloadLength = -1; bool m_isRxEnabled = false; bool m_isTxEnabled = false; bool m_needResampling = false; bool m_txOnlyTones = false; bool m_isDSSEnabled = false; // Common TxRxData m_dataEncoded; TxRxData m_workRSLength; // Reed-Solomon work buffers TxRxData m_workRSData; // Impl struct Rx { bool receiving = false; bool analyzing = false; int nMarkersSuccess = 0; int markerFreqStart = 0; int recvDuration_frames = 0; int minFreqStart = 0; int framesLeftToAnalyze = 0; int framesLeftToRecord = 0; int framesToAnalyze = 0; int framesToRecord = 0; int samplesNeeded = 0; ggvector fftOut; // complex ggvector fftWorkI; ggvector fftWorkF; bool hasNewRxData = false; bool hasNewSpectrum = false; bool hasNewAmplitude = false; Spectrum spectrum; Amplitude amplitude; Amplitude amplitudeResampled; TxRxData amplitudeTmp; int dataLength = 0; TxRxData data; RxProtocol protocol; RxProtocolId protocolId; RxProtocols protocols; // variable-length decoding int historyId = 0; Amplitude amplitudeAverage; AmplitudeArr amplitudeHistory; RecordedData amplitudeRecorded; // fixed-length decoding int historyIdFixed = 0; ggmatrix spectrumHistoryFixed; ggvector detectedBins; ggvector detectedTones; } m_rx; struct Tx { bool hasData = false; float sendVolume = 0.1f; int dataLength = 0; int lastAmplitudeSize = 0; ggvector dataBits; ggvector phaseOffsets; AmplitudeArr bit1Amplitude; AmplitudeArr bit0Amplitude; TxRxData data; TxProtocol protocol; TxProtocols protocols; Amplitude output; Amplitude outputResampled; TxRxData outputTmp; AmplitudeI16 outputI16; int nTones = 0; Tones tones; } m_tx; mutable Resampler m_resampler; void * m_heap = nullptr; int m_heapSize = 0; }; #endif #endif ================================================ FILE: media/favicons-waver/browserconfig.xml ================================================ #ffffff ================================================ FILE: media/favicons-waver/manifest.json ================================================ { "name": "App", "icons": [ { "src": "\/android-icon-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { "src": "\/android-icon-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { "src": "\/android-icon-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { "src": "\/android-icon-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { "src": "\/android-icon-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { "src": "\/android-icon-192x192.png", "sizes": "192x192", "type": "image\/png", "density": "4.0" } ] } ================================================ FILE: snap/snapcraft.yaml ================================================ name: waver version: '1.5.2' summary: Data over sound description: | Waver allows you to send and receive text messages from nearby devices through sound waves. This application can be useful for communicating with multiple nearby devices at once. Both audible and ultrasound communication protocols are available. The app does not connect to the internet and all information is transmitted only through sound. In order to receive incoming messages you only need to allow access to your device's microphone so that it can record nearby sounds. How to use: - Before starting - make sure the speaker of your device is enabled and disconnect/unplug any headphones. The app uses your device's speaker to emit sounds when sending a text message - To send a message - tap on "Messages", enter some text at the bottom of the screen and click "Send" - Any nearby device that is also running this application can capture the emitted sound and display the received message - In the settings menu, you can adjust the volume and the transmission protocol that will be used when sending messages. Make sure to adjust the volume level high enough, so the sounds can be picked up by other devices - Tap on "Spectrum" to see a real-time frequency spectrum of the currently captured audio by your device's microphone File sharing in a local network: As of v1.3.0 Waver supports file sharing. It works like this: - Add files that you would like to transmit by drag and dropping them in Waver - In the "Files" menu, click on "Broadcast". This plays an audio message that contains a file broadcast offer - Nearby devices in the same local network can receive this offer and initiate a TCP/IP connection to your device - The files are transmitted over TCP/IP. The sound message is used only to initiate the network connections between the devices - Waver allows sharing multiple files to multiple devices at once base: core18 grade: stable confinement: strict parts: alsa-mixin: plugin: dump source: https://github.com/diddlesnaps/snapcraft-alsa.git source-subdir: snapcraft-assets build-packages: - libasound2-dev stage-packages: - libasound2 - libasound2-plugins - yad waver: source: https://github.com/ggerganov/ggwave source-type: git plugin: cmake #configflags: [-DBUILD_SHARED_LIBS=OFF] build-packages: - g++ - make - libsdl2-dev stage-packages: - libopengl0 - libsdl2-2.0-0 - libgl1 - libglx0 - libglu1-mesa - libgl1-mesa-dri after: [alsa-mixin] waver-fonts: plugin: dump source: ./examples/assets/fonts/ organize: '*.ttf' : examples/assets/fonts/ stage: - examples/assets/fonts/ apps: waver: command-chain: ["snap/command-chain/alsa-launch"] command: bin/waver plugs: [unity7, opengl, alsa, audio-playback, audio-record, network, network-bind, home] environment: ALWAYS_USE_PULSEAUDIO: '1' ================================================ FILE: src/CMakeLists.txt ================================================ # core set(TARGET ggwave) add_library(${TARGET} ggwave.cpp ) target_include_directories(${TARGET} PUBLIC . ../include ) if (BUILD_SHARED_LIBS) target_link_libraries(${TARGET} PUBLIC ${CMAKE_DL_LIBS} ) target_compile_definitions(${TARGET} PUBLIC GGWAVE_SHARED ) endif() if (MINGW) target_link_libraries(${TARGET} PUBLIC stdc++ ) endif() install(DIRECTORY ../include/ggwave DESTINATION include ) install(TARGETS ${TARGET} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static ) ================================================ FILE: src/fft.h ================================================ #pragma once /* The FFT routines below are taken from: https://www.kurims.kyoto-u.ac.jp/~ooura/fft.html License Copyright Takuya OOURA, 1996-2001 */ /* Fast Fourier/Cosine/Sine Transform dimension :one data length :power of 2 decimation :frequency radix :4, 2 data :inplace table :use functions rdft: Real Discrete Fourier Transform function prototypes void rdft(int, int, float *, int *, float *); -------- Real DFT / Inverse of Real DFT -------- [definition] RDFT R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2 I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0 IRDFT (excluding scale) a[k] = (R[0] + R[n/2]*cos(pi*k))/2 + sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) + sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k ip[0] = 0; // first time only rdft(n, 1, a, ip, w); ip[0] = 0; // first time only rdft(n, -1, a, ip, w); [parameters] n :data length (int) n >= 2, n = power of 2 a[0...n-1] :input/output data (float *) output data a[2*k] = R[k], 0<=k input data a[2*j] = R[j], 0<=j= 2+sqrt(n/2) strictly, length of ip >= 2+(1<<(int)(log(n/2+0.5)/log(2))/2). ip[0],ip[1] are pointers of the cos/sin table. w[0...n/2-1] :cos/sin table (float *) w[],ip[] are initialized if ip[0] == 0. [remark] Inverse of rdft(n, 1, a, ip, w); is rdft(n, -1, a, ip, w); for (j = 0; j <= n - 1; j++) { a[j] *= 2.0 / n; } . Appendix : The cos/sin table is recalculated when the larger table required. w[] and ip[] are compatible with all routines. */ void rdft(int n, int isgn, float *a, int *ip, float *w) { void makewt(int nw, int *ip, float *w); void makect(int nc, int *ip, float *c); void bitrv2(int n, int *ip, float *a); void cftfsub(int n, float *a, float *w); void cftbsub(int n, float *a, float *w); void rftfsub(int n, float *a, int nc, float *c); void rftbsub(int n, float *a, int nc, float *c); int nw, nc; float xi; nw = ip[0]; if (n > (nw << 2)) { nw = n >> 2; makewt(nw, ip, w); } nc = ip[1]; if (n > (nc << 2)) { nc = n >> 2; makect(nc, ip, w + nw); } if (isgn >= 0) { if (n > 4) { bitrv2(n, ip + 2, a); cftfsub(n, a, w); rftfsub(n, a, nc, w + nw); } else if (n == 4) { cftfsub(n, a, w); } xi = a[0] - a[1]; a[0] += a[1]; a[1] = xi; } else { a[1] = 0.5 * (a[0] - a[1]); a[0] -= a[1]; if (n > 4) { rftbsub(n, a, nc, w + nw); bitrv2(n, ip + 2, a); cftbsub(n, a, w); } else if (n == 4) { cftfsub(n, a, w); } } } /* -------- initializing routines -------- */ #include void makewt(int nw, int *ip, float *w) { void bitrv2(int n, int *ip, float *a); int j, nwh; float delta, x, y; ip[0] = nw; ip[1] = 1; if (nw > 2) { nwh = nw >> 1; delta = atan(1.0) / nwh; w[0] = 1; w[1] = 0; w[nwh] = cos(delta * nwh); w[nwh + 1] = w[nwh]; if (nwh > 2) { for (j = 2; j < nwh; j += 2) { x = cos(delta * j); y = sin(delta * j); w[j] = x; w[j + 1] = y; w[nw - j] = y; w[nw - j + 1] = x; } bitrv2(nw, ip + 2, w); } } } void makect(int nc, int *ip, float *c) { int j, nch; float delta; ip[1] = nc; if (nc > 1) { nch = nc >> 1; delta = atan(1.0) / nch; c[0] = cos(delta * nch); c[nch] = 0.5 * c[0]; for (j = 1; j < nch; j++) { c[j] = 0.5 * cos(delta * j); c[nc - j] = 0.5 * sin(delta * j); } } } /* -------- child routines -------- */ void bitrv2(int n, int *ip, float *a) { int j, j1, k, k1, l, m, m2; float xr, xi, yr, yi; ip[0] = 0; l = n; m = 1; while ((m << 3) < l) { l >>= 1; for (j = 0; j < m; j++) { ip[m + j] = ip[j] + l; } m <<= 1; } m2 = 2 * m; if ((m << 3) == l) { for (k = 0; k < m; k++) { for (j = 0; j < k; j++) { j1 = 2 * j + ip[k]; k1 = 2 * k + ip[j]; xr = a[j1]; xi = a[j1 + 1]; yr = a[k1]; yi = a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 += 2 * m2; xr = a[j1]; xi = a[j1 + 1]; yr = a[k1]; yi = a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 -= m2; xr = a[j1]; xi = a[j1 + 1]; yr = a[k1]; yi = a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 += 2 * m2; xr = a[j1]; xi = a[j1 + 1]; yr = a[k1]; yi = a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; } j1 = 2 * k + m2 + ip[k]; k1 = j1 + m2; xr = a[j1]; xi = a[j1 + 1]; yr = a[k1]; yi = a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; } } else { for (k = 1; k < m; k++) { for (j = 0; j < k; j++) { j1 = 2 * j + ip[k]; k1 = 2 * k + ip[j]; xr = a[j1]; xi = a[j1 + 1]; yr = a[k1]; yi = a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 += m2; xr = a[j1]; xi = a[j1 + 1]; yr = a[k1]; yi = a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; } } } } void bitrv2conj(int n, int *ip, float *a) { int j, j1, k, k1, l, m, m2; float xr, xi, yr, yi; ip[0] = 0; l = n; m = 1; while ((m << 3) < l) { l >>= 1; for (j = 0; j < m; j++) { ip[m + j] = ip[j] + l; } m <<= 1; } m2 = 2 * m; if ((m << 3) == l) { for (k = 0; k < m; k++) { for (j = 0; j < k; j++) { j1 = 2 * j + ip[k]; k1 = 2 * k + ip[j]; xr = a[j1]; xi = -a[j1 + 1]; yr = a[k1]; yi = -a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 += 2 * m2; xr = a[j1]; xi = -a[j1 + 1]; yr = a[k1]; yi = -a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 -= m2; xr = a[j1]; xi = -a[j1 + 1]; yr = a[k1]; yi = -a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 += 2 * m2; xr = a[j1]; xi = -a[j1 + 1]; yr = a[k1]; yi = -a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; } k1 = 2 * k + ip[k]; a[k1 + 1] = -a[k1 + 1]; j1 = k1 + m2; k1 = j1 + m2; xr = a[j1]; xi = -a[j1 + 1]; yr = a[k1]; yi = -a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; k1 += m2; a[k1 + 1] = -a[k1 + 1]; } } else { a[1] = -a[1]; a[m2 + 1] = -a[m2 + 1]; for (k = 1; k < m; k++) { for (j = 0; j < k; j++) { j1 = 2 * j + ip[k]; k1 = 2 * k + ip[j]; xr = a[j1]; xi = -a[j1 + 1]; yr = a[k1]; yi = -a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; j1 += m2; k1 += m2; xr = a[j1]; xi = -a[j1 + 1]; yr = a[k1]; yi = -a[k1 + 1]; a[j1] = yr; a[j1 + 1] = yi; a[k1] = xr; a[k1 + 1] = xi; } k1 = 2 * k + ip[k]; a[k1 + 1] = -a[k1 + 1]; a[k1 + m2 + 1] = -a[k1 + m2 + 1]; } } } void cftfsub(int n, float *a, float *w) { void cft1st(int n, float *a, float *w); void cftmdl(int n, int l, float *a, float *w); int j, j1, j2, j3, l; float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; l = 2; if (n > 8) { cft1st(n, a, w); l = 8; while ((l << 2) < n) { cftmdl(n, l, a, w); l <<= 2; } } if ((l << 2) == n) { for (j = 0; j < l; j += 2) { j1 = j + l; j2 = j1 + l; j3 = j2 + l; x0r = a[j] + a[j1]; x0i = a[j + 1] + a[j1 + 1]; x1r = a[j] - a[j1]; x1i = a[j + 1] - a[j1 + 1]; x2r = a[j2] + a[j3]; x2i = a[j2 + 1] + a[j3 + 1]; x3r = a[j2] - a[j3]; x3i = a[j2 + 1] - a[j3 + 1]; a[j] = x0r + x2r; a[j + 1] = x0i + x2i; a[j2] = x0r - x2r; a[j2 + 1] = x0i - x2i; a[j1] = x1r - x3i; a[j1 + 1] = x1i + x3r; a[j3] = x1r + x3i; a[j3 + 1] = x1i - x3r; } } else { for (j = 0; j < l; j += 2) { j1 = j + l; x0r = a[j] - a[j1]; x0i = a[j + 1] - a[j1 + 1]; a[j] += a[j1]; a[j + 1] += a[j1 + 1]; a[j1] = x0r; a[j1 + 1] = x0i; } } } void cftbsub(int n, float *a, float *w) { void cft1st(int n, float *a, float *w); void cftmdl(int n, int l, float *a, float *w); int j, j1, j2, j3, l; float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; l = 2; if (n > 8) { cft1st(n, a, w); l = 8; while ((l << 2) < n) { cftmdl(n, l, a, w); l <<= 2; } } if ((l << 2) == n) { for (j = 0; j < l; j += 2) { j1 = j + l; j2 = j1 + l; j3 = j2 + l; x0r = a[j] + a[j1]; x0i = -a[j + 1] - a[j1 + 1]; x1r = a[j] - a[j1]; x1i = -a[j + 1] + a[j1 + 1]; x2r = a[j2] + a[j3]; x2i = a[j2 + 1] + a[j3 + 1]; x3r = a[j2] - a[j3]; x3i = a[j2 + 1] - a[j3 + 1]; a[j] = x0r + x2r; a[j + 1] = x0i - x2i; a[j2] = x0r - x2r; a[j2 + 1] = x0i + x2i; a[j1] = x1r - x3i; a[j1 + 1] = x1i - x3r; a[j3] = x1r + x3i; a[j3 + 1] = x1i + x3r; } } else { for (j = 0; j < l; j += 2) { j1 = j + l; x0r = a[j] - a[j1]; x0i = -a[j + 1] + a[j1 + 1]; a[j] += a[j1]; a[j + 1] = -a[j + 1] - a[j1 + 1]; a[j1] = x0r; a[j1 + 1] = x0i; } } } void cft1st(int n, float *a, float *w) { int j, k1, k2; float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; x0r = a[0] + a[2]; x0i = a[1] + a[3]; x1r = a[0] - a[2]; x1i = a[1] - a[3]; x2r = a[4] + a[6]; x2i = a[5] + a[7]; x3r = a[4] - a[6]; x3i = a[5] - a[7]; a[0] = x0r + x2r; a[1] = x0i + x2i; a[4] = x0r - x2r; a[5] = x0i - x2i; a[2] = x1r - x3i; a[3] = x1i + x3r; a[6] = x1r + x3i; a[7] = x1i - x3r; wk1r = w[2]; x0r = a[8] + a[10]; x0i = a[9] + a[11]; x1r = a[8] - a[10]; x1i = a[9] - a[11]; x2r = a[12] + a[14]; x2i = a[13] + a[15]; x3r = a[12] - a[14]; x3i = a[13] - a[15]; a[8] = x0r + x2r; a[9] = x0i + x2i; a[12] = x2i - x0i; a[13] = x0r - x2r; x0r = x1r - x3i; x0i = x1i + x3r; a[10] = wk1r * (x0r - x0i); a[11] = wk1r * (x0r + x0i); x0r = x3i + x1r; x0i = x3r - x1i; a[14] = wk1r * (x0i - x0r); a[15] = wk1r * (x0i + x0r); k1 = 0; for (j = 16; j < n; j += 16) { k1 += 2; k2 = 2 * k1; wk2r = w[k1]; wk2i = w[k1 + 1]; wk1r = w[k2]; wk1i = w[k2 + 1]; wk3r = wk1r - 2 * wk2i * wk1i; wk3i = 2 * wk2i * wk1r - wk1i; x0r = a[j] + a[j + 2]; x0i = a[j + 1] + a[j + 3]; x1r = a[j] - a[j + 2]; x1i = a[j + 1] - a[j + 3]; x2r = a[j + 4] + a[j + 6]; x2i = a[j + 5] + a[j + 7]; x3r = a[j + 4] - a[j + 6]; x3i = a[j + 5] - a[j + 7]; a[j] = x0r + x2r; a[j + 1] = x0i + x2i; x0r -= x2r; x0i -= x2i; a[j + 4] = wk2r * x0r - wk2i * x0i; a[j + 5] = wk2r * x0i + wk2i * x0r; x0r = x1r - x3i; x0i = x1i + x3r; a[j + 2] = wk1r * x0r - wk1i * x0i; a[j + 3] = wk1r * x0i + wk1i * x0r; x0r = x1r + x3i; x0i = x1i - x3r; a[j + 6] = wk3r * x0r - wk3i * x0i; a[j + 7] = wk3r * x0i + wk3i * x0r; wk1r = w[k2 + 2]; wk1i = w[k2 + 3]; wk3r = wk1r - 2 * wk2r * wk1i; wk3i = 2 * wk2r * wk1r - wk1i; x0r = a[j + 8] + a[j + 10]; x0i = a[j + 9] + a[j + 11]; x1r = a[j + 8] - a[j + 10]; x1i = a[j + 9] - a[j + 11]; x2r = a[j + 12] + a[j + 14]; x2i = a[j + 13] + a[j + 15]; x3r = a[j + 12] - a[j + 14]; x3i = a[j + 13] - a[j + 15]; a[j + 8] = x0r + x2r; a[j + 9] = x0i + x2i; x0r -= x2r; x0i -= x2i; a[j + 12] = -wk2i * x0r - wk2r * x0i; a[j + 13] = -wk2i * x0i + wk2r * x0r; x0r = x1r - x3i; x0i = x1i + x3r; a[j + 10] = wk1r * x0r - wk1i * x0i; a[j + 11] = wk1r * x0i + wk1i * x0r; x0r = x1r + x3i; x0i = x1i - x3r; a[j + 14] = wk3r * x0r - wk3i * x0i; a[j + 15] = wk3r * x0i + wk3i * x0r; } } void cftmdl(int n, int l, float *a, float *w) { int j, j1, j2, j3, k, k1, k2, m, m2; float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; m = l << 2; for (j = 0; j < l; j += 2) { j1 = j + l; j2 = j1 + l; j3 = j2 + l; x0r = a[j] + a[j1]; x0i = a[j + 1] + a[j1 + 1]; x1r = a[j] - a[j1]; x1i = a[j + 1] - a[j1 + 1]; x2r = a[j2] + a[j3]; x2i = a[j2 + 1] + a[j3 + 1]; x3r = a[j2] - a[j3]; x3i = a[j2 + 1] - a[j3 + 1]; a[j] = x0r + x2r; a[j + 1] = x0i + x2i; a[j2] = x0r - x2r; a[j2 + 1] = x0i - x2i; a[j1] = x1r - x3i; a[j1 + 1] = x1i + x3r; a[j3] = x1r + x3i; a[j3 + 1] = x1i - x3r; } wk1r = w[2]; for (j = m; j < l + m; j += 2) { j1 = j + l; j2 = j1 + l; j3 = j2 + l; x0r = a[j] + a[j1]; x0i = a[j + 1] + a[j1 + 1]; x1r = a[j] - a[j1]; x1i = a[j + 1] - a[j1 + 1]; x2r = a[j2] + a[j3]; x2i = a[j2 + 1] + a[j3 + 1]; x3r = a[j2] - a[j3]; x3i = a[j2 + 1] - a[j3 + 1]; a[j] = x0r + x2r; a[j + 1] = x0i + x2i; a[j2] = x2i - x0i; a[j2 + 1] = x0r - x2r; x0r = x1r - x3i; x0i = x1i + x3r; a[j1] = wk1r * (x0r - x0i); a[j1 + 1] = wk1r * (x0r + x0i); x0r = x3i + x1r; x0i = x3r - x1i; a[j3] = wk1r * (x0i - x0r); a[j3 + 1] = wk1r * (x0i + x0r); } k1 = 0; m2 = 2 * m; for (k = m2; k < n; k += m2) { k1 += 2; k2 = 2 * k1; wk2r = w[k1]; wk2i = w[k1 + 1]; wk1r = w[k2]; wk1i = w[k2 + 1]; wk3r = wk1r - 2 * wk2i * wk1i; wk3i = 2 * wk2i * wk1r - wk1i; for (j = k; j < l + k; j += 2) { j1 = j + l; j2 = j1 + l; j3 = j2 + l; x0r = a[j] + a[j1]; x0i = a[j + 1] + a[j1 + 1]; x1r = a[j] - a[j1]; x1i = a[j + 1] - a[j1 + 1]; x2r = a[j2] + a[j3]; x2i = a[j2 + 1] + a[j3 + 1]; x3r = a[j2] - a[j3]; x3i = a[j2 + 1] - a[j3 + 1]; a[j] = x0r + x2r; a[j + 1] = x0i + x2i; x0r -= x2r; x0i -= x2i; a[j2] = wk2r * x0r - wk2i * x0i; a[j2 + 1] = wk2r * x0i + wk2i * x0r; x0r = x1r - x3i; x0i = x1i + x3r; a[j1] = wk1r * x0r - wk1i * x0i; a[j1 + 1] = wk1r * x0i + wk1i * x0r; x0r = x1r + x3i; x0i = x1i - x3r; a[j3] = wk3r * x0r - wk3i * x0i; a[j3 + 1] = wk3r * x0i + wk3i * x0r; } wk1r = w[k2 + 2]; wk1i = w[k2 + 3]; wk3r = wk1r - 2 * wk2r * wk1i; wk3i = 2 * wk2r * wk1r - wk1i; for (j = k + m; j < l + (k + m); j += 2) { j1 = j + l; j2 = j1 + l; j3 = j2 + l; x0r = a[j] + a[j1]; x0i = a[j + 1] + a[j1 + 1]; x1r = a[j] - a[j1]; x1i = a[j + 1] - a[j1 + 1]; x2r = a[j2] + a[j3]; x2i = a[j2 + 1] + a[j3 + 1]; x3r = a[j2] - a[j3]; x3i = a[j2 + 1] - a[j3 + 1]; a[j] = x0r + x2r; a[j + 1] = x0i + x2i; x0r -= x2r; x0i -= x2i; a[j2] = -wk2i * x0r - wk2r * x0i; a[j2 + 1] = -wk2i * x0i + wk2r * x0r; x0r = x1r - x3i; x0i = x1i + x3r; a[j1] = wk1r * x0r - wk1i * x0i; a[j1 + 1] = wk1r * x0i + wk1i * x0r; x0r = x1r + x3i; x0i = x1i - x3r; a[j3] = wk3r * x0r - wk3i * x0i; a[j3 + 1] = wk3r * x0i + wk3i * x0r; } } } void rftfsub(int n, float *a, int nc, float *c) { int j, k, kk, ks, m; float wkr, wki, xr, xi, yr, yi; m = n >> 1; ks = 2 * nc / m; kk = 0; for (j = 2; j < m; j += 2) { k = n - j; kk += ks; wkr = 0.5 - c[nc - kk]; wki = c[kk]; xr = a[j] - a[k]; xi = a[j + 1] + a[k + 1]; yr = wkr * xr - wki * xi; yi = wkr * xi + wki * xr; a[j] -= yr; a[j + 1] -= yi; a[k] += yr; a[k + 1] -= yi; } } void rftbsub(int n, float *a, int nc, float *c) { int j, k, kk, ks, m; float wkr, wki, xr, xi, yr, yi; a[1] = -a[1]; m = n >> 1; ks = 2 * nc / m; kk = 0; for (j = 2; j < m; j += 2) { k = n - j; kk += ks; wkr = 0.5 - c[nc - kk]; wki = c[kk]; xr = a[j] - a[k]; xi = a[j + 1] + a[k + 1]; yr = wkr * xr + wki * xi; yi = wkr * xi - wki * xr; a[j] -= yr; a[j + 1] = yi - a[j + 1]; a[k] += yr; a[k + 1] = yi - a[k + 1]; } a[m + 1] = -a[m + 1]; } ================================================ FILE: src/ggwave.cpp ================================================ #include "ggwave/ggwave.h" #if !defined(ARDUINO) && !defined(PROGMEM) #define PROGMEM #endif #include "fft.h" #include "reed-solomon/rs.hpp" #include #include //#include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifdef GGWAVE_DISABLE_LOG #define ggprintf(...) #else #ifdef ARDUINO #define ggprintf(...) #else #define ggprintf(...) \ g_fptr && fprintf(g_fptr, __VA_ARGS__) #endif #endif #define GG_MIN(A, B) (((A) < (B)) ? (A) : (B)) #define GG_MAX(A, B) (((A) >= (B)) ? (A) : (B)) // // C interface // namespace { FILE * g_fptr = stderr; GGWave * g_instances[GGWAVE_MAX_INSTANCES]; double linear_interp(double first_number, double second_number, double fraction) { return (first_number + ((second_number - first_number)*fraction)); } } extern "C" void ggwave_setLogFile(void * fptr) { GGWave::setLogFile((FILE *) fptr); } extern "C" ggwave_Parameters ggwave_getDefaultParameters(void) { return GGWave::getDefaultParameters(); } extern "C" ggwave_Instance ggwave_init(ggwave_Parameters parameters) { for (ggwave_Instance id = 0; id < GGWAVE_MAX_INSTANCES; ++id) { if (g_instances[id] == nullptr) { g_instances[id] = new GGWave({ parameters.payloadLength, parameters.sampleRateInp, parameters.sampleRateOut, parameters.sampleRate, parameters.samplesPerFrame, parameters.soundMarkerThreshold, parameters.sampleFormatInp, parameters.sampleFormatOut, parameters.operatingMode}); return id; } } ggprintf("Failed to create GGWave instance - reached maximum number of instances (%d)\n", GGWAVE_MAX_INSTANCES); return -1; } extern "C" void ggwave_free(ggwave_Instance id) { if (id >= 0 && id < GGWAVE_MAX_INSTANCES && g_instances[id]) { delete (GGWave *) g_instances[id]; g_instances[id] = nullptr; return; } ggprintf("Failed to free GGWave instance - invalid GGWave instance id %d\n", id); } extern "C" int ggwave_encode( ggwave_Instance id, const void * payloadBuffer, int payloadSize, ggwave_ProtocolId protocolId, int volume, void * waveformBuffer, int query) { GGWave * ggWave = (GGWave *) g_instances[id]; if (ggWave == nullptr) { ggprintf("Invalid GGWave instance %d\n", id); return -1; } if (ggWave->init(payloadSize, (const char *) payloadBuffer, protocolId, volume) == false) { ggprintf("Failed to initialize Tx transmission for GGWave instance %d\n", id); return -1; } if (query != 0) { if (query == 1) { return ggWave->encodeSize_bytes(); } return ggWave->encodeSize_samples(); } const int nBytes = ggWave->encode(); if (nBytes == 0) { ggprintf("Failed to encode data - GGWave instance %d\n", id); return -1; } { auto pSrc = (const char *) ggWave->txWaveform(); auto pDst = ( char *) waveformBuffer; memcpy(pDst, pSrc, nBytes); } return nBytes; } extern "C" int ggwave_decode( ggwave_Instance id, const void * waveformBuffer, int waveformSize, void * payloadBuffer) { GGWave * ggWave = (GGWave *) g_instances[id]; if (ggWave->decode(waveformBuffer, waveformSize) == false) { ggprintf("Failed to decode data - GGWave instance %d\n", id); return -1; } static thread_local GGWave::TxRxData data; const auto dataLength = ggWave->rxTakeData(data); if (dataLength == -1) { // failed to decode message return -1; } else if (dataLength > 0) { memcpy(payloadBuffer, data.data(), dataLength); } return dataLength; } extern "C" int ggwave_ndecode( ggwave_Instance id, const void * waveformBuffer, int waveformSize, void * payloadBuffer, int payloadSize) { GGWave * ggWave = (GGWave *) g_instances[id]; if (ggWave->decode(waveformBuffer, waveformSize) == false) { ggprintf("Failed to decode data - GGWave instance %d\n", id); return -1; } static thread_local GGWave::TxRxData data; const auto dataLength = ggWave->rxTakeData(data); if (dataLength == -1) { // failed to decode message return -1; } else if (dataLength > payloadSize) { // the payloadBuffer is not big enough to store the data return -2; } else if (dataLength > 0) { memcpy(payloadBuffer, data.data(), dataLength); } return dataLength; } extern "C" void ggwave_rxToggleProtocol( ggwave_ProtocolId protocolId, int state) { GGWave::Protocols::rx().toggle(protocolId, state != 0); } extern "C" void ggwave_txToggleProtocol( ggwave_ProtocolId protocolId, int state) { GGWave::Protocols::tx().toggle(protocolId, state != 0); } extern "C" void ggwave_rxProtocolSetFreqStart( ggwave_ProtocolId protocolId, int freqStart) { GGWave::Protocols::rx()[protocolId].freqStart = freqStart; } extern "C" void ggwave_txProtocolSetFreqStart( ggwave_ProtocolId protocolId, int freqStart) { GGWave::Protocols::tx()[protocolId].freqStart = freqStart; } extern "C" int ggwave_rxDurationFrames(ggwave_Instance id) { GGWave * ggWave = (GGWave *) g_instances[id]; return ggWave->rxDurationFrames(); } // // C++ implementation // namespace { // magic numbers used to XOR the Rx / Tx data // this achieves more homogeneous distribution of the sound energy across the spectrum constexpr int kDSSMagicSize = 64; const uint8_t kDSSMagic[kDSSMagicSize] PROGMEM = { 0x96, 0x9f, 0xb4, 0xaf, 0x1b, 0x91, 0xde, 0xc5, 0x45, 0x75, 0xe8, 0x2e, 0x0f, 0x32, 0x4a, 0x5f, 0xb4, 0x56, 0x95, 0xcb, 0x7f, 0x6a, 0x54, 0x6a, 0x48, 0xf2, 0x0b, 0x7b, 0xcd, 0xfb, 0x93, 0x6d, 0x3c, 0x77, 0x5e, 0xc3, 0x33, 0x47, 0xc0, 0xf1, 0x71, 0x32, 0x33, 0x27, 0x35, 0x68, 0x47, 0x1f, 0x4e, 0xac, 0x23, 0x42, 0x5f, 0x00, 0x37, 0xa4, 0x50, 0x6d, 0x48, 0x24, 0x91, 0x7c, 0xa1, 0x4e, }; uint8_t getDSSMagic(int i) { #ifdef ARDUINO return pgm_read_byte(&kDSSMagic[i % kDSSMagicSize]); #else return kDSSMagic[i % kDSSMagicSize]; #endif } void FFT(float * f, int N, int * wi, float * wf) { rdft(N, 1, f, wi, wf); } void FFT(const float * src, float * dst, int N, int * wi, float * wf) { memcpy(dst, src, N * sizeof(float)); FFT(dst, N, wi, wf); } inline void addAmplitudeSmooth( const GGWave::Amplitude & src, GGWave::Amplitude & dst, float scalar, int startId, int finalId, int cycleMod, int nPerCycle) { const int nTotal = nPerCycle*finalId; const float frac = 0.15f; const float ds = frac*nTotal; const float ids = 1.0f/ds; const int nBegin = frac*nTotal; const int nEnd = (1.0f - frac)*nTotal; for (int i = startId; i < finalId; i++) { const 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]; } } } int getECCBytesForLength(int len) { return len < 4 ? 2 : GG_MAX(4, 2*(len/5)); } int bytesForSampleFormat(GGWave::SampleFormat sampleFormat) { switch (sampleFormat) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: return 0; break; case GGWAVE_SAMPLE_FORMAT_U8: return sizeof(uint8_t); break; case GGWAVE_SAMPLE_FORMAT_I8: return sizeof(int8_t); break; case GGWAVE_SAMPLE_FORMAT_U16: return sizeof(uint16_t); break; case GGWAVE_SAMPLE_FORMAT_I16: return sizeof(int16_t); break; case GGWAVE_SAMPLE_FORMAT_F32: return sizeof(float); break; }; ggprintf("Invalid sample format: %d\n", (int) sampleFormat); return 0; } } // // ggvector // template void ggvector::assign(const ggvector & other) { m_data = other.m_data; m_size = other.m_size; } template void ggvector::copy(const ggvector & other) { if (this == &other) { assert(false); } memcpy(m_data, other.m_data, GG_MIN(m_size, other.m_size)*sizeof(T)); } template void ggvector::zero() { memset(m_data, 0, m_size*sizeof(T)); } template void ggvector::zero(int n) { memset(m_data, 0, n*sizeof(T)); } template struct ggvector; // // ggmatrix // template void ggmatrix::zero() { if (m_size0 > 0 && m_size1 > 0) { memset(m_data, 0, m_size0*m_size1*sizeof(T)); } } // // Protocols // void GGWave::Protocols::enableAll() { for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; i++) { auto & p = this->data[i]; if (p.name) { p.enabled = true; } } } void GGWave::Protocols::disableAll() { for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; i++) { auto & p = this->data[i]; p.enabled = false; } } void GGWave::Protocols::toggle(ProtocolId id, bool state) { if (state) { // enable protocol data[id].enabled = true; } else { // disable protocol data[id].enabled = false; } } void GGWave::Protocols::only(ProtocolId id) { disableAll(); data[id].enabled = true; } GGWave::TxProtocols & GGWave::Protocols::tx() { static TxProtocols protocols = kDefault(); return protocols; } GGWave::RxProtocols & GGWave::Protocols::rx() { static RxProtocols protocols = kDefault(); return protocols; } // this probably does not matter, but adding it anyway #ifdef ARDUINO const int kAlignment = 4; #else const int kAlignment = 8; #endif //template //void ggalloc(std::vector & v, int n, void * buf, int & bufSize) { // if (buf == nullptr) { // bufSize += n*sizeof(T); // bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment; // return; // } // // v.resize(n); // bufSize += n*sizeof(T); // bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment; //} // //template //void ggalloc(std::vector> & v, int n, int m, void * buf, int & bufSize) { // if (buf == nullptr) { // bufSize += n*m*sizeof(T); // bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment; // return; // } // // v.resize(n); // for (int i = 0; i < n; i++) { // v[i].resize(m); // } // bufSize += n*m*sizeof(T); // bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment; //} template void ggalloc(ggvector & v, int n, void * buf, int & bufSize) { if (buf == nullptr) { bufSize += n*sizeof(T); bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment; return; } v.assign(ggvector((T *)((char *) buf + bufSize), n)); bufSize += n*sizeof(T); bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment; } template void ggalloc(ggmatrix & v, int n, int m, void * buf, int & bufSize) { if (buf == nullptr) { bufSize += n*m*sizeof(T); bufSize = ((bufSize + kAlignment - 1) / kAlignment)*kAlignment; return; } v = ggmatrix((T *)((char *) buf + bufSize), n, m); bufSize += n*m*sizeof(T); bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment; } // // GGWave // GGWave::GGWave(const Parameters & parameters) { prepare(parameters); } GGWave::~GGWave() { if (m_heap) { free(m_heap); } } bool GGWave::prepare(const Parameters & parameters, bool allocate) { if (m_heap) { free(m_heap); m_heap = nullptr; m_heapSize = 0; } // parameter initialization: m_sampleRateInp = parameters.sampleRateInp; m_sampleRateOut = parameters.sampleRateOut; m_sampleRate = parameters.sampleRate; m_samplesPerFrame = parameters.samplesPerFrame; m_isamplesPerFrame = 1.0f/m_samplesPerFrame; m_sampleSizeInp = bytesForSampleFormat(parameters.sampleFormatInp); m_sampleSizeOut = bytesForSampleFormat(parameters.sampleFormatOut); m_sampleFormatInp = parameters.sampleFormatInp; m_sampleFormatOut = parameters.sampleFormatOut; m_hzPerSample = m_sampleRate/m_samplesPerFrame; m_ihzPerSample = 1.0f/m_hzPerSample; m_freqDelta_bin = 1; m_freqDelta_hz = 2*m_hzPerSample; m_nBitsInMarker = 16; m_nMarkerFrames = parameters.payloadLength > 0 ? 0 : kDefaultMarkerFrames; m_encodedDataOffset = parameters.payloadLength > 0 ? 0 : kDefaultEncodedDataOffset; m_soundMarkerThreshold = parameters.soundMarkerThreshold; m_isFixedPayloadLength = parameters.payloadLength > 0; m_payloadLength = parameters.payloadLength; m_isRxEnabled = parameters.operatingMode & GGWAVE_OPERATING_MODE_RX; m_isTxEnabled = parameters.operatingMode & GGWAVE_OPERATING_MODE_TX; m_needResampling = m_sampleRateInp != m_sampleRate || m_sampleRateOut != m_sampleRate; m_txOnlyTones = parameters.operatingMode & GGWAVE_OPERATING_MODE_TX_ONLY_TONES; m_isDSSEnabled = parameters.operatingMode & GGWAVE_OPERATING_MODE_USE_DSS; if (m_sampleSizeInp == 0) { ggprintf("Invalid or unsupported capture sample format: %d\n", (int) parameters.sampleFormatInp); return false; } if (m_sampleSizeOut == 0) { ggprintf("Invalid or unsupported playback sample format: %d\n", (int) parameters.sampleFormatOut); return false; } if (parameters.samplesPerFrame > kMaxSamplesPerFrame) { ggprintf("Invalid samples per frame: %d, max: %d\n", parameters.samplesPerFrame, kMaxSamplesPerFrame); return false; } if (m_sampleRateInp < kSampleRateMin) { ggprintf("Error: capture sample rate (%g Hz) must be >= %g Hz\n", m_sampleRateInp, kSampleRateMin); return false; } if (m_sampleRateInp > kSampleRateMax) { ggprintf("Error: capture sample rate (%g Hz) must be <= %g Hz\n", m_sampleRateInp, kSampleRateMax); return false; } // memory allocation: m_heap = nullptr; m_heapSize = 0; if (this->alloc(m_heap, m_heapSize) == false) { ggprintf("Error: failed to compute the size of the required memory\n"); return false; } if (allocate == false) { return true; } const auto heapSize0 = m_heapSize; m_heap = calloc(m_heapSize, 1); m_heapSize = 0; if (this->alloc(m_heap, m_heapSize) == false) { ggprintf("Error: failed to allocate the required memory: %d\n", m_heapSize); return false; } if (heapSize0 != m_heapSize) { ggprintf("Error: failed to allocate memory - heapSize0: %d, heapSize: %d\n", heapSize0, m_heapSize); return false; } if (m_isRxEnabled) { m_rx.samplesNeeded = m_samplesPerFrame; m_rx.fftWorkI[0] = 0; m_rx.protocol = {}; m_rx.protocolId = GGWAVE_PROTOCOL_COUNT; m_rx.protocols = Protocols::rx(); m_rx.minFreqStart = minFreqStart(m_rx.protocols); } if (m_isTxEnabled) { m_tx.protocols = Protocols::tx(); } return init("", {}, 0); } bool GGWave::alloc(void * p, int & n) { const int maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable; const int totalLength = maxLength + getECCBytesForLength(maxLength); const int totalTxs = (totalLength + minBytesPerTx(Protocols::rx()) - 1)/minBytesPerTx(Protocols::tx()); if (totalLength > kMaxDataSize) { ggprintf("Error: total length %d (payload %d + ECC %d bytes) is too large ( > %d)\n", totalLength, maxLength, getECCBytesForLength(maxLength), kMaxDataSize); return false; } // common ::ggalloc(m_dataEncoded, totalLength + m_encodedDataOffset, p, n); if (m_isRxEnabled) { ::ggalloc(m_rx.fftOut, 2*m_samplesPerFrame, p, n); ::ggalloc(m_rx.fftWorkI, 3 + sqrt(m_samplesPerFrame/2), p, n); ::ggalloc(m_rx.fftWorkF, m_samplesPerFrame/2, p, n); ::ggalloc(m_rx.spectrum, m_samplesPerFrame, p, n); // small extra space because sometimes resampling needs a few more samples: ::ggalloc(m_rx.amplitude, m_needResampling ? m_samplesPerFrame + 128 : m_samplesPerFrame, p, n); // min input sampling rate is 0.125*m_sampleRate: ::ggalloc(m_rx.amplitudeResampled, m_needResampling ? 8*m_samplesPerFrame : m_samplesPerFrame, p, n); ::ggalloc(m_rx.amplitudeTmp, m_needResampling ? 8*m_samplesPerFrame*m_sampleSizeInp : m_samplesPerFrame*m_sampleSizeInp, p, n); ::ggalloc(m_rx.data, maxLength + 1, p, n); // extra byte for null-termination if (m_isFixedPayloadLength) { if (m_payloadLength > kMaxLengthFixed) { ggprintf("Invalid payload length: %d, max: %d\n", m_payloadLength, kMaxLengthFixed); return false; } ::ggalloc(m_rx.spectrumHistoryFixed, totalTxs*maxFramesPerTx(Protocols::rx(), false), m_samplesPerFrame, p, n); ::ggalloc(m_rx.detectedBins, 2*totalLength, p, n); ::ggalloc(m_rx.detectedTones, 2*16*maxBytesPerTx(Protocols::rx()), p, n); } else { // variable payload length ::ggalloc(m_rx.amplitudeRecorded, kMaxRecordedFrames*m_samplesPerFrame, p, n); ::ggalloc(m_rx.amplitudeAverage, m_samplesPerFrame, p, n); ::ggalloc(m_rx.amplitudeHistory, kMaxSpectrumHistory, m_samplesPerFrame, p, n); } } if (m_isTxEnabled) { const int maxDataBits = 2*16*maxBytesPerTx(Protocols::tx()); if (m_txOnlyTones == false) { ::ggalloc(m_tx.phaseOffsets, maxDataBits, p, n); ::ggalloc(m_tx.bit0Amplitude, maxDataBits, m_samplesPerFrame, p, n); ::ggalloc(m_tx.bit1Amplitude, maxDataBits, m_samplesPerFrame, p, n); ::ggalloc(m_tx.output, m_samplesPerFrame, p, n); ::ggalloc(m_tx.outputResampled, 2*m_samplesPerFrame, p, n); ::ggalloc(m_tx.outputTmp, kMaxRecordedFrames*m_samplesPerFrame*m_sampleSizeOut, p, n); ::ggalloc(m_tx.outputI16, kMaxRecordedFrames*m_samplesPerFrame, p, n); } const int maxTones = m_isFixedPayloadLength ? maxTonesPerTx(Protocols::tx()) : m_nBitsInMarker; ::ggalloc(m_tx.data, maxLength + 1, p, n); // first byte stores the length ::ggalloc(m_tx.dataBits, maxDataBits, p, n); ::ggalloc(m_tx.tones, maxTones*totalTxs + (maxTones > 1 ? totalTxs : 0), p, n); } // pre-allocate Reed-Solomon memory buffers { const auto maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable; if (m_isFixedPayloadLength == false) { ::ggalloc(m_workRSLength, RS::ReedSolomon::getWorkSize_bytes(1, m_encodedDataOffset - 1), p, n); } ::ggalloc(m_workRSData, RS::ReedSolomon::getWorkSize_bytes(maxLength, getECCBytesForLength(maxLength)), p, n); } if (m_needResampling) { m_resampler.alloc(p, n); } return true; } void GGWave::setLogFile(FILE * fptr) { g_fptr = fptr; } const GGWave::Parameters & GGWave::getDefaultParameters() { static ggwave_Parameters result { -1, // vaiable payload length kDefaultSampleRate, kDefaultSampleRate, kDefaultSampleRate, kDefaultSamplesPerFrame, kDefaultSoundMarkerThreshold, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_SAMPLE_FORMAT_F32, GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX, }; return result; } bool GGWave::init(const char * text, TxProtocolId protocolId, const int volume) { return init(strlen(text), text, protocolId, volume); } bool GGWave::init(int dataSize, const char * dataBuffer, TxProtocolId protocolId, const int volume) { if (dataSize < 0) { ggprintf("Negative data size: %d\n", dataSize); return false; } // Tx if (m_isTxEnabled) { const auto maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable; if (dataSize > maxLength) { ggprintf("Truncating data from %d to %d bytes\n", dataSize, maxLength); dataSize = maxLength; } if (volume < 0 || volume > 100) { ggprintf("Invalid volume: %d\n", volume); return false; } m_tx.hasData = false; m_tx.data.zero(); m_dataEncoded.zero(); if (dataSize > 0) { if (protocolId < 0 || protocolId >= m_tx.protocols.size()) { ggprintf("Invalid protocol ID: %d\n", protocolId); return false; } const auto & protocol = m_tx.protocols[protocolId]; if (protocol.enabled == false) { ggprintf("Protocol %d is not enabled - make sure to enable it before creating the instance\n", protocolId); return false; } if (protocol.extra == 2 && m_isFixedPayloadLength == false) { ggprintf("Mono-tone protocols with variable length are not supported\n"); return false; } m_tx.protocol = protocol; m_tx.dataLength = m_isFixedPayloadLength ? m_payloadLength : dataSize; m_tx.sendVolume = ((double)(volume))/100.0f; m_tx.data[0] = m_tx.dataLength; for (int i = 0; i < m_tx.dataLength; ++i) { m_tx.data[i + 1] = i < dataSize ? dataBuffer[i] : 0; if (m_isDSSEnabled) { m_tx.data[i + 1] ^= getDSSMagic(i); } } m_tx.hasData = true; } } else { if (dataSize > 0) { ggprintf("Tx is disabled - cannot transmit data with this GGWave instance\n"); } } // Rx if (m_isRxEnabled) { m_rx.receiving = false; m_rx.analyzing = false; m_rx.framesToAnalyze = 0; m_rx.framesLeftToAnalyze = 0; m_rx.framesToRecord = 0; m_rx.framesLeftToRecord = 0; m_rx.spectrum.zero(); m_rx.amplitude.zero(); m_rx.amplitudeHistory.zero(); m_rx.data.zero(); m_rx.spectrumHistoryFixed.zero(); } return true; } uint32_t GGWave::encodeSize_bytes() const { return encodeSize_samples()*m_sampleSizeOut; } uint32_t GGWave::encodeSize_samples() const { if (m_tx.hasData == false) { return 0; } float factor = 1.0f; int samplesPerFrameOut = m_samplesPerFrame; if (m_needResampling) { factor = m_sampleRate/m_sampleRateOut; // note : +1 extra sample in order to overestimate the buffer size samplesPerFrameOut = m_resampler.resample(factor, m_samplesPerFrame, m_tx.output.data(), nullptr) + 1; } const int nECCBytesPerTx = getECCBytesForLength(m_tx.dataLength); const int sendDataLength = m_tx.dataLength + m_encodedDataOffset; const int totalBytes = sendDataLength + nECCBytesPerTx; const int totalDataFrames = m_tx.protocol.extra*((totalBytes + m_tx.protocol.bytesPerTx - 1)/m_tx.protocol.bytesPerTx)*m_tx.protocol.framesPerTx; return ( m_nMarkerFrames + totalDataFrames + m_nMarkerFrames )*samplesPerFrameOut; } uint32_t GGWave::encode() { if (m_isTxEnabled == false) { ggprintf("Tx is disabled - cannot transmit data with this GGWave instance\n"); return 0; } if (m_needResampling) { m_resampler.reset(); } const int nECCBytesPerTx = getECCBytesForLength(m_tx.dataLength); const int sendDataLength = m_tx.dataLength + m_encodedDataOffset; const int totalBytes = sendDataLength + nECCBytesPerTx; const int totalDataFrames = m_tx.protocol.extra*((totalBytes + m_tx.protocol.bytesPerTx - 1)/m_tx.protocol.bytesPerTx)*m_tx.protocol.framesPerTx; if (m_isFixedPayloadLength == false) { RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1, m_workRSLength.data()); rsLength.Encode(m_tx.data.data(), m_dataEncoded.data()); } // first byte of m_tx.data contains the length of the payload, so we skip it: RS::ReedSolomon rsData = RS::ReedSolomon(m_tx.dataLength, nECCBytesPerTx, m_workRSData.data()); rsData.Encode(m_tx.data.data() + 1, m_dataEncoded.data() + m_encodedDataOffset); // generate tones { int frameId = 0; bool hasData = m_tx.hasData; m_tx.nTones = 0; while (hasData) { if (frameId < m_nMarkerFrames) { for (int i = 0; i < m_nBitsInMarker; ++i) { m_tx.tones[m_tx.nTones++] = 2*i + i%2; } } else if (frameId < m_nMarkerFrames + totalDataFrames) { int dataOffset = frameId - m_nMarkerFrames; dataOffset /= m_tx.protocol.framesPerTx; dataOffset *= m_tx.protocol.bytesPerTx; m_tx.dataBits.zero(); for (int j = 0; j < m_tx.protocol.bytesPerTx; ++j) { if (m_tx.protocol.extra == 1) { { uint8_t d = m_dataEncoded[dataOffset + j] & 15; m_tx.dataBits[(2*j + 0)*16 + d] = 1; } { uint8_t d = m_dataEncoded[dataOffset + j] & 240; m_tx.dataBits[(2*j + 1)*16 + (d >> 4)] = 1; } } else { if (dataOffset % m_tx.protocol.extra == 0) { uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 15; m_tx.dataBits[(2*j + 0)*16 + d] = 1; } else { uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 240; m_tx.dataBits[(2*j + 0)*16 + (d >> 4)] = 1; } } } for (int k = 0; k < 2*m_tx.protocol.bytesPerTx*16; ++k) { if (m_tx.dataBits[k] == 0) continue; m_tx.tones[m_tx.nTones++] = k; } } else if (frameId < m_nMarkerFrames + totalDataFrames + m_nMarkerFrames) { for (int i = 0; i < m_nBitsInMarker; ++i) { m_tx.tones[m_tx.nTones++] = 2*i + (1 - i%2); } } else { hasData = false; break; } if (m_tx.protocol.nTones() > 1) { m_tx.tones[m_tx.nTones++] = -1; } frameId += m_tx.protocol.framesPerTx; } if (m_txOnlyTones) { m_tx.hasData = false; return true; } } // compute Tx data { for (int k = 0; k < (int) m_tx.phaseOffsets.size(); ++k) { m_tx.phaseOffsets[k] = (M_PI*k)/(m_tx.protocol.nDataBitsPerTx()); } // note : what is the purpose of this shuffle ? I forgot .. :( //std::random_device rd; //std::mt19937 g(rd()); //std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g); for (int k = 0; k < (int) m_tx.dataBits.size(); ++k) { const double freq = bitFreq(m_tx.protocol, k); const double phaseOffset = m_tx.phaseOffsets[k]; const double curHzPerSample = m_hzPerSample; const double curIHzPerSample = 1.0/curHzPerSample; for (int i = 0; i < m_samplesPerFrame; i++) { const double curi = i; m_tx.bit1Amplitude[k][i] = sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*(freq*curIHzPerSample) + phaseOffset); } for (int i = 0; i < m_samplesPerFrame; i++) { const double curi = i; m_tx.bit0Amplitude[k][i] = sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*((freq + m_hzPerSample*m_freqDelta_bin)*curIHzPerSample) + phaseOffset); } } } int frameId = 0; uint32_t offset = 0; const float factor = m_sampleRate/m_sampleRateOut; while (m_tx.hasData) { m_tx.output.zero(); uint16_t nFreq = 0; if (frameId < m_nMarkerFrames) { nFreq = m_nBitsInMarker; for (int i = 0; i < m_nBitsInMarker; ++i) { if (i%2 == 0) { ::addAmplitudeSmooth(m_tx.bit1Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames); } else { ::addAmplitudeSmooth(m_tx.bit0Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames); } } } else if (frameId < m_nMarkerFrames + totalDataFrames) { int dataOffset = frameId - m_nMarkerFrames; int cycleModMain = dataOffset%m_tx.protocol.framesPerTx; dataOffset /= m_tx.protocol.framesPerTx; dataOffset *= m_tx.protocol.bytesPerTx; m_tx.dataBits.zero(); for (int j = 0; j < m_tx.protocol.bytesPerTx; ++j) { if (m_tx.protocol.extra == 1) { { uint8_t d = m_dataEncoded[dataOffset + j] & 15; m_tx.dataBits[(2*j + 0)*16 + d] = 1; } { uint8_t d = m_dataEncoded[dataOffset + j] & 240; m_tx.dataBits[(2*j + 1)*16 + (d >> 4)] = 1; } } else { if (dataOffset % m_tx.protocol.extra == 0) { uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 15; m_tx.dataBits[(2*j + 0)*16 + d] = 1; } else { uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 240; m_tx.dataBits[(2*j + 0)*16 + (d >> 4)] = 1; } } } for (int k = 0; k < 2*m_tx.protocol.bytesPerTx*16; ++k) { if (m_tx.dataBits[k] == 0) continue; ++nFreq; if (k%2) { ::addAmplitudeSmooth(m_tx.bit0Amplitude[k/2], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, cycleModMain, m_tx.protocol.framesPerTx); } else { ::addAmplitudeSmooth(m_tx.bit1Amplitude[k/2], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, cycleModMain, m_tx.protocol.framesPerTx); } } } else if (frameId < m_nMarkerFrames + totalDataFrames + m_nMarkerFrames) { nFreq = m_nBitsInMarker; const int fId = frameId - (m_nMarkerFrames + totalDataFrames); for (int i = 0; i < m_nBitsInMarker; ++i) { if (i%2 == 0) { addAmplitudeSmooth(m_tx.bit0Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames); } else { addAmplitudeSmooth(m_tx.bit1Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames); } } } else { m_tx.hasData = false; break; } if (nFreq == 0) nFreq = 1; const float scale = 1.0f/nFreq; for (int i = 0; i < m_samplesPerFrame; ++i) { m_tx.output[i] *= scale; } int samplesPerFrameOut = m_samplesPerFrame; if (m_needResampling) { samplesPerFrameOut = m_resampler.resample(factor, m_samplesPerFrame, m_tx.output.data(), m_tx.outputResampled.data()); } else { m_tx.outputResampled.copy(m_tx.output); } // default output is in 16-bit signed int so we always compute it for (int i = 0; i < samplesPerFrameOut; ++i) { m_tx.outputI16[offset + i] = 32768*m_tx.outputResampled[i]; } // convert from 32-bit float switch (m_sampleFormatOut) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break; case GGWAVE_SAMPLE_FORMAT_U8: { auto p = reinterpret_cast(m_tx.outputTmp.data()); for (int i = 0; i < samplesPerFrameOut; ++i) { p[offset + i] = 128*(m_tx.outputResampled[i] + 1.0f); } } break; case GGWAVE_SAMPLE_FORMAT_I8: { auto p = reinterpret_cast(m_tx.outputTmp.data()); for (int i = 0; i < samplesPerFrameOut; ++i) { p[offset + i] = 128*m_tx.outputResampled[i]; } } break; case GGWAVE_SAMPLE_FORMAT_U16: { auto p = reinterpret_cast(m_tx.outputTmp.data()); for (int i = 0; i < samplesPerFrameOut; ++i) { p[offset + i] = 32768*(m_tx.outputResampled[i] + 1.0f); } } break; case GGWAVE_SAMPLE_FORMAT_I16: { // skip because we already have the data in m_tx.outputI16 //auto p = reinterpret_cast(m_tx.outputTmp.data()); //for (int i = 0; i < samplesPerFrameOut; ++i) { // p[offset + i] = 32768*m_tx.outputResampled[i]; //} } break; case GGWAVE_SAMPLE_FORMAT_F32: { auto p = reinterpret_cast(m_tx.outputTmp.data()); for (int i = 0; i < samplesPerFrameOut; ++i) { p[offset + i] = m_tx.outputResampled[i]; } } break; } ++frameId; offset += samplesPerFrameOut; } m_tx.lastAmplitudeSize = offset; // the encoded waveform can be accessed via the txWaveform() method // we return the size of the waveform in bytes: return offset*m_sampleSizeOut; } bool GGWave::decode(const void * data, uint32_t nBytes) { if (m_isRxEnabled == false) { ggprintf("Rx is disabled - cannot receive data with this GGWave instance\n"); return false; } if (m_tx.hasData) { ggprintf("Cannot decode while transmitting\n"); return false; } auto dataBuffer = (uint8_t *) data; const float factor = m_sampleRateInp/m_sampleRate; while (true) { // read capture data uint32_t nBytesNeeded = m_rx.samplesNeeded*m_sampleSizeInp; if (m_needResampling) { // note : predict 4 extra samples just to make sure we have enough data nBytesNeeded = (m_resampler.resample(1.0f/factor, m_rx.samplesNeeded, m_rx.amplitudeResampled.data(), nullptr) + 4)*m_sampleSizeInp; } const uint32_t nBytesRecorded = GG_MIN(nBytes, nBytesNeeded); if (nBytesRecorded == 0) { break; } switch (m_sampleFormatInp) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break; case GGWAVE_SAMPLE_FORMAT_U8: case GGWAVE_SAMPLE_FORMAT_I8: case GGWAVE_SAMPLE_FORMAT_U16: case GGWAVE_SAMPLE_FORMAT_I16: { memcpy(m_rx.amplitudeTmp.data(), dataBuffer, nBytesRecorded); } break; case GGWAVE_SAMPLE_FORMAT_F32: { memcpy(m_rx.amplitudeResampled.data(), dataBuffer, nBytesRecorded); } break; } dataBuffer += nBytesRecorded; nBytes -= nBytesRecorded; if (nBytesRecorded % m_sampleSizeInp != 0) { ggprintf("Failure during capture - provided bytes (%d) are not multiple of sample size (%d)\n", nBytesRecorded, m_sampleSizeInp); m_rx.samplesNeeded = m_samplesPerFrame; break; } // convert to 32-bit float int nSamplesRecorded = nBytesRecorded/m_sampleSizeInp; switch (m_sampleFormatInp) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break; case GGWAVE_SAMPLE_FORMAT_U8: { constexpr float scale = 1.0f/128; auto p = reinterpret_cast(m_rx.amplitudeTmp.data()); for (int i = 0; i < nSamplesRecorded; ++i) { m_rx.amplitudeResampled[i] = float(int16_t(*(p + i)) - 128)*scale; } } break; case GGWAVE_SAMPLE_FORMAT_I8: { constexpr float scale = 1.0f/128; auto p = reinterpret_cast(m_rx.amplitudeTmp.data()); for (int i = 0; i < nSamplesRecorded; ++i) { m_rx.amplitudeResampled[i] = float(*(p + i))*scale; } } break; case GGWAVE_SAMPLE_FORMAT_U16: { constexpr float scale = 1.0f/32768; auto p = reinterpret_cast(m_rx.amplitudeTmp.data()); for (int i = 0; i < nSamplesRecorded; ++i) { m_rx.amplitudeResampled[i] = float(int32_t(*(p + i)) - 32768)*scale; } } break; case GGWAVE_SAMPLE_FORMAT_I16: { constexpr float scale = 1.0f/32768; auto p = reinterpret_cast(m_rx.amplitudeTmp.data()); for (int i = 0; i < nSamplesRecorded; ++i) { m_rx.amplitudeResampled[i] = float(*(p + i))*scale; } } break; case GGWAVE_SAMPLE_FORMAT_F32: break; } uint32_t offset = m_samplesPerFrame - m_rx.samplesNeeded; if (m_needResampling) { if (nSamplesRecorded <= 2*Resampler::kWidth) { m_rx.samplesNeeded = m_samplesPerFrame; break; } // reset resampler state every minute if (!m_rx.receiving && m_resampler.nSamplesTotal() > 60.0f*factor*m_sampleRate) { m_resampler.reset(); } int nSamplesResampled = offset + m_resampler.resample(factor, nSamplesRecorded, m_rx.amplitudeResampled.data(), m_rx.amplitude.data() + offset); nSamplesRecorded = nSamplesResampled; } else { for (int i = 0; i < nSamplesRecorded; ++i) { m_rx.amplitude[offset + i] = m_rx.amplitudeResampled[i]; } } // we have enough bytes to do analysis if (nSamplesRecorded >= m_samplesPerFrame) { m_rx.hasNewAmplitude = true; if (m_isFixedPayloadLength) { decode_fixed(); } else { decode_variable(); } int nExtraSamples = nSamplesRecorded - m_samplesPerFrame; for (int i = 0; i < nExtraSamples; ++i) { m_rx.amplitude[i] = m_rx.amplitude[m_samplesPerFrame + i]; } m_rx.samplesNeeded = m_samplesPerFrame - nExtraSamples; } else { m_rx.samplesNeeded = m_samplesPerFrame - nSamplesRecorded; break; } } return true; } // // instance state // bool GGWave::isDSSEnabled() const { return m_isDSSEnabled; } int GGWave::samplesPerFrame() const { return m_samplesPerFrame; } int GGWave::sampleSizeInp() const { return m_sampleSizeInp; } int GGWave::sampleSizeOut() const { return m_sampleSizeOut; } float GGWave::hzPerSample() const { return m_hzPerSample; } float GGWave::sampleRateInp() const { return m_sampleRateInp; } float GGWave::sampleRateOut() const { return m_sampleRateOut; } GGWave::SampleFormat GGWave::sampleFormatInp() const { return m_sampleFormatInp; } GGWave::SampleFormat GGWave::sampleFormatOut() const { return m_sampleFormatOut; } int GGWave::heapSize() const { return m_heapSize; } // // Tx // const void * GGWave::txWaveform() const { switch (m_sampleFormatOut) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break; case GGWAVE_SAMPLE_FORMAT_I16: { return m_tx.outputI16.data(); } break; case GGWAVE_SAMPLE_FORMAT_U8: case GGWAVE_SAMPLE_FORMAT_I8: case GGWAVE_SAMPLE_FORMAT_U16: case GGWAVE_SAMPLE_FORMAT_F32: { return m_tx.outputTmp.data(); } break; } return nullptr; } const GGWave::Tones GGWave::txTones() const { return { m_tx.tones.data(), m_tx.nTones }; } bool GGWave::txHasData() const { return m_tx.hasData; } bool GGWave::txTakeAmplitudeI16(AmplitudeI16 & dst) { if (m_tx.lastAmplitudeSize == 0) return false; dst.assign({ m_tx.outputI16.data(), m_tx.lastAmplitudeSize }); m_tx.lastAmplitudeSize = 0; return true; } const GGWave::RxProtocols & GGWave::txProtocols() const { return m_tx.protocols; } // // Rx // bool GGWave::rxReceiving() const { return m_rx.receiving; } bool GGWave::rxAnalyzing() const { return m_rx.analyzing; } int GGWave::rxSamplesNeeded() const { return m_rx.samplesNeeded; } int GGWave::rxFramesToRecord() const { return m_rx.framesToRecord; } int GGWave::rxFramesLeftToRecord() const { return m_rx.framesLeftToRecord; } int GGWave::rxFramesToAnalyze() const { return m_rx.framesToAnalyze; } int GGWave::rxFramesLeftToAnalyze() const { return m_rx.framesLeftToAnalyze; } int GGWave::rxDurationFrames() const { return m_rx.recvDuration_frames; } bool GGWave::rxStopReceiving() { if (m_rx.receiving == false) { return false; } m_rx.receiving = false; return true; } GGWave::RxProtocols & GGWave::rxProtocols() { return m_rx.protocols; } int GGWave::rxDataLength() const { return m_rx.dataLength; } const GGWave::TxRxData & GGWave::rxData() const { return m_rx.data; } const GGWave::RxProtocol & GGWave::rxProtocol() const { return m_rx.protocol; } const GGWave::RxProtocolId & GGWave::rxProtocolId() const { return m_rx.protocolId; } const GGWave::Spectrum & GGWave::rxSpectrum() const { return m_rx.spectrum; } const GGWave::Amplitude & GGWave::rxAmplitude() const { return m_rx.amplitude; } int GGWave::rxTakeData(TxRxData & dst) { if (m_rx.dataLength == 0) return 0; auto res = m_rx.dataLength; m_rx.dataLength = 0; if (res != -1) { dst.assign({ m_rx.data.data(), res }); } return res; } bool GGWave::rxTakeSpectrum(Spectrum & dst) { if (m_rx.hasNewSpectrum == false) return false; m_rx.hasNewSpectrum = false; dst.assign(m_rx.spectrum); return true; } bool GGWave::rxTakeAmplitude(Amplitude & dst) { if (m_rx.hasNewAmplitude == false) return false; m_rx.hasNewAmplitude = false; dst.assign(m_rx.amplitude); return true; } bool GGWave::computeFFTR(const float * src, float * dst, int N) { if (N != m_samplesPerFrame) { ggprintf("computeFFTR: N (%d) must be equal to 'samplesPerFrame' %d\n", N, m_samplesPerFrame); return false; } FFT(src, dst, N, m_rx.fftWorkI.data(), m_rx.fftWorkF.data()); return true; } int GGWave::computeFFTR(const float * src, float * dst, int N, int * wi, float * wf) { if (wi == nullptr) return 2*N; if (wf == nullptr) return 3 + sqrt(N/2); FFT(src, dst, N, wi, wf); return 1; } int GGWave::filter(ggwave_Filter filter, float * waveform, int N, float p0, float p1, float * w) { if (w == nullptr) { switch (filter) { case GGWAVE_FILTER_HANN: return N; case GGWAVE_FILTER_HAMMING: return N; case GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS: return 11; }; } if (w[0] == 0.0f && w[1] == 0.0f) { switch (filter) { case GGWAVE_FILTER_HANN: { const float f = 2.0f*M_PI/(float)N; for (int i = 0; i < N; i++) { w[i] = 0.5f - 0.5f*cosf(f*(float)i); } } break; case GGWAVE_FILTER_HAMMING: { const float f = 2.0f*M_PI/(float)N; for (int i = 0; i < N; i++) { w[i] = 0.54f - 0.46f*cosf(f*(float)i); } } break; case GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS: { const float th = 2.0f*M_PI*p0/p1; const float g = cos(th)/(1.0f + sin(th)); w[0] = (1.0f + g)/2.0f; w[1] = -((1.0f + g)/2.0f); w[2] = 0.0f; w[3] = -g; w[4] = 0.0f; w[5] = 0.0f; w[6] = 0.0f; w[7] = 0.0f; w[8] = 0.0f; } break; }; } switch (filter) { case GGWAVE_FILTER_HANN: case GGWAVE_FILTER_HAMMING: { for (int i = 0; i < N; i++) { waveform[i] *= w[i]; } } break; case GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS: { for (int i = 0; i < N; i++) { float xn = waveform[i]; float yn = w[0]*xn + w[1]*w[5] + w[2]*w[6] + w[3]*w[7] + w[4]*w[8]; w[6] = w[5]; w[5] = xn; w[8] = w[7]; w[7] = yn; waveform[i] = yn; } } break; }; return 1; } // // GGWave::Resampler // GGWave::Resampler::Resampler() {} bool GGWave::Resampler::alloc(void * p, int & n) { ggalloc(m_sincTable, kWidth*kSamplesPerZeroCrossing, p, n); ggalloc(m_delayBuffer, 3*kWidth, p, n); ggalloc(m_edgeSamples, kWidth, p, n); ggalloc(m_samplesInp, 4096, p, n); if (p) { makeSinc(); reset(); } return true; } void GGWave::Resampler::reset() { m_state = {}; m_edgeSamples.zero(); m_delayBuffer.zero(); m_samplesInp.zero(); } int GGWave::Resampler::resample( float factor, int nSamples, const float * samplesInp, float * samplesOut) { int idxInp = -1; int idxOut = 0; int notDone = 1; float data_in = 0.0f; float data_out = 0.0f; double one_over_factor = 1.0; auto stateSave = m_state; m_state.nSamplesTotal += nSamples; if (samplesOut) { assert(nSamples > kWidth); assert((int) m_samplesInp.size() >= nSamples + kWidth); //if ((int) m_samplesInp.size() < nSamples + kWidth) { // m_samplesInp.resize(nSamples + kWidth); //} for (int i = 0; i < kWidth; ++i) { m_samplesInp[i] = m_edgeSamples[i]; m_edgeSamples[i] = samplesInp[nSamples - kWidth + i]; } for (int i = 0; i < nSamples; ++i) { m_samplesInp[i + kWidth] = samplesInp[i]; } samplesInp = m_samplesInp.data(); } while (notDone) { while (m_state.timeLast < m_state.timeInt) { if (++idxInp >= nSamples) { notDone = 0; break; } else { data_in = samplesInp[idxInp]; } //printf("xxxx idxInp = %d\n", idxInp); if (samplesOut) newData(data_in); m_state.timeLast += 1; } if (notDone == false) break; double temp1 = 0.0; int left_limit = m_state.timeNow - kWidth + 1; /* leftmost neighboring sample used for interp.*/ int right_limit = m_state.timeNow + kWidth; /* rightmost leftmost neighboring sample used for interp.*/ if (left_limit < 0) left_limit = 0; if (right_limit > m_state.nSamplesTotal + kWidth) right_limit = m_state.nSamplesTotal + kWidth; if (factor < 1.0) { for (int j = left_limit; j < right_limit; j++) { temp1 += getData(j - m_state.timeInt)*sinc(m_state.timeNow - (double) j); } data_out = temp1; } else { one_over_factor = 1.0 / factor; for (int j = left_limit; j < right_limit; j++) { temp1 += getData(j - m_state.timeInt)*one_over_factor*sinc(one_over_factor*(m_state.timeNow - (double) j)); } data_out = temp1; } if (samplesOut) { //printf("inp = %d, l = %d, r = %d, n = %d, a = %d, b = %d\n", idxInp, left_limit, right_limit, m_state.nSamplesTotal, left_limit - m_state.timeInt, right_limit - m_state.timeInt - 1); samplesOut[idxOut] = data_out; } ++idxOut; m_state.timeNow += factor; m_state.timeLast = m_state.timeInt; m_state.timeInt = m_state.timeNow; while (m_state.timeLast < m_state.timeInt) { if (++idxInp >= nSamples) { notDone = 0; break; } else { data_in = samplesInp[idxInp]; } if (samplesOut) newData(data_in); m_state.timeLast += 1; } //printf("last idxInp = %d, nSamples = %d\n", idxInp, nSamples); } if (samplesOut == nullptr) { m_state = stateSave; } return idxOut; } float GGWave::Resampler::getData(int j) const { return m_delayBuffer[(int) j + kWidth]; } void GGWave::Resampler::newData(float data) { for (int i = 0; i < kDelaySize - 5; i++) { m_delayBuffer[i] = m_delayBuffer[i + 1]; } m_delayBuffer[kDelaySize - 5] = data; } void GGWave::Resampler::makeSinc() { double temp, win_freq, win; win_freq = M_PI/kWidth/kSamplesPerZeroCrossing; m_sincTable[0] = 1.0; for (int i = 1; i < kWidth*kSamplesPerZeroCrossing; i++) { temp = (double) i*M_PI/kSamplesPerZeroCrossing; m_sincTable[i] = sin(temp)/temp; win = 0.5 + 0.5*cos(win_freq*i); m_sincTable[i] *= win; } } double GGWave::Resampler::sinc(double x) const { int low; double temp, delta; if (fabs(x) >= kWidth - 1) { return 0.0; } else { temp = fabs(x)*(double) kSamplesPerZeroCrossing; low = temp; /* these are interpolation steps */ delta = temp - low; /* and can be ommited if desired */ return linear_interp(m_sincTable[low], m_sincTable[low + 1], delta); } } // // Variable payload length // void GGWave::decode_variable() { m_rx.amplitudeHistory[m_rx.historyId].copy(m_rx.amplitude); if (++m_rx.historyId >= kMaxSpectrumHistory) { m_rx.historyId = 0; } if (m_rx.historyId == 0 || m_rx.receiving) { m_rx.hasNewSpectrum = true; m_rx.amplitudeAverage.zero(); for (int j = 0; j < (int) m_rx.amplitudeHistory.size(); ++j) { auto s = m_rx.amplitudeHistory[j]; for (int i = 0; i < m_samplesPerFrame; ++i) { m_rx.amplitudeAverage[i] += s[i]; } } float norm = 1.0f/kMaxSpectrumHistory; for (int i = 0; i < m_samplesPerFrame; ++i) { m_rx.amplitudeAverage[i] *= norm; } // calculate spectrum FFT(m_rx.amplitudeAverage.data(), m_rx.fftOut.data(), m_samplesPerFrame, m_rx.fftWorkI.data(), m_rx.fftWorkF.data()); for (int i = 0; i < m_samplesPerFrame; ++i) { m_rx.spectrum[i] = (m_rx.fftOut[2*i + 0]*m_rx.fftOut[2*i + 0] + m_rx.fftOut[2*i + 1]*m_rx.fftOut[2*i + 1]); } for (int i = 1; i < m_samplesPerFrame/2; ++i) { m_rx.spectrum[i] += m_rx.spectrum[m_samplesPerFrame - i]; } } if (m_rx.framesLeftToRecord > 0) { memcpy(m_rx.amplitudeRecorded.data() + (m_rx.framesToRecord - m_rx.framesLeftToRecord)*m_samplesPerFrame, m_rx.amplitude.data(), m_samplesPerFrame*sizeof(float)); if (--m_rx.framesLeftToRecord <= 0) { m_rx.analyzing = true; } } if (m_rx.analyzing) { ggprintf("Analyzing captured data ..\n"); const int stepsPerFrame = 16; const int step = m_samplesPerFrame/stepsPerFrame; bool isValid = false; for (int protocolId = 0; protocolId < (int) m_rx.protocols.size(); ++protocolId) { const auto & protocol = m_rx.protocols[protocolId]; if (protocol.enabled == false) { continue; } // skip Rx protocol if it is mono-tone if (protocol.extra == 2) { continue; } // skip Rx protocol if start frequency is different from detected one if (protocol.freqStart != m_rx.markerFreqStart) { continue; } m_rx.spectrum.zero(); m_rx.framesToAnalyze = m_nMarkerFrames*stepsPerFrame; m_rx.framesLeftToAnalyze = m_rx.framesToAnalyze; // note : not sure if looping backwards here is more meaningful than looping forwards for (int ii = m_nMarkerFrames*stepsPerFrame - 1; ii >= 0; --ii) { bool knownLength = false; int decodedLength = 0; const int offsetStart = ii; for (int itx = 0; itx < 1024; ++itx) { int offsetTx = offsetStart + itx*protocol.framesPerTx*stepsPerFrame; if (offsetTx >= m_rx.recvDuration_frames*stepsPerFrame || (itx + 1)*protocol.bytesPerTx >= (int) m_dataEncoded.size()) { break; } memcpy(m_rx.fftOut.data(), m_rx.amplitudeRecorded.data() + offsetTx*step, m_samplesPerFrame*sizeof(float)); // note : should we skip the first and last frame here as they are amplitude-smoothed? for (int k = 1; k < protocol.framesPerTx; ++k) { for (int i = 0; i < m_samplesPerFrame; ++i) { m_rx.fftOut[i] += m_rx.amplitudeRecorded[(offsetTx + k*stepsPerFrame)*step + i]; } } FFT(m_rx.fftOut.data(), m_samplesPerFrame, m_rx.fftWorkI.data(), m_rx.fftWorkF.data()); for (int i = 0; i < m_samplesPerFrame; ++i) { m_rx.spectrum[i] = (m_rx.fftOut[2*i + 0]*m_rx.fftOut[2*i + 0] + m_rx.fftOut[2*i + 1]*m_rx.fftOut[2*i + 1]); } for (int i = 1; i < m_samplesPerFrame/2; ++i) { m_rx.spectrum[i] += m_rx.spectrum[m_samplesPerFrame - i]; } uint8_t curByte = 0; for (int i = 0; i < 2*protocol.bytesPerTx; ++i) { double freq = m_hzPerSample*protocol.freqStart; int bin = round(freq*m_ihzPerSample) + 16*i; int kmax = 0; double amax = 0.0; for (int k = 0; k < 16; ++k) { if (m_rx.spectrum[bin + k] > amax) { kmax = k; amax = m_rx.spectrum[bin + k]; } } if (i%2) { curByte += (kmax << 4); m_dataEncoded[itx*protocol.bytesPerTx + i/2] = curByte; curByte = 0; } else { curByte = kmax; } } if (itx*protocol.bytesPerTx > m_encodedDataOffset && knownLength == false) { RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1, m_workRSLength.data()); if ((rsLength.Decode(m_dataEncoded.data(), m_rx.data.data()) == 0) && (m_rx.data[0] > 0 && m_rx.data[0] <= 140)) { knownLength = true; decodedLength = m_rx.data[0]; //printf("decoded length = %d, recvDuration_frames = %d\n", decodedLength, m_rx.recvDuration_frames); const int nTotalBytesExpected = m_encodedDataOffset + decodedLength + ::getECCBytesForLength(decodedLength); const int nTotalFramesExpected = 2*m_nMarkerFrames + ((nTotalBytesExpected + protocol.bytesPerTx - 1)/protocol.bytesPerTx)*protocol.framesPerTx; if (m_rx.recvDuration_frames > nTotalFramesExpected || m_rx.recvDuration_frames < nTotalFramesExpected - 2*m_nMarkerFrames) { //printf(" - invalid number of frames: %d (expected %d)\n", m_rx.recvDuration_frames, nTotalFramesExpected); knownLength = false; break; } } else { break; } } { const int nTotalBytesExpected = m_encodedDataOffset + decodedLength + ::getECCBytesForLength(decodedLength); if (knownLength && itx*protocol.bytesPerTx > nTotalBytesExpected + 1) { break; } } } if (knownLength) { RS::ReedSolomon rsData(decodedLength, ::getECCBytesForLength(decodedLength), m_workRSData.data()); if (rsData.Decode(m_dataEncoded.data() + m_encodedDataOffset, m_rx.data.data()) == 0) { if (decodedLength > 0) { if (m_isDSSEnabled) { for (int i = 0; i < decodedLength; ++i) { m_rx.data[i] = m_rx.data[i] ^ getDSSMagic(i); } } ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", decodedLength, protocol.name, protocolId); ggprintf("Received sound data successfully: '%s'\n", m_rx.data.data()); isValid = true; m_rx.hasNewRxData = true; m_rx.dataLength = decodedLength; m_rx.protocol = protocol; m_rx.protocolId = RxProtocolId(protocolId); } } } if (isValid) { break; } --m_rx.framesLeftToAnalyze; } if (isValid) break; } m_rx.framesToRecord = 0; if (isValid == false) { ggprintf("Failed to capture sound data. Please try again (length = %d)\n", m_rx.data[0]); m_rx.dataLength = -1; m_rx.framesToRecord = -1; } m_rx.receiving = false; m_rx.analyzing = false; m_rx.spectrum.zero(); m_rx.framesToAnalyze = 0; m_rx.framesLeftToAnalyze = 0; } // check if receiving data if (m_rx.receiving == false) { bool isReceiving = false; for (int i = 0; i < m_rx.protocols.size(); ++i) { const auto & protocol = m_rx.protocols[i]; if (protocol.enabled == false) { continue; } int nDetectedMarkerBits = m_nBitsInMarker; for (int i = 0; i < m_nBitsInMarker; ++i) { double freq = bitFreq(protocol, i); int bin = round(freq*m_ihzPerSample); if (i%2 == 0) { if (m_rx.spectrum[bin] <= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) --nDetectedMarkerBits; } else { if (m_rx.spectrum[bin] >= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) --nDetectedMarkerBits; } } if (nDetectedMarkerBits == m_nBitsInMarker) { m_rx.markerFreqStart = protocol.freqStart; isReceiving = true; break; } } if (isReceiving) { if (++m_rx.nMarkersSuccess >= 1) { } else { isReceiving = false; } } else { m_rx.nMarkersSuccess = 0; } if (isReceiving) { ggprintf("Receiving sound data ...\n"); m_rx.receiving = true; m_rx.data.zero(); // max recieve duration m_rx.recvDuration_frames = 2*m_nMarkerFrames + maxFramesPerTx(m_rx.protocols, true)*( (kMaxLengthVariable + ::getECCBytesForLength(kMaxLengthVariable))/minBytesPerTx(m_rx.protocols) + 1 ); m_rx.nMarkersSuccess = 0; m_rx.framesToRecord = m_rx.recvDuration_frames; m_rx.framesLeftToRecord = m_rx.recvDuration_frames; } } else { bool isEnded = false; for (int i = 0; i < m_rx.protocols.size(); ++i) { const auto & protocol = m_rx.protocols[i]; if (protocol.enabled == false) { continue; } int nDetectedMarkerBits = m_nBitsInMarker; for (int i = 0; i < m_nBitsInMarker; ++i) { double freq = bitFreq(protocol, i); int bin = round(freq*m_ihzPerSample); if (i%2 == 0) { if (m_rx.spectrum[bin] >= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) nDetectedMarkerBits--; } else { if (m_rx.spectrum[bin] <= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) nDetectedMarkerBits--; } } if (nDetectedMarkerBits == m_nBitsInMarker) { isEnded = true; break; } } if (isEnded) { if (++m_rx.nMarkersSuccess >= 1) { } else { isEnded = false; } } else { m_rx.nMarkersSuccess = 0; } if (isEnded && m_rx.framesToRecord > 1) { m_rx.recvDuration_frames -= m_rx.framesLeftToRecord - 1; ggprintf("Received end marker. Frames left = %d, recorded = %d\n", m_rx.framesLeftToRecord, m_rx.recvDuration_frames); m_rx.nMarkersSuccess = 0; m_rx.framesLeftToRecord = 1; } } } // // Fixed payload length void GGWave::decode_fixed() { m_rx.hasNewSpectrum = true; // calculate spectrum FFT(m_rx.amplitude.data(), m_rx.fftOut.data(), m_samplesPerFrame, m_rx.fftWorkI.data(), m_rx.fftWorkF.data()); float amax = 0.0f; for (int i = 0; i < m_samplesPerFrame; ++i) { m_rx.spectrum[i] = (m_rx.fftOut[2*i + 0]*m_rx.fftOut[2*i + 0] + m_rx.fftOut[2*i + 1]*m_rx.fftOut[2*i + 1]); } for (int i = 1; i < m_samplesPerFrame/2; ++i) { m_rx.spectrum[i] += m_rx.spectrum[m_samplesPerFrame - i]; if (i >= m_rx.minFreqStart) { amax = GG_MAX(amax, m_rx.spectrum[i]); } } // original, floating-point version //m_rx.spectrumHistoryFixed[m_rx.historyIdFixed].copy(m_rx.spectrum); // float -> uint8_t amax = 255.0f/(amax == 0.0f ? 1.0f : amax); for (int i = 0; i < m_samplesPerFrame; ++i) { m_rx.spectrumHistoryFixed[m_rx.historyIdFixed][i] = GG_MIN(255.0f, GG_MAX(0.0f, (float) round(m_rx.spectrum[i]*amax))); } // float -> uint16_t //amax = 65535.0f/(amax == 0.0f ? 1.0f : amax); //for (int i = 0; i < m_samplesPerFrame; ++i) { // m_rx.spectrumHistoryFixed[m_rx.historyIdFixed][i] = GG_MIN(65535.0f, GG_MAX(0.0f, (float) round(m_rx.spectrum[i]*amax))); //} if (++m_rx.historyIdFixed >= (int) m_rx.spectrumHistoryFixed.size()) { m_rx.historyIdFixed = 0; } bool isValid = false; for (int protocolId = 0; protocolId < (int) m_rx.protocols.size(); ++protocolId) { const auto & protocol = m_rx.protocols[protocolId]; if (protocol.enabled == false) { continue; } const int binStart = protocol.freqStart; const int binDelta = 16; const int binOffset = protocol.extra == 1 ? binDelta : 0; if (binStart > m_samplesPerFrame) { continue; } const int totalLength = m_payloadLength + getECCBytesForLength(m_payloadLength); const int totalTxs = protocol.extra*((totalLength + protocol.bytesPerTx - 1)/protocol.bytesPerTx); int historyStartId = m_rx.historyIdFixed - totalTxs*protocol.framesPerTx; if (historyStartId < 0) { historyStartId += m_rx.spectrumHistoryFixed.size(); } const int nTones = 2*protocol.bytesPerTx; m_rx.detectedBins.zero(); int txNeededTotal = 0; int txDetectedTotal = 0; bool detectedSignal = true; for (int k = 0; k < totalTxs; ++k) { if (k % protocol.extra == 0) { m_rx.detectedTones.zero(16*nTones); } for (int i = 0; i < protocol.framesPerTx; ++i) { int historyId = historyStartId + k*protocol.framesPerTx + i; if (historyId >= (int) m_rx.spectrumHistoryFixed.size()) { historyId -= m_rx.spectrumHistoryFixed.size(); } for (int j = 0; j < protocol.bytesPerTx; ++j) { int f0bin = 0; auto f0max = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta]; for (int b = 1; b < 16; ++b) { { const auto & v = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + b]; if (f0max <= v) { f0max = v; f0bin = b; } } } int f1bin = 0; if (protocol.extra == 1) { auto f1max = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + binOffset]; for (int b = 1; b < 16; ++b) { const auto & v = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + binOffset + b]; if (f1max <= v) { f1max = v; f1bin = b; } } } else { f1bin = f0bin; } if ((k + 0)%protocol.extra == 0) m_rx.detectedTones[(2*j + 0)*16 + f0bin]++; if ((k + 1)%protocol.extra == 0) m_rx.detectedTones[(2*j + 1)*16 + f1bin]++; } } if (protocol.extra > 1 && (k % protocol.extra == 0)) continue; int txNeeded = 0; int txDetected = 0; for (int j = 0; j < protocol.bytesPerTx; ++j) { if ((k/protocol.extra)*protocol.bytesPerTx + j >= totalLength) break; txNeeded += 2; for (int b = 0; b < 16; ++b) { if (m_rx.detectedTones[(2*j + 0)*16 + b] > protocol.framesPerTx/2) { m_rx.detectedBins[2*((k/protocol.extra)*protocol.bytesPerTx + j) + 0] = b; txDetected++; } if (m_rx.detectedTones[(2*j + 1)*16 + b] > protocol.framesPerTx/2) { m_rx.detectedBins[2*((k/protocol.extra)*protocol.bytesPerTx + j) + 1] = b; txDetected++; } } } txDetectedTotal += txDetected; txNeededTotal += txNeeded; } if (txDetectedTotal < 0.75*txNeededTotal) { detectedSignal = false; } if (detectedSignal) { RS::ReedSolomon rsData(m_payloadLength, getECCBytesForLength(m_payloadLength), m_workRSData.data()); for (int j = 0; j < totalLength; ++j) { m_dataEncoded[j] = (m_rx.detectedBins[2*j + 1] << 4) + m_rx.detectedBins[2*j + 0]; } if (rsData.Decode(m_dataEncoded.data(), m_rx.data.data()) == 0) { if (m_isDSSEnabled) { for (int i = 0; i < m_payloadLength; ++i) { m_rx.data[i] = m_rx.data[i] ^ getDSSMagic(i); } } ggprintf("Decoded length = %d, protocol = '%s' (%d)\n", m_payloadLength, protocol.name, protocolId); ggprintf("Received sound data successfully: '%s'\n", m_rx.data.data()); isValid = true; m_rx.hasNewRxData = true; m_rx.dataLength = m_payloadLength; m_rx.protocol = protocol; m_rx.protocolId = RxProtocolId(protocolId); } } if (isValid) { break; } } } int GGWave::maxFramesPerTx(const Protocols & protocols, bool excludeMT) const { int res = 0; for (int i = 0; i < protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled == false) { continue; } if (excludeMT && protocol.extra > 1) { continue; } res = GG_MAX(res, protocol.framesPerTx*protocol.extra); } return res; } int GGWave::minBytesPerTx(const Protocols & protocols) const { int res = 1; for (int i = 0; i < protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled == false) { continue; } res = GG_MIN(res, (int) protocol.bytesPerTx); } return res; } int GGWave::maxBytesPerTx(const Protocols & protocols) const { int res = 1; for (int i = 0; i < protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled == false) { continue; } res = GG_MAX(res, (int) protocol.bytesPerTx); } return res; } int GGWave::maxTonesPerTx(const Protocols & protocols) const { int res = 1; for (int i = 0; i < protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled == false) { continue; } res = GG_MAX(res, protocol.nTones()); } return res; } int GGWave::minFreqStart(const Protocols & protocols) const { int res = m_samplesPerFrame; for (int i = 0; i < protocols.size(); ++i) { const auto & protocol = protocols[i]; if (protocol.enabled == false) { continue; } res = GG_MIN(res, protocol.freqStart); } return res; } double GGWave::bitFreq(const Protocol & p, int bit) const { return m_hzPerSample*p.freqStart + m_freqDelta_hz*bit; } ================================================ FILE: src/reed-solomon/LICENSE ================================================ 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. ================================================ FILE: src/reed-solomon/gf.hpp ================================================ /* Author: Mike Lubinets (aka mersinvald) * Date: 29.12.15 * * See LICENSE */ #ifndef GF_H #define GF_H #include "poly.hpp" #include #include #include namespace RS { namespace gf { /* GF tables pre-calculated for 0x11d primitive polynomial */ const uint8_t exp[512] PROGMEM = { 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] PROGMEM = { 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; #ifdef ARDUINO return pgm_read_byte(exp + pgm_read_byte(log + x) + pgm_read_byte(log + y)); #else return exp[log[x] + log[y]]; #endif } /* @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; #ifdef ARDUINO return pgm_read_byte(exp + (pgm_read_byte(log + x) + 255 - pgm_read_byte(log + y)) % 255); #else return exp[(log[x] + 255 - log[y]) % 255]; #endif } /* @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){ #ifdef ARDUINO intmax_t i = pgm_read_byte(log + x); #else intmax_t i = log[x]; #endif i *= power; i %= 255; if(i < 0) i = i + 255; #ifdef ARDUINO return pgm_read_byte(exp + i); #else return exp[i]; #endif } /* @brief Inversion in Galua Fields * @param x - number * @return inversion of x */ inline uint8_t inverse(uint8_t x){ #ifdef ARDUINO return pgm_read_byte(exp + 255 - pgm_read_byte(log + x)); /* == div(1, x); */ #else return exp[255 - log[x]]; /* == div(1, x); */ #endif } /* ########################## * # 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 ================================================ FILE: src/reed-solomon/poly.hpp ================================================ /* Author: Mike Lubinets (aka mersinvald) * Date: 29.12.15 * * See LICENSE */ #ifndef POLY_H #define POLY_H #include #include #include 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 ================================================ FILE: src/reed-solomon/rs.hpp ================================================ /* Author: Mike Lubinets (aka mersinvald) * Date: 29.12.15 * * See LICENSE */ #ifndef RS_HPP #define RS_HPP #include "poly.hpp" #include "gf.hpp" #include #include #include #include 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 * heap_memory = nullptr; uint8_t * generator_cache = nullptr; bool owns_heap_memory = false; bool generator_cached = false; // used to pre-allocate a memory buffer for the Reed-Solomon class in order to avoid memory allocations static size_t getWorkSize_bytes(uint8_t msg_length, uint8_t ecc_length) { return ecc_length + 1 + MSG_CNT * msg_length + POLY_CNT * ecc_length * 2; } ReedSolomon(uint8_t msg_length_p, uint8_t ecc_length_p, uint8_t * heap_memory_p = nullptr) : msg_length(msg_length_p), ecc_length(ecc_length_p) { if (heap_memory_p) { heap_memory = heap_memory_p; owns_heap_memory = false; } else { heap_memory = (uint8_t *) malloc(getWorkSize_bytes(msg_length, ecc_length)); owns_heap_memory = true; } generator_cache = heap_memory; 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() { if (owns_heap_memory) { delete[] heap_memory; } // 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 this->memory = heap_memory + ecc_length + 1; 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 this->memory = heap_memory + ecc_length + 1; 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 ================================================ FILE: tests/CMakeLists.txt ================================================ if (EMSCRIPTEN) # # test-ggwave-js set(TEST_TARGET test-ggwave-js) add_test(NAME ${TEST_TARGET} COMMAND node test-ggwave.js WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) return() endif() # # test-ggwave-c set(TEST_TARGET test-ggwave-c) add_executable(${TEST_TARGET} test-ggwave.c ) target_link_libraries(${TEST_TARGET} PRIVATE ggwave ) add_test(NAME ${TEST_TARGET} COMMAND $) # # test-ggwave-cpp set(TEST_TARGET test-ggwave-cpp) add_executable(${TEST_TARGET} test-ggwave.cpp ) target_link_libraries(${TEST_TARGET} PRIVATE ggwave ) add_test(NAME ${TEST_TARGET} COMMAND $) if (GGWAVE_SUPPORT_PYTHON) # # test-ggwave-py set(TEST_TARGET test-ggwave-py) add_test(NAME ${TEST_TARGET} COMMAND python test-ggwave.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) set_tests_properties(${TEST_TARGET} PROPERTIES ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/bindings/python") endif() ================================================ FILE: tests/test-ggwave.c ================================================ #include "ggwave/ggwave.h" #include #include #include #define CHECK(cond) \ if (!(cond)) { \ fprintf(stderr, "[%s:%d] Check failed: %s\n", __FILE__, __LINE__, #cond); \ exit(1); \ } #define CHECK_T(cond) CHECK(cond) #define CHECK_F(cond) CHECK(!(cond)) int main() { //ggwave_setLogFile(NULL); // disable logging ggwave_setLogFile(stdout); ggwave_Parameters parameters = ggwave_getDefaultParameters(); parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; parameters.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; ggwave_Instance instance = ggwave_init(parameters); int ret; const char * payload = "test"; char decoded[16]; int n = ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 50, NULL, 1); char *waveform = malloc(n); CHECK(waveform != NULL); int ne = ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 50, waveform, 0); CHECK(ne > 0); // not enough output buffer size to store the decoded message ret = ggwave_ndecode(instance, waveform, ne, decoded, 3); CHECK(ret == -2); // fail // just enough size to store it ret = ggwave_ndecode(instance, waveform, ne, decoded, 4); CHECK(ret == 4); // success // unsafe method - will write the decoded output to the output buffer regardless of the size ret = ggwave_decode(instance, waveform, ne, decoded); CHECK(ret == 4); // disable Rx protocol { ggwave_rxToggleProtocol(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 0); ggwave_Instance instanceTmp = ggwave_init(parameters); ret = ggwave_ndecode(instanceTmp, waveform, ne, decoded, 4); CHECK(ret == -1); // fail ggwave_free(instanceTmp); } // enable Rx protocol { ggwave_rxToggleProtocol(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 1); ggwave_Instance instanceTmp = ggwave_init(parameters); ret = ggwave_ndecode(instanceTmp, waveform, ne, decoded, 4); CHECK(ret == 4); // success ggwave_free(instanceTmp); } decoded[ret] = 0; // null-terminate the received data CHECK(strcmp(decoded, payload) == 0); ggwave_free(instance); free(waveform); return 0; } ================================================ FILE: tests/test-ggwave.cpp ================================================ #include "ggwave/ggwave.h" #include #include #include #include #include #include #include #include #include constexpr float iRandMax = 1.0f/float(RAND_MAX); float frand() { return float(rand()%RAND_MAX)*iRandMax; } #define CHECK(cond) \ if (!(cond)) { \ fprintf(stderr, "[%s:%d] Check failed: %s\n", __FILE__, __LINE__, #cond); \ exit(1); \ } #define CHECK_T(cond) CHECK(cond) #define CHECK_F(cond) CHECK(!(cond)) const std::map kSampleScale = { { typeid(uint8_t), std::numeric_limits::max() }, { typeid(int8_t), std::numeric_limits::max() }, { typeid(uint16_t), std::numeric_limits::max() }, { typeid(int16_t), std::numeric_limits::max() }, { typeid(float), 1.0f }, }; const std::map kSampleOffset = { { typeid(uint8_t), 0.5f*std::numeric_limits::max() }, { typeid(int8_t), 0.0f }, { typeid(uint16_t), 0.5f*std::numeric_limits::max() }, { typeid(int16_t), 0.0f }, { typeid(float), 0.0f }, }; const std::set kFormats = { GGWAVE_SAMPLE_FORMAT_U8, GGWAVE_SAMPLE_FORMAT_I8, GGWAVE_SAMPLE_FORMAT_U16, GGWAVE_SAMPLE_FORMAT_I16, GGWAVE_SAMPLE_FORMAT_F32, }; template void convert(std::vector & src) { const int n = src.size()/sizeof(S); std::vector dst(n); S v; for (int i = 0; i < n; ++i) { std::memcpy(&v, &src[i*sizeof(S)], sizeof(S)); dst[i] = ((float(v) - kSampleOffset.at(typeid(S)))/kSampleScale.at(typeid(S)))*kSampleScale.at(typeid(D)) + kSampleOffset.at(typeid(D)); } src.resize(n*sizeof(D)); std::memcpy(&src[0], &dst[0], n*sizeof(D)); } int main(int argc, char ** argv) { bool full = false; if (argc > 1) { if (strcmp(argv[1], "--full") == 0) { full = true; } } std::vector buffer; auto convertHelper = [&](GGWave::SampleFormat formatOut, GGWave::SampleFormat formatInp) { switch (formatOut) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break; case GGWAVE_SAMPLE_FORMAT_U8: { switch (formatInp) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break; case GGWAVE_SAMPLE_FORMAT_U8: break; case GGWAVE_SAMPLE_FORMAT_I8: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_U16: convert(buffer); break; case GGWAVE_SAMPLE_FORMAT_I16: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_F32: convert (buffer); break; }; } break; case GGWAVE_SAMPLE_FORMAT_I8: { switch (formatInp) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break; case GGWAVE_SAMPLE_FORMAT_U8: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_I8: break; case GGWAVE_SAMPLE_FORMAT_U16: convert(buffer); break; case GGWAVE_SAMPLE_FORMAT_I16: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_F32: convert (buffer); break; }; } break; case GGWAVE_SAMPLE_FORMAT_U16: { switch (formatInp) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break; case GGWAVE_SAMPLE_FORMAT_U8: convert(buffer); break; case GGWAVE_SAMPLE_FORMAT_I8: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_U16: break; case GGWAVE_SAMPLE_FORMAT_I16: convert(buffer); break; case GGWAVE_SAMPLE_FORMAT_F32: convert (buffer); break; }; } break; case GGWAVE_SAMPLE_FORMAT_I16: { switch (formatInp) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break; case GGWAVE_SAMPLE_FORMAT_U8: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_I8: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_U16: convert(buffer); break; case GGWAVE_SAMPLE_FORMAT_I16: break; case GGWAVE_SAMPLE_FORMAT_F32: convert (buffer); break; }; } break; case GGWAVE_SAMPLE_FORMAT_F32: { switch (formatInp) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break; case GGWAVE_SAMPLE_FORMAT_U8: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_I8: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_U16: convert(buffer); break; case GGWAVE_SAMPLE_FORMAT_I16: convert (buffer); break; case GGWAVE_SAMPLE_FORMAT_F32: break; }; } break; }; }; auto addNoiseHelper = [&](float level, GGWave::SampleFormat format) { switch (format) { case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break; case GGWAVE_SAMPLE_FORMAT_U8: { const int n = buffer.size()/sizeof(uint8_t); auto p = (uint8_t *) buffer.data(); for (int i = 0; i < n; ++i) { p[i] = std::max(0.0f, std::min(255.0f, (float) p[i] + (frand() - 0.5f)*(level*256))); } } break; case GGWAVE_SAMPLE_FORMAT_I8: { const int n = buffer.size()/sizeof(int8_t); auto p = (int8_t *) buffer.data(); for (int i = 0; i < n; ++i) { p[i] = std::max(-128.0f, std::min(127.0f, (float) p[i] + (frand() - 0.5f)*(level*256))); } } break; case GGWAVE_SAMPLE_FORMAT_U16: { const int n = buffer.size()/sizeof(uint16_t); auto p = (uint16_t *) buffer.data(); for (int i = 0; i < n; ++i) { p[i] = std::max(0.0f, std::min(65535.0f, (float) p[i] + (frand() - 0.5f)*(level*65536))); } } break; case GGWAVE_SAMPLE_FORMAT_I16: { const int n = buffer.size()/sizeof(int16_t); auto p = (int16_t *) buffer.data(); for (int i = 0; i < n; ++i) { p[i] = std::max(-32768.0f, std::min(32767.0f, (float) p[i] + (frand() - 0.5f)*(level*65536))); } } break; case GGWAVE_SAMPLE_FORMAT_F32: { const int n = buffer.size()/sizeof(float); auto p = (float *) buffer.data(); for (int i = 0; i < n; ++i) { p[i] = std::max(-1.0f, std::min(1.0f, p[i] + (frand() - 0.5f)*(level))); } } break; }; }; { GGWave instance(GGWave::getDefaultParameters()); std::string payload = "hello"; CHECK(instance.init(payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST)); // data CHECK_F(instance.init(-1, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST)); CHECK_T(instance.init(0, nullptr, GGWAVE_PROTOCOL_AUDIBLE_FAST)); CHECK_T(instance.init(0, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST)); CHECK_T(instance.init(1, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST)); CHECK_T(instance.init(2, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST)); CHECK_T(instance.init(3, "asd", GGWAVE_PROTOCOL_AUDIBLE_FAST)); // volume CHECK_F(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, -1)); CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 0)); CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 50)); CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 100)); CHECK_F(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 101)); } // playback / capture at different sample rates for (int srInp = GGWave::kDefaultSampleRate/6; srInp <= 2*GGWave::kDefaultSampleRate; srInp += 1371) { printf("Testing: sample rate = %d\n", srInp); auto parameters = GGWave::getDefaultParameters(); parameters.soundMarkerThreshold = 3.0f; const std::string payload = "hello123"; // encode { parameters.sampleRateOut = srInp; GGWave instanceOut(parameters); instanceOut.init(payload.c_str(), GGWAVE_PROTOCOL_DT_FASTEST, 25); const auto expectedSize = instanceOut.encodeSize_bytes(); const auto nBytes = instanceOut.encode(); printf("Expected = %d, actual = %d\n", expectedSize, nBytes); CHECK(expectedSize >= nBytes); { auto p = (const uint8_t *)(instanceOut.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); } addNoiseHelper(0.01, parameters.sampleFormatOut); // add some artificial noise convertHelper(parameters.sampleFormatOut, parameters.sampleFormatInp); } // decode { parameters.sampleRateInp = srInp; GGWave instanceInp(parameters); instanceInp.rxProtocols().only(GGWAVE_PROTOCOL_DT_FASTEST); instanceInp.decode(buffer.data(), buffer.size()); GGWave::TxRxData result; CHECK(instanceInp.rxTakeData(result) == (int) payload.size()); for (int i = 0; i < (int) payload.size(); ++i) { CHECK(payload[i] == result[i]); } } } const std::string payload = "a0Z5kR2g"; // encode / decode using different sample formats and Tx protocols for (const auto & formatOut : kFormats) { for (const auto & formatInp : kFormats) { if (full == false) { if (formatOut != GGWAVE_SAMPLE_FORMAT_I16) continue; if (formatInp != GGWAVE_SAMPLE_FORMAT_F32) continue; } for (int protocolId = 0; protocolId < GGWAVE_PROTOCOL_COUNT; ++protocolId) { const auto & protocol = GGWave::Protocols::kDefault()[protocolId]; if (protocol.enabled == false) continue; printf("Testing: protocol = %s, in = %d, out = %d\n", protocol.name, formatInp, formatOut); for (int length = 1; length <= (int) payload.size(); ++length) { // mono-tone protocols with variable length are not supported if (protocol.extra == 2) { break; } // variable payload length { auto parameters = GGWave::getDefaultParameters(); parameters.sampleFormatInp = formatInp; parameters.sampleFormatOut = formatOut; // it seems DSS is not suitable for "variable-length" transmission // sometimes, the decoder incorrectly detects an early "end" marker when DSS is enabled //if (rand() % 2 == 0) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS; GGWave instance(parameters); instance.rxProtocols().only(GGWave::ProtocolId(protocolId)); instance.init(length, payload.data(), GGWave::ProtocolId(protocolId), 25); const auto expectedSize = instance.encodeSize_bytes(); const auto nBytes = instance.encode(); printf("Expected = %d, actual = %d\n", expectedSize, nBytes); CHECK(expectedSize == nBytes); { auto p = (const uint8_t *)(instance.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); } addNoiseHelper(0.02, parameters.sampleFormatOut); // add some artificial noise convertHelper(formatOut, formatInp); instance.decode(buffer.data(), buffer.size()); GGWave::TxRxData result; CHECK(instance.rxTakeData(result) == length); for (int i = 0; i < length; ++i) { CHECK(payload[i] == result[i]); } } } for (int length = 1; length <= (int) payload.size(); ++length) { // fixed payload length { auto parameters = GGWave::getDefaultParameters(); parameters.payloadLength = length; parameters.sampleFormatInp = formatInp; parameters.sampleFormatOut = formatOut; if (rand() % 2 == 0) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS; GGWave instance(parameters); instance.rxProtocols().only(GGWave::ProtocolId(protocolId)); instance.init(length, payload.data(), GGWave::ProtocolId(protocolId), 10); const auto expectedSize = instance.encodeSize_bytes(); const auto nBytes = instance.encode(); printf("Expected = %d, actual = %d\n", expectedSize, nBytes); CHECK(expectedSize == nBytes); { auto p = (const uint8_t *)(instance.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); } addNoiseHelper(0.10, parameters.sampleFormatOut); // add some artificial noise convertHelper(formatOut, formatInp); instance.decode(buffer.data(), buffer.size()); GGWave::TxRxData result; CHECK(instance.rxTakeData(result) == length); for (int i = 0; i < length; ++i) { CHECK(payload[i] == result[i]); } } } } } } return 0; } ================================================ FILE: tests/test-ggwave.js ================================================ var factory = require('../bindings/javascript/ggwave.js') factory().then(function(ggwave) { // create ggwave instance with default parameters var parameters = ggwave.getDefaultParameters(); parameters.operatingMode |= ggwave.GGWAVE_OPERATING_MODE_USE_DSS; var instance = ggwave.init(parameters); console.log('instance: ' + instance); var payload = 'hello js'; // generate audio waveform for string "hello js" var waveform = ggwave.encode(instance, payload, ggwave.ProtocolId.GGWAVE_PROTOCOL_AUDIBLE_FAST, 10); // decode the audio waveform back to text var res = ggwave.decode(instance, waveform); if (new TextDecoder("utf-8").decode(res) != payload) { process.exit(1); } }); ================================================ FILE: tests/test-ggwave.py ================================================ import sys import ggwave # optionally disable logging #ggwave.disableLog() # create ggwave instance with default parameters instance = ggwave.init() payload = 'hello python' # generate audio waveform for string "hello python" waveform = ggwave.encode(payload, protocolId = 1, volume = 20, instance = instance) # decode the audio waveform back to text res = ggwave.decode(instance, waveform) if res != payload.encode(): sys.exit(1) # disable the Rx protocol - the decoding should fail ggwave.rxToggleProtocol(protocolId = 1, state = 0) instanceTmp = ggwave.init() res = ggwave.decode(instanceTmp, waveform) if res != None: sys.exit(1) ggwave.free(instanceTmp); # re-enable the Rx protocol - the decoding should succeed ggwave.rxToggleProtocol( protocolId = 1, state = 1) instanceTmp = ggwave.init() res = ggwave.decode(instance, waveform) if res != payload.encode(): sys.exit(1) ggwave.free(instanceTmp); ggwave.free(instance);